Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Support
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in / Register
Toggle navigation
Z
Zope
Project overview
Project overview
Details
Activity
Releases
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Issues
0
Issues
0
List
Boards
Labels
Milestones
Merge Requests
0
Merge Requests
0
Analytics
Analytics
Repository
Value Stream
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Create a new issue
Commits
Issue Boards
Open sidebar
Kirill Smelkov
Zope
Commits
bb7b4499
Commit
bb7b4499
authored
Jun 04, 2007
by
Philipp von Weitershausen
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Point to the one and only RestrictedPython we now have (the one that has been set free
as its own project, egg).
parent
ed0ec8b0
Changes
22
Show whitespace changes
Inline
Side-by-side
Showing
22 changed files
with
0 additions
and
2660 deletions
+0
-2660
lib/python/RestrictedPython/Eval.py
lib/python/RestrictedPython/Eval.py
+0
-118
lib/python/RestrictedPython/Guards.py
lib/python/RestrictedPython/Guards.py
+0
-136
lib/python/RestrictedPython/Limits.py
lib/python/RestrictedPython/Limits.py
+0
-46
lib/python/RestrictedPython/MutatingWalker.py
lib/python/RestrictedPython/MutatingWalker.py
+0
-74
lib/python/RestrictedPython/PrintCollector.py
lib/python/RestrictedPython/PrintCollector.py
+0
-23
lib/python/RestrictedPython/RCompile.py
lib/python/RestrictedPython/RCompile.py
+0
-240
lib/python/RestrictedPython/RestrictionMutator.py
lib/python/RestrictedPython/RestrictionMutator.py
+0
-403
lib/python/RestrictedPython/SafeMapping.py
lib/python/RestrictedPython/SafeMapping.py
+0
-44
lib/python/RestrictedPython/SelectCompiler.py
lib/python/RestrictedPython/SelectCompiler.py
+0
-28
lib/python/RestrictedPython/Utilities.py
lib/python/RestrictedPython/Utilities.py
+0
-76
lib/python/RestrictedPython/__init__.py
lib/python/RestrictedPython/__init__.py
+0
-19
lib/python/RestrictedPython/notes.txt
lib/python/RestrictedPython/notes.txt
+0
-95
lib/python/RestrictedPython/tests/__init__.py
lib/python/RestrictedPython/tests/__init__.py
+0
-1
lib/python/RestrictedPython/tests/before_and_after.py
lib/python/RestrictedPython/tests/before_and_after.py
+0
-261
lib/python/RestrictedPython/tests/before_and_after24.py
lib/python/RestrictedPython/tests/before_and_after24.py
+0
-41
lib/python/RestrictedPython/tests/class.py
lib/python/RestrictedPython/tests/class.py
+0
-13
lib/python/RestrictedPython/tests/lambda.py
lib/python/RestrictedPython/tests/lambda.py
+0
-5
lib/python/RestrictedPython/tests/restricted_module.py
lib/python/RestrictedPython/tests/restricted_module.py
+0
-180
lib/python/RestrictedPython/tests/security_in_syntax.py
lib/python/RestrictedPython/tests/security_in_syntax.py
+0
-69
lib/python/RestrictedPython/tests/testRestrictions.py
lib/python/RestrictedPython/tests/testRestrictions.py
+0
-537
lib/python/RestrictedPython/tests/unpack.py
lib/python/RestrictedPython/tests/unpack.py
+0
-81
lib/python/RestrictedPython/tests/verify.py
lib/python/RestrictedPython/tests/verify.py
+0
-170
No files found.
lib/python/RestrictedPython/Eval.py
deleted
100644 → 0
View file @
ed0ec8b0
##############################################################################
#
# Copyright (c) 2002 Zope Corporation and Contributors. All Rights Reserved.
#
# This software is subject to the provisions of the Zope Public License,
# Version 2.1 (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 Expressions
$Id$
"""
__version__
=
'$Revision: 1.6 $'
[
11
:
-
2
]
from
RestrictedPython
import
compile_restricted_eval
from
string
import
translate
,
strip
import
string
nltosp
=
string
.
maketrans
(
'
\
r
\
n
'
,
' '
)
default_guarded_getattr
=
getattr
# No restrictions.
def
default_guarded_getitem
(
ob
,
index
):
# No restrictions.
return
ob
[
index
]
PROFILE
=
0
class
RestrictionCapableEval
:
"""A base class for restricted code."""
globals
=
{
'__builtins__'
:
None
}
rcode
=
None
# restricted
ucode
=
None
# unrestricted
used
=
None
def
__init__
(
self
,
expr
):
"""Create a restricted expression
where:
expr -- a string containing the expression to be evaluated.
"""
expr
=
strip
(
expr
)
self
.
__name__
=
expr
expr
=
translate
(
expr
,
nltosp
)
self
.
expr
=
expr
self
.
prepUnrestrictedCode
()
# Catch syntax errors.
def
prepRestrictedCode
(
self
):
if
self
.
rcode
is
None
:
if
PROFILE
:
from
time
import
clock
start
=
clock
()
co
,
err
,
warn
,
used
=
compile_restricted_eval
(
self
.
expr
,
'<string>'
)
if
PROFILE
:
end
=
clock
()
print
'prepRestrictedCode: %d ms for %s'
%
(
(
end
-
start
)
*
1000
,
`self.expr`
)
if
err
:
raise
SyntaxError
,
err
[
0
]
self
.
used
=
tuple
(
used
.
keys
())
self
.
rcode
=
co
def
prepUnrestrictedCode
(
self
):
if
self
.
ucode
is
None
:
# Use the standard compiler.
co
=
compile
(
self
.
expr
,
'<string>'
,
'eval'
)
if
self
.
used
is
None
:
# Examine the code object, discovering which names
# the expression needs.
names
=
list
(
co
.
co_names
)
used
=
{}
i
=
0
code
=
co
.
co_code
l
=
len
(
code
)
LOAD_NAME
=
101
HAVE_ARGUMENT
=
90
while
(
i
<
l
):
c
=
ord
(
code
[
i
])
if
c
==
LOAD_NAME
:
name
=
names
[
ord
(
code
[
i
+
1
])
+
256
*
ord
(
code
[
i
+
2
])]
used
[
name
]
=
1
i
=
i
+
3
elif
c
>=
HAVE_ARGUMENT
:
i
=
i
+
3
else
:
i
=
i
+
1
self
.
used
=
tuple
(
used
.
keys
())
self
.
ucode
=
co
def
eval
(
self
,
mapping
):
# This default implementation is probably not very useful. :-(
# This is meant to be overridden.
self
.
prepRestrictedCode
()
code
=
self
.
rcode
d
=
{
'_getattr_'
:
default_guarded_getattr
,
'_getitem_'
:
default_guarded_getitem
}
d
.
update
(
self
.
globals
)
has_key
=
d
.
has_key
for
name
in
self
.
used
:
try
:
if
not
has_key
(
name
):
d
[
name
]
=
mapping
[
name
]
except
KeyError
:
# Swallow KeyErrors since the expression
# might not actually need the name. If it
# does need the name, a NameError will occur.
pass
return
eval
(
code
,
d
)
def
__call__
(
self
,
**
kw
):
return
self
.
eval
(
kw
)
lib/python/RestrictedPython/Guards.py
deleted
100644 → 0
View file @
ed0ec8b0
##############################################################################
#
# Copyright (c) 2002 Zope Corporation and Contributors. All Rights Reserved.
#
# This software is subject to the provisions of the Zope Public License,
# Version 2.1 (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
#
##############################################################################
__version__
=
'$Revision: 1.14 $'
[
11
:
-
2
]
import
exceptions
# This tiny set of safe builtins is extended by users of the module.
# AccessControl.ZopeGuards contains a large set of wrappers for builtins.
# DocumentTemplate.DT_UTil contains a few.
safe_builtins
=
{}
for
name
in
[
'False'
,
'None'
,
'True'
,
'abs'
,
'basestring'
,
'bool'
,
'callable'
,
'chr'
,
'cmp'
,
'complex'
,
'divmod'
,
'float'
,
'hash'
,
'hex'
,
'id'
,
'int'
,
'isinstance'
,
'issubclass'
,
'len'
,
'long'
,
'oct'
,
'ord'
,
'pow'
,
'range'
,
'repr'
,
'round'
,
'str'
,
'tuple'
,
'unichr'
,
'unicode'
,
'xrange'
,
'zip'
]:
safe_builtins
[
name
]
=
__builtins__
[
name
]
# Wrappers provided by this module:
# delattr
# setattr
# Wrappers provided by ZopeGuards:
# __import__
# apply
# dict
# enumerate
# filter
# getattr
# hasattr
# iter
# list
# map
# max
# min
# sum
# Builtins that are intentionally disabled
# compile - don't let them produce new code
# dir - a general purpose introspector, probably hard to wrap
# execfile - no direct I/O
# file - no direct I/O
# globals - uncontrolled namespace access
# input - no direct I/O
# locals - uncontrolled namespace access
# open - no direct I/O
# raw_input - no direct I/O
# vars - uncontrolled namespace access
# There are several strings that describe Python. I think there's no
# point to including these, although they are obviously safe:
# copyright, credits, exit, help, license, quit
# Not provided anywhere. Do something about these? Several are
# related to new-style classes, which we are too scared of to support
# <0.3 wink>. coerce, buffer, and reload are esoteric enough that no
# one should care.
# buffer
# classmethod
# coerce
# eval
# intern
# object
# property
# reload
# slice
# staticmethod
# super
# type
for
name
in
dir
(
exceptions
):
if
name
[
0
]
!=
"_"
:
safe_builtins
[
name
]
=
getattr
(
exceptions
,
name
)
def
_write_wrapper
():
# Construct the write wrapper class
def
_handler
(
secattr
,
error_msg
):
# Make a class method.
def
handler
(
self
,
*
args
):
try
:
f
=
getattr
(
self
.
ob
,
secattr
)
except
AttributeError
:
raise
TypeError
,
error_msg
f
(
*
args
)
return
handler
class
Wrapper
:
def
__len__
(
self
):
# Required for slices with negative bounds.
return
len
(
self
.
ob
)
def
__init__
(
self
,
ob
):
self
.
__dict__
[
'ob'
]
=
ob
__setitem__
=
_handler
(
'__guarded_setitem__'
,
'object does not support item or slice assignment'
)
__delitem__
=
_handler
(
'__guarded_delitem__'
,
'object does not support item or slice assignment'
)
__setattr__
=
_handler
(
'__guarded_setattr__'
,
'attribute-less object (assign or del)'
)
__delattr__
=
_handler
(
'__guarded_delattr__'
,
'attribute-less object (assign or del)'
)
return
Wrapper
def
_full_write_guard
():
# Nested scope abuse!
# safetype and Wrapper variables are used by guard()
safetype
=
{
dict
:
True
,
list
:
True
}.
has_key
Wrapper
=
_write_wrapper
()
def
guard
(
ob
):
# Don't bother wrapping simple types, or objects that claim to
# handle their own write security.
if
safetype
(
type
(
ob
))
or
hasattr
(
ob
,
'_guarded_writes'
):
return
ob
# Hand the object to the Wrapper instance, then return the instance.
return
Wrapper
(
ob
)
return
guard
full_write_guard
=
_full_write_guard
()
def
guarded_setattr
(
object
,
name
,
value
):
setattr
(
full_write_guard
(
object
),
name
,
value
)
safe_builtins
[
'setattr'
]
=
guarded_setattr
def
guarded_delattr
(
object
,
name
):
delattr
(
full_write_guard
(
object
),
name
)
safe_builtins
[
'delattr'
]
=
guarded_delattr
lib/python/RestrictedPython/Limits.py
deleted
100644 → 0
View file @
ed0ec8b0
##############################################################################
#
# Copyright (c) 2002 Zope Corporation and Contributors. All Rights Reserved.
#
# This software is subject to the provisions of the Zope Public License,
# Version 2.1 (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
#
##############################################################################
__version__
=
'$Revision: 1.5 $'
[
11
:
-
2
]
limited_builtins
=
{}
def
limited_range
(
iFirst
,
*
args
):
# limited range function from Martijn Pieters
RANGELIMIT
=
1000
if
not
len
(
args
):
iStart
,
iEnd
,
iStep
=
0
,
iFirst
,
1
elif
len
(
args
)
==
1
:
iStart
,
iEnd
,
iStep
=
iFirst
,
args
[
0
],
1
elif
len
(
args
)
==
2
:
iStart
,
iEnd
,
iStep
=
iFirst
,
args
[
0
],
args
[
1
]
else
:
raise
AttributeError
,
'range() requires 1-3 int arguments'
if
iStep
==
0
:
raise
ValueError
,
'zero step for range()'
iLen
=
int
((
iEnd
-
iStart
)
/
iStep
)
if
iLen
<
0
:
iLen
=
0
if
iLen
>=
RANGELIMIT
:
raise
ValueError
,
'range() too large'
return
range
(
iStart
,
iEnd
,
iStep
)
limited_builtins
[
'range'
]
=
limited_range
def
limited_list
(
seq
):
if
isinstance
(
seq
,
str
):
raise
TypeError
,
'cannot convert string to list'
return
list
(
seq
)
limited_builtins
[
'list'
]
=
limited_list
def
limited_tuple
(
seq
):
if
isinstance
(
seq
,
str
):
raise
TypeError
,
'cannot convert string to tuple'
return
tuple
(
seq
)
limited_builtins
[
'tuple'
]
=
limited_tuple
lib/python/RestrictedPython/MutatingWalker.py
deleted
100644 → 0
View file @
ed0ec8b0
##############################################################################
#
# Copyright (c) 2002 Zope Corporation and Contributors. All Rights Reserved.
#
# This software is subject to the provisions of the Zope Public License,
# Version 2.1 (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
#
##############################################################################
__version__
=
'$Revision: 1.6 $'
[
11
:
-
2
]
from
SelectCompiler
import
ast
ListType
=
type
([])
TupleType
=
type
(())
SequenceTypes
=
(
ListType
,
TupleType
)
class
MutatingWalker
:
def
__init__
(
self
,
visitor
):
self
.
visitor
=
visitor
self
.
_cache
=
{}
def
defaultVisitNode
(
self
,
node
,
walker
=
None
,
exclude
=
None
):
for
name
,
child
in
node
.
__dict__
.
items
():
if
exclude
is
not
None
and
name
in
exclude
:
continue
v
=
self
.
dispatchObject
(
child
)
if
v
is
not
child
:
# Replace the node.
node
.
__dict__
[
name
]
=
v
return
node
def
visitSequence
(
self
,
seq
):
res
=
seq
for
idx
in
range
(
len
(
seq
)):
child
=
seq
[
idx
]
v
=
self
.
dispatchObject
(
child
)
if
v
is
not
child
:
# Change the sequence.
if
type
(
res
)
is
ListType
:
res
[
idx
:
idx
+
1
]
=
[
v
]
else
:
res
=
res
[:
idx
]
+
(
v
,)
+
res
[
idx
+
1
:]
return
res
def
dispatchObject
(
self
,
ob
):
'''
Expected to return either ob or something that will take
its place.
'''
if
isinstance
(
ob
,
ast
.
Node
):
return
self
.
dispatchNode
(
ob
)
elif
type
(
ob
)
in
SequenceTypes
:
return
self
.
visitSequence
(
ob
)
else
:
return
ob
def
dispatchNode
(
self
,
node
):
klass
=
node
.
__class__
meth
=
self
.
_cache
.
get
(
klass
,
None
)
if
meth
is
None
:
className
=
klass
.
__name__
meth
=
getattr
(
self
.
visitor
,
'visit'
+
className
,
self
.
defaultVisitNode
)
self
.
_cache
[
klass
]
=
meth
return
meth
(
node
,
self
)
def
walk
(
tree
,
visitor
):
return
MutatingWalker
(
visitor
).
dispatchNode
(
tree
)
lib/python/RestrictedPython/PrintCollector.py
deleted
100644 → 0
View file @
ed0ec8b0
##############################################################################
#
# Copyright (c) 2002 Zope Corporation and Contributors. All Rights Reserved.
#
# This software is subject to the provisions of the Zope Public License,
# Version 2.1 (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
#
##############################################################################
__version__
=
'$Revision: 1.4 $'
[
11
:
-
2
]
class
PrintCollector
:
'''Collect written text, and return it when called.'''
def
__init__
(
self
):
self
.
txt
=
[]
def
write
(
self
,
text
):
self
.
txt
.
append
(
text
)
def
__call__
(
self
):
return
''
.
join
(
self
.
txt
)
lib/python/RestrictedPython/RCompile.py
deleted
100644 → 0
View file @
ed0ec8b0
##############################################################################
#
# Copyright (c) 2002 Zope Corporation and Contributors. All Rights Reserved.
#
# This software is subject to the provisions of the Zope Public License,
# Version 2.1 (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
#
##############################################################################
"""Compiles restricted code using the compiler module from the
Python standard library.
"""
__version__
=
'$Revision: 1.6 $'
[
11
:
-
2
]
from
compiler
import
ast
,
parse
,
misc
,
syntax
,
pycodegen
from
compiler.pycodegen
import
AbstractCompileMode
,
Expression
,
\
Interactive
,
Module
,
ModuleCodeGenerator
,
FunctionCodeGenerator
,
findOp
import
MutatingWalker
from
RestrictionMutator
import
RestrictionMutator
def
niceParse
(
source
,
filename
,
mode
):
try
:
return
parse
(
source
,
mode
)
except
:
# Try to make a clean error message using
# the builtin Python compiler.
try
:
compile
(
source
,
filename
,
mode
)
except
SyntaxError
:
raise
# Some other error occurred.
raise
class
RestrictedCompileMode
(
AbstractCompileMode
):
"""Abstract base class for hooking up custom CodeGenerator."""
# See concrete subclasses below.
def
__init__
(
self
,
source
,
filename
):
if
source
:
source
=
'
\
n
'
.
join
(
source
.
splitlines
())
self
.
rm
=
RestrictionMutator
()
AbstractCompileMode
.
__init__
(
self
,
source
,
filename
)
def
parse
(
self
):
return
niceParse
(
self
.
source
,
self
.
filename
,
self
.
mode
)
def
_get_tree
(
self
):
tree
=
self
.
parse
()
MutatingWalker
.
walk
(
tree
,
self
.
rm
)
if
self
.
rm
.
errors
:
raise
SyntaxError
,
self
.
rm
.
errors
[
0
]
misc
.
set_filename
(
self
.
filename
,
tree
)
syntax
.
check
(
tree
)
return
tree
def
compile
(
self
):
tree
=
self
.
_get_tree
()
gen
=
self
.
CodeGeneratorClass
(
tree
)
self
.
code
=
gen
.
getCode
()
def
compileAndTuplize
(
gen
):
try
:
gen
.
compile
()
except
SyntaxError
,
v
:
return
None
,
(
str
(
v
),),
gen
.
rm
.
warnings
,
gen
.
rm
.
used_names
return
gen
.
getCode
(),
(),
gen
.
rm
.
warnings
,
gen
.
rm
.
used_names
def
compile_restricted_function
(
p
,
body
,
name
,
filename
,
globalize
=
None
):
"""Compiles a restricted code object for a function.
The function can be reconstituted using the 'new' module:
new.function(<code>, <globals>)
The globalize argument, if specified, is a list of variable names to be
treated as globals (code is generated as if each name in the list
appeared in a global statement at the top of the function).
"""
gen
=
RFunction
(
p
,
body
,
name
,
filename
,
globalize
)
return
compileAndTuplize
(
gen
)
def
compile_restricted_exec
(
s
,
filename
=
'<string>'
):
"""Compiles a restricted code suite."""
gen
=
RModule
(
s
,
filename
)
return
compileAndTuplize
(
gen
)
def
compile_restricted_eval
(
s
,
filename
=
'<string>'
):
"""Compiles a restricted expression."""
gen
=
RExpression
(
s
,
filename
)
return
compileAndTuplize
(
gen
)
def
compile_restricted
(
source
,
filename
,
mode
):
"""Replacement for the builtin compile() function."""
if
mode
==
"single"
:
gen
=
RInteractive
(
source
,
filename
)
elif
mode
==
"exec"
:
gen
=
RModule
(
source
,
filename
)
elif
mode
==
"eval"
:
gen
=
RExpression
(
source
,
filename
)
else
:
raise
ValueError
(
"compile_restricted() 3rd arg must be 'exec' or "
"'eval' or 'single'"
)
gen
.
compile
()
return
gen
.
getCode
()
class
RestrictedCodeGenerator
:
"""Mixin for CodeGenerator to replace UNPACK_SEQUENCE bytecodes.
The UNPACK_SEQUENCE opcode is not safe because it extracts
elements from a sequence without using a safe iterator or
making __getitem__ checks.
This code generator replaces use of UNPACK_SEQUENCE with calls to
a function that unpacks the sequence, performes the appropriate
security checks, and returns a simple list.
"""
# Replace the standard code generator for assignments to tuples
# and lists.
def
_gen_safe_unpack_sequence
(
self
,
num
):
# We're at a place where UNPACK_SEQUENCE should be generated, to
# unpack num items. That's a security hole, since it exposes
# individual items from an arbitrary iterable. We don't remove
# the UNPACK_SEQUENCE, but instead insert a call to our _getiter_()
# wrapper first. That applies security checks to each item as
# it's delivered. codegen is (just) a bit messy because the
# iterable is already on the stack, so we have to do a stack swap
# to get things in the right order.
self
.
emit
(
'LOAD_GLOBAL'
,
'_getiter_'
)
self
.
emit
(
'ROT_TWO'
)
self
.
emit
(
'CALL_FUNCTION'
,
1
)
self
.
emit
(
'UNPACK_SEQUENCE'
,
num
)
def
_visitAssSequence
(
self
,
node
):
if
findOp
(
node
)
!=
'OP_DELETE'
:
self
.
_gen_safe_unpack_sequence
(
len
(
node
.
nodes
))
for
child
in
node
.
nodes
:
self
.
visit
(
child
)
visitAssTuple
=
_visitAssSequence
visitAssList
=
_visitAssSequence
# Call to generate code for unpacking nested tuple arguments
# in function calls.
def
unpackSequence
(
self
,
tup
):
self
.
_gen_safe_unpack_sequence
(
len
(
tup
))
for
elt
in
tup
:
if
isinstance
(
elt
,
tuple
):
self
.
unpackSequence
(
elt
)
else
:
self
.
_nameOp
(
'STORE'
,
elt
)
# A collection of code generators that adds the restricted mixin to
# handle unpacking for all the different compilation modes. They
# are defined here (at the end) so that can refer to RestrictedCodeGenerator.
class
RestrictedFunctionCodeGenerator
(
RestrictedCodeGenerator
,
pycodegen
.
FunctionCodeGenerator
):
pass
class
RestrictedExpressionCodeGenerator
(
RestrictedCodeGenerator
,
pycodegen
.
ExpressionCodeGenerator
):
pass
class
RestrictedInteractiveCodeGenerator
(
RestrictedCodeGenerator
,
pycodegen
.
InteractiveCodeGenerator
):
pass
class
RestrictedModuleCodeGenerator
(
RestrictedCodeGenerator
,
pycodegen
.
ModuleCodeGenerator
):
def
initClass
(
self
):
ModuleCodeGenerator
.
initClass
(
self
)
self
.
__class__
.
FunctionGen
=
RestrictedFunctionCodeGenerator
# These subclasses work around the definition of stub compile and mode
# attributes in the common base class AbstractCompileMode. If it
# didn't define new attributes, then the stub code inherited via
# RestrictedCompileMode would override the real definitions in
# Expression.
class
RExpression
(
RestrictedCompileMode
,
Expression
):
mode
=
"eval"
CodeGeneratorClass
=
RestrictedExpressionCodeGenerator
class
RInteractive
(
RestrictedCompileMode
,
Interactive
):
mode
=
"single"
CodeGeneratorClass
=
RestrictedInteractiveCodeGenerator
class
RModule
(
RestrictedCompileMode
,
Module
):
mode
=
"exec"
CodeGeneratorClass
=
RestrictedModuleCodeGenerator
class
RFunction
(
RModule
):
"""A restricted Python function built from parts."""
CodeGeneratorClass
=
RestrictedModuleCodeGenerator
def
__init__
(
self
,
p
,
body
,
name
,
filename
,
globals
):
self
.
params
=
p
if
body
:
body
=
'
\
n
'
.
join
(
body
.
splitlines
())
self
.
body
=
body
self
.
name
=
name
self
.
globals
=
globals
or
[]
RModule
.
__init__
(
self
,
None
,
filename
)
def
parse
(
self
):
# Parse the parameters and body, then combine them.
firstline
=
'def f(%s): pass'
%
self
.
params
tree
=
niceParse
(
firstline
,
'<function parameters>'
,
'exec'
)
f
=
tree
.
node
.
nodes
[
0
]
body_code
=
niceParse
(
self
.
body
,
self
.
filename
,
'exec'
)
# Stitch the body code into the function.
f
.
code
.
nodes
=
body_code
.
node
.
nodes
f
.
name
=
self
.
name
# Look for a docstring, if there are any nodes at all
if
len
(
f
.
code
.
nodes
)
>
0
:
stmt1
=
f
.
code
.
nodes
[
0
]
if
(
isinstance
(
stmt1
,
ast
.
Discard
)
and
isinstance
(
stmt1
.
expr
,
ast
.
Const
)
and
isinstance
(
stmt1
.
expr
.
value
,
str
)):
f
.
doc
=
stmt1
.
expr
.
value
# The caller may specify that certain variables are globals
# so that they can be referenced before a local assignment.
# The only known example is the variables context, container,
# script, traverse_subpath in PythonScripts.
if
self
.
globals
:
f
.
code
.
nodes
.
insert
(
0
,
ast
.
Global
(
self
.
globals
))
return
tree
lib/python/RestrictedPython/RestrictionMutator.py
deleted
100644 → 0
View file @
ed0ec8b0
##############################################################################
#
# Copyright (c) 2002 Zope Corporation and Contributors. All Rights Reserved.
#
# This software is subject to the provisions of the Zope Public License,
# Version 2.1 (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
#
##############################################################################
"""Modify AST to include security checks.
RestrictionMutator modifies a tree produced by
compiler.transformer.Transformer, restricting and enhancing the
code in various ways before sending it to pycodegen.
$Revision: 1.13 $
"""
from
SelectCompiler
import
ast
,
parse
,
OP_ASSIGN
,
OP_DELETE
,
OP_APPLY
# These utility functions allow us to generate AST subtrees without
# line number attributes. These trees can then be inserted into other
# trees without affecting line numbers shown in tracebacks, etc.
def
rmLineno
(
node
):
"""Strip lineno attributes from a code tree."""
if
node
.
__dict__
.
has_key
(
'lineno'
):
del
node
.
lineno
for
child
in
node
.
getChildren
():
if
isinstance
(
child
,
ast
.
Node
):
rmLineno
(
child
)
def
stmtNode
(
txt
):
"""Make a "clean" statement node."""
node
=
parse
(
txt
).
node
.
nodes
[
0
]
rmLineno
(
node
)
return
node
# The security checks are performed by a set of six functions that
# must be provided by the restricted environment.
_apply_name
=
ast
.
Name
(
"_apply_"
)
_getattr_name
=
ast
.
Name
(
"_getattr_"
)
_getitem_name
=
ast
.
Name
(
"_getitem_"
)
_getiter_name
=
ast
.
Name
(
"_getiter_"
)
_print_target_name
=
ast
.
Name
(
"_print"
)
_write_name
=
ast
.
Name
(
"_write_"
)
_inplacevar_name
=
ast
.
Name
(
"_inplacevar_"
)
# Constants.
_None_const
=
ast
.
Const
(
None
)
_write_const
=
ast
.
Const
(
"write"
)
_printed_expr
=
stmtNode
(
"_print()"
).
expr
_print_target_node
=
stmtNode
(
"_print = _print_()"
)
class
FuncInfo
:
print_used
=
False
printed_used
=
False
class
RestrictionMutator
:
def
__init__
(
self
):
self
.
warnings
=
[]
self
.
errors
=
[]
self
.
used_names
=
{}
self
.
funcinfo
=
FuncInfo
()
def
error
(
self
,
node
,
info
):
"""Records a security error discovered during compilation."""
lineno
=
getattr
(
node
,
'lineno'
,
None
)
if
lineno
is
not
None
and
lineno
>
0
:
self
.
errors
.
append
(
'Line %d: %s'
%
(
lineno
,
info
))
else
:
self
.
errors
.
append
(
info
)
def
checkName
(
self
,
node
,
name
):
"""Verifies that a name being assigned is safe.
This is to prevent people from doing things like:
__metatype__ = mytype (opens up metaclasses, a big unknown
in terms of security)
__path__ = foo (could this confuse the import machinery?)
_getattr = somefunc (not very useful, but could open a hole)
Note that assigning a variable is not the only way to assign
a name. def _badname, class _badname, import foo as _badname,
and perhaps other statements assign names. Special case:
'_' is allowed.
"""
if
name
.
startswith
(
"_"
)
and
name
!=
"_"
:
# Note: "_" *is* allowed.
self
.
error
(
node
,
'"%s" is an invalid variable name because'
' it starts with "_"'
%
name
)
if
name
.
endswith
(
'__roles__'
):
self
.
error
(
node
,
'"%s" is an invalid variable name because '
'it ends with "__roles__".'
%
name
)
if
name
==
"printed"
:
self
.
error
(
node
,
'"printed" is a reserved name.'
)
def
checkAttrName
(
self
,
node
):
"""Verifies that an attribute name does not start with _.
As long as guards (security proxies) have underscored names,
this underscore protection is important regardless of the
security policy. Special case: '_' is allowed.
"""
name
=
node
.
attrname
if
name
.
startswith
(
"_"
)
and
name
!=
"_"
:
# Note: "_" *is* allowed.
self
.
error
(
node
,
'"%s" is an invalid attribute name '
'because it starts with "_".'
%
name
)
if
name
.
endswith
(
'__roles__'
):
self
.
error
(
node
,
'"%s" is an invalid attribute name '
'because it ends with "__roles__".'
%
name
)
def
prepBody
(
self
,
body
):
"""Insert code for print at the beginning of the code suite."""
if
self
.
funcinfo
.
print_used
or
self
.
funcinfo
.
printed_used
:
# Add code at top for creating _print_target
body
.
insert
(
0
,
_print_target_node
)
if
not
self
.
funcinfo
.
printed_used
:
self
.
warnings
.
append
(
"Prints, but never reads 'printed' variable."
)
elif
not
self
.
funcinfo
.
print_used
:
self
.
warnings
.
append
(
"Doesn't print, but reads 'printed' variable."
)
def
visitFunction
(
self
,
node
,
walker
):
"""Checks and mutates a function definition.
Checks the name of the function and the argument names using
checkName(). It also calls prepBody() to prepend code to the
beginning of the code suite.
"""
self
.
checkName
(
node
,
node
.
name
)
for
argname
in
node
.
argnames
:
if
isinstance
(
argname
,
str
):
self
.
checkName
(
node
,
argname
)
else
:
for
name
in
argname
:
self
.
checkName
(
node
,
name
)
walker
.
visitSequence
(
node
.
defaults
)
former_funcinfo
=
self
.
funcinfo
self
.
funcinfo
=
FuncInfo
()
node
=
walker
.
defaultVisitNode
(
node
,
exclude
=
(
'defaults'
,))
self
.
prepBody
(
node
.
code
.
nodes
)
self
.
funcinfo
=
former_funcinfo
return
node
def
visitLambda
(
self
,
node
,
walker
):
"""Checks and mutates an anonymous function definition.
Checks the argument names using checkName(). It also calls
prepBody() to prepend code to the beginning of the code suite.
"""
for
argname
in
node
.
argnames
:
self
.
checkName
(
node
,
argname
)
return
walker
.
defaultVisitNode
(
node
)
def
visitPrint
(
self
,
node
,
walker
):
"""Checks and mutates a print statement.
Adds a target to all print statements. 'print foo' becomes
'print >> _print, foo', where _print is the default print
target defined for this scope.
Alternatively, if the untrusted code provides its own target,
we have to check the 'write' method of the target.
'print >> ob, foo' becomes
'print >> (_getattr(ob, 'write') and ob), foo'.
Otherwise, it would be possible to call the write method of
templates and scripts; 'write' happens to be the name of the
method that changes them.
"""
node
=
walker
.
defaultVisitNode
(
node
)
self
.
funcinfo
.
print_used
=
True
if
node
.
dest
is
None
:
node
.
dest
=
_print_target_name
else
:
# Pre-validate access to the "write" attribute.
# "print >> ob, x" becomes
# "print >> (_getattr(ob, 'write') and ob), x"
node
.
dest
=
ast
.
And
([
ast
.
CallFunc
(
_getattr_name
,
[
node
.
dest
,
_write_const
]),
node
.
dest
])
return
node
visitPrintnl
=
visitPrint
def
visitName
(
self
,
node
,
walker
):
"""Prevents access to protected names as defined by checkName().
Also converts use of the name 'printed' to an expression.
"""
if
node
.
name
==
'printed'
:
# Replace name lookup with an expression.
self
.
funcinfo
.
printed_used
=
True
return
_printed_expr
self
.
checkName
(
node
,
node
.
name
)
self
.
used_names
[
node
.
name
]
=
True
return
node
def
visitCallFunc
(
self
,
node
,
walker
):
"""Checks calls with *-args and **-args.
That's a way of spelling apply(), and needs to use our safe
_apply_ instead.
"""
walked
=
walker
.
defaultVisitNode
(
node
)
if
node
.
star_args
is
None
and
node
.
dstar_args
is
None
:
# This is not an extended function call
return
walked
# Otherwise transform foo(a, b, c, d=e, f=g, *args, **kws) into a call
# of _apply_(foo, a, b, c, d=e, f=g, *args, **kws). The interesting
# thing here is that _apply_() is defined with just *args and **kws,
# so it gets Python to collapse all the myriad ways to call functions
# into one manageable form.
#
# From there, _apply_() digs out the first argument of *args (it's the
# function to call), wraps args and kws in guarded accessors, then
# calls the function, returning the value.
# Transform foo(...) to _apply(foo, ...)
walked
.
args
.
insert
(
0
,
walked
.
node
)
walked
.
node
=
_apply_name
return
walked
def
visitAssName
(
self
,
node
,
walker
):
"""Checks a name assignment using checkName()."""
self
.
checkName
(
node
,
node
.
name
)
return
node
def
visitFor
(
self
,
node
,
walker
):
# convert
# for x in expr:
# to
# for x in _getiter(expr):
# # Note that visitListCompFor is the same thing.
#
# Also for list comprehensions:
# [... for x in expr ...]
# to
# [... for x in _getiter(expr) ...]
node
=
walker
.
defaultVisitNode
(
node
)
node
.
list
=
ast
.
CallFunc
(
_getiter_name
,
[
node
.
list
])
return
node
visitListCompFor
=
visitFor
def
visitGenExprFor
(
self
,
node
,
walker
):
# convert
# (... for x in expr ...)
# to
# (... for x in _getiter(expr) ...)
node
=
walker
.
defaultVisitNode
(
node
)
node
.
iter
=
ast
.
CallFunc
(
_getiter_name
,
[
node
.
iter
])
return
node
def
visitGetattr
(
self
,
node
,
walker
):
"""Converts attribute access to a function call.
'foo.bar' becomes '_getattr(foo, "bar")'.
Also prevents augmented assignment of attributes, which would
be difficult to support correctly.
"""
self
.
checkAttrName
(
node
)
node
=
walker
.
defaultVisitNode
(
node
)
if
getattr
(
node
,
'in_aug_assign'
,
False
):
# We're in an augmented assignment
# We might support this later...
self
.
error
(
node
,
'Augmented assignment of '
'attributes is not allowed.'
)
return
ast
.
CallFunc
(
_getattr_name
,
[
node
.
expr
,
ast
.
Const
(
node
.
attrname
)])
def
visitSubscript
(
self
,
node
,
walker
):
"""Checks all kinds of subscripts.
'foo[bar] += baz' is disallowed.
'a = foo[bar, baz]' becomes 'a = _getitem(foo, (bar, baz))'.
'a = foo[bar]' becomes 'a = _getitem(foo, bar)'.
'a = foo[bar:baz]' becomes 'a = _getitem(foo, slice(bar, baz))'.
'a = foo[:baz]' becomes 'a = _getitem(foo, slice(None, baz))'.
'a = foo[bar:]' becomes 'a = _getitem(foo, slice(bar, None))'.
'del foo[bar]' becomes 'del _write(foo)[bar]'.
'foo[bar] = a' becomes '_write(foo)[bar] = a'.
The _write function returns a security proxy.
"""
node
=
walker
.
defaultVisitNode
(
node
)
if
node
.
flags
==
OP_APPLY
:
# Set 'subs' to the node that represents the subscript or slice.
if
getattr
(
node
,
'in_aug_assign'
,
False
):
# We're in an augmented assignment
# We might support this later...
self
.
error
(
node
,
'Augmented assignment of '
'object items and slices is not allowed.'
)
if
hasattr
(
node
,
'subs'
):
# Subscript.
subs
=
node
.
subs
if
len
(
subs
)
>
1
:
# example: ob[1,2]
subs
=
ast
.
Tuple
(
subs
)
else
:
# example: ob[1]
subs
=
subs
[
0
]
else
:
# Slice.
# example: obj[0:2]
lower
=
node
.
lower
if
lower
is
None
:
lower
=
_None_const
upper
=
node
.
upper
if
upper
is
None
:
upper
=
_None_const
subs
=
ast
.
Sliceobj
([
lower
,
upper
])
return
ast
.
CallFunc
(
_getitem_name
,
[
node
.
expr
,
subs
])
elif
node
.
flags
in
(
OP_DELETE
,
OP_ASSIGN
):
# set or remove subscript or slice
node
.
expr
=
ast
.
CallFunc
(
_write_name
,
[
node
.
expr
])
return
node
visitSlice
=
visitSubscript
def
visitAssAttr
(
self
,
node
,
walker
):
"""Checks and mutates attribute assignment.
'a.b = c' becomes '_write(a).b = c'.
The _write function returns a security proxy.
"""
self
.
checkAttrName
(
node
)
node
=
walker
.
defaultVisitNode
(
node
)
node
.
expr
=
ast
.
CallFunc
(
_write_name
,
[
node
.
expr
])
return
node
def
visitExec
(
self
,
node
,
walker
):
self
.
error
(
node
,
'Exec statements are not allowed.'
)
def
visitYield
(
self
,
node
,
walker
):
self
.
error
(
node
,
'Yield statements are not allowed.'
)
def
visitClass
(
self
,
node
,
walker
):
"""Checks the name of a class using checkName().
Should classes be allowed at all? They don't cause security
issues, but they aren't very useful either since untrusted
code can't assign instance attributes.
"""
self
.
checkName
(
node
,
node
.
name
)
return
walker
.
defaultVisitNode
(
node
)
def
visitModule
(
self
,
node
,
walker
):
"""Adds prep code at module scope.
Zope doesn't make use of this. The body of Python scripts is
always at function scope.
"""
node
=
walker
.
defaultVisitNode
(
node
)
self
.
prepBody
(
node
.
node
.
nodes
)
return
node
def
visitAugAssign
(
self
,
node
,
walker
):
"""Makes a note that augmented assignment is in use.
Note that although augmented assignment of attributes and
subscripts is disallowed, augmented assignment of names (such
as 'n += 1') is allowed.
This could be a problem if untrusted code got access to a
mutable database object that supports augmented assignment.
"""
if
node
.
node
.
__class__
.
__name__
==
'Name'
:
node
=
walker
.
defaultVisitNode
(
node
)
newnode
=
ast
.
Assign
(
[
ast
.
AssName
(
node
.
node
.
name
,
OP_ASSIGN
)],
ast
.
CallFunc
(
_inplacevar_name
,
[
ast
.
Const
(
node
.
op
),
ast
.
Name
(
node
.
node
.
name
),
node
.
expr
,
]
),
)
newnode
.
lineno
=
node
.
lineno
return
newnode
else
:
node
.
node
.
in_aug_assign
=
True
return
walker
.
defaultVisitNode
(
node
)
def
visitImport
(
self
,
node
,
walker
):
"""Checks names imported using checkName()."""
for
name
,
asname
in
node
.
names
:
self
.
checkName
(
node
,
name
)
if
asname
:
self
.
checkName
(
node
,
asname
)
return
node
lib/python/RestrictedPython/SafeMapping.py
deleted
100644 → 0
View file @
ed0ec8b0
##############################################################################
#
# Copyright (c) 2002 Zope Corporation and Contributors. All Rights Reserved.
#
# This software is subject to the provisions of the Zope Public License,
# Version 2.1 (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
#
##############################################################################
__doc__
=
'''Read-only Mapping class based on MultiMapping
$Id$'''
__version__
=
'$Revision: 1.3 $'
[
11
:
-
2
]
from
MultiMapping
import
MultiMapping
_marker
=
[]
class
SafeMapping
(
MultiMapping
):
'''Mapping with security declarations and limited method exposure.
Since it subclasses MultiMapping, this class can be used to wrap
one or more mapping objects. Restricted Python code will not be
able to mutate the SafeMapping or the wrapped mappings, but will be
able to read any value.
'''
__allow_access_to_unprotected_subobjects__
=
1
push
=
pop
=
None
def
_push
(
self
,
ob
):
MultiMapping
.
push
(
self
,
ob
)
def
_pop
(
self
,
*
args
):
if
args
:
return
apply
(
MultiMapping
.
pop
,
(
self
,)
+
args
)
else
:
return
MultiMapping
.
pop
(
self
)
def
has_get
(
self
,
key
):
v
=
self
.
get
(
key
,
_marker
)
if
v
is
_marker
:
return
0
,
None
else
:
return
1
,
v
lib/python/RestrictedPython/SelectCompiler.py
deleted
100644 → 0
View file @
ed0ec8b0
##############################################################################
#
# Copyright (c) 2002 Zope Corporation and Contributors. All Rights Reserved.
#
# This software is subject to the provisions of the Zope Public License,
# Version 2.1 (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
#
##############################################################################
"""Compiler selector.
$Id$
"""
# Use the compiler from the standard library.
import
compiler
from
compiler
import
ast
from
compiler.transformer
import
parse
from
compiler.consts
import
OP_ASSIGN
,
OP_DELETE
,
OP_APPLY
from
RCompile
import
\
compile_restricted
,
\
compile_restricted_function
,
\
compile_restricted_exec
,
\
compile_restricted_eval
lib/python/RestrictedPython/Utilities.py
deleted
100644 → 0
View file @
ed0ec8b0
##############################################################################
#
# Copyright (c) 2002 Zope Corporation and Contributors. All Rights Reserved.
#
# This software is subject to the provisions of the Zope Public License,
# Version 2.1 (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
#
##############################################################################
__version__
=
'$Revision: 1.7 $'
[
11
:
-
2
]
import
string
,
math
,
random
import
DocumentTemplate.sequence
from
DateTime.DateTime
import
DateTime
import
sets
utility_builtins
=
{}
utility_builtins
[
'string'
]
=
string
utility_builtins
[
'math'
]
=
math
utility_builtins
[
'random'
]
=
random
utility_builtins
[
'sequence'
]
=
DocumentTemplate
.
sequence
utility_builtins
[
'DateTime'
]
=
DateTime
utility_builtins
[
'sets'
]
=
sets
def
same_type
(
arg1
,
*
args
):
'''Compares the class or type of two or more objects.'''
t
=
getattr
(
arg1
,
'__class__'
,
type
(
arg1
))
for
arg
in
args
:
if
getattr
(
arg
,
'__class__'
,
type
(
arg
))
is
not
t
:
return
0
return
1
utility_builtins
[
'same_type'
]
=
same_type
def
test
(
*
args
):
l
=
len
(
args
)
for
i
in
range
(
1
,
l
,
2
):
if
args
[
i
-
1
]:
return
args
[
i
]
if
l
%
2
:
return
args
[
-
1
]
utility_builtins
[
'test'
]
=
test
def
reorder
(
s
,
with
=
None
,
without
=
()):
# s, with, and without are sequences treated as sets.
# The result is subtract(intersect(s, with), without),
# unless with is None, in which case it is subtract(s, without).
if
with
is
None
:
with
=
s
d
=
{}
tt
=
type
(())
for
i
in
s
:
if
type
(
i
)
is
tt
and
len
(
i
)
==
2
:
k
,
v
=
i
else
:
k
=
v
=
i
d
[
k
]
=
v
r
=
[]
a
=
r
.
append
h
=
d
.
has_key
for
i
in
without
:
if
type
(
i
)
is
tt
and
len
(
i
)
==
2
:
k
,
v
=
i
else
:
k
=
v
=
i
if
h
(
k
):
del
d
[
k
]
for
i
in
with
:
if
type
(
i
)
is
tt
and
len
(
i
)
==
2
:
k
,
v
=
i
else
:
k
=
v
=
i
if
h
(
k
):
a
((
k
,
d
[
k
]))
del
d
[
k
]
return
r
utility_builtins
[
'reorder'
]
=
reorder
lib/python/RestrictedPython/__init__.py
deleted
100644 → 0
View file @
ed0ec8b0
##############################################################################
#
# Copyright (c) 2002 Zope Corporation and Contributors. All Rights Reserved.
#
# This software is subject to the provisions of the Zope Public License,
# Version 2.1 (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
#
##############################################################################
'''
RestrictedPython package.
$Id$
'''
from
SelectCompiler
import
*
from
PrintCollector
import
PrintCollector
lib/python/RestrictedPython/notes.txt
deleted
100644 → 0
View file @
ed0ec8b0
How it works
============
Every time I see this code, I have to relearn it. These notes will
hopefully make this a little easier. :)
- The important module is RCompile. The entry points are the
compile_restricted_* functions.
+ compile_restricted_function is used by Python scripts.
+ compile_restricted_eval is used by ZPT
and by DTML indirectly through Eval.RestrictionCapableEval.
- OK, so lets see how this works by following the logic of
compile_restricted_eval.
- First, we create an RExpression, passing the source and a
"file name", to be used in tracebacks.
Now, an RExpression is just:
+ a subclass of RestrictedCompileMode and Expression.
Expression is a subclass of AbstractCompileMode that sets it's
mode to 'eval' and everided compile. Sigh.
+ RestrictedCompileMode is a subclass of AbstractCompileMode
that changes a bunch of things. :) These include compile, so we
can ignore the compile we got from Expression. It would have
been simpler to just set the dang mode in RExpression. Sigh.
RestrictedCompileMode seem to be the interestng base class. I
assume it implements the interesting functionality. We'll see
below...
- Next, we call compileAndTuplize.
+ This calls compile on the RExpression. It has an error
handler that does something that I hope I don't care about. :)
+ It then calls the genCode method on the RExpression. This is
boring, so we'll not worry about it.
- The compile method provided by RestrictedCompileMode is
interesting.
+ First it calls _get_tree.
* It uses compiler.parse to parse the source
* it uses MutatingWalker.walk to mutaate the tree using the
RestrictedCompileMode's 'rm' attr, which is a
RestrictionMutator.
The RestrictionMutator has the recipies for mutating the parse
tree. (Note, for comparison, that Zope3's
zope.security.untrustedpython.rcompile module an alternative
RestrictionMutator that provides a much smaller set of
changes.)
A mutator has visit method for different kinds of AST
nodes. These visit methods may mutate nodes or return new
nodes that replace the originally visited nodes. There is a
default visitor that visits a node's children and replaces the
children who's visitors returned new nodes.
The walk function just calls the visitor for the root node of
the given tree. Note _get_tree ignores the walk return value,
thus assuming that the visitor for the root node doesn't
return a new node. This is a theoretical bug that we can
ignore.
+ Second, it generates the code. This too is boring.
- So this seems simple enough. ;) When we want to add a check, we
need to update or add a visit function in RestrictionMutator.
How does a visit function work.
- First, we usually call walker.defaultVisitNode(node). This
transforms the node's child nodes.
- Then we hack the node, or possibly return the node. To do this, we
have to know how the node works.
- The hack often involved changing the code to call some checker
function. These have names like _name_. These are names that
would be illegal in the input source.
If this is a new function, we have to provide it in
AccessControl.ZopeGuards._safe_globals.
- Don't forget to add a test case to tests.before_and_after.
lib/python/RestrictedPython/tests/__init__.py
deleted
100644 → 0
View file @
ed0ec8b0
'''Python package.'''
lib/python/RestrictedPython/tests/before_and_after.py
deleted
100644 → 0
View file @
ed0ec8b0
##############################################################################
#
# Copyright (c) 2003 Zope Corporation and Contributors.
# All Rights Reserved.
#
# This software is subject to the provisions of the Zope Public License,
# Version 2.1 (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$
"""
# 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
# augmented operators
# Note that we don't have to worry about item, attr, or slice assignment,
# as they are disallowed. Yay!
## def inplace_id_add_before():
## x += y+z
## def inplace_id_add_after():
## x = _inplacevar_('+=', x, y+z)
lib/python/RestrictedPython/tests/before_and_after24.py
deleted
100644 → 0
View file @
ed0ec8b0
##############################################################################
#
# Copyright (c) 2003 Zope Corporation and Contributors.
# All Rights Reserved.
#
# This software is subject to the provisions of the Zope Public License,
# Version 2.1 (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$
"""
def
simple_generator_expression_before
():
x
=
(
y
**
2
for
y
in
whatever
if
y
>
3
)
def
simple_generator_expression_after
():
x
=
(
y
**
2
for
y
in
_getiter_
(
whatever
)
if
y
>
3
)
def
nested_generator_expression_before
():
x
=
(
x
**
2
+
y
**
2
for
x
in
whatever1
if
x
>=
0
for
y
in
whatever2
if
y
>=
x
)
def
nested_generator_expression_after
():
x
=
(
x
**
2
+
y
**
2
for
x
in
_getiter_
(
whatever1
)
if
x
>=
0
for
y
in
_getiter_
(
whatever2
)
if
y
>=
x
)
lib/python/RestrictedPython/tests/class.py
deleted
100644 → 0
View file @
ed0ec8b0
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
()
lib/python/RestrictedPython/tests/lambda.py
deleted
100644 → 0
View file @
ed0ec8b0
f
=
lambda
x
,
y
=
1
:
x
+
y
if
f
(
2
)
!=
3
:
raise
ValueError
if
f
(
2
,
2
)
!=
4
:
raise
ValueError
lib/python/RestrictedPython/tests/restricted_module.py
deleted
100644 → 0
View file @
ed0ec8b0
from
__future__
import
nested_scopes
import
sys
def
print0
():
print
'Hello, world!'
,
return
printed
def
print1
():
print
'Hello,'
,
print
'world!'
,
return
printed
def
printStuff
():
print
'a'
,
'b'
,
'c'
,
return
printed
def
printToNone
():
x
=
None
print
>>
x
,
'Hello, world!'
,
return
printed
def
printLines
():
# This failed before Zope 2.4.0a2
r
=
range
(
3
)
for
n
in
r
:
for
m
in
r
:
print
m
+
n
*
len
(
r
),
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
try_inplace
():
x
=
1
x
+=
3
def
primes
():
# Somewhat obfuscated code on purpose
print
filter
(
None
,
map
(
lambda
y
:
y
*
reduce
(
lambda
x
,
y
:
x
*
y
!=
0
,
map
(
lambda
x
,
y
=
y
:
y
%
x
,
range
(
2
,
int
(
pow
(
y
,
0.5
)
+
1
))),
1
),
range
(
2
,
20
))),
return
printed
def
allowed_read
(
ob
):
print
ob
.
allowed
print
ob
.
s
print
ob
[
0
]
print
ob
[
2
]
print
ob
[
3
:
-
1
]
print
len
(
ob
)
return
printed
def
allowed_default_args
(
ob
):
def
f
(
a
=
ob
.
allowed
,
s
=
ob
.
s
):
return
a
,
s
def
allowed_simple
():
q
=
{
'x'
:
'a'
}
q
[
'y'
]
=
'b'
q
.
update
({
'z'
:
'c'
})
r
=
[
'a'
]
r
.
append
(
'b'
)
r
[
2
:
2
]
=
[
'c'
]
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
def
allowed_write
(
ob
):
ob
.
writeable
=
1
#ob.writeable += 1
[
1
for
ob
.
writeable
in
1
,
2
]
ob
[
'safe'
]
=
2
#ob['safe'] += 2
[
1
for
ob
[
'safe'
]
in
1
,
2
]
def
denied_print
(
ob
):
print
>>
ob
,
'Hello, world!'
,
def
denied_getattr
(
ob
):
#ob.disallowed += 1
ob
.
disallowed
=
1
return
ob
.
disallowed
def
denied_default_args
(
ob
):
def
f
(
d
=
ob
.
disallowed
):
return
d
def
denied_setattr
(
ob
):
ob
.
allowed
=
-
1
def
denied_setattr2
(
ob
):
#ob.allowed += -1
ob
.
allowed
=
-
1
def
denied_setattr3
(
ob
):
[
1
for
ob
.
allowed
in
1
,
2
]
def
denied_getitem
(
ob
):
ob
[
1
]
def
denied_getitem2
(
ob
):
#ob[1] += 1
ob
[
1
]
def
denied_setitem
(
ob
):
ob
[
'x'
]
=
2
def
denied_setitem2
(
ob
):
#ob[0] += 2
ob
[
'x'
]
=
2
def
denied_setitem3
(
ob
):
[
1
for
ob
[
'x'
]
in
1
,
2
]
def
denied_setslice
(
ob
):
ob
[
0
:
1
]
=
'a'
def
denied_setslice2
(
ob
):
#ob[0:1] += 'a'
ob
[
0
:
1
]
=
'a'
def
denied_setslice3
(
ob
):
[
1
for
ob
[
0
:
1
]
in
1
,
2
]
##def strange_attribute():
## # If a guard has attributes with names that don't start with an
## # underscore, those attributes appear to be an attribute of
## # anything.
## return [].attribute_of_anything
def
order_of_operations
():
return
3
*
4
*
-
2
+
2
*
12
def
rot13
(
ss
):
mapping
=
{}
orda
=
ord
(
'a'
)
ordA
=
ord
(
'A'
)
for
n
in
range
(
13
):
c1
=
chr
(
orda
+
n
)
c2
=
chr
(
orda
+
n
+
13
)
c3
=
chr
(
ordA
+
n
)
c4
=
chr
(
ordA
+
n
+
13
)
mapping
[
c1
]
=
c2
mapping
[
c2
]
=
c1
mapping
[
c3
]
=
c4
mapping
[
c4
]
=
c3
del
c1
,
c2
,
c3
,
c4
,
orda
,
ordA
res
=
''
for
c
in
ss
:
res
=
res
+
mapping
.
get
(
c
,
c
)
return
res
def
nested_scopes_1
():
# Fails if 'a' is consumed by the first function.
a
=
1
def
f1
():
return
a
def
f2
():
return
a
return
f1
()
+
f2
()
class
Classic
:
pass
lib/python/RestrictedPython/tests/security_in_syntax.py
deleted
100644 → 0
View file @
ed0ec8b0
# These are all supposed to raise a SyntaxError when using
# compile_restricted() but not when using compile().
# Each function in this module is compiled using compile_restricted().
from
__future__
import
generators
def
overrideGuardWithFunction
():
def
_getattr
(
o
):
return
o
def
overrideGuardWithLambda
():
lambda
o
,
_getattr
=
None
:
o
def
overrideGuardWithClass
():
class
_getattr
:
pass
def
overrideGuardWithName
():
_getattr
=
None
def
overrideGuardWithArgument
():
def
f
(
_getattr
=
None
):
pass
def
reserved_names
():
printed
=
''
def
bad_name
():
__
=
12
def
bad_attr
():
some_ob
.
_some_attr
=
15
def
no_exec
():
exec
'q = 1'
def
no_yield
():
yield
42
def
check_getattr_in_lambda
(
arg
=
lambda
_getattr
=
(
lambda
ob
,
name
:
name
):
_getattr
):
42
def
import_as_bad_name
():
import
os
as
_leading_underscore
def
except_using_bad_name
():
try
:
foo
except
NameError
,
_leading_underscore
:
# 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
def
no_augmeneted_assignment_to_sub
():
a
[
b
]
+=
c
def
no_augmeneted_assignment_to_attr
():
a
.
b
+=
c
def
no_augmeneted_assignment_to_slice
():
a
[
x
:
y
]
+=
c
def
no_augmeneted_assignment_to_slice2
():
a
[
x
:
y
:
z
]
+=
c
lib/python/RestrictedPython/tests/testRestrictions.py
deleted
100644 → 0
View file @
ed0ec8b0
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
,
verify
from
RestrictedPython.RCompile
import
RModule
,
RFunction
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."""
indent
=
0
for
c
in
line
:
if
c
==
' '
:
indent
=
indent
+
1
elif
c
==
'
\
t
'
:
indent
=
indent
+
8
else
:
break
return
indent
def
find_source
(
fn
,
func
):
"""Given a func_code object, this function tries to find and return
the python source code of the function. Originally written by
Harm van der Heijden (H.v.d.Heijden@phys.tue.nl)"""
f
=
open
(
fn
,
"r"
)
for
i
in
range
(
func
.
co_firstlineno
):
line
=
f
.
readline
()
ind
=
_getindent
(
line
)
msg
=
""
while
line
:
msg
=
msg
+
line
line
=
f
.
readline
()
# the following should be <= ind, but then we get
# confused by multiline docstrings. Using == works most of
# the time... but not always!
if
_getindent
(
line
)
==
ind
:
break
f
.
close
()
return
fn
,
msg
def
get_source
(
func
):
"""Less silly interface to find_source"""
file
=
func
.
func_globals
[
'__file__'
]
if
file
.
endswith
(
'.pyc'
):
file
=
file
[:
-
1
]
source
=
find_source
(
file
,
func
.
func_code
)[
1
]
assert
source
.
strip
(),
"Source should not be empty!"
return
source
def
create_rmodule
():
global
rmodule
fn
=
os
.
path
.
join
(
_HERE
,
'restricted_module.py'
)
f
=
open
(
fn
,
'r'
)
source
=
f
.
read
()
f
.
close
()
# Sanity check
compile
(
source
,
fn
,
'exec'
)
# Now compile it for real
code
=
compile_restricted
(
source
,
fn
,
'exec'
)
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'
,
):
rmodule
[
name
]
=
builtins
[
name
]
exec
code
in
rmodule
class
AccessDenied
(
Exception
):
pass
DisallowedObject
=
[]
class
RestrictedObject
:
disallowed
=
DisallowedObject
allowed
=
1
_
=
2
__
=
3
_some_attr
=
4
__some_other_attr__
=
5
s
=
'Another day, another test...'
__writeable_attrs__
=
(
'writeable'
,)
def
__getitem__
(
self
,
idx
):
if
idx
==
'protected'
:
raise
AccessDenied
elif
idx
==
0
or
idx
==
'safe'
:
return
1
elif
idx
==
1
:
return
DisallowedObject
else
:
return
self
.
s
[
idx
]
def
__getslice__
(
self
,
lo
,
hi
):
return
self
.
s
[
lo
:
hi
]
def
__len__
(
self
):
return
len
(
self
.
s
)
def
__setitem__
(
self
,
idx
,
v
):
if
idx
==
'safe'
:
self
.
safe
=
v
else
:
raise
AccessDenied
def
__setslice__
(
self
,
lo
,
hi
,
value
):
raise
AccessDenied
write
=
DisallowedObject
def
guarded_getattr
(
ob
,
name
):
v
=
getattr
(
ob
,
name
)
if
v
is
DisallowedObject
:
raise
AccessDenied
return
v
SliceType
=
type
(
slice
(
0
))
def
guarded_getitem
(
ob
,
index
):
if
type
(
index
)
is
SliceType
and
index
.
step
is
None
:
start
=
index
.
start
stop
=
index
.
stop
if
start
is
None
:
start
=
0
if
stop
is
None
:
v
=
ob
[
start
:]
else
:
v
=
ob
[
start
:
stop
]
else
:
v
=
ob
[
index
]
if
v
is
DisallowedObject
:
raise
AccessDenied
return
v
def
minimal_import
(
name
,
_globals
,
_locals
,
names
):
if
name
!=
"__future__"
:
raise
ValueError
,
"Only future imports are allowed"
import
__future__
return
__future__
class
TestGuard
:
'''A guard class'''
def
__init__
(
self
,
_ob
,
write
=
None
):
self
.
__dict__
[
'_ob'
]
=
_ob
# Write guard methods
def
__setattr__
(
self
,
name
,
value
):
_ob
=
self
.
__dict__
[
'_ob'
]
writeable
=
getattr
(
_ob
,
'__writeable_attrs__'
,
())
if
name
not
in
writeable
:
raise
AccessDenied
if
name
[:
5
]
==
'func_'
:
raise
AccessDenied
setattr
(
_ob
,
name
,
value
)
def
__setitem__
(
self
,
index
,
value
):
_ob
=
self
.
__dict__
[
'_ob'
]
_ob
[
index
]
=
value
def
__setslice__
(
self
,
lo
,
hi
,
value
):
_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
)
inplacevar_wrapper_called
=
{}
def
inplacevar_wrapper
(
op
,
x
,
y
):
inplacevar_wrapper_called
[
op
]
=
x
,
y
# This is really lame. But it's just a test. :)
globs
=
{
'x'
:
x
,
'y'
:
y
}
exec
'x'
+
op
+
'y'
in
globs
return
globs
[
'x'
]
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
,
# 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
,
'_inplacevar_'
:
inplacevar_wrapper
,
})
return
func
(
*
args
,
**
kw
)
def
checkPrint
(
self
):
for
i
in
range
(
2
):
res
=
self
.
execFunc
(
'print%s'
%
i
)
self
.
assertEqual
(
res
,
'Hello, world!'
)
def
checkPrintToNone
(
self
):
try
:
res
=
self
.
execFunc
(
'printToNone'
)
except
AttributeError
:
# Passed. "None" has no "write" attribute.
pass
else
:
self
.
fail
(
0
,
res
)
def
checkPrintStuff
(
self
):
res
=
self
.
execFunc
(
'printStuff'
)
self
.
assertEqual
(
res
,
'a b c'
)
def
checkPrintLines
(
self
):
res
=
self
.
execFunc
(
'printLines'
)
self
.
assertEqual
(
res
,
'0 1 2
\
n
3 4 5
\
n
6 7 8
\
n
'
)
def
checkPrimes
(
self
):
res
=
self
.
execFunc
(
'primes'
)
self
.
assertEqual
(
res
,
'[2, 3, 5, 7, 11, 13, 17, 19]'
)
def
checkAllowedSimple
(
self
):
res
=
self
.
execFunc
(
'allowed_simple'
)
self
.
assertEqual
(
res
,
'abcabcabc'
)
def
checkAllowedRead
(
self
):
self
.
execFunc
(
'allowed_read'
,
RestrictedObject
())
def
checkAllowedWrite
(
self
):
self
.
execFunc
(
'allowed_write'
,
RestrictedObject
())
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
checkInplace
(
self
):
inplacevar_wrapper_called
.
clear
()
res
=
self
.
execFunc
(
'try_inplace'
)
self
.
assertEqual
(
inplacevar_wrapper_called
[
'+='
],
(
1
,
3
))
def
checkDenied
(
self
):
for
k
in
rmodule
.
keys
():
if
k
[:
6
]
==
'denied'
:
try
:
self
.
execFunc
(
k
,
RestrictedObject
())
except
AccessDenied
:
# Passed the test
pass
else
:
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'
)
f
=
open
(
fn
,
'r'
)
source
=
f
.
read
()
f
.
close
()
# Unrestricted compile.
code
=
compile
(
source
,
fn
,
'exec'
)
m
=
{
'__builtins__'
:
{
'__import__'
:
minimal_import
}}
exec
code
in
m
for
k
,
v
in
m
.
items
():
if
hasattr
(
v
,
'func_code'
):
filename
,
source
=
find_source
(
fn
,
v
.
func_code
)
source
=
"from __future__ import generators
\
n
\
n
"
+
source
# Now compile it with restrictions
try
:
code
=
compile_restricted
(
source
,
filename
,
'exec'
)
except
SyntaxError
:
# Passed the test.
pass
else
:
self
.
fail
(
'%s should not have compiled'
%
k
)
def
checkOrderOfOperations
(
self
):
res
=
self
.
execFunc
(
'order_of_operations'
)
self
.
assertEqual
(
res
,
0
)
def
checkRot13
(
self
):
res
=
self
.
execFunc
(
'rot13'
,
'Zope is k00l'
)
self
.
assertEqual
(
res
,
'Mbcr vf x00y'
)
def
checkNestedScopes1
(
self
):
res
=
self
.
execFunc
(
'nested_scopes_1'
)
self
.
assertEqual
(
res
,
2
)
def
checkUnrestrictedEval
(
self
):
expr
=
RestrictionCapableEval
(
"{'a':[m.pop()]}['a'] + [m[0]]"
)
v
=
[
12
,
34
]
expect
=
v
[:]
expect
.
reverse
()
res
=
expr
.
eval
({
'm'
:
v
})
self
.
assertEqual
(
res
,
expect
)
v
=
[
12
,
34
]
res
=
expr
(
m
=
v
)
self
.
assertEqual
(
res
,
expect
)
def
checkStackSize
(
self
):
for
k
,
rfunc
in
rmodule
.
items
():
if
not
k
.
startswith
(
'_'
)
and
hasattr
(
rfunc
,
'func_code'
):
rss
=
rfunc
.
func_code
.
co_stacksize
ss
=
getattr
(
restricted_module
,
k
).
func_code
.
co_stacksize
self
.
failUnless
(
rss
>=
ss
,
'The stack size estimate for %s() '
'should have been at least %d, but was only %d'
%
(
k
,
ss
,
rss
))
def
checkBeforeAndAfter
(
self
):
from
RestrictedPython.RCompile
import
RModule
from
RestrictedPython.tests
import
before_and_after
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
())
if
sys
.
version_info
[:
2
]
>=
(
2
,
4
):
def
checkBeforeAndAfter24
(
self
):
from
RestrictedPython.RCompile
import
RModule
from
RestrictedPython.tests
import
before_and_after24
from
compiler
import
parse
defre
=
re
.
compile
(
r'def ([_A-Za-z0-9]+)_(after|before)\
(
')
beforel = [name for name in before_and_after24.__dict__
if name.endswith("_before")]
for name in beforel:
before = getattr(before_and_after24, 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_after24
,
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
,
'_inplacevar_'
:
inplacevar_wrapper
}
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
checkEmpty
(
self
):
rf
=
RFunction
(
""
,
""
,
"issue945"
,
"empty.py"
,
{})
rf
.
parse
()
rf2
=
RFunction
(
""
,
"# still empty
\
n
\
n
# by"
,
"issue945"
,
"empty.py"
,
{})
rf2
.
parse
()
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"
)
# these two tests check that source code with Windows line
# endings still works.
def
checkLineEndingsRFunction
(
self
):
from
RestrictedPython.RCompile
import
RFunction
gen
=
RFunction
(
p
=
''
,
body
=
'# testing
\
r
\
n
print "testing"
\
r
\
n
return printed
\
n
'
,
name
=
'test'
,
filename
=
'<test>'
,
globals
=
(),
)
gen
.
mode
=
'exec'
# if the source has any line ending other than \n by the time
# parse() is called, then you'll get a syntax error.
gen
.
parse
()
def
checkLineEndingsRestrictedCompileMode
(
self
):
from
RestrictedPython.RCompile
import
RestrictedCompileMode
gen
=
RestrictedCompileMode
(
'# testing
\
r
\
n
print "testing"
\
r
\
n
return printed
\
n
'
,
'<testing>'
)
gen
.
mode
=
'exec'
# if the source has any line ending other than \n by the time
# parse() is called, then you'll get a syntax error.
gen
.
parse
()
create_rmodule
()
def
test_suite
():
return
unittest
.
makeSuite
(
RestrictionTests
,
'check'
)
if
__name__
==
'__main__'
:
unittest
.
main
(
defaultTest
=
"test_suite"
)
lib/python/RestrictedPython/tests/unpack.py
deleted
100644 → 0
View file @
ed0ec8b0
# 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
]]])
)
lib/python/RestrictedPython/tests/verify.py
deleted
100644 → 0
View file @
ed0ec8b0
##############################################################################
#
# Copyright (c) 2003 Zope Corporation and Contributors. All Rights Reserved.
#
# This software is subject to the provisions of the Zope Public License,
# Version 2.1 (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
)
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment