]>
Commit | Line | Data |
---|---|---|
2b25cb5d PH |
1 | from __future__ import unicode_literals |
2 | ||
3 | import re | |
4 | ||
5 | from .utils import ( | |
6 | ExtractorError, | |
7 | ) | |
8 | ||
9 | ||
10 | class JSInterpreter(object): | |
11 | def __init__(self, code): | |
12 | self.code = code | |
13 | self._functions = {} | |
ad25aee2 | 14 | self._objects = {} |
2b25cb5d PH |
15 | |
16 | def interpret_statement(self, stmt, local_vars, allow_recursion=20): | |
17 | if allow_recursion < 0: | |
18 | raise ExtractorError('Recursion limit reached') | |
19 | ||
20 | if stmt.startswith('var '): | |
21 | stmt = stmt[len('var '):] | |
22 | ass_m = re.match(r'^(?P<out>[a-z]+)(?:\[(?P<index>[^\]]+)\])?' + | |
23 | r'=(?P<expr>.*)$', stmt) | |
24 | if ass_m: | |
25 | if ass_m.groupdict().get('index'): | |
26 | def assign(val): | |
27 | lvar = local_vars[ass_m.group('out')] | |
28 | idx = self.interpret_expression( | |
29 | ass_m.group('index'), local_vars, allow_recursion) | |
30 | assert isinstance(idx, int) | |
31 | lvar[idx] = val | |
32 | return val | |
33 | expr = ass_m.group('expr') | |
34 | else: | |
35 | def assign(val): | |
36 | local_vars[ass_m.group('out')] = val | |
37 | return val | |
38 | expr = ass_m.group('expr') | |
39 | elif stmt.startswith('return '): | |
40 | assign = lambda v: v | |
41 | expr = stmt[len('return '):] | |
42 | else: | |
43 | raise ExtractorError( | |
44 | 'Cannot determine left side of statement in %r' % stmt) | |
45 | ||
46 | v = self.interpret_expression(expr, local_vars, allow_recursion) | |
47 | return assign(v) | |
48 | ||
49 | def interpret_expression(self, expr, local_vars, allow_recursion): | |
50 | if expr.isdigit(): | |
51 | return int(expr) | |
52 | ||
53 | if expr.isalpha(): | |
54 | return local_vars[expr] | |
55 | ||
56 | m = re.match(r'^(?P<in>[a-z]+)\.(?P<member>.*)$', expr) | |
57 | if m: | |
58 | member = m.group('member') | |
ad25aee2 JMF |
59 | variable = m.group('in') |
60 | ||
61 | if variable not in local_vars: | |
62 | if variable not in self._objects: | |
63 | self._objects[variable] = self.extract_object(variable) | |
64 | obj = self._objects[variable] | |
65 | key, args = member.split('(', 1) | |
66 | args = args.strip(')') | |
67 | argvals = [int(v) if v.isdigit() else local_vars[v] | |
68 | for v in args.split(',')] | |
69 | return obj[key](argvals) | |
70 | ||
71 | val = local_vars[variable] | |
2b25cb5d PH |
72 | if member == 'split("")': |
73 | return list(val) | |
74 | if member == 'join("")': | |
61989fb5 | 75 | return ''.join(val) |
2b25cb5d PH |
76 | if member == 'length': |
77 | return len(val) | |
78 | if member == 'reverse()': | |
79 | return val[::-1] | |
80 | slice_m = re.match(r'slice\((?P<idx>.*)\)', member) | |
81 | if slice_m: | |
82 | idx = self.interpret_expression( | |
83 | slice_m.group('idx'), local_vars, allow_recursion - 1) | |
84 | return val[idx:] | |
85 | ||
86 | m = re.match( | |
87 | r'^(?P<in>[a-z]+)\[(?P<idx>.+)\]$', expr) | |
88 | if m: | |
89 | val = local_vars[m.group('in')] | |
90 | idx = self.interpret_expression( | |
91 | m.group('idx'), local_vars, allow_recursion - 1) | |
92 | return val[idx] | |
93 | ||
94 | m = re.match(r'^(?P<a>.+?)(?P<op>[%])(?P<b>.+?)$', expr) | |
95 | if m: | |
96 | a = self.interpret_expression( | |
97 | m.group('a'), local_vars, allow_recursion) | |
98 | b = self.interpret_expression( | |
99 | m.group('b'), local_vars, allow_recursion) | |
100 | return a % b | |
101 | ||
102 | m = re.match( | |
103 | r'^(?P<func>[a-zA-Z$]+)\((?P<args>[a-z0-9,]+)\)$', expr) | |
104 | if m: | |
105 | fname = m.group('func') | |
106 | if fname not in self._functions: | |
107 | self._functions[fname] = self.extract_function(fname) | |
108 | argvals = [int(v) if v.isdigit() else local_vars[v] | |
109 | for v in m.group('args').split(',')] | |
110 | return self._functions[fname](argvals) | |
111 | raise ExtractorError('Unsupported JS expression %r' % expr) | |
112 | ||
ad25aee2 JMF |
113 | def extract_object(self, objname): |
114 | obj = {} | |
115 | obj_m = re.search( | |
116 | (r'(?:var\s+)?%s\s*=\s*\{' % re.escape(objname)) + | |
117 | r'\s*(?P<fields>([a-zA-Z$]+\s*:\s*function\(.*?\)\s*\{.*?\})*)' + | |
118 | r'\}\s*;', | |
119 | self.code) | |
120 | fields = obj_m.group('fields') | |
121 | # Currently, it only supports function definitions | |
122 | fields_m = re.finditer( | |
123 | r'(?P<key>[a-zA-Z$]+)\s*:\s*function' | |
124 | r'\((?P<args>[a-z,]+)\){(?P<code>[^}]+)}', | |
125 | fields) | |
126 | for f in fields_m: | |
127 | argnames = f.group('args').split(',') | |
128 | obj[f.group('key')] = self.build_function(argnames, f.group('code')) | |
129 | ||
130 | return obj | |
131 | ||
2b25cb5d PH |
132 | def extract_function(self, funcname): |
133 | func_m = re.search( | |
fc040bfd | 134 | (r'(?:function %s|[{;]%s\s*=\s*function)' % ( |
77ffa957 | 135 | re.escape(funcname), re.escape(funcname))) + |
2b25cb5d PH |
136 | r'\((?P<args>[a-z,]+)\){(?P<code>[^}]+)}', |
137 | self.code) | |
77ffa957 PH |
138 | if func_m is None: |
139 | raise ExtractorError('Could not find JS function %r' % funcname) | |
2b25cb5d PH |
140 | argnames = func_m.group('args').split(',') |
141 | ||
ad25aee2 JMF |
142 | return self.build_function(argnames, func_m.group('code')) |
143 | ||
144 | def build_function(self, argnames, code): | |
2b25cb5d PH |
145 | def resf(args): |
146 | local_vars = dict(zip(argnames, args)) | |
ad25aee2 | 147 | for stmt in code.split(';'): |
2b25cb5d PH |
148 | res = self.interpret_statement(stmt, local_vars) |
149 | return res | |
150 | return resf |