refactor(upload): use IncomingFile interface instead of multipart headers
All checks were successful
Build and Publish Docker Image / deploy (push) Successful in 1m58s

Refactors the upload handler to use the `services.IncomingFile` interface instead of concrete `*multipart.FileHeader` pointers. This decouples the core upload logic from the HTTP multipart implementation, allowing for more flexible file sources.

Changes include:
- Introducing `namedMultipartFile` to adapt multipart headers to the new interface.
- Updating `createOrAppendBox`, `checkUploadPolicy`, and `totalUploadBytes` to accept `IncomingFile`.
- Renaming service calls to `CreateBoxFromIncoming` and `AppendIncomingFiles`.
This commit is contained in:
2026-06-10 18:19:45 +03:00
parent 5d77b36634
commit 6a7590493c
4 changed files with 76 additions and 23 deletions

View File

@@ -4,6 +4,7 @@ import (
"context"
"errors"
"fmt"
"io"
"mime/multipart"
"net/http"
"strconv"
@@ -62,7 +63,7 @@ func (a *App) Upload(w http.ResponseWriter, r *http.Request) {
return
}
files := uploadFiles(r)
files := uploadIncomingFiles(r)
totalBytes := totalUploadBytes(files)
var ownerID string
var collectionID string
@@ -164,7 +165,7 @@ func (a *App) Upload(w http.ResponseWriter, r *http.Request) {
// uploadGroupWindow are folded into one box. Without the header the behaviour is
// identical to creating a fresh box every time. Returns the result and how many
// boxes were created (1 for a new box, 0 for an append) for usage accounting.
func (a *App) createOrAppendBox(r *http.Request, user services.User, loggedIn bool, policy services.EffectiveUploadPolicy, files []*multipart.FileHeader, opts services.UploadOptions, enforceBoxLimits bool) (services.UploadResult, int, int, string, error) {
func (a *App) createOrAppendBox(r *http.Request, user services.User, loggedIn bool, policy services.EffectiveUploadPolicy, files []services.IncomingFile, opts services.UploadOptions, enforceBoxLimits bool) (services.UploadResult, int, int, string, error) {
batch := strings.TrimSpace(r.Header.Get(uploadBatchHeader))
if batch == "" {
if enforceBoxLimits {
@@ -172,7 +173,7 @@ func (a *App) createOrAppendBox(r *http.Request, user services.User, loggedIn bo
return services.UploadResult{}, 0, status, message, nil
}
}
result, err := a.uploadService.CreateBox(files, opts)
result, err := a.uploadService.CreateBoxFromIncoming(files, opts)
if err != nil {
return services.UploadResult{}, 0, 0, "", err
}
@@ -193,7 +194,7 @@ func (a *App) createOrAppendBox(r *http.Request, user services.User, loggedIn bo
if entry.boxID != "" && time.Since(entry.at) < uploadGroupWindow {
if box, err := a.uploadService.GetBox(entry.boxID); err == nil && a.batchBoxMatches(box, user, loggedIn, r) && a.uploadService.CanDownload(box) == nil {
if result, err := a.uploadService.AppendFiles(entry.boxID, files, opts); err == nil {
if result, err := a.uploadService.AppendIncomingFiles(entry.boxID, files, opts); err == nil {
// Re-attach the manage/delete URLs from the box's creation so every
// upload in the batch returns a working deletion URL.
result.ManageURL = entry.manageURL
@@ -209,7 +210,7 @@ func (a *App) createOrAppendBox(r *http.Request, user services.User, loggedIn bo
return services.UploadResult{}, 0, status, message, nil
}
}
result, err := a.uploadService.CreateBox(files, opts)
result, err := a.uploadService.CreateBoxFromIncoming(files, opts)
if err != nil {
return services.UploadResult{}, 0, 0, "", err
}
@@ -229,13 +230,13 @@ func (a *App) batchBoxMatches(box services.Box, user services.User, loggedIn boo
return box.OwnerID == "" && box.CreatorIP == uploadClientIP(r)
}
func (a *App) checkUploadPolicy(r *http.Request, user services.User, loggedIn bool, settings services.UploadPolicySettings, policy services.EffectiveUploadPolicy, files []*multipart.FileHeader, totalBytes int64) (int, string) {
func (a *App) checkUploadPolicy(r *http.Request, user services.User, loggedIn bool, settings services.UploadPolicySettings, policy services.EffectiveUploadPolicy, files []services.IncomingFile, totalBytes int64) (int, string) {
if len(files) == 0 {
return 0, ""
}
sizes := make([]int64, 0, len(files))
for _, file := range files {
sizes = append(sizes, file.Size)
sizes = append(sizes, file.Size())
}
return a.checkUploadPolicyForSizes(r, user, loggedIn, settings, policy, sizes, totalBytes)
}
@@ -383,10 +384,10 @@ func uploadRateKey(r *http.Request, user services.User, loggedIn bool) string {
return "ip:" + uploadClientIP(r)
}
func totalUploadBytes(files []*multipart.FileHeader) int64 {
func totalUploadBytes(files []services.IncomingFile) int64 {
var total int64
for _, file := range files {
total += file.Size
total += file.Size()
}
return total
}
@@ -409,13 +410,48 @@ func statusForDownloadError(err error) int {
return http.StatusForbidden
}
func uploadFiles(r *http.Request) []*multipart.FileHeader {
type namedMultipartFile struct {
header *multipart.FileHeader
name string
}
func (f namedMultipartFile) Name() string {
if strings.TrimSpace(f.name) != "" {
return f.name
}
return f.header.Filename
}
func (f namedMultipartFile) Size() int64 {
return f.header.Size
}
func (f namedMultipartFile) ContentType() string {
return f.header.Header.Get("Content-Type")
}
func (f namedMultipartFile) Open() (io.ReadCloser, error) {
return f.header.Open()
}
func uploadIncomingFiles(r *http.Request) []services.IncomingFile {
if r.MultipartForm == nil {
return nil
}
files := make([]*multipart.FileHeader, 0)
files = append(files, r.MultipartForm.File["file"]...)
files = append(files, r.MultipartForm.File["sharex"]...)
fileHeaders := r.MultipartForm.File["file"]
shareXHeaders := r.MultipartForm.File["sharex"]
paths := r.MultipartForm.Value["file_path"]
files := make([]services.IncomingFile, 0, len(fileHeaders)+len(shareXHeaders))
for index, header := range fileHeaders {
name := ""
if index < len(paths) {
name = paths[index]
}
files = append(files, namedMultipartFile{header: header, name: name})
}
for _, header := range shareXHeaders {
files = append(files, namedMultipartFile{header: header})
}
return files
}