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
RawPath
to 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/url
does 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%20up
whileRawPath
ends withwhat's%20up
, so the two don't match and URL's RawPath is set. func GetPath(r *http.Request) string
will then pass the raw URL toFind
, ending withwhat's%20up
, and sinceFind
never does any unescaping, weird behaviors will appear: a handler for"/foo/bar/what's up"
will not match, or path parametermsg
will be set towhat's%20up
instead 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) string
and always use the URL'sPath
instead -
Give
Find
a way to tell when it got a raw path (raw bool
argument?) 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