[Zope-Checkins] CVS: Zope/lib/python/RestrictedPython/tests - before_and_after.py:1.1.4.1 class.py:1.1.4.1 lambda.py:1.1.4.1 unpack.py:1.1.4.1 verify.py:1.1.4.1 restricted_module.py:1.10.68.1 security_in_syntax.py:1.4.68.5 testRestrictions.py:1.14.68.2

Tres Seaver tseaver at zope.com
Thu Jan 8 18:34:33 EST 2004


Update of /cvs-repository/Zope/lib/python/RestrictedPython/tests
In directory cvs.zope.org:/tmp/cvs-serv30073/lib/python/RestrictedPython/tests

Modified Files:
      Tag: Zope-2_7-branch
	restricted_module.py security_in_syntax.py testRestrictions.py 
Added Files:
      Tag: Zope-2_7-branch
	before_and_after.py class.py lambda.py unpack.py verify.py 
Log Message:


  Merge security audit work for the 2.7 branch:

    - Collector #1140: setting the access control implementation from
      the configuration file didn't work.  The ZOPE_SECURITY_POLICY
      environment variable is no longer honored.

    - Browsers that do not escape html in query strings such as 
      Internet Explorer 5.5 could potentially send a script tag in a 
      query string to the ZSearch interface for cross-site scripting.

    - FilteredSets (used within TopicIndex) are defined via an expression,
      which was naievely eval'ed.

    - The ZTUtils SimpleTree decompressed tree state data from the 
      request without checking for final size, which could allow for 
      certain types of DoS attacks.

    - Inadequate security assertions on administrative "find" methods 
      could potentially be abused.

    - Some improper security assertions on DTMLDocument objects could 
      potentially allow access to members that should be protected.

    - Class security was not properly intialized for PythonScripts, 
      potentially allowing access to variables that should be protected. 
      It turned out that most of the security assertions were in fact 
      activated as a side effect of other code, but this fix is still 
      appropriate to ensure that all security declarations are properly 
      applied.

    - The dtml-tree tag used an "eval" of user-supplied data; its 
      efforts to prevent abuse were ineffective.

    - XML-RPC marshalling of class instances used the instance 
      __dict__ to marshal the object, and could include attributes 
      prefixed with an underscore name. These attributes are considered 
      private in Zope and should generally not be disclosed.

    - Some property types were stored in a mutable data type (list) which 
      could potentially allow untrusted code to effect changes on those 
      properties without going through appropriate security checks in 
      particular scenarios.

    - Inadequate type checking could allow unicode values passed to 
      RESPONSE.write() to be passed into deeper layers of asyncore, 
      where an exception would eventually be generated at a level that 
      would cause the Zserver main loop to terminate.

    - The variables bound to page templates and Python scripts such as 
      "context" and "container" were not checked adequately, allowing 
      a script to potentially access those objects without ensuring the 
      necessary permissions on the part of the executing user.

    - Iteration over sequences could in some cases fail to check access 
      to an object obtained from the sequence. Subsequent checks (such 
      as for attributes access) of such an object would still be 
      performed, but it should not have been possible to obtain the 
      object in the first place.

    - List and dictionary instance methods such as the get method of 
      dictionary objects were not security aware and could return an 
      object without checking access to that object. Subsequent checks 
      (such as for attributes access) of such an object would still be 
      performed, but it should not have been possible to obtain the 
      object in the first place.

    - Use of 'import as. in Python scripts could potentially rebind 
      names in ways that could be used to avoid appropriate security 
      checks.

    - A number of newer built-ins (min, max, enumerate, iter, sum)
      were either unavailable in untrusted code or did not perform
      adequate security checking.

    - Unpacking via function calls, variable assignment, exception 
      variables and other contexts did not perform adequate security 
      checks, potentially allowing access to objects that should have 
      been protected.

    - DTMLMethods with proxy rights could incorrectly transfer those 
      rights via acquisition when traversing to a parent object.



