diff options
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 |