You can not select more than 25 topics
			Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
		
		
		
		
			
				
					476 lines
				
				16 KiB
			
		
		
			
		
	
	
					476 lines
				
				16 KiB
			| 
											8 years ago
										 | # -*- 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) 2017 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, PY2
 | ||
|  | from jinja2.utils import Markup
 | ||
|  | 
 | ||
|  | from markupsafe import EscapeFormatter
 | ||
|  | from string import Formatter
 | ||
|  | 
 | ||
|  | 
 | ||
|  | #: maximum number of items a range may produce
 | ||
|  | MAX_RANGE = 100000
 | ||
|  | 
 | ||
|  | #: attributes of function objects that are considered unsafe.
 | ||
|  | if PY2:
 | ||
|  |     UNSAFE_FUNCTION_ATTRIBUTES = set(['func_closure', 'func_code', 'func_dict',
 | ||
|  |                                       'func_defaults', 'func_globals'])
 | ||
|  | else:
 | ||
|  |     # On versions > python 2 the special attributes on functions are gone,
 | ||
|  |     # but they remain on methods and generators for whatever reason.
 | ||
|  |     UNSAFE_FUNCTION_ATTRIBUTES = set()
 | ||
|  | 
 | ||
|  | 
 | ||
|  | #: unsafe method attributes.  function attributes are unsafe for methods too
 | ||
|  | UNSAFE_METHOD_ATTRIBUTES = set(['im_class', 'im_func', 'im_self'])
 | ||
|  | 
 | ||
|  | #: unsafe generator attirbutes.
 | ||
|  | UNSAFE_GENERATOR_ATTRIBUTES = set(['gi_frame', 'gi_code'])
 | ||
|  | 
 | ||
|  | #: 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
 | ||
|  | 
 | ||
|  | # 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
 | ||
|  | from collections import MutableSet, MutableMapping, MutableSequence
 | ||
|  | _mutable_set_types += (MutableSet,)
 | ||
|  | _mutable_mapping_types += (MutableMapping,)
 | ||
|  | _mutable_sequence_types += (MutableSequence,)
 | ||
|  | 
 | ||
|  | 
 | ||
|  | _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'
 | ||
|  |     ]))
 | ||
|  | )
 | ||
|  | 
 | ||
|  | 
 | ||
|  | 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 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
 | ||
|  |     MAX_RANGE items.
 | ||
|  |     """
 | ||
|  |     rng = range(*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 overridden.
 | ||
|  | 
 | ||
|  |     >>> from jinja2.sandbox import is_internal_attribute
 | ||
|  |     >>> is_internal_attribute(str, "mro")
 | ||
|  |     True
 | ||
|  |     >>> is_internal_attribute(str, "upper")
 | ||
|  |     False
 | ||
|  |     """
 | ||
|  |     if isinstance(obj, types.FunctionType):
 | ||
|  |         if attr in UNSAFE_FUNCTION_ATTRIBUTES:
 | ||
|  |             return True
 | ||
|  |     elif isinstance(obj, types.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, (types.CodeType, types.TracebackType, types.FrameType)):
 | ||
|  |         return True
 | ||
|  |     elif isinstance(obj, types.GeneratorType):
 | ||
|  |         if attr in UNSAFE_GENERATOR_ATTRIBUTES:
 | ||
|  |             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('__')
 | ||
|  | 
 | ||
|  | 
 | ||
|  | 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 occur during the rendering so
 | ||
|  |     the caller has to ensure that all exceptions are caught.
 | ||
|  |     """
 | ||
|  |     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, string_types):
 | ||
|  |                 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 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):
 | ||
|  |             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)
 | ||
|  | 
 | ||
|  | 
 | ||
|  | # 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)
 |