diff options
| -rw-r--r-- | CHANGES.txt | 13 | ||||
| -rw-r--r-- | docs/api/exceptions.rst | 1 | ||||
| -rw-r--r-- | repoze/bfg/exceptions.py | 11 | ||||
| -rw-r--r-- | repoze/bfg/tests/test_traversal.py | 9 | ||||
| -rw-r--r-- | repoze/bfg/tests/test_urldispatch.py | 5 | ||||
| -rw-r--r-- | repoze/bfg/traversal.py | 35 | ||||
| -rw-r--r-- | repoze/bfg/urldispatch.py | 11 |
7 files changed, 65 insertions, 20 deletions
diff --git a/CHANGES.txt b/CHANGES.txt index 04f1f0726..b9de1f891 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -1,6 +1,19 @@ Next release ============ +Features +-------- + +- New internal exception: ``repoze.bfg.exceptions.URLDecodeError``. + This URL is a subclass of the built-in Python exception named + ``UnicodeDecodeError``. + +- When decoding a URL segment to Unicode fails, the exception raised + is now ``repoze.bfg.exceptions.URLDecodeError`` instead of + ``UnicodeDecodeError``. This makes it possible to register an + exception view invoked specifically when ``repoze.bfg`` cannot + decode a URL. + Bug Fixes --------- diff --git a/docs/api/exceptions.rst b/docs/api/exceptions.rst index 585292f39..94c75cc5c 100644 --- a/docs/api/exceptions.rst +++ b/docs/api/exceptions.rst @@ -11,3 +11,4 @@ .. autoclass:: ConfigurationError + .. autoclass:: URLDecodeError diff --git a/repoze/bfg/exceptions.py b/repoze/bfg/exceptions.py index e7b6dd570..b0922c1e3 100644 --- a/repoze/bfg/exceptions.py +++ b/repoze/bfg/exceptions.py @@ -26,6 +26,17 @@ class NotFound(Exception): ``repoze.bfg.message`` key, for availability to the :term:`Not Found View`.""" +class URLDecodeError(UnicodeDecodeError): + """ + This exception is raised when :mod:`repoze.bfg` cannot + successfully decode a URL or a URL path segment. This exception + it behaves just like the Python builtin + :exc:`UnicodeDecodeError`. It is a subclass of the builtin + :exc:`UnicodeDecodeError` exception only for identity purposes, + mostly so an exception view can be registered when a URL cannot be + decoded. + """ + class ConfigurationError(ZCE): """ Raised when inappropriate input values are supplied to an API method of a :term:`Configurator`""" diff --git a/repoze/bfg/tests/test_traversal.py b/repoze/bfg/tests/test_traversal.py index d21c3ee3e..29f11dd40 100644 --- a/repoze/bfg/tests/test_traversal.py +++ b/repoze/bfg/tests/test_traversal.py @@ -43,11 +43,12 @@ class TraversalPathTests(unittest.TestCase): self.assertEqual(self._callFUT(path), (decoded, decoded)) def test_utf16(self): + from repoze.bfg.exceptions import URLDecodeError import urllib la = unicode('La Pe\xc3\xb1a', 'utf-8').encode('utf-16') encoded = urllib.quote(la) path = '/'.join([encoded, encoded]) - self.assertRaises(TypeError, self._callFUT, path) + self.assertRaises(URLDecodeError, self._callFUT, path) class ModelGraphTraverserTests(unittest.TestCase): def setUp(self): @@ -246,7 +247,8 @@ class ModelGraphTraverserTests(unittest.TestCase): policy = self._makeOne(root) segment = unicode('LaPe\xc3\xb1a', 'utf-8').encode('utf-16') environ = self._getEnviron(PATH_INFO='/%s' % segment) - self.assertRaises(TypeError, policy, environ) + from repoze.bfg.exceptions import URLDecodeError + self.assertRaises(URLDecodeError, policy, environ) def test_non_utf8_path_segment_settings_unicode_path_segments_fails(self): foo = DummyContext() @@ -254,7 +256,8 @@ class ModelGraphTraverserTests(unittest.TestCase): policy = self._makeOne(root) segment = unicode('LaPe\xc3\xb1a', 'utf-8').encode('utf-16') environ = self._getEnviron(PATH_INFO='/%s' % segment) - self.assertRaises(TypeError, policy, environ) + from repoze.bfg.exceptions import URLDecodeError + self.assertRaises(URLDecodeError, policy, environ) def test_withroute_nothingfancy(self): model = DummyContext() diff --git a/repoze/bfg/tests/test_urldispatch.py b/repoze/bfg/tests/test_urldispatch.py index 69bd3d971..c5fa37758 100644 --- a/repoze/bfg/tests/test_urldispatch.py +++ b/repoze/bfg/tests/test_urldispatch.py @@ -224,6 +224,11 @@ class TestCompileRoute(unittest.TestCase): self.assertEqual(matcher('foo/baz/biz/buz/bar'), None) self.assertEqual(generator({'baz':1, 'buz':2}), '/foo/1/biz/2/bar') + def test_url_decode_error(self): + from repoze.bfg.exceptions import URLDecodeError + matcher, generator = self._callFUT('/:foo') + self.assertRaises(URLDecodeError, matcher, '/%FF%FE%8B%00') + class TestCompileRouteMatchFunctional(unittest.TestCase): def matches(self, pattern, path, result): from repoze.bfg.urldispatch import _compile_route diff --git a/repoze/bfg/traversal.py b/repoze/bfg/traversal.py index cf2a808e8..d45881a3b 100644 --- a/repoze/bfg/traversal.py +++ b/repoze/bfg/traversal.py @@ -10,6 +10,7 @@ from repoze.bfg.interfaces import ITraverser from repoze.bfg.interfaces import VH_ROOT_KEY from repoze.bfg.encode import url_quote +from repoze.bfg.exceptions import URLDecodeError from repoze.bfg.location import lineage from repoze.bfg.request import Request from repoze.bfg.threadlocal import get_current_registry @@ -129,7 +130,7 @@ def model_path(model, *elements): ``__name__`` which (by error) is a dictionary, the :func:`repoze.bfg.traversal.model_path` function will attempt to append it to a string and it will cause a - :exc:`TypeError`. + :exc:`repoze.bfg.exceptions.URLDecodeError`. .. note:: The :term:`root` model *must* have a ``__name__`` attribute with a value of either ``None`` or the empty @@ -251,13 +252,14 @@ def traverse(model, path): Unicode during traversal: Each segment is URL-unquoted, and decoded into Unicode. Each segment is assumed to be encoded using the UTF-8 encoding (or a subset, such as ASCII); a - :exc:`TypeError` is raised if a segment cannot be decoded. If a - segment name is empty or if it is ``.``, it is ignored. If a - segment name is ``..``, the previous segment is deleted, and the - ``..`` is ignored. As a result of this process, the return values - ``view_name``, each element in the ``subpath``, each element in - ``traversed``, and each element in the ``virtual_root_path`` will - be Unicode as opposed to a string, and will be URL-decoded. + :exc:`repoze.bfg.exceptions.URLDecodeError` is raised if a segment + cannot be decoded. If a segment name is empty or if it is ``.``, + it is ignored. If a segment name is ``..``, the previous segment + is deleted, and the ``..`` is ignored. As a result of this + process, the return values ``view_name``, each element in the + ``subpath``, each element in ``traversed``, and each element in + the ``virtual_root_path`` will be Unicode as opposed to a string, + and will be URL-decoded. """ if hasattr(path, '__iter__'): @@ -370,11 +372,11 @@ def traversal_path(path): traverse a graph. The ``PATH_INFO`` is split on slashes, creating a list of segments. Each segment is URL-unquoted, and decoded into Unicode. Each segment is assumed to be encoded using the - UTF-8 encoding (or a subset, such as ASCII); a :exc:`TypeError` is - raised if a segment cannot be decoded. If a segment name is empty - or if it is ``.``, it is ignored. If a segment name is ``..``, - the previous segment is deleted, and the ``..`` is ignored. - Examples: + UTF-8 encoding (or a subset, such as ASCII); a + :exc:`repoze.bfg.exceptions.URLDecodeError` is raised if a segment + cannot be decoded. If a segment name is empty or if it is ``.``, + it is ignored. If a segment name is ``..``, the previous segment + is deleted, and the ``..`` is ignored. Examples: ``/`` @@ -432,9 +434,10 @@ def traversal_path(path): else: try: segment = segment.decode('utf-8') - except UnicodeDecodeError: - raise TypeError('Could not decode path segment %r using the ' - 'UTF-8 decoding scheme' % segment) + except UnicodeDecodeError, e: + raise URLDecodeError( + e.encoding, e.object, e.start, e.end, e.reason + ) clean.append(segment) return tuple(clean) diff --git a/repoze/bfg/urldispatch.py b/repoze/bfg/urldispatch.py index 7a77fec5b..80880d8f0 100644 --- a/repoze/bfg/urldispatch.py +++ b/repoze/bfg/urldispatch.py @@ -3,6 +3,7 @@ from urllib import unquote from repoze.bfg.compat import all from repoze.bfg.encode import url_quote +from repoze.bfg.exceptions import URLDecodeError from repoze.bfg.traversal import traversal_path from repoze.bfg.traversal import quote_path_segment @@ -102,7 +103,15 @@ def _compile_route(route): if k == star: d[k] = traversal_path(v) else: - d[k] = unquote(v).decode('utf-8') + encoded = unquote(v) + try: + d[k] = encoded.decode('utf-8') + except UnicodeDecodeError, e: + raise URLDecodeError( + e.encoding, e.object, e.start, e.end, e.reason + ) + + return d |
