Add new option --source-address

Closes #3618, fixes #721, fixes #2481, fixes #4551, closes #1020.
This commit is contained in:
Philipp Hagemeister 2015-01-10 19:55:36 +01:00
parent 6ce08764a1
commit be4a824d74
6 changed files with 98 additions and 19 deletions

View file

@ -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( https_handler = make_HTTPS_handler(self.params, debuglevel=debuglevel)
self.params.get('nocheckcertificate', False), debuglevel=debuglevel) ydlh = YoutubeDLHandler(self.params, debuglevel=debuglevel)
ydlh = YoutubeDLHandler(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

View file

@ -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:

View file

@ -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',

View file

@ -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)

View file

@ -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

View file

@ -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): return YoutubeDLHTTPSHandler(params, https_conn_class=HTTPSConnectionV3, **kwargs)
def https_open(self, req):
return self.do_open(HTTPSConnectionV3, req)
return HTTPSHandlerV3(**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 """