mirror of
https://github.com/Wei-Shaw/sub2api.git
synced 2026-10-16 23:53:33 +08:00
账户开关 images_url_to_b64_json 下载的图片 url 来自上游响应,是不可信输入。此前沿用 base_url 的 security.url_allowlist 策略,默认配置下只做格式校验,上游返回 localhost:8080 之类地址时网关会真的去请求内网。 - 下载 URL 主机为 localhost、回环、私网、链路本地、未指定地址的字面量时直接拒绝, 不受 security.url_allowlist 配置影响(urlvalidator 导出 IsBlockedHost) - 新增 ctx 标记 WithHTTPUpstreamPublicHostsOnly:共享上游客户端对带标记的请求无视配置 校验解析后 IP,并为其克隆客户端挂 redirectChecker,重定向保持跟随但每一跳都校验 - 下载内容改为只信 http.DetectContentType 嗅探结果且限 png/jpeg/webp/gif, 响应头不再作为依据 - 下载失败仍只记日志并保留原样;超时与端口策略不变
93 lines
3.7 KiB
Go
93 lines
3.7 KiB
Go
package urlvalidator
|
|
|
|
import "testing"
|
|
|
|
func TestValidateURLFormat(t *testing.T) {
|
|
if _, err := ValidateURLFormat("", false); err == nil {
|
|
t.Fatalf("expected empty url to fail")
|
|
}
|
|
if _, err := ValidateURLFormat("://bad", false); err == nil {
|
|
t.Fatalf("expected invalid url to fail")
|
|
}
|
|
if _, err := ValidateURLFormat("http://example.com", false); err == nil {
|
|
t.Fatalf("expected http to fail when allow_insecure_http is false")
|
|
}
|
|
if _, err := ValidateURLFormat("https://example.com", false); err != nil {
|
|
t.Fatalf("expected https to pass, got %v", err)
|
|
}
|
|
if _, err := ValidateURLFormat("http://example.com", true); err != nil {
|
|
t.Fatalf("expected http to pass when allow_insecure_http is true, got %v", err)
|
|
}
|
|
if _, err := ValidateURLFormat("https://example.com:bad", true); err == nil {
|
|
t.Fatalf("expected invalid port to fail")
|
|
}
|
|
|
|
// 验证末尾斜杠被移除
|
|
normalized, err := ValidateURLFormat("https://example.com/", false)
|
|
if err != nil {
|
|
t.Fatalf("expected trailing slash url to pass, got %v", err)
|
|
}
|
|
if normalized != "https://example.com" {
|
|
t.Fatalf("expected trailing slash to be removed, got %s", normalized)
|
|
}
|
|
|
|
// 验证多个末尾斜杠被移除
|
|
normalized, err = ValidateURLFormat("https://example.com///", false)
|
|
if err != nil {
|
|
t.Fatalf("expected multiple trailing slashes to pass, got %v", err)
|
|
}
|
|
if normalized != "https://example.com" {
|
|
t.Fatalf("expected all trailing slashes to be removed, got %s", normalized)
|
|
}
|
|
|
|
// 验证带路径的 URL 末尾斜杠被移除
|
|
normalized, err = ValidateURLFormat("https://example.com/api/v1/", false)
|
|
if err != nil {
|
|
t.Fatalf("expected trailing slash url with path to pass, got %v", err)
|
|
}
|
|
if normalized != "https://example.com/api/v1" {
|
|
t.Fatalf("expected trailing slash to be removed from path, got %s", normalized)
|
|
}
|
|
}
|
|
|
|
func TestValidateHTTPURL(t *testing.T) {
|
|
if _, err := ValidateHTTPURL("http://example.com", false, ValidationOptions{}); err == nil {
|
|
t.Fatalf("expected http to fail when allow_insecure_http is false")
|
|
}
|
|
if _, err := ValidateHTTPURL("http://example.com", true, ValidationOptions{}); err != nil {
|
|
t.Fatalf("expected http to pass when allow_insecure_http is true, got %v", err)
|
|
}
|
|
if _, err := ValidateHTTPURL("https://example.com", false, ValidationOptions{RequireAllowlist: true}); err == nil {
|
|
t.Fatalf("expected require allowlist to fail when empty")
|
|
}
|
|
if _, err := ValidateHTTPURL("https://example.com", false, ValidationOptions{AllowedHosts: []string{"api.example.com"}}); err == nil {
|
|
t.Fatalf("expected host not in allowlist to fail")
|
|
}
|
|
if _, err := ValidateHTTPURL("https://api.example.com", false, ValidationOptions{AllowedHosts: []string{"api.example.com"}}); err != nil {
|
|
t.Fatalf("expected allowlisted host to pass, got %v", err)
|
|
}
|
|
if _, err := ValidateHTTPURL("https://sub.api.example.com", false, ValidationOptions{AllowedHosts: []string{"*.example.com"}}); err != nil {
|
|
t.Fatalf("expected wildcard allowlist to pass, got %v", err)
|
|
}
|
|
if _, err := ValidateHTTPURL("https://localhost", false, ValidationOptions{AllowPrivate: false}); err == nil {
|
|
t.Fatalf("expected localhost to be blocked when allow_private_hosts is false")
|
|
}
|
|
}
|
|
|
|
func TestIsBlockedHost(t *testing.T) {
|
|
for _, host := range []string{
|
|
"localhost", "LOCALHOST", "foo.localhost", " 127.0.0.1 ",
|
|
"127.0.0.1", "::1", "10.0.0.5", "172.16.0.1", "192.168.1.1",
|
|
"169.254.169.254", "0.0.0.0", "::", "fe80::1", "fc00::1", "::ffff:127.0.0.1",
|
|
} {
|
|
if !IsBlockedHost(host) {
|
|
t.Fatalf("expected %q to be blocked", host)
|
|
}
|
|
}
|
|
for _, host := range []string{"example.com", "cdn.example.com", "93.184.216.34", "8.8.8.8", "2606:4700:4700::1111", ""} {
|
|
if IsBlockedHost(host) {
|
|
t.Fatalf("expected %q to be allowed", host)
|
|
}
|
|
}
|
|
}
|