mirror of
https://github.com/ytdl-org/youtube-dl
synced 2024-07-27 18:33:31 +02:00
Compare commits
16 Commits
8c7afcb147
...
1e8e223b30
Author | SHA1 | Date | |
---|---|---|---|
|
1e8e223b30 | ||
|
e1b3fa242c | ||
|
451046d62a | ||
|
16f5bbc464 | ||
|
d35ce6ce95 | ||
|
76ac69917e | ||
|
756f6b45c7 | ||
|
43a74c5fa5 | ||
|
a452f9437c | ||
|
36801c62df | ||
|
f4b47754d9 | ||
|
37cea84f77 | ||
|
4652109643 | ||
|
3c466186a8 | ||
|
525c46942d | ||
|
ce7bb8d0ee |
70
README.md
70
README.md
|
@ -1,29 +1,29 @@
|
|||
[![Build Status](https://github.com/ytdl-org/youtube-dl/workflows/CI/badge.svg)](https://github.com/ytdl-org/youtube-dl/actions?query=workflow%3ACI)
|
||||
|
||||
|
||||
youtube-dl - download videos from youtube.com or other video platforms
|
||||
youtube-dl - download videos from youtube.com or other video platforms.
|
||||
|
||||
- [INSTALLATION](#installation)
|
||||
- [DESCRIPTION](#description)
|
||||
- [OPTIONS](#options)
|
||||
- [CONFIGURATION](#configuration)
|
||||
- [OUTPUT TEMPLATE](#output-template)
|
||||
- [FORMAT SELECTION](#format-selection)
|
||||
- [VIDEO SELECTION](#video-selection)
|
||||
- [Installation](#installation)
|
||||
- [Description](#description)
|
||||
- [Options](#options)
|
||||
- [Configuration](#configuration)
|
||||
- [Output template](#output-template)
|
||||
- [Format selection](#format-selection)
|
||||
- [Video selection](#video-selection)
|
||||
- [FAQ](#faq)
|
||||
- [DEVELOPER INSTRUCTIONS](#developer-instructions)
|
||||
- [EMBEDDING YOUTUBE-DL](#embedding-youtube-dl)
|
||||
- [BUGS](#bugs)
|
||||
- [COPYRIGHT](#copyright)
|
||||
- [Developer instructions](#developer-instructions)
|
||||
- [Embedding youtube-dl](#embedding-youtube-dl)
|
||||
- [Bugs](#bugs)
|
||||
- [Copyright](#copyright)
|
||||
|
||||
# INSTALLATION
|
||||
|
||||
To install it right away for all UNIX users (Linux, macOS, etc.), type:
|
||||
To install it right away for all Unix users (Linux, macOS, etc.), type:
|
||||
|
||||
sudo curl -L https://yt-dl.org/downloads/latest/youtube-dl -o /usr/local/bin/youtube-dl
|
||||
sudo chmod a+rx /usr/local/bin/youtube-dl
|
||||
|
||||
If you do not have curl, you can alternatively use a recent wget:
|
||||
If you do not have curl, you can alternatively use wget:
|
||||
|
||||
sudo wget https://yt-dl.org/downloads/latest/youtube-dl -O /usr/local/bin/youtube-dl
|
||||
sudo chmod a+rx /usr/local/bin/youtube-dl
|
||||
|
@ -44,10 +44,10 @@ Or with [MacPorts](https://www.macports.org/):
|
|||
|
||||
sudo port install youtube-dl
|
||||
|
||||
Alternatively, refer to the [developer instructions](#developer-instructions) for how to check out and work with the git repository. For further options, including PGP signatures, see the [youtube-dl Download Page](https://ytdl-org.github.io/youtube-dl/download.html).
|
||||
Alternatively, refer to the [developer instructions](#developer-instructions) for how to check out and work with the Git repository. For further options, including PGP signatures, see the [youtube-dl Download Page](https://ytdl-org.github.io/youtube-dl/download.html).
|
||||
|
||||
# DESCRIPTION
|
||||
**youtube-dl** is a command-line program to download videos from YouTube.com and a few more sites. It requires the Python interpreter, version 2.6, 2.7, or 3.2+, and it is not platform specific. It should work on your Unix box, on Windows or on macOS. It is released to the public domain, which means you can modify it, redistribute it or use it however you like.
|
||||
**youtube-dl** is a command-line program to download videos from YouTube.com and a few more sites. It requires the Python interpreter, version 2.6, 2.7, or 3.2+, and it is not platform specific. It should work on your Unix computer, on Windows or on macOS. It is released to the public domain, which means you can modify it, redistribute it or use it however you like.
|
||||
|
||||
youtube-dl [OPTIONS] URL [URL...]
|
||||
|
||||
|
@ -97,7 +97,7 @@ Alternatively, refer to the [developer instructions](#developer-instructions) fo
|
|||
only)
|
||||
--no-color Do not emit color codes in output
|
||||
|
||||
## Network Options:
|
||||
## Network options
|
||||
--proxy URL Use the specified HTTP/HTTPS/SOCKS
|
||||
proxy. To enable SOCKS proxy, specify a
|
||||
proper scheme. For example
|
||||
|
@ -110,7 +110,7 @@ Alternatively, refer to the [developer instructions](#developer-instructions) fo
|
|||
-4, --force-ipv4 Make all connections via IPv4
|
||||
-6, --force-ipv6 Make all connections via IPv6
|
||||
|
||||
## Geo Restriction:
|
||||
## Geo restriction
|
||||
--geo-verification-proxy URL Use this proxy to verify the IP address
|
||||
for some geo-restricted sites. The
|
||||
default proxy specified by --proxy (or
|
||||
|
@ -127,7 +127,7 @@ Alternatively, refer to the [developer instructions](#developer-instructions) fo
|
|||
with explicitly provided IP block in
|
||||
CIDR notation
|
||||
|
||||
## Video Selection:
|
||||
## Video selection
|
||||
--playlist-start NUMBER Playlist video to start at (default is
|
||||
1)
|
||||
--playlist-end NUMBER Playlist video to end at (default is
|
||||
|
@ -193,7 +193,7 @@ Alternatively, refer to the [developer instructions](#developer-instructions) fo
|
|||
--include-ads Download advertisements as well
|
||||
(experimental)
|
||||
|
||||
## Download Options:
|
||||
## Download options
|
||||
-r, --limit-rate RATE Maximum download rate in bytes per
|
||||
second (e.g. 50K or 4.2M)
|
||||
-R, --retries RETRIES Number of retries (default is 10), or
|
||||
|
@ -239,7 +239,7 @@ Alternatively, refer to the [developer instructions](#developer-instructions) fo
|
|||
--external-downloader-args ARGS Give these arguments to the external
|
||||
downloader
|
||||
|
||||
## Filesystem Options:
|
||||
## Filesystem options
|
||||
-a, --batch-file FILE File containing URLs to download ('-'
|
||||
for stdin), one URL per line. Lines
|
||||
starting with '#', ';' or ']' are
|
||||
|
@ -287,14 +287,14 @@ Alternatively, refer to the [developer instructions](#developer-instructions) fo
|
|||
--no-cache-dir Disable filesystem caching
|
||||
--rm-cache-dir Delete all filesystem cache files
|
||||
|
||||
## Thumbnail Options:
|
||||
## Thumbnail options
|
||||
--write-thumbnail Write thumbnail image to disk
|
||||
--write-all-thumbnails Write all thumbnail image formats to
|
||||
disk
|
||||
--list-thumbnails Simulate and list all available
|
||||
thumbnail formats
|
||||
|
||||
## Verbosity / Simulation Options:
|
||||
## Verbosity / simulation options
|
||||
-q, --quiet Activate quiet mode
|
||||
--no-warnings Ignore warnings
|
||||
-s, --simulate Do not download the video and do not
|
||||
|
@ -336,7 +336,7 @@ Alternatively, refer to the [developer instructions](#developer-instructions) fo
|
|||
--no-call-home Do NOT contact the youtube-dl server
|
||||
for debugging
|
||||
|
||||
## Workarounds:
|
||||
## Workarounds
|
||||
--encoding ENCODING Force the specified encoding
|
||||
(experimental)
|
||||
--no-check-certificate Suppress HTTPS certificate validation
|
||||
|
@ -365,7 +365,7 @@ Alternatively, refer to the [developer instructions](#developer-instructions) fo
|
|||
Must only be used along with --min-
|
||||
sleep-interval.
|
||||
|
||||
## Video Format Options:
|
||||
## Video format options
|
||||
-f, --format FORMAT Video format code, see the "FORMAT
|
||||
SELECTION" for all the info
|
||||
--all-formats Download all available video formats
|
||||
|
@ -381,7 +381,7 @@ Alternatively, refer to the [developer instructions](#developer-instructions) fo
|
|||
webm, flv. Ignored if no merge is
|
||||
required
|
||||
|
||||
## Subtitle Options:
|
||||
## Subtitle options
|
||||
--write-sub Write subtitle file
|
||||
--write-auto-sub Write automatically generated subtitle
|
||||
file (YouTube only)
|
||||
|
@ -396,7 +396,7 @@ Alternatively, refer to the [developer instructions](#developer-instructions) fo
|
|||
(optional) separated by commas, use
|
||||
--list-subs for available language tags
|
||||
|
||||
## Authentication Options:
|
||||
## Authentication options
|
||||
-u, --username USERNAME Login with this account ID
|
||||
-p, --password PASSWORD Account password. If this option is
|
||||
left out, youtube-dl will ask
|
||||
|
@ -405,7 +405,7 @@ Alternatively, refer to the [developer instructions](#developer-instructions) fo
|
|||
-n, --netrc Use .netrc authentication data
|
||||
--video-password PASSWORD Video password (vimeo, youku)
|
||||
|
||||
## Adobe Pass Options:
|
||||
## Adobe Pass options
|
||||
--ap-mso MSO Adobe Pass multiple-system operator (TV
|
||||
provider) identifier, use --ap-list-mso
|
||||
for a list of available MSOs
|
||||
|
@ -416,7 +416,7 @@ Alternatively, refer to the [developer instructions](#developer-instructions) fo
|
|||
--ap-list-mso List all supported multiple-system
|
||||
operators
|
||||
|
||||
## Post-processing Options:
|
||||
## Post-processing options
|
||||
-x, --extract-audio Convert video files to audio-only files
|
||||
(requires ffmpeg/avconv and
|
||||
ffprobe/avprobe)
|
||||
|
@ -480,7 +480,7 @@ Alternatively, refer to the [developer instructions](#developer-instructions) fo
|
|||
|
||||
# CONFIGURATION
|
||||
|
||||
You can configure youtube-dl by placing any supported command line option to a configuration file. On Linux and macOS, the system wide configuration file is located at `/etc/youtube-dl.conf` and the user wide configuration file at `~/.config/youtube-dl/config`. On Windows, the user wide configuration file locations are `%APPDATA%\youtube-dl\config.txt` or `C:\Users\<user name>\youtube-dl.conf`. Note that by default configuration file may not exist so you may need to create it yourself.
|
||||
You can configure youtube-dl by placing any supported command-line option to a configuration file. On Linux and macOS, the system-wide configuration file is located at `/etc/youtube-dl.conf` and the user-wide configuration file at `~/.config/youtube-dl/config`. On Windows, the user-wide configuration file locations are `%APPDATA%\youtube-dl\config.txt` or `C:\Users\<user name>\youtube-dl.conf`. Note that by default configuration file may not exist so you may need to create it yourself.
|
||||
|
||||
For example, with the following configuration file youtube-dl will always extract the audio, not copy the mtime, use a proxy and save all videos under `Movies` directory in your home directory:
|
||||
```
|
||||
|
@ -499,7 +499,7 @@ For example, with the following configuration file youtube-dl will always extrac
|
|||
-o ~/Movies/%(title)s.%(ext)s
|
||||
```
|
||||
|
||||
Note that options in configuration file are just the same options aka switches used in regular command line calls thus there **must be no whitespace** after `-` or `--`, e.g. `-o` or `--proxy` but not `- o` or `-- proxy`.
|
||||
Note that options in configuration file are just the same options aka switches used in regular command-line calls thus there **must be no whitespace** after `-` or `--`, e.g. `-o` or `--proxy` but not `- o` or `-- proxy`.
|
||||
|
||||
You can use `--ignore-config` if you want to disable the configuration file for a particular youtube-dl run.
|
||||
|
||||
|
@ -507,7 +507,7 @@ You can also use `--config-location` if you want to use custom configuration fil
|
|||
|
||||
### Authentication with `.netrc` file
|
||||
|
||||
You may also want to configure automatic credentials storage for extractors that support authentication (by providing login and password with `--username` and `--password`) in order not to pass credentials as command line arguments on every youtube-dl execution and prevent tracking plain text passwords in the shell command history. You can achieve this using a [`.netrc` file](https://stackoverflow.com/tags/.netrc/info) on a per extractor basis. For that you will need to create a `.netrc` file in your `$HOME` and restrict permissions to read/write by only you:
|
||||
You may also want to configure automatic credentials storage for extractors that support authentication (by providing login and password with `--username` and `--password`) in order not to pass credentials as command-line arguments on every youtube-dl execution and prevent tracking plain text passwords in the shell command history. You can achieve this using a [`.netrc` file](https://stackoverflow.com/tags/.netrc/info) on a per extractor basis. For that you will need to create a `.netrc` file in your `$HOME` and restrict permissions to read/write by only you:
|
||||
```
|
||||
touch $HOME/.netrc
|
||||
chmod a-rwx,u+rw $HOME/.netrc
|
||||
|
@ -534,7 +534,7 @@ The `-o` option allows users to indicate a template for the output file names.
|
|||
|
||||
**tl;dr:** [navigate me to examples](#output-template-examples).
|
||||
|
||||
The basic usage is not to set any template arguments when downloading a single file, like in `youtube-dl -o funny_video.flv "https://some/video"`. However, it may contain special sequences that will be replaced when downloading each video. The special sequences may be formatted according to [python string formatting operations](https://docs.python.org/2/library/stdtypes.html#string-formatting). For example, `%(NAME)s` or `%(NAME)05d`. To clarify, that is a percent symbol followed by a name in parentheses, followed by formatting operations. Allowed names along with sequence type are:
|
||||
The basic usage is not to set any template arguments when downloading a single file, like in `youtube-dl -o funny_video.flv "https://some/video"`. However, it may contain special sequences that will be replaced when downloading each video. The special sequences may be formatted according to [Python string formatting operations](https://docs.python.org/2/library/stdtypes.html#string-formatting). For example, `%(NAME)s` or `%(NAME)05d`. To clarify, that is a percent symbol followed by a name in parentheses, followed by formatting operations. Allowed names along with sequence type are:
|
||||
|
||||
- `id` (string): Video identifier
|
||||
- `title` (string): Video title
|
||||
|
@ -640,7 +640,7 @@ If you are using an output template inside a Windows batch file then you must es
|
|||
|
||||
#### Output template examples
|
||||
|
||||
Note that on Windows you may need to use double quotes instead of single.
|
||||
Note that on Windows you may need to use double quotes instead of single quotes.
|
||||
|
||||
```bash
|
||||
$ youtube-dl --get-filename -o '%(title)s.%(ext)s' BaW_jenozKc
|
||||
|
@ -733,7 +733,7 @@ If you want to preserve the old format selection behavior (prior to youtube-dl 2
|
|||
|
||||
#### Format selection examples
|
||||
|
||||
Note that on Windows you may need to use double quotes instead of single.
|
||||
Note that on Windows you may need to use double quotes instead of single quotes.
|
||||
|
||||
```bash
|
||||
# Download best mp4 format available or any other best if no mp4 available
|
||||
|
|
|
@ -11,194 +11,146 @@ sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
|||
import math
|
||||
import re
|
||||
|
||||
from youtube_dl.compat import compat_str
|
||||
from youtube_dl.jsinterp import JS_Undefined, JSInterpreter
|
||||
|
||||
NaN = object()
|
||||
|
||||
|
||||
class TestJSInterpreter(unittest.TestCase):
|
||||
def _test(self, jsi_or_code, expected, func='f', args=()):
|
||||
if isinstance(jsi_or_code, compat_str):
|
||||
jsi_or_code = JSInterpreter(jsi_or_code)
|
||||
got = jsi_or_code.call_function(func, *args)
|
||||
if expected is NaN:
|
||||
self.assertTrue(math.isnan(got), '{0} is not NaN'.format(got))
|
||||
else:
|
||||
self.assertEqual(got, expected)
|
||||
|
||||
def test_basic(self):
|
||||
jsi = JSInterpreter('function x(){;}')
|
||||
self.assertEqual(jsi.call_function('x'), None)
|
||||
self.assertEqual(repr(jsi.extract_function('x')), 'F<x>')
|
||||
jsi = JSInterpreter('function f(){;}')
|
||||
self.assertEqual(repr(jsi.extract_function('f')), 'F<f>')
|
||||
self._test(jsi, None)
|
||||
|
||||
jsi = JSInterpreter('function x3(){return 42;}')
|
||||
self.assertEqual(jsi.call_function('x3'), 42)
|
||||
|
||||
jsi = JSInterpreter('function x3(){42}')
|
||||
self.assertEqual(jsi.call_function('x3'), None)
|
||||
|
||||
jsi = JSInterpreter('var x5 = function(){return 42;}')
|
||||
self.assertEqual(jsi.call_function('x5'), 42)
|
||||
|
||||
def test_calc(self):
|
||||
jsi = JSInterpreter('function x4(a){return 2*a+1;}')
|
||||
self.assertEqual(jsi.call_function('x4', 3), 7)
|
||||
self._test('function f(){return 42;}', 42)
|
||||
self._test('function f(){42}', None)
|
||||
self._test('var f = function(){return 42;}', 42)
|
||||
|
||||
def test_add(self):
|
||||
jsi = JSInterpreter('function f(){return 42 + 7;}')
|
||||
self.assertEqual(jsi.call_function('f'), 49)
|
||||
jsi = JSInterpreter('function f(){return 42 + undefined;}')
|
||||
self.assertTrue(math.isnan(jsi.call_function('f')))
|
||||
jsi = JSInterpreter('function f(){return 42 + null;}')
|
||||
self.assertEqual(jsi.call_function('f'), 42)
|
||||
self._test('function f(){return 42 + 7;}', 49)
|
||||
self._test('function f(){return 42 + undefined;}', NaN)
|
||||
self._test('function f(){return 42 + null;}', 42)
|
||||
|
||||
def test_sub(self):
|
||||
jsi = JSInterpreter('function f(){return 42 - 7;}')
|
||||
self.assertEqual(jsi.call_function('f'), 35)
|
||||
jsi = JSInterpreter('function f(){return 42 - undefined;}')
|
||||
self.assertTrue(math.isnan(jsi.call_function('f')))
|
||||
jsi = JSInterpreter('function f(){return 42 - null;}')
|
||||
self.assertEqual(jsi.call_function('f'), 42)
|
||||
self._test('function f(){return 42 - 7;}', 35)
|
||||
self._test('function f(){return 42 - undefined;}', NaN)
|
||||
self._test('function f(){return 42 - null;}', 42)
|
||||
|
||||
def test_mul(self):
|
||||
jsi = JSInterpreter('function f(){return 42 * 7;}')
|
||||
self.assertEqual(jsi.call_function('f'), 294)
|
||||
jsi = JSInterpreter('function f(){return 42 * undefined;}')
|
||||
self.assertTrue(math.isnan(jsi.call_function('f')))
|
||||
jsi = JSInterpreter('function f(){return 42 * null;}')
|
||||
self.assertEqual(jsi.call_function('f'), 0)
|
||||
self._test('function f(){return 42 * 7;}', 294)
|
||||
self._test('function f(){return 42 * undefined;}', NaN)
|
||||
self._test('function f(){return 42 * null;}', 0)
|
||||
|
||||
def test_div(self):
|
||||
jsi = JSInterpreter('function f(a, b){return a / b;}')
|
||||
self.assertTrue(math.isnan(jsi.call_function('f', 0, 0)))
|
||||
self.assertTrue(math.isnan(jsi.call_function('f', JS_Undefined, 1)))
|
||||
self.assertTrue(math.isinf(jsi.call_function('f', 2, 0)))
|
||||
self.assertEqual(jsi.call_function('f', 0, 3), 0)
|
||||
self._test(jsi, NaN, args=(0, 0))
|
||||
self._test(jsi, NaN, args=(JS_Undefined, 1))
|
||||
self._test(jsi, float('inf'), args=(2, 0))
|
||||
self._test(jsi, 0, args=(0, 3))
|
||||
|
||||
def test_mod(self):
|
||||
jsi = JSInterpreter('function f(){return 42 % 7;}')
|
||||
self.assertEqual(jsi.call_function('f'), 0)
|
||||
jsi = JSInterpreter('function f(){return 42 % 0;}')
|
||||
self.assertTrue(math.isnan(jsi.call_function('f')))
|
||||
jsi = JSInterpreter('function f(){return 42 % undefined;}')
|
||||
self.assertTrue(math.isnan(jsi.call_function('f')))
|
||||
self._test('function f(){return 42 % 7;}', 0)
|
||||
self._test('function f(){return 42 % 0;}', NaN)
|
||||
self._test('function f(){return 42 % undefined;}', NaN)
|
||||
|
||||
def test_exp(self):
|
||||
jsi = JSInterpreter('function f(){return 42 ** 2;}')
|
||||
self.assertEqual(jsi.call_function('f'), 1764)
|
||||
jsi = JSInterpreter('function f(){return 42 ** undefined;}')
|
||||
self.assertTrue(math.isnan(jsi.call_function('f')))
|
||||
jsi = JSInterpreter('function f(){return 42 ** null;}')
|
||||
self.assertEqual(jsi.call_function('f'), 1)
|
||||
jsi = JSInterpreter('function f(){return undefined ** 42;}')
|
||||
self.assertTrue(math.isnan(jsi.call_function('f')))
|
||||
self._test('function f(){return 42 ** 2;}', 1764)
|
||||
self._test('function f(){return 42 ** undefined;}', NaN)
|
||||
self._test('function f(){return 42 ** null;}', 1)
|
||||
self._test('function f(){return undefined ** 42;}', NaN)
|
||||
|
||||
def test_calc(self):
|
||||
self._test('function f(a){return 2*a+1;}', 7, args=[3])
|
||||
|
||||
def test_empty_return(self):
|
||||
jsi = JSInterpreter('function f(){return; y()}')
|
||||
self.assertEqual(jsi.call_function('f'), None)
|
||||
self._test('function f(){return; y()}', None)
|
||||
|
||||
def test_morespace(self):
|
||||
jsi = JSInterpreter('function x (a) { return 2 * a + 1 ; }')
|
||||
self.assertEqual(jsi.call_function('x', 3), 7)
|
||||
|
||||
jsi = JSInterpreter('function f () { x = 2 ; return x; }')
|
||||
self.assertEqual(jsi.call_function('f'), 2)
|
||||
self._test('function f (a) { return 2 * a + 1 ; }', 7, args=[3])
|
||||
self._test('function f () { x = 2 ; return x; }', 2)
|
||||
|
||||
def test_strange_chars(self):
|
||||
jsi = JSInterpreter('function $_xY1 ($_axY1) { var $_axY2 = $_axY1 + 1; return $_axY2; }')
|
||||
self.assertEqual(jsi.call_function('$_xY1', 20), 21)
|
||||
self._test('function $_xY1 ($_axY1) { var $_axY2 = $_axY1 + 1; return $_axY2; }',
|
||||
21, args=[20], func='$_xY1')
|
||||
|
||||
def test_operators(self):
|
||||
jsi = JSInterpreter('function f(){return 1 << 5;}')
|
||||
self.assertEqual(jsi.call_function('f'), 32)
|
||||
|
||||
jsi = JSInterpreter('function f(){return 2 ** 5}')
|
||||
self.assertEqual(jsi.call_function('f'), 32)
|
||||
|
||||
jsi = JSInterpreter('function f(){return 19 & 21;}')
|
||||
self.assertEqual(jsi.call_function('f'), 17)
|
||||
|
||||
jsi = JSInterpreter('function f(){return 11 >> 2;}')
|
||||
self.assertEqual(jsi.call_function('f'), 2)
|
||||
|
||||
jsi = JSInterpreter('function f(){return []? 2+3: 4;}')
|
||||
self.assertEqual(jsi.call_function('f'), 5)
|
||||
|
||||
jsi = JSInterpreter('function f(){return 1 == 2}')
|
||||
self.assertEqual(jsi.call_function('f'), False)
|
||||
|
||||
jsi = JSInterpreter('function f(){return 0 && 1 || 2;}')
|
||||
self.assertEqual(jsi.call_function('f'), 2)
|
||||
|
||||
jsi = JSInterpreter('function f(){return 0 ?? 42;}')
|
||||
self.assertEqual(jsi.call_function('f'), 0)
|
||||
|
||||
jsi = JSInterpreter('function f(){return "life, the universe and everything" < 42;}')
|
||||
self.assertFalse(jsi.call_function('f'))
|
||||
self._test('function f(){return 1 << 5;}', 32)
|
||||
self._test('function f(){return 2 ** 5}', 32)
|
||||
self._test('function f(){return 19 & 21;}', 17)
|
||||
self._test('function f(){return 11 >> 2;}', 2)
|
||||
self._test('function f(){return []? 2+3: 4;}', 5)
|
||||
self._test('function f(){return 1 == 2}', False)
|
||||
self._test('function f(){return 0 && 1 || 2;}', 2)
|
||||
self._test('function f(){return 0 ?? 42;}', 0)
|
||||
self._test('function f(){return "life, the universe and everything" < 42;}', False)
|
||||
# https://github.com/ytdl-org/youtube-dl/issues/32815
|
||||
self._test('function f(){return 0 - 7 * - 6;}', 42)
|
||||
|
||||
def test_array_access(self):
|
||||
jsi = JSInterpreter('function f(){var x = [1,2,3]; x[0] = 4; x[0] = 5; x[2.0] = 7; return x;}')
|
||||
self.assertEqual(jsi.call_function('f'), [5, 2, 7])
|
||||
self._test('function f(){var x = [1,2,3]; x[0] = 4; x[0] = 5; x[2.0] = 7; return x;}', [5, 2, 7])
|
||||
|
||||
def test_parens(self):
|
||||
jsi = JSInterpreter('function f(){return (1) + (2) * ((( (( (((((3)))))) )) ));}')
|
||||
self.assertEqual(jsi.call_function('f'), 7)
|
||||
|
||||
jsi = JSInterpreter('function f(){return (1 + 2) * 3;}')
|
||||
self.assertEqual(jsi.call_function('f'), 9)
|
||||
self._test('function f(){return (1) + (2) * ((( (( (((((3)))))) )) ));}', 7)
|
||||
self._test('function f(){return (1 + 2) * 3;}', 9)
|
||||
|
||||
def test_quotes(self):
|
||||
jsi = JSInterpreter(r'function f(){return "a\"\\("}')
|
||||
self.assertEqual(jsi.call_function('f'), r'a"\(')
|
||||
self._test(r'function f(){return "a\"\\("}', r'a"\(')
|
||||
|
||||
def test_assignments(self):
|
||||
jsi = JSInterpreter('function f(){var x = 20; x = 30 + 1; return x;}')
|
||||
self.assertEqual(jsi.call_function('f'), 31)
|
||||
|
||||
jsi = JSInterpreter('function f(){var x = 20; x += 30 + 1; return x;}')
|
||||
self.assertEqual(jsi.call_function('f'), 51)
|
||||
|
||||
jsi = JSInterpreter('function f(){var x = 20; x -= 30 + 1; return x;}')
|
||||
self.assertEqual(jsi.call_function('f'), -11)
|
||||
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)
|
||||
|
||||
@unittest.skip('Not yet fully implemented')
|
||||
def test_comments(self):
|
||||
'Skipping: Not yet fully implemented'
|
||||
return
|
||||
jsi = JSInterpreter('''
|
||||
function x() {
|
||||
var x = /* 1 + */ 2;
|
||||
var y = /* 30
|
||||
* 40 */ 50;
|
||||
return x + y;
|
||||
}
|
||||
''')
|
||||
self.assertEqual(jsi.call_function('x'), 52)
|
||||
self._test('''
|
||||
function f() {
|
||||
var x = /* 1 + */ 2;
|
||||
var y = /* 30
|
||||
* 40 */ 50;
|
||||
return x + y;
|
||||
}
|
||||
''', 52)
|
||||
|
||||
jsi = JSInterpreter('''
|
||||
function f() {
|
||||
var x = "/*";
|
||||
var y = 1 /* comment */ + 2;
|
||||
return y;
|
||||
}
|
||||
''')
|
||||
self.assertEqual(jsi.call_function('f'), 3)
|
||||
self._test('''
|
||||
function f() {
|
||||
var x = "/*";
|
||||
var y = 1 /* comment */ + 2;
|
||||
return y;
|
||||
}
|
||||
''', 3)
|
||||
|
||||
def test_precedence(self):
|
||||
jsi = JSInterpreter('''
|
||||
function x() {
|
||||
var a = [10, 20, 30, 40, 50];
|
||||
var b = 6;
|
||||
a[0]=a[b%a.length];
|
||||
return a;
|
||||
}''')
|
||||
self.assertEqual(jsi.call_function('x'), [20, 20, 30, 40, 50])
|
||||
self._test('''
|
||||
function f() {
|
||||
var a = [10, 20, 30, 40, 50];
|
||||
var b = 6;
|
||||
a[0]=a[b%a.length];
|
||||
return a;
|
||||
}
|
||||
''', [20, 20, 30, 40, 50])
|
||||
|
||||
def test_builtins(self):
|
||||
jsi = JSInterpreter('''
|
||||
function x() { return NaN }
|
||||
''')
|
||||
self.assertTrue(math.isnan(jsi.call_function('x')))
|
||||
self._test('function f() { return NaN }', NaN)
|
||||
|
||||
def test_Date(self):
|
||||
jsi = JSInterpreter('''
|
||||
function x(dt) { return new Date(dt) - 0; }
|
||||
''')
|
||||
self.assertEqual(jsi.call_function('x', 'Wednesday 31 December 1969 18:01:26 MDT'), 86000)
|
||||
self._test('function f() { return new Date("Wednesday 31 December 1969 18:01:26 MDT") - 0; }', 86000)
|
||||
|
||||
jsi = JSInterpreter('function f(dt) { return new Date(dt) - 0; }')
|
||||
# date format m/d/y
|
||||
self.assertEqual(jsi.call_function('x', '12/31/1969 18:01:26 MDT'), 86000)
|
||||
|
||||
self._test(jsi, 86000, args=['12/31/1969 18:01:26 MDT'])
|
||||
# epoch 0
|
||||
self.assertEqual(jsi.call_function('x', '1 January 1970 00:00:00 UTC'), 0)
|
||||
self._test(jsi, 0, args=['1 January 1970 00:00:00 UTC'])
|
||||
|
||||
def test_call(self):
|
||||
jsi = JSInterpreter('''
|
||||
|
@ -206,179 +158,115 @@ class TestJSInterpreter(unittest.TestCase):
|
|||
function y(a) { return x() + (a?a:0); }
|
||||
function z() { return y(3); }
|
||||
''')
|
||||
self.assertEqual(jsi.call_function('z'), 5)
|
||||
self.assertEqual(jsi.call_function('y'), 2)
|
||||
self._test(jsi, 5, func='z')
|
||||
self._test(jsi, 2, func='y')
|
||||
|
||||
def test_if(self):
|
||||
jsi = JSInterpreter('''
|
||||
function x() {
|
||||
self._test('''
|
||||
function f() {
|
||||
let a = 9;
|
||||
if (0==0) {a++}
|
||||
return a
|
||||
}''')
|
||||
self.assertEqual(jsi.call_function('x'), 10)
|
||||
}
|
||||
''', 10)
|
||||
|
||||
jsi = JSInterpreter('''
|
||||
function x() {
|
||||
self._test('''
|
||||
function f() {
|
||||
if (0==0) {return 10}
|
||||
}''')
|
||||
self.assertEqual(jsi.call_function('x'), 10)
|
||||
}
|
||||
''', 10)
|
||||
|
||||
jsi = JSInterpreter('''
|
||||
function x() {
|
||||
self._test('''
|
||||
function f() {
|
||||
if (0!=0) {return 1}
|
||||
else {return 10}
|
||||
}''')
|
||||
self.assertEqual(jsi.call_function('x'), 10)
|
||||
|
||||
""" # Unsupported
|
||||
jsi = JSInterpreter('''
|
||||
function x() {
|
||||
if (0!=0) return 1;
|
||||
else {return 10}
|
||||
}''')
|
||||
self.assertEqual(jsi.call_function('x'), 10)
|
||||
"""
|
||||
}
|
||||
''', 10)
|
||||
|
||||
def test_elseif(self):
|
||||
jsi = JSInterpreter('''
|
||||
function x() {
|
||||
if (0!=0) {return 1}
|
||||
else if (1==0) {return 2}
|
||||
else {return 10}
|
||||
}''')
|
||||
self.assertEqual(jsi.call_function('x'), 10)
|
||||
|
||||
""" # Unsupported
|
||||
jsi = JSInterpreter('''
|
||||
function x() {
|
||||
if (0!=0) return 1;
|
||||
else if (1==0) {return 2}
|
||||
else {return 10}
|
||||
}''')
|
||||
self.assertEqual(jsi.call_function('x'), 10)
|
||||
# etc
|
||||
"""
|
||||
self._test('''
|
||||
function f() {
|
||||
if (0!=0) {return 1}
|
||||
else if (1==0) {return 2}
|
||||
else {return 10}
|
||||
}
|
||||
''', 10)
|
||||
|
||||
def test_for_loop(self):
|
||||
# function x() { a=0; for (i=0; i-10; i++) {a++} a }
|
||||
jsi = JSInterpreter('''
|
||||
function x() { a=0; for (i=0; i-10; i++) {a++} return a }
|
||||
''')
|
||||
self.assertEqual(jsi.call_function('x'), 10)
|
||||
self._test('function f() { a=0; for (i=0; i-10; i++) {a++} return a }', 10)
|
||||
|
||||
def test_while_loop(self):
|
||||
# function x() { a=0; while (a<10) {a++} a }
|
||||
jsi = JSInterpreter('''
|
||||
function x() { a=0; while (a<10) {a++} return a }
|
||||
''')
|
||||
self.assertEqual(jsi.call_function('x'), 10)
|
||||
self._test('function f() { a=0; while (a<10) {a++} return a }', 10)
|
||||
|
||||
def test_switch(self):
|
||||
jsi = JSInterpreter('''
|
||||
function x(f) { switch(f){
|
||||
case 1:f+=1;
|
||||
case 2:f+=2;
|
||||
case 3:f+=3;break;
|
||||
case 4:f+=4;
|
||||
default:f=0;
|
||||
} return f }
|
||||
function f(x) { switch(x){
|
||||
case 1:x+=1;
|
||||
case 2:x+=2;
|
||||
case 3:x+=3;break;
|
||||
case 4:x+=4;
|
||||
default:x=0;
|
||||
} return x }
|
||||
''')
|
||||
self.assertEqual(jsi.call_function('x', 1), 7)
|
||||
self.assertEqual(jsi.call_function('x', 3), 6)
|
||||
self.assertEqual(jsi.call_function('x', 5), 0)
|
||||
self._test(jsi, 7, args=[1])
|
||||
self._test(jsi, 6, args=[3])
|
||||
self._test(jsi, 0, args=[5])
|
||||
|
||||
def test_switch_default(self):
|
||||
jsi = JSInterpreter('''
|
||||
function x(f) { switch(f){
|
||||
case 2: f+=2;
|
||||
default: f-=1;
|
||||
case 5:
|
||||
case 6: f+=6;
|
||||
case 0: break;
|
||||
case 1: f+=1;
|
||||
} return f }
|
||||
function f(x) { switch(x){
|
||||
case 2: x+=2;
|
||||
default: x-=1;
|
||||
case 5:
|
||||
case 6: x+=6;
|
||||
case 0: break;
|
||||
case 1: x+=1;
|
||||
} return x }
|
||||
''')
|
||||
self.assertEqual(jsi.call_function('x', 1), 2)
|
||||
self.assertEqual(jsi.call_function('x', 5), 11)
|
||||
self.assertEqual(jsi.call_function('x', 9), 14)
|
||||
self._test(jsi, 2, args=[1])
|
||||
self._test(jsi, 11, args=[5])
|
||||
self._test(jsi, 14, args=[9])
|
||||
|
||||
def test_try(self):
|
||||
jsi = JSInterpreter('''
|
||||
function x() { try{return 10} catch(e){return 5} }
|
||||
''')
|
||||
self.assertEqual(jsi.call_function('x'), 10)
|
||||
self._test('function f() { try{return 10} catch(e){return 5} }', 10)
|
||||
|
||||
def test_catch(self):
|
||||
jsi = JSInterpreter('''
|
||||
function x() { try{throw 10} catch(e){return 5} }
|
||||
''')
|
||||
self.assertEqual(jsi.call_function('x'), 5)
|
||||
self._test('function f() { try{throw 10} catch(e){return 5} }', 5)
|
||||
|
||||
def test_finally(self):
|
||||
jsi = JSInterpreter('''
|
||||
function x() { try{throw 10} finally {return 42} }
|
||||
''')
|
||||
self.assertEqual(jsi.call_function('x'), 42)
|
||||
jsi = JSInterpreter('''
|
||||
function x() { try{throw 10} catch(e){return 5} finally {return 42} }
|
||||
''')
|
||||
self.assertEqual(jsi.call_function('x'), 42)
|
||||
self._test('function f() { try{throw 10} finally {return 42} }', 42)
|
||||
self._test('function f() { try{throw 10} catch(e){return 5} finally {return 42} }', 42)
|
||||
|
||||
def test_nested_try(self):
|
||||
jsi = JSInterpreter('''
|
||||
function x() {try {
|
||||
try{throw 10} finally {throw 42}
|
||||
self._test('''
|
||||
function f() {try {
|
||||
try{throw 10} finally {throw 42}
|
||||
} catch(e){return 5} }
|
||||
''')
|
||||
self.assertEqual(jsi.call_function('x'), 5)
|
||||
''', 5)
|
||||
|
||||
def test_for_loop_continue(self):
|
||||
jsi = JSInterpreter('''
|
||||
function x() { a=0; for (i=0; i-10; i++) { continue; a++ } return a }
|
||||
''')
|
||||
self.assertEqual(jsi.call_function('x'), 0)
|
||||
self._test('function f() { a=0; for (i=0; i-10; i++) { continue; a++ } return a }', 0)
|
||||
|
||||
def test_for_loop_break(self):
|
||||
jsi = JSInterpreter('''
|
||||
function x() { a=0; for (i=0; i-10; i++) { break; a++ } return a }
|
||||
''')
|
||||
self.assertEqual(jsi.call_function('x'), 0)
|
||||
self._test('function f() { a=0; for (i=0; i-10; i++) { break; a++ } return a }', 0)
|
||||
|
||||
def test_for_loop_try(self):
|
||||
jsi = JSInterpreter('''
|
||||
function x() {
|
||||
for (i=0; i-10; i++) { try { if (i == 5) throw i} catch {return 10} finally {break} };
|
||||
return 42 }
|
||||
''')
|
||||
self.assertEqual(jsi.call_function('x'), 42)
|
||||
self._test('''
|
||||
function f() {
|
||||
for (i=0; i-10; i++) { try { if (i == 5) throw i} catch {return 10} finally {break} };
|
||||
return 42 }
|
||||
''', 42)
|
||||
|
||||
def test_literal_list(self):
|
||||
jsi = JSInterpreter('''
|
||||
function x() { return [1, 2, "asdf", [5, 6, 7]][3] }
|
||||
''')
|
||||
self.assertEqual(jsi.call_function('x'), [5, 6, 7])
|
||||
self._test('function f() { return [1, 2, "asdf", [5, 6, 7]][3] }', [5, 6, 7])
|
||||
|
||||
def test_comma(self):
|
||||
jsi = JSInterpreter('''
|
||||
function x() { a=5; a -= 1, a+=3; return a }
|
||||
''')
|
||||
self.assertEqual(jsi.call_function('x'), 7)
|
||||
jsi = JSInterpreter('''
|
||||
function x() { a=5; return (a -= 1, a+=3, a); }
|
||||
''')
|
||||
self.assertEqual(jsi.call_function('x'), 7)
|
||||
|
||||
jsi = JSInterpreter('''
|
||||
function x() { return (l=[0,1,2,3], function(a, b){return a+b})((l[1], l[2]), l[3]) }
|
||||
''')
|
||||
self.assertEqual(jsi.call_function('x'), 5)
|
||||
self._test('function f() { a=5; a -= 1, a+=3; return a }', 7)
|
||||
self._test('function f() { a=5; return (a -= 1, a+=3, a); }', 7)
|
||||
self._test('function f() { return (l=[0,1,2,3], function(a, b){return a+b})((l[1], l[2]), l[3]) }', 5)
|
||||
|
||||
def test_void(self):
|
||||
jsi = JSInterpreter('''
|
||||
function x() { return void 42; }
|
||||
''')
|
||||
self.assertEqual(jsi.call_function('x'), None)
|
||||
self._test('function f() { return void 42; }', None)
|
||||
|
||||
def test_return_function(self):
|
||||
jsi = JSInterpreter('''
|
||||
|
@ -387,110 +275,60 @@ class TestJSInterpreter(unittest.TestCase):
|
|||
self.assertEqual(jsi.call_function('x')([]), 1)
|
||||
|
||||
def test_null(self):
|
||||
jsi = JSInterpreter('''
|
||||
function x() { return null; }
|
||||
''')
|
||||
self.assertIs(jsi.call_function('x'), None)
|
||||
|
||||
jsi = JSInterpreter('''
|
||||
function x() { return [null > 0, null < 0, null == 0, null === 0]; }
|
||||
''')
|
||||
self.assertEqual(jsi.call_function('x'), [False, False, False, False])
|
||||
|
||||
jsi = JSInterpreter('''
|
||||
function x() { return [null >= 0, null <= 0]; }
|
||||
''')
|
||||
self.assertEqual(jsi.call_function('x'), [True, True])
|
||||
self._test('function f() { return null; }', None)
|
||||
self._test('function f() { return [null > 0, null < 0, null == 0, null === 0]; }',
|
||||
[False, False, False, False])
|
||||
self._test('function f() { return [null >= 0, null <= 0]; }', [True, True])
|
||||
|
||||
def test_undefined(self):
|
||||
jsi = JSInterpreter('''
|
||||
function x() { return undefined === undefined; }
|
||||
''')
|
||||
self.assertTrue(jsi.call_function('x'))
|
||||
self._test('function f() { return undefined === undefined; }', True)
|
||||
self._test('function f() { return undefined; }', JS_Undefined)
|
||||
self._test('function f() {return undefined ?? 42; }', 42)
|
||||
self._test('function f() { let v; return v; }', JS_Undefined)
|
||||
self._test('function f() { let v; return v**0; }', 1)
|
||||
self._test('function f() { let v; return [v>42, v<=42, v&&42, 42&&v]; }',
|
||||
[False, False, JS_Undefined, JS_Undefined])
|
||||
|
||||
self._test('''
|
||||
function f() { return [
|
||||
undefined === undefined,
|
||||
undefined == undefined,
|
||||
undefined == null
|
||||
]; }
|
||||
''', [True] * 3)
|
||||
self._test('''
|
||||
function f() { return [
|
||||
undefined < undefined,
|
||||
undefined > undefined,
|
||||
undefined === 0,
|
||||
undefined == 0,
|
||||
undefined < 0,
|
||||
undefined > 0,
|
||||
undefined >= 0,
|
||||
undefined <= 0,
|
||||
undefined > null,
|
||||
undefined < null,
|
||||
undefined === null
|
||||
]; }
|
||||
''', [False] * 11)
|
||||
|
||||
jsi = JSInterpreter('''
|
||||
function x() { return undefined; }
|
||||
''')
|
||||
self.assertIs(jsi.call_function('x'), JS_Undefined)
|
||||
|
||||
jsi = JSInterpreter('''
|
||||
function x() { let v; return v; }
|
||||
''')
|
||||
self.assertIs(jsi.call_function('x'), JS_Undefined)
|
||||
|
||||
jsi = JSInterpreter('''
|
||||
function x() { return [undefined === undefined, undefined == undefined, undefined < undefined, undefined > undefined]; }
|
||||
''')
|
||||
self.assertEqual(jsi.call_function('x'), [True, True, False, False])
|
||||
|
||||
jsi = JSInterpreter('''
|
||||
function x() { return [undefined === 0, undefined == 0, undefined < 0, undefined > 0]; }
|
||||
''')
|
||||
self.assertEqual(jsi.call_function('x'), [False, False, False, False])
|
||||
|
||||
jsi = JSInterpreter('''
|
||||
function x() { return [undefined >= 0, undefined <= 0]; }
|
||||
''')
|
||||
self.assertEqual(jsi.call_function('x'), [False, False])
|
||||
|
||||
jsi = JSInterpreter('''
|
||||
function x() { return [undefined > null, undefined < null, undefined == null, undefined === null]; }
|
||||
''')
|
||||
self.assertEqual(jsi.call_function('x'), [False, False, True, False])
|
||||
|
||||
jsi = JSInterpreter('''
|
||||
function x() { return [undefined === null, undefined == null, undefined < null, undefined > null]; }
|
||||
''')
|
||||
self.assertEqual(jsi.call_function('x'), [False, True, False, False])
|
||||
|
||||
jsi = JSInterpreter('''
|
||||
function x() { let v; return [42+v, v+42, v**42, 42**v, 0**v]; }
|
||||
function x() { let v; return [42+v, v+42, v**42, 42**v, 0**v]; }
|
||||
''')
|
||||
for y in jsi.call_function('x'):
|
||||
self.assertTrue(math.isnan(y))
|
||||
|
||||
jsi = JSInterpreter('''
|
||||
function x() { let v; return v**0; }
|
||||
''')
|
||||
self.assertEqual(jsi.call_function('x'), 1)
|
||||
|
||||
jsi = JSInterpreter('''
|
||||
function x() { let v; return [v>42, v<=42, v&&42, 42&&v]; }
|
||||
''')
|
||||
self.assertEqual(jsi.call_function('x'), [False, False, JS_Undefined, JS_Undefined])
|
||||
|
||||
jsi = JSInterpreter('function x(){return undefined ?? 42; }')
|
||||
self.assertEqual(jsi.call_function('x'), 42)
|
||||
|
||||
def test_object(self):
|
||||
jsi = JSInterpreter('''
|
||||
function x() { return {}; }
|
||||
''')
|
||||
self.assertEqual(jsi.call_function('x'), {})
|
||||
|
||||
jsi = JSInterpreter('''
|
||||
function x() { let a = {m1: 42, m2: 0 }; return [a["m1"], a.m2]; }
|
||||
''')
|
||||
self.assertEqual(jsi.call_function('x'), [42, 0])
|
||||
|
||||
jsi = JSInterpreter('''
|
||||
function x() { let a; return a?.qq; }
|
||||
''')
|
||||
self.assertIs(jsi.call_function('x'), JS_Undefined)
|
||||
|
||||
jsi = JSInterpreter('''
|
||||
function x() { let a = {m1: 42, m2: 0 }; return a?.qq; }
|
||||
''')
|
||||
self.assertIs(jsi.call_function('x'), JS_Undefined)
|
||||
self._test('function f() { return {}; }', {})
|
||||
self._test('function f() { let a = {m1: 42, m2: 0 }; return [a["m1"], a.m2]; }', [42, 0])
|
||||
self._test('function f() { let a; return a?.qq; }', JS_Undefined)
|
||||
self._test('function f() { let a = {m1: 42, m2: 0 }; return a?.qq; }', JS_Undefined)
|
||||
|
||||
def test_regex(self):
|
||||
jsi = JSInterpreter('''
|
||||
function x() { let a=/,,[/,913,/](,)}/; }
|
||||
''')
|
||||
self.assertIs(jsi.call_function('x'), None)
|
||||
self._test('function f() { let a=/,,[/,913,/](,)}/; }', None)
|
||||
|
||||
jsi = JSInterpreter('''
|
||||
function x() { let a=/,,[/,913,/](,)}/; "".replace(a, ""); return a; }
|
||||
function x() { let a=/,,[/,913,/](,)}/; "".replace(a, ""); return a; }
|
||||
''')
|
||||
attrs = set(('findall', 'finditer', 'match', 'scanner', 'search',
|
||||
'split', 'sub', 'subn'))
|
||||
|
@ -500,94 +338,92 @@ class TestJSInterpreter(unittest.TestCase):
|
|||
self.assertSetEqual(set(dir(jsi.call_function('x'))) & attrs, attrs)
|
||||
|
||||
jsi = JSInterpreter('''
|
||||
function x() { let a=/,,[/,913,/](,)}/i; return a; }
|
||||
function x() { let a=/,,[/,913,/](,)}/i; return a; }
|
||||
''')
|
||||
self.assertEqual(jsi.call_function('x').flags & ~re.U, re.I)
|
||||
|
||||
jsi = JSInterpreter(r'''
|
||||
function x() { let a="data-name".replace("data-", ""); return a }
|
||||
''')
|
||||
self.assertEqual(jsi.call_function('x'), 'name')
|
||||
jsi = JSInterpreter(r'function f() { let a=/,][}",],()}(\[)/; return a; }')
|
||||
self.assertEqual(jsi.call_function('f').pattern, r',][}",],()}(\[)')
|
||||
|
||||
jsi = JSInterpreter(r'''
|
||||
function x() { let a="data-name".replace(new RegExp("^.+-"), ""); return a; }
|
||||
''')
|
||||
self.assertEqual(jsi.call_function('x'), 'name')
|
||||
jsi = JSInterpreter(r'function f() { let a=[/[)\\]/]; return a[0]; }')
|
||||
self.assertEqual(jsi.call_function('f').pattern, r'[)\\]')
|
||||
|
||||
jsi = JSInterpreter(r'''
|
||||
function x() { let a="data-name".replace(/^.+-/, ""); return a; }
|
||||
''')
|
||||
self.assertEqual(jsi.call_function('x'), 'name')
|
||||
|
||||
jsi = JSInterpreter(r'''
|
||||
function x() { let a="data-name".replace(/a/g, "o"); return a; }
|
||||
''')
|
||||
self.assertEqual(jsi.call_function('x'), 'doto-nome')
|
||||
|
||||
jsi = JSInterpreter(r'''
|
||||
function x() { let a="data-name".replaceAll("a", "o"); return a; }
|
||||
''')
|
||||
self.assertEqual(jsi.call_function('x'), 'doto-nome')
|
||||
|
||||
jsi = JSInterpreter(r'''
|
||||
function x() { let a=[/[)\\]/]; return a[0]; }
|
||||
''')
|
||||
self.assertEqual(jsi.call_function('x').pattern, r'[)\\]')
|
||||
|
||||
""" # fails
|
||||
jsi = JSInterpreter(r'''
|
||||
function x() { let a=100; a/=/[0-9]+/.exec('divide by 20 today')[0]; }
|
||||
''')
|
||||
self.assertEqual(jsi.call_function('x'), 5)
|
||||
"""
|
||||
def test_replace(self):
|
||||
self._test('function f() { let a="data-name".replace("data-", ""); return a }',
|
||||
'name')
|
||||
self._test('function f() { let a="data-name".replace(new RegExp("^.+-"), ""); return a; }',
|
||||
'name')
|
||||
self._test('function f() { let a="data-name".replace(/^.+-/, ""); return a; }',
|
||||
'name')
|
||||
self._test('function f() { let a="data-name".replace(/a/g, "o"); return a; }',
|
||||
'doto-nome')
|
||||
self._test('function f() { let a="data-name".replaceAll("a", "o"); return a; }',
|
||||
'doto-nome')
|
||||
|
||||
def test_char_code_at(self):
|
||||
jsi = JSInterpreter('function x(i){return "test".charCodeAt(i)}')
|
||||
self.assertEqual(jsi.call_function('x', 0), 116)
|
||||
self.assertEqual(jsi.call_function('x', 1), 101)
|
||||
self.assertEqual(jsi.call_function('x', 2), 115)
|
||||
self.assertEqual(jsi.call_function('x', 3), 116)
|
||||
self.assertEqual(jsi.call_function('x', 4), None)
|
||||
self.assertEqual(jsi.call_function('x', 'not_a_number'), 116)
|
||||
jsi = JSInterpreter('function f(i){return "test".charCodeAt(i)}')
|
||||
self._test(jsi, 116, args=[0])
|
||||
self._test(jsi, 101, args=[1])
|
||||
self._test(jsi, 115, args=[2])
|
||||
self._test(jsi, 116, args=[3])
|
||||
self._test(jsi, None, args=[4])
|
||||
self._test(jsi, 116, args=['not_a_number'])
|
||||
|
||||
def test_bitwise_operators_overflow(self):
|
||||
jsi = JSInterpreter('function x(){return -524999584 << 5}')
|
||||
self.assertEqual(jsi.call_function('x'), 379882496)
|
||||
self._test('function f(){return -524999584 << 5}', 379882496)
|
||||
self._test('function f(){return 1236566549 << 5}', 915423904)
|
||||
|
||||
jsi = JSInterpreter('function x(){return 1236566549 << 5}')
|
||||
self.assertEqual(jsi.call_function('x'), 915423904)
|
||||
def test_bitwise_operators_typecast(self):
|
||||
# madness
|
||||
self._test('function f(){return null << 5}', 0)
|
||||
self._test('function f(){return undefined >> 5}', 0)
|
||||
self._test('function f(){return 42 << NaN}', 42)
|
||||
self._test('function f(){return 42 << Infinity}', 42)
|
||||
|
||||
def test_bitwise_operators_madness(self):
|
||||
jsi = JSInterpreter('function x(){return null << 5}')
|
||||
self.assertEqual(jsi.call_function('x'), 0)
|
||||
|
||||
jsi = JSInterpreter('function x(){return undefined >> 5}')
|
||||
self.assertEqual(jsi.call_function('x'), 0)
|
||||
|
||||
jsi = JSInterpreter('function x(){return 42 << NaN}')
|
||||
self.assertEqual(jsi.call_function('x'), 42)
|
||||
|
||||
jsi = JSInterpreter('function x(){return 42 << Infinity}')
|
||||
self.assertEqual(jsi.call_function('x'), 42)
|
||||
def test_negative(self):
|
||||
self._test('function f(){return 2 * -2.0 ;}', -4)
|
||||
self._test('function f(){return 2 - - -2 ;}', 0)
|
||||
self._test('function f(){return 2 - - - -2 ;}', 4)
|
||||
self._test('function f(){return 2 - + + - -2;}', 0)
|
||||
self._test('function f(){return 2 + - + - -2;}', 0)
|
||||
|
||||
def test_32066(self):
|
||||
jsi = JSInterpreter("function x(){return Math.pow(3, 5) + new Date('1970-01-01T08:01:42.000+08:00') / 1000 * -239 - -24205;}")
|
||||
self.assertEqual(jsi.call_function('x'), 70)
|
||||
self._test(
|
||||
"function f(){return Math.pow(3, 5) + new Date('1970-01-01T08:01:42.000+08:00') / 1000 * -239 - -24205;}",
|
||||
70)
|
||||
|
||||
def test_unary_operators(self):
|
||||
jsi = JSInterpreter('function f(){return 2 - - - 2;}')
|
||||
self.assertEqual(jsi.call_function('f'), 0)
|
||||
jsi = JSInterpreter('function f(){return 2 + - + - - 2;}')
|
||||
self.assertEqual(jsi.call_function('f'), 0)
|
||||
# https://github.com/ytdl-org/youtube-dl/issues/32815
|
||||
jsi = JSInterpreter('function f(){return 0 - 7 * - 6;}')
|
||||
self.assertEqual(jsi.call_function('f'), 42)
|
||||
|
||||
""" # fails so far
|
||||
@unittest.skip('Not yet working')
|
||||
def test_packed(self):
|
||||
jsi = JSInterpreter('''function x(p,a,c,k,e,d){while(c--)if(k[c])p=p.replace(new RegExp('\\b'+c.toString(a)+'\\b','g'),k[c]);return p}''')
|
||||
self.assertEqual(jsi.call_function('x', '''h 7=g("1j");7.7h({7g:[{33:"w://7f-7e-7d-7c.v.7b/7a/79/78/77/76.74?t=73&s=2s&e=72&f=2t&71=70.0.0.1&6z=6y&6x=6w"}],6v:"w://32.v.u/6u.31",16:"r%",15:"r%",6t:"6s",6r:"",6q:"l",6p:"l",6o:"6n",6m:\'6l\',6k:"6j",9:[{33:"/2u?b=6i&n=50&6h=w://32.v.u/6g.31",6f:"6e"}],1y:{6d:1,6c:\'#6b\',6a:\'#69\',68:"67",66:30,65:r,},"64":{63:"%62 2m%m%61%5z%5y%5x.u%5w%5v%5u.2y%22 2k%m%1o%22 5t%m%1o%22 5s%m%1o%22 2j%m%5r%22 16%m%5q%22 15%m%5p%22 5o%2z%5n%5m%2z",5l:"w://v.u/d/1k/5k.2y",5j:[]},\'5i\':{"5h":"5g"},5f:"5e",5d:"w://v.u",5c:{},5b:l,1x:[0.25,0.50,0.75,1,1.25,1.5,2]});h 1m,1n,5a;h 59=0,58=0;h 7=g("1j");h 2x=0,57=0,56=0;$.55({54:{\'53-52\':\'2i-51\'}});7.j(\'4z\',6(x){c(5>0&&x.1l>=5&&1n!=1){1n=1;$(\'q.4y\').4x(\'4w\')}});7.j(\'13\',6(x){2x=x.1l});7.j(\'2g\',6(x){2w(x)});7.j(\'4v\',6(){$(\'q.2v\').4u()});6 2w(x){$(\'q.2v\').4t();c(1m)19;1m=1;17=0;c(4s.4r===l){17=1}$.4q(\'/2u?b=4p&2l=1k&4o=2t-4n-4m-2s-4l&4k=&4j=&4i=&17=\'+17,6(2r){$(\'#4h\').4g(2r)});$(\'.3-8-4f-4e:4d("4c")\').2h(6(e){2q();g().4b(0);g().4a(l)});6 2q(){h $14=$("<q />").2p({1l:"49",16:"r%",15:"r%",48:0,2n:0,2o:47,46:"45(10%, 10%, 10%, 0.4)","44-43":"42"});$("<41 />").2p({16:"60%",15:"60%",2o:40,"3z-2n":"3y"}).3x({\'2m\':\'/?b=3w&2l=1k\',\'2k\':\'0\',\'2j\':\'2i\'}).2f($14);$14.2h(6(){$(3v).3u();g().2g()});$14.2f($(\'#1j\'))}g().13(0);}6 3t(){h 9=7.1b(2e);2d.2c(9);c(9.n>1){1r(i=0;i<9.n;i++){c(9[i].1a==2e){2d.2c(\'!!=\'+i);7.1p(i)}}}}7.j(\'3s\',6(){g().1h("/2a/3r.29","3q 10 28",6(){g().13(g().27()+10)},"2b");$("q[26=2b]").23().21(\'.3-20-1z\');g().1h("/2a/3p.29","3o 10 28",6(){h 12=g().27()-10;c(12<0)12=0;g().13(12)},"24");$("q[26=24]").23().21(\'.3-20-1z\');});6 1i(){}7.j(\'3n\',6(){1i()});7.j(\'3m\',6(){1i()});7.j("k",6(y){h 9=7.1b();c(9.n<2)19;$(\'.3-8-3l-3k\').3j(6(){$(\'#3-8-a-k\').1e(\'3-8-a-z\');$(\'.3-a-k\').p(\'o-1f\',\'11\')});7.1h("/3i/3h.3g","3f 3e",6(){$(\'.3-1w\').3d(\'3-8-1v\');$(\'.3-8-1y, .3-8-1x\').p(\'o-1g\',\'11\');c($(\'.3-1w\').3c(\'3-8-1v\')){$(\'.3-a-k\').p(\'o-1g\',\'l\');$(\'.3-a-k\').p(\'o-1f\',\'l\');$(\'.3-8-a\').1e(\'3-8-a-z\');$(\'.3-8-a:1u\').3b(\'3-8-a-z\')}3a{$(\'.3-a-k\').p(\'o-1g\',\'11\');$(\'.3-a-k\').p(\'o-1f\',\'11\');$(\'.3-8-a:1u\').1e(\'3-8-a-z\')}},"39");7.j("38",6(y){1d.37(\'1c\',y.9[y.36].1a)});c(1d.1t(\'1c\')){35("1s(1d.1t(\'1c\'));",34)}});h 18;6 1s(1q){h 9=7.1b();c(9.n>1){1r(i=0;i<9.n;i++){c(9[i].1a==1q){c(i==18){19}18=i;7.1p(i)}}}}',36,270,'|||jw|||function|player|settings|tracks|submenu||if||||jwplayer|var||on|audioTracks|true|3D|length|aria|attr|div|100|||sx|filemoon|https||event|active||false|tt|seek|dd|height|width|adb|current_audio|return|name|getAudioTracks|default_audio|localStorage|removeClass|expanded|checked|addButton|callMeMaybe|vplayer|0fxcyc2ajhp1|position|vvplay|vvad|220|setCurrentAudioTrack|audio_name|for|audio_set|getItem|last|open|controls|playbackRates|captions|rewind|icon|insertAfter||detach|ff00||button|getPosition|sec|png|player8|ff11|log|console|track_name|appendTo|play|click|no|scrolling|frameborder|file_code|src|top|zIndex|css|showCCform|data|1662367683|383371|dl|video_ad|doPlay|prevt|mp4|3E||jpg|thumbs|file|300|setTimeout|currentTrack|setItem|audioTrackChanged|dualSound|else|addClass|hasClass|toggleClass|Track|Audio|svg|dualy|images|mousedown|buttons|topbar|playAttemptFailed|beforePlay|Rewind|fr|Forward|ff|ready|set_audio_track|remove|this|upload_srt|prop|50px|margin|1000001|iframe|center|align|text|rgba|background|1000000|left|absolute|pause|setCurrentCaptions|Upload|contains|item|content|html|fviews|referer|prem|embed|3e57249ef633e0d03bf76ceb8d8a4b65|216|83|hash|view|get|TokenZir|window|hide|show|complete|slow|fadeIn|video_ad_fadein|time||cache|Cache|Content|headers|ajaxSetup|v2done|tott|vastdone2|vastdone1|vvbefore|playbackRateControls|cast|aboutlink|FileMoon|abouttext|UHD|1870|qualityLabels|sites|GNOME_POWER|link|2Fiframe|3C|allowfullscreen|22360|22640|22no|marginheight|marginwidth|2FGNOME_POWER|2F0fxcyc2ajhp1|2Fe|2Ffilemoon|2F|3A||22https|3Ciframe|code|sharing|fontOpacity|backgroundOpacity|Tahoma|fontFamily|303030|backgroundColor|FFFFFF|color|userFontScale|thumbnails|kind|0fxcyc2ajhp10000|url|get_slides|start|startparam|none|preload|html5|primary|hlshtml|androidhls|duration|uniform|stretching|0fxcyc2ajhp1_xt|image|2048|sp|6871|asn|127|srv|43200|_g3XlBcu2lmD9oDexD2NLWSmah2Nu3XcDrl93m9PwXY|m3u8||master|0fxcyc2ajhp1_x|00076|01|hls2|to|s01|delivery|storage|moon|sources|setup'''.split('|')))
|
||||
"""
|
||||
self._test(
|
||||
'''function f(p,a,c,k,e,d){while(c--)if(k[c])p=p.replace(new RegExp('\\b'+c.toString(a)+'\\b','g'),k[c]);return p}''',
|
||||
'''h 7=g("1j");7.7h({7g:[{33:"w://7f-7e-7d-7c.v.7b/7a/79/78/77/76.74?t=73&s=2s&e=72&f=2t&71=70.0.0.1&6z=6y&6x=6w"}],6v:"w://32.v.u/6u.31",16:"r%",15:"r%",6t:"6s",6r:"",6q:"l",6p:"l",6o:"6n",6m:\'6l\',6k:"6j",9:[{33:"/2u?b=6i&n=50&6h=w://32.v.u/6g.31",6f:"6e"}],1y:{6d:1,6c:\'#6b\',6a:\'#69\',68:"67",66:30,65:r,},"64":{63:"%62 2m%m%61%5z%5y%5x.u%5w%5v%5u.2y%22 2k%m%1o%22 5t%m%1o%22 5s%m%1o%22 2j%m%5r%22 16%m%5q%22 15%m%5p%22 5o%2z%5n%5m%2z",5l:"w://v.u/d/1k/5k.2y",5j:[]},\'5i\':{"5h":"5g"},5f:"5e",5d:"w://v.u",5c:{},5b:l,1x:[0.25,0.50,0.75,1,1.25,1.5,2]});h 1m,1n,5a;h 59=0,58=0;h 7=g("1j");h 2x=0,57=0,56=0;$.55({54:{\'53-52\':\'2i-51\'}});7.j(\'4z\',6(x){c(5>0&&x.1l>=5&&1n!=1){1n=1;$(\'q.4y\').4x(\'4w\')}});7.j(\'13\',6(x){2x=x.1l});7.j(\'2g\',6(x){2w(x)});7.j(\'4v\',6(){$(\'q.2v\').4u()});6 2w(x){$(\'q.2v\').4t();c(1m)19;1m=1;17=0;c(4s.4r===l){17=1}$.4q(\'/2u?b=4p&2l=1k&4o=2t-4n-4m-2s-4l&4k=&4j=&4i=&17=\'+17,6(2r){$(\'#4h\').4g(2r)});$(\'.3-8-4f-4e:4d("4c")\').2h(6(e){2q();g().4b(0);g().4a(l)});6 2q(){h $14=$("<q />").2p({1l:"49",16:"r%",15:"r%",48:0,2n:0,2o:47,46:"45(10%, 10%, 10%, 0.4)","44-43":"42"});$("<41 />").2p({16:"60%",15:"60%",2o:40,"3z-2n":"3y"}).3x({\'2m\':\'/?b=3w&2l=1k\',\'2k\':\'0\',\'2j\':\'2i\'}).2f($14);$14.2h(6(){$(3v).3u();g().2g()});$14.2f($(\'#1j\'))}g().13(0);}6 3t(){h 9=7.1b(2e);2d.2c(9);c(9.n>1){1r(i=0;i<9.n;i++){c(9[i].1a==2e){2d.2c(\'!!=\'+i);7.1p(i)}}}}7.j(\'3s\',6(){g().1h("/2a/3r.29","3q 10 28",6(){g().13(g().27()+10)},"2b");$("q[26=2b]").23().21(\'.3-20-1z\');g().1h("/2a/3p.29","3o 10 28",6(){h 12=g().27()-10;c(12<0)12=0;g().13(12)},"24");$("q[26=24]").23().21(\'.3-20-1z\');});6 1i(){}7.j(\'3n\',6(){1i()});7.j(\'3m\',6(){1i()});7.j("k",6(y){h 9=7.1b();c(9.n<2)19;$(\'.3-8-3l-3k\').3j(6(){$(\'#3-8-a-k\').1e(\'3-8-a-z\');$(\'.3-a-k\').p(\'o-1f\',\'11\')});7.1h("/3i/3h.3g","3f 3e",6(){$(\'.3-1w\').3d(\'3-8-1v\');$(\'.3-8-1y, .3-8-1x\').p(\'o-1g\',\'11\');c($(\'.3-1w\').3c(\'3-8-1v\')){$(\'.3-a-k\').p(\'o-1g\',\'l\');$(\'.3-a-k\').p(\'o-1f\',\'l\');$(\'.3-8-a\').1e(\'3-8-a-z\');$(\'.3-8-a:1u\').3b(\'3-8-a-z\')}3a{$(\'.3-a-k\').p(\'o-1g\',\'11\');$(\'.3-a-k\').p(\'o-1f\',\'11\');$(\'.3-8-a:1u\').1e(\'3-8-a-z\')}},"39");7.j("38",6(y){1d.37(\'1c\',y.9[y.36].1a)});c(1d.1t(\'1c\')){35("1s(1d.1t(\'1c\'));",34)}});h 18;6 1s(1q){h 9=7.1b();c(9.n>1){1r(i=0;i<9.n;i++){c(9[i].1a==1q){c(i==18){19}18=i;7.1p(i)}}}}',36,270,'|||jw|||function|player|settings|tracks|submenu||if||||jwplayer|var||on|audioTracks|true|3D|length|aria|attr|div|100|||sx|filemoon|https||event|active||false|tt|seek|dd|height|width|adb|current_audio|return|name|getAudioTracks|default_audio|localStorage|removeClass|expanded|checked|addButton|callMeMaybe|vplayer|0fxcyc2ajhp1|position|vvplay|vvad|220|setCurrentAudioTrack|audio_name|for|audio_set|getItem|last|open|controls|playbackRates|captions|rewind|icon|insertAfter||detach|ff00||button|getPosition|sec|png|player8|ff11|log|console|track_name|appendTo|play|click|no|scrolling|frameborder|file_code|src|top|zIndex|css|showCCform|data|1662367683|383371|dl|video_ad|doPlay|prevt|mp4|3E||jpg|thumbs|file|300|setTimeout|currentTrack|setItem|audioTrackChanged|dualSound|else|addClass|hasClass|toggleClass|Track|Audio|svg|dualy|images|mousedown|buttons|topbar|playAttemptFailed|beforePlay|Rewind|fr|Forward|ff|ready|set_audio_track|remove|this|upload_srt|prop|50px|margin|1000001|iframe|center|align|text|rgba|background|1000000|left|absolute|pause|setCurrentCaptions|Upload|contains|item|content|html|fviews|referer|prem|embed|3e57249ef633e0d03bf76ceb8d8a4b65|216|83|hash|view|get|TokenZir|window|hide|show|complete|slow|fadeIn|video_ad_fadein|time||cache|Cache|Content|headers|ajaxSetup|v2done|tott|vastdone2|vastdone1|vvbefore|playbackRateControls|cast|aboutlink|FileMoon|abouttext|UHD|1870|qualityLabels|sites|GNOME_POWER|link|2Fiframe|3C|allowfullscreen|22360|22640|22no|marginheight|marginwidth|2FGNOME_POWER|2F0fxcyc2ajhp1|2Fe|2Ffilemoon|2F|3A||22https|3Ciframe|code|sharing|fontOpacity|backgroundOpacity|Tahoma|fontFamily|303030|backgroundColor|FFFFFF|color|userFontScale|thumbnails|kind|0fxcyc2ajhp10000|url|get_slides|start|startparam|none|preload|html5|primary|hlshtml|androidhls|duration|uniform|stretching|0fxcyc2ajhp1_xt|image|2048|sp|6871|asn|127|srv|43200|_g3XlBcu2lmD9oDexD2NLWSmah2Nu3XcDrl93m9PwXY|m3u8||master|0fxcyc2ajhp1_x|00076|01|hls2|to|s01|delivery|storage|moon|sources|setup'''.split('|'))
|
||||
|
||||
def test_join(self):
|
||||
test_input = list('test')
|
||||
tests = [
|
||||
'function f(a, b){return a.join(b)}',
|
||||
'function f(a, b){return Array.prototype.join.call(a, b)}',
|
||||
'function f(a, b){return Array.prototype.join.apply(a, [b])}',
|
||||
]
|
||||
for test in tests:
|
||||
jsi = JSInterpreter(test)
|
||||
self._test(jsi, 'test', args=[test_input, ''])
|
||||
self._test(jsi, 't-e-s-t', args=[test_input, '-'])
|
||||
self._test(jsi, '', args=[[], '-'])
|
||||
|
||||
def test_split(self):
|
||||
test_result = list('test')
|
||||
tests = [
|
||||
'function f(a, b){return a.split(b)}',
|
||||
'function f(a, b){return String.prototype.split.call(a, b)}',
|
||||
'function f(a, b){return String.prototype.split.apply(a, [b])}',
|
||||
]
|
||||
for test in tests:
|
||||
jsi = JSInterpreter(test)
|
||||
self._test(jsi, test_result, args=['test', ''])
|
||||
self._test(jsi, test_result, args=['t-e-s-t', '-'])
|
||||
self._test(jsi, [''], args=['', '-'])
|
||||
self._test(jsi, [], args=['', ''])
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
|
|
|
@ -14,9 +14,11 @@ sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
|||
import io
|
||||
import itertools
|
||||
import json
|
||||
import types
|
||||
import xml.etree.ElementTree
|
||||
|
||||
from youtube_dl.utils import (
|
||||
_UnsafeExtensionError,
|
||||
age_restricted,
|
||||
args_to_str,
|
||||
base_url,
|
||||
|
@ -270,6 +272,27 @@ class TestUtil(unittest.TestCase):
|
|||
expand_path('~/%s' % env('YOUTUBE_DL_EXPATH_PATH')),
|
||||
'%s/expanded' % compat_getenv('HOME'))
|
||||
|
||||
_uncommon_extensions = [
|
||||
('exe', 'abc.exe.ext'),
|
||||
('de', 'abc.de.ext'),
|
||||
('../.mp4', None),
|
||||
('..\\.mp4', None),
|
||||
]
|
||||
|
||||
def assertUnsafeExtension(self, ext=None):
|
||||
assert_raises = self.assertRaises(_UnsafeExtensionError)
|
||||
assert_raises.ext = ext
|
||||
orig_exit = assert_raises.__exit__
|
||||
|
||||
def my_exit(self_, exc_type, exc_val, exc_tb):
|
||||
did_raise = orig_exit(exc_type, exc_val, exc_tb)
|
||||
if did_raise and assert_raises.ext is not None:
|
||||
self.assertEqual(assert_raises.ext, assert_raises.exception.extension, 'Unsafe extension not as unexpected')
|
||||
return did_raise
|
||||
|
||||
assert_raises.__exit__ = types.MethodType(my_exit, assert_raises)
|
||||
return assert_raises
|
||||
|
||||
def test_prepend_extension(self):
|
||||
self.assertEqual(prepend_extension('abc.ext', 'temp'), 'abc.temp.ext')
|
||||
self.assertEqual(prepend_extension('abc.ext', 'temp', 'ext'), 'abc.temp.ext')
|
||||
|
@ -278,6 +301,19 @@ class TestUtil(unittest.TestCase):
|
|||
self.assertEqual(prepend_extension('.abc', 'temp'), '.abc.temp')
|
||||
self.assertEqual(prepend_extension('.abc.ext', 'temp'), '.abc.temp.ext')
|
||||
|
||||
# Test uncommon extensions
|
||||
self.assertEqual(prepend_extension('abc.ext', 'bin'), 'abc.bin.ext')
|
||||
for ext, result in self._uncommon_extensions:
|
||||
with self.assertUnsafeExtension(ext):
|
||||
prepend_extension('abc', ext)
|
||||
if result:
|
||||
self.assertEqual(prepend_extension('abc.ext', ext, 'ext'), result)
|
||||
else:
|
||||
with self.assertUnsafeExtension(ext):
|
||||
prepend_extension('abc.ext', ext, 'ext')
|
||||
with self.assertUnsafeExtension(ext):
|
||||
prepend_extension('abc.unexpected_ext', ext, 'ext')
|
||||
|
||||
def test_replace_extension(self):
|
||||
self.assertEqual(replace_extension('abc.ext', 'temp'), 'abc.temp')
|
||||
self.assertEqual(replace_extension('abc.ext', 'temp', 'ext'), 'abc.temp')
|
||||
|
@ -286,6 +322,16 @@ class TestUtil(unittest.TestCase):
|
|||
self.assertEqual(replace_extension('.abc', 'temp'), '.abc.temp')
|
||||
self.assertEqual(replace_extension('.abc.ext', 'temp'), '.abc.temp')
|
||||
|
||||
# Test uncommon extensions
|
||||
self.assertEqual(replace_extension('abc.ext', 'bin'), 'abc.unknown_video')
|
||||
for ext, _ in self._uncommon_extensions:
|
||||
with self.assertUnsafeExtension(ext):
|
||||
replace_extension('abc', ext)
|
||||
with self.assertUnsafeExtension(ext):
|
||||
replace_extension('abc.ext', ext, 'ext')
|
||||
with self.assertUnsafeExtension(ext):
|
||||
replace_extension('abc.unexpected_ext', ext, 'ext')
|
||||
|
||||
def test_subtitles_filename(self):
|
||||
self.assertEqual(subtitles_filename('abc.ext', 'en', 'vtt'), 'abc.en.vtt')
|
||||
self.assertEqual(subtitles_filename('abc.ext', 'en', 'vtt', 'ext'), 'abc.en.vtt')
|
||||
|
|
|
@ -162,6 +162,18 @@ _NSIG_TESTS = [
|
|||
'https://www.youtube.com/s/player/590f65a6/player_ias.vflset/en_US/base.js',
|
||||
'1tm7-g_A9zsI8_Lay_', 'xI4Vem4Put_rOg',
|
||||
),
|
||||
(
|
||||
'https://www.youtube.com/s/player/b22ef6e7/player_ias.vflset/en_US/base.js',
|
||||
'b6HcntHGkvBLk_FRf', 'kNPW6A7FyP2l8A',
|
||||
),
|
||||
(
|
||||
'https://www.youtube.com/s/player/3400486c/player_ias.vflset/en_US/base.js',
|
||||
'lL46g3XifCKUZn1Xfw', 'z767lhet6V2Skl',
|
||||
),
|
||||
(
|
||||
'https://www.youtube.com/s/player/5604538d/player_ias.vflset/en_US/base.js',
|
||||
'7X-he4jjvMx7BCX', 'sViSydX8IHtdWA',
|
||||
),
|
||||
]
|
||||
|
||||
|
||||
|
|
|
@ -7,6 +7,7 @@ import collections
|
|||
import copy
|
||||
import datetime
|
||||
import errno
|
||||
import functools
|
||||
import io
|
||||
import itertools
|
||||
import json
|
||||
|
@ -53,6 +54,7 @@ from .compat import (
|
|||
compat_urllib_request_DataHandler,
|
||||
)
|
||||
from .utils import (
|
||||
_UnsafeExtensionError,
|
||||
age_restricted,
|
||||
args_to_str,
|
||||
bug_reports_message,
|
||||
|
@ -129,6 +131,20 @@ if compat_os_name == 'nt':
|
|||
import ctypes
|
||||
|
||||
|
||||
def _catch_unsafe_file_extension(func):
|
||||
@functools.wraps(func)
|
||||
def wrapper(self, *args, **kwargs):
|
||||
try:
|
||||
return func(self, *args, **kwargs)
|
||||
except _UnsafeExtensionError as error:
|
||||
self.report_error(
|
||||
'{0} found; to avoid damaging your system, this value is disallowed.'
|
||||
' If you believe this is an error{1}'.format(
|
||||
error_to_compat_str(error), bug_reports_message(',')))
|
||||
|
||||
return wrapper
|
||||
|
||||
|
||||
class YoutubeDL(object):
|
||||
"""YoutubeDL class.
|
||||
|
||||
|
@ -1925,6 +1941,7 @@ class YoutubeDL(object):
|
|||
if self.params.get('forcejson', False):
|
||||
self.to_stdout(json.dumps(self.sanitize_info(info_dict)))
|
||||
|
||||
@_catch_unsafe_file_extension
|
||||
def process_info(self, info_dict):
|
||||
"""Process a single resolved IE result."""
|
||||
|
||||
|
@ -2097,18 +2114,26 @@ class YoutubeDL(object):
|
|||
# TODO: Check acodec/vcodec
|
||||
return False
|
||||
|
||||
filename_real_ext = os.path.splitext(filename)[1][1:]
|
||||
filename_wo_ext = (
|
||||
os.path.splitext(filename)[0]
|
||||
if filename_real_ext == info_dict['ext']
|
||||
else filename)
|
||||
exts = [info_dict['ext']]
|
||||
requested_formats = info_dict['requested_formats']
|
||||
if self.params.get('merge_output_format') is None and not compatible_formats(requested_formats):
|
||||
info_dict['ext'] = 'mkv'
|
||||
self.report_warning(
|
||||
'Requested formats are incompatible for merge and will be merged into mkv.')
|
||||
exts.append(info_dict['ext'])
|
||||
|
||||
# Ensure filename always has a correct extension for successful merge
|
||||
filename = '%s.%s' % (filename_wo_ext, info_dict['ext'])
|
||||
def correct_ext(filename, ext=exts[1]):
|
||||
if filename == '-':
|
||||
return filename
|
||||
f_name, f_real_ext = os.path.splitext(filename)
|
||||
f_real_ext = f_real_ext[1:]
|
||||
filename_wo_ext = f_name if f_real_ext in exts else filename
|
||||
if ext is None:
|
||||
ext = f_real_ext or None
|
||||
return join_nonempty(filename_wo_ext, ext, delim='.')
|
||||
|
||||
filename = correct_ext(filename)
|
||||
if os.path.exists(encodeFilename(filename)):
|
||||
self.to_screen(
|
||||
'[download] %s has already been downloaded and '
|
||||
|
@ -2118,8 +2143,9 @@ class YoutubeDL(object):
|
|||
new_info = dict(info_dict)
|
||||
new_info.update(f)
|
||||
fname = prepend_extension(
|
||||
self.prepare_filename(new_info),
|
||||
'f%s' % f['format_id'], new_info['ext'])
|
||||
correct_ext(
|
||||
self.prepare_filename(new_info), new_info['ext']),
|
||||
'f%s' % (f['format_id'],), new_info['ext'])
|
||||
if not ensure_dir_exists(fname):
|
||||
return
|
||||
downloaded.append(fname)
|
||||
|
|
|
@ -21,6 +21,7 @@ from .compat import (
|
|||
workaround_optparse_bug9161,
|
||||
)
|
||||
from .utils import (
|
||||
_UnsafeExtensionError,
|
||||
DateRange,
|
||||
decodeOption,
|
||||
DEFAULT_OUTTMPL,
|
||||
|
@ -173,6 +174,9 @@ def _real_main(argv=None):
|
|||
if opts.ap_mso and opts.ap_mso not in MSO_INFO:
|
||||
parser.error('Unsupported TV Provider, use --ap-list-mso to get a list of supported TV Providers')
|
||||
|
||||
if opts.no_check_extensions:
|
||||
_UnsafeExtensionError.lenient = True
|
||||
|
||||
def parse_retries(retries):
|
||||
if retries in ('inf', 'infinite'):
|
||||
parsed_retries = float('inf')
|
||||
|
|
|
@ -106,6 +106,25 @@ class YandexMusicTrackIE(YandexMusicBaseIE):
|
|||
}, {
|
||||
'url': 'http://music.yandex.com/album/540508/track/4878838',
|
||||
'only_matching': True,
|
||||
}, {
|
||||
'url': 'https://music.yandex.ru/album/16302456/track/85430762',
|
||||
'md5': '11b8d50ab03b57738deeaadf661a0a48',
|
||||
'info_dict': {
|
||||
'id': '85430762',
|
||||
'ext': 'mp3',
|
||||
'abr': 128,
|
||||
'title': 'Haddadi Von Engst, Phonic Youth, Super Flu - Til The End (Super Flu Remix)',
|
||||
'filesize': int,
|
||||
'duration': 431.14,
|
||||
'track': 'Til The End (Super Flu Remix)',
|
||||
'album': 'Til The End',
|
||||
'album_artist': 'Haddadi Von Engst, Phonic Youth',
|
||||
'artist': 'Haddadi Von Engst, Phonic Youth, Super Flu',
|
||||
'release_year': 2021,
|
||||
'genre': 'house',
|
||||
'disc_number': 1,
|
||||
'track_number': 2,
|
||||
}
|
||||
}]
|
||||
|
||||
def _real_extract(self, url):
|
||||
|
@ -116,10 +135,14 @@ class YandexMusicTrackIE(YandexMusicBaseIE):
|
|||
'track', tld, url, track_id, 'Downloading track JSON',
|
||||
{'track': '%s:%s' % (track_id, album_id)})['track']
|
||||
track_title = track['title']
|
||||
track_version = track.get('version')
|
||||
if track_version:
|
||||
track_title = '%s (%s)' % (track_title, track_version)
|
||||
|
||||
download_data = self._download_json(
|
||||
'https://music.yandex.ru/api/v2.1/handlers/track/%s:%s/web-album_track-track-track-main/download/m' % (track_id, album_id),
|
||||
track_id, 'Downloading track location url JSON',
|
||||
query={'hq': 1},
|
||||
headers={'X-Retpath-Y': url})
|
||||
|
||||
fd_data = self._download_json(
|
||||
|
|
|
@ -1636,7 +1636,7 @@ class YoutubeIE(YoutubeBaseInfoExtractor):
|
|||
try:
|
||||
jsi, player_id, func_code = self._extract_n_function_code(video_id, player_url)
|
||||
except ExtractorError as e:
|
||||
raise ExtractorError('Unable to extract nsig jsi, player_id, func_codefunction code', cause=e)
|
||||
raise ExtractorError('Unable to extract nsig function code', cause=e)
|
||||
if self.get_param('youtube_print_sig_code'):
|
||||
self.to_screen('Extracted nsig function from {0}:\n{1}\n'.format(
|
||||
player_id, func_code[1]))
|
||||
|
@ -1647,7 +1647,7 @@ class YoutubeIE(YoutubeBaseInfoExtractor):
|
|||
except JSInterpreter.Exception as e:
|
||||
self.report_warning(
|
||||
'%s (%s %s)' % (
|
||||
'Unable to decode n-parameter: download likely to be throttled',
|
||||
'Unable to decode n-parameter: expect download to be blocked or throttled',
|
||||
error_to_compat_str(e),
|
||||
traceback.format_exc()),
|
||||
video_id=video_id)
|
||||
|
@ -1658,13 +1658,23 @@ class YoutubeIE(YoutubeBaseInfoExtractor):
|
|||
|
||||
def _extract_n_function_name(self, jscode):
|
||||
func_name, idx = self._search_regex(
|
||||
r'\.get\("n"\)\)&&\(b=(?P<nfunc>[a-zA-Z_$][\w$]*)(?:\[(?P<idx>\d+)\])?\([\w$]+\)',
|
||||
jscode, 'Initial JS player n function name', group=('nfunc', 'idx'))
|
||||
# new: (b=String.fromCharCode(110),c=a.get(b))&&c=nfunc[idx](c)
|
||||
# or: (b="nn"[+a.D],c=a.get(b))&&(c=nfunc[idx](c)s
|
||||
# old: .get("n"))&&(b=nfunc[idx](b)
|
||||
# older: .get("n"))&&(b=nfunc(b)
|
||||
r'''(?x)
|
||||
(?:\(\s*(?P<b>[a-z])\s*=\s*(?:
|
||||
String\s*\.\s*fromCharCode\s*\(\s*110\s*\)|
|
||||
"n+"\[\s*\+?s*[\w$.]+\s*]
|
||||
)\s*,(?P<c>[a-z])\s*=\s*[a-z]\s*)?
|
||||
\.\s*get\s*\(\s*(?(b)(?P=b)|"n{1,2}")(?:\s*\)){2}\s*&&\s*\(\s*(?(c)(?P=c)|b)\s*=\s*
|
||||
(?P<nfunc>[a-zA-Z_$][\w$]*)(?:\s*\[(?P<idx>\d+)\])?\s*\(\s*[\w$]+\s*\)
|
||||
''', jscode, 'Initial JS player n function name', group=('nfunc', 'idx'))
|
||||
if not idx:
|
||||
return func_name
|
||||
|
||||
return self._parse_json(self._search_regex(
|
||||
r'var {0}\s*=\s*(\[.+?\])\s*[,;]'.format(re.escape(func_name)), jscode,
|
||||
r'var\s+{0}\s*=\s*(\[.+?\])\s*[,;]'.format(re.escape(func_name)), jscode,
|
||||
'Initial JS player n function list ({0}.{1})'.format(func_name, idx)),
|
||||
func_name, transform_source=js_to_json)[int(idx)]
|
||||
|
||||
|
@ -1679,17 +1689,7 @@ class YoutubeIE(YoutubeBaseInfoExtractor):
|
|||
|
||||
func_name = self._extract_n_function_name(jscode)
|
||||
|
||||
# For redundancy
|
||||
func_code = self._search_regex(
|
||||
r'''(?xs)%s\s*=\s*function\s*\((?P<var>[\w$]+)\)\s*
|
||||
# NB: The end of the regex is intentionally kept strict
|
||||
{(?P<code>.+?}\s*return\ [\w$]+.join\(""\))};''' % func_name,
|
||||
jscode, 'nsig function', group=('var', 'code'), default=None)
|
||||
if func_code:
|
||||
func_code = ([func_code[0]], func_code[1])
|
||||
else:
|
||||
self.write_debug('Extracting nsig function with jsinterp')
|
||||
func_code = jsi.extract_function_code(func_name)
|
||||
func_code = jsi.extract_function_code(func_name)
|
||||
|
||||
self.cache.store('youtube-nsig', player_id, func_code)
|
||||
return jsi, player_id, func_code
|
||||
|
|
|
@ -20,7 +20,9 @@ from .compat import (
|
|||
compat_basestring,
|
||||
compat_chr,
|
||||
compat_collections_chain_map as ChainMap,
|
||||
compat_filter as filter,
|
||||
compat_itertools_zip_longest as zip_longest,
|
||||
compat_map as map,
|
||||
compat_str,
|
||||
)
|
||||
|
||||
|
@ -252,7 +254,7 @@ class Debugger(object):
|
|||
cls.write('=> Raises:', e, '<-|', stmt, level=allow_recursion)
|
||||
raise
|
||||
if cls.ENABLED and stmt.strip():
|
||||
if should_ret or not repr(ret) == stmt:
|
||||
if should_ret or repr(ret) != stmt:
|
||||
cls.write(['->', '=>'][should_ret], repr(ret), '<-|', stmt, level=allow_recursion)
|
||||
return ret, should_ret
|
||||
return interpret_statement
|
||||
|
@ -365,6 +367,8 @@ class JSInterpreter(object):
|
|||
start, splits, pos, delim_len = 0, 0, 0, len(delim) - 1
|
||||
in_quote, escaping, after_op, in_regex_char_group = None, False, True, False
|
||||
skipping = 0
|
||||
if skip_delims:
|
||||
skip_delims = variadic(skip_delims)
|
||||
for idx, char in enumerate(expr):
|
||||
paren_delta = 0
|
||||
if not in_quote:
|
||||
|
@ -391,7 +395,7 @@ class JSInterpreter(object):
|
|||
continue
|
||||
elif pos == 0 and skip_delims:
|
||||
here = expr[idx:]
|
||||
for s in variadic(skip_delims):
|
||||
for s in skip_delims:
|
||||
if here.startswith(s) and s:
|
||||
skipping = len(s) - 1
|
||||
break
|
||||
|
@ -412,7 +416,6 @@ class JSInterpreter(object):
|
|||
if delim is None:
|
||||
delim = expr and _MATCHING_PARENS[expr[0]]
|
||||
separated = list(cls._separate(expr, delim, 1))
|
||||
|
||||
if len(separated) < 2:
|
||||
raise cls.Exception('No terminating paren {delim} in {expr!r:.5500}'.format(**locals()))
|
||||
return separated[0][1:].strip(), separated[1].strip()
|
||||
|
@ -487,6 +490,7 @@ class JSInterpreter(object):
|
|||
# fails on (eg) if (...) stmt1; else stmt2;
|
||||
sub_statements = list(self._separate(stmt, ';')) or ['']
|
||||
expr = stmt = sub_statements.pop().strip()
|
||||
|
||||
for sub_stmt in sub_statements:
|
||||
ret, should_return = self.interpret_statement(sub_stmt, local_vars, allow_recursion)
|
||||
if should_return:
|
||||
|
@ -626,8 +630,7 @@ class JSInterpreter(object):
|
|||
if m.group('err'):
|
||||
catch_vars[m.group('err')] = err.error if isinstance(err, JS_Throw) else err
|
||||
catch_vars = local_vars.new_child(m=catch_vars)
|
||||
err = None
|
||||
pending = self.interpret_statement(sub_expr, catch_vars, allow_recursion)
|
||||
err, pending = None, self.interpret_statement(sub_expr, catch_vars, allow_recursion)
|
||||
|
||||
m = self._FINALLY_RE.match(expr)
|
||||
if m:
|
||||
|
@ -801,16 +804,19 @@ class JSInterpreter(object):
|
|||
if op in ('+', '-'):
|
||||
# simplify/adjust consecutive instances of these operators
|
||||
undone = 0
|
||||
while len(separated) > 1 and not separated[-1].strip():
|
||||
separated = [s.strip() for s in separated]
|
||||
while len(separated) > 1 and not separated[-1]:
|
||||
undone += 1
|
||||
separated.pop()
|
||||
if op == '-' and undone % 2 != 0:
|
||||
right_expr = op + right_expr
|
||||
elif op == '+':
|
||||
while len(separated) > 1 and separated[-1].strip() in self.OP_CHARS:
|
||||
while len(separated) > 1 and set(separated[-1]) <= self.OP_CHARS:
|
||||
right_expr = separated.pop() + right_expr
|
||||
if separated[-1][-1:] in self.OP_CHARS:
|
||||
right_expr = separated.pop() + right_expr
|
||||
# hanging op at end of left => unary + (strip) or - (push right)
|
||||
left_val = separated[-1]
|
||||
left_val = separated[-1] if separated else ''
|
||||
for dm_op in ('*', '%', '/', '**'):
|
||||
bodmas = tuple(self._separate(left_val, dm_op, skip_delims=skip_delim))
|
||||
if len(bodmas) > 1 and not bodmas[-1].strip():
|
||||
|
@ -844,7 +850,7 @@ class JSInterpreter(object):
|
|||
memb = member
|
||||
raise self.Exception('{memb} {msg}'.format(**locals()), expr=expr)
|
||||
|
||||
def eval_method():
|
||||
def eval_method(variable, member):
|
||||
if (variable, member) == ('console', 'debug'):
|
||||
if Debugger.ENABLED:
|
||||
Debugger.write(self.interpret_expression('[{}]'.format(arg_str), local_vars, allow_recursion))
|
||||
|
@ -852,6 +858,7 @@ class JSInterpreter(object):
|
|||
types = {
|
||||
'String': compat_str,
|
||||
'Math': float,
|
||||
'Array': list,
|
||||
}
|
||||
obj = local_vars.get(variable)
|
||||
if obj in (JS_Undefined, None):
|
||||
|
@ -877,12 +884,29 @@ class JSInterpreter(object):
|
|||
self.interpret_expression(v, local_vars, allow_recursion)
|
||||
for v in self._separate(arg_str)]
|
||||
|
||||
if obj == compat_str:
|
||||
# Fixup prototype call
|
||||
if isinstance(obj, type):
|
||||
new_member, rest = member.partition('.')[0::2]
|
||||
if new_member == 'prototype':
|
||||
new_member, func_prototype = rest.partition('.')[0::2]
|
||||
assertion(argvals, 'takes one or more arguments')
|
||||
assertion(isinstance(argvals[0], obj), 'must bind to type {0}'.format(obj))
|
||||
if func_prototype == 'call':
|
||||
obj = argvals.pop(0)
|
||||
elif func_prototype == 'apply':
|
||||
assertion(len(argvals) == 2, 'takes two arguments')
|
||||
obj, argvals = argvals
|
||||
assertion(isinstance(argvals, list), 'second argument must be a list')
|
||||
else:
|
||||
raise self.Exception('Unsupported Function method ' + func_prototype, expr)
|
||||
member = new_member
|
||||
|
||||
if obj is compat_str:
|
||||
if member == 'fromCharCode':
|
||||
assertion(argvals, 'takes one or more arguments')
|
||||
return ''.join(map(compat_chr, argvals))
|
||||
raise self.Exception('Unsupported string method ' + member, expr=expr)
|
||||
elif obj == float:
|
||||
elif obj is float:
|
||||
if member == 'pow':
|
||||
assertion(len(argvals) == 2, 'takes two arguments')
|
||||
return argvals[0] ** argvals[1]
|
||||
|
@ -907,12 +931,12 @@ class JSInterpreter(object):
|
|||
elif member == 'splice':
|
||||
assertion(isinstance(obj, list), 'must be applied on a list')
|
||||
assertion(argvals, 'takes one or more arguments')
|
||||
index, howMany = map(int, (argvals + [len(obj)])[:2])
|
||||
index, how_many = map(int, (argvals + [len(obj)])[:2])
|
||||
if index < 0:
|
||||
index += len(obj)
|
||||
add_items = argvals[2:]
|
||||
res = []
|
||||
for i in range(index, min(index + howMany, len(obj))):
|
||||
for _ in range(index, min(index + how_many, len(obj))):
|
||||
res.append(obj.pop(index))
|
||||
for i, item in enumerate(add_items):
|
||||
obj.insert(index + i, item)
|
||||
|
@ -970,11 +994,11 @@ class JSInterpreter(object):
|
|||
|
||||
if remaining:
|
||||
ret, should_abort = self.interpret_statement(
|
||||
self._named_object(local_vars, eval_method()) + remaining,
|
||||
self._named_object(local_vars, eval_method(variable, member)) + remaining,
|
||||
local_vars, allow_recursion)
|
||||
return ret, should_return or should_abort
|
||||
else:
|
||||
return eval_method(), should_return
|
||||
return eval_method(variable, member), should_return
|
||||
|
||||
elif md.get('function'):
|
||||
fname = m.group('fname')
|
||||
|
@ -1002,28 +1026,25 @@ class JSInterpreter(object):
|
|||
def extract_object(self, objname):
|
||||
_FUNC_NAME_RE = r'''(?:[a-zA-Z$0-9]+|"[a-zA-Z$0-9]+"|'[a-zA-Z$0-9]+')'''
|
||||
obj = {}
|
||||
fields = None
|
||||
for obj_m in re.finditer(
|
||||
fields = next(filter(None, (
|
||||
obj_m.group('fields') for obj_m in re.finditer(
|
||||
r'''(?xs)
|
||||
{0}\s*\.\s*{1}|{1}\s*=\s*\{{\s*
|
||||
(?P<fields>({2}\s*:\s*function\s*\(.*?\)\s*\{{.*?}}(?:,\s*)?)*)
|
||||
}}\s*;
|
||||
'''.format(_NAME_RE, re.escape(objname), _FUNC_NAME_RE),
|
||||
self.code):
|
||||
fields = obj_m.group('fields')
|
||||
if fields:
|
||||
break
|
||||
else:
|
||||
self.code))), None)
|
||||
if not fields:
|
||||
raise self.Exception('Could not find object ' + objname)
|
||||
# Currently, it only supports function definitions
|
||||
fields_m = re.finditer(
|
||||
r'''(?x)
|
||||
(?P<key>%s)\s*:\s*function\s*\((?P<args>(?:%s|,)*)\){(?P<code>[^}]+)}
|
||||
''' % (_FUNC_NAME_RE, _NAME_RE),
|
||||
fields)
|
||||
for f in fields_m:
|
||||
for f in re.finditer(
|
||||
r'''(?x)
|
||||
(?P<key>%s)\s*:\s*function\s*\((?P<args>(?:%s|,)*)\){(?P<code>[^}]+)}
|
||||
''' % (_FUNC_NAME_RE, _NAME_RE),
|
||||
fields):
|
||||
argnames = self.build_arglist(f.group('args'))
|
||||
obj[remove_quotes(f.group('key'))] = self.build_function(argnames, f.group('code'))
|
||||
name = remove_quotes(f.group('key'))
|
||||
obj[name] = function_with_repr(self.build_function(argnames, f.group('code')), 'F<{0}>'.format(name))
|
||||
|
||||
return obj
|
||||
|
||||
|
@ -1058,7 +1079,7 @@ class JSInterpreter(object):
|
|||
def extract_function(self, funcname):
|
||||
return function_with_repr(
|
||||
self.extract_function_from_code(*self.extract_function_code(funcname)),
|
||||
'F<%s>' % (funcname, ))
|
||||
'F<%s>' % (funcname,))
|
||||
|
||||
def extract_function_from_code(self, argnames, code, *global_stack):
|
||||
local_vars = {}
|
||||
|
@ -1067,7 +1088,7 @@ class JSInterpreter(object):
|
|||
if mobj is None:
|
||||
break
|
||||
start, body_start = mobj.span()
|
||||
body, remaining = self._separate_at_paren(code[body_start - 1:], '}')
|
||||
body, remaining = self._separate_at_paren(code[body_start - 1:])
|
||||
name = self._named_object(local_vars, self.extract_function_from_code(
|
||||
[x.strip() for x in mobj.group('args').split(',')],
|
||||
body, local_vars, *global_stack))
|
||||
|
@ -1095,8 +1116,7 @@ class JSInterpreter(object):
|
|||
argnames = tuple(argnames)
|
||||
|
||||
def resf(args, kwargs={}, allow_recursion=100):
|
||||
global_stack[0].update(
|
||||
zip_longest(argnames, args, fillvalue=None))
|
||||
global_stack[0].update(zip_longest(argnames, args, fillvalue=None))
|
||||
global_stack[0].update(kwargs)
|
||||
var_stack = LocalNameSpace(*global_stack)
|
||||
ret, should_abort = self.interpret_statement(code.replace('\n', ' '), var_stack, allow_recursion - 1)
|
||||
|
|
|
@ -533,6 +533,10 @@ def parseOpts(overrideArguments=None):
|
|||
'--no-check-certificate',
|
||||
action='store_true', dest='no_check_certificate', default=False,
|
||||
help='Suppress HTTPS certificate validation')
|
||||
workarounds.add_option(
|
||||
'--no-check-extensions',
|
||||
action='store_true', dest='no_check_extensions', default=False,
|
||||
help='Suppress file extension validation')
|
||||
workarounds.add_option(
|
||||
'--prefer-insecure',
|
||||
'--prefer-unsecure', action='store_true', dest='prefer_insecure',
|
||||
|
|
|
@ -1717,21 +1717,6 @@ TIMEZONE_NAMES = {
|
|||
'PST': -8, 'PDT': -7 # Pacific
|
||||
}
|
||||
|
||||
KNOWN_EXTENSIONS = (
|
||||
'mp4', 'm4a', 'm4p', 'm4b', 'm4r', 'm4v', 'aac',
|
||||
'flv', 'f4v', 'f4a', 'f4b',
|
||||
'webm', 'ogg', 'ogv', 'oga', 'ogx', 'spx', 'opus',
|
||||
'mkv', 'mka', 'mk3d',
|
||||
'avi', 'divx',
|
||||
'mov',
|
||||
'asf', 'wmv', 'wma',
|
||||
'3gp', '3g2',
|
||||
'mp3',
|
||||
'flac',
|
||||
'ape',
|
||||
'wav',
|
||||
'f4f', 'f4m', 'm3u8', 'smil')
|
||||
|
||||
# needed for sanitizing filenames in restricted mode
|
||||
ACCENT_CHARS = dict(zip('ÂÃÄÀÁÅÆÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖŐØŒÙÚÛÜŰÝÞßàáâãäåæçèéêëìíîïðñòóôõöőøœùúûüűýþÿ',
|
||||
itertools.chain('AAAAAA', ['AE'], 'CEEEEIIIIDNOOOOOOO', ['OE'], 'UUUUUY', ['TH', 'ss'],
|
||||
|
@ -3959,19 +3944,22 @@ def parse_duration(s):
|
|||
return duration
|
||||
|
||||
|
||||
def prepend_extension(filename, ext, expected_real_ext=None):
|
||||
def _change_extension(prepend, filename, ext, expected_real_ext=None):
|
||||
name, real_ext = os.path.splitext(filename)
|
||||
return (
|
||||
'{0}.{1}{2}'.format(name, ext, real_ext)
|
||||
if not expected_real_ext or real_ext[1:] == expected_real_ext
|
||||
else '{0}.{1}'.format(filename, ext))
|
||||
sanitize_extension = _UnsafeExtensionError.sanitize_extension
|
||||
|
||||
if not expected_real_ext or real_ext.partition('.')[0::2] == ('', expected_real_ext):
|
||||
filename = name
|
||||
if prepend and real_ext:
|
||||
sanitize_extension(ext, prepend=prepend)
|
||||
return ''.join((filename, '.', ext, real_ext))
|
||||
|
||||
# Mitigate path traversal and file impersonation attacks
|
||||
return '.'.join((filename, sanitize_extension(ext)))
|
||||
|
||||
|
||||
def replace_extension(filename, ext, expected_real_ext=None):
|
||||
name, real_ext = os.path.splitext(filename)
|
||||
return '{0}.{1}'.format(
|
||||
name if not expected_real_ext or real_ext[1:] == expected_real_ext else filename,
|
||||
ext)
|
||||
prepend_extension = functools.partial(_change_extension, True)
|
||||
replace_extension = functools.partial(_change_extension, False)
|
||||
|
||||
|
||||
def check_executable(exe, args=[]):
|
||||
|
@ -6561,3 +6549,169 @@ def join_nonempty(*values, **kwargs):
|
|||
if from_dict is not None:
|
||||
values = (traverse_obj(from_dict, variadic(v)) for v in values)
|
||||
return delim.join(map(compat_str, filter(None, values)))
|
||||
|
||||
|
||||
class Namespace(object):
|
||||
"""Immutable namespace"""
|
||||
|
||||
def __init__(self, **kw_attr):
|
||||
self.__dict__.update(kw_attr)
|
||||
|
||||
def __iter__(self):
|
||||
return iter(self.__dict__.values())
|
||||
|
||||
@property
|
||||
def items_(self):
|
||||
return self.__dict__.items()
|
||||
|
||||
|
||||
MEDIA_EXTENSIONS = Namespace(
|
||||
common_video=('avi', 'flv', 'mkv', 'mov', 'mp4', 'webm'),
|
||||
video=('3g2', '3gp', 'f4v', 'mk3d', 'divx', 'mpg', 'ogv', 'm4v', 'wmv'),
|
||||
common_audio=('aiff', 'alac', 'flac', 'm4a', 'mka', 'mp3', 'ogg', 'opus', 'wav'),
|
||||
audio=('aac', 'ape', 'asf', 'f4a', 'f4b', 'm4b', 'm4p', 'm4r', 'oga', 'ogx', 'spx', 'vorbis', 'wma', 'weba'),
|
||||
thumbnails=('jpg', 'png', 'webp'),
|
||||
# storyboards=('mhtml', ),
|
||||
subtitles=('srt', 'vtt', 'ass', 'lrc', 'ttml'),
|
||||
manifests=('f4f', 'f4m', 'm3u8', 'smil', 'mpd'),
|
||||
)
|
||||
MEDIA_EXTENSIONS.video = MEDIA_EXTENSIONS.common_video + MEDIA_EXTENSIONS.video
|
||||
MEDIA_EXTENSIONS.audio = MEDIA_EXTENSIONS.common_audio + MEDIA_EXTENSIONS.audio
|
||||
|
||||
KNOWN_EXTENSIONS = (
|
||||
MEDIA_EXTENSIONS.video + MEDIA_EXTENSIONS.audio
|
||||
+ MEDIA_EXTENSIONS.manifests
|
||||
)
|
||||
|
||||
|
||||
class _UnsafeExtensionError(Exception):
|
||||
"""
|
||||
Mitigation exception for unwanted file overwrite/path traversal
|
||||
|
||||
Ref: https://github.com/yt-dlp/yt-dlp/security/advisories/GHSA-79w7-vh3h-8g4j
|
||||
"""
|
||||
_ALLOWED_EXTENSIONS = frozenset(itertools.chain(
|
||||
( # internal
|
||||
'description',
|
||||
'json',
|
||||
'meta',
|
||||
'orig',
|
||||
'part',
|
||||
'temp',
|
||||
'uncut',
|
||||
'unknown_video',
|
||||
'ytdl',
|
||||
),
|
||||
# video
|
||||
MEDIA_EXTENSIONS.video, (
|
||||
'asx',
|
||||
'ismv',
|
||||
'm2t',
|
||||
'm2ts',
|
||||
'm2v',
|
||||
'm4s',
|
||||
'mng',
|
||||
'mp2v',
|
||||
'mp4v',
|
||||
'mpe',
|
||||
'mpeg',
|
||||
'mpeg1',
|
||||
'mpeg2',
|
||||
'mpeg4',
|
||||
'mxf',
|
||||
'ogm',
|
||||
'qt',
|
||||
'rm',
|
||||
'swf',
|
||||
'ts',
|
||||
'vob',
|
||||
'vp9',
|
||||
),
|
||||
# audio
|
||||
MEDIA_EXTENSIONS.audio, (
|
||||
'3ga',
|
||||
'ac3',
|
||||
'adts',
|
||||
'aif',
|
||||
'au',
|
||||
'dts',
|
||||
'isma',
|
||||
'it',
|
||||
'mid',
|
||||
'mod',
|
||||
'mpga',
|
||||
'mp1',
|
||||
'mp2',
|
||||
'mp4a',
|
||||
'mpa',
|
||||
'ra',
|
||||
'shn',
|
||||
'xm',
|
||||
),
|
||||
# image
|
||||
MEDIA_EXTENSIONS.thumbnails, (
|
||||
'avif',
|
||||
'bmp',
|
||||
'gif',
|
||||
'ico',
|
||||
'heic',
|
||||
'jng',
|
||||
'jpeg',
|
||||
'jxl',
|
||||
'svg',
|
||||
'tif',
|
||||
'tiff',
|
||||
'wbmp',
|
||||
),
|
||||
# subtitle
|
||||
MEDIA_EXTENSIONS.subtitles, (
|
||||
'dfxp',
|
||||
'fs',
|
||||
'ismt',
|
||||
'json3',
|
||||
'sami',
|
||||
'scc',
|
||||
'srv1',
|
||||
'srv2',
|
||||
'srv3',
|
||||
'ssa',
|
||||
'tt',
|
||||
'xml',
|
||||
),
|
||||
# others
|
||||
MEDIA_EXTENSIONS.manifests,
|
||||
(
|
||||
# not used in yt-dl
|
||||
# *MEDIA_EXTENSIONS.storyboards,
|
||||
# 'desktop',
|
||||
# 'ism',
|
||||
# 'm3u',
|
||||
# 'sbv',
|
||||
# 'swp',
|
||||
# 'url',
|
||||
# 'webloc',
|
||||
)))
|
||||
|
||||
def __init__(self, extension):
|
||||
super(_UnsafeExtensionError, self).__init__('unsafe file extension: {0!r}'.format(extension))
|
||||
self.extension = extension
|
||||
|
||||
# support --no-check-extensions
|
||||
lenient = False
|
||||
|
||||
@classmethod
|
||||
def sanitize_extension(cls, extension, **kwargs):
|
||||
# ... /, *, prepend=False
|
||||
prepend = kwargs.get('prepend', False)
|
||||
|
||||
if '/' in extension or '\\' in extension:
|
||||
raise cls(extension)
|
||||
|
||||
if not prepend:
|
||||
last = extension.rpartition('.')[-1]
|
||||
if last == 'bin':
|
||||
extension = last = 'unknown_video'
|
||||
if not (cls.lenient or last.lower() in cls._ALLOWED_EXTENSIONS):
|
||||
raise cls(extension)
|
||||
|
||||
return extension
|
||||
|
|
Loading…
Reference in New Issue
Block a user