From f90b002e31f245286e34209a96e13d1d5e281c76 Mon Sep 17 00:00:00 2001
From: Jeremy Hylton <jeremy@alum.mit.edu>
Date: Thu, 25 Feb 1999 16:12:12 +0000
Subject: change indentation from 8 spaces to 4 spaces

---
 Lib/urllib.py | 1737 ++++++++++++++++++++++++++++-----------------------------
 1 file changed, 859 insertions(+), 878 deletions(-)

diff --git a/Lib/urllib.py b/Lib/urllib.py
index 2b0f2a7..3c8bc0f 100644
--- a/Lib/urllib.py
+++ b/Lib/urllib.py
@@ -29,25 +29,25 @@ import sys
 
 __version__ = '1.10'
 
-MAXFTPCACHE = 10		# Trim the ftp cache beyond this size
+MAXFTPCACHE = 10        # Trim the ftp cache beyond this size
 
 # Helper for non-unix systems
 if os.name == 'mac':
-	from macurl2path import url2pathname, pathname2url
+    from macurl2path import url2pathname, pathname2url
 elif os.name == 'nt':
-	from nturl2path import url2pathname, pathname2url 
+    from nturl2path import url2pathname, pathname2url 
 else:
-	def url2pathname(pathname):
-		return pathname
-	def pathname2url(pathname):
-		return pathname
+    def url2pathname(pathname):
+        return pathname
+    def pathname2url(pathname):
+        return pathname
 
 _url2pathname = url2pathname
 def url2pathname(url):
-	return _url2pathname(unquote(url))
+    return _url2pathname(unquote(url))
 _pathname2url = pathname2url
 def pathname2url(p):
-	return quote(_pathname2url(p))
+    return quote(_pathname2url(p))
 
 # This really consists of two pieces:
 # (1) a class which handles opening of all sorts of URLs
@@ -59,21 +59,21 @@ def pathname2url(p):
 # Shortcut for basic usage
 _urlopener = None
 def urlopen(url, data=None):
-	global _urlopener
-	if not _urlopener:
-		_urlopener = FancyURLopener()
-	if data is None:
-		return _urlopener.open(url)
-	else:
-		return _urlopener.open(url, data)
+    global _urlopener
+    if not _urlopener:
+        _urlopener = FancyURLopener()
+    if data is None:
+        return _urlopener.open(url)
+    else:
+        return _urlopener.open(url, data)
 def urlretrieve(url, filename=None, reporthook=None):
-	global _urlopener
-	if not _urlopener:
-		_urlopener = FancyURLopener()
-	return _urlopener.retrieve(url, filename, reporthook)
+    global _urlopener
+    if not _urlopener:
+        _urlopener = FancyURLopener()
+    return _urlopener.retrieve(url, filename, reporthook)
 def urlcleanup():
-	if _urlopener:
-		_urlopener.cleanup()
+    if _urlopener:
+        _urlopener.cleanup()
 
 
 # Class to open URLs.
@@ -85,451 +85,433 @@ def urlcleanup():
 ftpcache = {}
 class URLopener:
 
