Remove client MIME type parameter from Upload
- Fix: Always detect MIME type server-side from file content instead of trusting or forwarding the client-supplied value.
This commit is contained in:
@@ -4,6 +4,7 @@ import (
|
|||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
|
"mime"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
"net/url"
|
||||||
"strconv"
|
"strconv"
|
||||||
@@ -44,7 +45,8 @@ func (h *FileHandler) Upload(c *gin.Context) {
|
|||||||
contentType := c.GetHeader("Content-Type")
|
contentType := c.GetHeader("Content-Type")
|
||||||
|
|
||||||
// Directory creation via JSON.
|
// Directory creation via JSON.
|
||||||
if len(contentType) >= 16 && contentType[:16] == "application/json" {
|
mediaType, _, _ := mime.ParseMediaType(contentType)
|
||||||
|
if mediaType == "application/json" {
|
||||||
var req createDirRequest
|
var req createDirRequest
|
||||||
if err := c.ShouldBindJSON(&req); err != nil {
|
if err := c.ShouldBindJSON(&req); err != nil {
|
||||||
api.Error(c, http.StatusBadRequest, "invalid request: "+err.Error())
|
api.Error(c, http.StatusBadRequest, "invalid request: "+err.Error())
|
||||||
@@ -86,7 +88,7 @@ func (h *FileHandler) Upload(c *gin.Context) {
|
|||||||
}
|
}
|
||||||
defer file.Close()
|
defer file.Close()
|
||||||
|
|
||||||
info, err := h.fileService.Upload(c.Request.Context(), userID, parentID, header.Filename, file, header.Header.Get("Content-Type"))
|
info, err := h.fileService.Upload(c.Request.Context(), userID, parentID, header.Filename, file)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
api.Error(c, mapError(err), err.Error())
|
api.Error(c, mapError(err), err.Error())
|
||||||
return
|
return
|
||||||
|
|||||||
@@ -60,7 +60,8 @@ func NewFileService(
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Upload stores a file's content and creates its metadata record.
|
// Upload stores a file's content and creates its metadata record.
|
||||||
func (s *FileService) Upload(ctx context.Context, userID string, parentID *string, fileName string, reader io.Reader, clientMime string) (*FileInfo, error) {
|
// The MIME type is always detected server-side from the file content.
|
||||||
|
func (s *FileService) Upload(ctx context.Context, userID string, parentID *string, fileName string, reader io.Reader) (*FileInfo, error) {
|
||||||
if err := validateFileName(fileName); err != nil {
|
if err := validateFileName(fileName); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -83,19 +84,15 @@ func (s *FileService) Upload(ctx context.Context, userID string, parentID *strin
|
|||||||
reader = io.LimitReader(reader, s.maxUploadSize+1)
|
reader = io.LimitReader(reader, s.maxUploadSize+1)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Detect MIME type when the client does not provide a meaningful one.
|
// Detect MIME type from file content.
|
||||||
mimeType := clientMime
|
|
||||||
if mimeType == "" || mimeType == "application/octet-stream" {
|
|
||||||
// Read the first 512 bytes for detection, then replay them.
|
|
||||||
head := make([]byte, 512)
|
head := make([]byte, 512)
|
||||||
n, readErr := io.ReadFull(reader, head)
|
n, readErr := io.ReadFull(reader, head)
|
||||||
if readErr != nil && readErr != io.ErrUnexpectedEOF && readErr != io.EOF {
|
if readErr != nil && readErr != io.ErrUnexpectedEOF && readErr != io.EOF {
|
||||||
return nil, fmt.Errorf("read for mime detection: %w", readErr)
|
return nil, fmt.Errorf("read for mime detection: %w", readErr)
|
||||||
}
|
}
|
||||||
head = head[:n]
|
head = head[:n]
|
||||||
mimeType = mimetype.Detect(head).String()
|
mimeType := mimetype.Detect(head).String()
|
||||||
reader = io.MultiReader(bytes.NewReader(head), reader)
|
reader = io.MultiReader(bytes.NewReader(head), reader)
|
||||||
}
|
|
||||||
|
|
||||||
fileID := uuid.NewString()
|
fileID := uuid.NewString()
|
||||||
storagePath := fmt.Sprintf("%s/%s", userID, fileID)
|
storagePath := fmt.Sprintf("%s/%s", userID, fileID)
|
||||||
|
|||||||
@@ -77,7 +77,7 @@ func TestFileService_Upload(t *testing.T) {
|
|||||||
svc := setupFileService(t)
|
svc := setupFileService(t)
|
||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
|
|
||||||
info, err := svc.Upload(ctx, "user1", nil, "hello.txt", strings.NewReader("hello world"), "")
|
info, err := svc.Upload(ctx, "user1", nil, "hello.txt", strings.NewReader("hello world"))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("Upload = %v", err)
|
t.Fatalf("Upload = %v", err)
|
||||||
}
|
}
|
||||||
@@ -112,7 +112,7 @@ func TestFileService_UploadIntoDirectory(t *testing.T) {
|
|||||||
t.Fatalf("CreateDir = %v", err)
|
t.Fatalf("CreateDir = %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
info, err := svc.Upload(ctx, "user1", &dir.ID, "readme.txt", strings.NewReader("README"), "")
|
info, err := svc.Upload(ctx, "user1", &dir.ID, "readme.txt", strings.NewReader("README"))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("Upload = %v", err)
|
t.Fatalf("Upload = %v", err)
|
||||||
}
|
}
|
||||||
@@ -127,7 +127,7 @@ func TestFileService_UploadInvalidName(t *testing.T) {
|
|||||||
|
|
||||||
invalidNames := []string{"", "foo/bar", "foo\\bar", ".", "..", "foo\x00bar"}
|
invalidNames := []string{"", "foo/bar", "foo\\bar", ".", "..", "foo\x00bar"}
|
||||||
for _, name := range invalidNames {
|
for _, name := range invalidNames {
|
||||||
_, err := svc.Upload(ctx, "user1", nil, name, strings.NewReader("data"), "")
|
_, err := svc.Upload(ctx, "user1", nil, name, strings.NewReader("data"))
|
||||||
if err == nil {
|
if err == nil {
|
||||||
t.Errorf("expected error for name %q, got nil", name)
|
t.Errorf("expected error for name %q, got nil", name)
|
||||||
}
|
}
|
||||||
@@ -138,12 +138,12 @@ func TestFileService_UploadDuplicateName(t *testing.T) {
|
|||||||
svc := setupFileService(t)
|
svc := setupFileService(t)
|
||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
|
|
||||||
_, err := svc.Upload(ctx, "user1", nil, "file.txt", strings.NewReader("first"), "")
|
_, err := svc.Upload(ctx, "user1", nil, "file.txt", strings.NewReader("first"))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("first Upload = %v", err)
|
t.Fatalf("first Upload = %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
_, err = svc.Upload(ctx, "user1", nil, "file.txt", strings.NewReader("second"), "")
|
_, err = svc.Upload(ctx, "user1", nil, "file.txt", strings.NewReader("second"))
|
||||||
if err == nil {
|
if err == nil {
|
||||||
t.Fatal("expected duplicate name error, got nil")
|
t.Fatal("expected duplicate name error, got nil")
|
||||||
}
|
}
|
||||||
@@ -154,7 +154,7 @@ func TestFileService_UploadNonexistentParent(t *testing.T) {
|
|||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
|
|
||||||
fakeParentID := "nonexistent-id"
|
fakeParentID := "nonexistent-id"
|
||||||
_, err := svc.Upload(ctx, "user1", &fakeParentID, "file.txt", strings.NewReader("data"), "")
|
_, err := svc.Upload(ctx, "user1", &fakeParentID, "file.txt", strings.NewReader("data"))
|
||||||
if err == nil {
|
if err == nil {
|
||||||
t.Fatal("expected error for nonexistent parent, got nil")
|
t.Fatal("expected error for nonexistent parent, got nil")
|
||||||
}
|
}
|
||||||
@@ -164,12 +164,12 @@ func TestFileService_UploadParentNotDirectory(t *testing.T) {
|
|||||||
svc := setupFileService(t)
|
svc := setupFileService(t)
|
||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
|
|
||||||
file, err := svc.Upload(ctx, "user1", nil, "not-a-dir.txt", strings.NewReader("data"), "")
|
file, err := svc.Upload(ctx, "user1", nil, "not-a-dir.txt", strings.NewReader("data"))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("Upload = %v", err)
|
t.Fatalf("Upload = %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
_, err = svc.Upload(ctx, "user1", &file.ID, "child.txt", strings.NewReader("data"), "")
|
_, err = svc.Upload(ctx, "user1", &file.ID, "child.txt", strings.NewReader("data"))
|
||||||
if err == nil {
|
if err == nil {
|
||||||
t.Fatal("expected error for non-directory parent, got nil")
|
t.Fatal("expected error for non-directory parent, got nil")
|
||||||
}
|
}
|
||||||
@@ -180,7 +180,7 @@ func TestFileService_Download(t *testing.T) {
|
|||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
|
|
||||||
content := "download test content"
|
content := "download test content"
|
||||||
info, err := svc.Upload(ctx, "user1", nil, "download.txt", strings.NewReader(content), "")
|
info, err := svc.Upload(ctx, "user1", nil, "download.txt", strings.NewReader(content))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("Upload = %v", err)
|
t.Fatalf("Upload = %v", err)
|
||||||
}
|
}
|
||||||
@@ -208,7 +208,7 @@ func TestFileService_DownloadForbidden(t *testing.T) {
|
|||||||
svc := setupFileService(t)
|
svc := setupFileService(t)
|
||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
|
|
||||||
info, err := svc.Upload(ctx, "user1", nil, "file.txt", strings.NewReader("data"), "")
|
info, err := svc.Upload(ctx, "user1", nil, "file.txt", strings.NewReader("data"))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("Upload = %v", err)
|
t.Fatalf("Upload = %v", err)
|
||||||
}
|
}
|
||||||
@@ -238,7 +238,7 @@ func TestFileService_Get(t *testing.T) {
|
|||||||
svc := setupFileService(t)
|
svc := setupFileService(t)
|
||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
|
|
||||||
created, err := svc.Upload(ctx, "user1", nil, "info.txt", strings.NewReader("data"), "")
|
created, err := svc.Upload(ctx, "user1", nil, "info.txt", strings.NewReader("data"))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("Upload = %v", err)
|
t.Fatalf("Upload = %v", err)
|
||||||
}
|
}
|
||||||
@@ -266,7 +266,7 @@ func TestFileService_GetForbidden(t *testing.T) {
|
|||||||
svc := setupFileService(t)
|
svc := setupFileService(t)
|
||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
|
|
||||||
info, err := svc.Upload(ctx, "user1", nil, "file.txt", strings.NewReader("data"), "")
|
info, err := svc.Upload(ctx, "user1", nil, "file.txt", strings.NewReader("data"))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("Upload = %v", err)
|
t.Fatalf("Upload = %v", err)
|
||||||
}
|
}
|
||||||
@@ -285,11 +285,11 @@ func TestFileService_List(t *testing.T) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("CreateDir = %v", err)
|
t.Fatalf("CreateDir = %v", err)
|
||||||
}
|
}
|
||||||
_, err = svc.Upload(ctx, "user1", nil, "root.txt", strings.NewReader("root"), "")
|
_, err = svc.Upload(ctx, "user1", nil, "root.txt", strings.NewReader("root"))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("Upload root = %v", err)
|
t.Fatalf("Upload root = %v", err)
|
||||||
}
|
}
|
||||||
_, err = svc.Upload(ctx, "user1", &dir.ID, "nested.txt", strings.NewReader("nested"), "")
|
_, err = svc.Upload(ctx, "user1", &dir.ID, "nested.txt", strings.NewReader("nested"))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("Upload nested = %v", err)
|
t.Fatalf("Upload nested = %v", err)
|
||||||
}
|
}
|
||||||
@@ -317,7 +317,7 @@ func TestFileService_Update(t *testing.T) {
|
|||||||
svc := setupFileService(t)
|
svc := setupFileService(t)
|
||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
|
|
||||||
info, err := svc.Upload(ctx, "user1", nil, "oldname.txt", strings.NewReader("data"), "")
|
info, err := svc.Upload(ctx, "user1", nil, "oldname.txt", strings.NewReader("data"))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("Upload = %v", err)
|
t.Fatalf("Upload = %v", err)
|
||||||
}
|
}
|
||||||
@@ -344,7 +344,7 @@ func TestFileService_UpdateMove(t *testing.T) {
|
|||||||
t.Fatalf("CreateDir 2 = %v", err)
|
t.Fatalf("CreateDir 2 = %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
info, err := svc.Upload(ctx, "user1", &dir1.ID, "move.txt", strings.NewReader("data"), "")
|
info, err := svc.Upload(ctx, "user1", &dir1.ID, "move.txt", strings.NewReader("data"))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("Upload = %v", err)
|
t.Fatalf("Upload = %v", err)
|
||||||
}
|
}
|
||||||
@@ -362,7 +362,7 @@ func TestFileService_Delete(t *testing.T) {
|
|||||||
svc := setupFileService(t)
|
svc := setupFileService(t)
|
||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
|
|
||||||
info, err := svc.Upload(ctx, "user1", nil, "delete-me.txt", strings.NewReader("data"), "")
|
info, err := svc.Upload(ctx, "user1", nil, "delete-me.txt", strings.NewReader("data"))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("Upload = %v", err)
|
t.Fatalf("Upload = %v", err)
|
||||||
}
|
}
|
||||||
@@ -385,7 +385,7 @@ func TestFileService_DeleteNonEmptyDirectory(t *testing.T) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("CreateDir = %v", err)
|
t.Fatalf("CreateDir = %v", err)
|
||||||
}
|
}
|
||||||
_, err = svc.Upload(ctx, "user1", &dir.ID, "child.txt", strings.NewReader("data"), "")
|
_, err = svc.Upload(ctx, "user1", &dir.ID, "child.txt", strings.NewReader("data"))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("Upload = %v", err)
|
t.Fatalf("Upload = %v", err)
|
||||||
}
|
}
|
||||||
@@ -400,7 +400,7 @@ func TestFileService_DeleteForbidden(t *testing.T) {
|
|||||||
svc := setupFileService(t)
|
svc := setupFileService(t)
|
||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
|
|
||||||
info, err := svc.Upload(ctx, "user1", nil, "file.txt", strings.NewReader("data"), "")
|
info, err := svc.Upload(ctx, "user1", nil, "file.txt", strings.NewReader("data"))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("Upload = %v", err)
|
t.Fatalf("Upload = %v", err)
|
||||||
}
|
}
|
||||||
@@ -451,7 +451,7 @@ func TestFileService_VerifyParentForbidden(t *testing.T) {
|
|||||||
t.Fatalf("CreateDir = %v", err)
|
t.Fatalf("CreateDir = %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
_, err = svc.Upload(ctx, "user2", &dir.ID, "stolen.txt", strings.NewReader("data"), "")
|
_, err = svc.Upload(ctx, "user2", &dir.ID, "stolen.txt", strings.NewReader("data"))
|
||||||
if err != model.ErrForbidden {
|
if err != model.ErrForbidden {
|
||||||
t.Errorf("expected ErrForbidden, got %v", err)
|
t.Errorf("expected ErrForbidden, got %v", err)
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user