package api import ( "fmt" "io" "os" "path/filepath" "strings" "sync" "github.com/aptly-dev/aptly/utils" "github.com/gin-gonic/gin" "github.com/saracen/walker" ) func verifyPath(path string) bool { path = filepath.Clean(path) for _, part := range strings.Split(path, string(filepath.Separator)) { if part == ".." || part == "." { return false } } return true } func verifyDir(c *gin.Context) bool { if !verifyPath(c.Params.ByName("dir")) { AbortWithJSONError(c, 400, fmt.Errorf("wrong dir")) return false } return true } // @Summary List Directories // @Description **Get list of upload directories** // @Description // @Description **Example:** // @Description ``` // @Description $ curl http://localhost:8080/api/files // @Description ["aptly-0.9"] // @Description ``` // @Tags Files // @Produce json // @Success 200 {array} string "List of files" // @Router /api/files [get] func apiFilesListDirs(c *gin.Context) { list := []string{} listLock := &sync.Mutex{} err := walker.Walk(context.UploadPath(), func(path string, info os.FileInfo) error { if path == context.UploadPath() { return nil } if info.IsDir() { listLock.Lock() defer listLock.Unlock() list = append(list, filepath.Base(path)) return filepath.SkipDir } return nil }) if err != nil && !os.IsNotExist(err) { AbortWithJSONError(c, 400, err) return } c.JSON(200, list) } // @Summary Upload Files // @Description **Upload files to a directory** // @Description // @Description - one or more files can be uploaded // @Description - existing uploaded are overwritten // @Description // @Description **Example:** // @Description ``` // @Description $ curl -X POST -F file=@aptly_0.9~dev+217+ge5d646c_i386.deb http://localhost:8080/api/files/aptly-0.9 // @Description ["aptly-0.9/aptly_0.9~dev+217+ge5d646c_i386.deb"] // @Description ``` // @Tags Files // @Accept multipart/form-data // @Param dir path string true "Directory to upload files to. Created if does not exist" // @Param files formData file true "Files to upload" // @Produce json // @Success 200 {array} string "list of uploaded files" // @Failure 400 {object} Error "Bad Request" // @Failure 404 {object} Error "Not Found" // @Failure 500 {object} Error "Internal Server Error" // @Router /api/files/{dir} [post] func apiFilesUpload(c *gin.Context) { if !verifyDir(c) { return } path := filepath.Join(context.UploadPath(), utils.SanitizePath(c.Params.ByName("dir"))) err := os.MkdirAll(path, 0777) if err != nil { AbortWithJSONError(c, 500, err) return } err = c.Request.ParseMultipartForm(10 * 1024 * 1024) if err != nil { AbortWithJSONError(c, 400, err) return } stored := []string{} for _, files := range c.Request.MultipartForm.File { for _, file := range files { src, err := file.Open() if err != nil { AbortWithJSONError(c, 500, err) return } defer func() { _ = src.Close() }() destPath := filepath.Join(path, filepath.Base(file.Filename)) dst, err := os.Create(destPath) if err != nil { AbortWithJSONError(c, 500, err) return } defer func() { _ = dst.Close() }() _, err = io.Copy(dst, src) if err != nil { AbortWithJSONError(c, 500, err) return } stored = append(stored, filepath.Join(c.Params.ByName("dir"), filepath.Base(file.Filename))) } } apiFilesUploadedCounter.WithLabelValues(c.Params.ByName("dir")).Inc() c.JSON(200, stored) } // @Summary List Files // @Description **Show uploaded files in upload directory** // @Description // @Description **Example:** // @Description ``` // @Description $ curl http://localhost:8080/api/files/aptly-0.9 // @Description ["aptly_0.9~dev+217+ge5d646c_i386.deb"] // @Description ``` // @Tags Files // @Produce json // @Param dir path string true "Directory to list" // @Success 200 {array} string "Files found in directory" // @Failure 404 {object} Error "Not Found" // @Failure 500 {object} Error "Internal Server Error" // @Router /api/files/{dir} [get] func apiFilesListFiles(c *gin.Context) { if !verifyDir(c) { return } list := []string{} listLock := &sync.Mutex{} root := filepath.Join(context.UploadPath(), utils.SanitizePath(c.Params.ByName("dir"))) err := filepath.Walk(root, func(path string, _ os.FileInfo, err error) error { if err != nil { return err } if path == root { return nil } listLock.Lock() defer listLock.Unlock() list = append(list, filepath.Base(path)) return nil }) if err != nil { if os.IsNotExist(err) { AbortWithJSONError(c, 404, err) } else { AbortWithJSONError(c, 500, err) } return } c.JSON(200, list) } // @Summary Delete Directory // @Description **Delete upload directory and uploaded files within** // @Description // @Description **Example:** // @Description ``` // @Description $ curl -X DELETE http://localhost:8080/api/files/aptly-0.9 // @Description {} // @Description ``` // @Tags Files // @Produce json // @Param dir path string true "Directory" // @Success 200 {object} string "msg" // @Failure 500 {object} Error "Internal Server Error" // @Router /api/files/{dir} [delete] func apiFilesDeleteDir(c *gin.Context) { if !verifyDir(c) { return } err := os.RemoveAll(filepath.Join(context.UploadPath(), utils.SanitizePath(c.Params.ByName("dir")))) if err != nil { AbortWithJSONError(c, 500, err) return } c.JSON(200, gin.H{}) } // @Summary Delete File // @Description **Delete a uploaded file in upload directory** // @Description // @Description **Example:** // @Description ``` // @Description $ curl -X DELETE http://localhost:8080/api/files/aptly-0.9/aptly_0.9~dev+217+ge5d646c_i386.deb // @Description {} // @Description ``` // @Tags Files // @Produce json // @Param dir path string true "Directory to delete from" // @Param name path string true "File to delete" // @Success 200 {object} string "msg" // @Failure 400 {object} Error "Bad Request" // @Failure 500 {object} Error "Internal Server Error" // @Router /api/files/{dir}/{name} [delete] func apiFilesDeleteFile(c *gin.Context) { if !verifyDir(c) { return } dir := utils.SanitizePath(c.Params.ByName("dir")) name := utils.SanitizePath(c.Params.ByName("name")) if !verifyPath(name) { AbortWithJSONError(c, 400, fmt.Errorf("wrong file")) return } err := os.Remove(filepath.Join(context.UploadPath(), dir, name)) if err != nil { if err1, ok := err.(*os.PathError); !ok || !os.IsNotExist(err1.Err) { AbortWithJSONError(c, 500, err) return } } c.JSON(200, gin.H{}) }