-	__tempfiles = None
-
-	# Constructor
-	def __init__(self, proxies=None):
-		if proxies is None:
-			proxies = getproxies()
-		assert hasattr(proxies, 'has_key'), "proxies must be a mapping"
-		self.proxies = proxies
-		server_version = "Python-urllib/%s" % __version__
-		self.addheaders = [('User-agent', server_version)]
-		self.__tempfiles = []
-		self.__unlink = os.unlink # See cleanup()
-		self.tempcache = None
-		# Undocumented feature: if you assign {} to tempcache,
-		# it is used to cache files retrieved with
-		# self.retrieve().  This is not enabled by default
-		# since it does not work for changing documents (and I
-		# haven't got the logic to check expiration headers
-		# yet).
-		self.ftpcache = ftpcache
-		# Undocumented feature: you can use a different
-		# ftp cache by assigning to the .ftpcache member;
-		# in case you want logically independent URL openers
-		# XXX This is not threadsafe.  Bah.
-
-	def __del__(self):
-		self.close()
-
-	def close(self):
-		self.cleanup()
-
-	def cleanup(self):
-		# This code sometimes runs when the rest of this module
-		# has already been deleted, so it can't use any globals
-		# or import anything.
-		if self.__tempfiles:
-			for file in self.__tempfiles:
-				try:
-					self.__unlink(file)
-				except:
-					pass
-			del self.__tempfiles[:]
-		if self.tempcache:
-			self.tempcache.clear()
-
-	# Add a header to be used by the HTTP interface only
-	# e.g. u.addheader('Accept', 'sound/basic')
-	def addheader(self, *args):
-		self.addheaders.append(args)
-
-	# External interface
-	# Use URLopener().open(file) instead of open(file, 'r')
-	def open(self, fullurl, data=None):
-		fullurl = unwrap(fullurl)
-		if self.tempcache and self.tempcache.has_key(fullurl):
-			filename, headers = self.tempcache[fullurl]
-			fp = open(filename, 'rb')
-			return addinfourl(fp, headers, fullurl)
-		type, url = splittype(fullurl)
-		if not type: type = 'file'
-		if self.proxies.has_key(type):
-			proxy = self.proxies[type]
-			type, proxy = splittype(proxy)
-			host, selector = splithost(proxy)
-			url = (host, fullurl) # Signal special case to open_*()
-		name = 'open_' + type
-		if '-' in name:
-			# replace - with _
-			name = string.join(string.split(name, '-'), '_')
-		if not hasattr(self, name):
-			if data is None:
-				return self.open_unknown(fullurl)
-			else:
-				return self.open_unknown(fullurl, data)
-		try:
-			if data is None:
-				return getattr(self, name)(url)
-			else:
-				return getattr(self, name)(url, data)
-		except socket.error, msg:
-			raise IOError, ('socket error', msg), sys.exc_info()[2]
-
-	# Overridable interface to open unknown URL type
-	def open_unknown(self, fullurl, data=None):
-		type, url = splittype(fullurl)
-		raise IOError, ('url error', 'unknown url type', type)
-
-	# External interface
-	# retrieve(url) returns (filename, None) for a local object
-	# or (tempfilename, headers) for a remote object
-	def retrieve(self, url, filename=None, reporthook=None):
-		url = unwrap(url)
-		if self.tempcache and self.tempcache.has_key(url):
-			return self.tempcache[url]
-		type, url1 = splittype(url)
-		if not filename and (not type or type == 'file'):
-			try:
-				fp = self.open_local_file(url1)
-				hdrs = fp.info()
-				del fp
-				return url2pathname(splithost(url1)[1]), hdrs
-			except IOError, msg:
-				pass
-		fp = self.open(url)
-		headers = fp.info()
-		if not filename:
-			import tempfile
-			garbage, path = splittype(url)
-			garbage, path = splithost(path or "")
-			path, garbage = splitquery(path or "")
-			path, garbage = splitattr(path or "")
-			suffix = os.path.splitext(path)[1]
-			filename = tempfile.mktemp(suffix)
-			self.__tempfiles.append(filename)
-		result = filename, headers
-		if self.tempcache is not None:
-			self.tempcache[url] = result
-		tfp = open(filename, 'wb')
-		bs = 1024*8
-		size = -1
-		blocknum = 1
-		if reporthook:
-			if headers.has_key("content-length"):
-				size = int(headers["Content-Length"])
-			reporthook(0, bs, size)
-		block = fp.read(bs)
-		if reporthook:
-			reporthook(1, bs, size)
-		while block:
-			tfp.write(block)
-			block = fp.read(bs)
-			blocknum = blocknum + 1
-			if reporthook:
-				reporthook(blocknum, bs, size)
-		fp.close()
-		tfp.close()
-		del fp
-		del tfp
-		return result
-
-	# Each method named open_<type> knows how to open that type of URL
-
-	# Use HTTP protocol
-	def open_http(self, url, data=None):
-		import httplib
-		user_passwd = None
-		if type(url) is type(""):
-			host, selector = splithost(url)
-			if host:
-				user_passwd, host = splituser(host)
-				host = unquote(host)
-			realhost = host
-		else:
-			host, selector = url
-			urltype, rest = splittype(selector)
-			url = rest
-			user_passwd = None
-			if string.lower(urltype) != 'http':
-				realhost = None
-			else:
-				realhost, rest = splithost(rest)
-				if realhost:
-					user_passwd, realhost = \
-						     splituser(realhost)
-				if user_passwd:
-					selector = "%s://%s%s" % (urltype,
-								  realhost,
-								  rest)
-			#print "proxy via http:", host, selector
-		if not host: raise IOError, ('http error', 'no host given')
-		if user_passwd:
-			import base64
-			auth = string.strip(base64.encodestring(user_passwd))
-		else:
-			auth = None
-		h = httplib.HTTP(host)
-		if data is not None:
-			h.putrequest('POST', selector)
-			h.putheader('Content-type',
-				    'application/x-www-form-urlencoded')
-			h.putheader('Content-length', '%d' % len(data))
-		else:
-			h.putrequest('GET', selector)
-		if auth: h.putheader('Authorization', 'Basic %s' % auth)
-		if realhost: h.putheader('Host', realhost)
-		for args in self.addheaders: apply(h.putheader, args)
-		h.endheaders()
-		if data is not None:
-			h.send(data + '\r\n')
-		errcode, errmsg, headers = h.getreply()
-		fp = h.getfile()
-		if errcode == 200:
-			return addinfourl(fp, headers, "http:" + url)
-		else:
-			if data is None:
-				return self.http_error(url,
-						       fp, errcode,
-						       errmsg,
-						       headers,
-						       data)
-			else:
-				return self.http_error(url,
-						       fp, errcode,
-						       errmsg,
-						       headers)
-
-	# Handle http errors.
-	# Derived class can override this, or provide specific handlers
-	# named http_error_DDD where DDD is the 3-digit error code
-	def http_error(self, url, fp, errcode, errmsg, headers, data=None):
-		# First check if there's a specific handler for this error
-		name = 'http_error_%d' % errcode
-		if hasattr(self, name):
-			method = getattr(self, name)
-			if data is None:
-				result = method(url, fp, errcode, errmsg,
-						headers, data)
-			else:
-				result = method(url, fp, errcode, errmsg,
-						headers)
-			if result: return result
-		return self.http_error_default(
-			url, fp, errcode, errmsg, headers)
-
-	# Default http error handler: close the connection and raises IOError
-	def http_error_default(self, url, fp, errcode, errmsg, headers):
-		void = fp.read()
-		fp.close()
-		raise IOError, ('http error', errcode, errmsg, headers)
-
-	# Use Gopher protocol
-	def open_gopher(self, url):
-		import gopherlib
-		host, selector = splithost(url)
-		if not host: raise IOError, ('gopher error', 'no host given')
-		host = unquote(host)
-		type, selector = splitgophertype(selector)
-		selector, query = splitquery(selector)
-		selector = unquote(selector)
-		if query:
-			query = unquote(query)
-			fp = gopherlib.send_query(selector, query, host)
-		else:
-			fp = gopherlib.send_selector(selector, host)
-		return addinfourl(fp, noheaders(), "gopher:" + url)
-
-	# Use local file or FTP depending on form of URL
-	def open_file(self, url):
-		if url[:2] == '//' and url[2:3] != '/':
-			return self.open_ftp(url)
-		else:
-			return self.open_local_file(url)
-
-	# Use local file
-	def open_local_file(self, url):
-		import mimetypes, mimetools, StringIO
-		mtype = mimetypes.guess_type(url)[0]
-		headers = mimetools.Message(StringIO.StringIO(
-			'Content-Type: %s\n' % (mtype or 'text/plain')))
-		host, file = splithost(url)
-		if not host:
-			return addinfourl(
-				open(url2pathname(file), 'rb'),
-				headers, 'file:'+pathname2url(file))
-		host, port = splitport(host)
-		if not port and socket.gethostbyname(host) in (
-			  localhost(), thishost()):
-			return addinfourl(
-				open(url2pathname(file), 'rb'),
-				headers, 'file:'+pathname2url(file))
-		raise IOError, ('local file error', 'not on local host')
-
-	# Use FTP protocol
-	def open_ftp(self, url):
-		host, path = splithost(url)
-		if not host: raise IOError, ('ftp error', 'no host given')
-		host, port = splitport(host)
-		user, host = splituser(host)
-		if user: user, passwd = splitpasswd(user)
-		else: passwd = None
-		host = unquote(host)
-		user = unquote(user or '')
-		passwd = unquote(passwd or '')
-		host = socket.gethostbyname(host)
-		if not port:
-			import ftplib
-			port = ftplib.FTP_PORT
-		else:
-			port = int(port)
-		path, attrs = splitattr(path)
-		path = unquote(path)
-		dirs = string.splitfields(path, '/')
-		dirs, file = dirs[:-1], dirs[-1]
-		if dirs and not dirs[0]: dirs = dirs[1:]
-		key = (user, host, port, string.joinfields(dirs, '/'))
-		# XXX thread unsafe!
-		if len(self.ftpcache) > MAXFTPCACHE:
-			# Prune the cache, rather arbitrarily
-			for k in self.ftpcache.keys():
-				if k != key:
-					v = self.ftpcache[k]
-					del self.ftpcache[k]
-					v.close()
-		try:
-			if not self.ftpcache.has_key(key):
-				self.ftpcache[key] = \
-					ftpwrapper(user, passwd,
-						   host, port, dirs)
-			if not file: type = 'D'
-			else: type = 'I'
-			for attr in attrs:
-				attr, value = splitvalue(attr)
-				if string.lower(attr) == 'type' and \
-				   value in ('a', 'A', 'i', 'I', 'd', 'D'):
-					type = string.upper(value)
-			(fp, retrlen) = self.ftpcache[key].retrfile(file, type)
-			if retrlen is not None and retrlen >= 0:
-				import mimetools, StringIO
-				headers = mimetools.Message(StringIO.StringIO(
-					'Content-Length: %d\n' % retrlen))
-			else:
-				headers = noheaders()
-			return addinfourl(fp, headers, "ftp:" + url)
-		except ftperrors(), msg:
-			raise IOError, ('ftp error', msg), sys.exc_info()[2]
-
-	# Use "data" URL
-	def open_data(self, url, data=None):
-		# ignore POSTed data
-		#
-		# syntax of data URLs:
-		# dataurl   := "data:" [ mediatype ] [ ";base64" ] "," data
-		# mediatype := [ type "/" subtype ] *( ";" parameter )
-		# data      := *urlchar
-		# parameter := attribute "=" value
-		import StringIO, mimetools, time
-		try:
-			[type, data] = string.split(url, ',', 1)
-		except ValueError:
-			raise IOError, ('data error', 'bad data URL')
-		if not type:
-			type = 'text/plain;charset=US-ASCII'
-		semi = string.rfind(type, ';')
-		if semi >= 0 and '=' not in type[semi:]:
-			encoding = type[semi+1:]
-			type = type[:semi]
-		else:
-			encoding = ''
-		msg = []
-		msg.append('Date: %s'%time.strftime('%a, %d %b %Y %T GMT',
-						    time.gmtime(time.time())))
-		msg.append('Content-type: %s' % type)
-		if encoding == 'base64':
-			import base64
-			data = base64.decodestring(data)
-		else:
-			data = unquote(data)
-		msg.append('Content-length: %d' % len(data))
-		msg.append('')
-		msg.append(data)
-		msg = string.join(msg, '\n')
-		f = StringIO.StringIO(msg)
-		headers = mimetools.Message(f, 0)
-		f.fileno = None		# needed for addinfourl
-		return addinfourl(f, headers, url)
+    __tempfiles = None
+
+    # Constructor
+    def __init__(self, proxies=None):
+        if proxies is None:
+            proxies = getproxies()
+        assert hasattr(proxies, 'has_key'), "proxies must be a mapping"
+        self.proxies = proxies
+        server_version = "Python-urllib/%s" % __version__
+        self.addheaders = [('User-agent', server_version)]
+        self.__tempfiles = []
+        self.__unlink = os.unlink # See cleanup()
+        self.tempcache = None
+        # Undocumented feature: if you assign {} to tempcache,
+        # it is used to cache files retrieved with
+        # self.retrieve().  This is not enabled by default
+        # since it does not work for changing documents (and I
+        # haven't got the logic to check expiration headers
+        # yet).
+        self.ftpcache = ftpcache
+        # Undocumented feature: you can use a different
+        # ftp cache by assigning to the .ftpcache member;
+        # in case you want logically independent URL openers
+        # XXX This is not threadsafe.  Bah.
+
+    def __del__(self):
+        self.close()
+
+    def close(self):
+        self.cleanup()
+
+    def cleanup(self):
+        # This code sometimes runs when the rest of this module
+        # has already been deleted, so it can't use any globals
+        # or import anything.
+        if self.__tempfiles:
+            for file in self.__tempfiles:
+                try:
+                    self.__unlink(file)
+                except:
+                    pass
+            del self.__tempfiles[:]
+        if self.tempcache:
+            self.tempcache.clear()
+
+    # Add a header to be used by the HTTP interface only
+    # e.g. u.addheader('Accept', 'sound/basic')
+    def addheader(self, *args):
+        self.addheaders.append(args)
+
+    # External interface
+    # Use URLopener().open(file) instead of open(file, 'r')
+    def open(self, fullurl, data=None):
+        fullurl = unwrap(fullurl)
+        if self.tempcache and self.tempcache.has_key(fullurl):
+            filename, headers = self.tempcache[fullurl]
+            fp = open(filename, 'rb')
+            return addinfourl(fp, headers, fullurl)
+        type, url = splittype(fullurl)
+        if not type: type = 'file'
+        if self.proxies.has_key(type):
+            proxy = self.proxies[type]
+            type, proxy = splittype(proxy)
+            host, selector = splithost(proxy)
+            url = (host, fullurl) # Signal special case to open_*()
+        name = 'open_' + type
+        if '-' in name:
+            # replace - with _
+            name = string.join(string.split(name, '-'), '_')
+        if not hasattr(self, name):
+            if data is None:
+                return self.open_unknown(fullurl)
+            else:
+                return self.open_unknown(fullurl, data)
+        try:
+            if data is None:
+                return getattr(self, name)(url)
+            else:
+                return getattr(self, name)(url, data)
+        except socket.error, msg:
+            raise IOError, ('socket error', msg), sys.exc_info()[2]
+
+    # Overridable interface to open unknown URL type
+    def open_unknown(self, fullurl, data=None):
+        type, url = splittype(fullurl)
+        raise IOError, ('url error', 'unknown url type', type)
+
+    # External interface
+    # retrieve(url) returns (filename, None) for a local object
+    # or (tempfilename, headers) for a remote object
+    def retrieve(self, url, filename=None, reporthook=None):
+        url = unwrap(url)
+        if self.tempcache and self.tempcache.has_key(url):
+            return self.tempcache[url]
+        type, url1 = splittype(url)
+        if not filename and (not type or type == 'file'):
+            try:
+                fp = self.open_local_file(url1)
+                hdrs = fp.info()
+                del fp
+                return url2pathname(splithost(url1)[1]), hdrs
+            except IOError, msg:
+                pass
+        fp = self.open(url)
+        headers = fp.info()
+        if not filename:
+            import tempfile
+            garbage, path = splittype(url)
+            garbage, path = splithost(path or "")
+            path, garbage = splitquery(path or "")
+            path, garbage = splitattr(path or "")
+            suffix = os.path.splitext(path)[1]
+            filename = tempfile.mktemp(suffix)
+            self.__tempfiles.append(filename)
+        result = filename, headers
+        if self.tempcache is not None:
+            self.tempcache[url] = result
+        tfp = open(filename, 'wb')
+        bs = 1024*8
+        size = -1
+        blocknum = 1
+        if reporthook:
+            if headers.has_key("content-length"):
+                size = int(headers["Content-Length"])
+            reporthook(0, bs, size)
+        block = fp.read(bs)
+        if reporthook:
+            reporthook(1, bs, size)
+        while block:
+            tfp.write(block)
+            block = fp.read(bs)
+            blocknum = blocknum + 1
+            if reporthook:
+                reporthook(blocknum, bs, size)
+        fp.close()
+        tfp.close()
+        del fp
+        del tfp
+        return result
+
+    # Each method named open_<type> knows how to open that type of URL
+
+    # Use HTTP protocol
+    def open_http(self, url, data=None):
+        import httplib
+        user_passwd = None
+        if type(url) is type(""):
+            host, selector = splithost(url)
+            if host:
+                user_passwd, host = splituser(host)
+                host = unquote(host)
+            realhost = host
+        else:
+            host, selector = url
+            urltype, rest = splittype(selector)
+            url = rest
+            user_passwd = None
+            if string.lower(urltype) != 'http':
+                realhost = None
+            else:
+                realhost, rest = splithost(rest)
+                if realhost:
+                    user_passwd, realhost = splituser(realhost)
+                if user_passwd:
+                    selector = "%s://%s%s" % (urltype, realhost, rest)
+            #print "proxy via http:", host, selector
+        if not host: raise IOError, ('http error', 'no host given')
+        if user_passwd:
+            import base64
+            auth = string.strip(base64.encodestring(user_passwd))
+        else:
+            auth = None
+        h = httplib.HTTP(host)
+        if data is not None:
+            h.putrequest('POST', selector)
+            h.putheader('Content-type', 'application/x-www-form-urlencoded')
+            h.putheader('Content-length', '%d' % len(data))
+        else:
+            h.putrequest('GET', selector)
+        if auth: h.putheader('Authorization', 'Basic %s' % auth)
+        if realhost: h.putheader('Host', realhost)
+        for args in self.addheaders: apply(h.putheader, args)
+        h.endheaders()
+        if data is not None:
+            h.send(data + '\r\n')
+        errcode, errmsg, headers = h.getreply()
+        fp = h.getfile()
+        if errcode == 200:
+            return addinfourl(fp, headers, "http:" + url)
+        else:
+            if data is None:
+                return self.http_error(url, fp, errcode, errmsg,
+                                       headers, data)
+            else:
+                return self.http_error(url, fp, errcode, errmsg, headers)
+
+    # Handle http errors.
+    # Derived class can override this, or provide specific handlers
+    # named http_error_DDD where DDD is the 3-digit error code
+    def http_error(self, url, fp, errcode, errmsg, headers, data=None):
+        # First check if there's a specific handler for this error
+        name = 'http_error_%d' % errcode
+        if hasattr(self, name):
+            method = getattr(self, name)
+            if data is None:
+                result = method(url, fp, errcode, errmsg, headers, data)
+            else:
+                result = method(url, fp, errcode, errmsg, headers)
+            if result: return result
+        return self.http_error_default(
+            url, fp, errcode, errmsg, headers)
+
+    # Default http error handler: close the connection and raises IOError
+    def http_error_default(self, url, fp, errcode, errmsg, headers):
+        void = fp.read()
+        fp.close()
+        raise IOError, ('http error', errcode, errmsg, headers)
+
+    # Use Gopher protocol
+    def open_gopher(self, url):
+        import gopherlib
+        host, selector = splithost(url)
+        if not host: raise IOError, ('gopher error', 'no host given')
+        host = unquote(host)
+        type, selector = splitgophertype(selector)
+        selector, query = splitquery(selector)
+        selector = unquote(selector)
+        if query:
+            query = unquote(query)
+            fp = gopherlib.send_query(selector, query, host)
+        else:
+            fp = gopherlib.send_selector(selector, host)
+        return addinfourl(fp, noheaders(), "gopher:" + url)
+
+    # Use local file or FTP depending on form of URL
+    def open_file(self, url):
+        if url[:2] == '//' and url[2:3] != '/':
+            return self.open_ftp(url)
+        else:
+            return self.open_local_file(url)
+
+    # Use local file
+    def open_local_file(self, url):
+        import mimetypes, mimetools, StringIO
+        mtype = mimetypes.guess_type(url)[0]
+        headers = mimetools.Message(StringIO.StringIO(
+            'Content-Type: %s\n' % (mtype or 'text/plain')))
+        host, file = splithost(url)
+        if not host:
+            return addinfourl(open(url2pathname(file), 'rb'),
+                              headers, 'file:'+pathname2url(file))
+        host, port = splitport(host)
+        if not port \
+           and socket.gethostbyname(host) in (localhost(), thishost()): 
+            return addinfourl(open(url2pathname(file), 'rb'),
+                              headers, 'file:'+pathname2url(file))
+        raise IOError, ('local file error', 'not on local host')
+
+    # Use FTP protocol
+    def open_ftp(self, url):
+        host, path = splithost(url)
+        if not host: raise IOError, ('ftp error', 'no host given')
+        host, port = splitport(host)
+        user, host = splituser(host)
+        if user: user, passwd = splitpasswd(user)
+        else: passwd = None
+        host = unquote(host)
+        user = unquote(user or '')
+        passwd = unquote(passwd or '')
+        host = socket.gethostbyname(host)
+        if not port:
+            import ftplib
+            port = ftplib.FTP_PORT
+        else:
+            port = int(port)
+        path, attrs = splitattr(path)
+        path = unquote(path)
+        dirs = string.splitfields(path, '/')
+        dirs, file = dirs[:-1], dirs[-1]
+        if dirs and not dirs[0]: dirs = dirs[1:]
+        key = (user, host, port, string.joinfields(dirs, '/'))
+        # XXX thread unsafe!
+        if len(self.ftpcache) > MAXFTPCACHE:
+            # Prune the cache, rather arbitrarily
+            for k in self.ftpcache.keys():
+                if k != key:
+                    v = self.ftpcache[k]
+                    del self.ftpcache[k]
+                    v.close()
+        try:
+            if not self.ftpcache.has_key(key):
+                self.ftpcache[key] = \
+                    ftpwrapper(user, passwd, host, port, dirs)
+            if not file: type = 'D'
+            else: type = 'I'
+            for attr in attrs:
+                attr, value = splitvalue(attr)
+                if string.lower(attr) == 'type' and \
+                   value in ('a', 'A', 'i', 'I', 'd', 'D'):
+                    type = string.upper(value)
+            (fp, retrlen) = self.ftpcache[key].retrfile(file, type)
+            if retrlen is not None and retrlen >= 0:
+                import mimetools, StringIO
+                headers = mimetools.Message(StringIO.StringIO(
+                    'Content-Length: %d\n' % retrlen))
+            else:
+                headers = noheaders()
+            return addinfourl(fp, headers, "ftp:" + url)
+        except ftperrors(), msg:
+            raise IOError, ('ftp error', msg), sys.exc_info()[2]
+
+    # Use "data" URL
+    def open_data(self, url, data=None):
+        # ignore POSTed data
+        #
+        # syntax of data URLs:
+        # dataurl   := "data:" [ mediatype ] [ ";base64" ] "," data
+        # mediatype := [ type "/" subtype ] *( ";" parameter )
+        # data      := *urlchar
+        # parameter := attribute "=" value
+        import StringIO, mimetools, time
+        try:
+            [type, data] = string.split(url, ',', 1)
+        except ValueError:
+            raise IOError, ('data error', 'bad data URL')
+        if not type:
+            type = 'text/plain;charset=US-ASCII'
+        semi = string.rfind(type, ';')
+        if semi >= 0 and '=' not in type[semi:]:
+            encoding = type[semi+1:]
+            type = type[:semi]
+        else:
+            encoding = ''
+        msg = []
+        msg.append('Date: %s'%time.strftime('%a, %d %b %Y %T GMT',
+                                            time.gmtime(time.time())))
+        msg.append('Content-type: %s' % type)
+        if encoding == 'base64':
+            import base64
+            data = base64.decodestring(data)
+        else:
+            data = unquote(data)
+        msg.append('Content-length: %d' % len(data))
+        msg.append('')
+        msg.append(data)
+        msg = string.join(msg, '\n')
+        f = StringIO.StringIO(msg)
+        headers = mimetools.Message(f, 0)
+        f.fileno = None     # needed for addinfourl
+        return addinfourl(f, headers, url)
 
 
 # Derived class with handlers for errors we can handle (perhaps)
 class FancyURLopener(URLopener):
 
