From 208ce8a4f94d3d2470bef58896ee283fbcc5fe32 Mon Sep 17 00:00:00 2001 From: wjqserver <114663932+WJQSERVER@users.noreply.github.com> Date: Thu, 31 Jul 2025 20:01:03 +0800 Subject: [PATCH] 4.2.5 --- CHANGELOG.md | 4 ++ VERSION | 2 +- main.go | 5 ++ proxy/match.go | 133 ++++++++++++++++++++---------------------- proxy/matcher_test.go | 8 ++- 5 files changed, 80 insertions(+), 72 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7eb963c..ff99f7b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,9 @@ # 更新日志 +4.2.5 - 2025-07-31 +--- +- CHANGE: 进一步完善匹配器, 兼容更多情况 + 4.2.4 - 2025-07-29 --- - CHANGE: 改进匹配器, 防止匹配不应匹配的内容 diff --git a/VERSION b/VERSION index cf78d5b..ad35fe0 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -4.2.4 +4.2.5 \ No newline at end of file diff --git a/main.go b/main.go index 269727a..3313829 100644 --- a/main.go +++ b/main.go @@ -399,6 +399,11 @@ func main() { proxy.RoutingHandler(cfg)(c) }) + r.GET("/github.com/:user/:repo/releases/:tag/download/*filepath", func(c *touka.Context) { + c.Set("matcher", "releases") + proxy.RoutingHandler(cfg)(c) + }) + r.GET("/github.com/:user/:repo/archive/*filepath", func(c *touka.Context) { c.Set("matcher", "releases") proxy.RoutingHandler(cfg)(c) diff --git a/proxy/match.go b/proxy/match.go index 3f5f87f..9a37f0a 100644 --- a/proxy/match.go +++ b/proxy/match.go @@ -42,37 +42,62 @@ func Matcher(rawPath string, cfg *config.Config) (string, string, string, *GHPro // 匹配 "https://github.com/" if strings.HasPrefix(rawPath, githubPrefix) { - remaining := rawPath[githubPrefixLen:] - i := strings.IndexByte(remaining, '/') + pathAfterDomain := rawPath[githubPrefixLen:] + + // 解析 user + i := strings.IndexByte(pathAfterDomain, '/') if i <= 0 { return "", "", "", NewErrorWithStatusLookup(400, "malformed github path: missing user") } - user := remaining[:i] - remaining = remaining[i+1:] - i = strings.IndexByte(remaining, '/') + user := pathAfterDomain[:i] + pathAfterUser := pathAfterDomain[i+1:] + + // 解析 repo + i = strings.IndexByte(pathAfterUser, '/') if i <= 0 { - return "", "", "", NewErrorWithStatusLookup(400, "malformed github path: missing repo") - } - repo := remaining[:i] - remaining = remaining[i+1:] - if len(remaining) == 0 { return "", "", "", NewErrorWithStatusLookup(400, "malformed github path: missing action") } - i = strings.IndexByte(remaining, '/') - action := remaining - if i != -1 { - action = remaining[:i] + repo := pathAfterUser[:i] + pathAfterRepo := pathAfterUser[i+1:] + + if len(pathAfterRepo) == 0 { + return "", "", "", NewErrorWithStatusLookup(400, "malformed github path: missing action") } + + // 优先处理所有 "releases" 相关的下载路径 + if strings.HasPrefix(pathAfterRepo, "releases/") { + // 情况 A: "releases/download/..." + if strings.HasPrefix(pathAfterRepo, "releases/download/") { + return user, repo, "releases", nil + } + // 情况 B: "releases/:tag/download/..." + pathAfterReleases := pathAfterRepo[len("releases/"):] + slashIndex := strings.IndexByte(pathAfterReleases, '/') + if slashIndex > 0 { // 确保tag不为空 + pathAfterTag := pathAfterReleases[slashIndex+1:] + if strings.HasPrefix(pathAfterTag, "download/") { + return user, repo, "releases", nil + } + } + // 如果不满足上述下载链接的结构, 则为网页浏览路径, 予以拒绝 + return "", "", "", NewErrorWithStatusLookup(400, "unsupported releases page, only download links are allowed") + } + + // 检查 "archive/" 路径 + if strings.HasPrefix(pathAfterRepo, "archive/") { + // 根据测试用例, archive路径的matcher也应为releases + return user, repo, "releases", nil + } + + // 如果不是下载路径, 则解析action并进行分类 + i = strings.IndexByte(pathAfterRepo, '/') + action := pathAfterRepo + if i != -1 { + action = pathAfterRepo[:i] + } + var matcher string switch action { - case "releases": - if strings.HasPrefix(remaining, releasesDownloadSnippet) { - matcher = "releases" - } else { - return "", "", "", NewErrorWithStatusLookup(400, "malformed github path: not a releases download url") - } - case "archive": - matcher = "releases" case "blob": matcher = "blob" case "raw": @@ -88,59 +113,27 @@ func Matcher(rawPath string, cfg *config.Config) (string, string, string, *GHPro // 匹配 "https://raw.githubusercontent.com/" if strings.HasPrefix(rawPath, rawPrefix) { remaining := rawPath[rawPrefixLen:] - // 这里的逻辑与 github.com 的类似, 需要提取 user, repo, branch, file... - // 我们只需要 user 和 repo - i := strings.IndexByte(remaining, '/') - if i <= 0 { - return "", "", "", NewErrorWithStatusLookup(400, "malformed raw url: missing user") + parts := strings.SplitN(remaining, "/", 3) + if len(parts) < 3 { + return "", "", "", NewErrorWithStatusLookup(400, "malformed raw url: path too short") } - user := remaining[:i] - remaining = remaining[i+1:] - i = strings.IndexByte(remaining, '/') - if i <= 0 { - return "", "", "", NewErrorWithStatusLookup(400, "malformed raw url: missing repo") - } - repo := remaining[:i] - // raw 链接至少需要 user/repo/branch 三部分 - remaining = remaining[i+1:] - if len(remaining) == 0 { - return "", "", "", NewErrorWithStatusLookup(400, "malformed raw url: missing branch/commit") - } - return user, repo, "raw", nil + return parts[0], parts[1], "raw", nil } - // 匹配 "https://gist.github.com/" - if strings.HasPrefix(rawPath, gistPrefix) { - remaining := rawPath[gistPrefixLen:] - i := strings.IndexByte(remaining, '/') - if i <= 0 { - // case: https://gist.github.com/user - // 这种情况下, gist_id 缺失, 但我们仍然可以认为 user 是有效的 - if len(remaining) > 0 { - return remaining, "", "gist", nil - } + // 匹配 "https://gist.github.com/" 或 "https://gist.githubusercontent.com/" + isGist := strings.HasPrefix(rawPath, gistPrefix) + if isGist || strings.HasPrefix(rawPath, gistContentPrefix) { + var remaining string + if isGist { + remaining = rawPath[gistPrefixLen:] + } else { + remaining = rawPath[gistContentPrefixLen:] + } + parts := strings.SplitN(remaining, "/", 2) + if len(parts) == 0 || parts[0] == "" { return "", "", "", NewErrorWithStatusLookup(400, "malformed gist url: missing user") } - // case: https://gist.github.com/user/gist_id... - user := remaining[:i] - return user, "", "gist", nil - } - - // 匹配 "https://gist.githubusercontent.com/" - if strings.HasPrefix(rawPath, gistContentPrefix) { - remaining := rawPath[gistContentPrefixLen:] - i := strings.IndexByte(remaining, '/') - if i <= 0 { - // case: https://gist.githubusercontent.com/user - // 这种情况下, gist_id 缺失, 但我们仍然可以认为 user 是有效的 - if len(remaining) > 0 { - return remaining, "", "gist", nil - } - return "", "", "", NewErrorWithStatusLookup(400, "malformed gist url: missing user") - } - // case: https://gist.githubusercontent.com/user/gist_id... - user := remaining[:i] - return user, "", "gist", nil + return parts[0], "", "gist", nil } // 匹配 "https://api.github.com/" diff --git a/proxy/matcher_test.go b/proxy/matcher_test.go index 4b260c2..07f3e4a 100644 --- a/proxy/matcher_test.go +++ b/proxy/matcher_test.go @@ -33,11 +33,17 @@ func TestMatcher_Compatibility(t *testing.T) { expectedErrCode int }{ { - name: "GH Releases Path", + name: "GH Releases Path 1", rawPath: "https://github.com/owner/repo/releases/download/v1.0/asset.zip", config: cfgWithAuth, expectedUser: "owner", expectedRepo: "repo", expectedMatcher: "releases", }, + { + name: "GH Releases Path 2", + rawPath: "https://github.com/owner/repo/releases/v1.0/download/asset.zip", + config: cfgWithAuth, + expectedUser: "owner", expectedRepo: "repo", expectedMatcher: "releases", + }, { name: "GH Releases Path Page", rawPath: "https://github.com/owner/repo/releases",