]>
Raphaƫl G. Git Repositories - youtubedl/blob - youtube_dl/jsinterp.py
1 from __future__
import unicode_literals
15 ('>>', operator
.rshift
),
16 ('<<', operator
.lshift
),
20 ('/', operator
.truediv
),
23 _ASSIGN_OPERATORS
= [(op
+ '=', opfunc
) for op
, opfunc
in _OPERATORS
]
24 _ASSIGN_OPERATORS
.append(('=', lambda cur
, right
: right
))
26 _NAME_RE
= r
'[a-zA-Z_$][a-zA-Z_$0-9]*'
29 class JSInterpreter(object):
30 def __init__(self
, code
, objects
=None):
33 self
.code
= self
._remove
_comments
(code
)
35 self
._objects
= objects
37 def _remove_comments(self
, code
):
38 return re
.sub(r
'(?s)/\*.*?\*/', '', code
)
40 def interpret_statement(self
, stmt
, local_vars
, allow_recursion
=100):
41 if allow_recursion
< 0:
42 raise ExtractorError('Recursion limit reached')
46 stmt_m
= re
.match(r
'var\s', stmt
)
48 expr
= stmt
[len(stmt_m
.group(0)):]
50 return_m
= re
.match(r
'return(?:\s+|$)', stmt
)
52 expr
= stmt
[len(return_m
.group(0)):]
55 # Try interpreting it as an expression
58 v
= self
.interpret_expression(expr
, local_vars
, allow_recursion
)
59 return v
, should_abort
61 def interpret_expression(self
, expr
, local_vars
, allow_recursion
):
64 if expr
== '': # Empty expression
67 if expr
.startswith('('):
69 for m
in re
.finditer(r
'[()]', expr
):
75 sub_expr
= expr
[1:m
.start()]
76 sub_result
= self
.interpret_expression(
77 sub_expr
, local_vars
, allow_recursion
)
78 remaining_expr
= expr
[m
.end():].strip()
79 if not remaining_expr
:
82 expr
= json
.dumps(sub_result
) + remaining_expr
85 raise ExtractorError('Premature end of parens in %r' % expr
)
87 for op
, opfunc
in _ASSIGN_OPERATORS
:
89 (?P<out>%s)(?:\[(?P<index>[^\]]+?)\])?
91 (?P<expr>.*)$''' % (_NAME_RE
, re
.escape(op
)), expr
)
94 right_val
= self
.interpret_expression(
95 m
.group('expr'), local_vars
, allow_recursion
- 1)
97 if m
.groupdict().get('index'):
98 lvar
= local_vars
[m
.group('out')]
99 idx
= self
.interpret_expression(
100 m
.group('index'), local_vars
, allow_recursion
)
101 assert isinstance(idx
, int)
103 val
= opfunc(cur
, right_val
)
107 cur
= local_vars
.get(m
.group('out'))
108 val
= opfunc(cur
, right_val
)
109 local_vars
[m
.group('out')] = val
116 r
'(?!if|return|true|false)(?P<name>%s)$' % _NAME_RE
,
119 return local_vars
[var_m
.group('name')]
122 return json
.loads(expr
)
127 r
'(?P<var>%s)\.(?P<member>[^(]+)(?:\(+(?P<args>[^()]*)\))?$' % _NAME_RE
,
130 variable
= m
.group('var')
131 member
= m
.group('member')
132 arg_str
= m
.group('args')
134 if variable
in local_vars
:
135 obj
= local_vars
[variable
]
137 if variable
not in self
._objects
:
138 self
._objects
[variable
] = self
.extract_object(variable
)
139 obj
= self
._objects
[variable
]
143 if member
== 'length':
147 assert expr
.endswith(')')
153 self
.interpret_expression(v
, local_vars
, allow_recursion
)
154 for v
in arg_str
.split(',')])
156 if member
== 'split':
157 assert argvals
== ('',)
160 assert len(argvals
) == 1
161 return argvals
[0].join(obj
)
162 if member
== 'reverse':
163 assert len(argvals
) == 0
166 if member
== 'slice':
167 assert len(argvals
) == 1
168 return obj
[argvals
[0]:]
169 if member
== 'splice':
170 assert isinstance(obj
, list)
171 index
, howMany
= argvals
173 for i
in range(index
, min(index
+ howMany
, len(obj
))):
174 res
.append(obj
.pop(index
))
177 return obj
[member
](argvals
)
180 r
'(?P<in>%s)\[(?P<idx>.+)\]$' % _NAME_RE
, expr
)
182 val
= local_vars
[m
.group('in')]
183 idx
= self
.interpret_expression(
184 m
.group('idx'), local_vars
, allow_recursion
- 1)
187 for op
, opfunc
in _OPERATORS
:
188 m
= re
.match(r
'(?P<x>.+?)%s(?P<y>.+)' % re
.escape(op
), expr
)
191 x
, abort
= self
.interpret_statement(
192 m
.group('x'), local_vars
, allow_recursion
- 1)
194 raise ExtractorError(
195 'Premature left-side return of %s in %r' % (op
, expr
))
196 y
, abort
= self
.interpret_statement(
197 m
.group('y'), local_vars
, allow_recursion
- 1)
199 raise ExtractorError(
200 'Premature right-side return of %s in %r' % (op
, expr
))
204 r
'^(?P<func>%s)\((?P<args>[a-zA-Z0-9_$,]+)\)$' % _NAME_RE
, expr
)
206 fname
= m
.group('func')
208 int(v
) if v
.isdigit() else local_vars
[v
]
209 for v
in m
.group('args').split(',')])
210 if fname
not in self
._functions
:
211 self
._functions
[fname
] = self
.extract_function(fname
)
212 return self
._functions
[fname
](argvals
)
214 raise ExtractorError('Unsupported JS expression %r' % expr
)
216 def extract_object(self
, objname
):
219 (r
'(?:var\s+)?%s\s*=\s*\{' % re
.escape(objname
)) +
220 r
'\s*(?P<fields>([a-zA-Z$0-9]+\s*:\s*function\(.*?\)\s*\{.*?\})*)' +
223 fields
= obj_m
.group('fields')
224 # Currently, it only supports function definitions
225 fields_m
= re
.finditer(
226 r
'(?P<key>[a-zA-Z$0-9]+)\s*:\s*function'
227 r
'\((?P<args>[a-z,]+)\){(?P<code>[^}]+)}',
230 argnames
= f
.group('args').split(',')
231 obj
[f
.group('key')] = self
.build_function(argnames
, f
.group('code'))
235 def extract_function(self
, funcname
):
238 (?:function\s+%s|[{;]%s\s*=\s*function)\s*
239 \((?P<args>[^)]*)\)\s*
240 \{(?P<code>[^}]+)\}''' % (
241 re
.escape(funcname
), re
.escape(funcname
)),
244 raise ExtractorError('Could not find JS function %r' % funcname
)
245 argnames
= func_m
.group('args').split(',')
247 return self
.build_function(argnames
, func_m
.group('code'))
249 def call_function(self
, funcname
, *args
):
250 f
= self
.extract_function(funcname
)
253 def build_function(self
, argnames
, code
):
255 local_vars
= dict(zip(argnames
, args
))
256 for stmt
in code
.split(';'):
257 res
, abort
= self
.interpret_statement(stmt
, local_vars
)