"""M2Crypto enhancement to Python's urllib for handling
'https' url's.
FIXME: it is questionable whether we need this old-style module at all. urllib
(not urllib2) is in Python 3 support just as a legacy API.
Copyright (c) 1999-2003 Ng Pheng Siong. All rights reserved."""
import base64
import warnings
from M2Crypto import SSL, httpslib
from urllib.response import addinfourl
from typing import Optional, Union # noqa
from urllib.request import * # noqa for other modules to import
from urllib.parse import * # noqa for other modules to import
from urllib.error import * # noqa for other modules to import
if "URLopener" in globals():
[docs]
def open_https(
self: URLopener,
url: Union[str, bytes],
data: Optional[bytes] = None,
ssl_context: Optional[SSL.Context] = None,
) -> addinfourl:
"""
Open URL over the SSL connection.
:param url: URL to be opened
:param data: data for the POST request
:param ssl_context: SSL.Context to be used
:return:
"""
warnings.warn("URLOpener has been deprecated in Py3k", DeprecationWarning)
# Dynamically add the SSL context to the URLopener instance.
context: SSL.Context
if ssl_context is not None and isinstance(ssl_context, SSL.Context):
context = ssl_context
else:
context = SSL.Context()
self.ctx = context # type: ignore[attr-defined]
# Normalize the URL to a string for robust parsing.
url_str = url.decode("latin-1") if isinstance(url, bytes) else url
parsed = urlparse(url_str)
if parsed.scheme.lower() != "https":
raise IOError("url error", "invalid URL scheme")
host = parsed.hostname
if not host:
raise IOError("http error", "no host given")
# Build the host:port string for the connection.
host_port = host
if parsed.port:
host_port += f":{parsed.port}"
# Reconstruct the path and query part of the URL.
selector = urlunsplit(("", "", parsed.path, parsed.query, parsed.fragment))
if not selector:
selector = "/"
# Handle authentication from the URL.
auth_header_value = None
if parsed.username:
user_pass = parsed.username
if parsed.password:
user_pass += f":{parsed.password}"
# base64.encodebytes requires bytes and returns bytes.
auth_bytes = base64.encodebytes(user_pass.encode("utf-8")).strip()
# The final header value must be a string.
auth_header_value = f'Basic {auth_bytes.decode("ascii")}'
# Mypy gets confused by our re-defined HTTPSConnection.
h = httpslib.HTTPSConnection(
host=host_port, ssl_context=context
) # type: ignore[call-arg]
if data is not None:
h.putrequest("POST", selector)
h.putheader("Content-type", "application/x-www-form-urlencoded")
h.putheader("Content-length", str(len(data)))
else:
h.putrequest("GET", selector)
if auth_header_value:
h.putheader("Authorization", auth_header_value)
# URLopener stores extra headers in `addheaders`.
for header, value in self.addheaders: # type: ignore[attr-defined]
h.putheader(header, value)
h.endheaders()
if data is not None:
h.send(data)
# Get the response and wrap it in the expected addinfourl object.
resp = h.getresponse()
# Use the modern `.headers` attribute, not the deprecated `.msg`.
return addinfourl(resp, resp.headers, "https:" + url_str)
# Monkey-patch the method onto the (deprecated) URLopener class.
URLopener.open_https = open_https # type: ignore[attr-defined, method-assign, used-before-def, assignment]
else:
import sys
class URLopener: # type: ignore [no-redef]
msg = f'Python {"%d.%d" % (sys.version_info[:2])} does not support URLopener any more.'
def __init__(self):
raise RuntimeError(self.msg)
def open_https(self):
raise RuntimeError(self.msg)