diff --git a/src/api.go b/src/api.go
index eb99977..203fa01 100644
--- a/src/api.go
+++ b/src/api.go
@@ -34,6 +34,7 @@ func RegisterHTTPProxyAPIs(authRouter *auth.RouterDef) {
authRouter.HandleFunc("/api/proxy/detail", ReverseProxyListDetail)
authRouter.HandleFunc("/api/proxy/edit", ReverseProxyHandleEditEndpoint)
authRouter.HandleFunc("/api/proxy/setAlias", ReverseProxyHandleAlias)
+ authRouter.HandleFunc("/api/proxy/setHostname", ReverseProxyHandleSetHostname)
authRouter.HandleFunc("/api/proxy/del", DeleteProxyEndpoint)
authRouter.HandleFunc("/api/proxy/updateCredentials", UpdateProxyBasicAuthCredentials)
authRouter.HandleFunc("/api/proxy/tlscheck", domainsniff.HandleCheckSiteSupportTLS)
@@ -192,6 +193,7 @@ func RegisterStaticWebServerAPIs(authRouter *auth.RouterDef) {
authRouter.HandleFunc("/api/webserv/stop", staticWebServer.HandleStopServer)
authRouter.HandleFunc("/api/webserv/setPort", HandleStaticWebServerPortChange)
authRouter.HandleFunc("/api/webserv/setDirList", staticWebServer.SetEnableDirectoryListing)
+ authRouter.HandleFunc("/api/webserv/disableListenAllInterface", staticWebServer.SetDisableListenToAllInterface)
/* File Manager */
if *allowWebFileManager {
authRouter.HandleFunc("/api/fs/list", staticWebServer.FileManager.HandleList)
diff --git a/src/go.mod b/src/go.mod
index 3b0d5e4..804e62a 100644
--- a/src/go.mod
+++ b/src/go.mod
@@ -18,33 +18,16 @@ require (
github.com/microcosm-cc/bluemonday v1.0.26
github.com/monperrus/crawler-user-agents v1.1.0
github.com/shirou/gopsutil/v4 v4.25.1
+ github.com/stretchr/testify v1.10.0
github.com/syndtr/goleveldb v1.0.0
golang.org/x/net v0.33.0
+ golang.org/x/oauth2 v0.24.0
golang.org/x/text v0.21.0
)
require (
cloud.google.com/go/auth v0.13.0 // indirect
cloud.google.com/go/auth/oauth2adapt v0.2.6 // indirect
- github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/resourcegraph/armresourcegraph v0.9.0 // indirect
- github.com/benbjohnson/clock v1.3.0 // indirect
- github.com/ebitengine/purego v0.8.2 // indirect
- github.com/go-ole/go-ole v1.2.6 // indirect
- github.com/golang-jwt/jwt/v5 v5.2.1 // indirect
- github.com/golang/snappy v0.0.1 // indirect
- github.com/huaweicloud/huaweicloud-sdk-go-v3 v0.1.128 // indirect
- github.com/opentracing/opentracing-go v1.2.1-0.20220228012449-10b1cf09e00b // indirect
- github.com/peterhellberg/link v1.2.0 // indirect
- github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c // indirect
- github.com/shopspring/decimal v1.3.1 // indirect
- github.com/tjfoc/gmsm v1.4.1 // indirect
- github.com/vultr/govultr/v3 v3.9.1 // indirect
- github.com/yusufpapurcu/wmi v1.2.4 // indirect
- go.mongodb.org/mongo-driver v1.12.0 // indirect
- golang.org/x/sys v0.28.0 // indirect
-)
-
-require (
cloud.google.com/go/compute/metadata v0.6.0 // indirect
github.com/AdamSLevy/jsonrpc2/v14 v14.1.0 // indirect
github.com/Azure/azure-sdk-for-go v68.0.0+incompatible // indirect
@@ -53,6 +36,7 @@ require (
github.com/Azure/azure-sdk-for-go/sdk/internal v1.10.0 // indirect
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/dns/armdns v1.2.0 // indirect
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/privatedns/armprivatedns v1.3.0 // indirect
+ github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/resourcegraph/armresourcegraph v0.9.0 // indirect
github.com/Azure/go-autorest v14.2.0+incompatible // indirect
github.com/Azure/go-autorest/autorest v0.11.29 // indirect
github.com/Azure/go-autorest/autorest/adal v0.9.22 // indirect
@@ -82,6 +66,7 @@ require (
github.com/aws/aws-sdk-go-v2/service/sts v1.33.3 // indirect
github.com/aws/smithy-go v1.22.1 // indirect
github.com/aymerick/douceur v0.2.0 // indirect
+ github.com/benbjohnson/clock v1.3.0 // indirect
github.com/boombuler/barcode v1.0.1-0.20190219062509-6c824513bacc // indirect
github.com/cenkalti/backoff v2.2.1+incompatible // indirect
github.com/cenkalti/backoff/v4 v4.3.0 // indirect
@@ -94,6 +79,7 @@ require (
github.com/dnsimple/dnsimple-go v1.7.0 // indirect
github.com/docker/go-connections v0.5.0 // indirect
github.com/docker/go-units v0.5.0 // indirect
+ github.com/ebitengine/purego v0.8.2 // indirect
github.com/fatih/structs v1.1.0 // indirect
github.com/felixge/httpsnoop v1.0.4 // indirect
github.com/fsnotify/fsnotify v1.8.0 // indirect
@@ -102,11 +88,14 @@ require (
github.com/go-jose/go-jose/v4 v4.0.4 // indirect
github.com/go-logr/logr v1.4.2 // indirect
github.com/go-logr/stdr v1.2.2 // indirect
+ github.com/go-ole/go-ole v1.2.6 // indirect
github.com/go-resty/resty/v2 v2.16.2 // indirect
github.com/go-viper/mapstructure/v2 v2.2.1 // indirect
github.com/goccy/go-json v0.10.4 // indirect
github.com/gogo/protobuf v1.3.2 // indirect
github.com/golang-jwt/jwt/v4 v4.5.1 // indirect
+ github.com/golang-jwt/jwt/v5 v5.2.1 // indirect
+ github.com/golang/snappy v0.0.1 // indirect
github.com/google/go-querystring v1.1.0 // indirect
github.com/google/s2a-go v0.1.8 // indirect
github.com/googleapis/enterprise-certificate-proxy v0.3.4 // indirect
@@ -119,6 +108,7 @@ require (
github.com/hashicorp/go-cleanhttp v0.5.2 // indirect
github.com/hashicorp/go-multierror v1.1.1 // indirect
github.com/hashicorp/go-retryablehttp v0.7.7 // indirect
+ github.com/huaweicloud/huaweicloud-sdk-go-v3 v0.1.128 // indirect
github.com/iij/doapi v0.0.0-20190504054126-0bbf12d6d7df // indirect
github.com/infobloxopen/infoblox-go-client v1.1.1 // indirect
github.com/jmespath/go-jmespath v0.4.0 // indirect
@@ -155,29 +145,36 @@ require (
github.com/nzdjb/go-metaname v1.0.0 // indirect
github.com/opencontainers/go-digest v1.0.0 // indirect
github.com/opencontainers/image-spec v1.1.0 // indirect
+ github.com/opentracing/opentracing-go v1.2.1-0.20220228012449-10b1cf09e00b // indirect
github.com/ovh/go-ovh v1.6.0 // indirect
+ github.com/peterhellberg/link v1.2.0 // indirect
github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c // indirect
github.com/pkg/errors v0.9.1 // indirect
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
+ github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c // indirect
github.com/pquerna/otp v1.4.0 // indirect
github.com/sacloud/api-client-go v0.2.10 // indirect
github.com/sacloud/go-http v0.1.8 // indirect
github.com/sacloud/iaas-api-go v1.14.0 // indirect
github.com/sacloud/packages-go v0.0.10 // indirect
github.com/scaleway/scaleway-sdk-go v1.0.0-beta.30 // indirect
+ github.com/shopspring/decimal v1.3.1 // indirect
github.com/sirupsen/logrus v1.9.3 // indirect
github.com/smartystreets/go-aws-auth v0.0.0-20180515143844-0c1422d1fdb9 // indirect
github.com/softlayer/softlayer-go v1.1.7 // indirect
github.com/softlayer/xmlrpc v0.0.0-20200409220501-5f089df7cb7e // indirect
github.com/spf13/cast v1.6.0 // indirect
- github.com/stretchr/testify v1.10.0 // indirect
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.0.1065 // indirect
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/dnspod v1.0.1065 // indirect
+ github.com/tjfoc/gmsm v1.4.1 // indirect
github.com/transip/gotransip/v6 v6.26.0 // indirect
github.com/ultradns/ultradns-go-sdk v1.8.0-20241010134910-243eeec // indirect
github.com/vinyldns/go-vinyldns v0.9.16 // indirect
+ github.com/vultr/govultr/v3 v3.9.1 // indirect
github.com/yandex-cloud/go-genproto v0.0.0-20241220122821-aeb3b05efd1c // indirect
github.com/yandex-cloud/go-sdk v0.0.0-20241220131134-2393e243c134 // indirect
+ github.com/yusufpapurcu/wmi v1.2.4 // indirect
+ go.mongodb.org/mongo-driver v1.12.0 // indirect
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.54.0 // indirect
go.opentelemetry.io/otel v1.29.0 // indirect
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.27.0 // indirect
@@ -187,8 +184,8 @@ require (
go.uber.org/ratelimit v0.3.0 // indirect
golang.org/x/crypto v0.31.0 // indirect
golang.org/x/mod v0.22.0 // indirect
- golang.org/x/oauth2 v0.24.0 // indirect
golang.org/x/sync v0.10.0 // indirect
+ golang.org/x/sys v0.28.0 // indirect
golang.org/x/time v0.8.0 // indirect
golang.org/x/tools v0.28.0 // indirect
google.golang.org/api v0.214.0 // indirect
diff --git a/src/main.go b/src/main.go
index 18dd086..f577d50 100644
--- a/src/main.go
+++ b/src/main.go
@@ -37,6 +37,7 @@ import (
"net/http"
"os"
"os/signal"
+ "strings"
"syscall"
"time"
@@ -125,7 +126,13 @@ func main() {
//Start the finalize sequences
finalSequence()
- SystemWideLogger.Println(SYSTEM_NAME + " started. Visit control panel at http://localhost" + *webUIPort)
+ if strings.HasPrefix(*webUIPort, ":") {
+ //Bind to all interfaces, issue #672
+ SystemWideLogger.Println(SYSTEM_NAME + " started. Visit control panel at http://localhost" + *webUIPort)
+ } else {
+ SystemWideLogger.Println(SYSTEM_NAME + " started. Visit control panel at http://" + *webUIPort)
+ }
+
err = http.ListenAndServe(*webUIPort, csrfMiddleware(webminPanelMux))
if err != nil {
diff --git a/src/mod/auth/sso/forward/const.go b/src/mod/auth/sso/forward/const.go
index 164ef79..a4e935d 100644
--- a/src/mod/auth/sso/forward/const.go
+++ b/src/mod/auth/sso/forward/const.go
@@ -11,6 +11,7 @@ const (
DatabaseKeyResponseHeaders = "responseHeaders"
DatabaseKeyResponseClientHeaders = "responseClientHeaders"
DatabaseKeyRequestHeaders = "requestHeaders"
+ DatabaseKeyRequestIncludedCookies = "requestIncludedCookies"
DatabaseKeyRequestExcludedCookies = "requestExcludedCookies"
HeaderXForwardedProto = "X-Forwarded-Proto"
diff --git a/src/mod/auth/sso/forward/forward.go b/src/mod/auth/sso/forward/forward.go
index 16a84c9..44a2abb 100644
--- a/src/mod/auth/sso/forward/forward.go
+++ b/src/mod/auth/sso/forward/forward.go
@@ -3,7 +3,6 @@ package forward
import (
"encoding/json"
"io"
- "net"
"net/http"
"strings"
@@ -28,6 +27,10 @@ type AuthRouterOptions struct {
// headers are copied.
RequestHeaders []string
+ // RequestIncludedCookies is a list of cookie keys that if defined will be the only cookies sent in the request to
+ // the authorization server.
+ RequestIncludedCookies []string
+
// RequestExcludedCookies is a list of cookie keys that should be removed from every request sent to the upstream.
RequestExcludedCookies []string
@@ -47,16 +50,18 @@ func NewAuthRouter(options *AuthRouterOptions) *AuthRouter {
//Read settings from database if available.
options.Database.Read(DatabaseTable, DatabaseKeyAddress, &options.Address)
- responseHeaders, responseClientHeaders, requestHeaders, requestExcludedCookies := "", "", "", ""
+ responseHeaders, responseClientHeaders, requestHeaders, requestIncludedCookies, requestExcludedCookies := "", "", "", "", ""
options.Database.Read(DatabaseTable, DatabaseKeyResponseHeaders, &responseHeaders)
options.Database.Read(DatabaseTable, DatabaseKeyResponseClientHeaders, &responseClientHeaders)
options.Database.Read(DatabaseTable, DatabaseKeyRequestHeaders, &requestHeaders)
+ options.Database.Read(DatabaseTable, DatabaseKeyRequestIncludedCookies, &requestIncludedCookies)
options.Database.Read(DatabaseTable, DatabaseKeyRequestExcludedCookies, &requestExcludedCookies)
options.ResponseHeaders = strings.Split(responseHeaders, ",")
options.ResponseClientHeaders = strings.Split(responseClientHeaders, ",")
options.RequestHeaders = strings.Split(requestHeaders, ",")
+ options.RequestIncludedCookies = strings.Split(requestIncludedCookies, ",")
options.RequestExcludedCookies = strings.Split(requestExcludedCookies, ",")
return &AuthRouter{
@@ -82,11 +87,12 @@ func (ar *AuthRouter) HandleAPIOptions(w http.ResponseWriter, r *http.Request) {
}
func (ar *AuthRouter) handleOptionsGET(w http.ResponseWriter, r *http.Request) {
- js, _ := json.Marshal(map[string]interface{}{
+ js, _ := json.Marshal(map[string]any{
DatabaseKeyAddress: ar.options.Address,
DatabaseKeyResponseHeaders: ar.options.ResponseHeaders,
DatabaseKeyResponseClientHeaders: ar.options.ResponseClientHeaders,
DatabaseKeyRequestHeaders: ar.options.RequestHeaders,
+ DatabaseKeyRequestIncludedCookies: ar.options.RequestIncludedCookies,
DatabaseKeyRequestExcludedCookies: ar.options.RequestExcludedCookies,
})
@@ -108,6 +114,7 @@ func (ar *AuthRouter) handleOptionsPOST(w http.ResponseWriter, r *http.Request)
responseHeaders, _ := utils.PostPara(r, DatabaseKeyResponseHeaders)
responseClientHeaders, _ := utils.PostPara(r, DatabaseKeyResponseClientHeaders)
requestHeaders, _ := utils.PostPara(r, DatabaseKeyRequestHeaders)
+ requestIncludedCookies, _ := utils.PostPara(r, DatabaseKeyRequestIncludedCookies)
requestExcludedCookies, _ := utils.PostPara(r, DatabaseKeyRequestExcludedCookies)
// Write changes to runtime
@@ -115,6 +122,7 @@ func (ar *AuthRouter) handleOptionsPOST(w http.ResponseWriter, r *http.Request)
ar.options.ResponseHeaders = strings.Split(responseHeaders, ",")
ar.options.ResponseClientHeaders = strings.Split(responseClientHeaders, ",")
ar.options.RequestHeaders = strings.Split(requestHeaders, ",")
+ ar.options.RequestIncludedCookies = strings.Split(requestIncludedCookies, ",")
ar.options.RequestExcludedCookies = strings.Split(requestExcludedCookies, ",")
// Write changes to database
@@ -122,6 +130,7 @@ func (ar *AuthRouter) handleOptionsPOST(w http.ResponseWriter, r *http.Request)
ar.options.Database.Write(DatabaseTable, DatabaseKeyResponseHeaders, responseHeaders)
ar.options.Database.Write(DatabaseTable, DatabaseKeyResponseClientHeaders, responseClientHeaders)
ar.options.Database.Write(DatabaseTable, DatabaseKeyRequestHeaders, requestHeaders)
+ ar.options.Database.Write(DatabaseTable, DatabaseKeyRequestIncludedCookies, requestIncludedCookies)
ar.options.Database.Write(DatabaseTable, DatabaseKeyRequestExcludedCookies, requestExcludedCookies)
utils.SendOK(w)
@@ -136,60 +145,40 @@ func (ar *AuthRouter) handleOptionsMethodNotAllowed(w http.ResponseWriter, r *ht
// HandleAuthProviderRouting is the internal handler for Forward Auth authentication.
func (ar *AuthRouter) HandleAuthProviderRouting(w http.ResponseWriter, r *http.Request) error {
if ar.options.Address == "" {
- http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
-
- ar.options.Logger.PrintAndLog(LogTitle, "Address not set", nil)
-
- return ErrInternalServerError
+ return ar.handle500Error(w, nil, "Address not set")
}
// Make a request to Authz Server to verify the request
+ // TODO: Add opt-in support for copying the request body to the forward auth request. Currently it's just an
+ // empty body which is usually fine in most instances. It's likely best to see if anyone wants this feature
+ // as I'm unaware of any specific forward auth implementation that needs it.
req, err := http.NewRequest(http.MethodGet, ar.options.Address, nil)
if err != nil {
- http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
-
- ar.options.Logger.PrintAndLog(LogTitle, "Unable to create request", err)
-
- return ErrInternalServerError
+ return ar.handle500Error(w, err, "Unable to create request")
}
- // TODO: Add opt-in support for copying the request body to the forward auth request.
headerCopyIncluded(r.Header, req.Header, ar.options.RequestHeaders, true)
+ headerCookieRedact(r, ar.options.RequestIncludedCookies, false)
- // TODO: Add support for upstream headers.
+ // TODO: Add support for headers from upstream proxies. This will likely involve implementing some form of
+ // proxy specific trust system within Zoraxy.
rSetForwardedHeaders(r, req)
// Make the Authz Request.
respForwarded, err := ar.client.Do(req)
if err != nil {
- http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
-
- ar.options.Logger.PrintAndLog(LogTitle, "Unable to perform forwarded auth due to a request error", err)
-
- return ErrInternalServerError
+ return ar.handle500Error(w, err, "Unable to perform forwarded auth due to a request error")
}
defer respForwarded.Body.Close()
- body, err := io.ReadAll(respForwarded.Body)
- if err != nil {
- http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
-
- ar.options.Logger.PrintAndLog(LogTitle, "Unable to read response to forward auth request", err)
-
- return ErrInternalServerError
- }
-
// Responses within the 200-299 range are considered successful and allow the proxy to handle the request.
if respForwarded.StatusCode >= http.StatusOK && respForwarded.StatusCode < http.StatusMultipleChoices {
if len(ar.options.ResponseClientHeaders) != 0 {
headerCopyIncluded(respForwarded.Header, w.Header(), ar.options.ResponseClientHeaders, false)
}
- if len(ar.options.RequestExcludedCookies) != 0 {
- // If the user has specified a list of cookies to be removed from the request, deterministically remove them.
- headerCookieRedact(r, ar.options.RequestExcludedCookies)
- }
+ headerCookieRedact(r, ar.options.RequestExcludedCookies, true)
if len(ar.options.ResponseHeaders) != 0 {
// Copy specific user-specified headers from the response of the forward auth request to the request sent to the
@@ -197,138 +186,32 @@ func (ar *AuthRouter) HandleAuthProviderRouting(w http.ResponseWriter, r *http.R
headerCopyIncluded(respForwarded.Header, w.Header(), ar.options.ResponseHeaders, false)
}
+ // Return the request to the proxy for forwarding to the backend.
return nil
}
- // Copy the response.
+ // Copy the unsuccessful response.
headerCopyExcluded(respForwarded.Header, w.Header(), nil)
w.WriteHeader(respForwarded.StatusCode)
+
+ body, err := io.ReadAll(respForwarded.Body)
+ if err != nil {
+ return ar.handle500Error(w, err, "Unable to read response to forward auth request")
+ }
+
if _, err = w.Write(body); err != nil {
- http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
-
- ar.options.Logger.PrintAndLog(LogTitle, "Unable to write response", err)
-
- return ErrInternalServerError
+ return ar.handle500Error(w, err, "Unable to write response")
}
return ErrUnauthorized
}
-func scheme(r *http.Request) string {
- if r.TLS != nil {
- return "https"
- }
+// handle500Error is func intended on factorizing a commonly repeated functional flow within this provider.
+func (ar *AuthRouter) handle500Error(w http.ResponseWriter, err error, message string) error {
+ http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
- return "http"
-}
-
-func headerCookieRedact(r *http.Request, excluded []string) {
- original := r.Cookies()
-
- if len(original) == 0 {
- return
- }
-
- var cookies []string
-
- for _, cookie := range original {
- if stringInSlice(cookie.Name, excluded) {
- continue
- }
-
- cookies = append(cookies, cookie.String())
- }
-
- r.Header.Set(HeaderCookie, strings.Join(cookies, "; "))
-}
-
-func headerCopyExcluded(original, destination http.Header, excludedHeaders []string) {
- for key, values := range original {
- // We should never copy the headers in the below list.
- if stringInSliceFold(key, doNotCopyHeaders) {
- continue
- }
-
- if stringInSliceFold(key, excludedHeaders) {
- continue
- }
-
- destination[key] = append(destination[key], values...)
- }
-}
-
-func headerCopyIncluded(original, destination http.Header, includedHeaders []string, allIfEmpty bool) {
- if allIfEmpty && len(includedHeaders) == 0 {
- headerCopyAll(original, destination)
- } else {
- headerCopyIncludedExact(original, destination, includedHeaders)
- }
-}
-
-func headerCopyAll(original, destination http.Header) {
- for key, values := range original {
- // We should never copy the headers in the below list, even if they're in the list provided by a user.
- if stringInSliceFold(key, doNotCopyHeaders) {
- continue
- }
-
- destination[key] = append(destination[key], values...)
- }
-}
-
-func headerCopyIncludedExact(original, destination http.Header, keys []string) {
- for _, key := range keys {
- // We should never copy the headers in the below list, even if they're in the list provided by a user.
- if stringInSliceFold(key, doNotCopyHeaders) {
- continue
- }
-
- if values, ok := original[key]; ok {
- destination[key] = append(destination[key], values...)
- }
- }
-}
-
-func stringInSlice(needle string, haystack []string) bool {
- if len(haystack) == 0 {
- return false
- }
-
- for _, v := range haystack {
- if needle == v {
- return true
- }
- }
-
- return false
-}
-
-func stringInSliceFold(needle string, haystack []string) bool {
- if len(haystack) == 0 {
- return false
- }
-
- for _, v := range haystack {
- if strings.EqualFold(needle, v) {
- return true
- }
- }
-
- return false
-}
-
-func rSetForwardedHeaders(r, req *http.Request) {
- if r.RemoteAddr != "" {
- before, _, _ := strings.Cut(r.RemoteAddr, ":")
-
- if ip := net.ParseIP(before); ip != nil {
- req.Header.Set(HeaderXForwardedFor, ip.String())
- }
- }
-
- req.Header.Set(HeaderXForwardedMethod, r.Method)
- req.Header.Set(HeaderXForwardedProto, scheme(r))
- req.Header.Set(HeaderXForwardedHost, r.Host)
- req.Header.Set(HeaderXForwardedURI, r.URL.Path)
+ ar.options.Logger.PrintAndLog(LogTitle, message, err)
+
+ return ErrInternalServerError
}
diff --git a/src/mod/auth/sso/forward/util.go b/src/mod/auth/sso/forward/util.go
new file mode 100644
index 0000000..7ba9b3a
--- /dev/null
+++ b/src/mod/auth/sso/forward/util.go
@@ -0,0 +1,137 @@
+package forward
+
+import (
+ "net"
+ "net/http"
+ "strings"
+)
+
+func scheme(r *http.Request) string {
+ if r.TLS != nil {
+ return "https"
+ }
+
+ return "http"
+}
+
+func headerCookieRedact(r *http.Request, names []string, exclude bool) {
+ if len(names) == 0 {
+ return
+ }
+
+ original := r.Cookies()
+
+ if len(original) == 0 {
+ return
+ }
+
+ var cookies []string
+
+ for _, cookie := range original {
+ if exclude && stringInSlice(cookie.Name, names) {
+ continue
+ } else if !exclude && !stringInSlice(cookie.Name, names) {
+ continue
+ }
+
+ cookies = append(cookies, cookie.String())
+ }
+
+ value := strings.Join(cookies, "; ")
+
+ r.Header.Set(HeaderCookie, value)
+
+ return
+}
+
+func headerCopyExcluded(original, destination http.Header, excludedHeaders []string) {
+ for key, values := range original {
+ // We should never copy the headers in the below list.
+ if stringInSliceFold(key, doNotCopyHeaders) {
+ continue
+ }
+
+ if stringInSliceFold(key, excludedHeaders) {
+ continue
+ }
+
+ destination[key] = append(destination[key], values...)
+ }
+}
+
+func headerCopyIncluded(original, destination http.Header, includedHeaders []string, allIfEmpty bool) {
+ if allIfEmpty && len(includedHeaders) == 0 {
+ headerCopyAll(original, destination)
+ } else {
+ headerCopyIncludedExact(original, destination, includedHeaders)
+ }
+}
+
+func headerCopyAll(original, destination http.Header) {
+ for key, values := range original {
+ // We should never copy the headers in the below list, even if they're in the list provided by a user.
+ if stringInSliceFold(key, doNotCopyHeaders) {
+ continue
+ }
+
+ destination[key] = append(destination[key], values...)
+ }
+}
+
+func headerCopyIncludedExact(original, destination http.Header, keys []string) {
+ for key, values := range original {
+ // We should never copy the headers in the below list, even if they're in the list provided by a user.
+ if stringInSliceFold(key, doNotCopyHeaders) {
+ continue
+ }
+
+ if !stringInSliceFold(key, keys) {
+ continue
+ }
+
+ destination[key] = append(destination[key], values...)
+ }
+}
+
+func stringInSlice(needle string, haystack []string) bool {
+ if len(haystack) == 0 {
+ return false
+ }
+
+ for _, v := range haystack {
+ if needle == v {
+ return true
+ }
+ }
+
+ return false
+}
+
+func stringInSliceFold(needle string, haystack []string) bool {
+ if len(haystack) == 0 {
+ return false
+ }
+
+ for _, v := range haystack {
+ if strings.EqualFold(needle, v) {
+ return true
+ }
+ }
+
+ return false
+}
+
+func rSetForwardedHeaders(r, req *http.Request) {
+ if r.RemoteAddr != "" {
+ before, _, _ := strings.Cut(r.RemoteAddr, ":")
+
+ if ip := net.ParseIP(before); ip != nil {
+ req.Header.Set(HeaderXForwardedFor, ip.String())
+ }
+ }
+
+ req.Header.Set(HeaderXForwardedMethod, r.Method)
+ req.Header.Set(HeaderXForwardedProto, scheme(r))
+ req.Header.Set(HeaderXForwardedHost, r.Host)
+ req.Header.Set(HeaderXForwardedURI, r.URL.Path)
+}
diff --git a/src/mod/auth/sso/forward/util_test.go b/src/mod/auth/sso/forward/util_test.go
new file mode 100644
index 0000000..abd0241
--- /dev/null
+++ b/src/mod/auth/sso/forward/util_test.go
@@ -0,0 +1,217 @@
+package forward
+
+import (
+ "crypto/tls"
+ "net/http"
+ "testing"
+
+ "github.com/stretchr/testify/assert"
+)
+
+func TestScheme(t *testing.T) {
+ testCases := []struct {
+ name string
+ have *http.Request
+ expected string
+ }{
+ {
+ "ShouldHandleDefault",
+ &http.Request{},
+ "http",
+ },
+ {
+ "ShouldHandleExplicit",
+ &http.Request{
+ TLS: nil,
+ },
+ "http",
+ },
+ {
+ "ShouldHandleHTTPS",
+ &http.Request{
+ TLS: &tls.ConnectionState{},
+ },
+ "https",
+ },
+ }
+
+ for _, tc := range testCases {
+ t.Run(tc.name, func(t *testing.T) {
+ assert.Equal(t, tc.expected, scheme(tc.have))
+ })
+ }
+}
+
+func TestHeaderCookieRedact(t *testing.T) {
+ testCases := []struct {
+ name string
+ have string
+ names []string
+ expectedInclude string
+ expectedExclude string
+ }{
+ {
+ "ShouldHandleIncludeEmptyWithoutSettings",
+ "",
+ nil,
+ "",
+ "",
+ },
+ {
+ "ShouldHandleIncludeEmptyWithSettings",
+ "",
+ []string{"include"},
+ "",
+ "",
+ },
+ {
+ "ShouldHandleValueWithoutSettings",
+ "include=value; exclude=value",
+ nil,
+ "include=value; exclude=value",
+ "include=value; exclude=value",
+ },
+ {
+ "ShouldHandleValueWithSettings",
+ "include=value; exclude=value",
+ []string{"include"},
+ "include=value",
+ "exclude=value",
+ },
+ }
+
+ for _, tc := range testCases {
+ t.Run(tc.name, func(t *testing.T) {
+ var include, exclude *http.Request
+
+ include, exclude = &http.Request{Header: http.Header{}}, &http.Request{Header: http.Header{}}
+
+ if tc.have != "" {
+ include.Header.Set(HeaderCookie, tc.have)
+ exclude.Header.Set(HeaderCookie, tc.have)
+ }
+
+ headerCookieRedact(include, tc.names, false)
+
+ assert.Equal(t, tc.expectedInclude, include.Header.Get(HeaderCookie))
+
+ headerCookieRedact(exclude, tc.names, true)
+
+ assert.Equal(t, tc.expectedExclude, exclude.Header.Get(HeaderCookie))
+ })
+ }
+}
+
+func TestHeaderCopyExcluded(t *testing.T) {
+ testCases := []struct {
+ name string
+ original http.Header
+ excluded []string
+ expected http.Header
+ }{
+ {
+ "ShouldHandleNoSettingsNoHeaders",
+ http.Header{},
+ nil,
+ http.Header{},
+ },
+ {
+ "ShouldHandleNoSettingsWithHeaders",
+ http.Header{
+ "Example": []string{"value", "other"},
+ "Exclude": []string{"value", "other"},
+ HeaderUpgrade: []string{"do", "not", "copy"},
+ },
+ nil,
+ http.Header{
+ "Example": []string{"value", "other"},
+ "Exclude": []string{"value", "other"},
+ },
+ },
+ {
+ "ShouldHandleSettingsWithHeaders",
+ http.Header{
+ "Example": []string{"value", "other"},
+ "Exclude": []string{"value", "other"},
+ HeaderUpgrade: []string{"do", "not", "copy"},
+ },
+ []string{"exclude"},
+ http.Header{
+ "Example": []string{"value", "other"},
+ },
+ },
+ }
+
+ for _, tc := range testCases {
+ t.Run(tc.name, func(t *testing.T) {
+ headers := http.Header{}
+
+ headerCopyExcluded(tc.original, headers, tc.excluded)
+
+ assert.Equal(t, tc.expected, headers)
+ })
+ }
+}
+
+func TestHeaderCopyIncluded(t *testing.T) {
+ testCases := []struct {
+ name string
+ original http.Header
+ included []string
+ expected http.Header
+ expectedAll http.Header
+ }{
+ {
+ "ShouldHandleNoSettingsNoHeaders",
+ http.Header{},
+ nil,
+ http.Header{},
+ http.Header{},
+ },
+ {
+ "ShouldHandleNoSettingsWithHeaders",
+ http.Header{
+ "Example": []string{"value", "other"},
+ "Include": []string{"value", "other"},
+ HeaderUpgrade: []string{"do", "not", "copy"},
+ },
+ nil,
+ http.Header{},
+ http.Header{
+ "Example": []string{"value", "other"},
+ "Include": []string{"value", "other"},
+ },
+ },
+ {
+ "ShouldHandleSettingsWithHeaders",
+ http.Header{
+ "Example": []string{"value", "other"},
+ "Include": []string{"value", "other"},
+ HeaderUpgrade: []string{"do", "not", "copy"},
+ },
+ []string{"include"},
+ http.Header{
+ "Include": []string{"value", "other"},
+ },
+ http.Header{
+ "Include": []string{"value", "other"},
+ },
+ },
+ }
+
+ for _, tc := range testCases {
+ t.Run(tc.name, func(t *testing.T) {
+ headers := http.Header{}
+
+ headerCopyIncluded(tc.original, headers, tc.included, false)
+
+ assert.Equal(t, tc.expected, headers)
+
+ headers = http.Header{}
+
+ headerCopyIncluded(tc.original, headers, tc.included, true)
+
+ assert.Equal(t, tc.expectedAll, headers)
+ })
+ }
+}
diff --git a/src/mod/dynamicproxy/dpcore/dpcore.go b/src/mod/dynamicproxy/dpcore/dpcore.go
index eb0a489..4566769 100644
--- a/src/mod/dynamicproxy/dpcore/dpcore.go
+++ b/src/mod/dynamicproxy/dpcore/dpcore.go
@@ -70,8 +70,9 @@ type ResponseRewriteRuleSet struct {
DownstreamHeaders [][]string
/* Advance Usecase Options */
- HostHeaderOverwrite string //Force overwrite of request "Host" header (advanced usecase)
- NoRemoveHopByHop bool //Do not remove hop-by-hop headers (advanced usecase)
+ HostHeaderOverwrite string //Force overwrite of request "Host" header (advanced usecase)
+ NoRemoveHopByHop bool //Do not remove hop-by-hop headers (advanced usecase)
+ DisableChunkedTransferEncoding bool //Disable chunked transfer encoding
/* System Information Payload */
Version string //Version number of Zoraxy, use for X-Proxy-By
@@ -287,7 +288,7 @@ func (p *ReverseProxy) ProxyHTTP(rw http.ResponseWriter, req *http.Request, rrr
rewriteUserAgent(outreq.Header, "Zoraxy/"+rrr.Version)
//Fix proxmox transfer encoding bug if detected Proxmox Cookie
- if domainsniff.IsProxmox(req) {
+ if rrr.DisableChunkedTransferEncoding || domainsniff.IsProxmox(req) {
outreq.TransferEncoding = []string{"identity"}
}
diff --git a/src/mod/dynamicproxy/proxyRequestHandler.go b/src/mod/dynamicproxy/proxyRequestHandler.go
index 36e22df..9ba8813 100644
--- a/src/mod/dynamicproxy/proxyRequestHandler.go
+++ b/src/mod/dynamicproxy/proxyRequestHandler.go
@@ -11,7 +11,6 @@ import (
"sort"
"strings"
- "imuslab.com/zoraxy/mod/dynamicproxy/domainsniff"
"imuslab.com/zoraxy/mod/dynamicproxy/dpcore"
"imuslab.com/zoraxy/mod/dynamicproxy/rewrite"
"imuslab.com/zoraxy/mod/netutils"
@@ -186,16 +185,17 @@ func (h *ProxyHandler) hostRequest(w http.ResponseWriter, r *http.Request, targe
//Handle the request reverse proxy
statusCode, err := selectedUpstream.ServeHTTP(w, r, &dpcore.ResponseRewriteRuleSet{
- ProxyDomain: selectedUpstream.OriginIpOrDomain,
- OriginalHost: reqHostname,
- UseTLS: selectedUpstream.RequireTLS,
- NoCache: h.Parent.Option.NoCache,
- PathPrefix: "",
- UpstreamHeaders: upstreamHeaders,
- DownstreamHeaders: downstreamHeaders,
- HostHeaderOverwrite: headerRewriteOptions.RequestHostOverwrite,
- NoRemoveHopByHop: headerRewriteOptions.DisableHopByHopHeaderRemoval,
- Version: target.parent.Option.HostVersion,
+ ProxyDomain: selectedUpstream.OriginIpOrDomain,
+ OriginalHost: reqHostname,
+ UseTLS: selectedUpstream.RequireTLS,
+ NoCache: h.Parent.Option.NoCache,
+ PathPrefix: "",
+ UpstreamHeaders: upstreamHeaders,
+ DownstreamHeaders: downstreamHeaders,
+ DisableChunkedTransferEncoding: target.DisableChunkedTransferEncoding,
+ HostHeaderOverwrite: headerRewriteOptions.RequestHostOverwrite,
+ NoRemoveHopByHop: headerRewriteOptions.DisableHopByHopHeaderRemoval,
+ Version: target.parent.Option.HostVersion,
})
//validate the error
@@ -244,8 +244,8 @@ func (h *ProxyHandler) vdirRequest(w http.ResponseWriter, r *http.Request, targe
h.Parent.logRequest(r, true, 101, "vdir-websocket", r.Host, target.Domain)
wspHandler := websocketproxy.NewProxy(u, websocketproxy.Options{
SkipTLSValidation: target.SkipCertValidations,
- SkipOriginCheck: target.parent.EnableWebsocketCustomHeaders, //You should not use websocket via virtual directory. But keep this to true for compatibility
- CopyAllHeaders: domainsniff.RequireWebsocketHeaderCopy(r), //Left this as default to prevent nginx user setting / as vdir
+ SkipOriginCheck: true, //You should not use websocket via virtual directory. But keep this to true for compatibility
+ CopyAllHeaders: target.parent.EnableWebsocketCustomHeaders, //Left this as default to prevent nginx user setting / as vdir
UserDefinedHeaders: target.parent.HeaderRewriteRules.UserDefinedHeaders,
Logger: h.Parent.Option.Logger,
})
@@ -280,14 +280,15 @@ func (h *ProxyHandler) vdirRequest(w http.ResponseWriter, r *http.Request, targe
//Handle the virtual directory reverse proxy request
statusCode, err := target.proxy.ServeHTTP(w, r, &dpcore.ResponseRewriteRuleSet{
- ProxyDomain: target.Domain,
- OriginalHost: reqHostname,
- UseTLS: target.RequireTLS,
- PathPrefix: target.MatchingPath,
- UpstreamHeaders: upstreamHeaders,
- DownstreamHeaders: downstreamHeaders,
- HostHeaderOverwrite: headerRewriteOptions.RequestHostOverwrite,
- Version: target.parent.parent.Option.HostVersion,
+ ProxyDomain: target.Domain,
+ OriginalHost: reqHostname,
+ UseTLS: target.RequireTLS,
+ PathPrefix: target.MatchingPath,
+ UpstreamHeaders: upstreamHeaders,
+ DownstreamHeaders: downstreamHeaders,
+ DisableChunkedTransferEncoding: target.parent.DisableChunkedTransferEncoding,
+ HostHeaderOverwrite: headerRewriteOptions.RequestHostOverwrite,
+ Version: target.parent.parent.Option.HostVersion,
})
var dnsError *net.DNSError
diff --git a/src/mod/dynamicproxy/typedef.go b/src/mod/dynamicproxy/typedef.go
index 36eb39e..7a5b334 100644
--- a/src/mod/dynamicproxy/typedef.go
+++ b/src/mod/dynamicproxy/typedef.go
@@ -194,6 +194,9 @@ type ProxyEndpoint struct {
//Uptime Monitor
DisableUptimeMonitor bool //Disable uptime monitor for this endpoint
+ // Chunked Transfer Encoding
+ DisableChunkedTransferEncoding bool //Disable chunked transfer encoding for this endpoint
+
//Access Control
AccessFilterUUID string //Access filter ID
diff --git a/src/mod/webserv/handler.go b/src/mod/webserv/handler.go
index 74fef4f..e040b1f 100644
--- a/src/mod/webserv/handler.go
+++ b/src/mod/webserv/handler.go
@@ -17,22 +17,24 @@ import (
*/
type StaticWebServerStatus struct {
- ListeningPort int
- EnableDirectoryListing bool
- WebRoot string
- Running bool
- EnableWebDirManager bool
+ ListeningPort int
+ EnableDirectoryListing bool
+ WebRoot string
+ Running bool
+ EnableWebDirManager bool
+ DisableListenToAllInterface bool
}
// Handle getting current static web server status
func (ws *WebServer) HandleGetStatus(w http.ResponseWriter, r *http.Request) {
listeningPortInt, _ := strconv.Atoi(ws.option.Port)
currentStatus := StaticWebServerStatus{
- ListeningPort: listeningPortInt,
- EnableDirectoryListing: ws.option.EnableDirectoryListing,
- WebRoot: ws.option.WebRoot,
- Running: ws.isRunning,
- EnableWebDirManager: ws.option.EnableWebDirManager,
+ ListeningPort: listeningPortInt,
+ EnableDirectoryListing: ws.option.EnableDirectoryListing,
+ WebRoot: ws.option.WebRoot,
+ Running: ws.isRunning,
+ EnableWebDirManager: ws.option.EnableWebDirManager,
+ DisableListenToAllInterface: ws.option.DisableListenToAllInterface,
}
js, _ := json.Marshal(currentStatus)
@@ -91,3 +93,19 @@ func (ws *WebServer) SetEnableDirectoryListing(w http.ResponseWriter, r *http.Re
ws.option.EnableDirectoryListing = enableList
utils.SendOK(w)
}
+
+// Get or set disable listen to all interface settings
+func (ws *WebServer) SetDisableListenToAllInterface(w http.ResponseWriter, r *http.Request) {
+ disableListen, err := utils.PostBool(r, "disable")
+ if err != nil {
+ utils.SendErrorResponse(w, "invalid setting given")
+ return
+ }
+ err = ws.option.Sysdb.Write("webserv", "disableListenToAllInterface", disableListen)
+ if err != nil {
+ utils.SendErrorResponse(w, "unable to save setting")
+ return
+ }
+ ws.option.DisableListenToAllInterface = disableListen
+ utils.SendOK(w)
+}
diff --git a/src/mod/webserv/webserv.go b/src/mod/webserv/webserv.go
index efb8dc9..85214e4 100644
--- a/src/mod/webserv/webserv.go
+++ b/src/mod/webserv/webserv.go
@@ -25,13 +25,21 @@ import (
//go:embed templates/*
var templates embed.FS
+/*
+WebServerOptions define the default option for the webserv
+might get override by user settings loaded from db
+
+Any changes in here might need to also update the StaticWebServerStatus struct
+in handler.go. See handler.go for more information.
+*/
type WebServerOptions struct {
- Port string //Port for listening
- EnableDirectoryListing bool //Enable listing of directory
- WebRoot string //Folder for stroing the static web folders
- EnableWebDirManager bool //Enable web file manager to handle files in web directory
- Logger *logger.Logger //System logger
- Sysdb *database.Database //Database for storing configs
+ Port string //Port for listening
+ EnableDirectoryListing bool //Enable listing of directory
+ WebRoot string //Folder for stroing the static web folders
+ EnableWebDirManager bool //Enable web file manager to handle files in web directory
+ DisableListenToAllInterface bool // Disable listening to all interfaces, only listen to localhost
+ Logger *logger.Logger //System logger
+ Sysdb *database.Database //Database for storing configs
}
type WebServer struct {
@@ -92,6 +100,11 @@ func (ws *WebServer) RestorePreviousState() {
ws.option.Sysdb.Read("webserv", "dirlist", &enableDirList)
ws.option.EnableDirectoryListing = enableDirList
+ //Set disable listen to all interface
+ disableListenToAll := ws.option.DisableListenToAllInterface
+ ws.option.Sysdb.Read("webserv", "disableListenToAllInterface", &disableListenToAll)
+ ws.option.DisableListenToAllInterface = disableListenToAll
+
//Check the running state
webservRunning := true
ws.option.Sysdb.Read("webserv", "enabled", &webservRunning)
@@ -156,8 +169,12 @@ func (ws *WebServer) Start() error {
fs := http.FileServer(http.Dir(filepath.Join(ws.option.WebRoot, "html")))
ws.mux.Handle("/", ws.fsMiddleware(fs))
+ listenAddr := ":" + ws.option.Port
+ if ws.option.DisableListenToAllInterface {
+ listenAddr = "127.0.0.1:" + ws.option.Port
+ }
ws.server = &http.Server{
- Addr: ":" + ws.option.Port,
+ Addr: listenAddr,
Handler: ws.mux,
}
diff --git a/src/reverseproxy.go b/src/reverseproxy.go
index abb4e79..aaccaaf 100644
--- a/src/reverseproxy.go
+++ b/src/reverseproxy.go
@@ -556,6 +556,9 @@ func ReverseProxyHandleEditEndpoint(w http.ResponseWriter, r *http.Request) {
proxyRateLimit = 1000
}
+ // Disable chunked Encoding
+ disableChunkedEncoding, _ := utils.PostBool(r, "dChunkedEnc")
+
//Load the previous basic auth credentials from current proxy rules
targetProxyEntry, err := dynamicProxyRouter.LoadProxy(rootNameOrMatchingDomain)
if err != nil {
@@ -596,6 +599,7 @@ func ReverseProxyHandleEditEndpoint(w http.ResponseWriter, r *http.Request) {
newProxyEndpoint.RateLimit = proxyRateLimit
newProxyEndpoint.UseStickySession = useStickySession
newProxyEndpoint.DisableUptimeMonitor = disbleUtm
+ newProxyEndpoint.DisableChunkedTransferEncoding = disableChunkedEncoding
newProxyEndpoint.Tags = tags
//Prepare to replace the current routing rule
@@ -673,6 +677,83 @@ func ReverseProxyHandleAlias(w http.ResponseWriter, r *http.Request) {
utils.SendOK(w)
}
+func ReverseProxyHandleSetHostname(w http.ResponseWriter, r *http.Request) {
+ if r.Method != http.MethodPost {
+ utils.SendErrorResponse(w, "Method not supported")
+ return
+ }
+
+ originalRootnameOrMatchingDomain, err := utils.PostPara(r, "oldHostname")
+ if err != nil {
+ utils.SendErrorResponse(w, "Invalid original hostname given")
+ return
+ }
+
+ newHostname, err := utils.PostPara(r, "newHostname")
+ if err != nil {
+ utils.SendErrorResponse(w, "Invalid new hostname given")
+ return
+ }
+
+ originalRootnameOrMatchingDomain = strings.TrimSpace(originalRootnameOrMatchingDomain)
+ newHostname = strings.TrimSpace(newHostname)
+ if newHostname == "/" {
+ //Reserevd, reutrn error
+ utils.SendErrorResponse(w, "Invalid new hostname: system reserved path")
+ return
+ }
+
+ //Check if the endpoint already exists
+ _, err = dynamicProxyRouter.LoadProxy(newHostname)
+ if err == nil {
+ //Endpoint already exists, return error
+ utils.SendErrorResponse(w, "Endpoint with this hostname already exists")
+ return
+ }
+
+ //Clone, edit the endpoint and remove the original one
+ ept, err := dynamicProxyRouter.LoadProxy(originalRootnameOrMatchingDomain)
+ if err != nil {
+ utils.SendErrorResponse(w, err.Error())
+ return
+ }
+
+ newEndpoint := ept.Clone()
+ newEndpoint.RootOrMatchingDomain = newHostname
+
+ //Prepare to replace the current routing rule
+ readyRoutingRule, err := dynamicProxyRouter.PrepareProxyRoute(newEndpoint)
+ if err != nil {
+ utils.SendErrorResponse(w, err.Error())
+ return
+ }
+
+ //Remove the old endpoint from runtime
+ err = dynamicProxyRouter.RemoveProxyEndpointByRootname(originalRootnameOrMatchingDomain)
+ if err != nil {
+ utils.SendErrorResponse(w, err.Error())
+ return
+ }
+
+ //Remove the config from file
+ err = RemoveReverseProxyConfig(originalRootnameOrMatchingDomain)
+ if err != nil {
+ utils.SendErrorResponse(w, err.Error())
+ return
+ }
+
+ //Add the new endpoint to runtime
+ dynamicProxyRouter.AddProxyRouteToRuntime(readyRoutingRule)
+
+ //Save it to file
+ SaveReverseProxyConfig(newEndpoint)
+
+ //Update uptime monitor targets
+ UpdateUptimeMonitorTargets()
+
+ utils.SendOK(w)
+}
+
func DeleteProxyEndpoint(w http.ResponseWriter, r *http.Request) {
ep, err := utils.PostPara(r, "ep")
if err != nil {
diff --git a/src/web/components/httprp.html b/src/web/components/httprp.html
index 484eb37..1809e1a 100644
--- a/src/web/components/httprp.html
+++ b/src/web/components/httprp.html
@@ -9,7 +9,7 @@
}
.subdEntry td:not(.ignoremw){
- min-width: 200px;
+ min-width: 100px;
}
.httpProxyListTools{
@@ -20,10 +20,150 @@
cursor: pointer;
}
+ th.no-sort{
+ cursor: default !important;
+ }
+
.tag-select:hover{
text-decoration: underline;
opacity: 0.8;
}
+
+ .inlineEditActionBtn{
+ border: 0px solid transparent !important;
+ box-shadow: none !important;
+ background-color: transparent !important;
+ }
+
+ body.darkTheme .ui.basic.small.icon.circular.button.inlineEditActionBtn{
+ border: 0px solid transparent !important;
+ }
+
+ /* Custom, non overlaying modal for proxy rule editing */
+ #httprpEditModal{
+ position: fixed;
+ top: 50%;
+ left: 50%;
+ transform: translate(-50%, -50%);
+ width: 68vw;
+ height: 75vh;
+ background-color: var(--theme_bg_primary);
+ padding: 1.4em;
+ border-radius: .6em;
+ box-shadow: 0px 4px 8px rgba(0, 0, 0, 0.2), 0px 8px 16px rgba(0, 0, 0, 0.2);
+ z-index: 8;
+ max-width: 840px;
+ }
+
+ #httprpEditModal .rpconfig_content{
+ height: 100%;
+ }
+
+ #httprpEditModal .editor_side_wrapper{
+ position: absolute;
+ top: 0;
+ right: 0;
+ height:100%;
+ width: 100%;
+ background-color: var(--theme_bg_primary);
+ }
+
+ #httprpEditModal .wrapper_frame{
+ width: 100%;
+ border: 1px solid var(--divider_color);
+ border-radius: 0.5em;
+ height: calc(100%);
+ }
+
+ #httprpEditModal .editor_side_wrapper .wrapper_frame{
+ height: calc(100% - 30px);
+ }
+
+ #httprpEditModal .editor_back_button {
+ border: none;
+ background-color: transparent;
+ box-shadow: none;
+ color: var(--text_color);
+ font-size: 2.2em;
+ cursor: pointer;
+ padding-top: 20px;
+ }
+
+ #httprpEditModal .editor_back_button:hover{
+ opacity: 0.5;
+ }
+
+ @media screen and (max-width: 1024px) {
+ #httprpEditModal {
+ width: 85vw;
+ }
+ }
+
+ @media screen and (max-width: 768px) {
+ #httprpEditModal {
+ height: 80vh;
+ border-radius: 0;
+ overflow-y: scroll;
+ overflow-x: hidden;
+ width: 100%;
+ }
+
+ /* Override the ui grid */
+ #httprpEditModalSideMenu{
+ width: 16% !important;
+ }
+
+ #httprpEditModalContentWindow{
+ width: 84% !important;
+ }
+
+ #httprpEditModal{
+ padding-left: 0;
+ padding-right: 0;
+ }
+
+ .editorSideMenuText{
+ display:none;
+ }
+
+ .hrpedit_menu_item{
+ height: 2.4em;
+ }
+
+ .httpProxyEditClosePC{
+ display:none !important;
+ }
+ .httpProxyEditCloseMobile{
+ display:block !important;
+ position: absolute;
+ left: 1.25em !important;
+ bottom: 1em;
+ }
+ }
+
+ @media screen and (min-width: 769px) {
+ .httpProxyEditClosePC{
+ display:block !important;
+ }
+ .httpProxyEditCloseMobile{
+ display:none !important;
+ }
+ }
+
+ #httprpEditDarkenLayer {
+ position: fixed;
+ top: 0;
+ left: 0;
+ width: 100%;
+ height: 100%;
+ background-color: rgba(0, 0, 0, 0.5);
+ z-index: 7;
+ backdrop-filter: blur(3px);
+ }
+
+ #httprpEditModalWrapper {
+ display: none; /* Hidden by default */
+ }