diff --git a/handlers/proxy.go b/handlers/proxy.go index 55d79f5..48b1c86 100644 --- a/handlers/proxy.go +++ b/handlers/proxy.go @@ -52,14 +52,23 @@ type FlareSolverrResponse struct { } var ( - UserAgent = getenv("USER_AGENT", "Mozilla/5.0 (compatible; Googlebot/2.1; +http://www.google.com/bot.html)") - ForwardedFor = getenv("X_FORWARDED_FOR", "66.249.66.1") + UserAgent = getenv("USER_AGENT", "Mozilla/5.0 (compatible; Googlebot/2.1; +http://www.google.com/bot.html)") + ForwardedFor = getenv("X_FORWARDED_FOR", "66.249.66.1") flareSolverrHost = os.Getenv("FLARESOLVERR_HOST") - rulesSet = ruleset.NewRulesetFromEnv() - allowedDomains = []string{} - defaultTimeout = 15 // in seconds + rulesSet = ruleset.NewRulesetFromEnv() + allowedDomains = []string{} + defaultTimeout = 15 // in seconds ) +const ladderParamPrefix = "__ladder_" + +type debugUIOptions struct { + Enabled bool + ShowRequest bool + HasUI bool + ShowUI bool +} + func init() { allowedDomains = strings.Split(os.Getenv("ALLOWED_DOMAINS"), ",") if os.Getenv("ALLOWED_DOMAINS_RULESET") == "true" { @@ -180,13 +189,19 @@ func ProxySite(rulesetPath string) fiber.Handler { } queries := c.Queries() - body, _, resp, err := fetchSite(url, queries) + proxyQueries, debugOptions := splitProxyQueries(queries) + body, _, resp, err := fetchSite(url, proxyQueries) if err != nil { log.Println("ERROR:", err) c.SendStatus(fiber.StatusInternalServerError) return c.SendString(err.Error()) } + contentType := strings.ToLower(resp.Header.Get("Content-Type")) + if strings.Contains(contentType, "text/html") && shouldInjectDebugUI(debugOptions) { + body = injectDebugUI(body, debugOptions) + } + c.Cookie(&fiber.Cookie{}) c.Set("Content-Type", resp.Header.Get("Content-Type")) c.Set("Content-Security-Policy", resp.Header.Get("Content-Security-Policy")) @@ -195,6 +210,166 @@ func ProxySite(rulesetPath string) fiber.Handler { } } +func shouldInjectDebugUI(options debugUIOptions) bool { + if os.Getenv("DEBUG_UI") == "false" { + return false + } + + if options.HasUI { + return options.ShowUI + } + + if os.Getenv("DEBUG_UI") == "true" { + return true + } + + return true +} + +func splitProxyQueries(queries map[string]string) (map[string]string, debugUIOptions) { + proxyQueries := map[string]string{} + options := debugUIOptions{} + + for key, value := range queries { + switch key { + case "__ladder_debug": + options.Enabled = value == "1" + continue + case "__ladder_show_request": + options.ShowRequest = value == "1" + continue + case "__ladder_ui": + options.HasUI = true + options.ShowUI = value == "1" + continue + } + + if strings.HasPrefix(key, ladderParamPrefix) { + continue + } + + proxyQueries[key] = value + } + + return proxyQueries, options +} + +func injectDebugUI(body string, options debugUIOptions) string { + if !strings.Contains(strings.ToLower(body), "Request: ` + } + + injection := fmt.Sprintf(` + + + +`, debugChecked, showRequestChecked, requestInfo) + + bodyClose := regexp.MustCompile(`(?i)`) + if bodyClose.MatchString(body) { + return bodyClose.ReplaceAllString(body, injection+"") + } + + return body + injection +} + func modifyURL(uri string, rule ruleset.Rule) (string, error) { newUrl, err := url.Parse(uri) if err != nil { @@ -292,7 +467,7 @@ func fetchSite(urlpath string, queries map[string]string) (string, *http.Request // Handle FlareSolverr integration cookieValue := rule.Headers.Cookie debug := os.Getenv("LOG_URLS") == "true" - + if rule.UseFlareSolverr && flareSolverrHost != "" { if fsCookies, err := getFlareSolverrCookies(url); err == nil { if cookieValue != "" { @@ -307,7 +482,7 @@ func fetchSite(urlpath string, queries map[string]string) (string, *http.Request log.Printf("FlareSolverr error for %s: %v", url, err) } } - + if cookieValue != "" { req.Header.Set("Cookie", cookieValue) } diff --git a/handlers/proxy.test.go b/handlers/proxy.test.go index 0ed2c4b..6c0a592 100644 --- a/handlers/proxy.test.go +++ b/handlers/proxy.test.go @@ -5,6 +5,7 @@ import ( "net/http" "net/http/httptest" "net/url" + "strings" "testing" "ladder/pkg/ruleset" @@ -57,4 +58,34 @@ func TestRewriteHtml(t *testing.T) { assert.Equal(t, expected, actual) } +func TestSplitProxyQueries(t *testing.T) { + queries := map[string]string{ + "q": "news", + "page": "2", + "__ladder_debug": "1", + "__ladder_show_request": "1", + "__ladder_other": "x", + } + + proxyQueries, options := splitProxyQueries(queries) + + assert.Equal(t, map[string]string{ + "q": "news", + "page": "2", + }, proxyQueries) + assert.True(t, options.Enabled) + assert.True(t, options.ShowRequest) +} + +func TestInjectDebugUI(t *testing.T) { + body := `

hello

` + updated := injectDebugUI(body, debugUIOptions{Enabled: true, ShowRequest: true}) + + assert.True(t, strings.Contains(updated, `id="__ladderGear"`)) + assert.True(t, strings.Contains(updated, `id="__ladderPanel"`)) + assert.True(t, strings.Contains(updated, `data-param="__ladder_debug" checked`)) + assert.True(t, strings.Contains(updated, `data-param="__ladder_show_request" checked`)) + assert.True(t, strings.Contains(updated, `id="__ladderDebugInfo"`)) +} + // END: 6f8b3f5d5d5d