diff --git a/services/gateway-service/internal/mediaimage/inspect.go b/services/gateway-service/internal/mediaimage/inspect.go index 2fd98e13..ec95a138 100644 --- a/services/gateway-service/internal/mediaimage/inspect.go +++ b/services/gateway-service/internal/mediaimage/inspect.go @@ -10,9 +10,6 @@ import ( "fmt" "image/jpeg" "image/png" - "mime" - "path/filepath" - "strings" ) const ( @@ -35,8 +32,10 @@ type Info struct { SHA256 string } -// Inspect 严格要求魔数、扩展名和 multipart Content-Type 三者一致,并拒绝 APNG 及畸形动画容器。 -func Inspect(content []byte, filename string, partContentType string) (Info, string, error) { +// Inspect 只以完整文件字节为图片格式事实来源,并拒绝 APNG 及畸形动画容器。 +// multipart 文件名和 Content-Type 都由客户端提供,Android 相册缓存尤其可能保留错误后缀, +// 因而调用方必须使用这里返回的规范后缀与 Content-Type 保存对象,不能再信任上传元数据。 +func Inspect(content []byte) (Info, string, error) { var info Info var extension string switch { @@ -82,14 +81,6 @@ func Inspect(content []byte, filename string, partContentType string) (Info, str if err := validateGeometry(info); err != nil { return Info{}, "", err } - declared, _, _ := mime.ParseMediaType(strings.TrimSpace(partContentType)) - if !strings.EqualFold(declared, info.ContentType) { - return Info{}, "", fmt.Errorf("file content_type does not match signature") - } - fileExtension := strings.ToLower(strings.TrimPrefix(filepath.Ext(filename), ".")) - if fileExtension != extension && !(info.Format == "jpeg" && fileExtension == "jpeg") { - return Info{}, "", fmt.Errorf("filename extension does not match signature") - } sum := sha256.Sum256(content) info.SizeBytes, info.SHA256 = int64(len(content)), hex.EncodeToString(sum[:]) return info, extension, nil diff --git a/services/gateway-service/internal/transport/http/appapi/avatar_media_handler.go b/services/gateway-service/internal/transport/http/appapi/avatar_media_handler.go index 8752aa0e..f1cef9fc 100644 --- a/services/gateway-service/internal/transport/http/appapi/avatar_media_handler.go +++ b/services/gateway-service/internal/transport/http/appapi/avatar_media_handler.go @@ -68,7 +68,9 @@ func (h *Handler) uploadUserAvatar(writer http.ResponseWriter, request *http.Req httpkit.WriteError(writer, request, http.StatusBadRequest, httpkit.CodeInvalidArgument, "file size does not match multipart metadata") return } - info, _, err := mediaimage.Inspect(content, header.Filename, header.Header.Get("Content-Type")) + // 文件名和 multipart Content-Type 都可能来自错误的相册缓存元数据;格式、后缀和 + // 对象 Content-Type 只采用原始字节解析结果,避免真实 WebP/GIF 被误报为 JPEG 后拒绝。 + info, _, err := mediaimage.Inspect(content) if err != nil { httpkit.WriteError(writer, request, http.StatusUnsupportedMediaType, "UNSUPPORTED_MEDIA_TYPE", err.Error()) return diff --git a/services/gateway-service/internal/transport/http/roomapi/room_media_handler.go b/services/gateway-service/internal/transport/http/roomapi/room_media_handler.go index 65e3cc99..f6d2de60 100644 --- a/services/gateway-service/internal/transport/http/roomapi/room_media_handler.go +++ b/services/gateway-service/internal/transport/http/roomapi/room_media_handler.go @@ -9,9 +9,7 @@ import ( "image/jpeg" "image/png" "io" - "mime" "net/http" - "path/filepath" "strings" roomv1 "hyapp.local/api/proto/room/v1" @@ -151,7 +149,9 @@ func (h *Handler) uploadRoomMedia(writer http.ResponseWriter, request *http.Requ httpkit.WriteError(writer, request, http.StatusBadRequest, httpkit.CodeInvalidArgument, "file size does not match multipart metadata") return } - media, _, err := inspectRoomImage(content, header.Filename, header.Header.Get("Content-Type")) + // 相册缓存文件名和 multipart Content-Type 不可信;格式、后缀和对象 Content-Type + // 全部从原始字节解析,确保 WebP/GIF 即使带着 JPG 元数据也能按真实类型保存。 + media, _, err := inspectRoomImage(content) if err != nil { httpkit.WriteError(writer, request, http.StatusUnsupportedMediaType, "UNSUPPORTED_MEDIA_TYPE", err.Error()) return @@ -237,7 +237,7 @@ func (h *Handler) uploadRoomMedia(writer http.ResponseWriter, request *http.Requ }) } -func inspectRoomImage(content []byte, filename string, partContentType string) (roomMediaData, string, error) { +func inspectRoomImage(content []byte) (roomMediaData, string, error) { var data roomMediaData var extension string switch { @@ -280,15 +280,6 @@ func inspectRoomImage(content []byte, filename string, partContentType string) ( default: return data, "", fmt.Errorf("only jpeg, png, gif and webp are supported") } - declared, _, _ := mime.ParseMediaType(strings.TrimSpace(partContentType)) - if !strings.EqualFold(declared, data.ContentType) { - return roomMediaData{}, "", fmt.Errorf("file content_type does not match signature") - } - fileExtension := strings.ToLower(strings.TrimPrefix(filepath.Ext(filename), ".")) - validExtension := fileExtension == extension || (data.Format == "jpeg" && fileExtension == "jpeg") - if !validExtension { - return roomMediaData{}, "", fmt.Errorf("filename extension does not match signature") - } sum := sha256.Sum256(content) data.SizeBytes, data.SHA256, data.Status = int64(len(content)), hex.EncodeToString(sum[:]), "active" return data, extension, nil