summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--CHANGES.txt7
-rw-r--r--docs/designdefense.rst61
-rw-r--r--docs/whatsnew-1.1.rst9
-rw-r--r--pyramid/mako_templating.py2
-rw-r--r--pyramid/tests/fixtures/nonminimal.mak1
-rw-r--r--pyramid/tests/test_mako_templating.py5
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, &lt;b&gt;fred&lt;/b&gt;!\n')
+
class TestPkgResourceTemplateLookup(unittest.TestCase):
def _makeOne(self, **kw):
from pyramid.mako_templating import PkgResourceTemplateLookup