diff options
author | Guido van Rossum <guido@python.org> | 1999-12-07 21:37:17 (GMT) |
---|---|---|
committer | Guido van Rossum <guido@python.org> | 1999-12-07 21:37:17 (GMT) |
commit | 09c8b6c3e42313fe336ee4028a5d0e61605a3670 (patch) | |
tree | 9ba4c435d57bc0bee245805c2117e14dcd367027 /Lib | |
parent | 5274c336f5c33838259ab29e43ac88158953f474 (diff) | |
download | cpython-09c8b6c3e42313fe336ee4028a5d0e61605a3670.zip cpython-09c8b6c3e42313fe336ee4028a5d0e61605a3670.tar.gz cpython-09c8b6c3e42313fe336ee4028a5d0e61605a3670.tar.bz2 |
OpenSSL support. This is based on patches for a version of SSLeay by
Brian E Gallew, which were improved and adapted to OpenSSL 0.9.4 by
Laszlo Kovacs of HP. Both have kindly given permission to include
the patches in the Python distribution. Final formatting by GvR.
Diffstat (limited to 'Lib')
-rw-r--r-- | Lib/httplib.py | 105 | ||||
-rw-r--r-- | Lib/urllib.py | 62 |
2 files changed, 149 insertions, 18 deletions
diff --git a/Lib/httplib.py b/Lib/httplib.py index 05289b3..fade694 100644 --- a/Lib/httplib.py +++ b/Lib/httplib.py @@ -28,17 +28,48 @@ second request to the same server, you create a new HTTP object. connection for each request.) """ +import os import socket import string import mimetools +try: + from cStringIO import StringIO +except: + from StringIO import StringIO + HTTP_VERSION = 'HTTP/1.0' HTTP_PORT = 80 +HTTPS_PORT = 443 + +class FakeSocket: + def __init__(self, sock, ssl): + self.__sock = sock + self.__ssl = ssl + return + + def makefile(self, mode): # hopefully, never have to write + msgbuf = "" + while 1: + try: + msgbuf = msgbuf + self.__ssl.read() + except socket.sslerror, msg: + break + return StringIO(msgbuf) + + def send(self, stuff, flags = 0): + return self.__ssl.write(stuff) + + def recv(self, len = 1024, flags = 0): + return self.__ssl.read(len) + + def __getattr__(self, attr): + return getattr(self.__sock, attr) class HTTP: """This class manages a connection to an HTTP server.""" - - def __init__(self, host = '', port = 0): + + def __init__(self, host = '', port = 0, **x509): """Initialize a new instance. If specified, `host' is the name of the remote host to which @@ -46,10 +77,12 @@ class HTTP: to connect. By default, httplib.HTTP_PORT is used. """ + self.key_file = x509.get('key_file') + self.cert_file = x509.get('cert_file') self.debuglevel = 0 self.file = None if host: self.connect(host, port) - + def set_debuglevel(self, debuglevel): """Set the debug output level. @@ -58,10 +91,10 @@ class HTTP: """ self.debuglevel = debuglevel - + def connect(self, host, port = 0): """Connect to a host on a given port. - + Note: This method is automatically invoked by __init__, if a host is specified during instantiation. @@ -77,12 +110,12 @@ class HTTP: self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) if self.debuglevel > 0: print 'connect:', (host, port) self.sock.connect(host, port) - + def send(self, str): """Send `str' to the server.""" if self.debuglevel > 0: print 'send:', `str` self.sock.send(str) - + def putrequest(self, request, selector): """Send a request to the server. @@ -94,7 +127,7 @@ class HTTP: if not selector: selector = '/' str = '%s %s %s\r\n' % (request, selector, HTTP_VERSION) self.send(str) - + def putheader(self, header, *args): """Send a request header line to the server. @@ -103,14 +136,14 @@ class HTTP: """ str = '%s: %s\r\n' % (header, string.joinfields(args,'\r\n\t')) self.send(str) - + def endheaders(self): """Indicate that the last header line has been sent to the server.""" self.send('\r\n') - + def getreply(self): """Get a reply from the server. - + Returns a tuple consisting of: - server response code (e.g. '200' if all goes well) - server response string corresponding to response code @@ -136,7 +169,7 @@ class HTTP: errmsg = string.strip(msg) self.headers = mimetools.Message(self.file, 0) return errcode, errmsg, self.headers - + def getfile(self): """Get a file object from which to receive data from the HTTP server. @@ -145,7 +178,7 @@ class HTTP: """ return self.file - + def close(self): """Close the connection to the HTTP server.""" if self.file: @@ -155,6 +188,31 @@ class HTTP: self.sock.close() self.sock = None +if hasattr(socket, "ssl"): + class HTTPS(HTTP): + """This class allows communication via SSL.""" + + def connect(self, host, port = 0): + """Connect to a host on a given port. + + Note: This method is automatically invoked by __init__, + if a host is specified during instantiation. + + """ + if not port: + i = string.find(host, ':') + if i >= 0: + host, port = host[:i], host[i+1:] + try: port = string.atoi(port) + except string.atoi_error: + raise socket.error, "nonnumeric port" + if not port: port = HTTPS_PORT + sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + if self.debuglevel > 0: print 'connect:', (host, port) + sock.connect(host, port) + ssl = socket.ssl(sock, self.key_file, self.cert_file) + self.sock = FakeSocket(sock, ssl) + def test(): """Test this module. @@ -170,6 +228,7 @@ def test(): dl = 0 for o, a in opts: if o == '-d': dl = dl + 1 + print "testing HTTP..." host = 'www.python.org' selector = '/' if args[0:]: host = args[0] @@ -187,6 +246,26 @@ def test(): for header in headers.headers: print string.strip(header) print print h.getfile().read() + if hasattr(socket, "ssl"): + print "-"*40 + print "testing HTTPS..." + host = 'synergy.as.cmu.edu' + selector = '/~geek/' + if args[0:]: host = args[0] + if args[1:]: selector = args[1] + h = HTTPS() + h.set_debuglevel(dl) + h.connect(host) + h.putrequest('GET', selector) + h.endheaders() + errcode, errmsg, headers = h.getreply() + print 'errcode =', errcode + print 'errmsg =', errmsg + print + if headers: + for header in headers.headers: print string.strip(header) + print + print h.getfile().read() if __name__ == '__main__': diff --git a/Lib/urllib.py b/Lib/urllib.py index 4bd329f..ffbab22 100644 --- a/Lib/urllib.py +++ b/Lib/urllib.py @@ -27,7 +27,7 @@ import os import sys -__version__ = '1.11' # XXX This version is not always updated :-( +__version__ = '1.12' # XXX This version is not always updated :-( MAXFTPCACHE = 10 # Trim the ftp cache beyond this size @@ -81,11 +81,13 @@ class URLopener: __tempfiles = None # Constructor - def __init__(self, proxies=None): + def __init__(self, proxies=None, **x509): if proxies is None: proxies = getproxies() assert hasattr(proxies, 'has_key'), "proxies must be a mapping" self.proxies = proxies + self.key_file = x509.get('key_file') + self.cert_file = x509.get('cert_file') server_version = "Python-urllib/%s" % __version__ self.addheaders = [('User-agent', server_version)] self.__tempfiles = [] @@ -144,6 +146,7 @@ class URLopener: host, selector = splithost(proxy) url = (host, fullurl) # Signal special case to open_*() name = 'open_' + type + self.type = type if '-' in name: # replace - with _ name = string.join(string.split(name, '-'), '_') @@ -294,6 +297,42 @@ class URLopener: fp.close() raise IOError, ('http error', errcode, errmsg, headers) + # Use HTTPS protocol + if hasattr(socket, "ssl"): + def open_https(self, url): + import httplib + if type(url) is type(""): + host, selector = splithost(url) + user_passwd, host = splituser(host) + else: + host, selector = url + urltype, rest = splittype(selector) + if string.lower(urltype) == 'https': + realhost, rest = splithost(rest) + user_passwd, realhost = splituser(realhost) + if user_passwd: + selector = "%s://%s%s" % (urltype, realhost, rest) + print "proxy via https:", host, selector + if not host: raise IOError, ('https error', 'no host given') + if user_passwd: + import base64 + auth = string.strip(base64.encodestring(user_passwd)) + else: + auth = None + h = httplib.HTTPS(host, 0, + key_file=self.key_file, + cert_file=self.cert_file) + h.putrequest('GET', selector) + if auth: h.putheader('Authorization: Basic %s' % auth) + for args in self.addheaders: apply(h.putheader, args) + h.endheaders() + errcode, errmsg, headers = h.getreply() + fp = h.getfile() + if errcode == 200: + return addinfourl(fp, headers, url) + else: + return self.http_error(url, fp, errcode, errmsg, headers) + # Use Gopher protocol def open_gopher(self, url): import gopherlib @@ -477,7 +516,8 @@ class FancyURLopener(URLopener): if match: scheme, realm = match.groups() if string.lower(scheme) == 'basic': - return self.retry_http_basic_auth(url, realm, data) + name = 'retry_' + self.type + '_basic_auth' + return getattr(self,name)(url, realm) def retry_http_basic_auth(self, url, realm, data): host, selector = splithost(url) @@ -488,6 +528,16 @@ class FancyURLopener(URLopener): host = user + ':' + passwd + '@' + host newurl = 'http://' + host + selector return self.open(newurl, data) + + def retry_https_basic_auth(self, url, realm): + host, selector = splithost(url) + i = string.find(host, '@') + 1 + host = host[i:] + user, passwd = self.get_user_passwd(host, realm, i) + if not (user or passwd): return None + host = user + ':' + passwd + '@' + host + newurl = '//' + host + selector + return self.open_https(newurl) def get_user_passwd(self, host, realm, clear_cache = 0): key = realm + '@' + string.lower(host) @@ -630,8 +680,8 @@ class addbase: self.fp = fp self.read = self.fp.read self.readline = self.fp.readline - self.readlines = self.fp.readlines - self.fileno = self.fp.fileno + if hasattr(self.fp, "readlines"): self.readlines = self.fp.readlines + if hasattr(self.fp, "fileno"): self.fileno = self.fp.fileno def __repr__(self): return '<%s at %s whose fp = %s>' % (self.__class__.__name__, `id(self)`, `self.fp`) @@ -1015,6 +1065,8 @@ def test(args=[]): ## 'gopher://gopher.micro.umn.edu/1/', 'http://www.python.org/index.html', ] + if hasattr(URLopener, "open_https"): + args.append('https://synergy.as.cmu.edu/~geek/') try: for url in args: print '-'*10, url, '-'*10 |