summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--CHANGES.txt13
-rw-r--r--docs/api/exceptions.rst1
-rw-r--r--repoze/bfg/exceptions.py11
-rw-r--r--repoze/bfg/tests/test_traversal.py9
-rw-r--r--repoze/bfg/tests/test_urldispatch.py5
-rw-r--r--repoze/bfg/traversal.py35
-rw-r--r--repoze/bfg/urldispatch.py11
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