package proxy import ( "embed" "net/http" "strings" "code.wejust.rest/lain/ghproxy/internal/config" "github.com/gin-gonic/gin" ) //go:embed static/index.html var staticFS embed.FS type ProxyHandler struct { addr string githubProxy http.Handler assetsProxy http.Handler apiProxy http.Handler allowedRepos map[string]bool } func NewProxyHandler(cfg *config.Config) (*ProxyHandler, error) { allowed := make(map[string]bool) for _, repo := range cfg.AllowedRepos { allowed[repo] = true } return &ProxyHandler{ addr: cfg.Addr, githubProxy: CreateReverseProxy("https://github.com"), assetsProxy: CreateReverseProxy("https://github.githubassets.com"), apiProxy: CreateReverseProxy("https://api.github.com"), allowedRepos: allowed, }, nil } func (h *ProxyHandler) RegisterRoutes(r *gin.Engine) { r.GET("/", func(c *gin.Context) { if c.Request.URL.Path == "/" { data, err := staticFS.ReadFile("static/index.html") if err != nil { c.String(http.StatusInternalServerError, "failed to read embedded index.html") return } c.Data(http.StatusOK, "text/html; charset=utf-8", data) return } c.Next() }) r.GET("/robots.txt", func(c *gin.Context) { c.Data(http.StatusOK, "text/plain; charset=utf-8", []byte("User-Agent: *\nDisallow: /\n")) }) r.NoRoute(func(c *gin.Context) { reqPath := c.Request.URL.Path if strings.Contains(reqPath, "/_private/browser/stats") { c.AbortWithStatus(http.StatusNoContent) return } //NOTE(kroot): proxy GitHub GraphQL API as-is to avoid breaking the issues UI if reqPath == "/_graphql" { h.githubProxy.ServeHTTP(c.Writer, c.Request) return } //NOTE(kroot): handle assets, gravatars, and favicons if strings.Contains(reqPath, "/assets/") || strings.HasPrefix(reqPath, "/images/gravatars/") || strings.HasPrefix(reqPath, "/favicons/") { h.assetsProxy.ServeHTTP(c.Writer, c.Request) return } if strings.HasPrefix(reqPath, "/api/") { //NOTE(kroot): strip /api prefix if needed, or just proxy as is h.apiProxy.ServeHTTP(c.Writer, c.Request) return } parts := strings.Split(strings.Trim(reqPath, "/"), "/") //NOTE(kroot): if we have allowed_repos, restrict access if len(h.allowedRepos) > 0 { if len(parts) >= 2 { repoFullName := parts[0] + "/" + parts[1] if h.allowedRepos[repoFullName] { h.serveWithCache(c.Writer, c.Request, h.githubProxy) return } } //NOTE(kroot): if its not an allowed repository, and allowed_repos is not empty, just redirect to root c.Redirect(http.StatusSeeOther, "/") return } //NOTE(kroot): if allowed_repos is empty, allow everything h.serveWithCache(c.Writer, c.Request, h.githubProxy) }) }