-	def __init__(self, *args):
-		apply(URLopener.__init__, (self,) + args)
-		self.auth_cache = {}
-
-	# Default error handling -- don't raise an exception
-	def http_error_default(self, url, fp, errcode, errmsg, headers):
-		return addinfourl(fp, headers, "http:" + url)
-
-	# Error 302 -- relocated (temporarily)
-	def http_error_302(self, url, fp, errcode, errmsg, headers,
-			   data=None): 
-		# XXX The server can force infinite recursion here!
-		if headers.has_key('location'):
-			newurl = headers['location']
-		elif headers.has_key('uri'):
-			newurl = headers['uri']
-		else:
-			return
-		void = fp.read()
-		fp.close()
-		return self.open(newurl, data)
-
-	# Error 301 -- also relocated (permanently)
-	http_error_301 = http_error_302
-
-	# Error 401 -- authentication required
-	# See this URL for a description of the basic authentication scheme:
-	# http://www.ics.uci.edu/pub/ietf/http/draft-ietf-http-v10-spec-00.txt
-	def http_error_401(self, url, fp, errcode, errmsg, headers,
-			   data=None): 
-		if headers.has_key('www-authenticate'):
-			stuff = headers['www-authenticate']
-			import re
-			match = re.match(
-			    '[ \t]*([^ \t]+)[ \t]+realm="([^"]*)"', stuff)
-			if match:
-				scheme, realm = match.groups()
-				if string.lower(scheme) == 'basic':
-					return self.retry_http_basic_auth(
-						url, realm, data)
-
-	def retry_http_basic_auth(self, url, realm, data):
-		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 = 'http://' + host + selector
-		return self.open(newurl, data)
-
-	def get_user_passwd(self, host, realm, clear_cache = 0):
-		key = realm + '@' + string.lower(host)
-		if self.auth_cache.has_key(key):
-			if clear_cache:
-				del self.auth_cache[key]
-			else:
-				return self.auth_cache[key]
-		user, passwd = self.prompt_user_passwd(host, realm)
-		if user or passwd: self.auth_cache[key] = (user, passwd)
-		return user, passwd
-
-	def prompt_user_passwd(self, host, realm):
-		# Override this in a GUI environment!
-		import getpass
-		try:
-			user = raw_input("Enter username for %s at %s: " %
-					 (realm, host))
-			passwd = getpass.getpass(
-				"Enter password for %s in %s at %s: " %
-				(user, realm, host))
-			return user, passwd
-		except KeyboardInterrupt:
-			print
-			return None, None
+    def __init__(self, *args):
+        apply(URLopener.__init__, (self,) + args)
+        self.auth_cache = {}
+
+    # Default error handling -- don't raise an exception
+    def http_error_default(self, url, fp, errcode, errmsg, headers):
+        return addinfourl(fp, headers, "http:" + url)
+
+    # Error 302 -- relocated (temporarily)
+    def http_error_302(self, url, fp, errcode, errmsg, headers,
+               data=None): 
+        # XXX The server can force infinite recursion here!
+        if headers.has_key('location'):
+            newurl = headers['location']
+        elif headers.has_key('uri'):
+            newurl = headers['uri']
+        else:
+            return
+        void = fp.read()
+        fp.close()
+        return self.open(newurl, data)
+
+    # Error 301 -- also relocated (permanently)
+    http_error_301 = http_error_302
+
+    # Error 401 -- authentication required
+    # See this URL for a description of the basic authentication scheme:
+    # http://www.ics.uci.edu/pub/ietf/http/draft-ietf-http-v10-spec-00.txt
+    def http_error_401(self, url, fp, errcode, errmsg, headers,
+               data=None): 
+        if headers.has_key('www-authenticate'):
+            stuff = headers['www-authenticate']
+            import re
+            match = re.match('[ \t]*([^ \t]+)[ \t]+realm="([^"]*)"', stuff)
+            if match:
+                scheme, realm = match.groups()
+                if string.lower(scheme) == 'basic':
+                    return self.retry_http_basic_auth(url, realm, data)
+
+    def retry_http_basic_auth(self, url, realm, data):
+        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 = 'http://' + host + selector
+        return self.open(newurl, data)
+
+    def get_user_passwd(self, host, realm, clear_cache = 0):
+        key = realm + '@' + string.lower(host)
+        if self.auth_cache.has_key(key):
+            if clear_cache:
+                del self.auth_cache[key]
+            else:
+                return self.auth_cache[key]
+        user, passwd = self.prompt_user_passwd(host, realm)
+        if user or passwd: self.auth_cache[key] = (user, passwd)
+        return user, passwd
+
+    def prompt_user_passwd(self, host, realm):
+        # Override this in a GUI environment!
+        import getpass
+        try:
+            user = raw_input("Enter username for %s at %s: " % (realm,
+                                                                host)) 
+            passwd = getpass.getpass("Enter password for %s in %s at %s: " %
+                (user, realm, host))
+            return user, passwd
+        except KeyboardInterrupt:
+            print
+            return None, None
 
 
 # Utility functions
@@ -537,219 +519,218 @@ class FancyURLopener(URLopener):
 # Return the IP address of the magic hostname 'localhost'
 _localhost = None
 def localhost():
-	global _localhost
-	if not _localhost:
-		_localhost = socket.gethostbyname('localhost')
-	return _localhost
+    global _localhost
+    if not _localhost:
+        _localhost = socket.gethostbyname('localhost')
+    return _localhost
 
 # Return the IP address of the current host
 _thishost = None
 def thishost():
-	global _thishost
-	if not _thishost:
-		_thishost = socket.gethostbyname(socket.gethostname())
-	return _thishost
+    global _thishost
+    if not _thishost:
+        _thishost = socket.gethostbyname(socket.gethostname())
+    return _thishost
 
 # Return the set of errors raised by the FTP class
 _ftperrors = None
 def ftperrors():
-	global _ftperrors
-	if not _ftperrors:
-		import ftplib
-		_ftperrors = ftplib.all_errors
-	return _ftperrors
+    global _ftperrors
+    if not _ftperrors:
+        import ftplib
+        _ftperrors = ftplib.all_errors
+    return _ftperrors
 
 # Return an empty mimetools.Message object
 _noheaders = None
 def noheaders():
-	global _noheaders
-	if not _noheaders:
-		import mimetools
-		import StringIO
-		_noheaders = mimetools.Message(StringIO.StringIO(), 0)
-		_noheaders.fp.close()	# Recycle file descriptor
-	return _noheaders
+    global _noheaders
+    if not _noheaders:
+        import mimetools
+        import StringIO
+        _noheaders = mimetools.Message(StringIO.StringIO(), 0)
+        _noheaders.fp.close()   # Recycle file descriptor
+    return _noheaders
 
 
 # Utility classes
 
 # Class used by open_ftp() for cache of open FTP connections
 class ftpwrapper:
-	def __init__(self, user, passwd, host, port, dirs):
-		self.user = user
-		self.passwd = passwd
-		self.host = host
-		self.port = port
-		self.dirs = dirs
-		self.init()
-	def init(self):
-		import ftplib
-		self.busy = 0
-		self.ftp = ftplib.FTP()
-		self.ftp.connect(self.host, self.port)
-		self.ftp.login(self.user, self.passwd)
-		for dir in self.dirs:
-			self.ftp.cwd(dir)
-	def retrfile(self, file, type):
-		import ftplib
-		self.endtransfer()
-		if type in ('d', 'D'): cmd = 'TYPE A'; isdir = 1
-		else: cmd = 'TYPE ' + type; isdir = 0
-		try:
-			self.ftp.voidcmd(cmd)
-		except ftplib.all_errors:
-			self.init()
-			self.ftp.voidcmd(cmd)
-		conn = None
-		if file and not isdir:
-			# Use nlst to see if the file exists at all
-			try:
-				self.ftp.nlst(file)
-			except ftplib.error_perm, reason:
-				raise IOError, ('ftp error', reason), \
-				      sys.exc_info()[2]
-			# Restore the transfer mode!
-			self.ftp.voidcmd(cmd)
-			# Try to retrieve as a file
-			try:
-				cmd = 'RETR ' + file
-				conn = self.ftp.ntransfercmd(cmd)
-			except ftplib.error_perm, reason:
-				if reason[:3] != '550':
-					raise IOError, ('ftp error', reason), \
-					      sys.exc_info()[2]
-		if not conn:
-			# Set transfer mode to ASCII!
-			self.ftp.voidcmd('TYPE A')
-			# Try a directory listing
-			if file: cmd = 'LIST ' + file
-			else: cmd = 'LIST'
-			conn = self.ftp.ntransfercmd(cmd)
-		self.busy = 1
-                # Pass back both a suitably decorated object and a retrieval length
-		return (addclosehook(conn[0].makefile('rb'), self.endtransfer), conn[1])
-	def endtransfer(self):
-		if not self.busy:
-			return
-		self.busy = 0
-		try:
-			self.ftp.voidresp()
-		except ftperrors():
-			pass
-	def close(self):
-		self.endtransfer()
-		try:
-			self.ftp.close()
-		except ftperrors():
-			pass
+    def __init__(self, user, passwd, host, port, dirs):
+        self.user = user
+        self.passwd = passwd
+        self.host = host
+        self.port = port
+        self.dirs = dirs
+        self.init()
+    def init(self):
+        import ftplib
+        self.busy = 0
+        self.ftp = ftplib.FTP()
+        self.ftp.connect(self.host, self.port)
+        self.ftp.login(self.user, self.passwd)
+        for dir in self.dirs:
+            self.ftp.cwd(dir)
+    def retrfile(self, file, type):
+        import ftplib
+        self.endtransfer()
+        if type in ('d', 'D'): cmd = 'TYPE A'; isdir = 1
+        else: cmd = 'TYPE ' + type; isdir = 0
+        try:
+            self.ftp.voidcmd(cmd)
+        except ftplib.all_errors:
+            self.init()
+            self.ftp.voidcmd(cmd)
+        conn = None
+        if file and not isdir:
+            # Use nlst to see if the file exists at all
+            try:
+                self.ftp.nlst(file)
+            except ftplib.error_perm, reason:
+                raise IOError, ('ftp error', reason), sys.exc_info()[2]
+            # Restore the transfer mode!
+            self.ftp.voidcmd(cmd)
+            # Try to retrieve as a file
+            try:
+                cmd = 'RETR ' + file
+                conn = self.ftp.ntransfercmd(cmd)
+            except ftplib.error_perm, reason:
+                if reason[:3] != '550':
+                    raise IOError, ('ftp error', reason), sys.exc_info()[2]
+        if not conn:
+            # Set transfer mode to ASCII!
+            self.ftp.voidcmd('TYPE A')
+            # Try a directory listing
+            if file: cmd = 'LIST ' + file
+            else: cmd = 'LIST'
+            conn = self.ftp.ntransfercmd(cmd)
+        self.busy = 1
+        # Pass back both a suitably decorated object and a retrieval length
+        return (addclosehook(conn[0].makefile('rb'),
+                             self.endtransfer), conn[1]) 
+    def endtransfer(self):
+        if not self.busy:
+            return
+        self.busy = 0
+        try:
+            self.ftp.voidresp()
+        except ftperrors():
+            pass
+    def close(self):
+        self.endtransfer()
+        try:
+            self.ftp.close()
+        except ftperrors():
+            pass
 
 # Base class for addinfo and addclosehook
 class addbase:
