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.
		
		
		
		
		
			
		
			
				
					
					
						
							475 lines
						
					
					
						
							16 KiB
						
					
					
				
			
		
		
	
	
							475 lines
						
					
					
						
							16 KiB
						
					
					
				| # -*- 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)
 | |
| 
 |