-
Notifications
You must be signed in to change notification settings - Fork 353
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
* add patch path function Signed-off-by: Arpad Ryszka <[email protected]> * test rfc path patch Signed-off-by: Arpad Ryszka <[email protected]> * formatting Signed-off-by: Arpad Ryszka <[email protected]> * add missing files Signed-off-by: Arpad Ryszka <[email protected]> * allow optional central path patch in the proxy Signed-off-by: Arpad Ryszka <[email protected]> * enable path patch from the command line Signed-off-by: Arpad Ryszka <[email protected]> * add documentation for the rfc path patch Signed-off-by: Arpad Ryszka <[email protected]> * remove unnecessary check for the query Signed-off-by: Arpad Ryszka <[email protected]> * remove unnecessary test message Signed-off-by: Arpad Ryszka <[email protected]> * fix crash case Signed-off-by: Arpad Ryszka <[email protected]> * add non-ascii test cases Signed-off-by: Arpad Ryszka <[email protected]> * typo fix Signed-off-by: Arpad Ryszka <[email protected]> * use recent standard reference Signed-off-by: Arpad Ryszka <[email protected]>
- Loading branch information
Showing
11 changed files
with
400 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,27 @@ | ||
package rfc | ||
|
||
import ( | ||
"github.com/zalando/skipper/filters" | ||
"github.com/zalando/skipper/rfc" | ||
) | ||
|
||
const Name = "rfcPath" | ||
|
||
type path struct{} | ||
|
||
// NewPath creates a filter specification for the rfcPath() filter, that | ||
// reencodes the reserved characters in the request path, if it detects | ||
// that they are encoded in the raw path. | ||
// | ||
// See also the PatchPath documentation in the rfc package. | ||
// | ||
func NewPath() filters.Spec { return path{} } | ||
|
||
func (p path) Name() string { return Name } | ||
func (p path) CreateFilter([]interface{}) (filters.Filter, error) { return path{}, nil } | ||
func (p path) Response(filters.FilterContext) {} | ||
|
||
func (p path) Request(ctx filters.FilterContext) { | ||
req := ctx.Request() | ||
req.URL.Path = rfc.PatchPath(req.URL.Path, req.URL.RawPath) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,26 @@ | ||
package rfc | ||
|
||
import ( | ||
"net/http" | ||
"net/url" | ||
"testing" | ||
|
||
"github.com/zalando/skipper/filters/filtertest" | ||
) | ||
|
||
func TestPatch(t *testing.T) { | ||
req := &http.Request{URL: &url.URL{RawPath: "/foo%2Fbar", Path: "/foo/bar"}} | ||
ctx := &filtertest.Context{ | ||
FRequest: req, | ||
} | ||
|
||
f, err := NewPath().CreateFilter(nil) | ||
if err != nil { | ||
t.Fatal(err) | ||
} | ||
|
||
f.Request(ctx) | ||
if req.URL.Path != "/foo%2Fbar" { | ||
t.Error("failed to patch the path", req.URL.Path) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,51 @@ | ||
package proxy | ||
|
||
import ( | ||
"net/http" | ||
"net/http/httptest" | ||
"testing" | ||
|
||
"github.com/zalando/skipper/dataclients/routestring" | ||
"github.com/zalando/skipper/filters/builtin" | ||
"github.com/zalando/skipper/routing" | ||
) | ||
|
||
func testPatch(t *testing.T, title string, f Flags, expectedStatus int) { | ||
t.Run(title, func(t *testing.T) { | ||
dc, err := routestring.New(`Path("/foo%2Fbar") -> status(200) -> <shunt>`) | ||
if err != nil { | ||
t.Fatal(err) | ||
} | ||
|
||
rt := routing.New(routing.Options{ | ||
SignalFirstLoad: true, | ||
FilterRegistry: builtin.MakeRegistry(), | ||
DataClients: []routing.DataClient{dc}, | ||
}) | ||
defer rt.Close() | ||
|
||
p := WithParams(Params{Routing: rt, Flags: f}) | ||
defer p.Close() | ||
|
||
s := httptest.NewServer(p) | ||
defer s.Close() | ||
|
||
<-rt.FirstLoad() | ||
|
||
rsp, err := http.Get(s.URL + "/foo%2Fbar") | ||
if err != nil { | ||
t.Fatal(err) | ||
} | ||
|
||
defer rsp.Body.Close() | ||
|
||
if rsp.StatusCode != expectedStatus { | ||
t.Error() | ||
} | ||
}) | ||
} | ||
|
||
func TestPathPatch(t *testing.T) { | ||
testPatch(t, "not patched", FlagsNone, http.StatusNotFound) | ||
testPatch(t, "patched", PatchPath, http.StatusOK) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
/* | ||
Package rfc provides standards related functions. | ||
*/ | ||
package rfc |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,130 @@ | ||
package rfc | ||
|
||
const escapeLength = 3 // always, because we only handle the below cases | ||
|
||
const ( | ||
semicolon = ';' | ||
slash = '/' | ||
questionMark = '?' | ||
colon = ':' | ||
at = '@' | ||
and = '&' | ||
eq = '=' | ||
plus = '+' | ||
dollar = '$' | ||
comma = ',' | ||
) | ||
|
||
// https://tools.ietf.org/html/rfc3986#section-2.2 | ||
func unescape(seq []byte) (byte, bool) { | ||
switch string(seq) { | ||
case "%3B", "%3b": | ||
return semicolon, true | ||
case "%2F", "%2f": | ||
return slash, true | ||
case "%3F", "%3f": | ||
return questionMark, true | ||
case "%3A", "%3a": | ||
return colon, true | ||
case "%40": | ||
return at, true | ||
case "%26": | ||
return and, true | ||
case "%3D", "%3d": | ||
return eq, true | ||
case "%2B", "%2b": | ||
return plus, true | ||
case "%24": | ||
return dollar, true | ||
case "%2C", "%2c": | ||
return comma, true | ||
default: | ||
return 0, false | ||
} | ||
} | ||
|
||
// PatchPath attempts to patch a request path based on an interpretation of the standards | ||
// RFC 2616 and RFC 3986 where the reserved characters should not be unescaped. Currently | ||
// the Go stdlib does unescape these characters (v1.12.5). | ||
// | ||
// It expects the parsed path as found in http.Request.URL.Path and the raw path as found | ||
// in http.Request.URL.RawPath. It returns a path where characters e.g. like '/' have the | ||
// escaped form of %2F, if it was detected that they are unescaped in the raw path. | ||
// | ||
// It only returns the patched variant, if the only difference between the parsed and raw | ||
// paths are the encoding of the chars, according to RFC 3986. If it detects any other | ||
// difference between the two, it returns the original parsed path as provided. It | ||
// tolerates an empty argument for the raw path, which can happen when the URL parsed via | ||
// the stdlib url package, and there is no difference between the parsed and the raw path. | ||
// This basically means that the following code is correct: | ||
// | ||
// req.URL.Path = rfc.PatchPath(req.URL.Path, req.URL.RawPath) | ||
// | ||
// Links: | ||
// - https://tools.ietf.org/html/rfc2616#section-3.2.3 and | ||
// - https://tools.ietf.org/html/rfc3986#section-2.2 | ||
// | ||
func PatchPath(parsed, raw string) string { | ||
p, r := []byte(parsed), []byte(raw) | ||
patched := make([]byte, 0, len(r)) | ||
var ( | ||
escape bool | ||
seq []byte | ||
unescaped byte | ||
handled bool | ||
doPatch bool | ||
modified bool | ||
pi int | ||
) | ||
|
||
for i := 0; i < len(r); i++ { | ||
c := r[i] | ||
escape = c == '%' | ||
modified = pi >= len(p) || !escape && p[pi] != c | ||
if modified { | ||
doPatch = false | ||
break | ||
} | ||
|
||
if !escape { | ||
patched = append(patched, p[pi]) | ||
pi++ | ||
continue | ||
} | ||
|
||
if len(r) < i+escapeLength { | ||
doPatch = false | ||
break | ||
} | ||
|
||
seq = r[i : i+escapeLength] | ||
i += escapeLength - 1 | ||
unescaped, handled = unescape(seq) | ||
if !handled { | ||
patched = append(patched, p[pi]) | ||
pi++ | ||
continue | ||
} | ||
|
||
modified = p[pi] != unescaped | ||
if modified { | ||
doPatch = false | ||
break | ||
} | ||
|
||
patched = append(patched, seq...) | ||
doPatch = true | ||
pi++ | ||
} | ||
|
||
if !doPatch { | ||
return parsed | ||
} | ||
|
||
modified = pi < len(p) | ||
if modified { | ||
return parsed | ||
} | ||
|
||
return string(patched) | ||
} |
Oops, something went wrong.