From 1f493509870ed954dc57ecd2bd46c914c1865721 Mon Sep 17 00:00:00 2001 From: Toby Chui Date: Sun, 16 Nov 2025 19:33:47 +0800 Subject: [PATCH] Added remove User-Agent options - Added advance setting for removing user-agent --- .gitignore | 1 + src/api.go | 1 + src/mod/dynamicproxy/default.go | 13 ++-- src/mod/dynamicproxy/dpcore/dpcore.go | 3 +- src/mod/dynamicproxy/dynamicproxy.go | 17 ++--- src/mod/dynamicproxy/proxyRequestHandler.go | 2 + src/mod/dynamicproxy/typedef.go | 13 ++-- src/reverseproxy.go | 74 +++++++++++++++++++++ src/web/components/httprp.html | 4 +- src/web/snippet/customHeaders.html | 45 +++++++++++++ 10 files changed, 150 insertions(+), 23 deletions(-) diff --git a/.gitignore b/.gitignore index 919c978..e481cf7 100644 --- a/.gitignore +++ b/.gitignore @@ -64,3 +64,4 @@ www/html/index.html /src/plugins .DS_Store /build +src/zoraxy.exe~ diff --git a/src/api.go b/src/api.go index 54684bc..5b9a220 100644 --- a/src/api.go +++ b/src/api.go @@ -61,6 +61,7 @@ func RegisterHTTPProxyAPIs(authRouter *auth.RouterDef) { authRouter.HandleFunc("/api/proxy/header/remove", HandleCustomHeaderRemove) authRouter.HandleFunc("/api/proxy/header/handleHSTS", HandleHSTSState) authRouter.HandleFunc("/api/proxy/header/handleHopByHop", HandleHopByHop) + authRouter.HandleFunc("/api/proxy/header/handleUserAgent", HandleUserAgent) authRouter.HandleFunc("/api/proxy/header/handleHostOverwrite", HandleHostOverwrite) authRouter.HandleFunc("/api/proxy/header/handlePermissionPolicy", HandlePermissionPolicy) authRouter.HandleFunc("/api/proxy/header/handleWsHeaderBehavior", HandleWsHeaderBehavior) diff --git a/src/mod/dynamicproxy/default.go b/src/mod/dynamicproxy/default.go index aea7100..0e26ae9 100644 --- a/src/mod/dynamicproxy/default.go +++ b/src/mod/dynamicproxy/default.go @@ -32,12 +32,13 @@ func GetDefaultAuthenticationProvider() *AuthenticationProvider { // GetDefaultHeaderRewriteRules return a default header rewrite rules func GetDefaultHeaderRewriteRules() *HeaderRewriteRules { return &HeaderRewriteRules{ - UserDefinedHeaders: []*rewrite.UserDefinedHeader{}, - RequestHostOverwrite: "", - HSTSMaxAge: 0, - EnablePermissionPolicyHeader: false, - PermissionPolicy: nil, - DisableHopByHopHeaderRemoval: false, + UserDefinedHeaders: []*rewrite.UserDefinedHeader{}, + RequestHostOverwrite: "", + HSTSMaxAge: 0, + EnablePermissionPolicyHeader: false, + PermissionPolicy: nil, + DisableHopByHopHeaderRemoval: false, + DisableUserAgentHeaderRemoval: false, } } diff --git a/src/mod/dynamicproxy/dpcore/dpcore.go b/src/mod/dynamicproxy/dpcore/dpcore.go index dcdfbf2..8db705c 100644 --- a/src/mod/dynamicproxy/dpcore/dpcore.go +++ b/src/mod/dynamicproxy/dpcore/dpcore.go @@ -73,6 +73,7 @@ type ResponseRewriteRuleSet struct { /* Advance Usecase Options */ HostHeaderOverwrite string //Force overwrite of request "Host" header (advanced usecase) NoRemoveHopByHop bool //Do not remove hop-by-hop headers (advanced usecase) + NoRemoveUserAgentHeader bool //Do not remove User-Agent header from server response (advanced usecase) DisableChunkedTransferEncoding bool //Disable chunked transfer encoding /* System Information Payload */ @@ -331,7 +332,7 @@ func (p *ReverseProxy) ProxyHTTP(rw http.ResponseWriter, req *http.Request, rrr } //Remove the User-Agent header if exists - if _, ok := res.Header["User-Agent"]; ok { + if _, ok := res.Header["User-Agent"]; ok && !rrr.NoRemoveUserAgentHeader { //Server to client request should not contains a User-Agent header res.Header.Del("User-Agent") } diff --git a/src/mod/dynamicproxy/dynamicproxy.go b/src/mod/dynamicproxy/dynamicproxy.go index 5fb873e..4945336 100644 --- a/src/mod/dynamicproxy/dynamicproxy.go +++ b/src/mod/dynamicproxy/dynamicproxy.go @@ -165,14 +165,15 @@ func (router *Router) StartProxyService() error { } selectedUpstream.ServeHTTP(w, r, &dpcore.ResponseRewriteRuleSet{ - ProxyDomain: selectedUpstream.OriginIpOrDomain, - OriginalHost: originalHostHeader, - UseTLS: selectedUpstream.RequireTLS, - HostHeaderOverwrite: endpointProxyRewriteRules.RequestHostOverwrite, - NoRemoveHopByHop: endpointProxyRewriteRules.DisableHopByHopHeaderRemoval, - PathPrefix: "", - Version: sep.parent.Option.HostVersion, - DevelopmentMode: sep.parent.Option.DevelopmentMode, + ProxyDomain: selectedUpstream.OriginIpOrDomain, + OriginalHost: originalHostHeader, + UseTLS: selectedUpstream.RequireTLS, + HostHeaderOverwrite: endpointProxyRewriteRules.RequestHostOverwrite, + NoRemoveHopByHop: endpointProxyRewriteRules.DisableHopByHopHeaderRemoval, + NoRemoveUserAgentHeader: endpointProxyRewriteRules.DisableUserAgentHeaderRemoval, + PathPrefix: "", + Version: sep.parent.Option.HostVersion, + DevelopmentMode: sep.parent.Option.DevelopmentMode, }) return } diff --git a/src/mod/dynamicproxy/proxyRequestHandler.go b/src/mod/dynamicproxy/proxyRequestHandler.go index 24dbfc2..ced8377 100644 --- a/src/mod/dynamicproxy/proxyRequestHandler.go +++ b/src/mod/dynamicproxy/proxyRequestHandler.go @@ -237,6 +237,7 @@ func (h *ProxyHandler) hostRequest(w http.ResponseWriter, r *http.Request, targe UpstreamHeaders: upstreamHeaders, DownstreamHeaders: downstreamHeaders, DisableChunkedTransferEncoding: target.DisableChunkedTransferEncoding, + NoRemoveUserAgentHeader: headerRewriteOptions.DisableUserAgentHeaderRemoval, HostHeaderOverwrite: headerRewriteOptions.RequestHostOverwrite, NoRemoveHopByHop: headerRewriteOptions.DisableHopByHopHeaderRemoval, Version: target.parent.Option.HostVersion, @@ -332,6 +333,7 @@ func (h *ProxyHandler) vdirRequest(w http.ResponseWriter, r *http.Request, targe UpstreamHeaders: upstreamHeaders, DownstreamHeaders: downstreamHeaders, DisableChunkedTransferEncoding: target.parent.DisableChunkedTransferEncoding, + NoRemoveUserAgentHeader: headerRewriteOptions.DisableUserAgentHeaderRemoval, HostHeaderOverwrite: headerRewriteOptions.RequestHostOverwrite, Version: target.parent.parent.Option.HostVersion, DevelopmentMode: target.parent.parent.Option.DevelopmentMode, diff --git a/src/mod/dynamicproxy/typedef.go b/src/mod/dynamicproxy/typedef.go index 403a367..44b36b4 100644 --- a/src/mod/dynamicproxy/typedef.go +++ b/src/mod/dynamicproxy/typedef.go @@ -138,12 +138,13 @@ type VirtualDirectoryEndpoint struct { // Rules and settings for header rewriting type HeaderRewriteRules struct { - UserDefinedHeaders []*rewrite.UserDefinedHeader //Custom headers to append when proxying requests from this endpoint - RequestHostOverwrite string //If not empty, this domain will be used to overwrite the Host field in request header - HSTSMaxAge int64 //HSTS max age, set to 0 for disable HSTS headers - EnablePermissionPolicyHeader bool //Enable injection of permission policy header - PermissionPolicy *permissionpolicy.PermissionsPolicy //Permission policy header - DisableHopByHopHeaderRemoval bool //Do not remove hop-by-hop headers + UserDefinedHeaders []*rewrite.UserDefinedHeader //Custom headers to append when proxying requests from this endpoint + RequestHostOverwrite string //If not empty, this domain will be used to overwrite the Host field in request header + HSTSMaxAge int64 //HSTS max age, set to 0 for disable HSTS headers + EnablePermissionPolicyHeader bool //Enable injection of permission policy header + PermissionPolicy *permissionpolicy.PermissionsPolicy //Permission policy header + DisableHopByHopHeaderRemoval bool //Do not remove hop-by-hop headers + DisableUserAgentHeaderRemoval bool //Do not remove User-Agent header from server response } diff --git a/src/reverseproxy.go b/src/reverseproxy.go index 943837d..43e8dd6 100644 --- a/src/reverseproxy.go +++ b/src/reverseproxy.go @@ -1826,6 +1826,80 @@ func HandleHopByHop(w http.ResponseWriter, r *http.Request) { } } +// HandleUserAgent get and set the user agent header remover state +// note that it shows the ENABLE STATE of user-agent remover, not the disable state +func HandleUserAgent(w http.ResponseWriter, r *http.Request) { + domain, err := utils.PostPara(r, "domain") + if err != nil { + domain, err = utils.GetPara(r, "domain") + if err != nil { + utils.SendErrorResponse(w, "domain or matching rule not defined") + return + } + } + + targetProxyEndpoint, err := dynamicProxyRouter.LoadProxy(domain) + if err != nil { + utils.SendErrorResponse(w, "target endpoint not exists") + return + } + + if r.Method == http.MethodGet { + //Get the current user agent header state + js, _ := json.Marshal(!targetProxyEndpoint.HeaderRewriteRules.DisableUserAgentHeaderRemoval) + utils.SendJSONResponse(w, string(js)) + } else if r.Method == http.MethodPost { + //Set the user agent header state + enableUserAgentRemover, _ := utils.PostBool(r, "removeUserAgent") + + //As this will require change in the proxy instance we are running + //we need to clone and respawn this proxy endpoint + newProxyEndpoint := targetProxyEndpoint.Clone() + //Storage file use false as default, so disable removal = not enable remover + newProxyEndpoint.HeaderRewriteRules.DisableUserAgentHeaderRemoval = !enableUserAgentRemover + + //Save proxy endpoint + err = SaveReverseProxyConfig(newProxyEndpoint) + if err != nil { + utils.SendErrorResponse(w, err.Error()) + return + } + + //Spawn a new endpoint with updated dpcore + preparedEndpoint, err := dynamicProxyRouter.PrepareProxyRoute(newProxyEndpoint) + if err != nil { + utils.SendErrorResponse(w, err.Error()) + return + } + + //Remove the old endpoint + err = targetProxyEndpoint.Remove() + if err != nil { + utils.SendErrorResponse(w, err.Error()) + return + } + + //Add the newly prepared endpoint to runtime + err = dynamicProxyRouter.AddProxyRouteToRuntime(preparedEndpoint) + if err != nil { + utils.SendErrorResponse(w, err.Error()) + return + } + + //Print log message + if enableUserAgentRemover { + SystemWideLogger.Println("Enabled user-agent header removal on " + domain) + } else { + SystemWideLogger.Println("Disabled user-agent header removal on " + domain) + } + + utils.SendOK(w) + + } else { + http.Error(w, "405 - Method not allowed", http.StatusMethodNotAllowed) + } +} + // Handle view or edit HSTS states func HandleHSTSState(w http.ResponseWriter, r *http.Request) { domain, err := utils.PostPara(r, "domain") diff --git a/src/web/components/httprp.html b/src/web/components/httprp.html index 7c563bb..137e0bc 100644 --- a/src/web/components/httprp.html +++ b/src/web/components/httprp.html @@ -443,14 +443,14 @@
-

-
diff --git a/src/web/snippet/customHeaders.html b/src/web/snippet/customHeaders.html index 79df202..44bd020 100644 --- a/src/web/snippet/customHeaders.html +++ b/src/web/snippet/customHeaders.html @@ -116,6 +116,15 @@ This should be ON by default +
+

Remove User-Agent Header

+

Remove User-Agent header from upstream before sending to downstream. Set to ON by default.

+
+ + +
+

WebSocket Custom Headers

Copy custom headers from HTTP requests to WebSocket connections. @@ -649,6 +658,42 @@ } initHopByHopRemoverState(); + /* User-Agent headers */ + function initUserAgentRemoverState(){ + $.get("/api/proxy/header/handleUserAgent?domain=" + editingEndpoint.ep, function(data){ + if (data.error != undefined){ + parent.msgbox(data.error); + }else{ + if (data == true){ + $("#removeUserAgent").parent().checkbox("set checked"); + }else{ + $("#removeUserAgent").parent().checkbox("set unchecked"); + } + + //Bind event to the checkbox + $("#removeUserAgent").on("change", function(evt){ + let isChecked = $(this)[0].checked; + $.cjax({ + url: "/api/proxy/header/handleUserAgent", + method: "POST", + data: { + "domain": editingEndpoint.ep, + "removeUserAgent": isChecked, + }, + success: function(data){ + if (data.error != undefined){ + parent.msgbox(data.error, false); + }else{ + parent.msgbox("User-Agent header rule updated"); + } + } + }) + }) + } + }) + } + initUserAgentRemoverState(); + /* WebSocket Custom Headers */ function initWebSocketCustomHeaderState(){ $.get("/api/proxy/header/handleWsHeaderBehavior?domain=" + editingEndpoint.ep, function(data){