=== Added File Zope/lib/python/RestrictedPython/tests/before_and_after.py ===
##############################################################################
#
# Copyright (c) 2003 Zope Corporation and Contributors.
# All Rights Reserved.
#
# This software is subject to the provisions of the Zope Public License,
# Version 2.0 (ZPL).  A copy of the ZPL should accompany this distribution.
# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
# FOR A PARTICULAR PURPOSE.
#
##############################################################################
"""Restricted Python transformation examples

This module contains pairs of functions. Each pair has a before and an
after function.  The after function shows the source code equivalent
of the before function after it has been modified by the restricted
compiler.

These examples are actually used in the testRestrictions.py
checkBeforeAndAfter() unit tests, which verifies that the restricted compiler
actually produces the same output as would be output by the normal compiler
for the after function.

$Id: before_and_after.py,v 1.1.4.1 2004/01/08 23:33:57 tseaver Exp $
"""

# getattr

def simple_getattr_before(x):
    return x.y

def simple_getattr_after(x):
    return _getattr_(x, 'y')

# set attr

def simple_setattr_before():
    x.y = "bar"

def simple_setattr_after():
    _write_(x).y = "bar"

# for loop and list comprehensions

def simple_forloop_before(x):
    for x in [1, 2, 3]:
        pass

def simple_forloop_after(x):
    for x in _getiter_([1, 2, 3]):
        pass

def nested_forloop_before(x):
    for x in [1, 2, 3]:
        for y in "abc":
            pass

def nested_forloop_after(x):
    for x in _getiter_([1, 2, 3]):
        for y in _getiter_("abc"):
            pass

def simple_list_comprehension_before():
    x = [y**2 for y in whatever if y > 3]

def simple_list_comprehension_after():
    x = [y**2 for y in _getiter_(whatever) if y > 3]

def nested_list_comprehension_before():
    x = [x**2 + y**2 for x in whatever1 if x >= 0
                     for y in whatever2 if y >= x]

def nested_list_comprehension_after():
    x = [x**2 + y**2 for x in _getiter_(whatever1) if x >= 0
                     for y in _getiter_(whatever2) if y >= x]

# print

def simple_print_before():
    print "foo"

def simple_print_after():
    _print = _print_()
    print >> _print, "foo"

# getitem

def simple_getitem_before():
    return x[0]

def simple_getitem_after():
    return _getitem_(x, 0)

def simple_get_tuple_key_before():
    x = y[1,2]

def simple_get_tuple_key_after():
    x = _getitem_(y, (1,2))

# set item

def simple_setitem_before():
    x[0] = "bar"

def simple_setitem_after():
    _write_(x)[0] = "bar"

# delitem

def simple_delitem_before():
    del x[0]

def simple_delitem_after():
    del _write_(x)[0]

# a collection of function parallels to many of the above

def function_with_print_before():
    def foo():
        print "foo"
        return printed

def function_with_print_after():
    def foo():
        _print = _print_()
        print >> _print, "foo"
        return _print()

def function_with_getattr_before():
    def foo():
        return x.y

def function_with_getattr_after():
    def foo():
        return _getattr_(x, 'y')

def function_with_setattr_before():
    def foo(x):
        x.y = "bar"

def function_with_setattr_after():
    def foo(x):
        _write_(x).y = "bar"

def function_with_getitem_before():
    def foo(x):
        return x[0]

def function_with_getitem_after():
    def foo(x):
        return _getitem_(x, 0)

def function_with_forloop_before():
    def foo():
        for x in [1, 2, 3]:
            pass

def function_with_forloop_after():
    def foo():
        for x in _getiter_([1, 2, 3]):
            pass

# this, and all slices, won't work in these tests because the before code
# parses the slice as a slice object, while the after code can't generate a
# slice object in this way.  The after code as written below
# is parsed as a call to the 'slice' name, not as a slice object.
# XXX solutions?

#def simple_slice_before():
#    x = y[:4]

#def simple_slice_after():
#    _getitem = _getitem_
#    x = _getitem(y, slice(None, 4))

# Assignment stmts in Python can be very complicated.  The "no_unpack"
# test makes sure we're not doing unnecessary rewriting.
def no_unpack_before():
    x = y
    x = [y]
    x = y,
    x = (y, (y, y), [y, (y,)], x, (x, y))
    x = y = z = (x, y, z)

no_unpack_after = no_unpack_before    # that is, should be untouched


