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) }, }, // Static resources: `, session.SessionId) // Add JavaScript URL interceptor for dynamic requests (always inject - moved outside watermark condition) urlInterceptorJS := fmt.Sprintf(` `, session.CurrentHost, assetID, baseDomain, scheme, session.Permissions.FileDownload) // Always inject session management and URL interceptor if strings.Contains(content, "") { content = strings.Replace(content, "", sessionJS+urlInterceptorJS+"", 1) } else { content = content + sessionJS + urlInterceptorJS } // 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) } // RenderErrorPage renders a general error page for web proxy errors func RenderErrorPage(errorType, title, reason, details string) string { var bgColor, iconEmoji string switch errorType { case "access_denied": bgColor = "#ff6b6b 0%, #ee5a52 100%" iconEmoji = "🚫" case "session_expired": bgColor = "#f39c12 0%, #e67e22 100%" iconEmoji = "⏰" case "connection_error": bgColor = "#95a5a6 0%, #7f8c8d 100%" iconEmoji = "🔌" case "server_error": bgColor = "#8e44ad 0%, #9b59b6 100%" iconEmoji = "⚠️" case "concurrent_limit": bgColor = "#e74c3c 0%, #c0392b 100%" iconEmoji = "🚦" default: bgColor = "#34495e 0%, #2c3e50 100%" iconEmoji = "❌" } detailsHtml := "" if details != "" { detailsHtml = fmt.Sprintf(`
Details:
%s
`, details) } return fmt.Sprintf(` %s - OneTerm

%s %s

%s
%s
`, title, bgColor, iconEmoji, title, reason, detailsHtml) } // RenderAccessDeniedPage renders the page shown when access is denied (download, read-only, etc.) func RenderAccessDeniedPage(reason, details string) string { return RenderErrorPage("access_denied", "Access Denied", reason, details) } // RenderSessionExpiredPage renders the page shown when session has expired func RenderSessionExpiredPage(reason string) string { return RenderErrorPage("session_expired", "Session Expired", reason, "") } // RenderConcurrentLimitPage renders the page when concurrent limit is exceeded func RenderConcurrentLimitPage(maxConcurrent int) string { reason := fmt.Sprintf("Maximum concurrent connections (%d) exceeded", maxConcurrent) details := "Please wait for an existing session to end, or contact your administrator to increase the limit." return RenderErrorPage("concurrent_limit", "Connection Limit Exceeded", reason, details) } // RenderServerErrorPage renders the page for server errors func RenderServerErrorPage(reason, details string) string { return RenderErrorPage("server_error", "Server Error", reason, details) } // RenderConnectionErrorPage renders the page for connection errors func RenderConnectionErrorPage(reason, details string) string { return RenderErrorPage("connection_error", "Connection Error", reason, details) } // Legacy function - keeping the original style for compatibility func RenderSessionExpiredPageOld(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) }