| Index: third_party/jinja2/sandbox.py
|
| diff --git a/third_party/jinja2/sandbox.py b/third_party/jinja2/sandbox.py
|
| new file mode 100644
|
| index 0000000000000000000000000000000000000000..a1cbb29ac3166101945ea9f8e9fdb49c60095d31
|
| --- /dev/null
|
| +++ b/third_party/jinja2/sandbox.py
|
| @@ -0,0 +1,361 @@
|
| +# -*- coding: utf-8 -*-
|
| +"""
|
| + jinja2.sandbox
|
| + ~~~~~~~~~~~~~~
|
| +
|
| + Adds a sandbox layer to Jinja as it was the default behavior in the old
|
| + Jinja 1 releases. This sandbox is slightly different from Jinja 1 as the
|
| + default behavior is easier to use.
|
| +
|
| + The behavior can be changed by subclassing the environment.
|
| +
|
| + :copyright: (c) 2010 by the Jinja Team.
|
| + :license: BSD.
|
| +"""
|
| +import operator
|
| +from jinja2.environment import Environment
|
| +from jinja2.exceptions import SecurityError
|
| +from jinja2.utils import FunctionType, MethodType, TracebackType, CodeType, \
|
| + FrameType, GeneratorType
|
| +
|
| +
|
| +#: maximum number of items a range may produce
|
| +MAX_RANGE = 100000
|
| +
|
| +#: attributes of function objects that are considered unsafe.
|
| +UNSAFE_FUNCTION_ATTRIBUTES = set(['func_closure', 'func_code', 'func_dict',
|
| + 'func_defaults', 'func_globals'])
|
| +
|
| +#: unsafe method attributes. function attributes are unsafe for methods too
|
| +UNSAFE_METHOD_ATTRIBUTES = set(['im_class', 'im_func', 'im_self'])
|
| +
|
| +
|
| +import warnings
|
| +
|
| +# make sure we don't warn in python 2.6 about stuff we don't care about
|
| +warnings.filterwarnings('ignore', 'the sets module', DeprecationWarning,
|
| + module='jinja2.sandbox')
|
| +
|
| +from collections import deque
|
| +
|
| +_mutable_set_types = (set,)
|
| +_mutable_mapping_types = (dict,)
|
| +_mutable_sequence_types = (list,)
|
| +
|
| +
|
| +# on python 2.x we can register the user collection types
|
| +try:
|
| + from UserDict import UserDict, DictMixin
|
| + from UserList import UserList
|
| + _mutable_mapping_types += (UserDict, DictMixin)
|
| + _mutable_set_types += (UserList,)
|
| +except ImportError:
|
| + pass
|
| +
|
| +# if sets is still available, register the mutable set from there as well
|
| +try:
|
| + from sets import Set
|
| + _mutable_set_types += (Set,)
|
| +except ImportError:
|
| + pass
|
| +
|
| +#: register Python 2.6 abstract base classes
|
| +try:
|
| + from collections import MutableSet, MutableMapping, MutableSequence
|
| + _mutable_set_types += (MutableSet,)
|
| + _mutable_mapping_types += (MutableMapping,)
|
| + _mutable_sequence_types += (MutableSequence,)
|
| +except ImportError:
|
| + pass
|
| +
|
| +_mutable_spec = (
|
| + (_mutable_set_types, frozenset([
|
| + 'add', 'clear', 'difference_update', 'discard', 'pop', 'remove',
|
| + 'symmetric_difference_update', 'update'
|
| + ])),
|
| + (_mutable_mapping_types, frozenset([
|
| + 'clear', 'pop', 'popitem', 'setdefault', 'update'
|
| + ])),
|
| + (_mutable_sequence_types, frozenset([
|
| + 'append', 'reverse', 'insert', 'sort', 'extend', 'remove'
|
| + ])),
|
| + (deque, frozenset([
|
| + 'append', 'appendleft', 'clear', 'extend', 'extendleft', 'pop',
|
| + 'popleft', 'remove', 'rotate'
|
| + ]))
|
| +)
|
| +
|
| +
|
| +def safe_range(*args):
|
| + """A range that can't generate ranges with a length of more than
|
| + MAX_RANGE items.
|
| + """
|
| + rng = xrange(*args)
|
| + if len(rng) > MAX_RANGE:
|
| + raise OverflowError('range too big, maximum size for range is %d' %
|
| + MAX_RANGE)
|
| + return rng
|
| +
|
| +
|
| +def unsafe(f):
|
| + """Marks a function or method as unsafe.
|
| +
|
| + ::
|
| +
|
| + @unsafe
|
| + def delete(self):
|
| + pass
|
| + """
|
| + f.unsafe_callable = True
|
| + return f
|
| +
|
| +
|
| +def is_internal_attribute(obj, attr):
|
| + """Test if the attribute given is an internal python attribute. For
|
| + example this function returns `True` for the `func_code` attribute of
|
| + python objects. This is useful if the environment method
|
| + :meth:`~SandboxedEnvironment.is_safe_attribute` is overriden.
|
| +
|
| + >>> from jinja2.sandbox import is_internal_attribute
|
| + >>> is_internal_attribute(lambda: None, "func_code")
|
| + True
|
| + >>> is_internal_attribute((lambda x:x).func_code, 'co_code')
|
| + True
|
| + >>> is_internal_attribute(str, "upper")
|
| + False
|
| + """
|
| + if isinstance(obj, FunctionType):
|
| + if attr in UNSAFE_FUNCTION_ATTRIBUTES:
|
| + return True
|
| + elif isinstance(obj, MethodType):
|
| + if attr in UNSAFE_FUNCTION_ATTRIBUTES or \
|
| + attr in UNSAFE_METHOD_ATTRIBUTES:
|
| + return True
|
| + elif isinstance(obj, type):
|
| + if attr == 'mro':
|
| + return True
|
| + elif isinstance(obj, (CodeType, TracebackType, FrameType)):
|
| + return True
|
| + elif isinstance(obj, GeneratorType):
|
| + if attr == 'gi_frame':
|
| + return True
|
| + return attr.startswith('__')
|
| +
|
| +
|
| +def modifies_known_mutable(obj, attr):
|
| + """This function checks if an attribute on a builtin mutable object
|
| + (list, dict, set or deque) would modify it if called. It also supports
|
| + the "user"-versions of the objects (`sets.Set`, `UserDict.*` etc.) and
|
| + with Python 2.6 onwards the abstract base classes `MutableSet`,
|
| + `MutableMapping`, and `MutableSequence`.
|
| +
|
| + >>> modifies_known_mutable({}, "clear")
|
| + True
|
| + >>> modifies_known_mutable({}, "keys")
|
| + False
|
| + >>> modifies_known_mutable([], "append")
|
| + True
|
| + >>> modifies_known_mutable([], "index")
|
| + False
|
| +
|
| + If called with an unsupported object (such as unicode) `False` is
|
| + returned.
|
| +
|
| + >>> modifies_known_mutable("foo", "upper")
|
| + False
|
| + """
|
| + for typespec, unsafe in _mutable_spec:
|
| + if isinstance(obj, typespec):
|
| + return attr in unsafe
|
| + return False
|
| +
|
| +
|
| +class SandboxedEnvironment(Environment):
|
| + """The sandboxed environment. It works like the regular environment but
|
| + tells the compiler to generate sandboxed code. Additionally subclasses of
|
| + this environment may override the methods that tell the runtime what
|
| + 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.
|
| + """
|
| + sandboxed = True
|
| +
|
| + #: default callback table for the binary operators. A copy of this is
|
| + #: available on each instance of a sandboxed environment as
|
| + #: :attr:`binop_table`
|
| + default_binop_table = {
|
| + '+': operator.add,
|
| + '-': operator.sub,
|
| + '*': operator.mul,
|
| + '/': operator.truediv,
|
| + '//': operator.floordiv,
|
| + '**': operator.pow,
|
| + '%': operator.mod
|
| + }
|
| +
|
| + #: default callback table for the unary operators. A copy of this is
|
| + #: available on each instance of a sandboxed environment as
|
| + #: :attr:`unop_table`
|
| + default_unop_table = {
|
| + '+': operator.pos,
|
| + '-': operator.neg
|
| + }
|
| +
|
| + #: a set of binary operators that should be intercepted. Each operator
|
| + #: that is added to this set (empty by default) is delegated to the
|
| + #: :meth:`call_binop` method that will perform the operator. The default
|
| + #: operator callback is specified by :attr:`binop_table`.
|
| + #:
|
| + #: The following binary operators are interceptable:
|
| + #: ``//``, ``%``, ``+``, ``*``, ``-``, ``/``, and ``**``
|
| + #:
|
| + #: The default operation form the operator table corresponds to the
|
| + #: builtin function. Intercepted calls are always slower than the native
|
| + #: operator call, so make sure only to intercept the ones you are
|
| + #: interested in.
|
| + #:
|
| + #: .. versionadded:: 2.6
|
| + intercepted_binops = frozenset()
|
| +
|
| + #: a set of unary operators that should be intercepted. Each operator
|
| + #: that is added to this set (empty by default) is delegated to the
|
| + #: :meth:`call_unop` method that will perform the operator. The default
|
| + #: operator callback is specified by :attr:`unop_table`.
|
| + #:
|
| + #: The following unary operators are interceptable: ``+``, ``-``
|
| + #:
|
| + #: The default operation form the operator table corresponds to the
|
| + #: builtin function. Intercepted calls are always slower than the native
|
| + #: operator call, so make sure only to intercept the ones you are
|
| + #: interested in.
|
| + #:
|
| + #: .. versionadded:: 2.6
|
| + intercepted_unops = frozenset()
|
| +
|
| + def intercept_unop(self, operator):
|
| + """Called during template compilation with the name of a unary
|
| + operator to check if it should be intercepted at runtime. If this
|
| + method returns `True`, :meth:`call_unop` is excuted for this unary
|
| + operator. The default implementation of :meth:`call_unop` will use
|
| + the :attr:`unop_table` dictionary to perform the operator with the
|
| + same logic as the builtin one.
|
| +
|
| + The following unary operators are interceptable: ``+`` and ``-``
|
| +
|
| + Intercepted calls are always slower than the native operator call,
|
| + so make sure only to intercept the ones you are interested in.
|
| +
|
| + .. versionadded:: 2.6
|
| + """
|
| + return False
|
| +
|
| +
|
| + def __init__(self, *args, **kwargs):
|
| + Environment.__init__(self, *args, **kwargs)
|
| + self.globals['range'] = safe_range
|
| + self.binop_table = self.default_binop_table.copy()
|
| + self.unop_table = self.default_unop_table.copy()
|
| +
|
| + def is_safe_attribute(self, obj, attr, value):
|
| + """The sandboxed environment will call this method to check if the
|
| + attribute of an object is safe to access. Per default all attributes
|
| + starting with an underscore are considered private as well as the
|
| + special attributes of internal python objects as returned by the
|
| + :func:`is_internal_attribute` function.
|
| + """
|
| + return not (attr.startswith('_') or is_internal_attribute(obj, attr))
|
| +
|
| + def is_safe_callable(self, obj):
|
| + """Check if an object is safely callable. Per default a function is
|
| + considered safe unless the `unsafe_callable` attribute exists and is
|
| + True. Override this method to alter the behavior, but this won't
|
| + affect the `unsafe` decorator from this module.
|
| + """
|
| + return not (getattr(obj, 'unsafe_callable', False) or
|
| + getattr(obj, 'alters_data', False))
|
| +
|
| + def call_binop(self, context, operator, left, right):
|
| + """For intercepted binary operator calls (:meth:`intercepted_binops`)
|
| + this function is executed instead of the builtin operator. This can
|
| + be used to fine tune the behavior of certain operators.
|
| +
|
| + .. versionadded:: 2.6
|
| + """
|
| + return self.binop_table[operator](left, right)
|
| +
|
| + def call_unop(self, context, operator, arg):
|
| + """For intercepted unary operator calls (:meth:`intercepted_unops`)
|
| + this function is executed instead of the builtin operator. This can
|
| + be used to fine tune the behavior of certain operators.
|
| +
|
| + .. versionadded:: 2.6
|
| + """
|
| + return self.unop_table[operator](arg)
|
| +
|
| + def getitem(self, obj, argument):
|
| + """Subscribe an object from sandboxed code."""
|
| + try:
|
| + return obj[argument]
|
| + except (TypeError, LookupError):
|
| + if isinstance(argument, basestring):
|
| + try:
|
| + attr = str(argument)
|
| + except Exception:
|
| + pass
|
| + else:
|
| + try:
|
| + value = getattr(obj, attr)
|
| + except AttributeError:
|
| + pass
|
| + else:
|
| + if self.is_safe_attribute(obj, argument, value):
|
| + return value
|
| + return self.unsafe_undefined(obj, argument)
|
| + return self.undefined(obj=obj, name=argument)
|
| +
|
| + def getattr(self, obj, attribute):
|
| + """Subscribe an object from sandboxed code and prefer the
|
| + attribute. The attribute passed *must* be a bytestring.
|
| + """
|
| + try:
|
| + value = getattr(obj, attribute)
|
| + except AttributeError:
|
| + try:
|
| + return obj[attribute]
|
| + except (TypeError, LookupError):
|
| + pass
|
| + else:
|
| + if self.is_safe_attribute(obj, attribute, value):
|
| + return value
|
| + return self.unsafe_undefined(obj, attribute)
|
| + return self.undefined(obj=obj, name=attribute)
|
| +
|
| + def unsafe_undefined(self, obj, attribute):
|
| + """Return an undefined object for unsafe attributes."""
|
| + return self.undefined('access to attribute %r of %r '
|
| + 'object is unsafe.' % (
|
| + attribute,
|
| + obj.__class__.__name__
|
| + ), name=attribute, obj=obj, exc=SecurityError)
|
| +
|
| + def call(__self, __context, __obj, *args, **kwargs):
|
| + """Call an object from sandboxed code."""
|
| + # the double prefixes are to avoid double keyword argument
|
| + # errors when proxying the call.
|
| + if not __self.is_safe_callable(__obj):
|
| + raise SecurityError('%r is not safely callable' % (__obj,))
|
| + return __context.call(__obj, *args, **kwargs)
|
| +
|
| +
|
| +class ImmutableSandboxedEnvironment(SandboxedEnvironment):
|
| + """Works exactly like the regular `SandboxedEnvironment` but does not
|
| + permit modifications on the builtin mutable objects `list`, `set`, and
|
| + `dict` by using the :func:`modifies_known_mutable` function.
|
| + """
|
| +
|
| + def is_safe_attribute(self, obj, attr, value):
|
| + if not SandboxedEnvironment.is_safe_attribute(self, obj, attr, value):
|
| + return False
|
| + return not modifies_known_mutable(obj, attr)
|
|
|