From 2f1d33cfb57a6a93c18d4afcddb1d6e229e39dd5 Mon Sep 17 00:00:00 2001 From: FELD Boris Date: Tue, 16 Jun 2015 23:09:14 +0200 Subject: [PATCH] Change the behavior of url dispatching with quote characters. The quoted characters of the URL will not be significant during url dispatching but will be decoded after the matching. For example, with this route: ``` @asyncio.coroutine def handler(request): return web.Response(body=repr(request.match_info)) app = Application(loop=loop) app.router.add_route('GET', '/{route}', handler) ``` The following query will generate this match_info: ``` curl -I http://127.0.0.1:8080/route%2Fslash HTTP/1.1 200 OK ... > ``` --- CHANGES.txt | 4 ++++ CONTRIBUTORS.txt | 1 + aiohttp/web_reqrep.py | 11 ++++++++++- aiohttp/web_urldispatcher.py | 7 +++++-- docs/web_reference.rst | 14 ++++++++++++++ tests/test_urldispatch.py | 17 +++++++++++++++++ 6 files changed, 51 insertions(+), 3 deletions(-) diff --git a/CHANGES.txt b/CHANGES.txt index 397f63d9e61..49c93a0e31e 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -19,6 +19,10 @@ CHANGES - Allow gzip compression in high-level server response interface #403 +- Make UrlDispatcher ignore quoted characters during url matching #414 +Backward-compatibility warning: this may change the url matched by your queries +if they send quoted characted (like %2F for /). + 0.16.5 (06-13-2015) ------------------- diff --git a/CONTRIBUTORS.txt b/CONTRIBUTORS.txt index b8d511635f2..4ca5b9b5af8 100644 --- a/CONTRIBUTORS.txt +++ b/CONTRIBUTORS.txt @@ -14,6 +14,7 @@ Anton Kasyanov Arthur Darcet Ben Bader Brian C. Lane +Boris Feld Chris Laws Daniel Nelson David Michael Brown diff --git a/aiohttp/web_reqrep.py b/aiohttp/web_reqrep.py index b81ddb2e5c4..01c1ac41455 100644 --- a/aiohttp/web_reqrep.py +++ b/aiohttp/web_reqrep.py @@ -179,12 +179,21 @@ def _splitted_path(self): return urlsplit(self._path_qs) @property + def raw_path(self): + """ The URL including raw *PATH INFO* without the host or scheme. + Warning, the path is unquoted and may contains non valid URL characters + + E.g., ``/my%2Fpath%7Cwith%21some%25strange%24characters`` + """ + return self._splitted_path.path + + @reify def path(self): """The URL including *PATH INFO* without the host or scheme. E.g., ``/app/blog`` """ - return unquote(self._splitted_path.path) + return unquote(self.raw_path) @reify def query_string(self): diff --git a/aiohttp/web_urldispatcher.py b/aiohttp/web_urldispatcher.py index aba3e920c9c..e4cfc99ee7b 100644 --- a/aiohttp/web_urldispatcher.py +++ b/aiohttp/web_urldispatcher.py @@ -10,7 +10,7 @@ import os import inspect -from urllib.parse import urlencode +from urllib.parse import urlencode, unquote from . import hdrs from .abc import AbstractRouter, AbstractMatchInfo @@ -304,7 +304,7 @@ def __init__(self): @asyncio.coroutine def resolve(self, request): - path = request.path + path = request.raw_path method = request.method allowed_methods = set() @@ -315,6 +315,9 @@ def resolve(self, request): route_method = route.method if route_method == method or route_method == hdrs.METH_ANY: + # Unquote separate matching parts + match_dict = {key: unquote(value) for key, value in + match_dict.items()} return UrlMappingMatchInfo(match_dict, route) allowed_methods.add(route_method) diff --git a/docs/web_reference.rst b/docs/web_reference.rst index 98b1092adf3..94c382c9038 100644 --- a/docs/web_reference.rst +++ b/docs/web_reference.rst @@ -72,6 +72,15 @@ first positional parameter. Read-only :class:`str` property. + .. attribute:: raw_path + + The URL including raw *PATH INFO* without the host or scheme. + Warning, the path is unquoted and may contains non valid URL characters. + e.g., + ``/my%2Fpath%7Cwith%21some%25strange%24characters`` + + Read-only :class:`str` property. + .. attribute:: query_string The query string in the URL, e.g., ``id=10`` @@ -966,6 +975,11 @@ Router is any object that implements :class:`AbstractRouter` interface. *Named route* can be retrieved by ``app.router[name]`` call, checked for existence by ``name in app.router`` etc. + You can tell UrlDispatcher to use unquoted_path for dispatching instead + of quoted path by passing ``unquoted_path=true``. It's necessary if you try + to call the server with paths including ``/`` as they may interfere with + normal route matching. You will be then responsible for unquoting the path. + .. seealso:: :ref:`Route classes ` .. method:: add_route(method, path, handler, *, \ diff --git a/tests/test_urldispatch.py b/tests/test_urldispatch.py index ab6380b8dc4..269b18a0e04 100644 --- a/tests/test_urldispatch.py +++ b/tests/test_urldispatch.py @@ -2,6 +2,7 @@ import os import unittest from unittest import mock +from urllib.parse import unquote import aiohttp.web from aiohttp import hdrs from aiohttp.web import (UrlDispatcher, Request, Response, @@ -528,3 +529,19 @@ def go(): self.assertEqual({'name': 'file', 'ext': 'html'}, match_info) self.loop.run_until_complete(go()) + + def test_dynamic_match_unquoted_path(self): + + @asyncio.coroutine + def go(): + handler = self.make_handler() + self.router.add_route('GET', '/{path}/{subpath}', handler) + resource_id = 'my%2Fpath%7Cwith%21some%25strange%24characters' + req = self.make_request('GET', '/path/{0}'.format(resource_id)) + match_info = yield from self.router.resolve(req) + self.assertEqual(match_info, { + 'path': 'path', + 'subpath': unquote(resource_id) + }) + + self.loop.run_until_complete(go())