Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d18983e23d | ||
|
|
bc23f5039a |
No files matched your search
@@ -25,7 +25,8 @@ const (
|
||||
)
|
||||
|
||||
var (
|
||||
exps = []*regexp.Regexp{
|
||||
// 入口链接:用户可直接粘贴的 GitHub 链接。命中这些的 302 会被重写为代理地址。
|
||||
entryExps = []*regexp.Regexp{
|
||||
regexp.MustCompile(`^(?:https?://)?github\.com/([^/]+)/([^/]+)/(?:releases|archive)/.*$`),
|
||||
regexp.MustCompile(`^(?:https?://)?github\.com/([^/]+)/([^/]+)/(?:blob|raw)/.*$`),
|
||||
regexp.MustCompile(`^(?:https?://)?github\.com/([^/]+)/([^/]+)/(?:info|git-).*$`),
|
||||
@@ -33,6 +34,20 @@ var (
|
||||
regexp.MustCompile(`^(?:https?://)?gist\.github(?:usercontent|)\.com/([^/]+)/.+?/.+$`),
|
||||
regexp.MustCompile(`^(?:https?://)?api\.github\.com/.+?/([^/]+)(?:/.*)?$`),
|
||||
}
|
||||
// 新增:GitHub 资源 CDN 主机(release 资产签名直链 / 归档分流 / LFS 媒体)。
|
||||
// 这些链接可以直接代理,但**不参与 Location 重写**——保证 github.com 的 302
|
||||
// 仍在服务端内部跟随,客户端拿到的始终是文件内容而不是真实文件地址。
|
||||
assetExps = []*regexp.Regexp{
|
||||
// release-assets:新版 release 资产签名直链(302 目标)
|
||||
// objects / github-releases:旧版资产 CDN 主机(兼容历史链接)
|
||||
regexp.MustCompile(`^(?:https?://)?(?:release-assets|objects|github-releases)\.githubusercontent\.com/([^/]+).*$`),
|
||||
// codeload:源码归档(archive / tar.gz / zip)分流主机
|
||||
regexp.MustCompile(`^(?:https?://)?codeload\.github\.com/([^/]+)/([^/]+)/.*$`),
|
||||
// media:Git LFS 媒体文件
|
||||
regexp.MustCompile(`^(?:https?://)?media\.githubusercontent\.com/(?:media/)?([^/]+)/([^/]+)/.*$`),
|
||||
}
|
||||
// checkURL 使用:入口 + 资源 CDN
|
||||
exps = append(append([]*regexp.Regexp{}, entryExps...), assetExps...)
|
||||
httpClient *http.Client
|
||||
config *Config
|
||||
configLock sync.RWMutex
|
||||
@@ -196,7 +211,7 @@ func handler(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
if exps[1].MatchString(rawPath) {
|
||||
if entryExps[1].MatchString(rawPath) {
|
||||
rawPath = strings.Replace(rawPath, "/blob/", "/raw/", 1)
|
||||
}
|
||||
|
||||
@@ -216,8 +231,8 @@ func handler(c *gin.Context) {
|
||||
ipRequests[ip] = recentTimes
|
||||
}
|
||||
log.Debugf("clientIP: %s Rate: %d\n", clientIP, len(ipRequests[clientIP]))
|
||||
// 检查当前 IP 的请求次数是否超过限制
|
||||
if len(ipRequests[clientIP]) > int(limitRate) {
|
||||
// 检查当前 IP 的请求次数是否超过限制(如果 limitRate <= 0 则不限制)
|
||||
if limitRate > 0 && len(ipRequests[clientIP]) > int(limitRate) {
|
||||
ipLock.Unlock()
|
||||
c.String(http.StatusTooManyRequests, "Too Many Requests.")
|
||||
return
|
||||
@@ -292,7 +307,8 @@ func proxy(c *gin.Context, u string) {
|
||||
limitSize := config.RequestLimit.LimitSize * 1024 * 1024 // Convert MB to Bytes
|
||||
configLock.RUnlock()
|
||||
|
||||
if size > limitSize {
|
||||
// 如果 limitSize <= 0 则不限制文件大小
|
||||
if limitSize > 0 && size > limitSize {
|
||||
c.String(http.StatusRequestEntityTooLarge, "File too large.")
|
||||
return
|
||||
}
|
||||
@@ -302,6 +318,12 @@ func proxy(c *gin.Context, u string) {
|
||||
resp.Header.Del("Referrer-Policy")
|
||||
resp.Header.Del("Strict-Transport-Security")
|
||||
|
||||
// 如果 GitHub 返回 404,显示我们的 404 页面
|
||||
if resp.StatusCode == http.StatusNotFound {
|
||||
render404(c, tmpl404)
|
||||
return
|
||||
}
|
||||
|
||||
for key, values := range resp.Header {
|
||||
for _, value := range values {
|
||||
c.Header(key, value)
|
||||
@@ -309,7 +331,9 @@ func proxy(c *gin.Context, u string) {
|
||||
}
|
||||
|
||||
if location := resp.Header.Get("Location"); location != "" {
|
||||
if checkURL(location) != nil {
|
||||
// 仅入口链接重写为代理地址;资源 CDN 等跳转目标在服务端内部跟随,
|
||||
// 直接返回文件内容(客户端看不到真实文件地址)。
|
||||
if isEntryURL(location) {
|
||||
c.Header("Location", "/"+location)
|
||||
} else {
|
||||
proxy(c, location)
|
||||
@@ -363,6 +387,17 @@ func checkURL(u string) []string {
|
||||
return nil
|
||||
}
|
||||
|
||||
// isEntryURL 判断是否为「入口」链接(github.com / raw / gist / api)。
|
||||
// 用于决定 302 是否重写为代理地址;资源 CDN 链接不在其中。
|
||||
func isEntryURL(u string) bool {
|
||||
for _, exp := range entryExps {
|
||||
if exp.MatchString(u) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func checkList(matches, list []string) bool {
|
||||
for _, item := range list {
|
||||
if strings.HasPrefix(matches[0], item) {
|
||||
|
||||
@@ -0,0 +1,61 @@
|
||||
package main
|
||||
|
||||
import "testing"
|
||||
|
||||
func TestCheckURL(t *testing.T) {
|
||||
shouldMatch := []string{
|
||||
"https://release-assets.githubusercontent.com/github-production-release-asset/1/abc?sig=x",
|
||||
"https://objects.githubusercontent.com/foo/bar",
|
||||
"https://github-releases.githubusercontent.com/foo/bar",
|
||||
"https://codeload.github.com/cli/cli/tar.gz/refs/tags/v1",
|
||||
"https://media.githubusercontent.com/media/octocat/Hello-World/master/README",
|
||||
"https://media.githubusercontent.com/octocat/Hello-World/master/README",
|
||||
"https://github.com/a/b/releases/download/v1/x.zip",
|
||||
"https://raw.githubusercontent.com/a/b/main/c.txt",
|
||||
"https://api.github.com/repos/a/b",
|
||||
}
|
||||
for _, u := range shouldMatch {
|
||||
if checkURL(u) == nil {
|
||||
t.Errorf("checkURL 应命中但未命中: %s", u)
|
||||
}
|
||||
}
|
||||
shouldNotMatch := []string{
|
||||
"https://evil.example.com/a.zip",
|
||||
"https://example.com/",
|
||||
"https://github.com.evil.com/a/b",
|
||||
"https://notcodeload.github.com/a/b",
|
||||
}
|
||||
for _, u := range shouldNotMatch {
|
||||
if checkURL(u) != nil {
|
||||
t.Errorf("checkURL 不应命中但命中了: %s", u)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestIsEntryURL(t *testing.T) {
|
||||
// 入口:只有这些才允许重写 Location 返回客户端
|
||||
entry := []string{
|
||||
"https://github.com/a/b/releases/download/v1/x.zip",
|
||||
"https://raw.githubusercontent.com/a/b/main/c.txt",
|
||||
"https://gist.githubusercontent.com/u/1/raw/x.py",
|
||||
"https://api.github.com/repos/a/b",
|
||||
}
|
||||
for _, u := range entry {
|
||||
if !isEntryURL(u) {
|
||||
t.Errorf("isEntryURL 应命中: %s", u)
|
||||
}
|
||||
}
|
||||
// 非入口:资源 CDN 必须在服务端内部跟随,不能重写给客户端
|
||||
notEntry := []string{
|
||||
"https://release-assets.githubusercontent.com/github-production-release-asset/1/abc?sig=x",
|
||||
"https://objects.githubusercontent.com/foo/bar",
|
||||
"https://github-releases.githubusercontent.com/foo/bar",
|
||||
"https://codeload.github.com/cli/cli/tar.gz/refs/tags/v1",
|
||||
"https://media.githubusercontent.com/media/octocat/Hello-World/master/README",
|
||||
}
|
||||
for _, u := range notEntry {
|
||||
if isEntryURL(u) {
|
||||
t.Errorf("isEntryURL 不应命中(否则会泄露真实文件地址): %s", u)
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user