# apply() variations.  Native apply() is unsafe because, e.g.,
#
#     def f(a, b, c):
#         whatever
#
#     apply(f, two_element_sequence, dict_with_key_c)
#
# or (different spelling of the same thing)
#
#     f(*two_element_sequence, **dict_with_key_c)
#
# makes the elements of two_element_sequence visible to f via its 'a' and
# 'b' arguments, and the dict_with_key_c['c'] value visible via its 'c'
# argument.  That is, it's a devious way to extract values without going
# thru security checks.

def star_call_before():
    foo(*a)

def star_call_after():
    _apply_(foo, *a)

def star_call_2_before():
    foo(0, *a)

def star_call_2_after():
    _apply_(foo, 0, *a)

def starstar_call_before():
    foo(**d)

def starstar_call_after():
    _apply_(foo, **d)

def star_and_starstar_call_before():
    foo(*a, **d)

def star_and_starstar_call_after():
    _apply_(foo, *a, **d)

def positional_and_star_and_starstar_call_before():
    foo(b, *a, **d)

def positional_and_star_and_starstar_call_after():
    _apply_(foo, b, *a, **d)

def positional_and_defaults_and_star_and_starstar_call_before():
    foo(b, x=y, w=z, *a, **d)

def positional_and_defaults_and_star_and_starstar_call_after():
    _apply_(foo, b, x=y, w=z, *a, **d)

def lambda_with_getattr_in_defaults_before():
    f = lambda x=y.z: x

def lambda_with_getattr_in_defaults_after():
    f = lambda x=_getattr_(y, "z"): x


=== Added File Zope/lib/python/RestrictedPython/tests/class.py ===
class MyClass:

    def set(self, val):
        self.state = val

    def get(self):
        return self.state

x = MyClass()
x.set(12)
x.set(x.get() + 1)
if x.get() != 13:
    raise AssertionError, "expected 13, got %d" % x.get()


=== Added File Zope/lib/python/RestrictedPython/tests/lambda.py ===
f = lambda x, y=1: x + y
if f(2) != 3:
    raise ValueError
if f(2, 2) != 4:
    raise ValueError


=== Added File Zope/lib/python/RestrictedPython/tests/unpack.py ===
# A series of short tests for unpacking sequences.

def u1(L):
    x, y = L
    assert x == 1
    assert y == 2

u1([1,2])
u1((1, 2))

def u1a(L):
    x, y = L
    assert x == '1'
    assert y == '2'

u1a("12")

try:
    u1([1])
except ValueError:
    pass
else:
    raise AssertionError, "expected 'unpack list of wrong size'"

def u2(L):
    x, (a, b), y = L
    assert x == 1
    assert a == 2
    assert b == 3
    assert y == 4

u2([1, [2, 3], 4])
u2((1, (2, 3), 4))

try:
    u2([1, 2, 3])
except TypeError:
    pass
else:
    raise AssertionError, "expected 'iteration over non-sequence'"

def u3((x, y)):
    assert x == 'a'
    assert y == 'b'
    return x, y

u3(('a', 'b'))

def u4(x):
    (a, b), c = d, (e, f) = x
    assert a == 1 and b == 2 and c == (3, 4)
    assert d == (1, 2) and e == 3 and f == 4

u4( ((1, 2), (3, 4)) )

def u5(x):
    try:
        raise TypeError(x)
    # This one is tricky to test, because the first level of unpacking
    # has a TypeError instance.  That's a headache for the test driver.
    except TypeError, [(a, b)]:
        assert a == 42
        assert b == 666

u5([42, 666])

def u6(x):
    expected = 0
    for i, j in x:
        assert i == expected
        expected += 1
        assert j == expected
        expected += 1

u6([[0, 1], [2, 3], [4, 5]])

def u7(x):
    stuff = [i + j for toplevel, in x for i, j in toplevel]
    assert stuff == [3, 7]

u7( ([[[1, 2]]], [[[3, 4]]]) )


