]>
jfr.im git - yt-dlp.git/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):
35 self
._objects
= objects
37 def interpret_statement(self
, stmt
, local_vars
, allow_recursion
=100):
38 if allow_recursion
< 0:
39 raise ExtractorError('Recursion limit reached')
43 stmt_m
= re
.match(r
'var\s', stmt
)
45 expr
= stmt
[len(stmt_m
.group(0)):]
47 return_m
= re
.match(r
'return(?:\s+|$)', stmt
)
49 expr
= stmt
[len(return_m
.group(0)):]
52 # Try interpreting it as an expression
55 v
= self
.interpret_expression(expr
, local_vars
, allow_recursion
)
56 return v
, should_abort
58 def interpret_expression(self
, expr
, local_vars
, allow_recursion
):
61 if expr
== '': # Empty expression
64 if expr
.startswith('('):
66 for m
in re
.finditer(r
'[()]', expr
):
72 sub_expr
= expr
[1:m
.start()]
73 sub_result
= self
.interpret_expression(
74 sub_expr
, local_vars
, allow_recursion
)
75 remaining_expr
= expr
[m
.end():].strip()
76 if not remaining_expr
:
79 expr
= json
.dumps(sub_result
) + remaining_expr
82 raise ExtractorError('Premature end of parens in %r' % expr
)
84 for op
, opfunc
in _ASSIGN_OPERATORS
:
86 (?P<out>%s)(?:\[(?P<index>[^\]]+?)\])?
88 (?P<expr>.*)$''' % (_NAME_RE
, re
.escape(op
)), expr
)
91 right_val
= self
.interpret_expression(
92 m
.group('expr'), local_vars
, allow_recursion
- 1)
94 if m
.groupdict().get('index'):
95 lvar
= local_vars
[m
.group('out')]
96 idx
= self
.interpret_expression(
97 m
.group('index'), local_vars
, allow_recursion
)
98 assert isinstance(idx
, int)
100 val
= opfunc(cur
, right_val
)
104 cur
= local_vars
.get(m
.group('out'))
105 val
= opfunc(cur
, right_val
)
106 local_vars
[m
.group('out')] = val
113 r
'(?!if|return|true|false)(?P<name>%s)$' % _NAME_RE
,
116 return local_vars
[var_m
.group('name')]
119 return json
.loads(expr
)
124 r
'(?P<var>%s)\.(?P<member>[^(]+)(?:\(+(?P<args>[^()]*)\))?$' % _NAME_RE
,
127 variable
= m
.group('var')
128 member
= m
.group('member')
129 arg_str
= m
.group('args')
131 if variable
in local_vars
:
132 obj
= local_vars
[variable
]
134 if variable
not in self
._objects
:
135 self
._objects
[variable
] = self
.extract_object(variable
)
136 obj
= self
._objects
[variable
]
140 if member
== 'length':
144 assert expr
.endswith(')')
150 self
.interpret_expression(v
, local_vars
, allow_recursion
)
151 for v
in arg_str
.split(',')])
153 if member
== 'split':
154 assert argvals
== ('',)
157 assert len(argvals
) == 1
158 return argvals
[0].join(obj
)
159 if member
== 'reverse':
160 assert len(argvals
) == 0
163 if member
== 'slice':
164 assert len(argvals
) == 1
165 return obj
[argvals
[0]:]
166 if member
== 'splice':
167 assert isinstance(obj
, list)
168 index
, howMany
= argvals
170 for i
in range(index
, min(index
+ howMany
, len(obj
))):
171 res
.append(obj
.pop(index
))
174 return obj
[member
](argvals
)
177 r
'(?P<in>%s)\[(?P<idx>.+)\]$' % _NAME_RE
, expr
)
179 val
= local_vars
[m
.group('in')]
180 idx
= self
.interpret_expression(
181 m
.group('idx'), local_vars
, allow_recursion
- 1)
184 for op
, opfunc
in _OPERATORS
:
185 m
= re
.match(r
'(?P<x>.+?)%s(?P<y>.+)' % re
.escape(op
), expr
)
188 x
, abort
= self
.interpret_statement(
189 m
.group('x'), local_vars
, allow_recursion
- 1)
191 raise ExtractorError(
192 'Premature left-side return of %s in %r' % (op
, expr
))
193 y
, abort
= self
.interpret_statement(
194 m
.group('y'), local_vars
, allow_recursion
- 1)
196 raise ExtractorError(
197 'Premature right-side return of %s in %r' % (op
, expr
))
201 r
'^(?P<func>%s)\((?P<args>[a-zA-Z0-9_$,]+)\)$' % _NAME_RE
, expr
)
203 fname
= m
.group('func')
205 int(v
) if v
.isdigit() else local_vars
[v
]
206 for v
in m
.group('args').split(',')])
207 if fname
not in self
._functions
:
208 self
._functions
[fname
] = self
.extract_function(fname
)
209 return self
._functions
[fname
](argvals
)
211 raise ExtractorError('Unsupported JS expression %r' % expr
)
213 def extract_object(self
, objname
):
216 (r
'(?:var\s+)?%s\s*=\s*\{' % re
.escape(objname
)) +
217 r
'\s*(?P<fields>([a-zA-Z$0-9]+\s*:\s*function\(.*?\)\s*\{.*?\}(?:,\s*)?)*)' +
220 fields
= obj_m
.group('fields')
221 # Currently, it only supports function definitions
222 fields_m
= re
.finditer(
223 r
'(?P<key>[a-zA-Z$0-9]+)\s*:\s*function'
224 r
'\((?P<args>[a-z,]+)\){(?P<code>[^}]+)}',
227 argnames
= f
.group('args').split(',')
228 obj
[f
.group('key')] = self
.build_function(argnames
, f
.group('code'))
232 def extract_function(self
, funcname
):
235 (?:function\s+%s|[{;]%s\s*=\s*function|var\s+%s\s*=\s*function)\s*
236 \((?P<args>[^)]*)\)\s*
237 \{(?P<code>[^}]+)\}''' % (
238 re
.escape(funcname
), re
.escape(funcname
), re
.escape(funcname
)),
241 raise ExtractorError('Could not find JS function %r' % funcname
)
242 argnames
= func_m
.group('args').split(',')
244 return self
.build_function(argnames
, func_m
.group('code'))
246 def call_function(self
, funcname
, *args
):
247 f
= self
.extract_function(funcname
)
250 def build_function(self
, argnames
, code
):
252 local_vars
= dict(zip(argnames
, args
))
253 for stmt
in code
.split(';'):
254 res
, abort
= self
.interpret_statement(stmt
, local_vars
)