package main import ( "encoding/json" "fmt" "github.com/blang/semver" log "github.com/sirupsen/logrus" "io" "net/http" "os" "path" "strings" ) func requireAuth(fn http.HandlerFunc) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { key := extractAuthorization(r) if key == "" || key != conf.Http.Key { http.Error(w, "unauthorized", http.StatusForbidden) return } fn(w, r) } } func extractAuthorization(r *http.Request) string { auth := r.Header.Get("Authorization") if auth != "" { idx := strings.Index(auth, " ") if idx == -1 || auth[0:idx] != "Token" { return "" } return auth[idx+1:] } return r.URL.Query().Get("key") } 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, http.StatusBadRequest, "invalid 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" } force := false forceStr := r.URL.Query().Get("force") if forceStr == "true" { force = true } reader, err := r.MultipartReader() if err != nil { httpErrorf(w, http.StatusInternalServerError, "error creating multipart reader: %s", err) return } mutex.RLock() distro, exists := distros[distroName] if !exists { httpErrorf(w, http.StatusBadRequest, "invalid distro: %s", distroName) mutex.RUnlock() return } mutex.RUnlock() // Lock to prevent concurrent modification mutex.Lock() defer mutex.Unlock() modifiedArches := make(map[string]bool) for { part, err := reader.NextPart() if err != nil { break } if part.FileName() == "" || !strings.HasSuffix(part.FileName(), ".deb"){ continue } archType, err := loadAndCheckPackage(distroName, distro, part.FileName(), part, force) if err != nil { httpErrorf(w, http.StatusInternalServerError, err.Error()) return } modifiedArches[archType] = true } // Recreate the package index and release file. for archType, _ := range modifiedArches { log.WithFields(log.Fields{ "arch": archType, }).Debug("Updating package list...") if err := createPackagesCached(conf, distroName, archType, distro.Architectures[archType]); err != nil { httpErrorf(w, http.StatusInternalServerError, "error creating package: %s", err) return } } log.WithFields(log.Fields{ "distroName": distroName, }).Debug("Updating release files...") err = createRelease(conf, distroName) if err != nil { httpErrorf(w, http.StatusInternalServerError, "error creating package: %s", err) return } log.Debug("Dumping cache...") err = saveCache(distro) if err != nil { httpErrorf(w, http.StatusInternalServerError, "error updating cache: %s", err) return } log.Debug("Done.") w.WriteHeader(http.StatusCreated) } func copyToTemp(fileName string, r io.Reader) (string, error) { dst, err := tmpFs.Create(fileName) if err != nil { return "", fmt.Errorf("error creating deb file: %s", err) } defer dst.Close() if _, err := io.Copy(dst, r); err != nil { return "", fmt.Errorf("error creating deb file: %s", err) } return fileName, nil } func loadAndCheckPackage(distroName string, distro *Distro, fileName string, r io.Reader, force bool) (string, error) { tempFile, err := copyToTemp(fileName, r) if err != nil { return "", err } // Get package name, if it already exists remove the old file. f, err := newPackageFile(tmpFs, tempFile) if err != nil { return "", fmt.Errorf("error loading package info: %s", err) } archType := f.Info.Architecture // Get current packages packages, exists := distro.Architectures[archType] if !exists { return "", fmt.Errorf("invalid arch: %s", archType) } // New path based off package data newPath := conf.PoolPackagePath(distroName, archType, f.Info.Package) if _, err := fs.Stat(newPath); err != nil { if os.IsNotExist(err) { if err := fs.MkdirAll(newPath, 0755); err != nil { return "", fmt.Errorf("error creating path: %s", err) } } } f.Path = path.Join(conf.RelativePoolPackagePath(distroName, archType, f.Info.Package), fileName) if err := copyFile(tmpFs, tempFile, fs, path.Join(newPath, fileName)); err != nil { return "", fmt.Errorf("error copying temporary file: %s", err) } if err := tmpFs.Remove(tempFile); err != nil { return "", fmt.Errorf("unable to remove temporary file: %s", err) } 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 { if v1.Compare(v2) > 0 && !force { // Don't replace newer package return "", fmt.Errorf("version in old package is greater than new: %s, %s - override with \"force\"", p.Info.Version, f.Info.Version) } } // Archive old file log.WithFields(log.Fields{ "existing": p.Path, "new": f.Path, }).Debug("Replacing existing file") // If oldPath == newPath then we already overwrote it if path.Base(p.Path) != path.Base(f.Path) { if err := fs.Remove(p.Path); err != nil && !os.IsNotExist(err) { return "", fmt.Errorf("unable to remove old package: %s", err) } } } packages[f.Info.Package] = f return archType, nil } 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, http.StatusBadRequest, "failed to decode json: %s", err) return } mutex.RLock() distro, exists := distros[req.DistroName] if !exists { httpErrorf(w, http.StatusBadRequest, "invalid distro: %s", req.DistroName) mutex.RUnlock() return } packages, exists := distro.Architectures[req.Arch] if !exists { httpErrorf(w, http.StatusBadRequest, "invalid arch: %s", req.Arch) mutex.RUnlock() return } mutex.RUnlock() debPath := path.Join(conf.ArchPath(req.DistroName, req.Arch), req.Filename) if err := fs.Remove(debPath); err != nil { httpErrorf(w, http.StatusInternalServerError, "failed to delete: %s", err) return } mutex.Lock() defer mutex.Unlock() if err := createPackagesCached(conf, req.DistroName, req.Arch, packages); err != nil { httpErrorf(w, http.StatusInternalServerError, "failed to delete package: %s", err) return } if err := createRelease(conf, req.DistroName); err != nil { httpErrorf(w, http.StatusInternalServerError, "failed to delete package: %s", err) return } w.WriteHeader(http.StatusOK) } func httpErrorf(w http.ResponseWriter, code int, format string, a ...interface{}) { err := fmt.Errorf(format, a...) log.Println(err) http.Error(w, err.Error(), code) }