aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorbashonly <88596187+bashonly@users.noreply.github.com>2025-03-27 17:28:30 -0500
committerGitHub <noreply@github.com>2025-03-27 22:28:30 +0000
commita8b9ff3c2a0ae25735e580173becc78545b92572 (patch)
treec5614047b41f100867b0e21c53529b1a8ccaae69
parent6eaa574c8217ea9b9c5177fece0714fa622da34c (diff)
[jsinterp] Fix nested attributes and object extraction (#12760)
Authored by: bashonly, seproDev Co-authored-by: sepro <sepro@sepr0.com>
-rw-r--r--test/test_jsinterp.py6
-rw-r--r--yt_dlp/jsinterp.py17
2 files changed, 17 insertions, 6 deletions
diff --git a/test/test_jsinterp.py b/test/test_jsinterp.py
index d3076298c..b14069ccc 100644
--- a/test/test_jsinterp.py
+++ b/test/test_jsinterp.py
@@ -118,6 +118,7 @@ class TestJSInterpreter(unittest.TestCase):
self._test('function f(){var x = 20; x = 30 + 1; return x;}', 31)
self._test('function f(){var x = 20; x += 30 + 1; return x;}', 51)
self._test('function f(){var x = 20; x -= 30 + 1; return x;}', -11)
+ self._test('function f(){var x = 2; var y = ["a", "b"]; y[x%y["length"]]="z"; return y}', ['z', 'b'])
@unittest.skip('Not implemented')
def test_comments(self):
@@ -403,6 +404,8 @@ class TestJSInterpreter(unittest.TestCase):
test_result = list('test')
tests = [
'function f(a, b){return a.split(b)}',
+ 'function f(a, b){return a["split"](b)}',
+ 'function f(a, b){let x = ["split"]; return a[x[0]](b)}',
'function f(a, b){return String.prototype.split.call(a, b)}',
'function f(a, b){return String.prototype.split.apply(a, [b])}',
]
@@ -441,6 +444,9 @@ class TestJSInterpreter(unittest.TestCase):
self._test('function f(){return "012345678".slice(-1, 1)}', '')
self._test('function f(){return "012345678".slice(-3, -1)}', '67')
+ def test_splice(self):
+ self._test('function f(){var T = ["0", "1", "2"]; T["splice"](2, 1, "0")[0]; return T }', ['0', '1', '0'])
+
def test_js_number_to_string(self):
for test, radix, expected in [
(0, None, '0'),
diff --git a/yt_dlp/jsinterp.py b/yt_dlp/jsinterp.py
index d46b78f64..b59fb2c61 100644
--- a/yt_dlp/jsinterp.py
+++ b/yt_dlp/jsinterp.py
@@ -188,6 +188,7 @@ _COMP_OPERATORS = {'===', '!==', '==', '!=', '<=', '>=', '<', '>'}
_NAME_RE = r'[a-zA-Z_$][\w$]*'
_MATCHING_PARENS = dict(zip(*zip('()', '{}', '[]')))
_QUOTES = '\'"/'
+_NESTED_BRACKETS = r'[^[\]]+(?:\[[^[\]]+(?:\[[^\]]+\])?\])?'
class JS_Undefined:
@@ -606,15 +607,18 @@ class JSInterpreter:
m = re.match(fr'''(?x)
(?P<assign>
- (?P<out>{_NAME_RE})(?:\[(?P<index>[^\]]+?)\])?\s*
+ (?P<out>{_NAME_RE})(?:\[(?P<index>{_NESTED_BRACKETS})\])?\s*
(?P<op>{"|".join(map(re.escape, set(_OPERATORS) - _COMP_OPERATORS))})?
=(?!=)(?P<expr>.*)$
)|(?P<return>
(?!if|return|true|false|null|undefined|NaN)(?P<name>{_NAME_RE})$
+ )|(?P<attribute>
+ (?P<var>{_NAME_RE})(?:
+ (?P<nullish>\?)?\.(?P<member>[^(]+)|
+ \[(?P<member2>{_NESTED_BRACKETS})\]
+ )\s*
)|(?P<indexing>
(?P<in>{_NAME_RE})\[(?P<idx>.+)\]$
- )|(?P<attribute>
- (?P<var>{_NAME_RE})(?:(?P<nullish>\?)?\.(?P<member>[^(]+)|\[(?P<member2>[^\]]+)\])\s*
)|(?P<function>
(?P<fname>{_NAME_RE})\((?P<args>.*)\)$
)''', expr)
@@ -707,7 +711,7 @@ class JSInterpreter:
if obj is NO_DEFAULT:
if variable not in self._objects:
try:
- self._objects[variable] = self.extract_object(variable)
+ self._objects[variable] = self.extract_object(variable, local_vars)
except self.Exception:
if not nullish:
raise
@@ -847,7 +851,7 @@ class JSInterpreter:
raise self.Exception('Cannot return from an expression', expr)
return ret
- def extract_object(self, objname):
+ def extract_object(self, objname, *global_stack):
_FUNC_NAME_RE = r'''(?:[a-zA-Z$0-9]+|"[a-zA-Z$0-9]+"|'[a-zA-Z$0-9]+')'''
obj = {}
obj_m = re.search(
@@ -869,7 +873,8 @@ class JSInterpreter:
for f in fields_m:
argnames = f.group('args').split(',')
name = remove_quotes(f.group('key'))
- obj[name] = function_with_repr(self.build_function(argnames, f.group('code')), f'F<{name}>')
+ obj[name] = function_with_repr(
+ self.build_function(argnames, f.group('code'), *global_stack), f'F<{name}>')
return obj