Skip to content

Commit

Permalink
switching to latest python jsbeautifier
Browse files Browse the repository at this point in the history
  • Loading branch information
scott2449 committed Oct 16, 2012
1 parent 9ba9f30 commit d8bdf34
Show file tree
Hide file tree
Showing 20 changed files with 1,351 additions and 104 deletions.
261 changes: 157 additions & 104 deletions jsbeautifier.py → jsbeautifier/__init__.py

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions jsbeautifier/tests/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
# Empty file :)
43 changes: 43 additions & 0 deletions jsbeautifier/tests/testindentation.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import re
import unittest
import jsbeautifier

class TestJSBeautifierIndentation(unittest.TestCase):
def test_tabs(self):
test_fragment = self.decodesto

self.options.indent_with_tabs = 1;
test_fragment('{tabs()}', "{\n\ttabs()\n}");

def test_function_indent(self):
test_fragment = self.decodesto

self.options.indent_with_tabs = 1;
self.options.keep_function_indentation = 1;
test_fragment('var foo = function(){ bar() }();', "var foo = function() {\n\tbar()\n}();");

self.options.tabs = 1;
self.options.keep_function_indentation = 0;
test_fragment('var foo = function(){ baz() }();', "var foo = function() {\n\tbaz()\n}();");

def decodesto(self, input, expectation=None):
self.assertEqual(
jsbeautifier.beautify(input, self.options), expectation or input)

@classmethod
def setUpClass(cls):
options = jsbeautifier.default_options()
options.indent_size = 4
options.indent_char = ' '
options.preserve_newlines = True
options.jslint_happy = False
options.keep_array_indentation = False
options.brace_style = 'collapse'
options.indent_level = 0

cls.options = options
cls.wrapregex = re.compile('^(.+)$', re.MULTILINE)


if __name__ == '__main__':
unittest.main()
511 changes: 511 additions & 0 deletions jsbeautifier/tests/testjsbeautifier.py

Large diffs are not rendered by default.

25 changes: 25 additions & 0 deletions jsbeautifier/unpackers/README.specs.mkd
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
# UNPACKERS SPECIFICATIONS

Nothing very difficult: an unpacker is a submodule placed in the directory
where this file was found. Each unpacker must define three symbols:

* `PRIORITY` : integer number expressing the priority in applying this
unpacker. Lower number means higher priority.
Makes sense only if a source file has been packed with
more than one packer.
* `detect(source)` : returns `True` if source is packed, otherwise, `False`.
* `unpack(source)` : takes a `source` string and unpacks it. Must always return
valid JavaScript. That is to say, your code should look
like:

```
if detect(source):
return do_your_fancy_things_with(source)
else:
return source
```

*You can safely define any other symbol in your module, as it will be ignored.*

`__init__` code will automatically load new unpackers, without any further step
to be accomplished. Simply drop it in this directory.
67 changes: 67 additions & 0 deletions jsbeautifier/unpackers/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
#
# General code for JSBeautifier unpackers infrastructure. See README.specs
# written by Stefano Sanfilippo <a.little.coder@gmail.com>
#

"""General code for JSBeautifier unpackers infrastructure."""

import pkgutil
import re
from jsbeautifier.unpackers import evalbased

# NOTE: AT THE MOMENT, IT IS DEACTIVATED FOR YOUR SECURITY: it runs js!
BLACKLIST = ['jsbeautifier.unpackers.evalbased']

class UnpackingError(Exception):
"""Badly packed source or general error. Argument is a
meaningful description."""
pass

def getunpackers():
"""Scans the unpackers dir, finds unpackers and add them to UNPACKERS list.
An unpacker will be loaded only if it is a valid python module (name must
adhere to naming conventions) and it is not blacklisted (i.e. inserted
into BLACKLIST."""
path = __path__
prefix = __name__ + '.'
unpackers = []
interface = ['unpack', 'detect', 'PRIORITY']
for _importer, modname, _ispkg in pkgutil.iter_modules(path, prefix):
if 'tests' not in modname and modname not in BLACKLIST:
try:
module = __import__(modname, fromlist=interface)
except ImportError:
raise UnpackingError('Bad unpacker: %s' % modname)
else:
unpackers.append(module)

return sorted(unpackers, key = lambda mod: mod.PRIORITY)

UNPACKERS = getunpackers()

def run(source, evalcode=False):
"""Runs the applicable unpackers and return unpacked source as a string."""
for unpacker in [mod for mod in UNPACKERS if mod.detect(source)]:
source = unpacker.unpack(source)
if evalcode and evalbased.detect(source):
source = evalbased.unpack(source)
return source

def filtercomments(source):
"""NOT USED: strips trailing comments and put them at the top."""
trailing_comments = []
comment = True

while comment:
if re.search(r'^\s*\/\*', source):
comment = source[0, source.index('*/') + 2]
elif re.search(r'^\s*\/\/', source):
comment = re.search(r'^\s*\/\/', source).group(0)
else:
comment = None

if comment:
source = re.sub(r'^\s+', '', source[len(comment):])
trailing_comments.append(comment)

return '\n'.join(trailing_comments) + source
39 changes: 39 additions & 0 deletions jsbeautifier/unpackers/evalbased.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
#
# Unpacker for eval() based packers, a part of javascript beautifier
# by Einar Lielmanis <einar@jsbeautifier.org>
#
# written by Stefano Sanfilippo <a.little.coder@gmail.com>
#
# usage:
#
# if detect(some_string):
# unpacked = unpack(some_string)
#

"""Unpacker for eval() based packers: runs JS code and returns result.
Works only if a JS interpreter (e.g. Mozilla's Rhino) is installed and
properly set up on host."""

from subprocess import PIPE, Popen

PRIORITY = 3

