From 0cb078ecc058a2e2f5ffe8b26768387129a71f45 Mon Sep 17 00:00:00 2001 From: Haruko386 Date: Tue, 4 Aug 2026 18:01:04 +0800 Subject: [PATCH] fix: can upload file larger than 10MB (#17801) --- internal/common/environments.go | 1 + internal/handler/file.go | 8 +++++- internal/service/file/file_test.go | 32 +++++++++++++++++++++++ internal/service/file/file_upload.go | 38 ++++++++++++++++++++++++++-- 4 files changed, 76 insertions(+), 3 deletions(-) diff --git a/internal/common/environments.go b/internal/common/environments.go index ecf3d2855f..2699253f00 100644 --- a/internal/common/environments.go +++ b/internal/common/environments.go @@ -37,6 +37,7 @@ const ( EnvComponentExecTimeout = "COMPONENT_EXEC_TIMEOUT" EnvDocEngine = "DOC_ENGINE" EnvMaxFileNumPerUser = "MAX_FILE_NUM_PER_USER" + EnvMaxContentLength = "MAX_CONTENT_LENGTH" EnvRAGFlowDictPath = "RAGFLOW_DICT_PATH" EnvDefaultSuperuserEmail = "DEFAULT_SUPERUSER_EMAIL" EnvDefaultSuperuserNickname = "DEFAULT_SUPERUSER_NICKNAME" diff --git a/internal/handler/file.go b/internal/handler/file.go index bd3fb29ee4..7d42ad39e0 100644 --- a/internal/handler/file.go +++ b/internal/handler/file.go @@ -279,6 +279,12 @@ func (h *FileHandler) UploadFile(c *gin.Context) { ctx := c.Request.Context() if strings.Contains(contentType, "multipart/form-data") { + uploadLimit := file.DeploymentUploadMaxBytes() + c.Request.Body = http.MaxBytesReader(c.Writer, c.Request.Body, uploadLimit) + if cl := c.Request.ContentLength; cl > uploadLimit { + common.ResponseWithCodeData(c, common.CodeBadRequest, nil, "request body exceeds deployment upload limit") + return + } if err := c.Request.ParseMultipartForm(32 << 20); err != nil { common.ResponseWithCodeData(c, common.CodeBadRequest, nil, "Failed to parse multipart form: "+err.Error()) return @@ -313,7 +319,7 @@ func (h *FileHandler) UploadFile(c *gin.Context) { } ctx := c.Request.Context() - result, err := h.fileService.UploadFile(ctx, userID, parentID, files) + result, err := h.fileService.UploadFile(ctx, userID, parentID, files, uploadLimit) if err != nil { common.ErrorWithCode(c, common.CodeBadRequest, err.Error()) return diff --git a/internal/service/file/file_test.go b/internal/service/file/file_test.go index 97626a06fc..055354f21c 100644 --- a/internal/service/file/file_test.go +++ b/internal/service/file/file_test.go @@ -45,6 +45,38 @@ func testFileService() *FileService { } } +func TestDeploymentUploadMaxBytes(t *testing.T) { + t.Setenv("MAX_CONTENT_LENGTH", "") + if got := DeploymentUploadMaxBytes(); got != defaultDeploymentUploadMaxBytes { + t.Fatalf("default limit = %d, want %d", got, defaultDeploymentUploadMaxBytes) + } + + t.Setenv("MAX_CONTENT_LENGTH", "12345") + if got := DeploymentUploadMaxBytes(); got != 12345 { + t.Fatalf("configured limit = %d, want 12345", got) + } + + t.Setenv("MAX_CONTENT_LENGTH", "invalid") + if got := DeploymentUploadMaxBytes(); got != defaultDeploymentUploadMaxBytes { + t.Fatalf("invalid limit = %d, want default %d", got, defaultDeploymentUploadMaxBytes) + } +} + +func TestReadDeploymentUploadDataLimit(t *testing.T) { + data, err := readDeploymentUploadData(strings.NewReader("hello"), 5) + if err != nil { + t.Fatalf("readDeploymentUploadData exact limit: %v", err) + } + if string(data) != "hello" { + t.Fatalf("data = %q, want hello", string(data)) + } + + _, err = readDeploymentUploadData(strings.NewReader("hello!"), 5) + if err == nil || !strings.Contains(err.Error(), "deployment upload limit of 5 bytes") { + t.Fatalf("error = %v, want deployment upload limit", err) + } +} + func (f *fakeStorage) Type() string { return "fake_storage" } func (f *fakeStorage) Health(ctx context.Context) bool { diff --git a/internal/service/file/file_upload.go b/internal/service/file/file_upload.go index 40ad428038..0c96f8022b 100644 --- a/internal/service/file/file_upload.go +++ b/internal/service/file/file_upload.go @@ -11,12 +11,27 @@ import ( "ragflow/internal/entity" "ragflow/internal/storage" "ragflow/internal/utility" + "strconv" "strings" "time" ) +const defaultDeploymentUploadMaxBytes int64 = 1 << 30 + +func DeploymentUploadMaxBytes() int64 { + raw := common.GetEnv(common.EnvMaxContentLength) + if raw == "" { + return defaultDeploymentUploadMaxBytes + } + n, err := strconv.ParseInt(raw, 10, 64) + if err != nil || n <= 0 { + return defaultDeploymentUploadMaxBytes + } + return n +} + // UploadFile uploads files to a folder -func (s *FileService) UploadFile(ctx context.Context, tenantID, parentID string, files []*multipart.FileHeader) ([]map[string]interface{}, error) { +func (s *FileService) UploadFile(ctx context.Context, tenantID, parentID string, files []*multipart.FileHeader, maxBytes int64) ([]map[string]interface{}, error) { if parentID == "" { rootFolder, err := s.fileDAO.GetRootFolder(ctx, dao.DB, tenantID) if err != nil { @@ -58,6 +73,10 @@ func (s *FileService) UploadFile(ctx context.Context, tenantID, parentID string, return nil, fmt.Errorf("no file selected") } + if maxBytes > 0 && fileHeader.Size > maxBytes { + return nil, fmt.Errorf("file %s exceeds deployment upload limit of %d bytes", filename, maxBytes) + } + fileType := utility.FilenameType(filename) fileObjNames := s.parseFilePath(filename) @@ -101,7 +120,7 @@ func (s *FileService) UploadFile(ctx context.Context, tenantID, parentID string, } var data []byte - data, err = io.ReadAll(src) + data, err = readDeploymentUploadData(src, maxBytes) src.Close() if err != nil { return nil, fmt.Errorf("failed to read file data: %w", err) @@ -139,6 +158,21 @@ func (s *FileService) UploadFile(ctx context.Context, tenantID, parentID string, return result, nil } +func readDeploymentUploadData(r io.Reader, maxBytes int64) ([]byte, error) { + if maxBytes <= 0 { + return io.ReadAll(r) + } + limited := &io.LimitedReader{R: r, N: maxBytes + 1} + data, err := io.ReadAll(limited) + if err != nil { + return nil, err + } + if int64(len(data)) > maxBytes { + return nil, fmt.Errorf("file size exceeds deployment upload limit of %d bytes", maxBytes) + } + return data, nil +} + // UploadInfos mirrors Python's upload_info file branch: store raw bytes in the // per-user downloads bucket and return lightweight upload descriptors instead // of creating full File rows in the file-management tree.