From d7f724b6a10b0b064d12851d5b4ddae2dfa0347f Mon Sep 17 00:00:00 2001 From: Arthur Darcet Date: Mon, 23 Jan 2017 14:19:31 +0100 Subject: [PATCH] do not use yarl.quote directly ; escaping should be consistent with the one done in yarl.URL.__init__ --- aiohttp/web_urldispatcher.py | 18 +++++++++++------- tests/test_web_urldispatcher.py | 22 ++++++++++++++++++++++ 2 files changed, 33 insertions(+), 7 deletions(-) diff --git a/aiohttp/web_urldispatcher.py b/aiohttp/web_urldispatcher.py index 3667c2538a9..1e9bb4e9e2a 100644 --- a/aiohttp/web_urldispatcher.py +++ b/aiohttp/web_urldispatcher.py @@ -11,7 +11,10 @@ from pathlib import Path from types import MappingProxyType -from yarl import URL, quote, unquote +# do not use yarl.quote directly, +# use `URL(path).raw_path` instead of `quote(path)` +# Escaping of the URLs need to be consitent with the escaping done by yarl +from yarl import URL, unquote from . import hdrs from .abc import AbstractMatchInfo, AbstractRouter, AbstractView @@ -373,7 +376,7 @@ def __init__(self, prefix, *, name=None): assert not prefix or prefix.startswith('/'), prefix assert prefix in ('', '/') or not prefix.endswith('/'), prefix super().__init__(name=name) - self._prefix = quote(prefix, safe='/') + self._prefix = URL(prefix).raw_path def add_prefix(self, prefix): assert prefix.startswith('/') @@ -421,7 +424,7 @@ def url_for(self, *, filename): while filename.startswith('/'): filename = filename[1:] filename = '/' + filename - url = self._prefix + quote(filename, safe='/') + url = self._prefix + URL(filename).raw_path return URL(url) def get_info(self): @@ -783,7 +786,8 @@ def add_resource(self, path, *, name=None): if path and not path.startswith('/'): raise ValueError("path should be started with / or be empty") if not ('{' in path or '}' in path or self.ROUTE_RE.search(path)): - resource = PlainResource(quote(path, safe='/'), name=name) + url = URL(path) + resource = PlainResource(url.raw_path, name=name) self.register_resource(resource) return resource @@ -805,9 +809,9 @@ def add_resource(self, path, *, name=None): if '{' in part or '}' in part: raise ValueError("Invalid path '{}'['{}']".format(path, part)) - part = quote(part, safe='/') - formatter += part - pattern += re.escape(part) + path = URL(part).raw_path + formatter += path + pattern += re.escape(path) try: compiled = re.compile(pattern) diff --git a/tests/test_web_urldispatcher.py b/tests/test_web_urldispatcher.py index 2d8733c8652..3cd7ecd10e0 100644 --- a/tests/test_web_urldispatcher.py +++ b/tests/test_web_urldispatcher.py @@ -158,6 +158,28 @@ def test_access_non_existing_resource(tmp_dir_path, loop, test_client): yield from r.release() +@pytest.mark.parametrize('registered_path,request_url', [ + ('/a:b', '/a:b'), + ('/a@b', '/a@b'), + ('/a:b', '/a%3Ab'), +]) +@asyncio.coroutine +def test_url_escaping(loop, test_client, registered_path, request_url): + """ + Tests accessing a resource with + """ + app = web.Application(loop=loop) + + def handler(_): + return web.Response() + app.router.add_get(registered_path, handler) + client = yield from test_client(app) + + r = yield from client.get(request_url) + assert r.status == 200 + yield from r.release() + + @asyncio.coroutine def test_unauthorized_folder_access(tmp_dir_path, loop, test_client): """