diff --git a/weed/admin/handlers/file_browser_handlers.go b/weed/admin/handlers/file_browser_handlers.go index bafaa60c3..eeb8e2d85 100644 --- a/weed/admin/handlers/file_browser_handlers.go +++ b/weed/admin/handlers/file_browser_handlers.go @@ -10,6 +10,7 @@ import ( "net" "net/http" "os" + "path" "path/filepath" "strconv" "strings" @@ -21,6 +22,7 @@ import ( "github.com/seaweedfs/seaweedfs/weed/admin/view/layout" "github.com/seaweedfs/seaweedfs/weed/glog" "github.com/seaweedfs/seaweedfs/weed/pb/filer_pb" + "github.com/seaweedfs/seaweedfs/weed/util" "github.com/seaweedfs/seaweedfs/weed/util/http/client" ) @@ -267,8 +269,12 @@ func (h *FileBrowserHandlers) UploadFile(c *gin.Context) { continue } - // Create full path for the file - fullPath := filepath.Join(currentPath, fileName) + // Normalize Windows-style backslashes to forward slashes + fileName = util.CleanWindowsPath(fileName) + + // Create full path for the file using path.Join for URL path semantics + // path.Join handles double slashes and is not OS-specific like filepath.Join + fullPath := path.Join(currentPath, fileName) if !strings.HasPrefix(fullPath, "/") { fullPath = "/" + fullPath } @@ -349,8 +355,10 @@ func (h *FileBrowserHandlers) uploadFileToFiler(filePath string, fileHeader *mul var body bytes.Buffer writer := multipart.NewWriter(&body) - // Create form file field - part, err := writer.CreateFormFile("file", fileHeader.Filename) + // Create form file field with normalized base filename + // Use path.Base (not filepath.Base) since cleanFilePath uses URL path semantics + baseFileName := path.Base(cleanFilePath) + part, err := writer.CreateFormFile("file", baseFileName) if err != nil { return fmt.Errorf("failed to create form file: %w", err) } @@ -452,8 +460,12 @@ func (h *FileBrowserHandlers) validateAndCleanFilePath(filePath string) (string, return "", fmt.Errorf("file path cannot be empty") } + // Normalize Windows-style backslashes to forward slashes + filePath = util.CleanWindowsPath(filePath) + // Clean the path to remove any .. or . components - cleanPath := filepath.Clean(filePath) + // Use path.Clean (not filepath.Clean) since this is a URL path + cleanPath := path.Clean(filePath) // Ensure the path starts with / if !strings.HasPrefix(cleanPath, "/") { diff --git a/weed/storage/needle/needle_parse_upload.go b/weed/storage/needle/needle_parse_upload.go index 89708303d..6fadd80d6 100644 --- a/weed/storage/needle/needle_parse_upload.go +++ b/weed/storage/needle/needle_parse_upload.go @@ -128,7 +128,7 @@ func parseUpload(r *http.Request, sizeLimit int64, pu *ParsedUpload) (e error) { pu.FileName = part.FileName() if pu.FileName != "" { - pu.FileName = path.Base(pu.FileName) + pu.FileName = util.CleanWindowsPathBase(pu.FileName) } dataSize, e = pu.bytesBuffer.ReadFrom(io.LimitReader(part, sizeLimit+1)) @@ -169,7 +169,7 @@ func parseUpload(r *http.Request, sizeLimit int64, pu *ParsedUpload) (e error) { // update pu.Data = pu.bytesBuffer.Bytes() - pu.FileName = path.Base(fName) + pu.FileName = util.CleanWindowsPathBase(fName) contentType = part.Header.Get("Content-Type") part = part2 break @@ -207,7 +207,7 @@ func parseUpload(r *http.Request, sizeLimit int64, pu *ParsedUpload) (e error) { } if pu.FileName != "" { - pu.FileName = path.Base(pu.FileName) + pu.FileName = util.CleanWindowsPathBase(pu.FileName) } else { pu.FileName = path.Base(r.URL.Path) } diff --git a/weed/util/fullpath.go b/weed/util/fullpath.go index c145919da..b485cae0d 100644 --- a/weed/util/fullpath.go +++ b/weed/util/fullpath.go @@ -1,6 +1,7 @@ package util import ( + "path" "path/filepath" "strings" ) @@ -85,3 +86,15 @@ func StringSplit(separatedValues string, sep string) []string { } return strings.Split(separatedValues, sep) } + +// CleanWindowsPath normalizes Windows-style backslashes to forward slashes. +// This handles paths from Windows clients where paths use backslashes. +func CleanWindowsPath(p string) string { + return strings.ReplaceAll(p, "\\", "/") +} + +// CleanWindowsPathBase normalizes Windows-style backslashes to forward slashes +// and returns the base name of the path. +func CleanWindowsPathBase(p string) string { + return path.Base(strings.ReplaceAll(p, "\\", "/")) +}