diff options
| -rw-r--r-- | CHANGES.txt | 7 | ||||
| -rw-r--r-- | docs/designdefense.rst | 61 | ||||
| -rw-r--r-- | docs/whatsnew-1.1.rst | 9 | ||||
| -rw-r--r-- | pyramid/mako_templating.py | 2 | ||||
| -rw-r--r-- | pyramid/tests/fixtures/nonminimal.mak | 1 | ||||
| -rw-r--r-- | pyramid/tests/test_mako_templating.py | 5 |
6 files changed, 84 insertions, 1 deletions
diff --git a/CHANGES.txt b/CHANGES.txt index 8b2dae7f1..9dd1af2c5 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -236,6 +236,13 @@ Deprecations Behavior Changes ---------------- +- The default Mako renderer is now configured to escape all HTML in + expression tags. This is intended to help prevent XSS attacks caused by + rendering unsanitized input from users. To revert this behavior in user's + templates, they need to filter the expression through the 'n' filter. + For example, ${ myhtml | n }. + See https://github.com/Pylons/pyramid/issues/193. + - A custom request factory is now required to return a response object that has a ``response`` attribute (or "reified"/lazy property) if they the request is meant to be used in a view that uses a renderer. This diff --git a/docs/designdefense.rst b/docs/designdefense.rst index 136b9c5de..0321113fa 100644 --- a/docs/designdefense.rst +++ b/docs/designdefense.rst @@ -1011,6 +1011,67 @@ which returns Zope3-security-proxy-wrapped objects for each traversed object (including the :term:`context` and the :term:`root`). This would have the effect of creating a more Zope3-like environment without much effort. +.. _simpler_traversal_model: + +Pyramid has Simpler Traversal Machinery than Does Zope +------------------------------------------------------ + +Zope's default traverser: + +- Allows developers to mutate the traversal name stack while traversing (they + can add and remove path elements). + +- Attempts to use an adaptation to obtain the "next" element in the path from + the currently traversed object, falling back to ``__bobo_traverse__``, + ``__getitem__`` and eventually ``__getattr__``. + +Zope's default traverser allows developers to mutate the traversal name stack +during traversal by mutating ``REQUEST['TraversalNameStack']``. Pyramid's +default traverser (``pyramid.traversal.ResourceTreeTraverser``) does not +offer a way to do this; it does not maintain a stack as a request attribute +and, even if it did, it does not pass the request to resource objects while +it's traversing. While it was handy at times, this feature was abused in +frameworks built atop Zope (like CMF and Plone), often making it difficult to +tell exactly what was happening when a traversal didn't match a view. I felt +it was better to make folks that wanted the feature replace the traverser +rather than build that particular honey pot in to the default traverser. + +Zope uses multiple mechanisms to attempt to obtain the next element in the +resource tree based on a name. It first tries an adaptation of the current +resource to ``ITraversable``, and if that fails, it falls back to attempting +number of magic methods on the resource (``__bobo_traverse__``, +``__getitem__``, and ``__getattr__``). My experience while both using Zope +and attempting to reimplement its publisher in ``repoze.zope2`` led me to +believe the following: + +- The *default* traverser should be as simple as possible. Zope's publisher + is somewhat difficult to follow and replicate due to the fallbacks it tried + when one traversal method failed. It is also slow. + +- The *entire traverser* should be replaceable, not just elements of the + traversal machinery. Pyramid has a few "big" components rather than a + plethora of small ones. If the entire traverser is replaceable, it's an + antipattern to make portions of the default traverser replaceable. Doing + so is a "knobs on knobs" pattern, which is unfortunately somewhat endemic + in Zope. In a "knobs on knobs" pattern, a replaceable subcomponent of a + larger component is made configurable using the same configuration + mechanism that can be used to replace the larger component. For example, + in Zope, you can replace the default traverser by registering an adapter. + But you can also (or alternately) control how the default traverser + traverses by registering one or more adapters. As a result of being able + to either replace the larger component entirely or turn knobs on the + default implementation of the larger component, no one understands when (or + whether) they should ever override the larger component entrirely. This + results, over time, in a "rusting together" of the larger "replaceable" + component and the framework itself, because people come to depend on the + availability of the default component in order just to turn its knobs. The + default component effectively becomes part of the framework, which entirely + subverts the goal of making it replaceable. In Pyramid, typically if a + component is replaceable, it will itself have no knobs (it will be "solid + state"). If you want to influence behavior controlled by that component, + you will replace the component instead of turning knobs attached to the + component. + .. _microframeworks_smaller_hello_world: Microframeworks Have Smaller Hello World Programs diff --git a/docs/whatsnew-1.1.rst b/docs/whatsnew-1.1.rst index ea56e2020..761fa8d3a 100644 --- a/docs/whatsnew-1.1.rst +++ b/docs/whatsnew-1.1.rst @@ -94,6 +94,15 @@ Minor Feature Additions Deprecations and Behavior Differences ------------------------------------- +- The default Mako renderer is now configured to escape all HTML in + expression tags. This is intended to help prevent XSS attacks caused by + rendering unsanitized input from users. To revert this behavior in user's + templates, they need to filter the expression through the 'n' filter:: + + ${ myhtml | n }. + + See https://github.com/Pylons/pyramid/issues/193. + - Deprecated all assignments to ``request.response_*`` attributes (for example ``request.response_content_type = 'foo'`` is now deprecated). Assignments and mutations of assignable request attributes that were diff --git a/pyramid/mako_templating.py b/pyramid/mako_templating.py index 9d14ca8fe..fea8066d4 100644 --- a/pyramid/mako_templating.py +++ b/pyramid/mako_templating.py @@ -66,7 +66,7 @@ def renderer_factory(info): module_directory = settings.get('mako.module_directory', None) input_encoding = settings.get('mako.input_encoding', 'utf-8') error_handler = settings.get('mako.error_handler', None) - default_filters = settings.get('mako.default_filters', None) + default_filters = settings.get('mako.default_filters', 'h') imports = settings.get('mako.imports', None) strict_undefined = settings.get('mako.strict_undefined', 'false') if directories is None: diff --git a/pyramid/tests/fixtures/nonminimal.mak b/pyramid/tests/fixtures/nonminimal.mak new file mode 100644 index 000000000..9de95ec92 --- /dev/null +++ b/pyramid/tests/fixtures/nonminimal.mak @@ -0,0 +1 @@ +Hello, ${name}! diff --git a/pyramid/tests/test_mako_templating.py b/pyramid/tests/test_mako_templating.py index 054c83d2b..6b2adbe09 100644 --- a/pyramid/tests/test_mako_templating.py +++ b/pyramid/tests/test_mako_templating.py @@ -354,6 +354,11 @@ class TestIntegration(unittest.TestCase): self.assertRaises(TemplateLookupException, render, 'helloworld_not_here.mak', {}) + def test_template_default_escaping(self): + from pyramid.renderers import render + result = render('nonminimal.mak', {'name':'<b>fred</b>'}).replace('\r','') + self.assertEqual(result, u'Hello, <b>fred</b>!\n') + class TestPkgResourceTemplateLookup(unittest.TestCase): def _makeOne(self, **kw): from pyramid.mako_templating import PkgResourceTemplateLookup |
