Browse Source

Add new option --source-address

Closes #3618, fixes #721, fixes #2481, fixes #4551, closes #1020.
Philipp Hagemeister 10 years ago
parent
commit
be4a824d74
6 changed files with 98 additions and 19 deletions
  1. 3 3
      youtube_dl/YoutubeDL.py
  2. 1 0
      youtube_dl/__init__.py
  3. 28 0
      youtube_dl/compat.py
  4. 16 8
      youtube_dl/options.py
  5. 1 1
      youtube_dl/update.py
  6. 49 7
      youtube_dl/utils.py

+ 3 - 3
youtube_dl/YoutubeDL.py

@@ -211,6 +211,7 @@ class YoutubeDL(object):
                        - "warn": only emit a warning
                        - "warn": only emit a warning
                        - "detect_or_warn": check whether we can do anything
                        - "detect_or_warn": check whether we can do anything
                                            about it, warn otherwise
                                            about it, warn otherwise
+    source_address:    (Experimental) Client-side IP address to bind to.
 
 
 
 
     The following parameters are not used by YoutubeDL itself, they are used by
     The following parameters are not used by YoutubeDL itself, they are used by
@@ -1493,9 +1494,8 @@ class YoutubeDL(object):
         proxy_handler = compat_urllib_request.ProxyHandler(proxies)
         proxy_handler = compat_urllib_request.ProxyHandler(proxies)
 
 
         debuglevel = 1 if self.params.get('debug_printtraffic') else 0
         debuglevel = 1 if self.params.get('debug_printtraffic') else 0
-        https_handler = make_HTTPS_handler(
-            self.params.get('nocheckcertificate', False), debuglevel=debuglevel)
-        ydlh = YoutubeDLHandler(debuglevel=debuglevel)
+        https_handler = make_HTTPS_handler(self.params, debuglevel=debuglevel)
+        ydlh = YoutubeDLHandler(self.params, debuglevel=debuglevel)
         opener = compat_urllib_request.build_opener(
         opener = compat_urllib_request.build_opener(
             https_handler, proxy_handler, cookie_processor, ydlh)
             https_handler, proxy_handler, cookie_processor, ydlh)
         # Delete the default user-agent header, which would otherwise apply in
         # Delete the default user-agent header, which would otherwise apply in

+ 1 - 0
youtube_dl/__init__.py

@@ -327,6 +327,7 @@ def _real_main(argv=None):
         'merge_output_format': opts.merge_output_format,
         'merge_output_format': opts.merge_output_format,
         'postprocessors': postprocessors,
         'postprocessors': postprocessors,
         'fixup': opts.fixup,
         'fixup': opts.fixup,
+        'source_address': opts.source_address,
     }
     }
 
 
     with YoutubeDL(ydl_opts) as ydl:
     with YoutubeDL(ydl_opts) as ydl:

+ 28 - 0
youtube_dl/compat.py

@@ -4,6 +4,7 @@ import getpass
 import optparse
 import optparse
 import os
 import os
 import re
 import re
+import socket
 import subprocess
 import subprocess
 import sys
 import sys
 
 
@@ -307,6 +308,32 @@ else:
     compat_kwargs = lambda kwargs: kwargs
     compat_kwargs = lambda kwargs: kwargs
 
 
 
 
+if sys.version_info < (2, 7):
+    def compat_socket_create_connection(address, timeout, source_address=None):
+        host, port = address
+        err = None
+        for res in socket.getaddrinfo(host, port, 0, socket.SOCK_STREAM):
+            af, socktype, proto, canonname, sa = res
+            sock = None
+            try:
+                sock = socket.socket(af, socktype, proto)
+                sock.settimeout(timeout)
+                if source_address:
+                    sock.bind(source_address)
+                sock.connect(sa)
+                return sock
+            except socket.error as _:
+                err = _
+                if sock is not None:
+                    sock.close()
+        if err is not None:
+            raise err
+        else:
+            raise error("getaddrinfo returns an empty list")
+else:
+    compat_socket_create_connection = socket.create_connection
+
+
 # Fix https://github.com/rg3/youtube-dl/issues/4223
 # Fix https://github.com/rg3/youtube-dl/issues/4223
 # See http://bugs.python.org/issue9161 for what is broken
 # See http://bugs.python.org/issue9161 for what is broken
 def workaround_optparse_bug9161():
 def workaround_optparse_bug9161():
@@ -343,6 +370,7 @@ __all__ = [
     'compat_parse_qs',
     'compat_parse_qs',
     'compat_print',
     'compat_print',
     'compat_str',
     'compat_str',
+    'compat_socket_create_connection',
     'compat_subprocess_get_DEVNULL',
     'compat_subprocess_get_DEVNULL',
     'compat_urllib_error',
     'compat_urllib_error',
     'compat_urllib_parse',
     'compat_urllib_parse',

+ 16 - 8
youtube_dl/options.py

@@ -148,14 +148,6 @@ def parseOpts(overrideArguments=None):
         '--extractor-descriptions',
         '--extractor-descriptions',
         action='store_true', dest='list_extractor_descriptions', default=False,
         action='store_true', dest='list_extractor_descriptions', default=False,
         help='Output descriptions of all supported extractors')
         help='Output descriptions of all supported extractors')
