This commit is contained in:
wjqserver
2025-07-31 20:01:03 +08:00
parent 44f28e593a
commit 208ce8a4f9
5 changed files with 80 additions and 72 deletions

View File

@@ -1,5 +1,9 @@
# 更新日志 # 更新日志
4.2.5 - 2025-07-31
---
- CHANGE: 进一步完善匹配器, 兼容更多情况
4.2.4 - 2025-07-29 4.2.4 - 2025-07-29
--- ---
- CHANGE: 改进匹配器, 防止匹配不应匹配的内容 - CHANGE: 改进匹配器, 防止匹配不应匹配的内容

View File

@@ -1 +1 @@
4.2.4 4.2.5

View File

@@ -399,6 +399,11 @@ func main() {
proxy.RoutingHandler(cfg)(c) 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) { r.GET("/github.com/:user/:repo/archive/*filepath", func(c *touka.Context) {
c.Set("matcher", "releases") c.Set("matcher", "releases")
proxy.RoutingHandler(cfg)(c) proxy.RoutingHandler(cfg)(c)

View File

@@ -42,37 +42,62 @@ func Matcher(rawPath string, cfg *config.Config) (string, string, string, *GHPro
// 匹配 "https://github.com/" // 匹配 "https://github.com/"
if strings.HasPrefix(rawPath, githubPrefix) { if strings.HasPrefix(rawPath, githubPrefix) {
remaining := rawPath[githubPrefixLen:] pathAfterDomain := rawPath[githubPrefixLen:]
i := strings.IndexByte(remaining, '/')
// 解析 user
i := strings.IndexByte(pathAfterDomain, '/')
if i <= 0 { if i <= 0 {
return "", "", "", NewErrorWithStatusLookup(400, "malformed github path: missing user") return "", "", "", NewErrorWithStatusLookup(400, "malformed github path: missing user")
} }
user := remaining[:i] user := pathAfterDomain[:i]
remaining = remaining[i+1:] pathAfterUser := pathAfterDomain[i+1:]
i = strings.IndexByte(remaining, '/')
// 解析 repo
i = strings.IndexByte(pathAfterUser, '/')
if i <= 0 { 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") return "", "", "", NewErrorWithStatusLookup(400, "malformed github path: missing action")
} }
i = strings.IndexByte(remaining, '/') repo := pathAfterUser[:i]
action := remaining pathAfterRepo := pathAfterUser[i+1:]
if i != -1 {
action = remaining[:i] 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 var matcher string
switch action { 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": case "blob":
matcher = "blob" matcher = "blob"
case "raw": case "raw":
@@ -88,59 +113,27 @@ func Matcher(rawPath string, cfg *config.Config) (string, string, string, *GHPro
// 匹配 "https://raw.githubusercontent.com/" // 匹配 "https://raw.githubusercontent.com/"
if strings.HasPrefix(rawPath, rawPrefix) { if strings.HasPrefix(rawPath, rawPrefix) {
remaining := rawPath[rawPrefixLen:] remaining := rawPath[rawPrefixLen:]
// 这里的逻辑与 github.com 的类似, 需要提取 user, repo, branch, file... parts := strings.SplitN(remaining, "/", 3)
// 我们只需要 user 和 repo if len(parts) < 3 {
i := strings.IndexByte(remaining, '/') return "", "", "", NewErrorWithStatusLookup(400, "malformed raw url: path too short")
if i <= 0 {
return "", "", "", NewErrorWithStatusLookup(400, "malformed raw url: missing user")
} }
user := remaining[:i] return parts[0], parts[1], "raw", nil
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
} }
// 匹配 "https://gist.github.com/" // 匹配 "https://gist.github.com/" 或 "https://gist.githubusercontent.com/"
if strings.HasPrefix(rawPath, gistPrefix) { isGist := strings.HasPrefix(rawPath, gistPrefix)
remaining := rawPath[gistPrefixLen:] if isGist || strings.HasPrefix(rawPath, gistContentPrefix) {
i := strings.IndexByte(remaining, '/') var remaining string
if i <= 0 { if isGist {
// case: https://gist.github.com/user remaining = rawPath[gistPrefixLen:]
// 这种情况下, gist_id 缺失, 但我们仍然可以认为 user 是有效的 } else {
if len(remaining) > 0 { remaining = rawPath[gistContentPrefixLen:]
return remaining, "", "gist", nil
} }
parts := strings.SplitN(remaining, "/", 2)
if len(parts) == 0 || parts[0] == "" {
return "", "", "", NewErrorWithStatusLookup(400, "malformed gist url: missing user") return "", "", "", NewErrorWithStatusLookup(400, "malformed gist url: missing user")
} }
// case: https://gist.github.com/user/gist_id... return parts[0], "", "gist", nil
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
} }
// 匹配 "https://api.github.com/" // 匹配 "https://api.github.com/"

View File

@@ -33,11 +33,17 @@ func TestMatcher_Compatibility(t *testing.T) {
expectedErrCode int expectedErrCode int
}{ }{
{ {
name: "GH Releases Path", name: "GH Releases Path 1",
rawPath: "https://github.com/owner/repo/releases/download/v1.0/asset.zip", rawPath: "https://github.com/owner/repo/releases/download/v1.0/asset.zip",
config: cfgWithAuth, config: cfgWithAuth,
expectedUser: "owner", expectedRepo: "repo", expectedMatcher: "releases", 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", name: "GH Releases Path Page",
rawPath: "https://github.com/owner/repo/releases", rawPath: "https://github.com/owner/repo/releases",