mirror of
https://github.com/WJQSERVER-STUDIO/ghproxy.git
synced 2026-02-03 00:01:10 +08:00
234 lines
8.4 KiB
Go
234 lines
8.4 KiB
Go
package proxy
|
|
|
|
import (
|
|
"fmt"
|
|
"ghproxy/auth"
|
|
"ghproxy/config"
|
|
"ghproxy/rate"
|
|
"net/http"
|
|
"regexp"
|
|
"strings"
|
|
|
|
"github.com/gin-gonic/gin"
|
|
)
|
|
|
|
var re = regexp.MustCompile(`^(http:|https:)?/?/?(.*)`) // 匹配http://或https://开头的路径
|
|
/*
|
|
var exps = []*regexp.Regexp{
|
|
regexp.MustCompile(`^(?:https?://)?github\.com/([^/]+)/([^/]+)/(?:releases|archive)/.*`), // 匹配 GitHub Releases 或 Archive 链接
|
|
regexp.MustCompile(`^(?:https?://)?github\.com/([^/]+)/([^/]+)/(?:blob|raw)/.*`), // 匹配 GitHub Blob 或 Raw 链接
|
|
regexp.MustCompile(`^(?:https?://)?github\.com/([^/]+)/([^/]+)/(?:info|git-).*`), // 匹配 GitHub Info 或 Git 相关链接 (例如 .gitattributes, .gitignore)
|
|
regexp.MustCompile(`^(?:https?://)?raw\.github(?:usercontent|)\.com/([^/]+)/([^/]+)/.+?/.+`), // 匹配 raw.githubusercontent.com 链接
|
|
regexp.MustCompile(`^(?:https?://)?gist\.github(?:usercontent|)\.com/([^/]+)/.+?/.+`), // 匹配 gist.githubusercontent.com 链接
|
|
regexp.MustCompile(`^(?:https?://)?api\.github\.com/repos/([^/]+)/([^/]+)/.*`), // 匹配 api.github.com/repos 链接 (GitHub API)
|
|
}
|
|
*/
|
|
|
|
func NoRouteHandler(cfg *config.Config, limiter *rate.RateLimiter, iplimiter *rate.IPRateLimiter, runMode string) gin.HandlerFunc {
|
|
return func(c *gin.Context) {
|
|
|
|
// 限制访问频率
|
|
if cfg.RateLimit.Enabled {
|
|
|
|
var allowed bool
|
|
|
|
switch cfg.RateLimit.RateMethod {
|
|
case "ip":
|
|
allowed = iplimiter.Allow(c.ClientIP())
|
|
case "total":
|
|
allowed = limiter.Allow()
|
|
default:
|
|
logWarning("Invalid RateLimit Method")
|
|
return
|
|
}
|
|
|
|
if !allowed {
|
|
c.JSON(http.StatusTooManyRequests, gin.H{"error": "Too Many Requests"})
|
|
logWarning("%s %s %s %s %s 429-TooManyRequests", c.ClientIP(), c.Request.Method, c.Request.URL.RequestURI(), c.Request.Header.Get("User-Agent"), c.Request.Proto)
|
|
return
|
|
}
|
|
}
|
|
|
|
//rawPath := strings.TrimPrefix(c.Request.URL.Path, "/") // 去掉前缀/
|
|
rawPath := strings.TrimPrefix(c.Request.URL.RequestURI(), "/") // 去掉前缀/
|
|
matches := re.FindStringSubmatch(rawPath) // 匹配路径
|
|
logInfo("Matches: %v", matches)
|
|
|
|
// 匹配路径错误处理
|
|
if len(matches) < 3 {
|
|
errMsg := fmt.Sprintf("%s %s %s %s %s Invalid URL", c.ClientIP(), c.Request.Method, rawPath, c.Request.Header.Get("User-Agent"), c.Request.Proto)
|
|
logWarning(errMsg)
|
|
c.String(http.StatusForbidden, "Invalid URL Format. Path: %s", rawPath)
|
|
return
|
|
}
|
|
|
|
// 制作url
|
|
rawPath = "https://" + matches[2]
|
|
|
|
var (
|
|
user string
|
|
repo string
|
|
matcher string
|
|
)
|
|
|
|
// 匹配 "https://github.com"开头的链接
|
|
if strings.HasPrefix(rawPath, "https://github.com") {
|
|
remainingPath := strings.TrimPrefix(rawPath, "https://github.com")
|
|
if strings.HasPrefix(remainingPath, "/") {
|
|
remainingPath = strings.TrimPrefix(remainingPath, "/")
|
|
}
|
|
// 预期格式/user/repo/more...
|
|
// 取出user和repo和最后部分
|
|
parts := strings.Split(remainingPath, "/")
|
|
if len(parts) <= 2 {
|
|
logWarning("%s %s %s %s %s Invalid URL", c.ClientIP(), c.Request.Method, rawPath, c.Request.Header.Get("User-Agent"), c.Request.Proto)
|
|
c.String(http.StatusForbidden, "Invalid URL Format. Path: %s", rawPath)
|
|
return
|
|
}
|
|
user = parts[0]
|
|
repo = parts[1]
|
|
// 匹配 "https://github.com"开头的链接
|
|
if len(parts) >= 3 {
|
|
switch parts[2] {
|
|
case "releases", "archive":
|
|
matcher = "releases"
|
|
case "blob", "raw":
|
|
matcher = "blob"
|
|
case "info", "git-upload-pack":
|
|
matcher = "clone"
|
|
default:
|
|
fmt.Println("Invalid URL: Unknown type")
|
|
}
|
|
}
|
|
}
|
|
// 匹配 "https://raw"开头的链接
|
|
if strings.HasPrefix(rawPath, "https://raw") {
|
|
remainingPath := strings.TrimPrefix(rawPath, "https://")
|
|
parts := strings.Split(remainingPath, "/")
|
|
if len(parts) <= 3 {
|
|
logWarning("%s %s %s %s %s Invalid URL", c.ClientIP(), c.Request.Method, rawPath, c.Request.Header.Get("User-Agent"), c.Request.Proto)
|
|
c.String(http.StatusForbidden, "Invalid URL Format. Path: %s", rawPath)
|
|
return
|
|
}
|
|
user = parts[1]
|
|
repo = parts[2]
|
|
matcher = "raw"
|
|
}
|
|
// 匹配 "https://gist"开头的链接
|
|
if strings.HasPrefix(rawPath, "https://gist") {
|
|
remainingPath := strings.TrimPrefix(rawPath, "https://")
|
|
parts := strings.Split(remainingPath, "/")
|
|
if len(parts) <= 3 {
|
|
logWarning("%s %s %s %s %s Invalid URL", c.ClientIP(), c.Request.Method, rawPath, c.Request.Header.Get("User-Agent"), c.Request.Proto)
|
|
c.String(http.StatusForbidden, "Invalid URL Format. Path: %s", rawPath)
|
|
return
|
|
}
|
|
user = parts[1]
|
|
matcher = "gist"
|
|
}
|
|
// 匹配 "https://api.github.com/"开头的链接
|
|
if strings.HasPrefix(rawPath, "https://api.github.com/") {
|
|
matcher = "api"
|
|
remainingPath := strings.TrimPrefix(rawPath, "https://api.github.com/")
|
|
|
|
parts := strings.Split(remainingPath, "/")
|
|
if parts[0] == "repos" {
|
|
user = parts[1]
|
|
repo = parts[2]
|
|
}
|
|
if parts[0] == "users" {
|
|
user = parts[1]
|
|
}
|
|
if cfg.Auth.AuthMethod != "header" || !cfg.Auth.Enabled {
|
|
c.JSON(http.StatusForbidden, gin.H{"error": "HeaderAuth is not enabled."})
|
|
logError("%s %s %s %s %s HeaderAuth-Error: HeaderAuth is not enabled.", c.ClientIP(), c.Request.Method, rawPath, c.Request.Header.Get("User-Agent"), c.Request.Proto)
|
|
return
|
|
}
|
|
}
|
|
|
|
username := user
|
|
//username, repo := MatchUserRepo(rawPath, cfg, c, matches) // 匹配用户名和仓库名
|
|
|
|
logInfo("%s %s %s %s %s Matched-Username: %s, Matched-Repo: %s", c.ClientIP(), c.Request.Method, rawPath, c.Request.Header.Get("User-Agent"), c.Request.Proto, username, repo)
|
|
// dump log 记录详细信息 c.ClientIP(), c.Request.Method, rawPath, c.Request.Header.Get("User-Agent"), c.Request.Proto, full Header
|
|
logDump("%s %s %s %s %s %s", c.ClientIP(), c.Request.Method, rawPath, c.Request.Header.Get("User-Agent"), c.Request.Proto, c.Request.Header)
|
|
repouser := fmt.Sprintf("%s/%s", username, repo)
|
|
|
|
// 白名单检查
|
|
if cfg.Whitelist.Enabled {
|
|
whitelist := auth.CheckWhitelist(username, repo)
|
|
if !whitelist {
|
|
logErrMsg := fmt.Sprintf("%s %s %s %s %s Whitelist Blocked repo: %s", c.ClientIP(), c.Request.Method, rawPath, c.Request.Header.Get("User-Agent"), c.Request.Proto, repouser)
|
|
errMsg := fmt.Sprintf("Whitelist Blocked repo: %s", repouser)
|
|
c.JSON(http.StatusForbidden, gin.H{"error": errMsg})
|
|
logWarning(logErrMsg)
|
|
return
|
|
}
|
|
}
|
|
|
|
// 黑名单检查
|
|
if cfg.Blacklist.Enabled {
|
|
blacklist := auth.CheckBlacklist(username, repo)
|
|
if blacklist {
|
|
logErrMsg := fmt.Sprintf("%s %s %s %s %s Blacklist Blocked repo: %s", c.ClientIP(), c.Request.Method, rawPath, c.Request.Header.Get("User-Agent"), c.Request.Proto, repouser)
|
|
errMsg := fmt.Sprintf("Blacklist Blocked repo: %s", repouser)
|
|
c.JSON(http.StatusForbidden, gin.H{"error": errMsg})
|
|
logWarning(logErrMsg)
|
|
return
|
|
}
|
|
}
|
|
|
|
/*
|
|
matches = CheckURL(rawPath, c)
|
|
if matches == nil {
|
|
c.AbortWithStatus(http.StatusNotFound)
|
|
logWarning("%s %s %s %s %s 404-NOMATCH", c.ClientIP(), c.Request.Method, rawPath, c.Request.Header.Get("User-Agent"), c.Request.Proto)
|
|
return
|
|
}
|
|
*/
|
|
|
|
// 若匹配api.github.com/repos/用户名/仓库名/路径, 则检查是否开启HeaderAuth
|
|
|
|
// 处理blob/raw路径
|
|
if matcher == "blob" {
|
|
rawPath = strings.Replace(rawPath, "/blob/", "/raw/", 1)
|
|
}
|
|
|
|
// 鉴权
|
|
authcheck, err := auth.AuthHandler(c, cfg)
|
|
if !authcheck {
|
|
c.AbortWithStatusJSON(401, gin.H{"error": "Unauthorized"})
|
|
logWarning("%s %s %s %s %s Auth-Error: %v", c.ClientIP(), c.Request.Method, rawPath, c.Request.Header.Get("User-Agent"), c.Request.Proto, err)
|
|
return
|
|
}
|
|
|
|
// IP METHOD URL USERAGENT PROTO MATCHES
|
|
logDebug("%s %s %s %s %s Matches: %v", c.ClientIP(), c.Request.Method, rawPath, c.Request.Header.Get("User-Agent"), c.Request.Proto, matches)
|
|
|
|
switch matcher {
|
|
case "releases", "blob", "raw", "gist", "api":
|
|
//ProxyRequest(c, rawPath, cfg, "chrome", runMode)
|
|
ChunkedProxyRequest(c, rawPath, cfg, "chrome", runMode) // dev test chunk
|
|
case "clone":
|
|
//ProxyRequest(c, rawPath, cfg, "git", runMode)
|
|
GitReq(c, rawPath, cfg, "git", runMode)
|
|
default:
|
|
c.String(http.StatusForbidden, "Invalid input.")
|
|
fmt.Println("Invalid input.")
|
|
return
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
func CheckURL(u string, c *gin.Context) []string {
|
|
for _, exp := range exps {
|
|
if matches := exp.FindStringSubmatch(u); matches != nil {
|
|
return matches[1:]
|
|
}
|
|
}
|
|
errMsg := fmt.Sprintf("%s %s %s %s %s Invalid URL", c.ClientIP(), c.Request.Method, u, c.Request.Header.Get("User-Agent"), c.Request.Proto)
|
|
logError(errMsg)
|
|
return nil
|
|
}
|
|
*/
|