package io import ( "context" "errors" "testing" ) // TestValidateRequestURL_DeniedByDefault 钉住**共享判定本体**的默认拒绝面(收口 X3): // 收口之前浏览器侧另有一份同策实现,两份各自被自己的用例覆盖;收口之后浏览器侧 // (`internal/capability/browser` 的守卫用例)与本用例打的是**同一张表、同一份代码**。 func TestValidateRequestURL_DeniedByDefault(t *testing.T) { t.Parallel() denied := []string{ "http://127.0.0.1/", "http://[::1]/", "http://192.168.1.10/admin", "http://10.0.0.5/", "http://172.16.3.4/", "http://169.254.169.254/latest/meta-data/", "http://0.0.0.0/", "file:///etc/passwd", "ftp://example.com/", "http:///no-host", "", } for _, raw := range denied { if _, err := ValidateRequestURL(context.Background(), raw, nil); err == nil { t.Errorf("ValidateRequestURL(%q) = nil,应当拒绝", raw) } } // 网段拒绝必须**保住 ErrSSRF 身份**:调用方(含浏览器侧的门面)按 errors.Is 分类降级。 if _, err := ValidateRequestURL(context.Background(), "http://127.0.0.1/", nil); !errors.Is(err, ErrSSRF) { t.Errorf("回环目标的错误丢了 ErrSSRF 身份: %v", err) } } // TestValidateRequestURL_AllowlistEntryForms 钉住白名单条目的**全部写法**都按主机名匹配。 // 这是收口 X3 的回归网:完整 URL 写法(以及裸 IPv6 字面量)原先只有被删掉的那份 // 浏览器侧实现认;合并成一份时若丢掉它,面板里写 `http://host:port` 的白名单会**静默 // 失效**(改前生效、改后不生效,没有一条日志会说出这件事)。 func TestValidateRequestURL_AllowlistEntryForms(t *testing.T) { t.Parallel() cases := []struct{ entry, raw string }{ {"127.0.0.1", "http://127.0.0.1:7777/x"}, {"127.0.0.1:9", "http://127.0.0.1:7777/x"}, {"http://127.0.0.1:9999/", "http://127.0.0.1:7777/x"}, {"localhost", "http://localhost:8080/x"}, {"LocalHost:8080", "http://LOCALHOST/x"}, {"http://localhost:8080", "http://localhost/x"}, {"[::1]", "http://[::1]:9999/x"}, {"::1", "http://[::1]:9999/x"}, } for _, tc := range cases { if _, err := ValidateRequestURL(context.Background(), tc.raw, []string{tc.entry}); err != nil { t.Errorf("条目 %q 未放行 %q: %v", tc.entry, tc.raw, err) } } } // TestValidateRequestURL_AllowlistDoesNotOverreach 钉住**放行的边界**:白名单只放行它 // 自己那条主机名,且不依赖 DNS 的放行面照旧(公网字面 IP)。 func TestValidateRequestURL_AllowlistDoesNotOverreach(t *testing.T) { t.Parallel() if _, err := ValidateRequestURL(context.Background(), "http://93.184.216.34/", nil); err != nil { t.Fatalf("公网字面 IP 被拒: %v", err) } // 白名单按主机名字符串比较:写 `localhost` 不等于放行 `127.0.0.1`(是两个名字)。 if _, err := ValidateRequestURL(context.Background(), "http://127.0.0.1/", []string{"localhost"}); !errors.Is(err, ErrSSRF) { t.Errorf("条目 localhost 不该放行 127.0.0.1(实得 %v)", err) } // 无关的 URL 条目(主机是别的名字)不能被「看谁像 URL」放过。 if _, err := ValidateRequestURL(context.Background(), "http://127.0.0.1/", []string{"http://other:1/"}); !errors.Is(err, ErrSSRF) { t.Errorf("无关的 URL 条目不该放行 127.0.0.1(实得 %v)", err) } // 空条目不能让任何主机命中(面板里删空一行 = 不放行任何东西)。 if _, err := ValidateRequestURL(context.Background(), "http://127.0.0.1/", []string{"", " "}); !errors.Is(err, ErrSSRF) { t.Errorf("空白条目不该放行任何主机(实得 %v)", err) } }