-
-
Notifications
You must be signed in to change notification settings - Fork 2.3k
Description
Issue Description
Abstract: func (r *Router) Find(method, path string, c Context) (in file router.go) is sometimes given a raw path instead of a unescaped path, but because it has no way to tell between the two situations it does not behave properly if given a raw path.
Argument path of func (r *Router) Find is provided by func GetPath(r *http.Request) string (in file echo.go) which returns the Path of the request's URL, unless the URL structure has a RawPath in which case it returns the RawPath. A URL structure from package net/url has a RawPath if escape(unescape(RawPath)) != RawPath (where escape applies URL percent-encoding). See net/url source code. The rationale of this extra RawPath field in net/url package is to handle the case of URLs containing %2F, which unescape will transform into character / but which should not be used as a path separator by the server.
What a server should do when it receives an URL which has a RawPath is:
- use the
RawPathto split the path into segments based on the ”true”/characters (the ones not coming from unescaping%2F) - then, unescape every segment
Because Echo's func (r *Router) Find does not when the path it received is a RawPath, it fails to do step (2), and this causes bugs.
Checklist
- Dependencies installed
- No typos
- Searched existing issues and docs
Expected behaviour vs. Actual behaviour (+ steps to reproduce)
If a JavaScript application sends a request with a path containing and single quote and a space, such as fetch("/foo/bar/what's up"), Echo will return a HTTP 404 Not Found even if there was a handler for "/foo/bar/what's up" or "/foo/bar/:msg". The reasons are that:
- JavaScript does not escape the single quote, in conformance with RFC 2396 §2.3 (note that this RFC has now been obsoleted by RFC 3986 of which paragraph 2.2 seems to say single quotes should be escaped, but, well, JS doesn't do it).
- Go's
net/urldoes escape the single quote (see net/url: URL.String() hashbang "#!" encodes as "#%21" golang/go#19917 and golang/go@8a330454) - as a result,
escape(unescape(RawPath))ends withwhat%27s%20upwhileRawPathends withwhat's%20up, so the two don't match and URL's RawPath is set. func GetPath(r *http.Request) stringwill then pass the raw URL toFind, ending withwhat's%20up, and sinceFindnever does any unescaping, weird behaviors will appear: a handler for"/foo/bar/what's up"will not match, or path parametermsgwill be set towhat's%20upinstead ofwhat's up.
Of course, this situation can be fixed easily by modifying the JS application to encode the single quote. But strictly speaking it's not the fault of the JS app: it is legal not to escape the single quote, and the server (that is, Echo) should work with unescaped path fragments even if it had to do path splitting on a raw path.
Suggested Solutions
One way would be to change Find's argument path string to pathSegments []string, where the caller of Find would be responsible for splitting the path into segments. This way if the URL has a RawPath the caller can use it to do the splitting, then unescape all parts before sending them to Find.
The problem is that Find currently processes path byte-by-byte, and it would probably require a complete rewrite of Find (and potentially other parts of Echo, such as the logic adding a handler to a router) to operate on path segments.
I don't see any solution that would correctly handle URLs with a RawPath and would not require a big rewrite. A few mitigations I can suggest:
-
add an option to skip
func GetPath(r *http.Request) stringand always use the URL'sPathinstead -
Give
Finda way to tell when it got a raw path (raw boolargument?) and then replacingparamValues[paramIndex] = search[:i]with:if raw { paramValues[paramIndex] = PathUnescape(search[:i]) } else { paramValues[paramIndex] = search[:i] }
This will at least fix the situation for path parameters. It will still break if path segments contains percent-encoded characters.
-
add a warning about this known issue somewhere visible