diff --git a/proxy/hotpath_test.go b/proxy/hotpath_test.go index d20ac06..8d9b4e9 100644 --- a/proxy/hotpath_test.go +++ b/proxy/hotpath_test.go @@ -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) { src := http.Header{ "X-Test": {"one", "two"}, diff --git a/proxy/match.go b/proxy/match.go index 5988f58..29a6cdf 100644 --- a/proxy/match.go +++ b/proxy/match.go @@ -161,21 +161,28 @@ func Matcher(rawPath string, cfg *config.Config) (string, string, string, *GHPro var user, repo string if strings.HasPrefix(remaining, "repos/") { 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, '/') if i > 0 { - userCandidate := remaining[:i] + user = remaining[:i] rest := remaining[i+1:] - if rest != "" { - if j := strings.IndexByte(rest, '/'); j != -1 { - repo = rest[:j] - } else { - repo = rest - } - user = userCandidate + if j := strings.IndexByte(rest, '/'); j != -1 { + repo = rest[:j] + } else { + repo = rest } } } else if strings.HasPrefix(remaining, "users/") { remaining = remaining[6:] + if q := strings.IndexByte(remaining, '?'); q != -1 { + remaining = remaining[:q] + } if remaining != "" { if i := strings.IndexByte(remaining, '/'); i != -1 { user = remaining[:i] diff --git a/proxy/matcher_test.go b/proxy/matcher_test.go index dec623e..9a58a7e 100644 --- a/proxy/matcher_test.go +++ b/proxy/matcher_test.go @@ -151,7 +151,19 @@ func TestMatcher_Compatibility(t *testing.T) { name: "API Repos Path (missing repo)", rawPath: "https://api.github.com/repos/owner", 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)", diff --git a/proxy/reqheader.go b/proxy/reqheader.go index bb20cc9..dce4e2f 100644 --- a/proxy/reqheader.go +++ b/proxy/reqheader.go @@ -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{}) { for k, vv := range src { if _, denied := denylist[k]; denied {