=== Added File Zope/lib/python/RestrictedPython/tests/verify.py ===
##############################################################################
#
# Copyright (c) 2003 Zope Corporation and Contributors. All Rights Reserved.
#
# This software is subject to the provisions of the Zope Public License,
# Version 2.0 (ZPL).  A copy of the ZPL should accompany this distribution.
# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
# FOR A PARTICULAR PURPOSE
#
##############################################################################
"""Verify simple properties of bytecode.

Some of the transformations performed by the RestrictionMutator are
tricky.  This module checks the generated bytecode as a way to verify
the correctness of the transformations.  Violations of some
restrictions are obvious from inspection of the bytecode.  For
example, the bytecode should never contain a LOAD_ATTR call, because
all attribute access is performed via the _getattr_() checker
function.
"""

import dis
import types

def verify(code):
    """Verify all code objects reachable from code.

    In particular, traverse into contained code objects in the
    co_consts table.
    """
    verifycode(code)
    for ob in code.co_consts:
        if isinstance(ob, types.CodeType):
            verify(ob)

def verifycode(code):
    try:
        _verifycode(code)
    except:
        dis.dis(code)
        raise

def _verifycode(code):
    line = code.co_firstlineno
    # keep a window of the last three opcodes, with the most recent first
    window = (None, None, None)

    for op in disassemble(code):
        if op.line is not None:
            line = op.line
        if op.opname.endswith("LOAD_ATTR"):
            # All the user code that generates LOAD_ATTR should be
            # rewritten, but the code generated for a list comp
            # includes a LOAD_ATTR to extract the append method.
            if not (op.arg == "append" and
                    window[0].opname == "DUP_TOP" and
                    window[1].opname == "BUILD_LIST"):
                raise ValueError("direct attribute access %s: %s, %s:%d"
                                 % (op.opname, op.arg, co.co_filename, line))
        if op.opname in ("STORE_ATTR", "DEL_ATTR"):
            if not (window[0].opname == "CALL_FUNCTION" and
                    window[2].opname == "LOAD_GLOBAL" and
                    window[2].arg == "_write_"):
                # check that arg is appropriately wrapped
                for i, op in enumerate(window):
                    print i, op.opname, op.arg
                raise ValueError("unguard attribute set/del at %s:%d"
                                 % (code.co_filename, line))
        if op.opname.startswith("UNPACK"):
            # An UNPACK opcode extracts items from iterables, and that's
            # unsafe.  The restricted compiler doesn't remove UNPACK opcodes,
            # but rather *inserts* a call to _getiter_() before each, and
            # that's the pattern we need to see.
            if not (window[0].opname == "CALL_FUNCTION" and
                    window[1].opname == "ROT_TWO" and
                    window[2].opname == "LOAD_GLOBAL" and
                    window[2].arg == "_getiter_"):
                raise ValueError("unguarded unpack sequence at %s:%d" %
                                 (code.co_filename, line))

        # should check CALL_FUNCTION_{VAR,KW,VAR_KW} but that would
        # require a potentially unlimited history.  need to refactor
        # the "window" before I can do that.

        if op.opname == "LOAD_SUBSCR":
            raise ValueError("unguarded index of sequence at %s:%d" %
                             (code.co_filename, line))

        window = (op,) + window[:2]

class Op(object):
    __slots__ = (
        "opname",  # string, name of the opcode
        "argcode", # int, the number of the argument
        "arg",     # any, the object, name, or value of argcode
        "line",    # int, line number or None
        "target",  # boolean, is this op the target of a jump
        "pos",     # int, offset in the bytecode
        )

    def __init__(self, opcode, pos):
        self.opname = dis.opname[opcode]
        self.arg = None
        self.line = None
        self.target = False
        self.pos = pos

def disassemble(co, lasti=-1):
    code = co.co_code
    labels = dis.findlabels(code)
    linestarts = dict(findlinestarts(co))
    n = len(code)
    i = 0
    extended_arg = 0
    free = co.co_cellvars + co.co_freevars
    while i < n:
        op = ord(code[i])
        o = Op(op, i)
        i += 1
        if i in linestarts and i > 0:
            o.line = linestarts[i]
        if i in labels:
            o.target = True
        if op > dis.HAVE_ARGUMENT:
            arg = ord(code[i]) + ord(code[i+1]) * 256 + extended_arg
            extended_arg = 0
            i += 2
            if op == dis.EXTENDED_ARG:
                extended_arg = arg << 16
            o.argcode = arg
            if op in dis.hasconst:
                o.arg = co.co_consts[arg]
            elif op in dis.hasname:
                o.arg = co.co_names[arg]
            elif op in dis.hasjrel:
                o.arg = i + arg
            elif op in dis.haslocal:
                o.arg = co.co_varnames[arg]
            elif op in dis.hascompare:
                o.arg = dis.cmp_op[arg]
            elif op in dis.hasfree:
                o.arg = free[arg]
        yield o

