diff --git a/main.go b/main.go index 50df92e..6e557c2 100644 --- a/main.go +++ b/main.go @@ -404,56 +404,54 @@ func main() { os.Exit(1) } - // 添加Recovery中间件 - r.Use(recovery.Recovery()) - // 添加log中间件 - r.Use(loggin.Middleware()) - + r.Use(recovery.Recovery()) // Recovery中间件 + r.Use(loggin.Middleware()) // log中间件 setupApi(cfg, r, version) - setupPages(cfg, r) - /* - // 1. GitHub Releases/Archive - Use distinct path segments for type - r.GET("/github.com/:username/:repo/releases/*filepath", func(ctx context.Context, c *app.RequestContext) { // Distinct path for releases - proxy.NoRouteHandler(cfg, limiter, iplimiter)(ctx, c) - }) + r.GET("/github.com/:username/:repo/releases/*filepath", func(ctx context.Context, c *app.RequestContext) { + ctx = context.WithValue(ctx, "matcher", "release") + proxy.RoutingHandler(cfg, limiter, iplimiter)(ctx, c) + }) - r.GET("/github.com/:username/:repo/archive/*filepath", func(ctx context.Context, c *app.RequestContext) { // Distinct path for archive - proxy.NoRouteHandler(cfg, limiter, iplimiter)(ctx, c) - }) + r.GET("/github.com/:username/:repo/archive/*filepath", func(ctx context.Context, c *app.RequestContext) { + ctx = context.WithValue(ctx, "matcher", "release") + proxy.RoutingHandler(cfg, limiter, iplimiter)(ctx, c) + }) - // 2. GitHub Blob/Raw - Use distinct path segments for type - r.GET("/github.com/:username/:repo/blob/*filepath", func(ctx context.Context, c *app.RequestContext) { // Distinct path for blob - proxy.NoRouteHandler(cfg, limiter, iplimiter)(ctx, c) - }) + r.GET("/github.com/:username/:repo/blob/*filepath", func(ctx context.Context, c *app.RequestContext) { + ctx = context.WithValue(ctx, "matcher", "blob") + proxy.RoutingHandler(cfg, limiter, iplimiter)(ctx, c) + }) - r.GET("/github.com/:username/:repo/raw/*filepath", func(ctx context.Context, c *app.RequestContext) { // Distinct path for raw - proxy.NoRouteHandler(cfg, limiter, iplimiter)(ctx, c) - }) + r.GET("/github.com/:username/:repo/raw/*filepath", func(ctx context.Context, c *app.RequestContext) { + ctx = context.WithValue(ctx, "matcher", "raw") + proxy.RoutingHandler(cfg, limiter, iplimiter)(ctx, c) + }) - r.GET("/github.com/:username/:repo/info/*filepath", func(ctx context.Context, c *app.RequestContext) { // Distinct path for info - proxy.NoRouteHandler(cfg, limiter, iplimiter)(ctx, c) - }) - r.GET("/github.com/:username/:repo/git-upload-pack", func(ctx context.Context, c *app.RequestContext) { - proxy.NoRouteHandler(cfg, limiter, iplimiter)(ctx, c) - }) + r.GET("/github.com/:username/:repo/info/*filepath", func(ctx context.Context, c *app.RequestContext) { + ctx = context.WithValue(ctx, "matcher", "gitclone") + proxy.RoutingHandler(cfg, limiter, iplimiter)(ctx, c) + }) + r.GET("/github.com/:username/:repo/git-upload-pack", func(ctx context.Context, c *app.RequestContext) { + ctx = context.WithValue(ctx, "matcher", "gitclone") + proxy.RoutingHandler(cfg, limiter, iplimiter)(ctx, c) + }) - // 4. Raw GitHubusercontent - Keep as is (assuming it's distinct enough) - r.GET("/raw.githubusercontent.com/:username/:repo/*filepath", func(ctx context.Context, c *app.RequestContext) { - proxy.NoRouteHandler(cfg, limiter, iplimiter)(ctx, c) - }) + r.GET("/raw.githubusercontent.com/:username/:repo/*filepath", func(ctx context.Context, c *app.RequestContext) { + ctx = context.WithValue(ctx, "matcher", "raw") + proxy.RoutingHandler(cfg, limiter, iplimiter)(ctx, c) + }) - // 5. Gist GitHubusercontent - Keep as is (assuming it's distinct enough) - r.GET("/gist.githubusercontent.com/:username/*filepath", func(ctx context.Context, c *app.RequestContext) { - proxy.NoRouteHandler(cfg, limiter, iplimiter)(ctx, c) - }) + r.GET("/gist.githubusercontent.com/:username/*filepath", func(ctx context.Context, c *app.RequestContext) { + ctx = context.WithValue(ctx, "matcher", "gist") + proxy.NoRouteHandler(cfg, limiter, iplimiter)(ctx, c) + }) - // 6. GitHub API Repos - Keep as is (assuming it's distinct enough) - r.GET("/api.github.com/repos/:username/:repo/*filepath", func(ctx context.Context, c *app.RequestContext) { - proxy.NoRouteHandler(cfg, limiter, iplimiter)(ctx, c) - }) - */ + r.GET("/api.github.com/repos/:username/:repo/*filepath", func(ctx context.Context, c *app.RequestContext) { + ctx = context.WithValue(ctx, "matcher", "api") + proxy.RoutingHandler(cfg, limiter, iplimiter)(ctx, c) + }) r.NoRoute(func(ctx context.Context, c *app.RequestContext) { proxy.NoRouteHandler(cfg, limiter, iplimiter)(ctx, c) diff --git a/proxy/handler.go b/proxy/handler.go index 9d214d3..9e1ced0 100644 --- a/proxy/handler.go +++ b/proxy/handler.go @@ -147,3 +147,123 @@ func NoRouteHandler(cfg *config.Config, limiter *rate.RateLimiter, iplimiter *ra } } } + +func RoutingHandler(cfg *config.Config, limiter *rate.RateLimiter, iplimiter *rate.IPRateLimiter) app.HandlerFunc { + return func(ctx context.Context, c *app.RequestContext) { + // 输出所有传入参数 + logDebug("All Request Params: %v", c.Params) + logDebug("Context Params(Matcher): %v", ctx.Value("matcher")) + + // 限制访问频率 + 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, map[string]string{"error": "Too Many Requests"}) + logWarning("%s %s %s %s %s 429-TooManyRequests", c.ClientIP(), c.Method(), c.Request.RequestURI(), c.Request.Header.UserAgent(), c.Request.Header.GetProtocol()) + return + } + } + + var ( + rawPath string + errMsg string + ) + + rawPath = strings.TrimPrefix(string(c.Request.RequestURI()), "/") // 去掉前缀/ + + var ( + user string + repo string + matcher string + err error + ) + + user = c.Param("user") + repo = c.Param("repo") + matcher = ctx.Value("matcher").(string) + + logInfo("%s %s %s %s %s Matched-Username: %s, Matched-Repo: %s", c.ClientIP(), c.Method(), rawPath, c.Request.Header.UserAgent(), c.Request.Header.GetProtocol(), user, repo) + // dump log 记录详细信息 c.ClientIP(), c.Method(), rawPath,c.Request.Header.UserAgent(), c.Request.Header.GetProtocol(), full Header + logDump("%s %s %s %s %s %s", c.ClientIP(), c.Method(), rawPath, c.Request.Header.UserAgent(), c.Request.Header.GetProtocol(), c.Request.Header.Header()) + + // 白名单检查 + if cfg.Whitelist.Enabled { + var whitelist bool + whitelist = auth.CheckWhitelist(user, repo) + if !whitelist { + errMsg = fmt.Sprintf("Whitelist Blocked repo: %s/%s", user, repo) + c.JSON(http.StatusForbidden, map[string]string{"error": errMsg}) + logWarning("%s %s %s %s %s Whitelist Blocked repo: %s/%s", c.ClientIP(), c.Method(), rawPath, c.Request.Header.UserAgent(), c.Request.Header.GetProtocol(), user, repo) + return + } + } + + // 黑名单检查 + if cfg.Blacklist.Enabled { + var blacklist bool + blacklist = auth.CheckBlacklist(user, repo) + if blacklist { + errMsg = fmt.Sprintf("Blacklist Blocked repo: %s/%s", user, repo) + c.JSON(http.StatusForbidden, map[string]string{"error": errMsg}) + logWarning("%s %s %s %s %s Blacklist Blocked repo: %s/%s", c.ClientIP(), c.Method(), rawPath, c.Request.Header.UserAgent(), c.Request.Header.GetProtocol(), user, repo) + return + } + } + + if matcher == "api" && !cfg.Auth.ForceAllowApi { + if cfg.Auth.Method != "header" || !cfg.Auth.Enabled { + c.JSON(http.StatusForbidden, map[string]string{"error": "Github API Req without AuthHeader is Not Allowed"}) + logWarning("%s %s %s %s %s AuthHeader Unavailable", c.ClientIP(), c.Method(), rawPath) + return + } + } + + // 鉴权 + if cfg.Auth.Enabled { + var authcheck bool + authcheck, err = auth.AuthHandler(ctx, c, cfg) + if !authcheck { + //c.AbortWithStatusJSON(401, gin.H{"error": "Unauthorized"}) + c.AbortWithStatusJSON(401, map[string]string{"error": "Unauthorized"}) + logWarning("%s %s %s %s %s Auth-Error: %v", c.ClientIP(), c.Method(), rawPath, c.Request.Header.UserAgent(), c.Request.Header.GetProtocol(), err) + return + } + } + + // 若匹配api.github.com/repos/用户名/仓库名/路径, 则检查是否开启HeaderAuth + + // 处理blob/raw路径 + if matcher == "blob" { + rawPath = strings.Replace(rawPath, "/blob/", "/raw/", 1) + } + + // 为rawpath加入https:// 头 + rawPath = "https://" + rawPath + + // IP METHOD URL USERAGENT PROTO MATCHES + logDebug("%s %s %s %s %s Matched: %v", c.ClientIP(), c.Method(), rawPath, c.Request.Header.UserAgent(), c.Request.Header.GetProtocol(), matcher) + + switch matcher { + case "releases", "blob", "raw", "gist", "api": + ChunkedProxyRequest(ctx, c, rawPath, cfg, matcher) + case "clone": + GitReq(ctx, c, rawPath, cfg, "git") + default: + c.String(http.StatusForbidden, "Invalid input.") + fmt.Println("Invalid input.") + return + } + } +} diff --git a/proxy/match.go b/proxy/match.go index 1cae46c..fa42494 100644 --- a/proxy/match.go +++ b/proxy/match.go @@ -65,8 +65,10 @@ func Matcher(rawPath string, cfg *config.Config) (string, string, string, error) switch parts[2] { case "releases", "archive": matcher = "releases" - case "blob", "raw": + case "blob": matcher = "blob" + case "raw": + matcher = "raw" case "info", "git-upload-pack": matcher = "clone" default: