From 8483e3ef50356dc843907e1ada8fbe78707c7570 Mon Sep 17 00:00:00 2001 From: dencoded <33698537+dencoded@users.noreply.github.com> Date: Wed, 17 Jan 2018 12:26:25 -0500 Subject: [PATCH 1/2] Added new `proxy.skip_target_path_escaping` to disable URL escaping (#1382) Fix #1374 --- apidef/api_definitions.go | 1 + gateway_test.go | 190 +++++++++++++++++++++++++++++++++++++- handler_success.go | 2 + helpers_test.go | 24 +++-- reverse_proxy.go | 14 +++ 5 files changed, 220 insertions(+), 11 deletions(-) diff --git a/apidef/api_definitions.go b/apidef/api_definitions.go index a4a2d775476c..20d34f648d13 100644 --- a/apidef/api_definitions.go +++ b/apidef/api_definitions.go @@ -348,6 +348,7 @@ type APIDefinition struct { StructuredTargetList *HostList `bson:"-" json:"-"` CheckHostAgainstUptimeTests bool `bson:"check_host_against_uptime_tests" json:"check_host_against_uptime_tests"` ServiceDiscovery ServiceDiscoveryConfiguration `bson:"service_discovery" json:"service_discovery"` + SkipTargetPathEscaping bool `bson:"skip_target_path_escaping" json:"skip_target_path_escaping"` } `bson:"proxy" json:"proxy"` DisableRateLimit bool `bson:"disable_rate_limit" json:"disable_rate_limit"` DisableQuota bool `bson:"disable_quota" json:"disable_quota"` diff --git a/gateway_test.go b/gateway_test.go index b8cafdfe869b..2ba073b56e81 100644 --- a/gateway_test.go +++ b/gateway_test.go @@ -47,6 +47,7 @@ var ( const defaultListenPort = 8080 var defaultTestConfig config.Config +var testServerRouter *mux.Router func resetTestConfig() { configMu.Lock() @@ -75,9 +76,10 @@ func reloadSimulation() { } func TestMain(m *testing.M) { + testServerRouter = testHttpHandler() testServer := &http.Server{ Addr: testHttpListen, - Handler: testHttpHandler(), + Handler: testServerRouter, ReadTimeout: 1 * time.Second, WriteTimeout: 1 * time.Second, MaxHeaderBytes: 1 << 20, @@ -266,6 +268,190 @@ func TestParambasedAuth(t *testing.T) { }) } +func TestSkipTargetPassEscapingOff(t *testing.T) { + ts := newTykTestServer() + defer ts.Close() + + t.Run("With escaping, default", func(t *testing.T) { + buildAndLoadAPI(func(spec *APISpec) { + spec.Proxy.SkipTargetPathEscaping = false + spec.Proxy.ListenPath = "/" + }) + + ts.Run(t, []test.TestCase{ + {Path: "/(abc,xyz)?arg=val", BodyMatch: `"Url":"/%28abc,xyz%29?arg=val`}, + {Path: "/%28abc,xyz%29?arg=val", BodyMatch: `"Url":"/%28abc,xyz%29?arg=val`}, + }...) + }) + + t.Run("Without escaping", func(t *testing.T) { + buildAndLoadAPI(func(spec *APISpec) { + spec.Proxy.SkipTargetPathEscaping = true + spec.Proxy.ListenPath = "/" + }) + + ts.Run(t, []test.TestCase{ + {Path: "/(abc,xyz)?arg=val", BodyMatch: `"Url":"/(abc,xyz)?arg=val"`}, + {Path: "/%28abc,xyz%29?arg=val", BodyMatch: `"Url":"/%28abc,xyz%29?arg=val"`}, + }...) + }) + + t.Run("With escaping, listen path and target URL are set, StripListenPath is OFF", func(t *testing.T) { + buildAndLoadAPI(func(spec *APISpec) { + spec.Proxy.StripListenPath = false + spec.Proxy.SkipTargetPathEscaping = false + spec.Proxy.ListenPath = "/listen_me" + spec.Proxy.TargetURL = testHttpAny + "/sent_to_me" + }) + + ts.Run(t, []test.TestCase{ + {Path: "/listen_me/(abc,xyz)?arg=val", BodyMatch: `"Url":"/sent_to_me/listen_me/%28abc,xyz%29?arg=val"`}, + {Path: "/listen_me/%28abc,xyz%29?arg=val", BodyMatch: `"Url":"/sent_to_me/listen_me/%28abc,xyz%29?arg=val"`}, + }...) + }) + + t.Run("Without escaping, listen path and target URL are set, StripListenPath is OFF", func(t *testing.T) { + buildAndLoadAPI(func(spec *APISpec) { + spec.Proxy.StripListenPath = false + spec.Proxy.SkipTargetPathEscaping = true + spec.Proxy.ListenPath = "/listen_me" + spec.Proxy.TargetURL = testHttpAny + "/sent_to_me" + }) + + ts.Run(t, []test.TestCase{ + {Path: "/listen_me/(abc,xyz)?arg=val", BodyMatch: `"Url":"/sent_to_me/listen_me/(abc,xyz)?arg=val"`}, + {Path: "/listen_me/%28abc,xyz%29?arg=val", BodyMatch: `"Url":"/sent_to_me/listen_me/%28abc,xyz%29?arg=val"`}, + }...) + }) + + t.Run("With escaping, listen path and target URL are set, StripListenPath is ON", func(t *testing.T) { + buildAndLoadAPI(func(spec *APISpec) { + spec.Proxy.StripListenPath = true + spec.Proxy.SkipTargetPathEscaping = false + spec.Proxy.ListenPath = "/listen_me" + spec.Proxy.TargetURL = testHttpAny + "/sent_to_me" + }) + + ts.Run(t, []test.TestCase{ + {Path: "/listen_me/(abc,xyz)?arg=val", BodyMatch: `"Url":"/sent_to_me/%28abc,xyz%29?arg=val"`}, + {Path: "/listen_me/%28abc,xyz%29?arg=val", BodyMatch: `"Url":"/sent_to_me/%28abc,xyz%29?arg=val"`}, + }...) + }) + + t.Run("Without escaping, listen path and target URL are set, StripListenPath is ON", func(t *testing.T) { + buildAndLoadAPI(func(spec *APISpec) { + spec.Proxy.StripListenPath = true + spec.Proxy.SkipTargetPathEscaping = true + spec.Proxy.ListenPath = "/listen_me" + spec.Proxy.TargetURL = testHttpAny + "/sent_to_me" + }) + + ts.Run(t, []test.TestCase{ + {Path: "/listen_me/(abc,xyz)?arg=val", BodyMatch: `"Url":"/sent_to_me/(abc,xyz)?arg=val"`}, + {Path: "/listen_me/%28abc,xyz%29?arg=val", BodyMatch: `"Url":"/sent_to_me/%28abc,xyz%29?arg=val"`}, + }...) + }) +} + +func TestSkipTargetPassEscapingOffWithSkipURLCleaningTrue(t *testing.T) { + config.Global.HttpServerOptions.OverrideDefaults = true + config.Global.HttpServerOptions.SkipURLCleaning = true + defer resetTestConfig() + + // here we expect that test gateway will be sending to test upstream requests with not cleaned URI + // so test upstream shouldn't reply with 301 and process them as well + prevSkipClean := defaultTestConfig.HttpServerOptions.OverrideDefaults && + defaultTestConfig.HttpServerOptions.SkipURLCleaning + testServerRouter.SkipClean(true) + defer testServerRouter.SkipClean(prevSkipClean) + + ts := newTykTestServer() + defer ts.Close() + + t.Run("With escaping, default", func(t *testing.T) { + buildAndLoadAPI(func(spec *APISpec) { + spec.Proxy.SkipTargetPathEscaping = false + spec.Proxy.ListenPath = "/" + }) + + ts.Run(t, []test.TestCase{ + {Path: "/abc/xyz/http%3A%2F%2Ftest.com?arg=val", BodyMatch: `"Url":"/abc/xyz/http%3A%2F%2Ftest.com?arg=val`}, + }...) + }) + + t.Run("Without escaping, default", func(t *testing.T) { + buildAndLoadAPI(func(spec *APISpec) { + spec.Proxy.SkipTargetPathEscaping = true + spec.Proxy.ListenPath = "/" + }) + + ts.Run(t, []test.TestCase{ + {Path: "/abc/xyz/http%3A%2F%2Ftest.com?arg=val", BodyMatch: `"Url":"/abc/xyz/http%3A%2F%2Ftest.com?arg=val`}, + }...) + }) + + t.Run("With escaping, listen path and target URL are set, StripListenPath is OFF", func(t *testing.T) { + buildAndLoadAPI(func(spec *APISpec) { + spec.Proxy.StripListenPath = false + spec.Proxy.SkipTargetPathEscaping = false + spec.Proxy.ListenPath = "/listen_me" + spec.Proxy.TargetURL = testHttpAny + "/sent_to_me" + }) + + ts.Run(t, []test.TestCase{ + {Path: "/listen_me/(abc,xyz)?arg=val", BodyMatch: `"Url":"/sent_to_me/listen_me/%28abc,xyz%29?arg=val"`}, + {Path: "/listen_me/%28abc,xyz%29?arg=val", BodyMatch: `"Url":"/sent_to_me/listen_me/%28abc,xyz%29?arg=val"`}, + {Path: "/listen_me/http%3A%2F%2Ftest.com?arg=val", BodyMatch: `"Url":"/sent_to_me/listen_me/http%3A%2F%2Ftest.com?arg=val`}, + }...) + }) + + t.Run("Without escaping, listen path and target URL are set, StripListenPath is OFF", func(t *testing.T) { + buildAndLoadAPI(func(spec *APISpec) { + spec.Proxy.StripListenPath = false + spec.Proxy.SkipTargetPathEscaping = true + spec.Proxy.ListenPath = "/listen_me" + spec.Proxy.TargetURL = testHttpAny + "/sent_to_me" + }) + + ts.Run(t, []test.TestCase{ + {Path: "/listen_me/(abc,xyz)?arg=val", BodyMatch: `"Url":"/sent_to_me/listen_me/(abc,xyz)?arg=val"`}, + {Path: "/listen_me/%28abc,xyz%29?arg=val", BodyMatch: `"Url":"/sent_to_me/listen_me/%28abc,xyz%29?arg=val"`}, + {Path: "/listen_me/http%3A%2F%2Ftest.com?arg=val", BodyMatch: `"Url":"/sent_to_me/listen_me/http%3A%2F%2Ftest.com?arg=val`}, + }...) + }) + + t.Run("With escaping, listen path and target URL are set, StripListenPath is ON", func(t *testing.T) { + buildAndLoadAPI(func(spec *APISpec) { + spec.Proxy.StripListenPath = true + spec.Proxy.SkipTargetPathEscaping = false + spec.Proxy.ListenPath = "/listen_me" + spec.Proxy.TargetURL = testHttpAny + "/sent_to_me" + }) + + ts.Run(t, []test.TestCase{ + {Path: "/listen_me/(abc,xyz)?arg=val", BodyMatch: `"Url":"/sent_to_me/%28abc,xyz%29?arg=val"`}, + {Path: "/listen_me/%28abc,xyz%29?arg=val", BodyMatch: `"Url":"/sent_to_me/%28abc,xyz%29?arg=val"`}, + {Path: "/listen_me/http%3A%2F%2Ftest.com?arg=val", BodyMatch: `"Url":"/sent_to_me/http%3A%2F%2Ftest.com?arg=val`}, + }...) + }) + + t.Run("Without escaping, listen path and target URL are set, StripListenPath is ON", func(t *testing.T) { + buildAndLoadAPI(func(spec *APISpec) { + spec.Proxy.StripListenPath = true + spec.Proxy.SkipTargetPathEscaping = true + spec.Proxy.ListenPath = "/listen_me" + spec.Proxy.TargetURL = testHttpAny + "/sent_to_me" + }) + + ts.Run(t, []test.TestCase{ + {Path: "/listen_me/(abc,xyz)?arg=val", BodyMatch: `"Url":"/sent_to_me/(abc,xyz)?arg=val"`}, + {Path: "/listen_me/%28abc,xyz%29?arg=val", BodyMatch: `"Url":"/sent_to_me/%28abc,xyz%29?arg=val"`}, + {Path: "/listen_me/http%3A%2F%2Ftest.com?arg=val", BodyMatch: `"Url":"/sent_to_me/http%3A%2F%2Ftest.com?arg=val`}, + }...) + }) + +} + func TestQuota(t *testing.T) { ts := newTykTestServer() defer ts.Close() @@ -406,7 +592,7 @@ func TestAnalytics(t *testing.T) { } func TestListener(t *testing.T) { - // Trcik to get spec JSON, without loading API + // Trick to get spec JSON, without loading API // Specs will be reseted when we do `newTykTestServer` specs := buildAndLoadAPI() specJSON, _ := json.Marshal(specs[0].APIDefinition) diff --git a/handler_success.go b/handler_success.go index da47a9507849..0f90987e9f63 100644 --- a/handler_success.go +++ b/handler_success.go @@ -227,6 +227,7 @@ func (s *SuccessHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) *http if s.Spec.Proxy.StripListenPath { log.Debug("Stripping: ", s.Spec.Proxy.ListenPath) r.URL.Path = strings.Replace(r.URL.Path, s.Spec.Proxy.ListenPath, "", 1) + r.URL.RawPath = strings.Replace(r.URL.RawPath, s.Spec.Proxy.ListenPath, "", 1) log.Debug("Upstream Path is: ", r.URL.Path) } @@ -262,6 +263,7 @@ func (s *SuccessHandler) ServeHTTPWithCache(w http.ResponseWriter, r *http.Reque // Make sure we get the correct target URL if s.Spec.Proxy.StripListenPath { r.URL.Path = strings.Replace(r.URL.Path, s.Spec.Proxy.ListenPath, "", 1) + r.URL.RawPath = strings.Replace(r.URL.RawPath, s.Spec.Proxy.ListenPath, "", 1) } var copiedRequest *http.Request diff --git a/helpers_test.go b/helpers_test.go index bc15ccd2eca5..72caee1636a7 100644 --- a/helpers_test.go +++ b/helpers_test.go @@ -24,6 +24,8 @@ import ( "github.com/miekg/dns" "github.com/satori/go.uuid" + "github.com/gorilla/mux" + "github.com/TykTechnologies/tyk/apidef" "github.com/TykTechnologies/tyk/config" "github.com/TykTechnologies/tyk/test" @@ -92,7 +94,7 @@ const ( testHttpFailureAny = "http://" + testHttpFailure ) -func testHttpHandler() http.Handler { +func testHttpHandler() *mux.Router { var upgrader = websocket.Upgrader{ ReadBufferSize: 1024, WriteBufferSize: 1024, @@ -124,6 +126,7 @@ func testHttpHandler() http.Handler { httpError(w, http.StatusInternalServerError) return } + r.URL.Opaque = r.URL.RawPath err := json.NewEncoder(w).Encode(testHttpResponse{ Method: r.Method, Url: r.URL.String(), @@ -143,17 +146,20 @@ func testHttpHandler() http.Handler { } } } - mux := http.NewServeMux() - mux.HandleFunc("/", handleMethod("")) - mux.HandleFunc("/get", handleMethod("GET")) - mux.HandleFunc("/post", handleMethod("POST")) - mux.HandleFunc("/ws", wsHandler) - mux.HandleFunc("/jwk.json", func(w http.ResponseWriter, r *http.Request) { + + // use gorilla's mux as it allows to cancel URI cleaning + // (it is not configurable in standard http mux) + r := mux.NewRouter() + r.HandleFunc("/get", handleMethod("GET")) + r.HandleFunc("/post", handleMethod("POST")) + r.HandleFunc("/ws", wsHandler) + r.HandleFunc("/jwk.json", func(w http.ResponseWriter, r *http.Request) { io.WriteString(w, jwkTestJson) }) + r.HandleFunc("/bundles/{rest:.*}", bundleHandleFunc) + r.HandleFunc("/{rest:.*}", handleMethod("")) - mux.HandleFunc("/bundles/", bundleHandleFunc) - return mux + return r } const jwkTestJson = `{ diff --git a/reverse_proxy.go b/reverse_proxy.go index 1b4e489fe912..0ca3fb8949dc 100644 --- a/reverse_proxy.go +++ b/reverse_proxy.go @@ -235,6 +235,9 @@ func TykNewSingleHostReverseProxy(target *url.URL, spec *APISpec) *ReverseProxy req.URL.Scheme = targetToUse.Scheme req.URL.Host = targetToUse.Host req.URL.Path = singleJoiningSlash(targetToUse.Path, req.URL.Path) + if req.URL.RawPath != "" { + req.URL.RawPath = singleJoiningSlash(targetToUse.Path, req.URL.RawPath) + } } if !spec.Proxy.PreserveHostHeader { req.Host = targetToUse.Host @@ -249,6 +252,17 @@ func TykNewSingleHostReverseProxy(target *url.URL, spec *APISpec) *ReverseProxy // this line, we would get the net/http default. req.Header.Set("User-Agent", defaultUserAgent) } + + if spec.Proxy.SkipTargetPathEscaping { + // force RequestURI to skip escaping if API's proxy is set for this + // if we set opaque here it will force URL.RequestURI to skip escaping + if req.URL.RawPath != "" { + req.URL.Opaque = req.URL.RawPath + } + } else if req.URL.RawPath == req.URL.Path { + // this should force URL to do escaping + req.URL.RawPath = "" + } } proxy := &ReverseProxy{ From 5eb0a319a61b3eda42fa7b56574bf7354623d1ca Mon Sep 17 00:00:00 2001 From: Leonid Bugaev Date: Wed, 17 Jan 2018 20:28:12 +0200 Subject: [PATCH 2/2] Configure `skip_target_path_escaping` using tyk conf instead of API definition White nature of this issue depends on upstream, its usage very similar to `skip_url_cleaning`. And additionally helps us avoid doing UI for this feature --- apidef/api_definitions.go | 1 - config/config.go | 29 +++++++++++++++-------------- gateway_test.go | 37 +++++++++++++++++++++++++------------ lint/schema.go | 3 +++ reverse_proxy.go | 2 +- 5 files changed, 44 insertions(+), 28 deletions(-) diff --git a/apidef/api_definitions.go b/apidef/api_definitions.go index 20d34f648d13..a4a2d775476c 100644 --- a/apidef/api_definitions.go +++ b/apidef/api_definitions.go @@ -348,7 +348,6 @@ type APIDefinition struct { StructuredTargetList *HostList `bson:"-" json:"-"` CheckHostAgainstUptimeTests bool `bson:"check_host_against_uptime_tests" json:"check_host_against_uptime_tests"` ServiceDiscovery ServiceDiscoveryConfiguration `bson:"service_discovery" json:"service_discovery"` - SkipTargetPathEscaping bool `bson:"skip_target_path_escaping" json:"skip_target_path_escaping"` } `bson:"proxy" json:"proxy"` DisableRateLimit bool `bson:"disable_rate_limit" json:"disable_rate_limit"` DisableQuota bool `bson:"disable_quota" json:"disable_quota"` diff --git a/config/config.go b/config/config.go index ca5856379bab..23bc7b998811 100644 --- a/config/config.go +++ b/config/config.go @@ -113,20 +113,21 @@ type LocalSessionCacheConf struct { } type HttpServerOptionsConfig struct { - OverrideDefaults bool `json:"override_defaults"` - ReadTimeout int `json:"read_timeout"` - WriteTimeout int `json:"write_timeout"` - UseSSL bool `json:"use_ssl"` - UseLE_SSL bool `json:"use_ssl_le"` - SSLInsecureSkipVerify bool `json:"ssl_insecure_skip_verify"` - EnableWebSockets bool `json:"enable_websockets"` - Certificates []CertData `json:"certificates"` - SSLCertificates []string `json:"ssl_certificates"` - ServerName string `json:"server_name"` - MinVersion uint16 `json:"min_version"` - FlushInterval int `json:"flush_interval"` - SkipURLCleaning bool `json:"skip_url_cleaning"` - Ciphers []string `json:"ssl_ciphers"` + OverrideDefaults bool `json:"override_defaults"` + ReadTimeout int `json:"read_timeout"` + WriteTimeout int `json:"write_timeout"` + UseSSL bool `json:"use_ssl"` + UseLE_SSL bool `json:"use_ssl_le"` + SSLInsecureSkipVerify bool `json:"ssl_insecure_skip_verify"` + EnableWebSockets bool `json:"enable_websockets"` + Certificates []CertData `json:"certificates"` + SSLCertificates []string `json:"ssl_certificates"` + ServerName string `json:"server_name"` + MinVersion uint16 `json:"min_version"` + FlushInterval int `json:"flush_interval"` + SkipURLCleaning bool `json:"skip_url_cleaning"` + SkipTargetPathEscaping bool `json:"skip_target_path_escaping"` + Ciphers []string `json:"ssl_ciphers"` } type AuthOverrideConf struct { diff --git a/gateway_test.go b/gateway_test.go index 2ba073b56e81..6460518d3082 100644 --- a/gateway_test.go +++ b/gateway_test.go @@ -271,10 +271,12 @@ func TestParambasedAuth(t *testing.T) { func TestSkipTargetPassEscapingOff(t *testing.T) { ts := newTykTestServer() defer ts.Close() + defer resetTestConfig() t.Run("With escaping, default", func(t *testing.T) { + config.Global.HttpServerOptions.SkipTargetPathEscaping = false + buildAndLoadAPI(func(spec *APISpec) { - spec.Proxy.SkipTargetPathEscaping = false spec.Proxy.ListenPath = "/" }) @@ -285,8 +287,9 @@ func TestSkipTargetPassEscapingOff(t *testing.T) { }) t.Run("Without escaping", func(t *testing.T) { + config.Global.HttpServerOptions.SkipTargetPathEscaping = true + buildAndLoadAPI(func(spec *APISpec) { - spec.Proxy.SkipTargetPathEscaping = true spec.Proxy.ListenPath = "/" }) @@ -297,9 +300,10 @@ func TestSkipTargetPassEscapingOff(t *testing.T) { }) t.Run("With escaping, listen path and target URL are set, StripListenPath is OFF", func(t *testing.T) { + config.Global.HttpServerOptions.SkipTargetPathEscaping = false + buildAndLoadAPI(func(spec *APISpec) { spec.Proxy.StripListenPath = false - spec.Proxy.SkipTargetPathEscaping = false spec.Proxy.ListenPath = "/listen_me" spec.Proxy.TargetURL = testHttpAny + "/sent_to_me" }) @@ -311,9 +315,10 @@ func TestSkipTargetPassEscapingOff(t *testing.T) { }) t.Run("Without escaping, listen path and target URL are set, StripListenPath is OFF", func(t *testing.T) { + config.Global.HttpServerOptions.SkipTargetPathEscaping = true + buildAndLoadAPI(func(spec *APISpec) { spec.Proxy.StripListenPath = false - spec.Proxy.SkipTargetPathEscaping = true spec.Proxy.ListenPath = "/listen_me" spec.Proxy.TargetURL = testHttpAny + "/sent_to_me" }) @@ -325,9 +330,10 @@ func TestSkipTargetPassEscapingOff(t *testing.T) { }) t.Run("With escaping, listen path and target URL are set, StripListenPath is ON", func(t *testing.T) { + config.Global.HttpServerOptions.SkipTargetPathEscaping = false + buildAndLoadAPI(func(spec *APISpec) { spec.Proxy.StripListenPath = true - spec.Proxy.SkipTargetPathEscaping = false spec.Proxy.ListenPath = "/listen_me" spec.Proxy.TargetURL = testHttpAny + "/sent_to_me" }) @@ -339,9 +345,10 @@ func TestSkipTargetPassEscapingOff(t *testing.T) { }) t.Run("Without escaping, listen path and target URL are set, StripListenPath is ON", func(t *testing.T) { + config.Global.HttpServerOptions.SkipTargetPathEscaping = true + buildAndLoadAPI(func(spec *APISpec) { spec.Proxy.StripListenPath = true - spec.Proxy.SkipTargetPathEscaping = true spec.Proxy.ListenPath = "/listen_me" spec.Proxy.TargetURL = testHttpAny + "/sent_to_me" }) @@ -369,8 +376,9 @@ func TestSkipTargetPassEscapingOffWithSkipURLCleaningTrue(t *testing.T) { defer ts.Close() t.Run("With escaping, default", func(t *testing.T) { + config.Global.HttpServerOptions.SkipTargetPathEscaping = false + buildAndLoadAPI(func(spec *APISpec) { - spec.Proxy.SkipTargetPathEscaping = false spec.Proxy.ListenPath = "/" }) @@ -380,8 +388,9 @@ func TestSkipTargetPassEscapingOffWithSkipURLCleaningTrue(t *testing.T) { }) t.Run("Without escaping, default", func(t *testing.T) { + config.Global.HttpServerOptions.SkipTargetPathEscaping = true + buildAndLoadAPI(func(spec *APISpec) { - spec.Proxy.SkipTargetPathEscaping = true spec.Proxy.ListenPath = "/" }) @@ -391,9 +400,10 @@ func TestSkipTargetPassEscapingOffWithSkipURLCleaningTrue(t *testing.T) { }) t.Run("With escaping, listen path and target URL are set, StripListenPath is OFF", func(t *testing.T) { + config.Global.HttpServerOptions.SkipTargetPathEscaping = false + buildAndLoadAPI(func(spec *APISpec) { spec.Proxy.StripListenPath = false - spec.Proxy.SkipTargetPathEscaping = false spec.Proxy.ListenPath = "/listen_me" spec.Proxy.TargetURL = testHttpAny + "/sent_to_me" }) @@ -406,9 +416,10 @@ func TestSkipTargetPassEscapingOffWithSkipURLCleaningTrue(t *testing.T) { }) t.Run("Without escaping, listen path and target URL are set, StripListenPath is OFF", func(t *testing.T) { + config.Global.HttpServerOptions.SkipTargetPathEscaping = true + buildAndLoadAPI(func(spec *APISpec) { spec.Proxy.StripListenPath = false - spec.Proxy.SkipTargetPathEscaping = true spec.Proxy.ListenPath = "/listen_me" spec.Proxy.TargetURL = testHttpAny + "/sent_to_me" }) @@ -421,9 +432,10 @@ func TestSkipTargetPassEscapingOffWithSkipURLCleaningTrue(t *testing.T) { }) t.Run("With escaping, listen path and target URL are set, StripListenPath is ON", func(t *testing.T) { + config.Global.HttpServerOptions.SkipTargetPathEscaping = false + buildAndLoadAPI(func(spec *APISpec) { spec.Proxy.StripListenPath = true - spec.Proxy.SkipTargetPathEscaping = false spec.Proxy.ListenPath = "/listen_me" spec.Proxy.TargetURL = testHttpAny + "/sent_to_me" }) @@ -436,9 +448,10 @@ func TestSkipTargetPassEscapingOffWithSkipURLCleaningTrue(t *testing.T) { }) t.Run("Without escaping, listen path and target URL are set, StripListenPath is ON", func(t *testing.T) { + config.Global.HttpServerOptions.SkipTargetPathEscaping = true + buildAndLoadAPI(func(spec *APISpec) { spec.Proxy.StripListenPath = true - spec.Proxy.SkipTargetPathEscaping = true spec.Proxy.ListenPath = "/listen_me" spec.Proxy.TargetURL = testHttpAny + "/sent_to_me" }) diff --git a/lint/schema.go b/lint/schema.go index 8a8ac9752c5e..87f97d5f9804 100644 --- a/lint/schema.go +++ b/lint/schema.go @@ -331,6 +331,9 @@ const confSchema = `{ "skip_url_cleaning": { "type": "boolean" }, + "skip_target_path_escaping": { + "type": "boolean" + }, "ssl_insecure_skip_verify": { "type": "boolean" }, diff --git a/reverse_proxy.go b/reverse_proxy.go index 0ca3fb8949dc..af33ca6cc3dd 100644 --- a/reverse_proxy.go +++ b/reverse_proxy.go @@ -253,7 +253,7 @@ func TykNewSingleHostReverseProxy(target *url.URL, spec *APISpec) *ReverseProxy req.Header.Set("User-Agent", defaultUserAgent) } - if spec.Proxy.SkipTargetPathEscaping { + if config.Global.HttpServerOptions.SkipTargetPathEscaping { // force RequestURI to skip escaping if API's proxy is set for this // if we set opaque here it will force URL.RequestURI to skip escaping if req.URL.RawPath != "" {