Files

99 lines
3.3 KiB
Go
Raw Permalink Normal View History

// 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)
}
}
}