def test_obfuscate_no_global_recursive(self): node = es5( dedent(""" (function named(param1, param2) { param1 = param1 * param2 - param2; param2--; if (param2 < 0) { return named(param1, param2); } return param1; })(); """).strip()) self.assertEqual( dedent(""" (function named(b, a) { b = b * a - a; a--; if (a < 0) { return named(b, a); } return b; })(); """).lstrip(), ''.join(c.text for c in Unparser(rules=( default_rules, indent(indent_str=' '), obfuscate(obfuscate_globals=False), ))(node)))
def test_obfuscate_try_catch_shadowed(self): node = es5( dedent(""" var value = 1; try { console.log(value); throw Error("welp"); } catch (value) { console.log(value); } """).strip()) self.assertEqual( dedent(""" var a = 1; try { console.log(a); throw Error("welp"); } catch (a) { console.log(a); } """).lstrip(), ''.join(c.text for c in Unparser(rules=( default_rules, indent(indent_str=' '), obfuscate(obfuscate_globals=True), ))(node)))
def test_no_resolve(self): # a simple test to show that an obfuscator without the initial # loading run executed (i.e. the one with the required handlers) # with the resolve added to the dispatcher will not crash, but # simply not have any effect. tree = es5( dedent(""" (function() { var foo = 1; var bar = 2; })(this); """).strip()) unparser = Unparser() obfuscator = Obfuscator() dispatcher = Dispatcher( unparser.definitions, token_handler_str_default, { Space: layout_handler_space_minimum, RequiredSpace: layout_handler_space_minimum, OpenBlock: layout_handler_openbrace, CloseBlock: layout_handler_closebrace, EndStatement: layout_handler_semicolon, }, { Resolve: obfuscator.resolve, }) # see that the manually constructed minimum output works. self.assertEqual("(function(){var foo=1;var bar=2;})(this);", ''.join(c.text for c in walk(dispatcher, tree)))
def test_functions_in_lists(self): node = es5( dedent(""" (function main(root) { root.exports = [ (function(module, exports) { module.exports = {}; }), (function(module, exports) { exports.fun = 1; }), ]; })(this); """).strip()) self.assertEqual( dedent(""" (function main(a) { a.exports = [(function(a, b) { a.exports = {}; }), (function(b, a) { a.fun = 1; })]; })(this); """).lstrip(), ''.join(c.text for c in Unparser(rules=( default_rules, indent(indent_str=' '), obfuscate(), ))(node)))
def test_obfuscate_try_catch_scope_inside_catch(self): node = es5( dedent(""" var value = 100; (function() { var dummy = 0; console.log(value); value = 1; try { console.log(value); throw Error("welp"); } catch (exc) { var value = 2; var welped = value; welped = 'welped'; (function() { console.log(exc); console.log(value); console.log(welped); })(); } value = 3; })(); console.log(value); """).strip()) # note that dummy -> c because exc is a local, not counted in # the priority at the parent scope. self.assertEqual( dedent(""" var value = 100; (function() { var c = 0; console.log(a); a = 1; try { console.log(a); throw Error("welp"); } catch (d) { var a = 2; var b = a; b = 'welped'; (function() { console.log(d); console.log(a); console.log(b); })(); } a = 3; })(); console.log(value); """).lstrip(), ''.join(c.text for c in Unparser(rules=( default_rules, indent(indent_str=' '), obfuscate(), ))(node)))
def test_obfuscate_globals(self): node = es5( dedent(""" var a_global = 1; (function(a_param) { var a_local = 1; a_local = a_param; a_local = a_global; })(); """).strip()) self.assertEqual( dedent(""" var a_global = 1; (function(b) { var a = 1; a = b; a = a_global; })(); """).lstrip(), ''.join(c.text for c in Unparser(rules=( default_rules, indent(indent_str=' '), obfuscate(obfuscate_globals=False), ))(node))) self.assertEqual( dedent(""" var a = 1; (function(c) { var b = 1; b = c; b = a; })(); """).lstrip(), ''.join(c.text for c in Unparser(rules=( default_rules, indent(indent_str=' '), obfuscate(obfuscate_globals=True), ))(node)))
def test_obfuscate_no_shadow_funcname_not_mapped(self): node = es5( dedent(""" function a(arg) { } """).strip()) self.assertEqual( dedent(""" function a(b) { } """).lstrip(), ''.join(c.text for c in Unparser(rules=( default_rules, indent(indent_str=' '), obfuscate(obfuscate_globals=False, shadow_funcname=False), ))(node)))
def test_multiple_reuse(self): tree = es5( dedent(""" (function() { var foo = 1; var bar = 2; bar = 3; })(this); """).strip()) obfuscator_unparser = Unparser(rules=( minimum_rules, obfuscate(), )) self.assertEqual( ''.join(c.text for c in obfuscator_unparser(tree)), ''.join(c.text for c in obfuscator_unparser(tree)), )
def test_obfuscate_try_catch_vardeclare_inside_catch(self): node = es5( dedent(""" var value = 100; (function() { console.log(value); value = 1; try { console.log(value); throw Error("welp"); } catch (exc) { var value = 2; console.log(value); } value = 3; })(); console.log(value); """).strip()) self.assertEqual( dedent(""" var value = 100; (function() { console.log(a); a = 1; try { console.log(a); throw Error("welp"); } catch (b) { var a = 2; console.log(a); } a = 3; })(); console.log(value); """).lstrip(), ''.join(c.text for c in Unparser(rules=( default_rules, indent(indent_str=' '), obfuscate(), ))(node)))
def test_no_shadow_children(self): node = es5( dedent(""" var some_value = 1; (function(param) { a.is_not_declared_in_this_file(param); })(); """).strip()) self.assertEqual( dedent(""" var b = 1; (function(b) { a.is_not_declared_in_this_file(b); })(); """).lstrip(), ''.join(c.text for c in Unparser(rules=( default_rules, indent(indent_str=' '), obfuscate(obfuscate_globals=True), ))(node)))
def test_obfuscate_skip(self): node = es5( dedent(""" (function(a_param) { var a_local = a_param; })(); """).strip()) self.assertEqual( dedent(""" (function(c) { var d = c; })(); """).lstrip(), ''.join(c.text for c in Unparser(rules=( default_rules, indent(indent_str=' '), obfuscate(reserved_keywords=( 'a', 'b', )), ))(node)))
def test_obfuscate_try_catch_no_declare(self): node = es5( dedent(""" var value = 100; (function() { value = 1; try { console.log(value); throw Error("welp"); } catch (value) { value = 0; console.log(value); } console.log(value); })(); console.log(value); """).strip()) self.assertEqual( dedent(""" var value = 100; (function() { value = 1; try { console.log(value); throw Error("welp"); } catch (a) { a = 0; console.log(a); } console.log(value); })(); console.log(value); """).lstrip(), ''.join(c.text for c in Unparser(rules=( default_rules, indent(indent_str=' '), obfuscate(), ))(node)))
def test_no_shadow_parent_remapped(self): node = es5( dedent(""" var a = c; (function(param) { b.is_not_declared_in_this_file(a, param); })(c); """).strip()) # param can remap to c because the global 'c' not used in that # scope. self.assertEqual( dedent(""" var a = c; (function(c) { b.is_not_declared_in_this_file(a, c); })(c); """).lstrip(), ''.join(c.text for c in Unparser(rules=( default_rules, indent(indent_str=' '), obfuscate(obfuscate_globals=True), ))(node)))
def test_obfuscate_no_global_recursive_redeclared_no_shadow_funcname(self): node = es5( dedent(""" (function $() { $(); (function $() { var foo = 1; })(); })(); """).strip()) self.assertEqual( dedent(""" (function $() { a(); (function a() { var b = 1; })(); })(); """).lstrip(), ''.join(c.text for c in Unparser(rules=( default_rules, indent(indent_str=' '), obfuscate(obfuscate_globals=False, shadow_funcname=False), ))(node)))
config_definitions = dict(**definitions) config_definitions.update({ # definition to ensure that array items are serialized one per line 'Array': ( Text(value='['), Indent, Newline, ElisionJoinAttr('items', value=(Newline, )), Dedent, OptionalNewline, Text(value=']'), ), }) wpconf_serializer = Unparser( definitions=config_definitions, rules=(indent(indent_str=' '), ), ) # produce customized versions of the commented calmjs.parse imports # from calmjs.parse import asttypes asttypes = AstTypesFactory( lambda ast: ''.join(chunk.text for chunk in wpconf_serializer(ast)), ReprWalker(), ) # from calmjs.parse import es5 def es5(source): return Parser(asttypes=asttypes).parse(source)
def run(inputs, output, mangle, obfuscate, pretty, source_map, indent_width, drop_semi, encoding, version): """ Not a general use method, as sys.exit is called. """ def stdin(): return ( sys.stdin if isinstance(sys.stdin, (StringIO, TextIOWrapper)) else codecs.getreader(sys.stdin.encoding or encoding)(sys.stdin) ) def stdout(): return ( sys.stdout if isinstance(sys.stdout, (StringIO, TextIOWrapper)) else codecs.getwriter(sys.stdout.encoding or encoding)(sys.stdout) ) abs_output = abspath(output) if output else output abs_source_map = abspath(source_map) if source_map else source_map input_streams = ( [partial(codecs.open, abspath(p), encoding=encoding) for p in inputs] if inputs else [stdin] ) output_stream = ( partial(codecs.open, abs_output, 'w', encoding=encoding) if abs_output else stdout ) # if source_map is flagged (from empty string, in the const), if # no further name is supplied, use the output to build the path, # if it is also not stdout. source_map_path = ( abs_output + '.map' if source_map == '' and output_stream is not stdout else abs_source_map ) if source_map and abs_source_map == abs_output: # if a source_map was specified and the absolute paths of that # and the output is equal, use the output_stream that was # already created. sourcemap_stream = output_stream else: # no source map if source_map path is actually None, and only # generate a callable if the source_map_path is not an empty # string. sourcemap_stream = None if source_map_path is None else ( partial(codecs.open, source_map_path, 'w', encoding=encoding) if source_map_path else stdout ) enabled_rules = [rules.minify(drop_semi=drop_semi or mangle)] if obfuscate or mangle: enabled_rules.append(rules.obfuscate( reserved_keywords=Lexer.keywords_dict.keys() )) if pretty: enabled_rules.append(rules.indent(indent_str=' ' * indent_width)) printer = Unparser(rules=enabled_rules) try: io.write( printer, (io.read(parse, f) for f in input_streams), output_stream, sourcemap_stream, ) except ECMASyntaxError as e: logger.error('%s', e) sys.exit(1) except (IOError, OSError) as e: logger.error('%s', e) if e.args and isinstance(e.args[0], int): sys.exit(e.args[0]) sys.exit(5) # EIO except UnicodeDecodeError as e: logger.error('read error: %s', e) sys.exit(1) except UnicodeEncodeError as e: logger.error('write error: %s', e) sys.exit(1) except KeyboardInterrupt: sys.exit(130) # no need to close any streams as they are callables and that the io # read/write functions take care of that. sys.exit(0)
def test_build_substitutions(self): tree = es5( dedent(""" (function(root) { var foo = 1; var bar = 2; baz = 3; foo = 4; window.document.body.focus(); })(this, factory); """).strip()) unparser = Unparser() obfuscator = Obfuscator() # a bare dispatcher should work, as it is used for extracting # the definitions from. sub_dispatcher = Dispatcher(unparser.definitions, rule_handler_noop, {}, {}) # only do the intial walk. result = obfuscator.walk(sub_dispatcher, tree) # should be empty list as the run should produce nothing, due to # the null token producer. self.assertEqual(result, []) # only one scope was defined. self.assertEqual(1, len(obfuscator.scopes)) self.assertEqual(1, len(obfuscator.global_scope.children)) # do some validation on the scope itself. self.assertEqual(set(), obfuscator.global_scope.declared_symbols) self.assertEqual({ 'factory': 1, 'baz': 1, 'window': 1 }, obfuscator.global_scope.referenced_symbols) scope = obfuscator.global_scope.children[0] self.assertEqual({'root', 'foo', 'bar'}, scope.declared_symbols) self.assertEqual( { 'root': 1, 'foo': 2, 'bar': 1, 'baz': 1, 'window': 1, }, scope.referenced_symbols) # do a trial run to show that the resolution works. main_dispatcher = Dispatcher( unparser.definitions, token_handler_unobfuscate, { Space: layout_handler_space_minimum, RequiredSpace: layout_handler_space_minimum, OpenBlock: layout_handler_openbrace, CloseBlock: layout_handler_closebrace, EndStatement: layout_handler_semicolon, }, { Resolve: obfuscator.resolve, }) # see that the manually constructed minimum output works. self.assertEqual( "(function(root){var foo=1;var bar=2;baz=3;foo=4;" "window.document.body.focus();})(this,factory);", ''.join(c.text for c in walk(main_dispatcher, tree))) # since nothing was remapped. self.assertEqual([], [(c.text, c.name) for c in walk(main_dispatcher, tree) if c.name]) # now manually give the scope with a set of replacement names scope.remapped_symbols.update({ 'root': 'r', 'foo': 'f', 'bar': 'b', 'baz': 'z', }) self.assertEqual( "(function(r){var f=1;var b=2;z=3;f=4;" "window.document.body.focus();})(this,factory);", ''.join(c.text for c in walk(main_dispatcher, tree))) # check that the source chunks yielded contain both the original # text token and the original row/col location self.assertEqual([ ('r', 1, 11, 'root'), ('f', 2, 7, 'foo'), ('b', 3, 7, 'bar'), ('z', 4, 3, 'baz'), ('f', 5, 3, 'foo'), ], [c[:4] for c in walk(main_dispatcher, tree) if c.name])
def test_multi_scope(self): node = es5( dedent(""" (function(value) { var parent = 1; (function(value) { var child = 2; (function(value) { var grandchild = 3; (function(value) { var greatgrandchild = 4; (function(value) { var result = 1; // using greatgrandchild a lot to ensure priority // mucking console.log(parent); console.log(greatgrandchild); console.log(greatgrandchild * value); console.log(greatgrandchild * parent); console.log(greatgrandchild * greatgrandchild); })(value); })(value); })(value); })(value); })(0); """).strip()) # Note that grandchild and greatgrandchild never had any of # their local variables referenced by any of their nested # scopes, their values got shadowed in the greatgrandchild scope # before both that and parent is used in the innermost scope. # Also, note that since greatgrandchild stopped propagating the # reference usage upwards, even though it has a lot more usage, # it never will claim priority over the parent at the parent # scope since parent got mapped to 'a' thus forcing # greatgrandchild to map to 'b', the next lowest value symbol. self.assertEqual( dedent(""" (function(b) { var a = 1; (function(b) { var c = 2; (function(b) { var c = 3; (function(c) { var b = 4; (function(c) { var d = 1; console.log(a); console.log(b); console.log(b * c); console.log(b * a); console.log(b * b); })(c); })(b); })(b); })(b); })(0); """).lstrip(), ''.join(c.text for c in Unparser(rules=( default_rules, indent(indent_str=' '), obfuscate(), ))(node)))