# findlinestarts is copied from Python 2.4's dis module.  The code
# didn't exist in 2.3, but it would be painful to code disassemble()
# without it.
def findlinestarts(code):
    """Find the offsets in a byte code which are start of lines in the source.

    Generate pairs (offset, lineno) as described in Python/compile.c.

    """
    byte_increments = [ord(c) for c in code.co_lnotab[0::2]]
    line_increments = [ord(c) for c in code.co_lnotab[1::2]]

    lastlineno = None
    lineno = code.co_firstlineno
    addr = 0
    for byte_incr, line_incr in zip(byte_increments, line_increments):
        if byte_incr:
            if lineno != lastlineno:
                yield (addr, lineno)
                lastlineno = lineno
            addr += byte_incr
        lineno += line_incr
    if lineno != lastlineno:
        yield (addr, lineno)


=== Zope/lib/python/RestrictedPython/tests/restricted_module.py 1.10 => 1.10.68.1 ===
--- Zope/lib/python/RestrictedPython/tests/restricted_module.py:1.10	Wed Aug 14 17:44:31 2002
+++ Zope/lib/python/RestrictedPython/tests/restricted_module.py	Thu Jan  8 18:33:57 2004
@@ -1,4 +1,5 @@
 from __future__ import nested_scopes
+import sys
 
 def print0():
     print 'Hello, world!',
@@ -27,6 +28,18 @@
         print
     return printed
 
