Skip to content

Echo Does Not Correctly Handle URLs with a RawPath #1974

Open
@cedricvanrompay

Description

@cedricvanrompay

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:

  1. use the RawPath to split the path into segments based on the ”true” / characters (the ones not coming from unescaping %2F)
  2. 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 with what%27s%20up while RawPath ends with what'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 to Find, ending with what's%20up, and since Find never does any unescaping, weird behaviors will appear: a handler for "/foo/bar/what's up" will not match, or path parameter msg will be set to what's%20up instead of what'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's Path instead

  • Give Find a way to tell when it got a raw path (raw bool argument?) and then replacing paramValues[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

Version/commit

7f502b1

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions