package web_proxy import ( "bytes" "compress/gzip" "fmt" "io" "net/http" "regexp" "strings" "github.com/samber/lo" ) // RewriteHTMLContent rewrites HTML content to redirect external links through proxy func RewriteHTMLContent(resp *http.Response, assetID int, scheme, proxyHost string) { if resp.Body == nil { return } // Remove Content-Encoding to avoid decoding issues resp.Header.Del("Content-Encoding") resp.Header.Del("Content-Length") body, err := io.ReadAll(resp.Body) if err != nil { return } resp.Body.Close() baseDomain := lo.Ternary(strings.HasPrefix(proxyHost, "asset-"), func() string { parts := strings.SplitN(proxyHost, ".", 2) return lo.Ternary(len(parts) > 1, parts[1], proxyHost) }(), proxyHost) content := string(body) // Universal URL rewriting patterns - catch ALL external URLs patterns := []struct { pattern string rewrite func(matches []string) string }{ // JavaScript location assignments: window.location = "http://example.com/path" { `(window\.location(?:\.href)?\s*=\s*["'])https?://([^/'"]+)(/[^"']*)?["']`, func(matches []string) string { path := lo.Ternary(len(matches) > 3 && matches[3] != "", matches[3], "") return fmt.Sprintf(`%s%s://asset-%d.%s%s"`, matches[1], scheme, assetID, baseDomain, path) }, }, // Form actions:
3 && matches[3] != "", matches[3], "") return fmt.Sprintf(`%s%s://asset-%d.%s%s"`, matches[1], scheme, assetID, baseDomain, path) }, }, // Link hrefs: 3 && matches[3] != "", matches[3], "") return fmt.Sprintf(`%s%s://asset-%d.%s%s"`, matches[1], scheme, assetID, baseDomain, path) }, }, } for _, p := range patterns { re := regexp.MustCompile(p.pattern) content = re.ReplaceAllStringFunc(content, func(match string) string { matches := re.FindStringSubmatch(match) if len(matches) >= 4 { return p.rewrite(matches) } return match }) } newBody := bytes.NewReader([]byte(content)) resp.Body = io.NopCloser(newBody) resp.ContentLength = int64(len(content)) resp.Header.Set("Content-Length", fmt.Sprintf("%d", len(content))) } // ProcessHTMLResponse processes HTML response for content rewriting and injection func ProcessHTMLResponse(resp *http.Response, assetID int, scheme, proxyHost string, session *WebProxySession) { if resp.Body == nil { return } // Check if content is compressed contentEncoding := resp.Header.Get("Content-Encoding") // Remove Content-Encoding to avoid decoding issues resp.Header.Del("Content-Encoding") resp.Header.Del("Content-Length") var body []byte var err error // Handle compressed content if contentEncoding == "gzip" { gzipReader, err := gzip.NewReader(resp.Body) if err != nil { resp.Body.Close() return } defer gzipReader.Close() body, err = io.ReadAll(gzipReader) } else { body, err = io.ReadAll(resp.Body) } if err != nil { resp.Body.Close() return } resp.Body.Close() content := string(body) // Step 1: URL rewriting for external links baseDomain := lo.Ternary(strings.HasPrefix(proxyHost, "asset-"), func() string { parts := strings.SplitN(proxyHost, ".", 2) return lo.Ternary(len(parts) > 1, parts[1], proxyHost) }(), proxyHost) patterns := []struct { pattern string rewrite func(matches []string) string }{ { `(window\.location(?:\.href)?\s*=\s*["'])https?://([^/'"]+)(/[^"']*)?["']`, func(matches []string) string { path := lo.Ternary(len(matches) > 3 && matches[3] != "", matches[3], "") return fmt.Sprintf(`%s%s://asset-%d.%s%s"`, matches[1], scheme, assetID, baseDomain, path) }, }, { `(action\s*=\s*["'])https?://([^/'"]+)(/[^"']*)?["']`, func(matches []string) string { path := lo.Ternary(len(matches) > 3 && matches[3] != "", matches[3], "") return fmt.Sprintf(`%s%s://asset-%d.%s%s"`, matches[1], scheme, assetID, baseDomain, path) }, }, { `(href\s*=\s*["'])https?://([^/'"]+)(/[^"']*)?["']`, func(matches []string) string { path := lo.Ternary(len(matches) > 3 && matches[3] != "", matches[3], "") return fmt.Sprintf(`%s%s://asset-%d.%s%s"`, matches[1], scheme, assetID, baseDomain, path) }, }, } for _, p := range patterns { re := regexp.MustCompile(p.pattern) content = re.ReplaceAllStringFunc(content, func(match string) string { matches := re.FindStringSubmatch(match) if len(matches) >= 4 { return p.rewrite(matches) } return match }) } // Step 2: Add watermark if enabled if session.WebConfig != nil && session.WebConfig.ProxySettings != nil && session.WebConfig.ProxySettings.WatermarkEnabled { watermarkCSS := ` ` // Generate watermark HTML with multiple OneTerm texts var watermarkTexts []string for row := 0; row < 30; row++ { for col := 0; col < 15; col++ { top := row * 100 left := col * 300 watermarkTexts = append(watermarkTexts, fmt.Sprintf(`
OneTerm
`, top, left)) } } watermarkHTML := fmt.Sprintf(`
%s
`, strings.Join(watermarkTexts, "\n")) // Add session management JavaScript sessionJS := fmt.Sprintf(` `, session.SessionId) if strings.Contains(content, "") { content = strings.Replace(content, "", watermarkCSS+"", 1) } else { content = watermarkCSS + content } if strings.Contains(content, "") { content = strings.Replace(content, "", watermarkHTML+sessionJS+"", 1) } else { content = content + watermarkHTML + sessionJS } } // Step 3: Record activity if enabled if session.WebConfig != nil && session.WebConfig.ProxySettings != nil && session.WebConfig.ProxySettings.RecordingEnabled { // Activity recording is handled elsewhere to avoid accessing ctx.Request here } // Update response newBody := bytes.NewReader([]byte(content)) resp.Body = io.NopCloser(newBody) resp.ContentLength = int64(len(content)) resp.Header.Set("Content-Length", fmt.Sprintf("%d", len(content))) } // RenderExternalRedirectPage renders the page shown when external redirect is blocked func RenderExternalRedirectPage(targetURL string) string { return fmt.Sprintf(` External Redirect Blocked - OneTerm

🛡️ External Redirect Blocked

The target website attempted to redirect you to an external domain, which has been blocked by the bastion host for security reasons.
Target URL:
%s
All web access must go through the bastion host to maintain security and audit compliance. External redirects are not permitted.
`, targetURL) } // RenderSessionExpiredPage renders the page shown when session has expired func RenderSessionExpiredPage(reason string) string { return fmt.Sprintf(` Session Expired - OneTerm
Session Expired
Your web proxy session has expired and you need to reconnect.
Reason: %s
← Go Back
`, reason) }