Sign Up
Log In
Log In
or
Sign Up
Places
All Projects
Status Monitor
Collapse sidebar
openSUSE:Backports:SLE-15-SP3:Update
python-reportlab
make_toColor_safe.patch
Overview
Repositories
Revisions
Requests
Users
Attributes
Meta
File make_toColor_safe.patch of Package python-reportlab
From de97afeae2adfaf129d577932caf2e455be8e606 Mon Sep 17 00:00:00 2001 From: robin <unknown> Date: Tue, 14 Jan 2020 09:08:36 +0000 Subject: [PATCH] try to make toColor safe; version --> 3.5.34 --- src/reportlab/__init__.py | 4 +- src/reportlab/lib/PyFontify.py | 3 +- src/reportlab/lib/colors.py | 15 +- src/reportlab/lib/rl_safe_eval.py | 1343 +++++++++++++++++++++++++++ src/reportlab/lib/testutils.py | 2 +- src/reportlab/lib/utils.py | 14 +- src/reportlab/platypus/flowables.py | 4 +- tests/test_lib_colors.py | 28 +- tests/test_lib_rl_safe_eval.py | 167 ++++ tests/test_paragraphs.py | 17 +- 10 files changed, 1571 insertions(+), 26 deletions(-) create mode 100644 src/reportlab/lib/rl_safe_eval.py create mode 100644 tests/test_lib_rl_safe_eval.py --- a/src/reportlab/lib/PyFontify.py +++ b/src/reportlab/lib/PyFontify.py @@ -49,7 +49,8 @@ keywordsList = [ "break", "else", "if", "or", "while", "class", "except", "import", "pass", "continue", "finally", "in", "print", - "def", "for", "is", "raise", "yield"] + "def", "for", "is", "raise", "yield", + "with"] # Build up a regular expression which will match anything # interesting, including multi-line triple-quoted strings. --- a/src/reportlab/lib/colors.py +++ b/src/reportlab/lib/colors.py @@ -42,7 +42,7 @@ ValueError: css color 'pcmyka(100,0,0,0) import math, re, functools from reportlab import isPy3 from reportlab.lib.rl_accel import fp_str -from reportlab.lib.utils import asNative, isStr +from reportlab.lib.utils import asNative, isStr, rl_safe_eval import collections class Color: @@ -751,7 +751,7 @@ def hue2rgb(m1, m2, h): if h*3<2: return m1+(m2-m1)*(4-6*h) return m1 -def hsl2rgb(h, s, l): +def hsl2rgb(h, s, l): if l<=0.5: m2 = l*(s+1) else: @@ -834,6 +834,7 @@ class cssParse: cssParse=cssParse() class toColor: + _G = {} #globals we like (eventually) def __init__(self): self.extraColorsNS = {} #used for overriding/adding to existing color names @@ -858,8 +859,18 @@ class toColor: C = getAllNamedColors() s = arg.lower() if s in C: return C[s] + G = C.copy() + G.update(self.extraColorsNS) + if not self._G: + C = globals() + self._G = {s:C[s] for s in '''Blacker CMYKColor CMYKColorSep Color ColorType HexColor PCMYKColor PCMYKColorSep Whiter + _chooseEnforceColorSpace _enforceCMYK _enforceError _enforceRGB _enforceSEP _enforceSEP_BLACK + _enforceSEP_CMYK _namedColors _re_css asNative cmyk2rgb cmykDistance color2bw colorDistance + cssParse describe fade fp_str getAllNamedColors hsl2rgb hue2rgb isStr linearlyInterpolatedColor + literal_eval obj_R_G_B opaqueColor rgb2cmyk setColors toColor toColorOrNone'''.split()} + G.update(self._G) try: - return toColor(eval(arg)) + return toColor(rl_safe_eval(arg, g=G, l={})) except: pass --- /dev/null +++ b/src/reportlab/lib/rl_safe_eval.py @@ -0,0 +1,1343 @@ +#this code is copied/stolen/borrowed/modified from various sources including +#https://github.com/zopefoundation/AccessControl +#https://github.com/zopefoundation/RestrictedPython +#https://github.com/danthedeckie/simpleeval +#hopefully we are standing on giants' shoulders +import sys, os, ast, re, weakref, time, copy, math +from collections import Mapping as collectionsMapping +from reportlab import isPy3 +isPy2 = not isPy3 +eval_debug = int(os.environ.get('EVAL_DEBUG','0')) +strTypes = basestring if isPy2 else (bytes,str) + +haveNameConstant = hasattr(ast,'NameConstant') +haveMatMult = haveMultiStarred = hasattr(ast,'MatMult') +import textwrap + +class BadCode(ValueError): + pass + +# For AugAssign the operator must be converted to a string. +augOps = { + # Shared by python2 and python3 + ast.Add: '+=', + ast.Sub: '-=', + ast.Mult: '*=', + ast.Div: '/=', + ast.Mod: '%=', + ast.Pow: '**=', + ast.LShift: '<<=', + ast.RShift: '>>=', + ast.BitOr: '|=', + ast.BitXor: '^=', + ast.BitAnd: '&=', + ast.FloorDiv: '//=' +} + +if haveMatMult: + augOps[ast.MatMult] = '@=' + + +# For creation allowed magic method names. See also +# https://docs.python.org/3/reference/datamodel.html#special-method-names +__allowed_magic_methods__ = frozenset([ + '__init__', + '__contains__', + '__lt__', + '__le__', + '__eq__', + '__ne__', + '__gt__', + '__ge__', + ]) + +__rl_unsafe__ = frozenset('''builtins breakpoint __annotations__ co_argcount co_cellvars co_code co_consts + __code__ co_filename co_firstlineno co_flags co_freevars co_kwonlyargcount + co_lnotab co_name co_names co_nlocals co_posonlyargcount co_stacksize + co_varnames cr_await cr_code cr_frame cr_origin cr_running __defaults__ + f_back f_builtins f_code f_exc_traceback f_exc_type f_exc_value f_globals + f_lasti f_lineno f_locals f_restricted f_trace __func__ func_code func_defaults + func_doc func_globals func_name gi_code gi_frame gi_running gi_yieldfrom + __globals__ im_class im_func im_self __iter__ __kwdefaults__ __module__ + __name__ next __qualname__ __self__ tb_frame tb_lasti tb_lineno tb_next + globals vars locals'''.split() + ) +__rl_unsafe_re__ = re.compile(r'\b(?:%s)' % '|'.join(__rl_unsafe__),re.M) + +def copy_locations(new_node, old_node): + new_node.lineno = old_node.lineno + new_node.col_offset = old_node.col_offset + ast.fix_missing_locations(new_node) + +class UntrustedAstTransformer(ast.NodeTransformer): + + def __init__(self, names_seen=None, nameIsAllowed=None): + super(UntrustedAstTransformer, self).__init__() + self.names_seen = {} if names_seen is None else names_seen + self.nameIsAllowed = nameIsAllowed + + # Global counter to construct temporary variable names. + self._tmp_idx = 0 + self._tmp_pfx = '_tmp%s' % repr(time.time()).replace('.','') + + @property + def tmpName(self): + name = '%s%s' % (self._tmp_pfx,self._tmp_idx) + self._tmp_idx += 1 + return name + + def error(self, node, msg): + raise BadCode('Line %s: %s' % (getattr(node, 'lineno', '??'), msg)) + + def guard_iter(self, node): + """ + Converts: + for x in expr + to + for x in __rl_getiter__(expr) + + Also used for + * list comprehensions + * dict comprehensions + * set comprehensions + * generator expresions + """ + node = self.visit_children(node) + + if isinstance(node.target, ast.Tuple): + spec = self.gen_unpack_spec(node.target) + new_iter = ast.Call( + func=ast.Name('__rl_iter_unpack_sequence__', ast.Load()), + args=[node.iter, spec, ast.Name('__rl_getiter__', ast.Load())], + keywords=[]) + else: + new_iter = ast.Call( + func=ast.Name('__rl_getiter__', ast.Load()), + args=[node.iter], + keywords=[]) + + copy_locations(new_iter, node.iter) + node.iter = new_iter + return node + + def is_starred(self, ob): + if isPy3: + return isinstance(ob, ast.Starred) + else: + return False + + def gen_unpack_spec(self, tpl): + """Generate a specification for '__rl_unpack_sequence__'. + + This spec is used to protect sequence unpacking. + The primary goal of this spec is to tell which elements in a sequence + are sequences again. These 'child' sequences have to be protected + again. + + For example there is a sequence like this: + (a, (b, c), (d, (e, f))) = g + + On a higher level the spec says: + - There is a sequence of len 3 + - The element at index 1 is a sequence again with len 2 + - The element at index 2 is a sequence again with len 2 + - The element at index 1 in this subsequence is a sequence again + with len 2 + + With this spec '__rl_unpack_sequence__' does something like this for + protection (len checks are omitted): + + t = list(__rl_getiter__(g)) + t[1] = list(__rl_getiter__(t[1])) + t[2] = list(__rl_getiter__(t[2])) + t[2][1] = list(__rl_getiter__(t[2][1])) + return t + + The 'real' spec for the case above is then: + spec = { + 'min_len': 3, + 'childs': ( + (1, {'min_len': 2, 'childs': ()}), + (2, { + 'min_len': 2, + 'childs': ( + (1, {'min_len': 2, 'childs': ()}) + ) + } + ) + ) + } + + So finally the assignment above is converted into: + (a, (b, c), (d, (e, f))) = __rl_unpack_sequence__(g, spec) + """ + spec = ast.Dict(keys=[], values=[]) + + spec.keys.append(ast.Str('childs')) + spec.values.append(ast.Tuple([], ast.Load())) + + # starred elements in a sequence do not contribute into the min_len. + # For example a, b, *c = g + # g must have at least 2 elements, not 3. 'c' is empyt if g has only 2. + min_len = len([ob for ob in tpl.elts if not self.is_starred(ob)]) + offset = 0 + + for idx, val in enumerate(tpl.elts): + # After a starred element specify the child index from the back. + # Since it is unknown how many elements from the sequence are + # consumed by the starred element. + # For example a, *b, (c, d) = g + # Then (c, d) has the index '-1' + if self.is_starred(val): + offset = min_len + 1 + + elif isinstance(val, ast.Tuple): + el = ast.Tuple([], ast.Load()) + el.elts.append(ast.Num(idx - offset)) + el.elts.append(self.gen_unpack_spec(val)) + spec.values[0].elts.append(el) + + spec.keys.append(ast.Str('min_len')) + spec.values.append(ast.Num(min_len)) + + return spec + + def protect_unpack_sequence(self, target, value): + spec = self.gen_unpack_spec(target) + return ast.Call( + func=ast.Name('__rl_unpack_sequence__', ast.Load()), + args=[value, spec, ast.Name('__rl_getiter__', ast.Load())], + keywords=[]) + + def gen_unpack_wrapper(self, node, target, ctx='store'): + """Helper function to protect tuple unpacks. + + node: used to copy the locations for the new nodes. + target: is the tuple which must be protected. + ctx: Defines the context of the returned temporary node. + + It returns a tuple with two element. + + Element 1: Is a temporary name node which must be used to + replace the target. + The context (store, param) is defined + by the 'ctx' parameter.. + + Element 2: Is a try .. finally where the body performs the + protected tuple unpack of the temporary variable + into the original target. + """ + + # Generate a tmp name to replace the tuple with. + tnam = self.tmpName + + # Generates an expressions which protects the unpack. + # converter looks like 'wrapper(tnam)'. + # 'wrapper' takes care to protect sequence unpacking with __rl_getiter__. + converter = self.protect_unpack_sequence( + target, + ast.Name(tnam, ast.Load())) + + # Assign the expression to the original names. + # Cleanup the temporary variable. + # Generates: + # try: + # # converter is 'wrapper(tnam)' + # arg = converter + # finally: + # del tmp_arg + try_body = [ast.Assign(targets=[target], value=converter)] + finalbody = [self.gen_del_stmt(tnam)] + + if isPy2: + cleanup = ast.TryFinally(body=try_body, finalbody=finalbody) + else: + cleanup = ast.Try( + body=try_body, finalbody=finalbody, handlers=[], orelse=[]) + + if ctx == 'store': + ctx = ast.Store() + elif ctx == 'param': + ctx = ast.Param() + else: # pragma: no cover + # Only store and param are defined ctx. + raise NotImplementedError('bad ctx "%s"' % type(ctx)) + + # This node is used to catch the tuple in a tmp variable. + tmp_target = ast.Name(tnam, ctx) + + copy_locations(tmp_target, node) + copy_locations(cleanup, node) + + return (tmp_target, cleanup) + + def gen_none_node(self): + return ast.NameConstant(value=None) if hasNameConstant else ast.Name(id='None', ctx=ast.Load()) + + def gen_lambda(self, args, body): + return ast.Lambda( + args=ast.arguments( + args=args, vararg=None, kwarg=None, defaults=[]), + body=body) + + def gen_del_stmt(self, name_to_del): + return ast.Delete(targets=[ast.Name(name_to_del, ast.Del())]) + + def transform_slice(self, slice_): + """Transform slices into function parameters. + + ast.Slice nodes are only allowed within a ast.Subscript node. + To use a slice as an argument of ast.Call it has to be converted. + Conversion is done by calling the 'slice' function from builtins + """ + + if isinstance(slice_, ast.Index): + return slice_.value + + elif isinstance(slice_, ast.Slice): + # Create a python slice object. + args = [] + + if slice_.lower: + args.append(slice_.lower) + else: + args.append(self.gen_none_node()) + + if slice_.upper: + args.append(slice_.upper) + else: + args.append(self.gen_none_node()) + + if slice_.step: + args.append(slice_.step) + else: + args.append(self.gen_none_node()) + + return ast.Call( + func=ast.Name('slice', ast.Load()), + args=args, + keywords=[]) + + elif isinstance(slice_, ast.ExtSlice): + dims = ast.Tuple([], ast.Load()) + for item in slice_.dims: + dims.elts.append(self.transform_slice(item)) + return dims + + else: # pragma: no cover + # Index, Slice and ExtSlice are only defined Slice types. + raise NotImplementedError("Unknown slice type: %s" % slice_) + + def isAllowedName(self, node, name): + if name is None: return + self.nameIsAllowed(name) + + def check_function_argument_names(self, node): + # In python3 arguments are always identifiers. + # In python2 the 'Python.asdl' specifies expressions, but + # the python grammer allows only identifiers or a tuple of + # identifiers. If its a tuple 'tuple parameter unpacking' is used, + # which is gone in python3. + # See https://www.python.org/dev/peps/pep-3113/ + + if isPy2: + # Needed to handle nested 'tuple parameter unpacking'. + # For example 'def foo((a, b, (c, (d, e)))): pass' + to_check = list(node.args.args) + while to_check: + item = to_check.pop() + if isinstance(item, ast.Tuple): + to_check.extend(item.elts) + else: + self.isAllowedName(node, item.id) + + self.isAllowedName(node, node.args.vararg) + self.isAllowedName(node, node.args.kwarg) + + else: + for arg in node.args.args: + self.isAllowedName(node, arg.arg) + + if node.args.vararg: + self.isAllowedName(node, node.args.vararg.arg) + + if node.args.kwarg: + self.isAllowedName(node, node.args.kwarg.arg) + + for arg in node.args.kwonlyargs: + self.isAllowedName(node, arg.arg) + + def check_import_names(self, node): + """Check the names being imported. + + This is a protection against rebinding dunder names like + __rl_getitem__,__rl_set__ via imports. + + => 'from _a import x' is ok, because '_a' is not added to the scope. + """ + for name in node.names: + if '*' in name.name: + self.error(node, '"*" imports are not allowed.') + self.isAllowedName(node, name.name) + if name.asname: + self.isAllowedName(node, name.asname) + + return self.visit_children(node) + + def gen_attr_check(self, node, attr_name): + """Check if 'attr_name' is allowed on the object in node. + + It generates (_getattr_(node, attr_name) and node). + """ + + call_getattr = ast.Call( + func=ast.Name('__rl_getattr__', ast.Load()), + args=[node, ast.Str(attr_name)], + keywords=[]) + + return ast.BoolOp(op=ast.And(), values=[call_getattr, node]) + + def visit_Constant(self, node): + """Allow constant literals with restriction for Ellipsis. + + Constant replaces Num, Str, Bytes, NameConstant and Ellipsis in + Python 3.8+. + :see: https://docs.python.org/dev/whatsnew/3.8.html#deprecated + """ + if node.value is Ellipsis: + # Deny using `...`. + # Special handling necessary as ``self.not_allowed(node)`` + # would return the Error Message: + # 'Constant statements are not allowed.' + # which is only partial true. + self.error(node, 'Ellipsis statements are not allowed.') + return + return self.visit_children(node) + + # ast for Variables + def visit_Name(self, node): + node = self.visit_children(node) + + if isinstance(node.ctx, ast.Load): + if node.id == 'print': + self.error(node,'print function is not allowed') + self.names_seen[node.id] = True + + self.isAllowedName(node, node.id) + return node + + def visit_Call(self, node): + """Checks calls with '*args' and '**kwargs'. + + Note: The following happens only if '*args' or '**kwargs' is used. + + Transfroms 'foo(<all the possible ways of args>)' into + __rl_apply__(foo, <all the possible ways for args>) + + The thing is that '__rl_apply__' has only '*args', '**kwargs', so it gets + Python to collapse all the myriad ways to call functions + into one manageable from. + + From there, '__rl_apply__()' wraps args and kws in guarded accessors, + then calls the function, returning the value. + """ + + if isinstance(node.func, ast.Name): + if node.func.id == 'exec': + self.error(node, 'Exec calls are not allowed.') + elif node.func.id == 'eval': + self.error(node, 'Eval calls are not allowed.') + + needs_wrap = False + + # In python2.7 till python3.4 '*args', '**kwargs' have dedicated + # attributes on the ast.Call node. + # In python 3.5 and greater this has changed due to the fact that + # multiple '*args' and '**kwargs' are possible. + # '*args' can be detected by 'ast.Starred' nodes. + # '**kwargs' can be deteced by 'keyword' nodes with 'arg=None'. + + if haveMultiStarred: + for pos_arg in node.args: + if isinstance(pos_arg, ast.Starred): + needs_wrap = True + + for keyword_arg in node.keywords: + if keyword_arg.arg is None: + needs_wrap = True + else: + if (node.starargs is not None) or (node.kwargs is not None): + needs_wrap = True + + node = self.visit_children(node) + + #if not needs_wrap: + # return node + + node.args.insert(0, node.func) + node.func = ast.Name('__rl_apply__', ast.Load()) + copy_locations(node.func, node.args[0]) + return node + + def visit_Attribute(self, node): + """Checks and mutates attribute access/assignment. + + 'a.b' becomes '__rl_getattr__(a, "b")' + """ + if node.attr.startswith('__') and node.attr != '__': + self.error(node, '"%s" is an invalid attribute'%node.attr) + + if isinstance(node.ctx, ast.Load): + node = self.visit_children(node) + new_node = ast.Call( + func=ast.Name('__rl_getattr__', ast.Load()), + args=[node.value, ast.Str(node.attr)], + keywords=[]) + + copy_locations(new_node, node) + return new_node + + elif isinstance(node.ctx, (ast.Store, ast.Del)): + node = self.visit_children(node) + new_value = ast.Call( + func=ast.Name('__rl_sd__', ast.Load()), + args=[node.value], + keywords=[]) + + copy_locations(new_value, node.value) + node.value = new_value + return node + + else: # pragma: no cover + # Impossible Case only ctx Load, Store and Del are defined in ast. + raise NotImplementedError("Unknown ctx type: %s" % type(node.ctx)) + + # Subscripting + def visit_Subscript(self, node): + """Transforms all kinds of subscripts. + + 'v[a]' becomes '__rl_getitem__(foo, a)' + 'v[:b]' becomes '__rl_getitem__(foo, slice(None, b, None))' + 'v[a:]' becomes '__rl_getitem__(foo, slice(a, None, None))' + 'v[a:b]' becomes '__rl_getitem__(foo, slice(a, b, None))' + 'v[a:b:c]' becomes '__rl_getitem__(foo, slice(a, b, c))' + 'v[a,b:c] becomes '__rl_getitem__(foo, (a, slice(b, c, None)))' + #'v[a] = c' becomes '_rl_write__(v)[a] = c' + #'del v[a]' becomes 'del __rl_sd__(v)[a]' + """ + node = self.visit_children(node) + + # 'AugStore' and 'AugLoad' are defined in 'Python.asdl' as possible + # 'expr_context'. However, according to Python/ast.c + # they are NOT used by the implementation => No need to worry here. + # Instead ast.c creates 'AugAssign' nodes, which can be visit_ed. + + if isinstance(node.ctx, ast.Load): + new_node = ast.Call( + func=ast.Name('__rl_getitem__', ast.Load()), + args=[node.value, self.transform_slice(node.slice)], + keywords=[]) + + copy_locations(new_node, node) + return new_node + + elif isinstance(node.ctx, (ast.Del, ast.Store)): + #new_value = ast.Call( + # func=ast.Name('__rl_sd__', ast.Load()), + # args=[node.value], + # keywords=[]) + + #copy_locations(new_value, node) + #node.value = new_value + return node + + else: # pragma: no cover + # Impossible Case only ctx Load, Store and Del are defined in ast. + raise NotImplementedError("Unknown ctx type: %s" % type(node.ctx)) + + # Statements + def visit_Assign(self, node): + node = self.visit_children(node) + + if not any(isinstance(t, ast.Tuple) for t in node.targets): + return node + + # Handle sequence unpacking. + # For briefness this example omits cleanup of the temporary variables. + # Check 'transform_tuple_assign' how its done. + # + # - Single target (with nested support) + # (a, (b, (c, d))) = <exp> + # is converted to + # (a, t1) = __rl_getiter__(<exp>) + # (b, t2) = __rl_getiter__(t1) + # (c, d) = __rl_getiter__(t2) + # + # - Multi targets + # (a, b) = (c, d) = <exp> + # is converted to + # (c, d) = __rl_getiter__(<exp>) + # (a, b) = __rl_getiter__(<exp>) + # Why is this valid ? The original bytecode for this multi targets + # behaves the same way. + + # ast.NodeTransformer works with list results. + # He injects it at the rightplace of the node's parent statements. + new_nodes = [] + + # python fills the right most target first. + for target in reversed(node.targets): + if isinstance(target, ast.Tuple): + wrapper = ast.Assign( + targets=[target], + value=self.protect_unpack_sequence(target, node.value)) + new_nodes.append(wrapper) + else: + new_node = ast.Assign(targets=[target], value=node.value) + new_nodes.append(new_node) + + for new_node in new_nodes: + copy_locations(new_node, node) + + return new_nodes + + def visit_AugAssign(self, node): + """Forbid certain kinds of AugAssign + + According to the language reference (and ast.c) the following nodes + are are possible: + Name, Attribute, Subscript + + Note that although augmented assignment of attributes and + subscripts is disallowed, augmented assignment of names (such + as 'n += 1') is allowed. + 'n += 1' becomes 'n = __rl_augAssign__("+=", n, 1)' + """ + + node = self.visit_children(node) + + if isinstance(node.target, ast.Attribute): + self.error(node, "Augmented assignment of attributes is not allowed.") + + elif isinstance(node.target, ast.Subscript): + self.error(node, "Augmented assignment of object items and slices is not allowed.") + + elif isinstance(node.target, ast.Name): + new_node = ast.Assign( + targets=[node.target], + value=ast.Call( + func=ast.Name('__rl_augAssign__', ast.Load()), + args=[ + ast.Str(augOps[type(node.op)]), + ast.Name(node.target.id, ast.Load()), + node.value + ], + keywords=[])) + + copy_locations(new_node, node) + return new_node + else: # pragma: no cover + # Impossible Case - Only Node Types: + # * Name + # * Attribute + # * Subscript + # defined, those are checked before. + raise NotImplementedError("Unknown target type: %s" % type(node.target)) + + def visit_While(node): + self.visit_children(node) + return node + + def visit_ExceptHandler(self, node): + """Protect tuple unpacking on exception handlers. + + try: + ..... + except Exception as (a, b): + .... + + becomes + + try: + ..... + except Exception as tmp: + try: + (a, b) = __rl_getiter__(tmp) + finally: + del tmp + """ + node = self.visit_children(node) + + if isPy3: + self.isAllowedName(node, node.name) + return node + + if not isinstance(node.name, ast.Tuple): + return node + + tmp_target, unpack = self.gen_unpack_wrapper(node, node.name) + + # Replace the tuple with the temporary variable. + node.name = tmp_target + + # Insert the unpack code within the body of the except clause. + node.body.insert(0, unpack) + + return node + + def visit_With(self, node): + """Protect tuple unpacking on with statements.""" + node = self.visit_children(node) + + if isPy2: + items = [node] + else: + items = node.items + + for item in reversed(items): + if isinstance(item.optional_vars, ast.Tuple): + tmp_target, unpack = self.gen_unpack_wrapper( + node, + item.optional_vars) + + item.optional_vars = tmp_target + node.body.insert(0, unpack) + + return node + + # Function and class definitions + def visit_FunctionDef(self, node): + """Allow function definitions (`def`) with some restrictions.""" + self.isAllowedName(node, node.name) + self.check_function_argument_names(node) + + if isPy3: + return node + + # Protect 'tuple parameter unpacking' with '__rl_getiter__'. + + unpacks = [] + for index, arg in enumerate(list(node.args.args)): + if isinstance(arg, ast.Tuple): + tmp_target, unpack = self.gen_unpack_wrapper( + node, arg, 'param') + + # Replace the tuple with a single (temporary) parameter. + node.args.args[index] = tmp_target + unpacks.append(unpack) + + # Add the unpacks at the front of the body. + # Keep the order, so that tuple one is unpacked first. + node.body[0:0] = unpacks + return node + + def visit_Lambda(self, node): + """Allow lambda with some restrictions.""" + self.check_function_argument_names(node) + + node = self.visit_children(node) + + if isPy3: + # Implicit Tuple unpacking is not anymore available in Python3 + return node + + # Check for tuple parameters which need __rl_getiter__ protection + if not any(isinstance(arg, ast.Tuple) for arg in node.args.args): + return node + + # Wrap this lambda function with another. Via this wrapping it is + # possible to protect the 'tuple arguments' with __rl_getiter__ + outer_params = [] + inner_args = [] + + for arg in node.args.args: + if isinstance(arg, ast.Tuple): + tnam = self.tmpName + converter = self.protect_unpack_sequence( + arg, + ast.Name(tnam, ast.Load())) + + outer_params.append(ast.Name(tnam, ast.Param())) + inner_args.append(converter) + + else: + outer_params.append(arg) + inner_args.append(ast.Name(arg.id, ast.Load())) + + body = ast.Call(func=node, args=inner_args, keywords=[]) + new_node = self.gen_lambda(outer_params, body) + + if node.args.vararg: + new_node.args.vararg = node.args.vararg + body.starargs = ast.Name(node.args.vararg, ast.Load()) + + if node.args.kwarg: + new_node.args.kwarg = node.args.kwarg + body.kwargs = ast.Name(node.args.kwarg, ast.Load()) + + copy_locations(new_node, node) + return new_node + + def visit_ClassDef(self, node): + """Check the name of a class definition.""" + self.isAllowedName(node, node.name) + node = self.visit_children(node) + if isPy2: + new_class_node = node + else: + if any(keyword.arg == 'metaclass' for keyword in node.keywords): + self.error(node, 'The keyword argument "metaclass" is not allowed.') + CLASS_DEF = textwrap.dedent('''\ + class %s(metaclass=__metaclass__): + pass + ''' % node.name) + new_class_node = ast.parse(CLASS_DEF).body[0] + new_class_node.body = node.body + new_class_node.bases = node.bases + new_class_node.decorator_list = node.decorator_list + return new_class_node + + # Imports + def visit_Import(self, node): + return self.check_import_names(node) + + node = self.visit_children(node) + new_node = ast.Call( + func=ast.Name('__rl_add__', ast.Load()), + args=[node.left, node.right], + keywords=[]) + copy_locations(new_node, node) + return new_node + + def visit_BinOp(self,node): + node = self.visit_children(node) + op = node.op + if isinstance(op,(ast.Mult,ast.Add,ast.Pow)): + opf = ('__rl_mult__' if isinstance(op,ast.Mult) + else '__rl_add__' if isinstance(op,ast.Add) + else '__rl_pow__') + new_node = ast.Call( + func=ast.Name(opf, ast.Load()), + args=[node.left, node.right], + keywords=[]) + copy_locations(new_node, node) + return new_node + return node + + visit_ImportFrom = visit_Import + visit_For = guard_iter + visit_comprehension = guard_iter + + def generic_visit(self, node): + """Reject nodes which do not have a corresponding `visit` method.""" + self.not_allowed(node) + + def not_allowed(self, node): + self.error(node, '%s statements are not allowed.'%node.__class__.__name__) + + def visit_children(self, node): + """Visit the contents of a node.""" + return super(UntrustedAstTransformer, self).generic_visit(node) + + if eval_debug>=2: + def visit(self, node): + method = 'visit_' + node.__class__.__name__ + visitor = getattr(self, method, self.generic_visit) + print('visitor=%s=%r node=%r' % (method,visitor,node)) + return visitor(node) + + visit_Ellipsis = not_allowed + visit_MatMult = not_allowed + visit_Exec = not_allowed + visit_Nonlocal = not_allowed + visit_AsyncFunctionDef = not_allowed + visit_Await = not_allowed + visit_AsyncFor = not_allowed + visit_AsyncWith = not_allowed + visit_Print = not_allowed + + visit_Num = visit_children + visit_Str = visit_children + visit_Bytes = visit_children + visit_List = visit_children + visit_Tuple = visit_children + visit_Set = visit_children + visit_Dict = visit_children + visit_FormattedValue = visit_children + visit_JoinedStr = visit_children + visit_NameConstant = visit_children + visit_Load = visit_children + visit_Store = visit_children + visit_Del = visit_children + visit_Starred = visit_children + visit_Expression = visit_children + visit_Expr = visit_children + visit_UnaryOp = visit_children + visit_UAdd = visit_children + visit_USub = visit_children + visit_Not = visit_children + visit_Invert = visit_children + visit_Add = visit_children + visit_Sub = visit_children + visit_Mult = visit_children + visit_Div = visit_children + visit_FloorDiv = visit_children + visit_Pow = visit_children + visit_Mod = visit_children + visit_LShift = visit_children + visit_RShift = visit_children + visit_BitOr = visit_children + visit_BitXor = visit_children + visit_BitAnd = visit_children + visit_BoolOp = visit_children + visit_And = visit_children + visit_Or = visit_children + visit_Compare = visit_children + visit_Eq = visit_children + visit_NotEq = visit_children + visit_Lt = visit_children + visit_LtE = visit_children + visit_Gt = visit_children + visit_GtE = visit_children + visit_Is = visit_children + visit_IsNot = visit_children + visit_In = visit_children + visit_NotIn = visit_children + visit_keyword = visit_children + visit_IfExp = visit_children + visit_Index = visit_children + visit_Slice = visit_children + visit_ExtSlice = visit_children + visit_ListComp = visit_children + visit_SetComp = visit_children + visit_GeneratorExp = visit_children + visit_DictComp = visit_children + visit_Raise = visit_children + visit_Assert = visit_children + visit_Delete = visit_children + visit_Pass = visit_children + visit_alias = visit_children + visit_If = visit_children + visit_Break = visit_children + visit_Continue = visit_children + visit_Try = visit_children + visit_TryFinally = visit_children + visit_TryExcept = visit_children + visit_withitem = visit_children + visit_arguments = visit_children + visit_arg = visit_children + visit_Return = visit_children + visit_Yield = visit_children + visit_YieldFrom = visit_children + visit_Global = visit_children + visit_Module = visit_children + visit_Param = visit_children + +def astFormat(node): + return ast.dump(copy.deepcopy(node),annotate_fields=True, include_attributes=True) + +class __rl_SafeIter__(object): + def __init__(self, it, owner): + self.__rl_iter__ = owner().__rl_real_iter__(it) + self.__rl_owner__ = owner + + def __iter__(self): + return self + + def __next__(self): + self.__rl_owner__().__rl_check__() + return next(self.__rl_iter__) + + next = __next__ # Python 2 compat + +__rl_safe_builtins__ = {} #constructed below +def safer_globals(g=None): + if g is None: + g = sys._getframe(1).f_globals.copy() + for name in ('__annotations__', '__doc__', '__loader__', '__name__', '__package__', '__spec__'): + if name in g: + del g[name] + g['__builtins__'] = __rl_safe_builtins__.copy() + return g + +math_log10 = math.log10 +__rl_undef__ = object() +class __RL_SAFE_ENV__(object): + __time_time__ = time.time + __weakref_ref__ = weakref.ref + __slicetype__ = type(slice(0)) + def __init__(self, timeout=None, allowed_magic_methods=None): + self.timeout = timeout if timeout is not None else self.__rl_tmax__ + self.allowed_magic_methods = (__allowed_magic_methods__ if allowed_magic_methods==True + else allowed_magic_methods) if allowed_magic_methods else [] + if isPy3: + import builtins + self.__rl_gen_range__ = builtins.range + else: + import __builtin__ as builtins + self.__rl_gen_range__ = builtins.xrange + + self.__rl_real_iter__ = builtins.iter + + class __rl_dict__(dict): + def __new__(cls, *args,**kwds): + if len(args)==1 and not isinstance(args[0],collectionsMapping): + try: + it = self.__real_iter__(args[0]) + except TypeError: + pass + else: + args = (self.__rl_getiter__(it),) + return dict.__new__(cls,*args,**kwds) + + class __rl_missing_func__(object): + def __init__(self,name): + self.__name__ = name + def __call__(self,*args,**kwds): + raise BadCode('missing global %s' % self.__name__) + + self.real_bi = builtins + self.bi_replace = ( + ('open',__rl_missing_func__('open')), + ('iter',self.__rl_getiter__), + ) + (( + ) if isPy3 else ( + ('file',__rl_missing_func__('file')), + )) + + __rl_safe_builtins__.update({_:getattr(builtins,_) for _ in + ('''None False True abs bool callable chr complex divmod float hash hex id int + isinstance issubclass len oct ord range repr round slice str tuple setattr + classmethod staticmethod property divmod next object getattr dict iter pow list + type max min sum enumerate zip hasattr filter map any all sorted reversed range + set frozenset + + ArithmeticError AssertionError AttributeError BaseException BufferError BytesWarning + DeprecationWarning EOFError EnvironmentError Exception FloatingPointError FutureWarning + GeneratorExit IOError ImportError ImportWarning IndentationError IndexError KeyError + KeyboardInterrupt LookupError MemoryError NameError NotImplementedError OSError + OverflowError PendingDeprecationWarning ReferenceError RuntimeError RuntimeWarning + StopIteration SyntaxError SyntaxWarning SystemError SystemExit TabError TypeError + UnboundLocalError UnicodeDecodeError UnicodeEncodeError UnicodeError UnicodeTranslateError + UnicodeWarning UserWarning ValueError Warning ZeroDivisionError + ''' + ('__build_class__' if isPy3 + else 'basestring cmp long unichr unicode xrange StandardError reduce apply')).split()}) + + self.__rl_builtins__ = __rl_builtins__ = {_:__rl_missing_func__(_) for _ in dir(builtins) if callable(getattr(builtins,_))} + __rl_builtins__.update(__rl_safe_builtins__) + + #these are used in the tree visitor + __rl_builtins__['__rl_add__'] = self.__rl_add__ + __rl_builtins__['__rl_mult__'] = self.__rl_mult__ + __rl_builtins__['__rl_pow__'] = self.__rl_pow__ + __rl_builtins__['__rl_sd__'] = self.__rl_sd__ + __rl_builtins__['__rl_augAssign__'] = self.__rl_augAssign__ + __rl_builtins__['__rl_getitem__'] = self.__rl_getitem__ + __rl_builtins__['__rl_getattr__'] = self.__rl_getattr__ + __rl_builtins__['__rl_getiter__'] = self.__rl_getiter__ + __rl_builtins__['__rl_max_len__'] = self.__rl_max_len__ + __rl_builtins__['__rl_max_pow_digits__'] = self.__rl_max_pow_digits__ + __rl_builtins__['__rl_iter_unpack_sequence__'] = self.__rl_iter_unpack_sequence__ + __rl_builtins__['__rl_unpack_sequence__'] = self.__rl_unpack_sequence__ + __rl_builtins__['__rl_apply__'] = lambda func,*args,**kwds: self.__rl_apply__(func,args,kwds) + __rl_builtins__['__rl_SafeIter__'] = __rl_SafeIter__ + + #these are tested builtins + __rl_builtins__['getattr'] = self.__rl_getattr__ + __rl_builtins__['dict'] = __rl_dict__ + __rl_builtins__['iter'] = self.__rl_getiter__ + __rl_builtins__['pow'] = self.__rl_pow__ + __rl_builtins__['list'] = self.__rl_list__ + __rl_builtins__['type'] = self.__rl_type__ + __rl_builtins__['max'] = self.__rl_max__ + __rl_builtins__['min'] = self.__rl_min__ + __rl_builtins__['sum'] = self.__rl_sum__ + __rl_builtins__['enumerate'] = self.__rl_enumerate__ + __rl_builtins__['zip'] = self.__rl_zip__ + __rl_builtins__['hasattr'] = self.__rl_hasattr__ + __rl_builtins__['filter'] = self.__rl_filter__ + __rl_builtins__['map'] = self.__rl_map__ + __rl_builtins__['any'] = self.__rl_any__ + __rl_builtins__['all'] = self.__rl_all__ + __rl_builtins__['sorted'] = self.__rl_sorted__ + __rl_builtins__['reversed'] = self.__rl_reversed__ + __rl_builtins__['range'] = self.__rl_range__ + __rl_builtins__['set'] = self.__rl_set__ + __rl_builtins__['frozenset'] = self.__rl_frozenset__ + if not isPy3: + __rl_builtins__['reduce'] = self.__rl_reduce__ + __rl_builtins__['xrange'] = self.__rl_xrange__ + __rl_builtins__['apply'] = self.__rl_apply__ + + def __rl_type__(self,*args): + if len(args)==1: return type(*args) + raise BadCode('type call error') + + def __rl_check__(self): + if self.__time_time__() >= self.__rl_limit__: + raise BadCode('Resources exceeded') + + def __rl_sd__(self,obj): + return obj + + def __rl_getiter__(self,it): + return __rl_SafeIter__(it,owner=self.__weakref_ref__(self)) + + def __rl_max__(self,arg,*args,**kwds): + if args: + arg = [arg] + arg.extend(args) + return max(self.__rl_args_iter__(arg),**kwds) + + def __rl_min__(self,arg,*args,**kwds): + if args: + arg = [arg] + arg.extend(args) + return min(self.__rl_args_iter__(arg),**kwds) + + def __rl_sum__(self, sequence, start=0): + return sum(self.__rl_args_iter__(sequence), start) + + def __rl_enumerate__(self, seq): + return enumerate(self.__rl_args_iter__(seq)) + + def __rl_zip__(self,*args): + return zip(*[self.__rl_args_iter__(self.__rl_getitem__(args, i)) for i in range(len(args))]) + + def __rl_hasattr__(self, obj, name): + try: + self.__rl_getattr__(obj, name) + except (AttributeError, BadCode, TypeError): + return False + return True + + def __rl_filter__(self, f, seq): + return filter(f,self.__rl_args_iter__(seq)) + + def __rl_map__(self, f, seq): + return map(f,self.__rl_args_iter__(seq)) + + def __rl_any__(self, seq): + return any(self.__rl_args_iter__(seq)) + + def __rl_all__(self, seq): + return all(self.__rl_args_iter__(seq)) + + def __rl_sorted__(self, seq, **kwds): + return sorted(self.__rl_args_iter__(seq),**kwds) + + def __rl_reversed__(self, seq): + return self.__rl_args_iter__(reversed(seq)) + + if not isPy3: + def __rl_reduce__(self, f, seq, initial=__rl_undef__): + if initial is __rl_undef__: + return reduce(f, self.__rl_args_iter__(seq)) + else: + return reduce(f, self.__rl_args_iter__(seq), initial) + def __rl_range__(self,start,*args): + return list(self.__rl_getiter__(range(start,*args))) + def __rl_xrange__(self,start,*args): + return self.__rl_getiter__(xrange(start,*args)) + else: + def __rl_range__(self,start,*args): + return self.__rl_getiter__(range(start,*args)) + + def __rl_set__(self, it): + return set(self.__rl_args_iter__(it)) + + def __rl_frozenset__(self, it): + return frozenset(self.__rl_args_iter__(it)) + + def __rl_iter_unpack_sequence__(self, it, spec, _getiter_): + """Protect sequence unpacking of targets in a 'for loop'. + + The target of a for loop could be a sequence. + For example "for a, b in it" + => Each object from the iterator needs guarded sequence unpacking. + """ + # The iteration itself needs to be protected as well. + for ob in _getiter_(it): + yield self.__rl_unpack_sequence__(ob, spec, _getiter_) + + def __rl_unpack_sequence__(self, it, spec, _getiter_): + """Protect nested sequence unpacking. + + Protect the unpacking of 'it' by wrapping it with '_getiter_'. + Furthermore for each child element, defined by spec, + __rl_unpack_sequence__ is called again. + + Have a look at transformer.py 'gen_unpack_spec' for a more detailed + explanation. + """ + # Do the guarded unpacking of the sequence. + ret = list(self.__rl__getiter__(it)) + + # If the sequence is shorter then expected the interpreter will raise + # 'ValueError: need more than X value to unpack' anyway + # => No childs are unpacked => nothing to protect. + if len(ret) < spec['min_len']: + return ret + + # For all child elements do the guarded unpacking again. + for (idx, child_spec) in spec['childs']: + ret[idx] = self.__rl_unpack_sequence__(ret[idx], child_spec, _getiter_) + return ret + + def __rl_is_allowed_name__(self, name): + """Check names if they are allowed. + If ``allow_magic_methods is True`` names in `__allowed_magic_methods__` + are additionally allowed although their names start with `_`. + """ + if isinstance(name,strTypes): + if name in __rl_unsafe__ or (name.startswith('__') + and name!='__' + and name not in self.allowed_magic_methods): + raise BadCode('unsafe access of %s' % name) + + def __rl_getattr__(self, obj, a, *args): + if isinstance(obj, strTypes) and a=='format': + raise BadCode('%s.format is not implemented' % type(obj)) + self.__rl_is_allowed_name__(a) + return getattr(obj,a,*args) + + def __rl_getitem__(self, obj, a): + if type(a) is self.__slicetype__: + if a.step is not None: + v = obj[a] + else: + start = a.start + stop = a.stop + if start is None: + start = 0 + if stop is None: + v = obj[start:] + else: + v = obj[start:stop] + return v + elif isinstance(a,strTypes): + self.__rl_is_allowed_name__(a) + return obj[a] + return obj[a] + + __rl_tmax__ = 5 + __rl_max_len__ = 100000 + __rl_max_pow_digits__ = 100 + + def __rl_add__(self, a, b): + if (hasattr(a, '__len__') and hasattr(b, '__len__') + and (len(a) + len(b)) > self.__rl_max_len__): + raise BadCode("excessive length") + return a + b + + def __rl_mult__(self, a, b): + if ((hasattr(a, '__len__') and b * len(a) > self.__rl_max_len__) + or (hasattr(b, '__len__') and a * len(b) > self.__rl_max_len__)): + raise BadCode("excessive length") + return a * b + + def __rl_pow__(self, a, b): + try: + if b>0: + if int(b*math_log10(a)+1)>self.__rl_max_pow_digits__: + raise BadCode + except: + raise BadCode('%r**%r invalid or too large' % (a,b)) + return a ** b + + def __rl_augAssign__(self,op,v,i): + if op=='+=': return self.__rl_add__(v,i) + if op=='-=': return v-i + if op=='*=': return self.__rl_mult__(v,i) + if op=='/=': return v/i + if op=='%=': return v%i + if op=='**=': return self.__rl_pow__(v,i) + if op=='<<=': return v<<i + if op=='>>=': return v>>i + if op=='|=': return v|i + if op=='^=': return v^i + if op=='&=': return v&i + if op=='//=': return v//i + + def __rl_apply__(self, func, args, kwds): + obj = getattr(func,'__self__',None) + if obj: + if isinstance(obj,dict) and func.__name__ in ('pop','setdefault','get', 'popitem'): + self.__rl_is_allowed_name__(args[0]) + return func(*[a for a in self.__rl_getiter__(args)], **{k:v for k,v in kwds.items()}) + + def __rl_args_iter__(self,*args): + if len(args) == 1: + i = args[0] + # Don't double-wrap + if isinstance(i, __rl_SafeIter__): + return i + if not isinstance(i,self.__rl_gen_range__): + return self.__rl_getiter__(i) + return self.__rl_getiter__(iter(*args)) + + def __rl_list__(self,it): + return list(self.__rl_getiter__(it)) + + def __rl_compile__(self, src, fname='<string>', mode="eval", flags=0, inherit=True, visit=None): + names_seen = {} + if not visit: + bcode = compile(src, fname, mode=mode, flags=flags, dont_inherit=not inherit) + else: + astc = ast.parse(src, fname, mode) + if eval_debug>0: + print('pre:\n%s\n'%astFormat(astc)) + astc = visit(astc) + if eval_debug>0: + print('post:\n%s\n'%astFormat(astc)) + bcode = compile(astc, fname, mode=mode) + return bcode, names_seen + + def __rl_safe_eval__(self, expr, g, l, mode, timeout=None, allowed_magic_methods=None, __frame_depth__=3): + bcode, ns = self.__rl_compile__(expr, fname='<string>', mode=mode, flags=0, inherit=True, + visit=UntrustedAstTransformer(nameIsAllowed=self.__rl_is_allowed_name__).visit) + if None in (l,g): + G = sys._getframe(__frame_depth__) + L = G.f_locals.copy() if l is None else l + G = G.f_globals.copy() if g is None else g + else: + G = g + L = l + obi = (G['__builtins__'],) if '__builtins__' in G else False + G['__builtins__'] = self.__rl_builtins__ + self.__rl_limit__ = self.__time_time__() + (timeout if timeout is not None else self.timeout) + if allowed_magic_methods is not None: + self.allowed_magic_methods = ( __allowed_magic_methods__ if allowed_magic_methods==True + else allowed_magic_methods) if allowed_magic_methods else [] + sbi = [].append + bi = self.real_bi + bir = self.bi_replace + for n, r in bir: + sbi(getattr(bi,n)) + setattr(bi,n,r) + try: + return eval(bcode,G,L) + finally: + sbi = sbi.__self__ + for i, (n, r) in enumerate(bir): + setattr(bi,n,sbi[i]) + if obi: + G['__builtins__'] = obi[0] + +class __rl_safe_eval__(object): + '''creates one environment and re-uses it''' + mode = 'eval' + def __init__(self): + self.env = None + + def __call__(self, expr, g=None, l=None, timeout=None, allowed_magic_methods=None): + if not self.env: self.env = __RL_SAFE_ENV__(timeout=timeout, allowed_magic_methods=allowed_magic_methods) + return self.env.__rl_safe_eval__(expr, g, l, self.mode, timeout=timeout, + allowed_magic_methods=allowed_magic_methods, + __frame_depth__=2) + +class __rl_safe_exec__(__rl_safe_eval__): + mode = 'exec' + +rl_safe_exec = __rl_safe_exec__() +rl_safe_eval = __rl_safe_eval__() --- a/src/reportlab/lib/testutils.py +++ b/src/reportlab/lib/testutils.py @@ -148,7 +148,7 @@ class ExtConfigParser(ConfigParser): val = value.replace('\n', '') if self.pat.match(val): - return eval(val) + return eval(val, {__builtins__: None}) else: return value --- a/src/reportlab/lib/utils.py +++ b/src/reportlab/lib/utils.py @@ -1,7 +1,7 @@ -#Copyright ReportLab Europe Ltd. 2000-2017 +#Copyright ReportLab Europe Ltd. 2000-2019 #see license.txt for license details # $URI:$ -__version__='3.3.0' +__version__='3.5.34' __doc__='''Gazillions of miscellaneous internal utility functions''' import os, sys, imp, time, types @@ -9,6 +9,7 @@ from base64 import decodestring as base6 from reportlab import isPy3 from reportlab.lib.logger import warnOnce from reportlab.lib.rltempfile import get_rl_tempfile, get_rl_tempdir, _rl_getuid +from . rl_safe_eval import rl_safe_exec, rl_safe_eval, safer_globals try: import cPickle as pickle @@ -232,6 +233,7 @@ else: return s.encode('latin1') if isinstance(s,unicode) else s def rl_exec(obj, G=None, L=None): + '''this is unsafe''' if G is None: frame = sys._getframe(1) G = frame.f_globals --- a/src/reportlab/platypus/flowables.py +++ b/src/reportlab/platypus/flowables.py @@ -30,7 +30,7 @@ from reportlab.lib.colors import red, gr from reportlab.lib.rl_accel import fp_str from reportlab.lib.enums import TA_LEFT, TA_CENTER, TA_RIGHT, TA_JUSTIFY from reportlab.lib.styles import _baseFontName -from reportlab.lib.utils import strTypes +from reportlab.lib.utils import strTypes, rl_safe_exec from reportlab.pdfbase import pdfutils from reportlab.pdfbase.pdfmetrics import stringWidth from reportlab.rl_config import _FUZZ, overlapAttachedSpace, ignoreContainerActions, listWrapOnFakeWidth @@ -59,11 +59,11 @@ class TraceInfo: ############################################################# class Flowable: """Abstract base class for things to be drawn. Key concepts: - + 1. It knows its size 2. It draws in its own coordinate system (this requires the base API to provide a translate() function. - + """ _fixedWidth = 0 #assume wrap results depend on arguments? _fixedHeight = 0 @@ -265,7 +265,7 @@ def splitLines(lines, maximum_length, sp def splitLine(line_to_split, lines_splitted, maximum_length, \ split_characters, new_line_characters): - # Used to implement the characters added + # Used to implement the characters added #at the beginning of each new line created first_line = True @@ -277,7 +277,7 @@ split_characters, new_line_characters): # Check if the line length still exceeds the maximum length if len(line_to_split) <= maximum_length: - # Return the remaining of the line + # Return the remaining of the line split_index = len(line_to_split) else: # Iterate for each character of the line @@ -286,7 +286,7 @@ split_characters, new_line_characters): # of allowed characters to split on if line_to_split[line_index] in split_characters: split_index = line_index + 1 - + # If the end of the line was reached # with no character to split on if split_index==0: @@ -299,7 +299,7 @@ split_characters, new_line_characters): else: lines_splitted.append(new_line_characters + \ line_to_split[0:split_index]) - + # Remaining text to split line_to_split = line_to_split[split_index:] @@ -307,7 +307,7 @@ class Preformatted(Flowable): """This is like the HTML <PRE> tag. It attempts to display text exactly as you typed it in a fixed width "typewriter" font. By default the line breaks are exactly where you put them, and it will not be wrapped. - You can optionally define a maximum line length and the code will be wrapped; and + You can optionally define a maximum line length and the code will be wrapped; and extra characters to be inserted at the beginning of each wrapped line (e.g. '> '). """ def __init__(self, text, style, bulletText = None, dedent=0, maxLineLength=None, splitChars=None, newLineChars=""): @@ -320,9 +320,9 @@ class Preformatted(Flowable): self.lines = _dedenter(text,dedent) if text and maxLineLength: self.lines = splitLines( - self.lines, - maxLineLength, - splitChars, + self.lines, + maxLineLength, + splitChars, newLineChars ) @@ -728,7 +728,7 @@ class Macro(Flowable): def wrap(self, availWidth, availHeight): return (0,0) def draw(self): - exec(self.command, globals(), {'canvas':self.canv}) + rl_safe_exec(self.command, g=None, l={'canvas':self.canv}) def _nullCallable(*args,**kwds): pass @@ -1390,7 +1390,7 @@ class FrameSplitter(NullDraw): '''When encountered this flowable should either switch directly to nextTemplate if remaining space in the current frame is less than gap+required or it should temporarily modify the current template to have the frames from nextTemplate - that are listed in nextFrames and switch to the first of those frames. + that are listed in nextFrames and switch to the first of those frames. ''' _ZEROSIZE=1 def __init__(self,nextTemplate,nextFrames=[],gap=10,required=72): --- a/tests/test_lib_colors.py +++ b/tests/test_lib_colors.py @@ -32,6 +32,13 @@ def framePage(canvas, title): class ColorTestCase(unittest.TestCase): "" + def ctAssertRaisesRegex(self,ex,regex,func,*args,**kwds): + try: + a = self.assertRaisesRegex + except AttributeError: + a = self.assertRaisesRegexp + return a(ex,regex,func,*args,**kwds) + def test0(self): "Test color2bw function on all named colors." @@ -62,6 +69,15 @@ class ColorTestCase(unittest.TestCase): for thing in allRed: assert colors.toColor(thing) == colors.red,"colors.toColor(%s)-->%s != colors.red(%s)" % (ascii(thing),ascii(colors.toColor(thing)),colors.red) + def test2a(self): + '''attempt to test toColor against simple attacks''' + ofn = outputfile('dumbo.txt') + self.assertRaises(ValueError,colors.toColor,"open(%s,'w').write('dumber and dumber')" % repr(ofn)) + self.assertFalse(os.path.isfile(ofn),"toColor managed to create a file %s :("% repr(ofn)) + self.assertRaises(ValueError,colors.toColor,"red.__class__.__bases__[0].__subclasses__()") + self.assertRaises(ValueError,colors.toColor, + '''(lambda fc=(lambda n: [c for c in ().__class__.__bases__[0].__subclasses__() if c.__name__ == n][0]): fc("function")(fc("code")(0,0,0,0,"KABOOM",(), (),(),"","",0,""),{})())()''') + self.assertEqual(colors.Blacker(colors.red,0.5),colors.toColor("Blacker(red,0.5)")) def test3(self): "Test roundtrip RGB to CMYK conversion." @@ -158,19 +174,19 @@ class ColorTestCase(unittest.TestCase): self.assertEquals(HexColor(b'0xffffff'),Color(1,1,1,1)) self.assertEquals(HexColor(b'0xFFFFFF'),Color(1,1,1,1)) self.assertEquals(HexColor(b'16777215'),Color(1,1,1,1)) - self.assertRaisesRegexp(ValueError,r"invalid literal for int\(\) with base 10:.*ffffff",HexColor,b'ffffff') + self.assertctRaisesRegex(ValueError,r"invalid literal for int\(\) with base 10:.*ffffff",HexColor,b'ffffff') self.assertEquals(HexColor(b'#FFFFFF', htmlOnly=True),Color(1,1,1,1)) - self.assertRaisesRegexp(ValueError,"not a hex string",HexColor,b'0xffffff',htmlOnly=True) - self.assertRaisesRegexp(ValueError,"not a hex string",HexColor,b'16777215',htmlOnly=True) + self.assertctRaisesRegex(ValueError,"not a hex string",HexColor,b'0xffffff',htmlOnly=True) + self.assertctRaisesRegex(ValueError,"not a hex string",HexColor,b'16777215',htmlOnly=True) self.assertEquals(HexColor(u'#ffffff'),Color(1,1,1,1)) self.assertEquals(HexColor(u'#FFFFFF'),Color(1,1,1,1)) self.assertEquals(HexColor(u'0xffffff'),Color(1,1,1,1)) self.assertEquals(HexColor(u'0xFFFFFF'),Color(1,1,1,1)) self.assertEquals(HexColor(u'16777215'),Color(1,1,1,1)) - self.assertRaisesRegexp(ValueError,r"invalid literal for int\(\) with base 10:.*ffffff",HexColor,u'ffffff') + self.assertctRaisesRegex(ValueError,r"invalid literal for int\(\) with base 10:.*ffffff",HexColor,u'ffffff') self.assertEquals(HexColor(u'#FFFFFF', htmlOnly=True),Color(1,1,1,1)) - self.assertRaisesRegexp(ValueError,"not a hex string",HexColor,u'0xffffff',htmlOnly=True) - self.assertRaisesRegexp(ValueError,"not a hex string",HexColor,u'16777215',htmlOnly=True) + self.assertctRaisesRegex(ValueError,"not a hex string",HexColor,u'0xffffff',htmlOnly=True) + self.assertctRaisesRegex(ValueError,"not a hex string",HexColor,u'16777215',htmlOnly=True) def makeSuite(): return makeSuiteForClasses(ColorTestCase) --- /dev/null +++ b/tests/test_lib_rl_safe_eval.py @@ -0,0 +1,167 @@ +#Copyright ReportLab Europe Ltd. 2000-2017 +#see license.txt for license details +"""Tests for reportlab.lib.rl_eval +""" +__version__='3.5.33' +from reportlab.lib.testutils import setOutDir,makeSuiteForClasses, printLocation +setOutDir(__name__) +import os, time, sys +import reportlab +from reportlab import rl_config +import unittest +from reportlab.lib import colors +from reportlab.lib.utils import rl_safe_eval, rl_safe_exec, isPy3, annotateException +from reportlab.lib.rl_safe_eval import BadCode + +testObj = [1,('a','b',2),{'A':1,'B':2.0},"32"] +class TestClass(object): + a = 1 + format = 3 +testInst = TestClass() +def testFunc(bad=False): + return open('/tmp/myfile','r') if bad else testObj + +class SafeEvalTestSequenceMeta(type): + def __new__(cls, name, bases, cdict): + def genTest(kind, expr,**kwds): + def test(self): + getattr(self,kind+'s')(expr,**kwds) + return test + + for kind, _data in ( + ( + 'work', + ( + '[i for i in range(10)]', + '3**4', + '3*"A"', + '3+4', + '(3,4)', + 'SafeEvalTestCase', + ("testObj",dict(g=dict(testObj=testObj))), + "(lambda x,y:[x,y])('a',2)", + "(lambda x:[x])(2)", + "(lambda *args:[args])(2)", + "(lambda y: (lambda f:f(y))(lambda x: x+1))(2)", + "(lambda f: lambda n: (1,(1,(1,(1,f(n-1))))) if n else 1)(lambda x:x)(5)", + "((lambda f: (lambda x: x(x))(lambda y: f(lambda *args: y(y)(*args)))) (lambda f: lambda n: (1,(1,(1,(1,f(n-1))))) if n else 1)(30))", + 'list(range(1000))[1:1000:100]', + 'tuple(range(1000))[1:1000:100]', + 'dict(a=1)["a"]', + 'dict(a=1).setdefault("a",2)', + 'dict(a=1).get("a",2)', + 'dict(a=1).pop("a",2)', + '{"_":1+_ for _ in (1,2)}.pop(1,None)', + '(type(1),type(str),type(testObj),type(TestClass))', + '1 if True else "a"', + '1 if False else "a"', + 'testFunc(bad=False)', + '(min([1]),min(1,2),max([1]),max(1,2))', + '(sum((1,2,3)),sum((1,2,3),-1))', + 'list(enumerate((1,2,3)))', + 'list(zip((1,2,3),("a","b","c")))', + '(hasattr(testInst,"b"),hasattr(testInst,"a"))', + (None if isPy3 else '(reduce(lambda x, y: x+y, [1, 2, 3, 4, 5]),reduce(lambda x, y: x+y, [[1], [2]],[0]))'), + 'list(map(lambda x: (x+13,chr(x)),(1,2,3,4)))', + '(any([1]),any([]),all([]),all([1,None]),all([1,2]))', + '(getattr(testInst,"a"),getattr(testInst,"a",12),getattr(testInst,"xxx",13))', + 'list(sorted([3,4,1,2,0],reverse=True))', + 'list(reversed([3,4,1,2,0]))', + 'list(range(1,10,3))', + None if isPy3 else 'list(xrange(1,10,3))', + '({1,2,3},set([4,5,6]),frozenset([7,8,9]),{i for i in range(1,10,3)})', + '"%s%s" % (1,2)', + None if isPy3 else 'apply(lambda x,y,a="a",b="b": (x,y,a,b), (1,2),dict(a="x",b="y"))', + ) + ), + ( + 'fail', + ( + 'open("/tmp/myfile")', + None if isPy3 else 'file("/tmp/myfile")', + 'SafeEvalTestCase.__module__', + ("testInst.__class__.__bases__[0].__subclasses__()",dict(g=dict(testInst=testInst))), + "10**200**200", + "pow(10,200)", + '__import__("reportlab")', + ('(lambda i: [i for i in ((i, 1) for j in range(1000000))][-1])(1)',dict(timeout=0.1)), + ('[i for i in ((j, 1) for j in range(1000000))][-1]',dict(timeout=0.1)), + ('''((lambda f: (lambda x: x(x))(lambda y: f(lambda *args: y(y)(*args)))) (lambda f: lambda n: (1,(1,(1,(1,f(n-1))))) if n else 1)(300))''', + dict(timeout=0.1,exception=RuntimeError)), + 'dict(__class__=1)["__class__"]', + 'dict(a=1).setdefault("__class__",2)', + 'dict(a=1).get("__class__",2)', + 'dict(a=1).pop("__class__",2)', + '{"__class__":1}["__class__"]', + '{"__class__":1}.setdefault("__class__",2)', + '{"__class__":1}.get("__class__",2)', + '{"__class__":1}.pop("__class__",2)', + '{"_":1 for _ in (1,2)}.pop("__class__",2)', + 'type("Devil",[dict],{__init__:lambda self:self.__class__})', + 'testFunc(bad=True)', + 'getattr(testInst,"__class__",14)', + '"{1}{2}".format(1,2)', + ) + ), + ): + tfmt = 'test_ExpectedTo%s_%%02d' % kind.capitalize() + for i, expr in enumerate(_data): + if expr is None: + test = genTest('skip','') + else: + expr, kwds = expr if isinstance(expr,tuple) else (expr,{}) + test = genTest(kind, expr,**kwds) + cdict[tfmt%i] = test + return type.__new__(cls, name, bases, cdict) + +def addMeta(mcs): + def wrap(cls): + ov = cls.__dict__.copy() + sl = ov.get('__slots__') + if sl is not None: + if isinstance(sl, str): + sl = [sl] + for slv in sl: + ov.pop(slv) + ov.pop('__dict__', None) + ov.pop('__weakref__', None) + if hasattr(cls, '__qualname__'): + ov['__qualname__'] = cls.__qualname__ + return mcs(cls.__name__, cls.__bases__, ov) + return wrap + +@addMeta(SafeEvalTestSequenceMeta) +class SafeEvalTestCase(unittest.TestCase): + def works(self, expr, g=None, l=None): + try: + answer = eval(expr,g,l) + result = rl_safe_eval(expr,g,l) + except: + print('expr=%r' % expr) + annotateException('\nexpr=%r\n' % expr) + self.assertEqual(answer,result,"rl_safe_eval(%r) = %r not expected %r" % (expr,result,answer)) + def skips(self,*args,**kwds): + raise unittest.SkipTest + def fails(self, expr, g=None, l=None, timeout=None, exception=BadCode): + try: + result = rl_safe_eval(expr,g,l,timeout=timeout) + self.assertEqual(True,False,"rl_safe_eval(%r)=%r did not raise %s" % (expr,result,exception.__name__)) + except exception: + return + except: + self.assertEqual(True,False,"rl_safe_eval(%r) raised %s: %s instead of %s" % (expr,sys.exc_info()[0].__name__,str(sys.exc_info()[1]),exception.__name__)) + +GA = 'ga' +class SafeEvalTestBasics(unittest.TestCase): + def test_001(self): + A=3 + self.assertTrue(rl_safe_eval("A==3")) + def test_002(self): + self.assertTrue(rl_safe_eval("GA=='ga'")) + +def makeSuite(): + return makeSuiteForClasses(SafeEvalTestCase,SafeEvalTestBasics) + +if __name__ == "__main__": #noruntests + unittest.TextTestRunner().run(makeSuite()) + printLocation() --- a/tests/test_paragraphs.py +++ b/tests/test_paragraphs.py @@ -4,7 +4,7 @@ __version__='3.3.0' from reportlab.lib.testutils import setOutDir,makeSuiteForClasses, outputfile, printLocation setOutDir(__name__) -import unittest +import unittest, os from reportlab.platypus import Paragraph, SimpleDocTemplate, XBox, Indenter, XPreformatted, PageBreak, Spacer from reportlab.lib.styles import ParagraphStyle from reportlab.lib.units import inch @@ -16,6 +16,7 @@ from reportlab.rl_config import defaultP from reportlab.pdfbase import ttfonts from reportlab.pdfbase import pdfmetrics from reportlab.lib.fonts import addMapping, tt2ps +from reportlab.pdfgen.canvas import Canvas (PAGE_WIDTH, PAGE_HEIGHT) = defaultPageSize @@ -257,6 +258,18 @@ class ParagraphTestCase(unittest.TestCas showBoundary=1) template.build(story, onFirstPage=myFirstPage, onLaterPages=myLaterPages) + + def testMalColor(self): + '''attempt to test may inputs via span etc etc''' + styNormal = ParagraphStyle('normal') + ofn = outputfile('dumbo.txt') + canv = Canvas(outputfile('testMalColor.pdf')) + self.assertRaises(ValueError,Paragraph, '''<span color="toColor(open(%s,'w').write('dumber and dumber'))">AAA</span>''' % ofn, styNormal) + self.assertFalse(os.path.isfile(ofn),"toColor managed to create a file %s :("% repr(ofn)) + self.assertRaises(ValueError,Paragraph, + '''<span color="(lambda fc=(lambda n: [c for c in ().__class__.__bases__[0].__subclasses__() if c.__name__ == n][0]): fc('function')(fc('code')(0,0,0,0,'KABOOM',(), (),(),'','',0,''),{})())()">AAA</span>''',styNormal) + #w, h = p.wrap(5*72,7*72) + #p.drawOn(canv,36,6.5*72) if rtlSupport: def testBidi(self): @@ -439,7 +452,7 @@ class ParagraphTestCase(unittest.TestCas styBI = ParagraphStyle('BI',fontName=fontNameBI) self.assertRaises(ValueError,Paragraph,'aaaa <b><i>bibibi</b></i> ccccc',stySTD) self.assertRaises(ValueError,Paragraph,'AAAA <b><i>BIBIBI</b></i> CCCCC',styBI) - + def makeSuite(): return makeSuiteForClasses(ParagraphTestCase)
Locations
Projects
Search
Status Monitor
Help
OpenBuildService.org
Documentation
API Documentation
Code of Conduct
Contact
Support
@OBShq
Terms
openSUSE Build Service is sponsored by
The Open Build Service is an
openSUSE project
.
Sign Up
Log In
Places
Places
All Projects
Status Monitor