aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorPhilipp Hagemeister <phihag@phihag.de>2014-07-18 14:20:34 +0200
committerPhilipp Hagemeister <phihag@phihag.de>2014-07-19 23:05:07 +0200
commit0cb2056304178ae8944e84c5bc72f96102291a12 (patch)
tree440fb20b2907a34f89cd9a4f4cbf04313b7d572b
parent5425626790a46f9b5bdecf4e33bb254c4c2423ea (diff)
downloadyoutube-dl-0cb2056304178ae8944e84c5bc72f96102291a12.tar.xz
[swfinterp] Start working on basic tests
-rw-r--r--test/swftests/.gitignore1
-rw-r--r--test/swftests/LocalVars.as13
-rw-r--r--test/test_swfinterp.py73
-rw-r--r--youtube_dl/swfinterp.py56
4 files changed, 126 insertions, 17 deletions
diff --git a/test/swftests/.gitignore b/test/swftests/.gitignore
new file mode 100644
index 000000000..da97ff7ca
--- /dev/null
+++ b/test/swftests/.gitignore
@@ -0,0 +1 @@
+*.swf
diff --git a/test/swftests/LocalVars.as b/test/swftests/LocalVars.as
new file mode 100644
index 000000000..b2911a9f3
--- /dev/null
+++ b/test/swftests/LocalVars.as
@@ -0,0 +1,13 @@
+// input: [1, 2]
+// output: 3
+
+package {
+public class LocalVars {
+ public static function main(a:int, b:int):int{
+ var c:int = a + b + b;
+ var d:int = c - b;
+ var e:int = d;
+ return e;
+ }
+}
+}
diff --git a/test/test_swfinterp.py b/test/test_swfinterp.py
new file mode 100644
index 000000000..98a14a006
--- /dev/null
+++ b/test/test_swfinterp.py
@@ -0,0 +1,73 @@
+#!/usr/bin/env python
+
+# Allow direct execution
+import os
+import sys
+import unittest
+sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
+
+
+import io
+import json
+import re
+import subprocess
+
+from youtube_dl.swfinterp import SWFInterpreter
+
+
+TEST_DIR = os.path.join(
+ os.path.dirname(os.path.abspath(__file__)), 'swftests')
+
+
+class TestSWFInterpreter(unittest.TestCase):
+ pass
+
+
+for testfile in os.listdir(TEST_DIR):
+ m = re.match(r'^(.*)\.(as)$', testfile)
+ if not m:
+ continue
+ test_id = m.group(1)
+
+ def test_func(self):
+ as_file = os.path.join(TEST_DIR, testfile)
+ swf_file = os.path.join(TEST_DIR, test_id + '.swf')
+ if ((not os.path.exists(swf_file))
+ or os.path.getmtime(swf_file) < os.path.getmtime(as_file)):
+ # Recompile
+ try:
+ subprocess.check_call(['mxmlc', '--output', swf_file, as_file])
+ except OSError as ose:
+ if ose.errno == errno.ENOENT:
+ print('mxmlc not found! Skipping test.')
+ return
+ raise
+
+ with open(swf_file, 'rb') as swf_f:
+ swf_content = swf_f.read()
+ swfi = SWFInterpreter(swf_content)
+
+ with io.open(as_file, 'r', encoding='utf-8') as as_f:
+ as_content = as_f.read()
+
+ def _find_spec(key):
+ m = re.search(
+ r'(?m)^//\s*%s:\s*(.*?)\n' % re.escape(key), as_content)
+ if not m:
+ raise ValueError('Cannot find %s in %s' % (key, testfile))
+ return json.loads(m.group(1))
+
+ input_args = _find_spec('input')
+ output = _find_spec('output')
+
+ swf_class = swfi.extract_class(test_id)
+ func = swfi.extract_function(swf_class, 'main')
+ res = func(input_args)
+ self.assertEqual(res, output)
+
+ test_func.__name__ = str('test_swf_' + test_id)
+ setattr(TestSWFInterpreter, test_func.__name__, test_func)
+
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/youtube_dl/swfinterp.py b/youtube_dl/swfinterp.py
index 1cd292138..49fade364 100644
--- a/youtube_dl/swfinterp.py
+++ b/youtube_dl/swfinterp.py
@@ -8,8 +8,22 @@ import zlib
from .utils import ExtractorError
-def _extract_tags(content):
- pos = 0
+def _extract_tags(file_contents):
+ if file_contents[1:3] != b'WS':
+ raise ExtractorError(
+ 'Not an SWF file; header is %r' % file_contents[:3])
+ if file_contents[:1] == b'C':
+ content = zlib.decompress(file_contents[8:])
+ else:
+ raise NotImplementedError(
+ 'Unsupported compression format %r' %
+ file_contents[:1])
+
+ # Determine number of bits in framesize rectangle
+ framesize_nbits = struct.unpack('!B', content[:1])[0] >> 3
+ framesize_len = (5 + 4 * framesize_nbits + 7) // 8
+
+ pos = framesize_len + 2 + 2
while pos < len(content):
header16 = struct.unpack('<H', content[pos:pos + 2])[0]
pos += 2
@@ -18,7 +32,9 @@ def _extract_tags(content):
if tag_len == 0x3f:
tag_len = struct.unpack('<I', content[pos:pos + 4])[0]
pos += 4
- assert pos + tag_len <= len(content)
+ assert pos + tag_len <= len(content), \
+ ('Tag %d ends at %d+%d - that\'s longer than the file (%d)'
+ % (tag_code, pos, tag_len, len(content)))
yield (tag_code, content[pos:pos + tag_len])
pos += tag_len
@@ -88,8 +104,7 @@ def _read_string(reader):
def _read_bytes(count, reader):
- if reader is None:
- reader = code_reader
+ assert count >= 0
resb = reader.read(count)
assert len(resb) == count
return resb
@@ -103,18 +118,8 @@ def _read_byte(reader):
class SWFInterpreter(object):
def __init__(self, file_contents):
- if file_contents[1:3] != b'WS':
- raise ExtractorError(
- 'Not an SWF file; header is %r' % file_contents[:3])
- if file_contents[:1] == b'C':
- content = zlib.decompress(file_contents[8:])
- else:
- raise NotImplementedError(
- 'Unsupported compression format %r' %
- file_contents[:1])
-
code_tag = next(tag
- for tag_code, tag in _extract_tags(content)
+ for tag_code, tag in _extract_tags(file_contents)
if tag_code == 82)
p = code_tag.index(b'\0', 4) + 1
code_reader = io.BytesIO(code_tag[p:])
@@ -139,7 +144,7 @@ class SWFInterpreter(object):
for _c in range(1, uint_count):
u32()
double_count = u30()
- read_bytes((double_count - 1) * 8)
+ read_bytes(max(0, (double_count - 1)) * 8)
string_count = u30()
constant_strings = ['']
for _c in range(1, string_count):
@@ -349,6 +354,9 @@ class SWFInterpreter(object):
elif opcode == 36: # pushbyte
v = _read_byte(coder)
stack.append(v)
+ elif opcode == 42: # dup
+ value = stack[-1]
+ stack.append(value)
elif opcode == 44: # pushstring
idx = u30()
stack.append(constant_strings[idx])
@@ -468,10 +476,24 @@ class SWFInterpreter(object):
obj = stack.pop()
assert isinstance(obj, list)
stack.append(obj[idx])
+ elif opcode == 115: # convert_
+ value = stack.pop()
+ intvalue = int(value)
+ stack.append(intvalue)
elif opcode == 128: # coerce
u30()
elif opcode == 133: # coerce_s
assert isinstance(stack[-1], (type(None), compat_str))
+ elif opcode == 160: # add
+ value2 = stack.pop()
+ value1 = stack.pop()
+ res = value1 + value2
+ stack.append(res)
+ elif opcode == 161: # subtract
+ value2 = stack.pop()
+ value1 = stack.pop()
+ res = value1 - value2
+ stack.append(res)
elif opcode == 164: # modulo
value2 = stack.pop()
value1 = stack.pop()