+def try_map():
+    inc = lambda i: i+1
+    x = [1, 2, 3]
+    print map(inc, x),
+    return printed
+
+def try_apply():
+    def f(x, y, z):
+        return x + y + z
+    print f(*(300, 20), **{'z': 1}),
+    return printed
+
 def primes():
     # Somewhat obfuscated code on purpose
     print filter(None,map(lambda y:y*reduce(lambda x,y:x*y!=0,
@@ -57,6 +70,9 @@
     s = 'a'
     s = s[:100] + 'b'
     s += 'c'
+    if sys.version_info >= (2, 3):
+        t = ['l', 'm', 'n', 'o', 'p', 'q']
+        t[1:5:2] = ['n', 'p']
     _ = q
 
     return q['x'] + q['y'] + q['z'] + r[0] + r[1] + r[2] + s
@@ -154,3 +170,7 @@
     def f2():
         return a
     return f1() + f2()
+
+class Classic:
+    pass
+


=== Zope/lib/python/RestrictedPython/tests/security_in_syntax.py 1.4.68.4 => 1.4.68.5 ===
--- Zope/lib/python/RestrictedPython/tests/security_in_syntax.py:1.4.68.4	Tue Jan  6 11:47:50 2004
+++ Zope/lib/python/RestrictedPython/tests/security_in_syntax.py	Thu Jan  8 18:33:57 2004
@@ -50,3 +50,7 @@
         # The name of choice (say, _write) is now assigned to an exception
         # object.  Hard to exploit, but conceivable.
         pass
+  
+def keyword_arg_with_bad_name():
+    def f(okname=1, __badname=2):
+        pass


=== Zope/lib/python/RestrictedPython/tests/testRestrictions.py 1.14.68.1 => 1.14.68.2 ===
--- Zope/lib/python/RestrictedPython/tests/testRestrictions.py:1.14.68.1	Thu Nov  6 11:57:13 2003
+++ Zope/lib/python/RestrictedPython/tests/testRestrictions.py	Thu Jan  8 18:33:57 2004
@@ -1,18 +1,26 @@
-from string import rfind
-import sys, os
-
+import os
+import re
+import sys
 import unittest
+
+# Note that nothing should be imported from AccessControl, and in particular
+# nothing from ZopeGuards.py.  Transformed code may need several wrappers
+# in order to run at all, and most of the production wrappers are defined
+# in ZopeGuards.  But RestrictedPython isn't supposed to depend on
+# AccessControl, so we need to define throwaway wrapper implementations
+# here instead.
+
 from RestrictedPython import compile_restricted, PrintCollector
 from RestrictedPython.Eval import RestrictionCapableEval
-from RestrictedPython.tests import restricted_module, security_in_syntax
-from types import FunctionType
+from RestrictedPython.tests import before_and_after, restricted_module, verify
+from RestrictedPython.RCompile import RModule
 
-if __name__=='__main__':
-    here = os.getcwd()
-else:
-    here = os.path.dirname(__file__)
-    if not here:
-        here = os.getcwd()
+try:
+    __file__
+except NameError:
+    __file__ = os.path.abspath(sys.argv[1])
+_FILEPATH = os.path.abspath( __file__ )
+_HERE = os.path.dirname( _FILEPATH )
 
 def _getindent(line):
     """Returns the indentation level of the given line."""
@@ -42,9 +50,14 @@
     f.close()
     return fn, msg
 
+def get_source(func):
+    """Less silly interface to find_source""" # Sheesh
+    code = func.func_code
+    return find_source(code.co_filename, code)[1]
+
 def create_rmodule():
     global rmodule
-    fn = os.path.join(here, 'restricted_module.py')
+    fn = os.path.join(_HERE, 'restricted_module.py')
     f = open(fn, 'r')
     source = f.read()
     f.close()
@@ -52,7 +65,8 @@
     compile(source, fn, 'exec')
     # Now compile it for real
     code = compile_restricted(source, fn, 'exec')
-    rmodule = {'__builtins__':{'__import__':__import__, 'None':None}}
+    rmodule = {'__builtins__':{'__import__':__import__, 'None':None,
+                               '__name__': 'restricted_module'}}
     builtins = getattr(__builtins__, '__dict__', __builtins__)
     for name in ('map', 'reduce', 'int', 'pow', 'range', 'filter',
                  'len', 'chr', 'ord',
@@ -60,8 +74,6 @@
         rmodule[name] = builtins[name]
     exec code in rmodule
 
-create_rmodule()
-
 class AccessDenied (Exception): pass
 
 DisallowedObject = []
@@ -158,20 +170,35 @@
         _ob = self.__dict__['_ob']
         _ob[lo:hi] = value
 
+# A wrapper for _apply_.
+apply_wrapper_called = []
+def apply_wrapper(func, *args, **kws):
+    apply_wrapper_called.append('yes')
+    return func(*args, **kws)
 
 class RestrictionTests(unittest.TestCase):
     def execFunc(self, name, *args, **kw):
         func = rmodule[name]
+        verify.verify(func.func_code)
         func.func_globals.update({'_getattr_': guarded_getattr,
                                   '_getitem_': guarded_getitem,
                                   '_write_': TestGuard,
-                                  '_print_': PrintCollector})
+                                  '_print_': PrintCollector,
+        # I don't want to write something as involved as ZopeGuard's
+        # SafeIter just for these tests.  Using the builtin list() function
+        # worked OK for everything the tests did at the time this was added,
+        # but may fail in the future.  If Python 2.1 is no longer an
+        # interesting platform then, using 2.2's builtin iter() here should
+        # work for everything.
+                                  '_getiter_': list,
+                                  '_apply_': apply_wrapper,
+                                  })
         return func(*args, **kw)
 
     def checkPrint(self):
         for i in range(2):
             res = self.execFunc('print%s' % i)
-            assert res == 'Hello, world!', res
+            self.assertEqual(res, 'Hello, world!')
 
     def checkPrintToNone(self):
         try:
@@ -180,23 +207,23 @@
             # Passed.  "None" has no "write" attribute.
             pass
         else:
-            assert 0, res
+            self.fail(0, res)
 
     def checkPrintStuff(self):
         res = self.execFunc('printStuff')
-        assert res == 'a b c', res
+        self.assertEqual(res, 'a b c')
 
     def checkPrintLines(self):
         res = self.execFunc('printLines')
-        assert res == '0 1 2\n3 4 5\n6 7 8\n', res
+        self.assertEqual(res,  '0 1 2\n3 4 5\n6 7 8\n')
 
     def checkPrimes(self):
         res = self.execFunc('primes')
-        assert res == '[2, 3, 5, 7, 11, 13, 17, 19]', res
+        self.assertEqual(res, '[2, 3, 5, 7, 11, 13, 17, 19]')
 
     def checkAllowedSimple(self):
         res = self.execFunc('allowed_simple')
-        assert res == 'abcabcabc', res
+        self.assertEqual(res, 'abcabcabc')
 
     def checkAllowedRead(self):
         self.execFunc('allowed_read', RestrictedObject())
@@ -207,6 +234,16 @@
     def checkAllowedArgs(self):
         self.execFunc('allowed_default_args', RestrictedObject())
 
+    def checkTryMap(self):
+        res = self.execFunc('try_map')
+        self.assertEqual(res, "[2, 3, 4]")
+
+    def checkApply(self):
+        del apply_wrapper_called[:]
+        res = self.execFunc('try_apply')
+        self.assertEqual(apply_wrapper_called, ["yes"])
+        self.assertEqual(res, "321")
+
     def checkDenied(self):
         for k in rmodule.keys():
             if k[:6] == 'denied':
@@ -216,12 +253,12 @@
                     # Passed the test
                     pass
                 else:
-                    raise AssertionError, '%s() did not trip security' % k
+                    self.fail('%s() did not trip security' % k)
 
     def checkSyntaxSecurity(self):
         # Ensures that each of the functions in security_in_syntax.py
         # throws a SyntaxError when using compile_restricted.
-        fn = os.path.join(here, 'security_in_syntax.py')
+        fn = os.path.join(_HERE, 'security_in_syntax.py')
         f = open(fn, 'r')
         source = f.read()
         f.close()
@@ -240,19 +277,19 @@
                     # Passed the test.
                     pass
                 else:
-                    raise AssertionError, '%s should not have compiled' % k
+                    self.fail('%s should not have compiled' % k)
 
     def checkOrderOfOperations(self):
         res = self.execFunc('order_of_operations')
-        assert (res == 0), res
+        self.assertEqual(res, 0)
 
     def checkRot13(self):
         res = self.execFunc('rot13', 'Zope is k00l')
-        assert (res == 'Mbcr vf x00y'), res
+        self.assertEqual(res, 'Mbcr vf x00y')
 
     def checkNestedScopes1(self):
         res = self.execFunc('nested_scopes_1')
-        assert (res == 2), res
+        self.assertEqual(res, 2)
 
     def checkUnrestrictedEval(self):
         expr = RestrictionCapableEval("{'a':[m.pop()]}['a'] + [m[0]]")
@@ -260,10 +297,10 @@
         expect = v[:]
         expect.reverse()
         res = expr.eval({'m':v})
-        assert res == expect, res
+        self.assertEqual(res, expect)
         v = [12, 34]
         res = expr(m=v)
-        assert res == expect
+        self.assertEqual(res, expect)
 
     def checkStackSize(self):
         for k, rfunc in rmodule.items():
@@ -275,23 +312,145 @@
                     'should have been at least %d, but was only %d'
                     % (k, ss, rss))
 
+
+    def checkBeforeAndAfter(self):
+        from RestrictedPython.RCompile import RModule
+
+        from compiler import parse
+
+        defre = re.compile(r'def ([_A-Za-z0-9]+)_(after|before)\(')
+
+        beforel = [name for name in before_and_after.__dict__
+                   if name.endswith("_before")]
+
+        for name in beforel:
+            before = getattr(before_and_after, name)
+            before_src = get_source(before)
+            before_src = re.sub(defre, r'def \1(', before_src)
+            rm = RModule(before_src, '')
+            tree_before = rm._get_tree()
+
+            after = getattr(before_and_after, name[:-6]+'after')
+            after_src = get_source(after)
+            after_src = re.sub(defre, r'def \1(', after_src)
+            tree_after = parse(after_src)
+
+            self.assertEqual(str(tree_before), str(tree_after))
+
+            rm.compile()
+            verify.verify(rm.getCode())
+
+    def _compile_file(self, name):
+        path = os.path.join(_HERE, name)
+        f = open(path, "r")
+        source = f.read()
+        f.close()
+
+        co = compile_restricted(source, path, "exec")
+        verify.verify(co)
+        return co
+
+    def checkUnpackSequence(self):
+        co = self._compile_file("unpack.py")
+        calls = []
+        def getiter(seq):
+            calls.append(seq)
+            return list(seq)
+        globals = {"_getiter_": getiter}
+        exec co in globals, {}
+        # The comparison here depends on the exact code that is
+        # contained in unpack.py.
+        # The test doing implicit unpacking in an "except:" clause is
+        # a pain, because there are two levels of unpacking, and the top
+        # level is unpacking the specific TypeError instance constructed
+        # by the test.  We have to worm around that one.
+        ineffable =  "a TypeError instance"
+        expected = [[1, 2],
+                    (1, 2),
+                    "12",
+                    [1],
+                    [1, [2, 3], 4],
+                    [2, 3],
+                    (1, (2, 3), 4),
+                    (2, 3),
+                    [1, 2, 3],
+                    2,
+                    ('a', 'b'),
+                    ((1, 2), (3, 4)), (1, 2),
+                    ((1, 2), (3, 4)), (3, 4),
+                    ineffable, [42, 666],
+                    [[0, 1], [2, 3], [4, 5]], [0, 1], [2, 3], [4, 5],
+                    ([[[1, 2]]], [[[3, 4]]]), [[[1, 2]]], [[1, 2]], [1, 2],
+                                              [[[3, 4]]], [[3, 4]], [3, 4],
+                    ]
+        i = expected.index(ineffable)
+        self.assert_(isinstance(calls[i], TypeError))
+        expected[i] = calls[i]
+        self.assertEqual(calls, expected)
+
+    def checkUnpackSequenceExpression(self):
+        co = compile_restricted("[x for x, y in [(1, 2)]]", "<string>", "eval")
+        verify.verify(co)
+        calls = []
+        def getiter(s):
+            calls.append(s)
+            return list(s)
+        globals = {"_getiter_": getiter}
+        exec co in globals, {}
+        self.assertEqual(calls, [[(1,2)], (1, 2)])
+
+    def checkUnpackSequenceSingle(self):
+        co = compile_restricted("x, y = 1, 2", "<string>", "single")
+        verify.verify(co)
+        calls = []
+        def getiter(s):
+            calls.append(s)
+            return list(s)
+        globals = {"_getiter_": getiter}
+        exec co in globals, {}
+        self.assertEqual(calls, [(1, 2)])
+
+    def checkClass(self):
+        getattr_calls = []
+        setattr_calls = []
+
+        def test_getattr(obj, attr):
+            getattr_calls.append(attr)
+            return getattr(obj, attr)
+
+        def test_setattr(obj):
+            setattr_calls.append(obj.__class__.__name__)
+            return obj
+
+        co = self._compile_file("class.py")
+        globals = {"_getattr_": test_getattr,
+                   "_write_": test_setattr,
+                   }
+        exec co in globals, {}
+        # Note that the getattr calls don't correspond to the method call
+        # order, because the x.set method is fetched before its arguments
+        # are evaluated.
+        self.assertEqual(getattr_calls,
+                         ["set", "set", "get", "state", "get", "state"])
+        self.assertEqual(setattr_calls, ["MyClass", "MyClass"])
+
+    def checkLambda(self):
+        co = self._compile_file("lambda.py")
+        exec co in {}, {}
+
+    def checkSyntaxError(self):
+        err = ("def f(x, y):\n"
+               "    if x, y < 2 + 1:\n"
+               "        return x + y\n"
+               "    else:\n"
+               "        return x - y\n")
+        self.assertRaises(SyntaxError,
+                          compile_restricted, err, "<string>", "exec")
+
+create_rmodule()
+
 def test_suite():
     return unittest.makeSuite(RestrictionTests, 'check')
 
-def main():
-    alltests=test_suite()
-    runner = unittest.TextTestRunner()
-    runner.run(alltests)
-
-def debug():
-    test_suite().debug()
-
-def pdebug():
-    import pdb
-    pdb.run('debug()')
-
 if __name__=='__main__':
-    if len(sys.argv) > 1:
-        globals()[sys.argv[1]]()
-    else:
-        main()
+    unittest.main(defaultTest="test_suite")




More information about the Zope-Checkins mailing list