-    general.add_option(
-        '--proxy', dest='proxy',
-        default=None, metavar='URL',
-        help='Use the specified HTTP/HTTPS proxy. Pass in an empty string (--proxy "") for direct connection')
-    general.add_option(
-        '--socket-timeout',
-        dest='socket_timeout', type=float, default=None,
-        help='Time to wait before giving up, in seconds')
     general.add_option(
     general.add_option(
         '--default-search',
         '--default-search',
         dest='default_search', metavar='PREFIX',
         dest='default_search', metavar='PREFIX',
@@ -173,6 +165,21 @@ def parseOpts(overrideArguments=None):
         default=False,
         default=False,
         help='Do not extract the videos of a playlist, only list them.')
         help='Do not extract the videos of a playlist, only list them.')
 
 
+    network = optparse.OptionGroup(parser, 'Network Options')
+    network.add_option(
+        '--proxy', dest='proxy',
+        default=None, metavar='URL',
+        help='Use the specified HTTP/HTTPS proxy. Pass in an empty string (--proxy "") for direct connection')
+    network.add_option(
+        '--socket-timeout',
+        dest='socket_timeout', type=float, default=None, metavar='SECONDS',
+        help='Time to wait before giving up, in seconds')
+    network.add_option(
+        '--source-address',
+        metavar='IP', dest='source_address', default=None,
+        help='Client-side IP address to bind to (experimental)',
+    )
+
     selection = optparse.OptionGroup(parser, 'Video Selection')
     selection = optparse.OptionGroup(parser, 'Video Selection')
     selection.add_option(
     selection.add_option(
         '--playlist-start',
         '--playlist-start',
@@ -652,6 +659,7 @@ def parseOpts(overrideArguments=None):
         help='Execute a command on the file after downloading, similar to find\'s -exec syntax. Example: --exec \'adb push {} /sdcard/Music/ && rm {}\'')
         help='Execute a command on the file after downloading, similar to find\'s -exec syntax. Example: --exec \'adb push {} /sdcard/Music/ && rm {}\'')
 
 
     parser.add_option_group(general)
     parser.add_option_group(general)
+    parser.add_option_group(network)
     parser.add_option_group(selection)
     parser.add_option_group(selection)
     parser.add_option_group(downloader)
     parser.add_option_group(downloader)
     parser.add_option_group(filesystem)
     parser.add_option_group(filesystem)

+ 1 - 1
youtube_dl/update.py

@@ -59,7 +59,7 @@ def update_self(to_screen, verbose):
         to_screen('It looks like you installed youtube-dl with a package manager, pip, setup.py or a tarball. Please use that to update.')
         to_screen('It looks like you installed youtube-dl with a package manager, pip, setup.py or a tarball. Please use that to update.')
         return
         return
 
 
-    https_handler = make_HTTPS_handler(False)
+    https_handler = make_HTTPS_handler({})
     opener = compat_urllib_request.build_opener(https_handler)
     opener = compat_urllib_request.build_opener(https_handler)
 
 
     # Check if there is a new version
     # Check if there is a new version

+ 49 - 7
youtube_dl/utils.py

@@ -10,6 +10,7 @@ import ctypes
 import datetime
 import datetime
 import email.utils
 import email.utils
 import errno
 import errno
+import functools
 import gzip
 import gzip
 import itertools
 import itertools
 import io
 import io
