mirror of
https://github.com/WJQSERVER-STUDIO/ghproxy.git
synced 2026-06-13 15:47:37 +08:00
fix(proxy): restore header filtering and API matcher consistency
- Canonicalize filtered header deny-lists so Cloudflare and CDN headers are still removed - Normalize incomplete API repo paths to stable owner-level matcher output regardless of trailing slash or query - Add regression tests covering header canonicalization and incomplete API repo path parsing
This commit is contained in:
parent
ba3dcf7624
commit
e9e48fcefd
4 changed files with 73 additions and 9 deletions
|
|
@ -65,6 +65,31 @@ func TestCopyHeaderFiltered(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestCopyHeaderFiltered_CanonicalizesDenylist(t *testing.T) {
|
||||||
|
src := http.Header{
|
||||||
|
"Cf-Ipcountry": {"CN"},
|
||||||
|
"Cf-Ray": {"abc123"},
|
||||||
|
"Cf-Ew-Via": {"edge"},
|
||||||
|
"X-Forwarded-For": {"127.0.0.1"},
|
||||||
|
}
|
||||||
|
dst := make(http.Header)
|
||||||
|
|
||||||
|
copyHeaderFiltered(dst, src, reqHeadersToRemove)
|
||||||
|
|
||||||
|
if got := dst.Values("Cf-Ipcountry"); len(got) != 0 {
|
||||||
|
t.Fatalf("Cf-Ipcountry should be filtered, got %v", got)
|
||||||
|
}
|
||||||
|
if got := dst.Values("Cf-Ray"); len(got) != 0 {
|
||||||
|
t.Fatalf("Cf-Ray should be filtered, got %v", got)
|
||||||
|
}
|
||||||
|
if got := dst.Values("Cf-Ew-Via"); len(got) != 0 {
|
||||||
|
t.Fatalf("Cf-Ew-Via should be filtered, got %v", got)
|
||||||
|
}
|
||||||
|
if got := dst.Values("X-Forwarded-For"); !reflect.DeepEqual(got, []string{"127.0.0.1"}) {
|
||||||
|
t.Fatalf("X-Forwarded-For = %v, want [127.0.0.1]", got)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestCopyHeaderFiltered_AllowsAllWhenDenylistEmpty(t *testing.T) {
|
func TestCopyHeaderFiltered_AllowsAllWhenDenylistEmpty(t *testing.T) {
|
||||||
src := http.Header{
|
src := http.Header{
|
||||||
"X-Test": {"one", "two"},
|
"X-Test": {"one", "two"},
|
||||||
|
|
|
||||||
|
|
@ -161,21 +161,28 @@ func Matcher(rawPath string, cfg *config.Config) (string, string, string, *GHPro
|
||||||
var user, repo string
|
var user, repo string
|
||||||
if strings.HasPrefix(remaining, "repos/") {
|
if strings.HasPrefix(remaining, "repos/") {
|
||||||
remaining = remaining[6:]
|
remaining = remaining[6:]
|
||||||
|
if q := strings.IndexByte(remaining, '?'); q != -1 {
|
||||||
|
remaining = remaining[:q]
|
||||||
|
}
|
||||||
|
if remaining != "" && !strings.ContainsRune(remaining, '/') {
|
||||||
|
user = remaining
|
||||||
|
return user, "", "api", nil
|
||||||
|
}
|
||||||
i := strings.IndexByte(remaining, '/')
|
i := strings.IndexByte(remaining, '/')
|
||||||
if i > 0 {
|
if i > 0 {
|
||||||
userCandidate := remaining[:i]
|
user = remaining[:i]
|
||||||
rest := remaining[i+1:]
|
rest := remaining[i+1:]
|
||||||
if rest != "" {
|
|
||||||
if j := strings.IndexByte(rest, '/'); j != -1 {
|
if j := strings.IndexByte(rest, '/'); j != -1 {
|
||||||
repo = rest[:j]
|
repo = rest[:j]
|
||||||
} else {
|
} else {
|
||||||
repo = rest
|
repo = rest
|
||||||
}
|
}
|
||||||
user = userCandidate
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
} else if strings.HasPrefix(remaining, "users/") {
|
} else if strings.HasPrefix(remaining, "users/") {
|
||||||
remaining = remaining[6:]
|
remaining = remaining[6:]
|
||||||
|
if q := strings.IndexByte(remaining, '?'); q != -1 {
|
||||||
|
remaining = remaining[:q]
|
||||||
|
}
|
||||||
if remaining != "" {
|
if remaining != "" {
|
||||||
if i := strings.IndexByte(remaining, '/'); i != -1 {
|
if i := strings.IndexByte(remaining, '/'); i != -1 {
|
||||||
user = remaining[:i]
|
user = remaining[:i]
|
||||||
|
|
|
||||||
|
|
@ -151,7 +151,19 @@ func TestMatcher_Compatibility(t *testing.T) {
|
||||||
name: "API Repos Path (missing repo)",
|
name: "API Repos Path (missing repo)",
|
||||||
rawPath: "https://api.github.com/repos/owner",
|
rawPath: "https://api.github.com/repos/owner",
|
||||||
config: cfgWithAuth,
|
config: cfgWithAuth,
|
||||||
expectedUser: "", expectedRepo: "", expectedMatcher: "api",
|
expectedUser: "owner", expectedRepo: "", expectedMatcher: "api",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "API Repos Path (trailing slash)",
|
||||||
|
rawPath: "https://api.github.com/repos/owner/",
|
||||||
|
config: cfgWithAuth,
|
||||||
|
expectedUser: "owner", expectedRepo: "", expectedMatcher: "api",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "API Repos Path (missing repo with query)",
|
||||||
|
rawPath: "https://api.github.com/repos/owner?per_page=1",
|
||||||
|
config: cfgWithAuth,
|
||||||
|
expectedUser: "owner", expectedRepo: "", expectedMatcher: "api",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "API Users Path (exact user)",
|
name: "API Users Path (exact user)",
|
||||||
|
|
|
||||||
|
|
@ -60,6 +60,26 @@ func copyHeader(dst, src http.Header) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func canonicalizeHeaderSet(headers map[string]struct{}) map[string]struct{} {
|
||||||
|
canonicalized := make(map[string]struct{}, len(headers))
|
||||||
|
for key := range headers {
|
||||||
|
canonicalized[http.CanonicalHeaderKey(key)] = struct{}{}
|
||||||
|
}
|
||||||
|
return canonicalized
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
reqHeadersToRemove = canonicalizeHeaderSet(reqHeadersToRemove)
|
||||||
|
cloneHeadersToRemove = canonicalizeHeaderSet(cloneHeadersToRemove)
|
||||||
|
respHeadersToRemove = canonicalizeHeaderSet(respHeadersToRemove)
|
||||||
|
defaultHeaders = map[string]string{
|
||||||
|
"Accept": "*/*",
|
||||||
|
"Accept-Encoding": "",
|
||||||
|
"Transfer-Encoding": "chunked",
|
||||||
|
"User-Agent": "GHProxy/1.0",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func copyHeaderFiltered(dst, src http.Header, denylist map[string]struct{}) {
|
func copyHeaderFiltered(dst, src http.Header, denylist map[string]struct{}) {
|
||||||
for k, vv := range src {
|
for k, vv := range src {
|
||||||
if _, denied := denylist[k]; denied {
|
if _, denied := denylist[k]; denied {
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue