home *** CD-ROM | disk | FTP | other *** search
/ Amiga Tools 5 / Amiga Tools 5.iso / tools / developer-tools / andere sprachen / python-1.3 / lib / urllib.py < prev    next >
Encoding:
Python Source  |  1996-07-16  |  19.5 KB  |  716 lines

  1. # Open an arbitrary URL
  2. #
  3. # See the following document for a tentative description of URLs:
  4. #     Uniform Resource Locators              Tim Berners-Lee
  5. #     INTERNET DRAFT                                    CERN
  6. #     IETF URL Working Group                    14 July 1993
  7. #     draft-ietf-uri-url-01.txt
  8. #
  9. # The object returned by URLopener().open(file) will differ per
  10. # protocol.  All you know is that is has methods read(), readline(),
  11. # readlines(), fileno(), close() and info().  The read*(), fileno()
  12. # and close() methods work like those of open files. 
  13. # The info() method returns an mimetools.Message object which can be
  14. # used to query various info about the object, if available.
  15. # (mimetools.Message objects are queried with the getheader() method.)
  16.  
  17. import string
  18. import socket
  19. import regex
  20. import os
  21.  
  22.  
  23. __version__ = '1.3'
  24.  
  25. # Helper for non-unix systems
  26. if os.name == 'mac':
  27.     from macurl2path import url2pathname, pathname2url
  28. else:
  29.     def url2pathname(pathname):
  30.         return pathname
  31.     def pathname2url(pathname):
  32.         return pathname
  33.  
  34. # This really consists of two pieces:
  35. # (1) a class which handles opening of all sorts of URLs
  36. #     (plus assorted utilities etc.)
  37. # (2) a set of functions for parsing URLs
  38. # XXX Should these be separated out into different modules?
  39.  
  40.  
  41. # Shortcut for basic usage
  42. _urlopener = None
  43. def urlopen(url):
  44.     global _urlopener
  45.     if not _urlopener:
  46.         _urlopener = FancyURLopener()
  47.     return _urlopener.open(url)
  48. def urlretrieve(url):
  49.     global _urlopener
  50.     if not _urlopener:
  51.         _urlopener = FancyURLopener()
  52.     return _urlopener.retrieve(url)
  53. def urlcleanup():
  54.     if _urlopener:
  55.         _urlopener.cleanup()
  56.  
  57.  
  58. # Class to open URLs.
  59. # This is a class rather than just a subroutine because we may need
  60. # more than one set of global protocol-specific options.
  61. # Note -- this is a base class for those who don't want the
  62. # automatic handling of errors type 302 (relocated) and 401
  63. # (authorization needed).
  64. ftpcache = {}
  65. class URLopener:
  66.  
  67.     # Constructor
  68.     def __init__(self, proxies=None):
  69.         if proxies is None:
  70.             proxies = getproxies()
  71.         self.proxies = proxies
  72.         server_version = "Python-urllib/%s" % __version__
  73.         self.addheaders = [('User-agent', server_version)]
  74.         self.tempcache = None
  75.         # Undocumented feature: if you assign {} to tempcache,
  76.         # it is used to cache files retrieved with
  77.         # self.retrieve().  This is not enabled by default
  78.         # since it does not work for changing documents (and I
  79.         # haven't got the logic to check expiration headers
  80.         # yet).
  81.         self.ftpcache = ftpcache
  82.         # Undocumented feature: you can use a different
  83.         # ftp cache by assigning to the .ftpcache member;
  84.         # in case you want logically independent URL openers
  85.  
  86.     def __del__(self):
  87.         self.close()
  88.  
  89.     def close(self):
  90.         self.cleanup()
  91.  
  92.     def cleanup(self):
  93.         import os
  94.         if self.tempcache:
  95.             for url in self.tempcache.keys():
  96.                 try:
  97.                     os.unlink(self.tempcache[url][0])
  98.                 except os.error:
  99.                     pass
  100.                 del self.tempcache[url]
  101.  
  102.     # Add a header to be used by the HTTP interface only
  103.     # e.g. u.addheader('Accept', 'sound/basic')
  104.     def addheader(self, *args):
  105.         self.addheaders.append(args)
  106.  
  107.     # External interface
  108.     # Use URLopener().open(file) instead of open(file, 'r')
  109.     def open(self, fullurl):
  110.         fullurl = unwrap(fullurl)
  111.         type, url = splittype(fullurl)
  112.          if not type: type = 'file'
  113.         if self.proxies.has_key(type):
  114.             proxy = self.proxies[type]
  115.             type, proxy = splittype(proxy)
  116.             host, selector = splithost(proxy)
  117.             url = (host, fullurl) # Signal special case to open_*()
  118.         name = 'open_' + type
  119.         if '-' in name:
  120.             import regsub
  121.             name = regsub.gsub('-', '_', name)
  122.         if not hasattr(self, name):
  123.             return self.open_unknown(fullurl)
  124.         try:
  125.             return getattr(self, name)(url)
  126.         except socket.error, msg:
  127.             raise IOError, ('socket error', msg)
  128.  
  129.     # Overridable interface to open unknown URL type
  130.     def open_unknown(self, fullurl):
  131.         type, url = splittype(fullurl)
  132.         raise IOError, ('url error', 'unknown url type', type)
  133.  
  134.     # External interface
  135.     # retrieve(url) returns (filename, None) for a local object
  136.     # or (tempfilename, headers) for a remote object
  137.     def retrieve(self, url):
  138.         if self.tempcache and self.tempcache.has_key(url):
  139.             return self.tempcache[url]
  140.         url1 = unwrap(url)
  141.         if self.tempcache and self.tempcache.has_key(url1):
  142.             self.tempcache[url] = self.tempcache[url1]
  143.             return self.tempcache[url1]
  144.         type, url1 = splittype(url1)
  145.         if not type or type == 'file':
  146.             try:
  147.                 fp = self.open_local_file(url1)
  148.                 del fp
  149.                 return url2pathname(splithost(url1)[1]), None
  150.             except IOError, msg:
  151.                 pass
  152.         fp = self.open(url)
  153.         headers = fp.info()
  154.         import tempfile
  155.         tfn = tempfile.mktemp()
  156.         result = tfn, headers
  157.         if self.tempcache is not None:
  158.             self.tempcache[url] = result
  159.         tfp = open(tfn, 'w')
  160.         bs = 1024*8
  161.         block = fp.read(bs)
  162.         while block:
  163.             tfp.write(block)
  164.             block = fp.read(bs)
  165.         del fp
  166.         del tfp
  167.         return result
  168.  
  169.     # Each method named open_<type> knows how to open that type of URL
  170.  
  171.     # Use HTTP protocol
  172.     def open_http(self, url):
  173.         import httplib
  174.         if type(url) is type(""):
  175.             host, selector = splithost(url)
  176.         else:
  177.             host, selector = url
  178.             print "proxy via http:", host, selector
  179.         if not host: raise IOError, ('http error', 'no host given')
  180.         i = string.find(host, '@')
  181.         if i >= 0:
  182.             user_passwd, host = host[:i], host[i+1:]
  183.         else:
  184.             user_passwd = None
  185.         if user_passwd:
  186.             import base64
  187.             auth = string.strip(base64.encodestring(user_passwd))
  188.         else:
  189.             auth = None
  190.         h = httplib.HTTP(host)
  191.         h.putrequest('GET', selector)
  192.         if auth: h.putheader('Authorization: Basic %s' % auth)
  193.         for args in self.addheaders: apply(h.putheader, args)
  194.         h.endheaders()
  195.         errcode, errmsg, headers = h.getreply()
  196.         fp = h.getfile()
  197.         if errcode == 200:
  198.             return addinfo(fp, headers)
  199.         else:
  200.             return self.http_error(url,
  201.                            fp, errcode, errmsg, headers)
  202.  
  203.     # Handle http errors.
  204.     # Derived class can override this, or provide specific handlers
  205.     # named http_error_DDD where DDD is the 3-digit error code
  206.     def http_error(self, url, fp, errcode, errmsg, headers):
  207.         # First check if there's a specific handler for this error
  208.         name = 'http_error_%d' % errcode
  209.         if hasattr(self, name):
  210.             method = getattr(self, name)
  211.             result = method(url, fp, errcode, errmsg, headers)
  212.             if result: return result
  213.         return self.http_error_default(
  214.             url, fp, errcode, errmsg, headers)
  215.  
  216.     # Default http error handler: close the connection and raises IOError
  217.     def http_error_default(self, url, fp, errcode, errmsg, headers):
  218.         void = fp.read()
  219.         fp.close()
  220.         raise IOError, ('http error', errcode, errmsg, headers)
  221.  
  222.     # Use Gopher protocol
  223.     def open_gopher(self, url):
  224.         import gopherlib
  225.         host, selector = splithost(url)
  226.         if not host: raise IOError, ('gopher error', 'no host given')
  227.         type, selector = splitgophertype(selector)
  228.         selector, query = splitquery(selector)
  229.         selector = unquote(selector)
  230.         if query:
  231.             query = unquote(query)
  232.             fp = gopherlib.send_query(selector, query, host)
  233.         else:
  234.             fp = gopherlib.send_selector(selector, host)
  235.         return addinfo(fp, noheaders())
  236.  
  237.     # Use local file or FTP depending on form of URL
  238.     def open_file(self, url):
  239.         if url[:2] == '//':
  240.             return self.open_ftp(url)
  241.         else:
  242.             return self.open_local_file(url)
  243.  
  244.     # Use local file
  245.     def open_local_file(self, url):
  246.         host, file = splithost(url)
  247.         if not host: return addinfo(open(url2pathname(file), 'r'), noheaders())
  248.         host, port = splitport(host)
  249.         if not port and socket.gethostbyname(host) in (
  250.               localhost(), thishost()):
  251.             file = unquote(file)
  252.             return addinfo(open(url2pathname(file), 'r'), noheaders())
  253.         raise IOError, ('local file error', 'not on local host')
  254.  
  255.     # Use FTP protocol
  256.     def open_ftp(self, url):
  257.         host, path = splithost(url)
  258.         if not host: raise IOError, ('ftp error', 'no host given')
  259.         host, port = splitport(host)
  260.         user, host = splituser(host)
  261.         if user: user, passwd = splitpasswd(user)
  262.         else: passwd = None
  263.         host = socket.gethostbyname(host)
  264.         if not port:
  265.             import ftplib
  266.             port = ftplib.FTP_PORT
  267.         path, attrs = splitattr(path)
  268.         dirs = string.splitfields(path, '/')
  269.         dirs, file = dirs[:-1], dirs[-1]
  270.         if dirs and not dirs[0]: dirs = dirs[1:]
  271.         key = (user, host, port, string.joinfields(dirs, '/'))
  272.         try:
  273.             if not self.ftpcache.has_key(key):
  274.                 self.ftpcache[key] = \
  275.                            ftpwrapper(user, passwd,
  276.                                   host, port, dirs)
  277.             if not file: type = 'D'
  278.             else: type = 'I'
  279.             for attr in attrs:
  280.                 attr, value = splitvalue(attr)
  281.                 if string.lower(attr) == 'type' and \
  282.                    value in ('a', 'A', 'i', 'I', 'd', 'D'):
  283.                     type = string.upper(value)
  284.             return addinfo(self.ftpcache[key].retrfile(file, type),
  285.                   noheaders())
  286.         except ftperrors(), msg:
  287.             raise IOError, ('ftp error', msg)
  288.  
  289.  
  290. # Derived class with handlers for errors we can handle (perhaps)
  291. class FancyURLopener(URLopener):
  292.  
  293.     def __init__(self, *args):
  294.         apply(URLopener.__init__, (self,) + args)
  295.         self.auth_cache = {}
  296.  
  297.     # Default error handling -- don't raise an exception
  298.     def http_error_default(self, url, fp, errcode, errmsg, headers):
  299.         return addinfo(fp, headers)
  300.  
  301.     # Error 302 -- relocated
  302.     def http_error_302(self, url, fp, errcode, errmsg, headers):
  303.         # XXX The server can force infinite recursion here!
  304.         if headers.has_key('location'):
  305.             newurl = headers['location']
  306.         elif headers.has_key('uri'):
  307.             newurl = headers['uri']
  308.         else:
  309.             return
  310.         void = fp.read()
  311.         fp.close()
  312.         return self.open(newurl)
  313.  
  314.     # Error 401 -- authentication required
  315.     # See this URL for a description of the basic authentication scheme:
  316.     # http://www.ics.uci.edu/pub/ietf/http/draft-ietf-http-v10-spec-00.txt
  317.     def http_error_401(self, url, fp, errcode, errmsg, headers):
  318.         if headers.has_key('www-authenticate'):
  319.             stuff = headers['www-authenticate']
  320.             p = regex.compile(
  321.                 '[ \t]*\([^ \t]+\)[ \t]+realm="\([^"]*\)"')
  322.             if p.match(stuff) >= 0:
  323.                 scheme, realm = p.group(1, 2)
  324.                 if string.lower(scheme) == 'basic':
  325.                     return self.retry_http_basic_auth(
  326.                         url, realm)
  327.  
  328.     def retry_http_basic_auth(self, url, realm):
  329.         host, selector = splithost(url)
  330.         i = string.find(host, '@') + 1
  331.         host = host[i:]
  332.         user, passwd = self.get_user_passwd(host, realm, i)
  333.         if not (user or passwd): return None
  334.         host = user + ':' + passwd + '@' + host
  335.         newurl = '//' + host + selector
  336.         return self.open_http(newurl)
  337.  
  338.     def get_user_passwd(self, host, realm, clear_cache = 0):
  339.         key = realm + '@' + string.lower(host)
  340.         if self.auth_cache.has_key(key):
  341.             if clear_cache:
  342.                 del self.auth_cache[key]
  343.             else:
  344.                 return self.auth_cache[key]
  345.         user, passwd = self.prompt_user_passwd(host, realm)
  346.         if user or passwd: self.auth_cache[key] = (user, passwd)
  347.         return user, passwd
  348.  
  349.     def prompt_user_passwd(self, host, realm):
  350.         # Override this in a GUI environment!
  351.         try:
  352.             user = raw_input("Enter username for %s at %s: " %
  353.                      (realm, host))
  354.             self.echo_off()
  355.             try:
  356.                 passwd = raw_input(
  357.                   "Enter password for %s in %s at %s: " %
  358.                   (user, realm, host))
  359.             finally:
  360.                 self.echo_on()
  361.             return user, passwd
  362.         except KeyboardInterrupt:
  363.             return None, None
  364.  
  365.     def echo_off(self):
  366.         import os
  367.         os.system("stty -echo")
  368.  
  369.     def echo_on(self):
  370.         import os
  371.         print
  372.         os.system("stty echo")
  373.  
  374.  
  375. # Utility functions
  376.  
  377. # Return the IP address of the magic hostname 'localhost'
  378. _localhost = None
  379. def localhost():
  380.     global _localhost
  381.     if not _localhost:
  382.         _localhost = socket.gethostbyname('localhost')
  383.     return _localhost
  384.  
  385. # Return the IP address of the current host
  386. _thishost = None
  387. def thishost():
  388.     global _thishost
  389.     if not _thishost:
  390.         _thishost = socket.gethostbyname(socket.gethostname())
  391.     return _thishost
  392.  
  393. # Return the set of errors raised by the FTP class
  394. _ftperrors = None
  395. def ftperrors():
  396.     global _ftperrors
  397.     if not _ftperrors:
  398.         import ftplib
  399.         _ftperrors = (ftplib.error_reply,
  400.                   ftplib.error_temp,
  401.                   ftplib.error_perm,
  402.                   ftplib.error_proto)
  403.     return _ftperrors
  404.  
  405. # Return an empty mimetools.Message object
  406. _noheaders = None
  407. def noheaders():
  408.     global _noheaders
  409.     if not _noheaders:
  410.         import mimetools
  411.         import StringIO
  412.         _noheaders = mimetools.Message(StringIO.StringIO(), 0)
  413.         _noheaders.fp.close()    # Recycle file descriptor
  414.     return _noheaders
  415.  
  416.  
  417. # Utility classes
  418.  
  419. # Class used by open_ftp() for cache of open FTP connections
  420. class ftpwrapper:
  421.     def __init__(self, user, passwd, host, port, dirs):
  422.         self.user = unquote(user or '')
  423.         self.passwd = unquote(passwd or '')
  424.         self.host = host
  425.         self.port = port
  426.         self.dirs = []
  427.         for dir in dirs:
  428.             self.dirs.append(unquote(dir))
  429.         self.init()
  430.     def init(self):
  431.         import ftplib
  432.         self.ftp = ftplib.FTP()
  433.         self.ftp.connect(self.host, self.port)
  434.         self.ftp.login(self.user, self.passwd)
  435.         for dir in self.dirs:
  436.             self.ftp.cwd(dir)
  437.     def retrfile(self, file, type):
  438.         import ftplib
  439.         if type in ('d', 'D'): cmd = 'TYPE A'; isdir = 1
  440.         else: cmd = 'TYPE ' + type; isdir = 0
  441.         try:
  442.             self.ftp.voidcmd(cmd)
  443.         except ftplib.all_errors:
  444.             self.init()
  445.             self.ftp.voidcmd(cmd)
  446.         conn = None
  447.         if file and not isdir:
  448.             try:
  449.                 cmd = 'RETR ' + file
  450.                 conn = self.ftp.transfercmd(cmd)
  451.             except ftplib.error_perm, reason:
  452.                 if reason[:3] != '550':
  453.                     raise IOError, ('ftp error', reason)
  454.         if not conn:
  455.             # Try a directory listing
  456.             if file: cmd = 'LIST ' + file
  457.             else: cmd = 'LIST'
  458.             conn = self.ftp.transfercmd(cmd)
  459.         return addclosehook(conn.makefile('rb'), self.ftp.voidresp)
  460.  
  461. # Base class for addinfo and addclosehook
  462. class addbase:
  463.     def __init__(self, fp):
  464.         self.fp = fp
  465.         self.read = self.fp.read
  466.         self.readline = self.fp.readline
  467.         self.readlines = self.fp.readlines
  468.         self.fileno = self.fp.fileno
  469.     def __repr__(self):
  470.         return '<%s at %s whose fp = %s>' % (
  471.               self.__class__.__name__, `id(self)`, `self.fp`)
  472.     def close(self):
  473.         self.read = None
  474.         self.readline = None
  475.         self.readlines = None
  476.         self.fileno = None
  477.         if self.fp: self.fp.close()
  478.         self.fp = None
  479.  
  480. # Class to add a close hook to an open file
  481. class addclosehook(addbase):
  482.     def __init__(self, fp, closehook, *hookargs):
  483.         addbase.__init__(self, fp)
  484.         self.closehook = closehook
  485.         self.hookargs = hookargs
  486.     def close(self):
  487.         if self.closehook:
  488.             apply(self.closehook, self.hookargs)
  489.             self.closehook = None
  490.             self.hookargs = None
  491.         addbase.close(self)
  492.  
  493. # class to add an info() method to an open file
  494. class addinfo(addbase):
  495.     def __init__(self, fp, headers):
  496.         addbase.__init__(self, fp)
  497.         self.headers = headers
  498.     def info(self):
  499.         return self.headers
  500.  
  501.  
  502. # Utility to combine a URL with a base URL to form a new URL
  503.  
  504. def basejoin(base, url):
  505.     type, path = splittype(url)
  506.     if type:
  507.         # if url is complete (i.e., it contains a type), return it
  508.         return url
  509.     host, path = splithost(path)
  510.     type, basepath = splittype(base) # inherit type from base
  511.     if host:
  512.         # if url contains host, just inherit type
  513.         if type: return type + '://' + host + path
  514.         else:
  515.             # no type inherited, so url must have started with //
  516.             # just return it
  517.             return url
  518.     host, basepath = splithost(basepath) # inherit host
  519.     basepath, basetag = splittag(basepath) # remove extraneuous cruft
  520.     basepath, basequery = splitquery(basepath) # idem
  521.     if path[:1] != '/':
  522.         # non-absolute path name
  523.         if path[:1] in ('#', '?'):
  524.             # path is just a tag or query, attach to basepath
  525.             i = len(basepath)
  526.         else:
  527.             # else replace last component
  528.             i = string.rfind(basepath, '/')
  529.         if i < 0:
  530.             # basepath not absolute
  531.             if host:
  532.                 # host present, make absolute
  533.                 basepath = '/'
  534.             else:
  535.                 # else keep non-absolute
  536.                 basepath = ''
  537.         else:
  538.             # remove last file component
  539.             basepath = basepath[:i+1]
  540.         path = basepath + path
  541.     if type and host: return type + '://' + host + path
  542.     elif type: return type + ':' + path
  543.     elif host: return '//' + host + path # don't know what this means
  544.     else: return path
  545.  
  546.  
  547. # Utilities to parse URLs (most of these return None for missing parts):
  548. # unwrap('<URL:type://host/path>') --> 'type://host/path'
  549. # splittype('type:opaquestring') --> 'type', 'opaquestring'
  550. # splithost('//host[:port]/path') --> 'host[:port]', '/path'
  551. # splituser('user[:passwd]@host[:port]') --> 'user[:passwd]', 'host[:port]'
  552. # splitpasswd('user:passwd') -> 'user', 'passwd'
  553. # splitport('host:port') --> 'host', 'port'
  554. # splitquery('/path?query') --> '/path', 'query'
  555. # splittag('/path#tag') --> '/path', 'tag'
  556. # splitattr('/path;attr1=value1;attr2=value2;...') ->
  557. #   '/path', ['attr1=value1', 'attr2=value2', ...]
  558. # splitvalue('attr=value') --> 'attr', 'value'
  559. # splitgophertype('/Xselector') --> 'X', 'selector'
  560. # unquote('abc%20def') -> 'abc def'
  561. # quote('abc def') -> 'abc%20def')
  562.  
  563. def unwrap(url):
  564.     url = string.strip(url)
  565.     if url[:1] == '<' and url[-1:] == '>':
  566.         url = string.strip(url[1:-1])
  567.     if url[:4] == 'URL:': url = string.strip(url[4:])
  568.     return url
  569.  
  570. _typeprog = regex.compile('^\([^/:]+\):\(.*\)$')
  571. def splittype(url):
  572.     if _typeprog.match(url) >= 0: return _typeprog.group(1, 2)
  573.     return None, url
  574.  
  575. _hostprog = regex.compile('^//\([^/]+\)\(.*\)$')
  576. def splithost(url):
  577.     if _hostprog.match(url) >= 0: return _hostprog.group(1, 2)
  578.     return None, url
  579.  
  580. _userprog = regex.compile('^\([^@]*\)@\(.*\)$')
  581. def splituser(host):
  582.     if _userprog.match(host) >= 0: return _userprog.group(1, 2)
  583.     return None, host
  584.  
  585. _passwdprog = regex.compile('^\([^:]*\):\(.*\)$')
  586. def splitpasswd(user):
  587.     if _passwdprog.match(user) >= 0: return _passwdprog.group(1, 2)
  588.     return user, None
  589.  
  590. _portprog = regex.compile('^\(.*\):\([0-9]+\)$')
  591. def splitport(host):
  592.     if _portprog.match(host) >= 0: return _portprog.group(1, 2)
  593.     return host, None
  594.  
  595. _queryprog = regex.compile('^\(.*\)\?\([^?]*\)$')
  596. def splitquery(url):
  597.     if _queryprog.match(url) >= 0: return _queryprog.group(1, 2)
  598.     return url, None
  599.  
  600. _tagprog = regex.compile('^\(.*\)#\([^#]*\)$')
  601. def splittag(url):
  602.     if _tagprog.match(url) >= 0: return _tagprog.group(1, 2)
  603.     return url, None
  604.  
  605. def splitattr(url):
  606.     words = string.splitfields(url, ';')
  607.     return words[0], words[1:]
  608.  
  609. _valueprog = regex.compile('^\([^=]*\)=\(.*\)$')
  610. def splitvalue(attr):
  611.     if _valueprog.match(attr) >= 0: return _valueprog.group(1, 2)
  612.     return attr, None
  613.  
  614. def splitgophertype(selector):
  615.     if selector[:1] == '/' and selector[1:2]:
  616.         return selector[1], selector[2:]
  617.     return None, selector
  618.  
  619. _quoteprog = regex.compile('%[0-9a-fA-F][0-9a-fA-F]')
  620. def unquote(s):
  621.     i = 0
  622.     n = len(s)
  623.     res = ''
  624.     while 0 <= i < n:
  625.         j = _quoteprog.search(s, i)
  626.         if j < 0:
  627.             res = res + s[i:]
  628.             break
  629.         res = res + (s[i:j] + chr(string.atoi(s[j+1:j+3], 16)))
  630.         i = j+3
  631.     return res
  632.  
  633. always_safe = string.letters + string.digits + '_,.-'
  634. def quote(s, safe = '/'):
  635.     safe = always_safe + safe
  636.     res = ''
  637.     for c in s:
  638.         if c in safe:
  639.             res = res + c
  640.         else:
  641.             res = res + '%%%02x' % ord(c)
  642.     return res
  643.  
  644.  
  645. # Proxy handling
  646. def getproxies():
  647.     """Return a dictionary of protocol scheme -> proxy server URL mappings.
  648.  
  649.     Scan the environment for variables named <scheme>_proxy;
  650.     this seems to be the standard convention.  If you need a
  651.     different way, you can pass a proxies dictionary to the
  652.     [Fancy]URLopener constructor.
  653.  
  654.     """
  655.     proxies = {}
  656.     for name, value in os.environ.items():
  657.         if value and name[-6:] == '_proxy':
  658.             proxies[name[:-6]] = value
  659.     return proxies
  660.  
  661.  
  662. # Test and time quote() and unquote()
  663. def test1():
  664.     import time
  665.     s = ''
  666.     for i in range(256): s = s + chr(i)
  667.     s = s*4
  668.     t0 = time.time()
  669.     qs = quote(s)
  670.     uqs = unquote(qs)
  671.     t1 = time.time()
  672.     if uqs != s:
  673.         print 'Wrong!'
  674.     print `s`
  675.     print `qs`
  676.     print `uqs`
  677.     print round(t1 - t0, 3), 'sec'
  678.  
  679.  
  680. # Test program
  681. def test():
  682.     import sys
  683.     import regsub
  684.     args = sys.argv[1:]
  685.     if not args:
  686.         args = [
  687.             '/etc/passwd',
  688.             'file:/etc/passwd',
  689.             'file://localhost/etc/passwd',
  690.             'ftp://ftp.cwi.nl/etc/passwd',
  691.             'gopher://gopher.cwi.nl/11/',
  692.             'http://www.cwi.nl/index.html',
  693.             ]
  694.     try:
  695.         for url in args:
  696.             print '-'*10, url, '-'*10
  697.             fn, h = urlretrieve(url)
  698.             print fn, h
  699.             if h:
  700.                 print '======'
  701.                 for k in h.keys(): print k + ':', h[k]
  702.                 print '======'
  703.             fp = open(fn, 'r')
  704.             data = fp.read()
  705.             del fp
  706.             print regsub.gsub('\r', '', data)
  707.             fn, h = None, None
  708.         print '-'*40
  709.     finally:
  710.         urlcleanup()
  711.  
  712. # Run test program when run as a script
  713. if __name__ == '__main__':
  714. ##    test1()
  715.     test()
  716.