-	def __init__(self, fp):
-		self.fp = fp
-		self.read = self.fp.read
-		self.readline = self.fp.readline
-		self.readlines = self.fp.readlines
-		self.fileno = self.fp.fileno
-	def __repr__(self):
-		return '<%s at %s whose fp = %s>' % (
-			  self.__class__.__name__, `id(self)`, `self.fp`)
-	def close(self):
-		self.read = None
-		self.readline = None
-		self.readlines = None
-		self.fileno = None
-		if self.fp: self.fp.close()
-		self.fp = None
+    def __init__(self, fp):
+        self.fp = fp
+        self.read = self.fp.read
+        self.readline = self.fp.readline
+        self.readlines = self.fp.readlines
+        self.fileno = self.fp.fileno
+    def __repr__(self):
+        return '<%s at %s whose fp = %s>' % (self.__class__.__name__,
+                                             `id(self)`, `self.fp`) 
+    def close(self):
+        self.read = None
+        self.readline = None
+        self.readlines = None
+        self.fileno = None
+        if self.fp: self.fp.close()
+        self.fp = None
 
 # Class to add a close hook to an open file
 class addclosehook(addbase):
-	def __init__(self, fp, closehook, *hookargs):
-		addbase.__init__(self, fp)
-		self.closehook = closehook
-		self.hookargs = hookargs
-	def close(self):
-		if self.closehook:
-			apply(self.closehook, self.hookargs)
-			self.closehook = None
-			self.hookargs = None
-		addbase.close(self)
+    def __init__(self, fp, closehook, *hookargs):
+        addbase.__init__(self, fp)
+        self.closehook = closehook
+        self.hookargs = hookargs
+    def close(self):
+        if self.closehook:
+            apply(self.closehook, self.hookargs)
+            self.closehook = None
+            self.hookargs = None
+        addbase.close(self)
 
 # class to add an info() method to an open file
 class addinfo(addbase):
-	def __init__(self, fp, headers):
-		addbase.__init__(self, fp)
-		self.headers = headers
-	def info(self):
-		return self.headers
+    def __init__(self, fp, headers):
+        addbase.__init__(self, fp)
+        self.headers = headers
+    def info(self):
+        return self.headers
 
 # class to add info() and geturl() methods to an open file
 class addinfourl(addbase):
-	def __init__(self, fp, headers, url):
-		addbase.__init__(self, fp)
-		self.headers = headers
-		self.url = url
-	def info(self):
-		return self.headers
-	def geturl(self):
-		return self.url
+    def __init__(self, fp, headers, url):
+        addbase.__init__(self, fp)
+        self.headers = headers
+        self.url = url
+    def info(self):
+        return self.headers
+    def geturl(self):
+        return self.url
 
 
 # Utility to combine a URL with a base URL to form a new URL
 
 def basejoin(base, url):
-	type, path = splittype(url)
-	if type:
-		# if url is complete (i.e., it contains a type), return it
-		return url
-	host, path = splithost(path)
-	type, basepath = splittype(base) # inherit type from base
-	if host:
-		# if url contains host, just inherit type
-		if type: return type + '://' + host + path
-		else:
-			# no type inherited, so url must have started with //
-			# just return it
-			return url
-	host, basepath = splithost(basepath) # inherit host
-	basepath, basetag = splittag(basepath) # remove extraneuous cruft
-	basepath, basequery = splitquery(basepath) # idem
-	if path[:1] != '/':
-		# non-absolute path name
-		if path[:1] in ('#', '?'):
-			# path is just a tag or query, attach to basepath
-			i = len(basepath)
-		else:
-			# else replace last component
-			i = string.rfind(basepath, '/')
-		if i < 0:
-			# basepath not absolute
-			if host:
-				# host present, make absolute
-				basepath = '/'
-			else:
-				# else keep non-absolute
-				basepath = ''
-		else:
-			# remove last file component
-			basepath = basepath[:i+1]
-		# Interpret ../ (important because of symlinks)
-		while basepath and path[:3] == '../':
-			path = path[3:]
-			i = string.rfind(basepath[:-1], '/')
-			if i > 0:
-				basepath = basepath[:i+1]
-			elif i == 0:
-				basepath = '/'
-				break
-			else:
-				basepath = ''
-			
-		path = basepath + path
-	if type and host: return type + '://' + host + path
-	elif type: return type + ':' + path
-	elif host: return '//' + host + path # don't know what this means
-	else: return path
+    type, path = splittype(url)
+    if type:
+        # if url is complete (i.e., it contains a type), return it
+        return url
+    host, path = splithost(path)
+    type, basepath = splittype(base) # inherit type from base
+    if host:
+        # if url contains host, just inherit type
+        if type: return type + '://' + host + path
+        else:
+            # no type inherited, so url must have started with //
+            # just return it
+            return url
+    host, basepath = splithost(basepath) # inherit host
+    basepath, basetag = splittag(basepath) # remove extraneuous cruft
+    basepath, basequery = splitquery(basepath) # idem
+    if path[:1] != '/':
+        # non-absolute path name
+        if path[:1] in ('#', '?'):
+            # path is just a tag or query, attach to basepath
+            i = len(basepath)
+        else:
+            # else replace last component
+            i = string.rfind(basepath, '/')
+        if i < 0:
+            # basepath not absolute
+            if host:
+                # host present, make absolute
+                basepath = '/'
+            else:
+                # else keep non-absolute
+                basepath = ''
+        else:
+            # remove last file component
+            basepath = basepath[:i+1]
+        # Interpret ../ (important because of symlinks)
+        while basepath and path[:3] == '../':
+            path = path[3:]
+            i = string.rfind(basepath[:-1], '/')
+            if i > 0:
+                basepath = basepath[:i+1]
+            elif i == 0:
+                basepath = '/'
+                break
+            else:
+                basepath = ''
+            
+        path = basepath + path
+    if type and host: return type + '://' + host + path
+    elif type: return type + ':' + path
+    elif host: return '//' + host + path # don't know what this means
+    else: return path
 
 
 # Utilities to parse URLs (most of these return None for missing parts):
@@ -769,68 +750,68 @@ def basejoin(base, url):
 # quote('abc def') -> 'abc%20def')
 
 def unwrap(url):
