Commit b206473e authored by Benjamin Peterson's avatar Benjamin Peterson

give urllib.urlopen a context parameter (closes #22927)

parent 79828343
...@@ -31,7 +31,7 @@ reading, and no seek operations are available. ...@@ -31,7 +31,7 @@ reading, and no seek operations are available.
High-level interface High-level interface
-------------------- --------------------
.. function:: urlopen(url[, data[, proxies]]) .. function:: urlopen(url[, data[, proxies[, context]]])
Open a network object denoted by a URL for reading. If the URL does not Open a network object denoted by a URL for reading. If the URL does not
have a scheme identifier, or if it has :file:`file:` as its scheme have a scheme identifier, or if it has :file:`file:` as its scheme
...@@ -122,8 +122,12 @@ High-level interface ...@@ -122,8 +122,12 @@ High-level interface
filehandle = urllib.urlopen(some_url, proxies=None) filehandle = urllib.urlopen(some_url, proxies=None)
filehandle = urllib.urlopen(some_url) filehandle = urllib.urlopen(some_url)
Proxies which require authentication for use are not currently supported; this Proxies which require authentication for use are not currently supported;
is considered an implementation limitation. this is considered an implementation limitation.
The *context* parameter may be set to a :class:`ssl.SSLContext` instance to
configure the SSL settings that are used if :func:`urlopen` makes a HTTPS
connection.
.. versionchanged:: 2.3 .. versionchanged:: 2.3
Added the *proxies* support. Added the *proxies* support.
...@@ -132,6 +136,9 @@ High-level interface ...@@ -132,6 +136,9 @@ High-level interface
Added :meth:`getcode` to returned object and support for the Added :meth:`getcode` to returned object and support for the
:envvar:`no_proxy` environment variable. :envvar:`no_proxy` environment variable.
.. versionchanged:: 2.7.9
The *context* parameter was added.
.. deprecated:: 2.6 .. deprecated:: 2.6
The :func:`urlopen` function has been removed in Python 3 in favor The :func:`urlopen` function has been removed in Python 3 in favor
of :func:`urllib2.urlopen`. of :func:`urllib2.urlopen`.
...@@ -292,7 +299,7 @@ Utility functions ...@@ -292,7 +299,7 @@ Utility functions
URL Opener objects URL Opener objects
------------------ ------------------
.. class:: URLopener([proxies[, **x509]]) .. class:: URLopener([proxies[, context[, **x509]]])
Base class for opening and reading URLs. Unless you need to support opening Base class for opening and reading URLs. Unless you need to support opening
objects using schemes other than :file:`http:`, :file:`ftp:`, or :file:`file:`, objects using schemes other than :file:`http:`, :file:`ftp:`, or :file:`file:`,
...@@ -309,6 +316,9 @@ URL Opener objects ...@@ -309,6 +316,9 @@ URL Opener objects
value is ``None``, in which case environmental proxy settings will be used if value is ``None``, in which case environmental proxy settings will be used if
present, as discussed in the definition of :func:`urlopen`, above. present, as discussed in the definition of :func:`urlopen`, above.
The *context* parameter may be a :class:`ssl.SSLContext` instance. If given,
it defines the SSL settings the opener uses to make HTTPS connections.
Additional keyword parameters, collected in *x509*, may be used for Additional keyword parameters, collected in *x509*, may be used for
authentication of the client when using the :file:`https:` scheme. The keywords authentication of the client when using the :file:`https:` scheme. The keywords
*key_file* and *cert_file* are supported to provide an SSL key and certificate; *key_file* and *cert_file* are supported to provide an SSL key and certificate;
......
...@@ -1238,14 +1238,15 @@ else: ...@@ -1238,14 +1238,15 @@ else:
_connection_class = HTTPSConnection _connection_class = HTTPSConnection
def __init__(self, host='', port=None, key_file=None, cert_file=None, def __init__(self, host='', port=None, key_file=None, cert_file=None,
strict=None): strict=None, context=None):
# provide a default host, pass the X509 cert info # provide a default host, pass the X509 cert info
# urf. compensate for bad input. # urf. compensate for bad input.
if port == 0: if port == 0:
port = None port = None
self._setup(self._connection_class(host, port, key_file, self._setup(self._connection_class(host, port, key_file,
cert_file, strict)) cert_file, strict,
context=context))
# we never actually use these for anything, but we keep them # we never actually use these for anything, but we keep them
# here for compatibility with post-1.5.2 CVS. # here for compatibility with post-1.5.2 CVS.
......
...@@ -7,6 +7,15 @@ import sys ...@@ -7,6 +7,15 @@ import sys
import os import os
import time import time
try:
import ssl
except ImportError:
ssl = None
here = os.path.dirname(__file__)
# Self-signed cert file for self-signed.pythontest.net
CERT_selfsigned_pythontestdotnet = os.path.join(here, 'selfsigned_pythontestdotnet.pem')
mimetools = test_support.import_module("mimetools", deprecated=True) mimetools = test_support.import_module("mimetools", deprecated=True)
...@@ -195,6 +204,14 @@ class urlretrieveNetworkTests(unittest.TestCase): ...@@ -195,6 +204,14 @@ class urlretrieveNetworkTests(unittest.TestCase):
self.fail('Date value not in %r format', dateformat) self.fail('Date value not in %r format', dateformat)
@unittest.skipIf(ssl is None, "requires ssl")
class urlopen_HttpsTests(unittest.TestCase):
def test_context_argument(self):
context = ssl.create_default_context(cafile=CERT_selfsigned_pythontestdotnet)
response = urllib.urlopen("https://self-signed.pythontest.net", context=context)
self.assertIn("Python", response.read())
def test_main(): def test_main():
test_support.requires('network') test_support.requires('network')
...@@ -202,7 +219,8 @@ def test_main(): ...@@ -202,7 +219,8 @@ def test_main():
("urllib.urlopen.. has been removed", DeprecationWarning)): ("urllib.urlopen.. has been removed", DeprecationWarning)):
test_support.run_unittest(URLTimeoutTest, test_support.run_unittest(URLTimeoutTest,
urlopenNetworkTests, urlopenNetworkTests,
urlretrieveNetworkTests) urlretrieveNetworkTests,
urlopen_HttpsTests)
if __name__ == "__main__": if __name__ == "__main__":
test_main() test_main()
...@@ -69,15 +69,15 @@ else: ...@@ -69,15 +69,15 @@ else:
# Shortcut for basic usage # Shortcut for basic usage
_urlopener = None _urlopener = None
def urlopen(url, data=None, proxies=None): def urlopen(url, data=None, proxies=None, context=None):
"""Create a file-like object for the specified URL to read from.""" """Create a file-like object for the specified URL to read from."""
from warnings import warnpy3k from warnings import warnpy3k
warnpy3k("urllib.urlopen() has been removed in Python 3.0 in " warnpy3k("urllib.urlopen() has been removed in Python 3.0 in "
"favor of urllib2.urlopen()", stacklevel=2) "favor of urllib2.urlopen()", stacklevel=2)
global _urlopener global _urlopener
if proxies is not None: if proxies is not None or context is not None:
opener = FancyURLopener(proxies=proxies) opener = FancyURLopener(proxies=proxies, context=context)
elif not _urlopener: elif not _urlopener:
opener = FancyURLopener() opener = FancyURLopener()
_urlopener = opener _urlopener = opener
...@@ -87,11 +87,15 @@ def urlopen(url, data=None, proxies=None): ...@@ -87,11 +87,15 @@ def urlopen(url, data=None, proxies=None):
return opener.open(url) return opener.open(url)
else: else:
return opener.open(url, data) return opener.open(url, data)
def urlretrieve(url, filename=None, reporthook=None, data=None): def urlretrieve(url, filename=None, reporthook=None, data=None, context=None):
global _urlopener global _urlopener
if not _urlopener: if context is not None:
_urlopener = FancyURLopener() opener = FancyURLopener(context=context)
return _urlopener.retrieve(url, filename, reporthook, data) elif not _urlopener:
_urlopener = opener = FancyURLopener()
else:
opener = _urlopener
return opener.retrieve(url, filename, reporthook, data)
def urlcleanup(): def urlcleanup():
if _urlopener: if _urlopener:
_urlopener.cleanup() _urlopener.cleanup()
...@@ -126,13 +130,14 @@ class URLopener: ...@@ -126,13 +130,14 @@ class URLopener:
version = "Python-urllib/%s" % __version__ version = "Python-urllib/%s" % __version__
# Constructor # Constructor
def __init__(self, proxies=None, **x509): def __init__(self, proxies=None, context=None, **x509):
if proxies is None: if proxies is None:
proxies = getproxies() proxies = getproxies()
assert hasattr(proxies, 'has_key'), "proxies must be a mapping" assert hasattr(proxies, 'has_key'), "proxies must be a mapping"
self.proxies = proxies self.proxies = proxies
self.key_file = x509.get('key_file') self.key_file = x509.get('key_file')
self.cert_file = x509.get('cert_file') self.cert_file = x509.get('cert_file')
self.context = context
self.addheaders = [('User-Agent', self.version)] self.addheaders = [('User-Agent', self.version)]
self.__tempfiles = [] self.__tempfiles = []
self.__unlink = os.unlink # See cleanup() self.__unlink = os.unlink # See cleanup()
...@@ -422,7 +427,8 @@ class URLopener: ...@@ -422,7 +427,8 @@ class URLopener:
auth = None auth = None
h = httplib.HTTPS(host, 0, h = httplib.HTTPS(host, 0,
key_file=self.key_file, key_file=self.key_file,
cert_file=self.cert_file) cert_file=self.cert_file,
context=self.context)
if data is not None: if data is not None:
h.putrequest('POST', selector) h.putrequest('POST', selector)
h.putheader('Content-Type', h.putheader('Content-Type',
......
...@@ -42,6 +42,9 @@ Core and Builtins ...@@ -42,6 +42,9 @@ Core and Builtins
Library Library
------- -------
- Issue #22927: Allow urllib.urlopen to take a *context* parameter to control
SSL settings for HTTPS connections.
- Issue #22921: Allow SSLContext to take the *hostname* parameter even if - Issue #22921: Allow SSLContext to take the *hostname* parameter even if
OpenSSL doesn't support SNI. OpenSSL doesn't support SNI.
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment