图片校验
This commit is contained in:
parent
f1f4f46ee9
commit
f564ecd059
@ -10,9 +10,6 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"image/jpeg"
|
"image/jpeg"
|
||||||
"image/png"
|
"image/png"
|
||||||
"mime"
|
|
||||||
"path/filepath"
|
|
||||||
"strings"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@ -35,8 +32,10 @@ type Info struct {
|
|||||||
SHA256 string
|
SHA256 string
|
||||||
}
|
}
|
||||||
|
|
||||||
// Inspect 严格要求魔数、扩展名和 multipart Content-Type 三者一致,并拒绝 APNG 及畸形动画容器。
|
// Inspect 只以完整文件字节为图片格式事实来源,并拒绝 APNG 及畸形动画容器。
|
||||||
func Inspect(content []byte, filename string, partContentType string) (Info, string, error) {
|
// multipart 文件名和 Content-Type 都由客户端提供,Android 相册缓存尤其可能保留错误后缀,
|
||||||
|
// 因而调用方必须使用这里返回的规范后缀与 Content-Type 保存对象,不能再信任上传元数据。
|
||||||
|
func Inspect(content []byte) (Info, string, error) {
|
||||||
var info Info
|
var info Info
|
||||||
var extension string
|
var extension string
|
||||||
switch {
|
switch {
|
||||||
@ -82,14 +81,6 @@ func Inspect(content []byte, filename string, partContentType string) (Info, str
|
|||||||
if err := validateGeometry(info); err != nil {
|
if err := validateGeometry(info); err != nil {
|
||||||
return Info{}, "", err
|
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)
|
sum := sha256.Sum256(content)
|
||||||
info.SizeBytes, info.SHA256 = int64(len(content)), hex.EncodeToString(sum[:])
|
info.SizeBytes, info.SHA256 = int64(len(content)), hex.EncodeToString(sum[:])
|
||||||
return info, extension, nil
|
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")
|
httpkit.WriteError(writer, request, http.StatusBadRequest, httpkit.CodeInvalidArgument, "file size does not match multipart metadata")
|
||||||
return
|
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 {
|
if err != nil {
|
||||||
httpkit.WriteError(writer, request, http.StatusUnsupportedMediaType, "UNSUPPORTED_MEDIA_TYPE", err.Error())
|
httpkit.WriteError(writer, request, http.StatusUnsupportedMediaType, "UNSUPPORTED_MEDIA_TYPE", err.Error())
|
||||||
return
|
return
|
||||||
|
|||||||
@ -9,9 +9,7 @@ import (
|
|||||||
"image/jpeg"
|
"image/jpeg"
|
||||||
"image/png"
|
"image/png"
|
||||||
"io"
|
"io"
|
||||||
"mime"
|
|
||||||
"net/http"
|
"net/http"
|
||||||
"path/filepath"
|
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
roomv1 "hyapp.local/api/proto/room/v1"
|
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")
|
httpkit.WriteError(writer, request, http.StatusBadRequest, httpkit.CodeInvalidArgument, "file size does not match multipart metadata")
|
||||||
return
|
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 {
|
if err != nil {
|
||||||
httpkit.WriteError(writer, request, http.StatusUnsupportedMediaType, "UNSUPPORTED_MEDIA_TYPE", err.Error())
|
httpkit.WriteError(writer, request, http.StatusUnsupportedMediaType, "UNSUPPORTED_MEDIA_TYPE", err.Error())
|
||||||
return
|
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 data roomMediaData
|
||||||
var extension string
|
var extension string
|
||||||
switch {
|
switch {
|
||||||
@ -280,15 +280,6 @@ func inspectRoomImage(content []byte, filename string, partContentType string) (
|
|||||||
default:
|
default:
|
||||||
return data, "", fmt.Errorf("only jpeg, png, gif and webp are supported")
|
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)
|
sum := sha256.Sum256(content)
|
||||||
data.SizeBytes, data.SHA256, data.Status = int64(len(content)), hex.EncodeToString(sum[:]), "active"
|
data.SizeBytes, data.SHA256, data.Status = int64(len(content)), hex.EncodeToString(sum[:]), "active"
|
||||||
return data, extension, nil
|
return data, extension, nil
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user