package webdevice import ( "net/url" "regexp" "strings" ) func JoinProxyTargetPath(basePath, requestPath string) string { if requestPath == "" { requestPath = "/" } if basePath == "" || basePath == "/" { return requestPath } if strings.HasSuffix(basePath, "/") && strings.HasPrefix(requestPath, "/") { return basePath + strings.TrimPrefix(requestPath, "/") } if !strings.HasSuffix(basePath, "/") && !strings.HasPrefix(requestPath, "/") { return basePath + "/" + requestPath } return basePath + requestPath } func RewriteLocation(targetIP string, targetURL *url.URL, location string) string { locationURL, err := url.Parse(location) if err != nil { return location } proxyPrefix := "/proxy/web/" + targetIP if locationURL.Host == "" && strings.HasPrefix(location, "/") { return proxyPrefix + location } if locationURL.Host != "" { locationHost := locationURL.Hostname() locationPort := locationURL.Port() if locationPort == "" && (locationURL.Scheme == "" || locationURL.Scheme == "http") { locationPort = "80" } targetHost := targetURL.Hostname() targetPort := targetURL.Port() if targetPort == "" && targetURL.Scheme == "http" { targetPort = "80" } if locationHost != targetHost || locationPort != targetPort { return location } rewrittenPath := locationURL.EscapedPath() if rewrittenPath == "" { rewrittenPath = "/" } if locationURL.RawQuery != "" { rewrittenPath += "?" + locationURL.RawQuery } if locationURL.Fragment != "" { rewrittenPath += "#" + locationURL.Fragment } return proxyPrefix + rewrittenPath } return location } var ( webProxyQuotedAttrPattern = regexp.MustCompile(`(?i)\b(href|src|action|poster|data-src|data-href)\s*=\s*(['"])([^'"]*)['"]`) webProxyBareAttrPattern = regexp.MustCompile(`(?i)\b(href|src|action|poster|data-src|data-href)\s*=\s*([^'">\s][^>\s]*)`) webProxyCSSURLPattern = regexp.MustCompile(`(?i)url\(\s*(['"]?)([^'"\)\s]+)['"]?\s*\)`) webProxyQuotedURLPattern = regexp.MustCompile(`(['"])(/[^'"<>\s\\)]*)['"]`) ) func ShouldRewriteBody(contentType string) bool { contentType = strings.ToLower(contentType) return strings.Contains(contentType, "text/html") || strings.Contains(contentType, "text/css") } func RewriteText(body, proxyPrefix string, targetURL *url.URL, contentType string) string { contentType = strings.ToLower(contentType) if strings.Contains(contentType, "text/html") { body = webProxyQuotedAttrPattern.ReplaceAllStringFunc(body, func(match string) string { parts := webProxyQuotedAttrPattern.FindStringSubmatch(match) if len(parts) != 4 { return match } rewritten := rewriteURL(parts[3], proxyPrefix, targetURL) return strings.Replace(match, parts[2]+parts[3]+parts[2], parts[2]+rewritten+parts[2], 1) }) body = webProxyBareAttrPattern.ReplaceAllStringFunc(body, func(match string) string { parts := webProxyBareAttrPattern.FindStringSubmatch(match) if len(parts) != 3 { return match } rewritten := rewriteURL(parts[2], proxyPrefix, targetURL) return strings.Replace(match, parts[2], rewritten, 1) }) body = webProxyQuotedURLPattern.ReplaceAllStringFunc(body, func(match string) string { parts := webProxyQuotedURLPattern.FindStringSubmatch(match) if len(parts) != 3 { return match } rewritten := rewriteURL(parts[2], proxyPrefix, targetURL) return parts[1] + rewritten + parts[1] }) } body = webProxyCSSURLPattern.ReplaceAllStringFunc(body, func(match string) string { parts := webProxyCSSURLPattern.FindStringSubmatch(match) if len(parts) != 3 { return match } rewritten := rewriteURL(parts[2], proxyPrefix, targetURL) return "url(" + parts[1] + rewritten + parts[1] + ")" }) if strings.Contains(contentType, "text/html") { body = injectRuntime(body, proxyPrefix) } return body } func injectRuntime(body, proxyPrefix string) string { if strings.Contains(body, "data-web-proxy-runtime") { return body } script := `` lower := strings.ToLower(body) if idx := strings.Index(lower, ""); idx >= 0 { return body[:idx] + script + body[idx:] } if idx := strings.Index(lower, "= 0 { if end := strings.Index(body[idx:], ">"); end >= 0 { insertAt := idx + end + 1 return body[:insertAt] + script + body[insertAt:] } } return script + body } func rewriteURL(rawURL, proxyPrefix string, targetURL *url.URL) string { rawURL = strings.TrimSpace(rawURL) if rawURL == "" || strings.HasPrefix(rawURL, "#") || strings.HasPrefix(rawURL, "//") || strings.HasPrefix(rawURL, proxyPrefix+"/") || strings.HasPrefix(rawURL, "/proxy/web/") { return rawURL } lower := strings.ToLower(rawURL) for _, prefix := range []string{"data:", "blob:", "mailto:", "tel:", "javascript:"} { if strings.HasPrefix(lower, prefix) { return rawURL } } if strings.HasPrefix(rawURL, "/") { return proxyPrefix + rawURL } parsed, err := url.Parse(rawURL) if err != nil || parsed.Host == "" { return rawURL } if targetURL == nil || !sameUpstreamHost(parsed, targetURL) { return rawURL } rewrittenPath := parsed.EscapedPath() if rewrittenPath == "" { rewrittenPath = "/" } if parsed.RawQuery != "" { rewrittenPath += "?" + parsed.RawQuery } if parsed.Fragment != "" { rewrittenPath += "#" + parsed.Fragment } return proxyPrefix + rewrittenPath } func sameUpstreamHost(left, right *url.URL) bool { leftPort := left.Port() if leftPort == "" && (left.Scheme == "" || left.Scheme == "http") { leftPort = "80" } if leftPort == "" && left.Scheme == "https" { leftPort = "443" } rightPort := right.Port() if rightPort == "" && (right.Scheme == "" || right.Scheme == "http") { rightPort = "80" } if rightPort == "" && right.Scheme == "https" { rightPort = "443" } return strings.EqualFold(left.Hostname(), right.Hostname()) && leftPort == rightPort } func RewriteSetCookie(cookie, proxyPrefix string) string { parts := strings.Split(cookie, ";") if len(parts) == 0 { return cookie } rewritten := []string{strings.TrimSpace(parts[0])} hasPath := false for _, part := range parts[1:] { attr := strings.TrimSpace(part) if attr == "" { continue } lower := strings.ToLower(attr) switch { case strings.HasPrefix(lower, "domain="): continue case strings.HasPrefix(lower, "path="): hasPath = true rewritten = append(rewritten, "Path="+proxyPrefix+"/") default: rewritten = append(rewritten, attr) } } if !hasPath { rewritten = append(rewritten, "Path="+proxyPrefix+"/") } return strings.Join(rewritten, "; ") }