diff -urN m2crypto/M2Crypto/__init__.py m2crypto-0.16/M2Crypto/__init__.py --- m2crypto/M2Crypto/__init__.py 2006-06-12 19:36:19.000000000 +0200 +++ m2crypto-0.16/M2Crypto/__init__.py 2006-10-20 16:04:57.000000000 +0200 @@ -33,6 +33,7 @@ import m2urllib # Backwards compatibility. urllib2 = m2urllib +import m2urllib2 import ftpslib import httpslib import m2xmlrpclib diff -urN m2crypto/M2Crypto/m2urllib2.py m2crypto-0.16/M2Crypto/m2urllib2.py --- m2crypto/M2Crypto/m2urllib2.py 1970-01-01 01:00:00.000000000 +0100 +++ m2crypto-0.16/M2Crypto/m2urllib2.py 2006-10-20 16:04:57.000000000 +0200 @@ -0,0 +1,123 @@ +""" +M2Crypto enhancement to Python's urllib2 for handling +'https' url's. + +Code from urllib2 is Copyright (c) 2001, 2002, 2003, 2004, 2005, 2006 +Python Software Foundation; All Rights Reserved + +Summary of changes: + * Add the SSL context to the https connection when performing https_open. + * Add the M2Crypto HTTPSHandler when building a default opener. +""" + +from urllib2 import * + +import SSL +import httpslib + +class HTTPSHandler(AbstractHTTPHandler): + def __init__(self, ssl_context = None): + AbstractHTTPHandler.__init__(self) + + if ssl_context is not None: + self.ctx = ssl_context + else: + self.ctx = SSL.Context() + + # Copied from urllib2, so we can set the ssl context. + def https_open(self, req): + """Return an addinfourl object for the request, using http_class. + + http_class must implement the HTTPConnection API from httplib. + The addinfourl return value is a file-like object. It also + has methods and attributes including: + - info(): return a mimetools.Message object for the headers + - geturl(): return the original request URL + - code: HTTP status code + """ + host = req.get_host() + if not host: + raise URLError('no host given') + + # Our change: add the ssl context. + h = httpslib.HTTPSConnection(host = host, ssl_context = self.ctx) + # End our change + h.set_debuglevel(self._debuglevel) + + headers = dict(req.headers) + headers.update(req.unredirected_hdrs) + # We want to make an HTTP/1.1 request, but the addinfourl + # class isn't prepared to deal with a persistent connection. + # It will try to read all remaining data from the socket, + # which will block while the server waits for the next request. + # So make sure the connection gets closed after the (only) + # request. + headers["Connection"] = "close" + try: + h.request(req.get_method(), req.get_selector(), req.data, headers) + r = h.getresponse() + except socket.error, err: # XXX what error? + raise URLError(err) + + # Pick apart the HTTPResponse object to get the addinfourl + # object initialized properly. + + # Wrap the HTTPResponse object in socket's file object adapter + # for Windows. That adapter calls recv(), so delegate recv() + # to read(). This weird wrapping allows the returned object to + # have readline() and readlines() methods. + + # XXX It might be better to extract the read buffering code + # out of socket._fileobject() and into a base class. + + r.recv = r.read + fp = socket._fileobject(r) + + resp = addinfourl(fp, r.msg, req.get_full_url()) + resp.code = r.status + resp.msg = r.reason + return resp + + + https_request = AbstractHTTPHandler.do_request_ + + +# Copied from urllib2 with modifications for ssl +def build_opener(ssl_context = None, *handlers): + """Create an opener object from a list of handlers. + + The opener will use several default handlers, including support + for HTTP and FTP. + + If any of the handlers passed as arguments are subclasses of the + default handlers, the default handlers will not be used. + """ + + opener = OpenerDirector() + default_classes = [ProxyHandler, UnknownHandler, HTTPHandler, + HTTPDefaultErrorHandler, HTTPRedirectHandler, + FTPHandler, FileHandler, HTTPErrorProcessor] + skip = [] + for klass in default_classes: + for check in handlers: + if inspect.isclass(check): + if issubclass(check, klass): + skip.append(klass) + elif isinstance(check, klass): + skip.append(klass) + for klass in skip: + default_classes.remove(klass) + + for klass in default_classes: + opener.add_handler(klass()) + + # Add the HTTPS handler with ssl_context + if HTTPSHandler not in skip: + opener.add_handler(HTTPSHandler(ssl_context)) + + + for h in handlers: + if inspect.isclass(h): + h = h() + opener.add_handler(h) + return opener diff -urN m2crypto/tests/test_ssl.py m2crypto-0.16/tests/test_ssl.py --- m2crypto/tests/test_ssl.py 2006-05-25 00:54:03.000000000 +0200 +++ m2crypto-0.16/tests/test_ssl.py 2006-10-20 16:04:57.000000000 +0200 @@ -556,6 +556,54 @@ self.stop_server(pid) self.failIf(string.find(data, 's_server -quiet -www') == -1) + # XXX Don't actually know how to use m2urllib safely! + #def test_urllib_safe_context(self): + #def test_urllib_safe_context_fail(self): + + def test_urllib2(self): + pid = self.start_server(self.args) + try: + from M2Crypto import m2urllib2 + opener = m2urllib2.build_opener() + opener.addheaders = [('Connection', 'close')] + u = opener.open('https://%s:%s/' % (srv_host, srv_port)) + data = u.read() + u.close() + finally: + self.stop_server(pid) + self.failIf(string.find(data, 's_server -quiet -www') == -1) + + def test_urllib2_secure_context(self): + pid = self.start_server(self.args) + try: + ctx = SSL.Context() + ctx.set_verify(SSL.verify_peer | SSL.verify_fail_if_no_peer_cert, 9) + ctx.load_verify_locations('ca.pem') + + from M2Crypto import m2urllib2 + opener = m2urllib2.build_opener(ctx) + opener.addheaders = [('Connection', 'close')] + u = opener.open('https://%s:%s/' % (srv_host, srv_port)) + data = u.read() + u.close() + finally: + self.stop_server(pid) + self.failIf(string.find(data, 's_server -quiet -www') == -1) + + def test_urllib2_secure_context_fail(self): + pid = self.start_server(self.args) + try: + ctx = SSL.Context() + ctx.set_verify(SSL.verify_peer | SSL.verify_fail_if_no_peer_cert, 9) + ctx.load_verify_locations('server.pem') + + from M2Crypto import m2urllib2 + opener = m2urllib2.build_opener(ctx) + opener.addheaders = [('Connection', 'close')] + self.assertRaises(SSL.SSLError, opener.open, 'https://%s:%s/' % (srv_host, srv_port)) + finally: + self.stop_server(pid) + def test_blocking0(self): pid = self.start_server(self.args) try: @@ -597,6 +645,8 @@ from twisted.internet import reactor import M2Crypto.SSL.TwistedProtocolWrapper as wrapper except ImportError: + import warnings + warnings.warn('Skipping twisted wrapper test because twisted not found') return class EchoClient(LineReceiver):