package main import ( "encoding/json" "fmt" "github.com/blang/semver" "io" "log" "net/http" "os" "path" "strings" ) func rescanHandler(w http.ResponseWriter, r *http.Request) { mutex.Lock() defer mutex.Unlock() distroName := r.URL.Query().Get("distro") if distroName == "" { distroName = "stable" } if _, exists := distros[distroName]; !exists { httpErrorf(w, "Unable to find distro %s", distroName) return } distro := &Distro{Name: distroName, Architectures: make(map[string]map[string]*PackageFile)} scanInitialPackages(conf, distro) saveCache(distro) distros[distroName] = distro w.WriteHeader(http.StatusOK) w.Write([]byte("ok")) } func uploadHandler(w http.ResponseWriter, r *http.Request) { if r.Method != "POST" { http.Error(w, "method not supported", http.StatusMethodNotAllowed) return } queryArchType := r.URL.Query().Get("arch") if queryArchType == "" { queryArchType = "all" } distroName := r.URL.Query().Get("distro") if distroName == "" { distroName = "stable" } key := r.URL.Query().Get("key") if key == "" || key != conf.Http.Key { http.Error(w, "unauthorized", 403) return } force := false forceStr := r.URL.Query().Get("force") if forceStr == "true" { force = true } reader, err := r.MultipartReader() if err != nil { httpErrorf(w, "error creating multipart reader: %s", err) return } mutex.RLock() distro, exists := distros[distroName] if !exists { httpErrorf(w, "invalid distro: %s", distroName) mutex.RUnlock() return } mutex.RUnlock() // Lock to prevent concurrent modification mutex.Lock() defer mutex.Unlock() modifiedArches := make(map[string]bool) baseDir := path.Join(os.TempDir(), "deb-simple") if _, err := os.Stat(baseDir); err != nil { if os.IsNotExist(err) { if err := os.MkdirAll(baseDir, 0755); err != nil { httpErrorf(w, "error creating path: %s", err) return } } } for { part, err := reader.NextPart() if err == io.EOF { break } if part.FileName() == "" || !strings.HasSuffix(part.FileName(), ".deb"){ continue } tempFile := path.Join(baseDir, part.FileName()) dst, err := os.Create(tempFile) if err != nil { httpErrorf(w, "error creating deb file: %s", err) return } if _, err := io.Copy(dst, part); err != nil { dst.Close() httpErrorf(w, "error writing deb file: %s", err) return } dst.Close() // Get package name, if it already exists remove the old file. f, err := newPackageFile(tempFile) if err != nil { httpErrorf(w, "error loading package info: %s", err) } archType := f.Info.Architecture if archType == "" { archType = queryArchType } modifiedArches[archType] = true // Get current packages packages, exists := distro.Architectures[archType] if !exists { httpErrorf(w, "invalid arch: %s", archType) continue } // New path based off package data newPath := conf.PoolPackagePath(distroName, archType, f.Info.Package) if _, err := os.Stat(newPath); err != nil { if os.IsNotExist(err) { if err := os.MkdirAll(newPath, 0755); err != nil { httpErrorf(w, "error creating path: %s", err) continue } } } f.Path = path.Join(conf.RelativePoolPackagePath(distroName, archType, f.Info.Package), part.FileName()) if err := copyFile(tempFile, path.Join(newPath, part.FileName())); err != nil { httpErrorf(w, "error copying temporary file: %s", err) continue } if err := os.Remove(tempFile); err != nil { httpErrorf(w, "unable to remove temporary file: %s", err) continue } if p, exists := packages[f.Info.Package]; exists { v1, err1 := semver.Parse(p.Info.Version) v2, err2 := semver.Parse(f.Info.Version) if err1 == nil && err2 == nil && v1.Compare(v2) > 0 && !force { // Don't replace newer package httpErrorf(w, "version in old package is greater than new: %s, %s - override with \"force\"", p.Info.Version, f.Info.Version) continue } // Archive old file log.Println("Replacing", p.Name, "with", f.Name) oldPath := path.Join(conf.Repo.Root, p.Path) // If oldPath == newPath then we already overwrote it if oldPath != newPath { if err := os.Remove(oldPath); err != nil && !os.IsNotExist(err) { httpErrorf(w, "Unable to remove old package: %s", err) continue } } } packages[f.Info.Package] = f } log.Println("got lock, updating package list...") // Recreate the package index and release file. for archType, _ := range modifiedArches { if err := createPackagesCached(conf, distroName, archType, distro.Architectures[archType]); err != nil { httpErrorf(w, "error creating package: %s", err) return } } err = createRelease(conf, distroName) if err != nil { httpErrorf(w, "error creating package: %s", err) return } err = saveCache(distro) if err != nil { httpErrorf(w, "error updating cache: %s", err) return } w.WriteHeader(http.StatusCreated) } func deleteHandler(w http.ResponseWriter, r *http.Request) { if r.Method != "DELETE" { http.Error(w, "method not supported", http.StatusMethodNotAllowed) return } var req DeleteObj if err := json.NewDecoder(r.Body).Decode(&req); err != nil { httpErrorf(w, "failed to decode json: %s", err) return } mutex.RLock() distro, exists := distros[req.DistroName] if !exists { httpErrorf(w, "invalid distro: %s", req.DistroName) mutex.RUnlock() return } packages, exists := distro.Architectures[req.Arch] if !exists { httpErrorf(w, "invalid arch: %s", req.Arch) mutex.RUnlock() return } mutex.RUnlock() key := r.URL.Query().Get("key") if key == "" || key != conf.Http.Key { http.Error(w, "unauthorized", http.StatusForbidden) return } debPath := path.Join(conf.ArchPath(req.DistroName, req.Arch), req.Filename) if err := os.Remove(debPath); err != nil { httpErrorf(w, "failed to delete: %s", err) return } mutex.Lock() defer mutex.Unlock() if err := createPackagesCached(conf, req.DistroName, req.Arch, packages); err != nil { httpErrorf(w, "failed to delete package: %s", err) return } if err := createRelease(conf, req.DistroName); err != nil { httpErrorf(w, "failed to delete package: %s", err) return } w.WriteHeader(http.StatusOK) } func httpErrorf(w http.ResponseWriter, format string, a ...interface{}) { err := fmt.Errorf(format, a...) log.Println(err) http.Error(w, err.Error(), http.StatusInternalServerError) }