Files
platforms/backend/api/internal/logic/upload/avatar_test.go
czl231 3ef33b531d 已完成用户APP首期功能开发
交付用户端首期页面、配套接口、后台资源及测试文档。用户APP构建、静态分析和三个管理后台构建通过;完整测试仍有2项失败,后端模型注释检查未通过,详见交付记录。
2026-09-13 00:57:32 +08:00

99 lines
3.3 KiB
Go
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// Package upload 测试头像上传的格式、尺寸与路径安全边界。
// 版本v1.0.0
package upload
import (
"bytes"
"image"
"image/color"
"image/png"
"os"
"path/filepath"
"strings"
"testing"
)
// pngBytes 生成指定尺寸的有效 PNG 测试图片。
func pngBytes(t *testing.T, width, height int) []byte {
t.Helper()
picture := image.NewRGBA(image.Rect(0, 0, width, height))
picture.Set(0, 0, color.RGBA{R: 32, G: 96, B: 192, A: 255})
var buffer bytes.Buffer
if err := png.Encode(&buffer, picture); err != nil {
t.Fatalf("encode PNG: %v", err)
}
return buffer.Bytes()
}
// TestAvatarOwnership 拒绝跨用户、跨客户端及穿越伪造路径,兼容读取旧头像路径。
func TestAvatarOwnership(t *testing.T) {
t.Setenv("HEQI_UPLOAD_DIR", t.TempDir())
uri := "/uploads/avatars/" + avatarOwnerDirectory("user_app", "alice") + "/2026/09/07/avatar.png"
path, err := avatarPathFromURI(uri)
if err != nil {
t.Fatal(err)
}
if err := os.MkdirAll(filepath.Dir(path), 0o750); err != nil {
t.Fatal(err)
}
if err := os.WriteFile(path, pngBytes(t, 2, 2), 0o640); err != nil {
t.Fatal(err)
}
if !OwnsAvatar("user_app", "alice", uri) {
t.Fatal("当前用户上传资源未识别")
}
if OwnsAvatar("user_app", "bob", uri) || OwnsAvatar("platform_admin", "alice", uri) {
t.Fatal("允许跨账户引用")
}
for _, unsafe := range []string{uri + "/../avatar.png", strings.ReplaceAll(uri, "/", "\\"), "/uploads/avatars/2026/09/07/avatar.png"} {
if OwnsAvatar("user_app", "alice", unsafe) {
t.Fatalf("接受未绑定 URI%s", unsafe)
}
}
}
// TestValidateAvatarAcceptsRealPNG 验证真实 PNG 可通过并规范化类型。
func TestValidateAvatarAcceptsRealPNG(t *testing.T) {
extension, contentType, err := validateAvatar("头像.PNG", pngBytes(t, 2, 2))
if err != nil {
t.Fatalf("valid PNG was rejected: %v", err)
}
if extension != ".png" || contentType != "image/png" {
t.Fatalf("avatar metadata = (%q, %q)", extension, contentType)
}
}
// TestValidateAvatarRejectsSpoofedAndOversizedImage 验证伪造扩展名与超大像素尺寸会被拒绝。
func TestValidateAvatarRejectsSpoofedAndOversizedImage(t *testing.T) {
if _, _, err := validateAvatar("fake.png", []byte("not an image")); err == nil {
t.Fatal("spoofed PNG was accepted")
}
if _, _, err := validateAvatar("wide.png", pngBytes(t, maxAvatarDimension+1, 1)); err == nil {
t.Fatal("oversized image dimensions were accepted")
}
}
// TestAvatarPathFromURIStaysInsideControlledRoot 验证头像 URI 不能逃逸受控目录。
func TestAvatarPathFromURIStaysInsideControlledRoot(t *testing.T) {
root := t.TempDir()
t.Setenv("HEQI_UPLOAD_DIR", root)
path, err := avatarPathFromURI("/uploads/avatars/2026/08/10/example.png")
if err != nil {
t.Fatalf("controlled URI was rejected: %v", err)
}
expectedRoot := filepath.Join(root, "avatars")
relative, err := filepath.Rel(expectedRoot, path)
if err != nil || relative == ".." || strings.HasPrefix(relative, ".."+string(filepath.Separator)) {
t.Fatalf("avatar path escaped root: %q", path)
}
for _, uri := range []string{
"https://example.com/avatar.png",
"/uploads/example.png",
"/uploads/avatars/../../secret.png",
} {
if _, err := avatarPathFromURI(uri); err == nil {
t.Fatalf("unsafe avatar URI was accepted: %q", uri)
}
}
}