def detect(source):
"""Detects if source is likely to be eval() packed."""
return source.strip().lower().startswith('eval(function(')

def unpack(source):
"""Runs source and return resulting code."""
return jseval('print %s;' % source[4:]) if detect(source) else source

# In case of failure, we'll just return the original, without crashing on user.
def jseval(script):
"""Run code in the JS interpreter and return output."""
try:
interpreter = Popen(['js'], stdin=PIPE, stdout=PIPE)
except OSError:
return script
result, errors = interpreter.communicate(script)
if interpreter.poll() or errors:
return script
return result
58 changes: 58 additions & 0 deletions jsbeautifier/unpackers/javascriptobfuscator.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
#
# simple unpacker/deobfuscator for scripts messed up with
# javascriptobfuscator.com
#
# written by Einar Lielmanis <einar@jsbeautifier.org>
# rewritten in Python by Stefano Sanfilippo <a.little.coder@gmail.com>
#
# Will always return valid javascript: if `detect()` is false, `code` is
# returned, unmodified.
#
# usage:
#
# if javascriptobfuscator.detect(some_string):
# some_string = javascriptobfuscator.unpack(some_string)
#

"""deobfuscator for scripts messed up with JavascriptObfuscator.com"""

import re

PRIORITY = 1

def smartsplit(code):
"""Split `code` at " symbol, only if it is not escaped."""
strings = []
pos = 0
while pos < len(code):
if code[pos] == '"':
word = '' # new word
pos += 1
while pos < len(code):
if code[pos] == '"':
break
if code[pos] == '\\':
word += '\\'
pos += 1
word += code[pos]
pos += 1
strings.append('"%s"' % word)
pos += 1
return strings

def detect(code):
"""Detects if `code` is JavascriptObfuscator.com packed."""
# prefer `is not` idiom, so that a true boolean is returned
return (re.search(r'^var _0x[a-f0-9]+ ?\= ?\[', code) is not None)

def unpack(code):
"""Unpacks JavascriptObfuscator.com packed code."""
if detect(code):
matches = re.search(r'var (_0x[a-f\d]+) ?\= ?\[(.*?)\];', code)
if matches:
variable = matches.group(1)
dictionary = smartsplit(matches.group(2))
code = code[len(matches.group(0)):]
for key, value in enumerate(dictionary):
code = code.replace(r'%s[%s]' % (variable, key), value)
return code
86 changes: 86 additions & 0 deletions jsbeautifier/unpackers/myobfuscate.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
#
# deobfuscator for scripts messed up with myobfuscate.com
# by Einar Lielmanis <einar@jsbeautifier.org>
#
# written by Stefano Sanfilippo <a.little.coder@gmail.com>
#
# usage:
#
# if detect(some_string):
# unpacked = unpack(some_string)
#

# CAVEAT by Einar Lielmanis

#
# You really don't want to obfuscate your scripts there: they're tracking
# your unpackings, your script gets turned into something like this,
# as of 2011-08-26:
#
# var _escape = 'your_script_escaped';
# var _111 = document.createElement('script');
# _111.src = 'http://api.www.myobfuscate.com/?getsrc=ok' +
# '&ref=' + encodeURIComponent(document.referrer) +
# '&url=' + encodeURIComponent(document.URL);
# var 000 = document.getElementsByTagName('head')[0];
# 000.appendChild(_111);
# document.write(unescape(_escape));
#

"""Deobfuscator for scripts messed up with MyObfuscate.com"""

import re
import base64

# Python 2 retrocompatibility
# pylint: disable=F0401
# pylint: disable=E0611
try:
from urllib import unquote
except ImportError:
from urllib.parse import unquote

from jsbeautifier.unpackers import UnpackingError

PRIORITY = 1

CAVEAT = """//
// Unpacker warning: be careful when using myobfuscate.com for your projects:
// scripts obfuscated by the free online version call back home.
//
"""

SIGNATURE = (r'["\x41\x42\x43\x44\x45\x46\x47\x48\x49\x4A\x4B\x4C\x4D\x4E\x4F'
r'\x50\x51\x52\x53\x54\x55\x56\x57\x58\x59\x5A\x61\x62\x63\x64\x65'
r'\x66\x67\x68\x69\x6A\x6B\x6C\x6D\x6E\x6F\x70\x71\x72\x73\x74\x75'
r'\x76\x77\x78\x79\x7A\x30\x31\x32\x33\x34\x35\x36\x37\x38\x39\x2B'
r'\x2F\x3D","","\x63\x68\x61\x72\x41\x74","\x69\x6E\x64\x65\x78'
r'\x4F\x66","\x66\x72\x6F\x6D\x43\x68\x61\x72\x43\x6F\x64\x65","'
r'\x6C\x65\x6E\x67\x74\x68"]')

def detect(source):
"""Detects MyObfuscate.com packer."""
return SIGNATURE in source

def unpack(source):
"""Unpacks js code packed with MyObfuscate.com"""
if not detect(source):
return source
payload = unquote(_filter(source))
match = re.search(r"^var _escape\='<script>(.*)<\/script>'",
payload, re.DOTALL)
polished = match.group(1) if match else source
return CAVEAT + polished

def _filter(source):
"""Extracts and decode payload (original file) from `source`"""
try:
varname = re.search(r'eval\(\w+\(\w+\((\w+)\)\)\);', source).group(1)
reverse = re.search(r"var +%s *\= *'(.*)';" % varname, source).group(1)
except AttributeError:
raise UnpackingError('Malformed MyObfuscate data.')
try:
return base64.b64decode(reverse[::-1].encode('utf8')).decode('utf8')
except TypeError:
raise UnpackingError('MyObfuscate payload is not base64-encoded.')
Loading

0 comments on commit d8bdf34

Please sign in to comment.