owpengram-server/internal/app/files/external_media_test.go

106 lines
4 KiB
Go
Raw 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 files
import (
"context"
"errors"
"net"
"net/http"
"net/http/httptest"
"testing"
)
func TestIsBlockedExternalIP(t *testing.T) {
cases := []struct {
ip string
blocked bool
}{
{"127.0.0.1", true}, // loopback
{"::1", true}, // loopback v6
{"10.0.0.5", true}, // private
{"172.16.3.4", true}, // private
{"192.168.1.1", true}, // private
{"169.254.1.1", true}, // link-local
{"fe80::1", true}, // link-local v6
{"0.0.0.0", true}, // unspecified
{"100.64.0.1", true}, // CGNAT
{"100.127.255.1", true}, // CGNAT 上界
{"224.0.0.1", true}, // multicast
{"8.8.8.8", false}, // 公网
{"1.1.1.1", false}, // 公网
{"100.63.255.1", false}, // CGNAT 下界外(公网)
{"100.128.0.1", false}, // CGNAT 上界外(公网)
{"2606:4700:4700::1111", false}, // 公网 v6
}
for _, c := range cases {
ip := net.ParseIP(c.ip)
if ip == nil {
t.Fatalf("parse %s failed", c.ip)
}
if got := isBlockedExternalIP(ip); got != c.blocked {
t.Errorf("isBlockedExternalIP(%s) = %v, want %v", c.ip, got, c.blocked)
}
}
}
// TestExternalMediaFetcherSSRFGuard 验证 SSRF 防护httptest 在 loopback 上,
// allowPrivate=false 必须拦截不连接内网allowPrivate=true 放行抓取到字节。
func TestExternalMediaFetcherSSRFGuard(t *testing.T) {
body := []byte("hello-external-bytes")
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/octet-stream")
_, _ = w.Write(body)
}))
defer srv.Close()
// 生产配置allowPrivate=falseloopback 目标被 SSRF 防护拦截。
guarded := newExternalMediaFetcher(DefaultExternalMediaMaxBytes, DefaultExternalMediaRatePerMin, false)
if _, _, err := guarded.fetch(context.Background(), srv.URL); !errors.Is(err, ErrExternalMediaInvalid) {
t.Fatalf("SSRF guard fetch err = %v, want ErrExternalMediaInvalid (loopback 应被拦)", err)
}
// 测试放行allowPrivate=true抓取成功。
open := newExternalMediaFetcher(DefaultExternalMediaMaxBytes, DefaultExternalMediaRatePerMin, true)
data, ct, err := open.fetch(context.Background(), srv.URL)
if err != nil {
t.Fatalf("open fetch err = %v", err)
}
if string(data) != string(body) {
t.Fatalf("fetched %q, want %q", data, body)
}
if ct != "application/octet-stream" {
t.Fatalf("content-type = %q, want application/octet-stream", ct)
}
}
// TestExternalMediaFetcherRejectsBadURL 非 http(s)/空 host 直接拒。
func TestExternalMediaFetcherRejectsBadURL(t *testing.T) {
f := newExternalMediaFetcher(DefaultExternalMediaMaxBytes, DefaultExternalMediaRatePerMin, true)
for _, bad := range []string{"", "ftp://x/y", "file:///etc/passwd", "javascript:alert(1)", "http://", "not a url"} {
if _, _, err := f.fetch(context.Background(), bad); !errors.Is(err, ErrExternalMediaInvalid) {
t.Errorf("fetch(%q) err = %v, want ErrExternalMediaInvalid", bad, err)
}
}
}
// TestExternalMediaFetcherSizeLimit 超大小上限拒。
func TestExternalMediaFetcherSizeLimit(t *testing.T) {
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
_, _ = w.Write(make([]byte, 2048))
}))
defer srv.Close()
f := newExternalMediaFetcher(1024, DefaultExternalMediaRatePerMin, true)
if _, _, err := f.fetch(context.Background(), srv.URL); !errors.Is(err, ErrExternalMediaInvalid) {
t.Fatalf("oversize fetch err = %v, want ErrExternalMediaInvalid", err)
}
}
// TestExternalMediaDisabled 未启用时 Create*FromURL 返回 ErrExternalMediaDisabled。
func TestExternalMediaDisabled(t *testing.T) {
s := &Service{}
if _, err := s.CreatePhotoFromURL(context.Background(), "http://x/y.png"); !errors.Is(err, ErrExternalMediaDisabled) {
t.Fatalf("disabled photo err = %v, want ErrExternalMediaDisabled", err)
}
if _, err := s.CreateDocumentFromURL(context.Background(), "http://x/y.bin"); !errors.Is(err, ErrExternalMediaDisabled) {
t.Fatalf("disabled doc err = %v, want ErrExternalMediaDisabled", err)
}
}