@@ -34,7 +35,9 @@ from .compat import (
     compat_chr,
     compat_chr,
     compat_getenv,
     compat_getenv,
     compat_html_entities,
     compat_html_entities,
+    compat_http_client,
     compat_parse_qs,
     compat_parse_qs,
+    compat_socket_create_connection,
     compat_str,
     compat_str,
     compat_urllib_error,
     compat_urllib_error,
     compat_urllib_parse,
     compat_urllib_parse,
@@ -391,13 +394,14 @@ def formatSeconds(secs):
         return '%d' % secs
         return '%d' % secs
 
 
 
 
-def make_HTTPS_handler(opts_no_check_certificate, **kwargs):
+def make_HTTPS_handler(params, **kwargs):
+    opts_no_check_certificate = params.get('nocheckcertificate', False)
     if hasattr(ssl, 'create_default_context'):  # Python >= 3.4 or 2.7.9
     if hasattr(ssl, 'create_default_context'):  # Python >= 3.4 or 2.7.9
         context = ssl.create_default_context(ssl.Purpose.CLIENT_AUTH)
         context = ssl.create_default_context(ssl.Purpose.CLIENT_AUTH)
         if opts_no_check_certificate:
         if opts_no_check_certificate:
             context.verify_mode = ssl.CERT_NONE
             context.verify_mode = ssl.CERT_NONE
         try:
         try:
-            return compat_urllib_request.HTTPSHandler(context=context, **kwargs)
+            return YoutubeDLHTTPSHandler(params, context=context, **kwargs)
         except TypeError:
         except TypeError:
             # Python 2.7.8
             # Python 2.7.8
             # (create_default_context present but HTTPSHandler has no context=)
             # (create_default_context present but HTTPSHandler has no context=)
@@ -420,17 +424,14 @@ def make_HTTPS_handler(opts_no_check_certificate, **kwargs):
                 except ssl.SSLError:
                 except ssl.SSLError:
                     self.sock = ssl.wrap_socket(sock, self.key_file, self.cert_file, ssl_version=ssl.PROTOCOL_SSLv23)
                     self.sock = ssl.wrap_socket(sock, self.key_file, self.cert_file, ssl_version=ssl.PROTOCOL_SSLv23)
 
 
-        class HTTPSHandlerV3(compat_urllib_request.HTTPSHandler):
-            def https_open(self, req):
-                return self.do_open(HTTPSConnectionV3, req)
-        return HTTPSHandlerV3(**kwargs)
+        return YoutubeDLHTTPSHandler(params, https_conn_class=HTTPSConnectionV3, **kwargs)
     else:  # Python < 3.4
     else:  # Python < 3.4
         context = ssl.SSLContext(ssl.PROTOCOL_SSLv23)
         context = ssl.SSLContext(ssl.PROTOCOL_SSLv23)
         context.verify_mode = (ssl.CERT_NONE
         context.verify_mode = (ssl.CERT_NONE
                                if opts_no_check_certificate
                                if opts_no_check_certificate
                                else ssl.CERT_REQUIRED)
                                else ssl.CERT_REQUIRED)
         context.set_default_verify_paths()
         context.set_default_verify_paths()
-        return compat_urllib_request.HTTPSHandler(context=context, **kwargs)
+        return YoutubeDLHTTPSHandler(params, context=context, **kwargs)
 
 
 
 
 class ExtractorError(Exception):
 class ExtractorError(Exception):
@@ -544,6 +545,26 @@ class ContentTooShortError(Exception):
         self.expected = expected
         self.expected = expected
 
 
 
 
+def _create_http_connection(ydl_handler, http_class, is_https=False, *args, **kwargs):
+    hc = http_class(*args, **kwargs)
+    source_address = ydl_handler._params.get('source_address')
+    if source_address is not None:
+        sa = (source_address, 0)
+        if hasattr(hc, 'source_address'):  # Python 2.7+
+            hc.source_address = sa
+        else:  # Python 2.6
+            def _hc_connect(self, *args, **kwargs):
+                sock = compat_socket_create_connection(
+                    (self.host, self.port), self.timeout, sa)
+                if is_https:
+                    self.sock = ssl.wrap_socket(sock, self.key_file, self.cert_file)
+                else:
+                    self.sock = sock
+            hc.connect = functools.partial(_hc_connect, hc)
+
+    return hc
+
+
 class YoutubeDLHandler(compat_urllib_request.HTTPHandler):
 class YoutubeDLHandler(compat_urllib_request.HTTPHandler):
     """Handler for HTTP requests and responses.
     """Handler for HTTP requests and responses.
 
 
@@ -562,6 +583,15 @@ class YoutubeDLHandler(compat_urllib_request.HTTPHandler):
     public domain.
     public domain.
     """
     """
 
 
+    def __init__(self, params, *args, **kwargs):
+        compat_urllib_request.HTTPHandler.__init__(self, *args, **kwargs)
+        self._params = params
+
+    def http_open(self, req):
+        return self.do_open(functools.partial(
+            _create_http_connection, self, compat_http_client.HTTPConnection),
+            req)
+
     @staticmethod
     @staticmethod
     def deflate(data):
     def deflate(data):
         try:
         try:
@@ -631,6 +661,18 @@ class YoutubeDLHandler(compat_urllib_request.HTTPHandler):
     https_response = http_response
     https_response = http_response
 
 
 
 
+class YoutubeDLHTTPSHandler(compat_urllib_request.HTTPSHandler):
+    def __init__(self, params, https_conn_class=None, *args, **kwargs):
+        compat_urllib_request.HTTPSHandler.__init__(self, *args, **kwargs)
+        self._https_conn_class = https_conn_class or compat_http_client.HTTPSConnection
+        self._params = params
+
+    def https_open(self, req):
+        return self.do_open(functools.partial(
+            _create_http_connection, self, self._https_conn_class, True),
+            req)
+
+
 def parse_iso8601(date_str, delimiter='T'):
 def parse_iso8601(date_str, delimiter='T'):
     """ Return a UNIX timestamp from the given date """
     """ Return a UNIX timestamp from the given date """