Blob Blame History Raw
diff --git a/jinja2/_compat.py b/jinja2/_compat.py
new file mode 100644
index 0000000..4dbf6ea
--- /dev/null
+++ b/jinja2/_compat.py
@@ -0,0 +1,105 @@
+# -*- coding: utf-8 -*-
+"""
+    jinja2._compat
+    ~~~~~~~~~~~~~~
+
+    Some py2/py3 compatibility support based on a stripped down
+    version of six so we don't have to depend on a specific version
+    of it.
+
+    :copyright: Copyright 2013 by the Jinja team, see AUTHORS.
+    :license: BSD, see LICENSE for details.
+"""
+import sys
+
+PY2 = sys.version_info[0] == 2
+PYPY = hasattr(sys, 'pypy_translation_info')
+_identity = lambda x: x
+
+
+if not PY2:
+    unichr = chr
+    range_type = range
+    text_type = str
+    string_types = (str,)
+    integer_types = (int,)
+
+    iterkeys = lambda d: iter(d.keys())
+    itervalues = lambda d: iter(d.values())
+    iteritems = lambda d: iter(d.items())
+
+    import pickle
+    from io import BytesIO, StringIO
+    NativeStringIO = StringIO
+
+    def reraise(tp, value, tb=None):
+        if value.__traceback__ is not tb:
+            raise value.with_traceback(tb)
+        raise value
+
+    ifilter = filter
+    imap = map
+    izip = zip
+    intern = sys.intern
+
+    implements_iterator = _identity
+    implements_to_string = _identity
+    encode_filename = _identity
+
+else:
+    unichr = unichr
+    text_type = unicode
+    range_type = xrange
+    string_types = (str, unicode)
+    integer_types = (int, long)
+
+    iterkeys = lambda d: d.iterkeys()
+    itervalues = lambda d: d.itervalues()
+    iteritems = lambda d: d.iteritems()
+
+    import cPickle as pickle
+    from cStringIO import StringIO as BytesIO, StringIO
+    NativeStringIO = BytesIO
+
+    exec('def reraise(tp, value, tb=None):\n raise tp, value, tb')
+
+    from itertools import imap, izip, ifilter
+    intern = intern
+
+    def implements_iterator(cls):
+        cls.next = cls.__next__
+        del cls.__next__
+        return cls
+
+    def implements_to_string(cls):
+        cls.__unicode__ = cls.__str__
+        cls.__str__ = lambda x: x.__unicode__().encode('utf-8')
+        return cls
+
+    def encode_filename(filename):
+        if isinstance(filename, unicode):
+            return filename.encode('utf-8')
+        return filename
+
+
+def with_metaclass(meta, *bases):
+    """Create a base class with a metaclass."""
+    # This requires a bit of explanation: the basic idea is to make a
+    # dummy metaclass for one level of class instantiation that replaces
+    # itself with the actual metaclass.
+    class metaclass(type):
+        def __new__(cls, name, this_bases, d):
+            return meta(name, bases, d)
+    return type.__new__(metaclass, 'temporary_class', (), {})
+
+
+try:
+    from urllib.parse import quote_from_bytes as url_quote
+except ImportError:
+    from urllib import quote as url_quote
+
+
+try:
+    from collections import abc
+except ImportError:
+    import collections as abc
diff --git a/jinja2/nodes.py b/jinja2/nodes.py
index f9da1da..addee6a 100644
--- a/jinja2/nodes.py
+++ b/jinja2/nodes.py
@@ -595,7 +595,7 @@ class Call(Expr):
 
     def as_const(self, eval_ctx=None):
         eval_ctx = get_eval_context(self, eval_ctx)
-        if eval_ctx.volatile:
+        if eval_ctx.volatile or eval_ctx.environment.sandboxed:
             raise Impossible()
         obj = self.node.as_const(eval_ctx)
 
