Skip to content

Bare and percent-encoded pluses in path variables become spaces in match_info #1816

Closed
@iillyyaa

Description

Long story short

When match info is constructed for variable resource routes, '+' characters are converted to spaces. I do not believe this is the expected behavior.

Furthermore, even '%2B' codes are converted to spaces, limiting workarounds.

Note that similar behavior yarl's path parsing, reported in yarl/issues/59, has been corrected in 88799aec. A similar fix is needed in match_info parsing.

Example

In our use case, the version string matched by the route is a semantic version that may include a plus sign followed by the build name, which runs afoul of the eager plus-to-space conversion.

import aiohttp.web
app = aiohttp.web.Application()
async def handler(request):
    print(request.url)
    print('path: ' + request.url.path)
    print('match: ' + request.match_info['version'])
    # This assertion does not hold IF(F?) version contains a plus sign
    if request.url.path.endswith(request.match_info['version']):
        return aiohttp.web.HTTPAccepted()
    else:
        return aiohttp.web.HTTPExpectationFailed()
app.router.add_get('/resource/{version}', handler)
aiohttp.web.run_app(app, host='127.0.0.1', port=8088)

Suppose the handler is reached via a URL('http://server/resources/1.0.0+build'):

curl http://127.0.0.1:8088/resource/1.0.0+build

# Note: this workaround fails too:
curl http://127.0.0.1:8088/resource/1.0.0%2Bbuild

Expected behavior

Inside the handler, I expect the plus sign to be preserved in the portion of the URL path that has been captured by match info:

request.url.raw_path == '/resources/1.0.0+build'
request.url.path == '/resources/1.0.0+build'
request.match_info['version'] == '1.0.0+build'

Actual behavior

Plus sign is preserved in the parsed path, but replaced by space in the match info:

request.url.raw_path == '/resources/1.0.0+build'
request.url.path == '/resources/1.0.0+build'
request.match_info['version'] == '1.0.0 build'  # UNEXPECTED

Your environment

Xubuntu 16.04, python3.5, yarl==0.10.0, aiohttp==1.2.0 (but confirmed with aiohttp==2.0.7)

Code inspection / suggested fix

The implementation of DynamicResource._match in aiohttp/web_urldispatcher.py is causing the problem.

return {key: unquote(value) for key, value in

    def _match(self, path):
        match = self._pattern.fullmatch(path)
        if match is None:
            return None
        else:
            return {key: unquote(value) for key, value in
                    match.groupdict().items()}

Similarly to 88799aec in yarl, if the unquote(value) is replaced with unquote(value, unsafe='+'), then the issue I demonstrated above is fixed. I am unclear on whether '+:' should be used here as well. Nor am I confident that the same fix is needed in StaticResource (I believe it is) or other resources.

If someone more familiar with the code validates the approach and helps determine which resources need this, I am more than happy to compose and file a PR.

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions