7171 resource_module_loaded = False
7272
7373
74+
75+ # These could lead to XSS or other code injection attacks, so be careful:
76+ __html__ = None
77+ def setHTML (htmlStr ):
78+ global __html__
79+ __html__ = htmlStr
80+
81+ __css__ = None
82+ def setCSS (cssStr ):
83+ global __css__
84+ __css__ = cssStr
85+
86+ __js__ = None
87+ def setJS (jsStr ):
88+ global __js__
89+ __js__ = jsStr
90+
91+
7492# ugh, I can't figure out why in Python 2, __builtins__ seems to
7593# be a dict, but in Python 3, __builtins__ seems to be a module,
7694# so just handle both cases ... UGLY!
82100
83101
84102# whitelist of module imports
85- ALLOWED_MODULE_IMPORTS = ('math' , 'random' , 'datetime' ,
103+ ALLOWED_STDLIB_MODULE_IMPORTS = ('math' , 'random' , 'datetime' ,
86104 'functools' , 'operator' , 'string' ,
87105 'collections' , 're' , 'json' ,
88106 'heapq' , 'bisect' )
89107
108+ # whitelist of custom modules to import into OPT
109+ # (TODO: support modules in a subdirectory, but there are various
110+ # logistical problems with doing so that I can't overcome at the moment,
111+ # especially getting setHTML, setCSS, and setJS to work in the imported
112+ # modules.)
113+ CUSTOM_MODULE_IMPORTS = ('callback_module' , 'ttt_module' )
114+
115+
90116# PREEMPTIVELY import all of these modules, so that when the user's
91117# script imports them, it won't try to do a file read (since they've
92118# already been imported and cached in memory). Remember that when
93119# the user's code runs, resource.setrlimit(resource.RLIMIT_NOFILE, (0, 0))
94120# will already be in effect, so no more files can be opened.
95- for m in ALLOWED_MODULE_IMPORTS :
121+ #
122+ # NB: All modules in CUSTOM_MODULE_IMPORTS will be imported, warts and
123+ # all, so they better work on Python 2 and 3!
124+ for m in ALLOWED_STDLIB_MODULE_IMPORTS + CUSTOM_MODULE_IMPORTS :
96125 __import__ (m )
97126
98127
99128# Restrict imports to a whitelist
100129def __restricted_import__ (* args ):
101- if args [0 ] in ALLOWED_MODULE_IMPORTS :
102- return BUILTIN_IMPORT (* args )
130+ if args [0 ] in ALLOWED_STDLIB_MODULE_IMPORTS + CUSTOM_MODULE_IMPORTS :
131+ imported_mod = BUILTIN_IMPORT (* args )
132+
133+ if args [0 ] in CUSTOM_MODULE_IMPORTS :
134+ # add special magical functions to custom imported modules
135+ setattr (imported_mod , 'setHTML' , setHTML )
136+ setattr (imported_mod , 'setCSS' , setCSS )
137+ setattr (imported_mod , 'setJS' , setJS )
138+
139+ return imported_mod
103140 else :
104141 raise ImportError ('{0} not supported' .format (args [0 ]))
105142
@@ -109,7 +146,7 @@ def __restricted_import__(*args):
109146# 1. running the entire program up to a call to raw_input (or input in py3),
110147# 2. bailing and returning a trace ending in a special 'raw_input' event,
111148# 3. letting the web frontend issue a prompt to the user to grab a string,
112- # 4. RE-RUNNING the whole program with that string added to raw_input_queue ,
149+ # 4. RE-RUNNING the whole program with that string added to input_string_queue ,
113150# 5. which should bring execution to the next raw_input call (if
114151# available), or to termination.
115152# Repeat until no more raw_input calls are encountered.
@@ -118,23 +155,33 @@ def __restricted_import__(*args):
118155# TODO: To make this technique more deterministic,
119156# save away and restore the random seed.
120157
121- # queue of input strings passed from the outside
122- raw_input_queue = []
158+ # queue of input strings passed from either raw_input or mouse_input
159+ input_string_queue = []
123160
124161class RawInputException (Exception ):
125162 pass
126163
127164def raw_input_wrapper (prompt = '' ):
128- if raw_input_queue :
129- return raw_input_queue .pop (0 )
165+ if input_string_queue :
166+ return input_string_queue .pop (0 )
130167 raise RawInputException (prompt )
131168
169+ class MouseInputException (Exception ):
170+ pass
171+
172+ def mouse_input_wrapper (prompt = '' ):
173+ if input_string_queue :
174+ return input_string_queue .pop (0 )
175+ raise MouseInputException (prompt )
176+
177+
132178
133179# blacklist of builtins
134- BANNED_BUILTINS = ['reload' , 'apply' , ' open' , 'compile' ,
180+ BANNED_BUILTINS = ['reload' , 'open' , 'compile' ,
135181 'file' , 'eval' , 'exec' , 'execfile' ,
136182 'exit' , 'quit' , 'help' ,
137183 'dir' , 'globals' , 'locals' , 'vars' ]
184+ # Peter says 'apply' isn't dangerous, so don't ban it
138185
139186# ban input() in Python 2 since it does an eval!
140187# (Python 3 input is Python 2 raw_input, so we're okay)
@@ -408,6 +455,9 @@ def user_exception(self, frame, exc_info):
408455 if exc_type_name == 'RawInputException' :
409456 self .trace .append (dict (event = 'raw_input' , prompt = exc_value .args [0 ]))
410457 self .done = True
458+ elif exc_type_name == 'MouseInputException' :
459+ self .trace .append (dict (event = 'mouse_input' , prompt = exc_value .args [0 ]))
460+ self .done = True
411461 else :
412462 self .interaction (frame , exc_traceback , 'exception' )
413463
@@ -420,13 +470,43 @@ def interaction(self, frame, traceback, event_type):
420470 top_frame = tos [0 ]
421471 lineno = tos [1 ]
422472
473+
474+ # debug ...
475+ '''
476+ print >> sys.stderr, '=== STACK ==='
477+ for (e,ln) in self.stack:
478+ print >> sys.stderr, e.f_code.co_name + ' ' + e.f_code.co_filename + ' ' + str(ln)
479+ print >> sys.stderr, "top_frame", top_frame.f_code.co_name
480+ print >> sys.stderr
481+ '''
482+
483+
423484 # don't trace inside of ANY functions that aren't user-written code
424485 # (e.g., those from imported modules -- e.g., random, re -- or the
425486 # __restricted_import__ function in this file)
426487 #
427488 # empirically, it seems like the FIRST entry in self.stack is
428489 # the 'run' function from bdb.py, but everything else on the
429490 # stack is the user program's "real stack"
491+
492+ # Look only at the "topmost" frame on the stack ...
493+
494+ # it seems like user-written code has a filename of '<string>',
495+ # but maybe there are false positives too?
496+ if self .canonic (top_frame .f_code .co_filename ) != '<string>' :
497+ return
498+ # also don't trace inside of the magic "constructor" code
499+ if top_frame .f_code .co_name == '__new__' :
500+ return
501+ # or __repr__, which is often called when running print statements
502+ if top_frame .f_code .co_name == '__repr__' :
503+ return
504+
505+
506+ # OLD CODE -- bail if any element on the stack matches these conditions
507+ # note that the old code passes tests/backend-tests/namedtuple.txt
508+ # but the new code above doesn't :/
509+ '''
430510 for (cur_frame, cur_line) in self.stack[1:]:
431511 # it seems like user-written code has a filename of '<string>',
432512 # but maybe there are false positives too?
@@ -438,14 +518,6 @@ def interaction(self, frame, traceback, event_type):
438518 # or __repr__, which is often called when running print statements
439519 if cur_frame.f_code.co_name == '__repr__':
440520 return
441-
442-
443- # debug ...
444- '''
445- print >> sys.stderr, '==='
446- for (e,ln) in self.stack:
447- print >> sys.stderr, e.f_code.co_name + ' ' + e.f_code.co_filename + ' ' + str(ln)
448- print >> sys.stderr
449521 '''
450522
451523
@@ -629,7 +701,25 @@ def create_encoded_stack_entry(cur_frame):
629701 if cur_name == '<module>' :
630702 break
631703
632- encoded_stack_locals .append (create_encoded_stack_entry (cur_frame ))
704+ # do this check because in some cases, certain frames on the
705+ # stack might NOT be tracked, so don't push a stack entry for
706+ # those frames. this happens when you have a callback function
707+ # in an imported module. e.g., your code:
708+ # def foo():
709+ # bar(baz)
710+ #
711+ # def baz(): pass
712+ #
713+ # imported module code:
714+ # def bar(callback_func):
715+ # callback_func()
716+ #
717+ # when baz is executing, the real stack is [foo, bar, baz] but
718+ # bar is in imported module code, so pg_logger doesn't trace
719+ # it, and it doesn't show up in frame_ordered_ids. thus, the
720+ # stack to render should only be [foo, baz].
721+ if cur_frame in self .frame_ordered_ids :
722+ encoded_stack_locals .append (create_encoded_stack_entry (cur_frame ))
633723 i -= 1
634724
635725 zombie_encoded_stack_locals = [create_encoded_stack_entry (e ) for e in zombie_frames_to_render ]
@@ -721,6 +811,15 @@ def create_encoded_stack_entry(cur_frame):
721811 heap = self .encoder .get_heap (),
722812 stdout = get_user_stdout (tos [0 ]))
723813
814+ # TODO: refactor into a non-global
815+ global __html__ , __css__ , __js__
816+ if __html__ :
817+ trace_entry ['html_output' ] = __html__
818+ if __css__ :
819+ trace_entry ['css_output' ] = __css__
820+ if __js__ :
821+ trace_entry ['js_output' ] = __js__
822+
724823 # if there's an exception, then record its info:
725824 if event_type == 'exception' :
726825 # always check in f_locals
@@ -792,6 +891,14 @@ def _runscript(self, script_str):
792891 else :
793892 user_builtins [k ] = v
794893
894+ user_builtins ['mouse_input' ] = mouse_input_wrapper
895+
896+ # uncomment to ease debugging, but in production,
897+ # don't expose these directly to user since it's more of a
898+ # security risk. instead, expose them only to imported modules
899+ #user_builtins['setHTML'] = setHTML
900+ #user_builtins['setCSS'] = setCSS
901+ #user_builtins['setJS'] = setJS
795902
796903 user_stdout = cStringIO .StringIO ()
797904
@@ -911,11 +1018,15 @@ def finalize(self):
9111018def exec_script_str (script_str , raw_input_lst_json , cumulative_mode , heap_primitives , finalizer_func ):
9121019 logger = PGLogger (cumulative_mode , heap_primitives , finalizer_func )
9131020
914- global raw_input_queue
915- raw_input_queue = []
1021+ # TODO: refactor these NOT to be globals
1022+ global input_string_queue
1023+ input_string_queue = []
9161024 if raw_input_lst_json :
9171025 # TODO: if we want to support unicode, remove str() cast
918- raw_input_queue = [str (e ) for e in json .loads (raw_input_lst_json )]
1026+ input_string_queue = [str (e ) for e in json .loads (raw_input_lst_json )]
1027+
1028+ global __html__ , __css__ , __js__
1029+ __html__ , __css__ , __js__ = None , None , None
9191030
9201031 try :
9211032 logger ._runscript (script_str )
@@ -931,11 +1042,15 @@ def exec_script_str(script_str, raw_input_lst_json, cumulative_mode, heap_primit
9311042def exec_script_str_local (script_str , raw_input_lst_json , cumulative_mode , heap_primitives , finalizer_func ):
9321043 logger = PGLogger (cumulative_mode , heap_primitives , finalizer_func , disable_security_checks = True )
9331044
934- global raw_input_queue
935- raw_input_queue = []
1045+ # TODO: refactor these NOT to be globals
1046+ global input_string_queue
1047+ input_string_queue = []
9361048 if raw_input_lst_json :
9371049 # TODO: if we want to support unicode, remove str() cast
938- raw_input_queue = [str (e ) for e in json .loads (raw_input_lst_json )]
1050+ input_string_queue = [str (e ) for e in json .loads (raw_input_lst_json )]
1051+
1052+ global __html__ , __css__ , __js__
1053+ __html__ , __css__ , __js__ = None , None , None
9391054
9401055 try :
9411056 logger ._runscript (script_str )
0 commit comments