diff --git a/jinja2/sandbox.py b/jinja2/sandbox.py
index a1cbb29..5f70ef8 100644
--- a/jinja2/sandbox.py
+++ b/jinja2/sandbox.py
@@ -12,12 +12,19 @@
     :copyright: (c) 2010 by the Jinja Team.
     :license: BSD.
 """
+import types
 import operator
+from collections import Mapping
 from jinja2.environment import Environment
 from jinja2.exceptions import SecurityError
+from jinja2._compat import string_types, text_type, PY2
 from jinja2.utils import FunctionType, MethodType, TracebackType, CodeType, \
-     FrameType, GeneratorType
+     FrameType, GeneratorType, Markup
 
+has_format = False
+if hasattr(text_type, 'format'):
+    from string import Formatter
+    has_format = True
 
 #: maximum number of items a range may produce
 MAX_RANGE = 100000
@@ -29,6 +36,11 @@ UNSAFE_FUNCTION_ATTRIBUTES = set(['func_closure', 'func_code', 'func_dict',
 #: unsafe method attributes.  function attributes are unsafe for methods too
 UNSAFE_METHOD_ATTRIBUTES = set(['im_class', 'im_func', 'im_self'])
 
+#: unsafe attributes on coroutines
+UNSAFE_COROUTINE_ATTRIBUTES = set(['cr_frame', 'cr_code'])
+
+#: unsafe attributes on async generators
+UNSAFE_ASYNC_GENERATOR_ATTRIBUTES = set(['ag_code', 'ag_frame'])
 
 import warnings
 
@@ -85,6 +97,79 @@ _mutable_spec = (
     ]))
 )
 
+# Bundled EscapeFormatter class from markupsafe >= 0.21 which is used by
+# jinja2 for fixing CVE-2016-10745
+# Copyright 2010 Pallets
+# BSD 3-Clause License
+# https://github.com/pallets/markupsafe/blob/79ee6ce0ed93c6da73512f069d7db866d955df04/LICENSE.rst
+if hasattr(text_type, "format"):
+
+    class EscapeFormatter(Formatter):
+        def __init__(self, escape):
+            self.escape = escape
+
+        def format_field(self, value, format_spec):
+            if hasattr(value, "__html_format__"):
+                rv = value.__html_format__(format_spec)
+            elif hasattr(value, "__html__"):
+                if format_spec:
+                    raise ValueError(
+                        "Format specifier {0} given, but {1} does not"
+                        " define __html_format__. A class that defines"
+                        " __html__ must define __html_format__ to work"
+                        " with format specifiers.".format(format_spec, type(value))
+                    )
+                rv = value.__html__()
+            else:
+                # We need to make sure the format spec is unicode here as
+                # otherwise the wrong callback methods are invoked.  For
+                # instance a byte string there would invoke __str__ and
+                # not __unicode__.
+                rv = Formatter.format_field(self, value, text_type(format_spec))
+            return text_type(self.escape(rv))
+
+class _MagicFormatMapping(Mapping):
+    """This class implements a dummy wrapper to fix a bug in the Python
+    standard library for string formatting.
+
+    See http://bugs.python.org/issue13598 for information about why
+    this is necessary.
+    """
+
+    def __init__(self, args, kwargs):
+        self._args = args
+        self._kwargs = kwargs
+        self._last_index = 0
+
+    def __getitem__(self, key):
+        if key == '':
+            idx = self._last_index
+            self._last_index += 1
+            try:
+                return self._args[idx]
+            except LookupError:
+                pass
+            key = str(idx)
+        return self._kwargs[key]
+
+    def __iter__(self):
+        return iter(self._kwargs)
+
+    def __len__(self):
+        return len(self._kwargs)
+
+
+def inspect_format_method(callable):
+    if not has_format:
+        return None
+    if not isinstance(callable, (types.MethodType,
+                                 types.BuiltinMethodType)) or \
+       callable.__name__ != 'format':
+        return None
+    obj = callable.__self__
+    if isinstance(obj, string_types):
+        return obj
+
 
 def safe_range(*args):
     """A range that can't generate ranges with a length of more than
@@ -139,6 +224,12 @@ def is_internal_attribute(obj, attr):
     elif isinstance(obj, GeneratorType):
         if attr == 'gi_frame':
             return True
+    elif hasattr(types, 'CoroutineType') and isinstance(obj, types.CoroutineType):
+        if attr in UNSAFE_COROUTINE_ATTRIBUTES:
+            return True
+    elif hasattr(types, 'AsyncGeneratorType') and isinstance(obj, types.AsyncGeneratorType):
+        if attr in UNSAFE_ASYNC_GENERATOR_ATTRIBUTES:
+            return True
     return attr.startswith('__')
 
 
@@ -177,8 +268,8 @@ class SandboxedEnvironment(Environment):
     attributes or functions are safe to access.
 
     If the template tries to access insecure code a :exc:`SecurityError` is
-    raised.  However also other exceptions may occour during the rendering so
-    the caller has to ensure that all exceptions are catched.
+    raised.  However also other exceptions may occur during the rendering so
+    the caller has to ensure that all exceptions are caught.
     """
     sandboxed = True
 
@@ -340,8 +431,24 @@ class SandboxedEnvironment(Environment):
             obj.__class__.__name__
         ), name=attribute, obj=obj, exc=SecurityError)
 
+    def format_string(self, s, args, kwargs):
+        """If a format call is detected, then this is routed through this
+        method so that our safety sandbox can be used for it.
+        """
+        if isinstance(s, Markup):
+            formatter = SandboxedEscapeFormatter(self, s.escape)
+        else:
+            formatter = SandboxedFormatter(self)
+        kwargs = _MagicFormatMapping(args, kwargs)
+        rv = formatter.vformat(s, args, kwargs)
+        return type(s)(rv)
+
     def call(__self, __context, __obj, *args, **kwargs):
         """Call an object from sandboxed code."""
+        fmt = inspect_format_method(__obj)
+        if fmt is not None:
+            return __self.format_string(fmt, args, kwargs)
+
         # the double prefixes are to avoid double keyword argument
         # errors when proxying the call.
         if not __self.is_safe_callable(__obj):
@@ -359,3 +466,37 @@ class ImmutableSandboxedEnvironment(SandboxedEnvironment):
         if not SandboxedEnvironment.is_safe_attribute(self, obj, attr, value):
             return False
         return not modifies_known_mutable(obj, attr)
+
+
+if has_format:
+    # This really is not a public API apparenlty.
+    try:
+        from _string import formatter_field_name_split
+    except ImportError:
+        def formatter_field_name_split(field_name):
+            return field_name._formatter_field_name_split()
+
+    class SandboxedFormatterMixin(object):
+
+        def __init__(self, env):
+            self._env = env
+
+        def get_field(self, field_name, args, kwargs):
+            first, rest = formatter_field_name_split(field_name)
+            obj = self.get_value(first, args, kwargs)
+            for is_attr, i in rest:
+                if is_attr:
+                    obj = self._env.getattr(obj, i)
+                else:
+                    obj = self._env.getitem(obj, i)
+            return obj, first
+
+    class SandboxedFormatter(SandboxedFormatterMixin, Formatter):
+        def __init__(self, env):
+            SandboxedFormatterMixin.__init__(self, env)
+            Formatter.__init__(self)
+
+    class SandboxedEscapeFormatter(SandboxedFormatterMixin, EscapeFormatter):
+        def __init__(self, env, escape):
+            SandboxedFormatterMixin.__init__(self, env)
+            EscapeFormatter.__init__(self, escape)