|
| 1 | +"""ndg_httpsclient HTTPS module containing PyOpenSSL implementation of |
| 2 | +httplib.HTTPSConnection |
| 3 | +
|
| 4 | +PyOpenSSL utility to make a httplib-like interface suitable for use with |
| 5 | +urllib2 |
| 6 | +""" |
| 7 | +__author__ = "P J Kershaw (STFC)" |
| 8 | +__date__ = "09/12/11" |
| 9 | +__copyright__ = "(C) 2012 Science and Technology Facilities Council" |
| 10 | +__license__ = "BSD - see LICENSE file in top-level directory" |
| 11 | + |
| 12 | +__revision__ = '$Id$' |
| 13 | +import logging |
| 14 | +import socket |
| 15 | +import sys |
| 16 | + |
| 17 | +if sys.version_info[0] > 2: |
| 18 | + from http.client import HTTPS_PORT |
| 19 | + from http.client import HTTPConnection |
| 20 | + |
| 21 | + from urllib.request import AbstractHTTPHandler |
| 22 | +else: |
| 23 | + from httplib import HTTPS_PORT |
| 24 | + from httplib import HTTPConnection |
| 25 | + |
| 26 | + from urllib2 import AbstractHTTPHandler |
| 27 | + |
| 28 | + |
| 29 | +from OpenSSL import SSL |
| 30 | + |
| 31 | +from ndg.httpsclient.ssl_socket import SSLSocket |
| 32 | + |
| 33 | +log = logging.getLogger(__name__) |
| 34 | + |
| 35 | + |
| 36 | +class HTTPSConnection(HTTPConnection): |
| 37 | + """This class allows communication via SSL using PyOpenSSL. |
| 38 | + It is based on httplib.HTTPSConnection, modified to use PyOpenSSL. |
| 39 | +
|
| 40 | + Note: This uses the constructor inherited from HTTPConnection to allow it to |
| 41 | + be used with httplib and HTTPSContextHandler. To use the class directly with |
| 42 | + an SSL context set ssl_context after construction. |
| 43 | + |
| 44 | + @cvar default_port: default port for this class (443) |
| 45 | + @type default_port: int |
| 46 | + @cvar default_ssl_method: default SSL method used if no SSL context is |
| 47 | + explicitly set - defaults to version 2/3. |
| 48 | + @type default_ssl_method: int |
| 49 | + """ |
| 50 | + default_port = HTTPS_PORT |
| 51 | + default_ssl_method = SSL.SSLv23_METHOD |
| 52 | + |
| 53 | + def __init__(self, host, port=None, strict=None, |
| 54 | + timeout=socket._GLOBAL_DEFAULT_TIMEOUT, ssl_context=None): |
| 55 | + HTTPConnection.__init__(self, host, port, strict, timeout) |
| 56 | + if not hasattr(self, 'ssl_context'): |
| 57 | + self.ssl_context = None |
| 58 | + |
| 59 | + if ssl_context is not None: |
| 60 | + if not isinstance(ssl_context, SSL.Context): |
| 61 | + raise TypeError('Expecting OpenSSL.SSL.Context type for "' |
| 62 | + 'ssl_context" keyword; got %r instead' % |
| 63 | + ssl_context) |
| 64 | + |
| 65 | + self.ssl_context = ssl_context |
| 66 | + |
| 67 | + def connect(self): |
| 68 | + """Create SSL socket and connect to peer |
| 69 | + """ |
| 70 | + if getattr(self, 'ssl_context', None): |
| 71 | + if not isinstance(self.ssl_context, SSL.Context): |
| 72 | + raise TypeError('Expecting OpenSSL.SSL.Context type for "' |
| 73 | + 'ssl_context" attribute; got %r instead' % |
| 74 | + self.ssl_context) |
| 75 | + ssl_context = self.ssl_context |
| 76 | + else: |
| 77 | + ssl_context = SSL.Context(self.__class__.default_ssl_method) |
| 78 | + |
| 79 | + sock = socket.create_connection((self.host, self.port), self.timeout) |
| 80 | + |
| 81 | + # Tunnel if using a proxy - ONLY available for Python 2.6.2 and above |
| 82 | + if getattr(self, '_tunnel_host', None): |
| 83 | + self.sock = sock |
| 84 | + self._tunnel() |
| 85 | + |
| 86 | + self.sock = SSLSocket(ssl_context, sock) |
| 87 | + |
| 88 | + # Go to client mode. |
| 89 | + self.sock.set_connect_state() |
| 90 | + |
| 91 | + def close(self): |
| 92 | + """Close socket and shut down SSL connection""" |
| 93 | + if hasattr(self.sock, "close"): |
| 94 | + self.sock.close() |
| 95 | + |
| 96 | + |
| 97 | +class HTTPSContextHandler(AbstractHTTPHandler): |
| 98 | + '''HTTPS handler that allows a SSL context to be set for the SSL |
| 99 | + connections. |
| 100 | + ''' |
| 101 | + https_request = AbstractHTTPHandler.do_request_ |
| 102 | + |
| 103 | + def __init__(self, ssl_context, debuglevel=0): |
| 104 | + """ |
| 105 | + @param ssl_context:SSL context |
| 106 | + @type ssl_context: OpenSSL.SSL.Context |
| 107 | + @param debuglevel: debug level for HTTPSHandler |
| 108 | + @type debuglevel: int |
| 109 | + """ |
| 110 | + AbstractHTTPHandler.__init__(self, debuglevel) |
| 111 | + |
| 112 | + if ssl_context is not None: |
| 113 | + if not isinstance(ssl_context, SSL.Context): |
| 114 | + raise TypeError('Expecting OpenSSL.SSL.Context type for "' |
| 115 | + 'ssl_context" keyword; got %r instead' % |
| 116 | + ssl_context) |
| 117 | + self.ssl_context = ssl_context |
| 118 | + else: |
| 119 | + self.ssl_context = SSL.Context(SSL.TLSv1_METHOD) |
| 120 | + |
| 121 | + def https_open(self, req): |
| 122 | + """Opens HTTPS request |
| 123 | + @param req: HTTP request |
| 124 | + @return: HTTP Response object |
| 125 | + """ |
| 126 | + # Make a custom class extending HTTPSConnection, with the SSL context |
| 127 | + # set as a class variable so that it is available to the connect method. |
| 128 | + customHTTPSContextConnection = type('CustomHTTPSContextConnection', |
| 129 | + (HTTPSConnection, object), |
| 130 | + {'ssl_context': self.ssl_context}) |
| 131 | + return self.do_open(customHTTPSContextConnection, req) |
0 commit comments