diff --git a/server/internal/handler/file.go b/server/internal/handler/file.go index ab7882ee..096e9a13 100644 --- a/server/internal/handler/file.go +++ b/server/internal/handler/file.go @@ -59,11 +59,23 @@ func (h *Handler) UploadFile(w http.ResponseWriter, r *http.Request) { } defer file.Close() - contentType := header.Header.Get("Content-Type") + // Sniff actual content type from file bytes instead of trusting the client header. + buf := make([]byte, 512) + n, err := file.Read(buf) + if err != nil && err != io.EOF { + writeError(w, http.StatusBadRequest, "failed to read file") + return + } + contentType := http.DetectContentType(buf[:n]) if !isContentTypeAllowed(contentType) { writeError(w, http.StatusBadRequest, fmt.Sprintf("file type not allowed: %s", contentType)) return } + // Seek back so the full file is uploaded. + if _, err := file.Seek(0, io.SeekStart); err != nil { + writeError(w, http.StatusInternalServerError, "failed to read file") + return + } data, err := io.ReadAll(file) if err != nil { @@ -77,7 +89,7 @@ func (h *Handler) UploadFile(w http.ResponseWriter, r *http.Request) { writeError(w, http.StatusInternalServerError, "internal error") return } - key := hex.EncodeToString(b) + path.Ext(header.Filename) + key := "uploads/" + hex.EncodeToString(b) + path.Ext(header.Filename) link, err := h.Storage.Upload(r.Context(), key, data, contentType, header.Filename) if err != nil { diff --git a/server/internal/storage/s3.go b/server/internal/storage/s3.go index 3f03ea86..b29375f0 100644 --- a/server/internal/storage/s3.go +++ b/server/internal/storage/s3.go @@ -6,6 +6,7 @@ import ( "fmt" "log/slog" "os" + "strings" "github.com/aws/aws-sdk-go-v2/aws" "github.com/aws/aws-sdk-go-v2/config" @@ -73,7 +74,7 @@ func (s *S3Storage) Upload(ctx context.Context, key string, data []byte, content Key: aws.String(key), Body: bytes.NewReader(data), ContentType: aws.String(contentType), - ContentDisposition: aws.String(fmt.Sprintf(`inline; filename="%s"`, filename)), + ContentDisposition: aws.String(fmt.Sprintf(`inline; filename="%s"`, strings.ReplaceAll(filename, `"`, "'"))), CacheControl: aws.String("max-age=432000,public"), StorageClass: types.StorageClassIntelligentTiering, })