-	url = string.strip(url)
-	if url[:1] == '<' and url[-1:] == '>':
-		url = string.strip(url[1:-1])
-	if url[:4] == 'URL:': url = string.strip(url[4:])
-	return url
+    url = string.strip(url)
+    if url[:1] == '<' and url[-1:] == '>':
+        url = string.strip(url[1:-1])
+    if url[:4] == 'URL:': url = string.strip(url[4:])
+    return url
 
 _typeprog = None
 def splittype(url):
-	global _typeprog
-	if _typeprog is None:
-		import re
-		_typeprog = re.compile('^([^/:]+):')
+    global _typeprog
+    if _typeprog is None:
+        import re
+        _typeprog = re.compile('^([^/:]+):')
 
-	match = _typeprog.match(url)
-	if match:
-		scheme = match.group(1)
-		return scheme, url[len(scheme) + 1:]
-	return None, url
+    match = _typeprog.match(url)
+    if match:
+        scheme = match.group(1)
+        return scheme, url[len(scheme) + 1:]
+    return None, url
 
 _hostprog = None
 def splithost(url):
-	global _hostprog
-	if _hostprog is None:
-		import re
-		_hostprog = re.compile('^//([^/]+)(.*)$')
+    global _hostprog
+    if _hostprog is None:
+        import re
+        _hostprog = re.compile('^//([^/]+)(.*)$')
 
-	match = _hostprog.match(url) 
-	if match: return match.group(1, 2)
-	return None, url
+    match = _hostprog.match(url) 
+    if match: return match.group(1, 2)
+    return None, url
 
 _userprog = None
 def splituser(host):
-	global _userprog
-	if _userprog is None:
-		import re
-		_userprog = re.compile('^([^@]*)@(.*)$')
+    global _userprog
+    if _userprog is None:
+        import re
+        _userprog = re.compile('^([^@]*)@(.*)$')
 
-	match = _userprog.match(host)
-	if match: return match.group(1, 2)
-	return None, host
+    match = _userprog.match(host)
+    if match: return match.group(1, 2)
+    return None, host
 
 _passwdprog = None
 def splitpasswd(user):
-	global _passwdprog
-	if _passwdprog is None:
-		import re
-		_passwdprog = re.compile('^([^:]*):(.*)$')
+    global _passwdprog
+    if _passwdprog is None:
+        import re
+        _passwdprog = re.compile('^([^:]*):(.*)$')
 
-	match = _passwdprog.match(user)
-	if match: return match.group(1, 2)
-	return user, None
+    match = _passwdprog.match(user)
+    if match: return match.group(1, 2)
+    return user, None
 
 _portprog = None
 def splitport(host):
-	global _portprog
-	if _portprog is None:
-		import re
-		_portprog = re.compile('^(.*):([0-9]+)$')
+    global _portprog
+    if _portprog is None:
+        import re
+        _portprog = re.compile('^(.*):([0-9]+)$')
 
-	match = _portprog.match(host)
-	if match: return match.group(1, 2)
-	return host, None
+    match = _portprog.match(host)
+    if match: return match.group(1, 2)
+    return host, None
 
 # Split host and port, returning numeric port.
 # Return given default port if no ':' found; defaults to -1.
@@ -838,183 +819,183 @@ def splitport(host):
 # Return None if ':' but not a valid number.
 _nportprog = None
 def splitnport(host, defport=-1):
-	global _nportprog
-	if _nportprog is None:
-		import re
-		_nportprog = re.compile('^(.*):(.*)$')
-
-	match = _nportprog.match(host)
-	if match:
-		host, port = match.group(1, 2)
-		try:
-			if not port: raise string.atoi_error, "no digits"
-			nport = string.atoi(port)
-		except string.atoi_error:
-			nport = None
-		return host, nport
-	return host, defport
+    global _nportprog
+    if _nportprog is None:
+        import re
+        _nportprog = re.compile('^(.*):(.*)$')
+
+    match = _nportprog.match(host)
+    if match:
+        host, port = match.group(1, 2)
+        try:
+            if not port: raise string.atoi_error, "no digits"
+            nport = string.atoi(port)
+        except string.atoi_error:
+            nport = None
+        return host, nport
+    return host, defport
 
 _queryprog = None
 def splitquery(url):
-	global _queryprog
-	if _queryprog is None:
-		import re
-		_queryprog = re.compile('^(.*)\?([^?]*)$')
+    global _queryprog
+    if _queryprog is None:
+        import re
+        _queryprog = re.compile('^(.*)\?([^?]*)$')
 
-	match = _queryprog.match(url)
-	if match: return match.group(1, 2)
-	return url, None
+    match = _queryprog.match(url)
+    if match: return match.group(1, 2)
+    return url, None
 
 _tagprog = None
 def splittag(url):
-	global _tagprog
-	if _tagprog is None:
-		import re
-		_tagprog = re.compile('^(.*)#([^#]*)$')
+    global _tagprog
+    if _tagprog is None:
+        import re
+        _tagprog = re.compile('^(.*)#([^#]*)$')
 
-	match = _tagprog.match(url)
-	if match: return match.group(1, 2)
-	return url, None
+    match = _tagprog.match(url)
+    if match: return match.group(1, 2)
+    return url, None
 
 def splitattr(url):
-	words = string.splitfields(url, ';')
-	return words[0], words[1:]
+    words = string.splitfields(url, ';')
+    return words[0], words[1:]
 
 _valueprog = None
 def splitvalue(attr):
-	global _valueprog
-	if _valueprog is None:
-		import re
-		_valueprog = re.compile('^([^=]*)=(.*)$')
+    global _valueprog
+    if _valueprog is None:
+        import re
+        _valueprog = re.compile('^([^=]*)=(.*)$')
 
-	match = _valueprog.match(attr)
-	if match: return match.group(1, 2)
-	return attr, None
+    match = _valueprog.match(attr)
+    if match: return match.group(1, 2)
+    return attr, None
 
 def splitgophertype(selector):
-	if selector[:1] == '/' and selector[1:2]:
-		return selector[1], selector[2:]
-	return None, selector
+    if selector[:1] == '/' and selector[1:2]:
+        return selector[1], selector[2:]
+    return None, selector
 
 def unquote(s):
-	mychr = chr
-	myatoi = string.atoi
-	list = string.split(s, '%')
-	res = [list[0]]
-	myappend = res.append
-	del list[0]
-	for item in list:
-		if item[1:2]:
-			try:
-				myappend(mychr(myatoi(item[:2], 16))
-					 + item[2:])
-			except:
-				myappend('%' + item)
-		else:
-			myappend('%' + item)
-	return string.join(res, "")
+    mychr = chr
+    myatoi = string.atoi
+    list = string.split(s, '%')
+    res = [list[0]]
+    myappend = res.append
+    del list[0]
+    for item in list:
+        if item[1:2]:
+            try:
+                myappend(mychr(myatoi(item[:2], 16))
+                     + item[2:])
+            except:
+                myappend('%' + item)
+        else:
+            myappend('%' + item)
+    return string.join(res, "")
 
 def unquote_plus(s):
-	if '+' in s:
-		# replace '+' with ' '
-		s = string.join(string.split(s, '+'), ' ')
-	return unquote(s)
+    if '+' in s:
+        # replace '+' with ' '
+        s = string.join(string.split(s, '+'), ' ')
+    return unquote(s)
 
 always_safe = string.letters + string.digits + '_,.-'
 def quote(s, safe = '/'):
-	safe = always_safe + safe
-	res = list(s)
-	for i in range(len(res)):
-		c = res[i]
-		if c not in safe:
-			res[i] = '%%%02x' % ord(c)
-	return string.joinfields(res, '')
+    safe = always_safe + safe
+    res = list(s)
+    for i in range(len(res)):
+        c = res[i]
+        if c not in safe:
+            res[i] = '%%%02x' % ord(c)
+    return string.joinfields(res, '')
 
 def quote_plus(s, safe = '/'):
