图片校验
This commit is contained in:
parent
f1f4f46ee9
commit
f564ecd059
@ -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
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user