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