-	if ' ' in s:
-		# replace ' ' with '+'
-		l = string.split(s, ' ')
-		for i in range(len(l)):
-			l[i] = quote(l[i], safe)
-		return string.join(l, '+')
-	else:
-		return quote(s, safe)
+    if ' ' in s:
+        # replace ' ' with '+'
+        l = string.split(s, ' ')
+        for i in range(len(l)):
+            l[i] = quote(l[i], safe)
+        return string.join(l, '+')
+    else:
+        return quote(s, safe)
 
 def urlencode(dict):
-	 l = []
-	 for k, v in dict.items():
-		 k = quote_plus(str(k))
-		 v = quote_plus(str(v))
-		 l.append(k + '=' + v)
-	 return string.join(l, '&')
+     l = []
+     for k, v in dict.items():
+         k = quote_plus(str(k))
+         v = quote_plus(str(v))
+         l.append(k + '=' + v)
+     return string.join(l, '&')
 
 
 # Proxy handling
 if os.name == 'mac':
-	def getproxies():
-		"""Return a dictionary of scheme -> proxy server URL mappings.
-
-		By convention the mac uses Internet Config to store
-		proxies.  An HTTP proxy, for instance, is stored under
-		the HttpProxy key.
-
-		"""
-		try:
-			import ic
-		except ImportError:
-			return {}
-		
-		try:
-			config = ic.IC()
-		except ic.error:
-			return {}
-		proxies = {}
-		# HTTP:
-		if config.has_key('UseHTTPProxy') and config['UseHTTPProxy']:
-			try:
-				value = config['HTTPProxyHost']
-			except ic.error:
-				pass
-			else:
-				proxies['http'] = 'http://%s' % value
-		# FTP: XXXX To be done.
-		# Gopher: XXXX To be done.
-		return proxies
-				
+    def getproxies():
+        """Return a dictionary of scheme -> proxy server URL mappings.
+
+        By convention the mac uses Internet Config to store
+        proxies.  An HTTP proxy, for instance, is stored under
+        the HttpProxy key.
+
+        """
+        try:
+            import ic
+        except ImportError:
+            return {}
+        
+        try:
+            config = ic.IC()
+        except ic.error:
+            return {}
+        proxies = {}
+        # HTTP:
+        if config.has_key('UseHTTPProxy') and config['UseHTTPProxy']:
+            try:
+                value = config['HTTPProxyHost']
+            except ic.error:
+                pass
+            else:
+                proxies['http'] = 'http://%s' % value
+        # FTP: XXXX To be done.
+        # Gopher: XXXX To be done.
+        return proxies
+                
 else:
-	def getproxies():
-		"""Return a dictionary of scheme -> proxy server URL mappings.
-	
-		Scan the environment for variables named <scheme>_proxy;
-		this seems to be the standard convention.  If you need a
-		different way, you can pass a proxies dictionary to the
-		[Fancy]URLopener constructor.
-	
-		"""
-		proxies = {}
-		for name, value in os.environ.items():
-			name = string.lower(name)
-			if value and name[-6:] == '_proxy':
-				proxies[name[:-6]] = value
-		return proxies
+    def getproxies():
+        """Return a dictionary of scheme -> proxy server URL mappings.
+    
+        Scan the environment for variables named <scheme>_proxy;
+        this seems to be the standard convention.  If you need a
+        different way, you can pass a proxies dictionary to the
+        [Fancy]URLopener constructor.
+    
+        """
+        proxies = {}
+        for name, value in os.environ.items():
+            name = string.lower(name)
+            if value and name[-6:] == '_proxy':
+                proxies[name[:-6]] = value
+        return proxies
 
 
 # Test and time quote() and unquote()
 def test1():
-	import time
-	s = ''
-	for i in range(256): s = s + chr(i)
-	s = s*4
-	t0 = time.time()
-	qs = quote(s)
-	uqs = unquote(qs)
-	t1 = time.time()
-	if uqs != s:
-		print 'Wrong!'
-	print `s`
-	print `qs`
-	print `uqs`
-	print round(t1 - t0, 3), 'sec'
+    import time
+    s = ''
+    for i in range(256): s = s + chr(i)
+    s = s*4
+    t0 = time.time()
+    qs = quote(s)
+    uqs = unquote(qs)
+    t1 = time.time()
+    if uqs != s:
+        print 'Wrong!'
+    print `s`
+    print `qs`
+    print `uqs`
+    print round(t1 - t0, 3), 'sec'
 
 
 def reporthook(blocknum, blocksize, totalsize):
@@ -1023,63 +1004,63 @@ def reporthook(blocknum, blocksize, totalsize):
 
 # Test program
 def test(args=[]):
-	if not args:
-		args = [
-			'/etc/passwd',
-			'file:/etc/passwd',
-			'file://localhost/etc/passwd',
-			'ftp://ftp.python.org/etc/passwd',
-## 			'gopher://gopher.micro.umn.edu/1/',
-			'http://www.python.org/index.html',
-			]
-	try:
-		for url in args:
-			print '-'*10, url, '-'*10
-			fn, h = urlretrieve(url, None, reporthook)
-			print fn, h
-			if h:
-				print '======'
-				for k in h.keys(): print k + ':', h[k]
-				print '======'
-			fp = open(fn, 'rb')
-			data = fp.read()
-			del fp
-			if '\r' in data:
-				table = string.maketrans("", "")
-				data = string.translate(data, table, "\r")
-			print data
-			fn, h = None, None
-		print '-'*40
-	finally:
-		urlcleanup()
+    if not args:
+        args = [
+            '/etc/passwd',
+            'file:/etc/passwd',
+            'file://localhost/etc/passwd',
+            'ftp://ftp.python.org/etc/passwd',
+##          'gopher://gopher.micro.umn.edu/1/',
+            'http://www.python.org/index.html',
+            ]
+    try:
+        for url in args:
+            print '-'*10, url, '-'*10
+            fn, h = urlretrieve(url, None, reporthook)
+            print fn, h
+            if h:
+                print '======'
+                for k in h.keys(): print k + ':', h[k]
+                print '======'
+            fp = open(fn, 'rb')
+            data = fp.read()
+            del fp
+            if '\r' in data:
+                table = string.maketrans("", "")
+                data = string.translate(data, table, "\r")
+            print data
+            fn, h = None, None
+        print '-'*40
+    finally:
+        urlcleanup()
 
 def main():
-	import getopt, sys
-	try:
-		opts, args = getopt.getopt(sys.argv[1:], "th")
-	except getopt.error, msg:
-		print msg
-		print "Use -h for help"
-		return
-	t = 0
-	for o, a in opts:
-		if o == '-t':
-			t = t + 1
-		if o == '-h':
-			print "Usage: python urllib.py [-t] [url ...]"
-			print "-t runs self-test;",
-			print "otherwise, contents of urls are printed"
-			return
-	if t:
-		if t > 1:
-			test1()
-		test(args)
-	else:
-		if not args:
-			print "Use -h for help"
-		for url in args:
-			print urlopen(url).read(),
+    import getopt, sys
+    try:
+        opts, args = getopt.getopt(sys.argv[1:], "th")
+    except getopt.error, msg:
+        print msg
+        print "Use -h for help"
+        return
+    t = 0
+    for o, a in opts:
+        if o == '-t':
+            t = t + 1
+        if o == '-h':
+            print "Usage: python urllib.py [-t] [url ...]"
+            print "-t runs self-test;",
+            print "otherwise, contents of urls are printed"
+            return
+    if t:
+        if t > 1:
+            test1()
+        test(args)
+    else:
+        if not args:
+            print "Use -h for help"
+        for url in args:
+            print urlopen(url).read(),
 
 # Run test program when run as a script
 if __name__ == '__main__':
-	main()
+    main()
-- 
cgit v0.12

