urllib2.py 35.5 KB
Newer Older
1
"""An extensible library for opening URLs using a variety of protocols
Jeremy Hylton's avatar
Jeremy Hylton committed
2 3

The simplest way to use this module is to call the urlopen function,
4
which accepts a string containing a URL or a Request object (described
Jeremy Hylton's avatar
Jeremy Hylton committed
5 6 7 8
below).  It opens the URL and returns the results as file-like
object; the returned object has some extra methods described below.

The OpenerDirectory manages a collection of Handler objects that do
9
all the actual work.  Each Handler implements a particular protocol or
Jeremy Hylton's avatar
Jeremy Hylton committed
10 11 12 13 14 15 16 17 18
option.  The OpenerDirector is a composite object that invokes the
Handlers needed to open the requested URL.  For example, the
HTTPHandler performs HTTP GET and POST requests and deals with
non-error returns.  The HTTPRedirectHandler automatically deals with
HTTP 301 & 302 redirect errors, and the HTTPDigestAuthHandler deals
with digest authentication.

urlopen(url, data=None) -- basic usage is that same as original
urllib.  pass the url and optionally data to post to an HTTP URL, and
19
get a file-like object back.  One difference is that you can also pass
Jeremy Hylton's avatar
Jeremy Hylton committed
20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44
a Request instance instead of URL.  Raises a URLError (subclass of
IOError); for HTTP errors, raises an HTTPError, which can also be
treated as a valid response.

build_opener -- function that creates a new OpenerDirector instance.
will install the default handlers.  accepts one or more Handlers as
arguments, either instances or Handler classes that it will
instantiate.  if one of the argument is a subclass of the default
handler, the argument will be installed instead of the default.

install_opener -- installs a new opener as the default opener.

objects of interest:
OpenerDirector --

Request -- an object that encapsulates the state of a request.  the
state can be a simple as the URL.  it can also include extra HTTP
headers, e.g. a User-Agent.

BaseHandler --

exceptions:
URLError-- a subclass of IOError, individual protocols have their own
specific subclass

45
HTTPError-- also a valid HTTP response, so you can treat an HTTP error
Jeremy Hylton's avatar
Jeremy Hylton committed
46 47 48 49 50 51 52 53 54 55 56 57 58 59
as an exceptional event or valid response

internals:
BaseHandler and parent
_call_chain conventions

Example usage:

import urllib2

# set up authentication info
authinfo = urllib2.HTTPBasicAuthHandler()
authinfo.add_password('realm', 'host', 'username', 'password')

60 61
proxy_support = urllib2.ProxyHandler({"http" : "http://ahad-haam:3128"})

62
# build a new opener that adds authentication and caching FTP handlers
63
opener = urllib2.build_opener(proxy_support, authinfo, urllib2.CacheFTPHandler)
Jeremy Hylton's avatar
Jeremy Hylton committed
64 65 66 67 68 69 70 71 72 73 74

# install it
urllib2.install_opener(opener)

f = urllib2.urlopen('http://www.python.org/')


"""

# XXX issues:
# If an authentication error handler that tries to perform
75 76 77 78 79
# authentication for some reason but fails, how should the error be
# signalled?  The client needs to know the HTTP error code.  But if
# the handler knows that the problem was, e.g., that it didn't know
# that hash algo that requested in the challenge, it would be good to
# pass that information along to the client, too.
Jeremy Hylton's avatar
Jeremy Hylton committed
80 81 82 83 84 85 86 87 88 89 90 91

# XXX to do:
# name!
# documentation (getting there)
# complex proxies
# abstract factory for opener
# ftp errors aren't handled cleanly
# gopher can return a socket.error
# check digest against correct (i.e. non-apache) implementation

import socket
import httplib
92
import inspect
Jeremy Hylton's avatar
Jeremy Hylton committed
93 94 95 96 97 98 99
import re
import base64
import types
import urlparse
import md5
import mimetypes
import mimetools
100
import rfc822
Jeremy Hylton's avatar
Jeremy Hylton committed
101 102 103
import ftplib
import sys
import time
104
import os
Jeremy Hylton's avatar
Jeremy Hylton committed
105
import gopherlib
106
import posixpath
Jeremy Hylton's avatar
Jeremy Hylton committed
107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127

try:
    from cStringIO import StringIO
except ImportError:
    from StringIO import StringIO

try:
    import sha
except ImportError:
    # need 1.5.2 final
    sha = None

# not sure how many of these need to be gotten rid of
from urllib import unwrap, unquote, splittype, splithost, \
     addinfourl, splitport, splitgophertype, splitquery, \
     splitattr, ftpwrapper, noheaders

# support for proxies via environment variables
from urllib import getproxies

# support for FileHandler
128
from urllib import localhost, url2pathname
Jeremy Hylton's avatar
Jeremy Hylton committed
129 130 131 132 133 134 135 136 137 138 139 140 141 142 143

__version__ = "2.0a1"

_opener = None
def urlopen(url, data=None):
    global _opener
    if _opener is None:
        _opener = build_opener()
    return _opener.open(url, data)

def install_opener(opener):
    global _opener
    _opener = opener

# do these error classes make sense?
144
# make sure all of the IOError stuff is overridden.  we just want to be
Jeremy Hylton's avatar
Jeremy Hylton committed
145 146 147 148 149 150
 # subtypes.

class URLError(IOError):
    # URLError is a sub-type of IOError, but it doesn't share any of
    # the implementation.  need to override __init__ and __str__
    def __init__(self, reason):
Fred Drake's avatar
Fred Drake committed
151
        self.reason = reason
Jeremy Hylton's avatar
Jeremy Hylton committed
152 153

    def __str__(self):
Fred Drake's avatar
Fred Drake committed
154
        return '<urlopen error %s>' % self.reason
Jeremy Hylton's avatar
Jeremy Hylton committed
155 156 157

class HTTPError(URLError, addinfourl):
    """Raised when HTTP error occurs, but also acts like non-error return"""
Jeremy Hylton's avatar
Jeremy Hylton committed
158
    __super_init = addinfourl.__init__
Jeremy Hylton's avatar
Jeremy Hylton committed
159 160

    def __init__(self, url, code, msg, hdrs, fp):
Jeremy Hylton's avatar
Jeremy Hylton committed
161
        self.__super_init(fp, hdrs, url)
Fred Drake's avatar
Fred Drake committed
162 163 164 165 166 167
        self.code = code
        self.msg = msg
        self.hdrs = hdrs
        self.fp = fp
        # XXX
        self.filename = url
168

Jeremy Hylton's avatar
Jeremy Hylton committed
169
    def __str__(self):
Fred Drake's avatar
Fred Drake committed
170
        return 'HTTP Error %s: %s' % (self.code, self.msg)
Jeremy Hylton's avatar
Jeremy Hylton committed
171 172

    def __del__(self):
Fred Drake's avatar
Fred Drake committed
173 174
        # XXX is this safe? what if user catches exception, then
        # extracts fp and discards exception?
Jeremy Hylton's avatar
Jeremy Hylton committed
175 176
        if self.fp:
            self.fp.close()
Jeremy Hylton's avatar
Jeremy Hylton committed
177 178 179 180

class GopherError(URLError):
    pass

181

Jeremy Hylton's avatar
Jeremy Hylton committed
182
class Request:
183

Jeremy Hylton's avatar
Jeremy Hylton committed
184
    def __init__(self, url, data=None, headers={}):
Fred Drake's avatar
Fred Drake committed
185 186 187 188 189 190
        # unwrap('<URL:type://host/path>') --> 'type://host/path'
        self.__original = unwrap(url)
        self.type = None
        # self.__r_type is what's left after doing the splittype
        self.host = None
        self.port = None
Jeremy Hylton's avatar
Jeremy Hylton committed
191
        self.data = data
Fred Drake's avatar
Fred Drake committed
192
        self.headers = {}
Jeremy Hylton's avatar
Jeremy Hylton committed
193 194 195
        self.headers.update(headers)

    def __getattr__(self, attr):
Fred Drake's avatar
Fred Drake committed
196
        # XXX this is a fallback mechanism to guard against these
197
        # methods getting called in a non-standard order.  this may be
Fred Drake's avatar
Fred Drake committed
198 199 200 201 202 203 204 205
        # too complicated and/or unnecessary.
        # XXX should the __r_XXX attributes be public?
        if attr[:12] == '_Request__r_':
            name = attr[12:]
            if hasattr(Request, 'get_' + name):
                getattr(self, 'get_' + name)()
                return getattr(self, attr)
        raise AttributeError, attr
Jeremy Hylton's avatar
Jeremy Hylton committed
206 207 208 209 210 211 212 213 214 215 216 217 218 219

    def add_data(self, data):
        self.data = data

    def has_data(self):
        return self.data is not None

    def get_data(self):
        return self.data

    def get_full_url(self):
        return self.__original

    def get_type(self):
Fred Drake's avatar
Fred Drake committed
220 221
        if self.type is None:
            self.type, self.__r_type = splittype(self.__original)
222 223
            if self.type is None:
                raise ValueError, "unknown url type: %s" % self.__original
Fred Drake's avatar
Fred Drake committed
224
        return self.type
Jeremy Hylton's avatar
Jeremy Hylton committed
225 226

    def get_host(self):
Fred Drake's avatar
Fred Drake committed
227 228 229 230 231
        if self.host is None:
            self.host, self.__r_host = splithost(self.__r_type)
            if self.host:
                self.host = unquote(self.host)
        return self.host
Jeremy Hylton's avatar
Jeremy Hylton committed
232 233

    def get_selector(self):
Fred Drake's avatar
Fred Drake committed
234
        return self.__r_host
Jeremy Hylton's avatar
Jeremy Hylton committed
235

236 237
    def set_proxy(self, host, type):
        self.host, self.type = host, type
Fred Drake's avatar
Fred Drake committed
238
        self.__r_host = self.__original
Jeremy Hylton's avatar
Jeremy Hylton committed
239 240

    def add_header(self, key, val):
Fred Drake's avatar
Fred Drake committed
241 242
        # useful for something like authentication
        self.headers[key] = val
Jeremy Hylton's avatar
Jeremy Hylton committed
243 244 245 246 247 248 249 250 251 252 253 254

class OpenerDirector:
    def __init__(self):
        server_version = "Python-urllib/%s" % __version__
        self.addheaders = [('User-agent', server_version)]
        # manage the individual handlers
        self.handlers = []
        self.handle_open = {}
        self.handle_error = {}

    def add_handler(self, handler):
        added = 0
255
        for meth in dir(handler):
Jeremy Hylton's avatar
Jeremy Hylton committed
256 257
            if meth[-5:] == '_open':
                protocol = meth[:-5]
258
                if self.handle_open.has_key(protocol):
Jeremy Hylton's avatar
Jeremy Hylton committed
259 260 261 262 263
                    self.handle_open[protocol].append(handler)
                else:
                    self.handle_open[protocol] = [handler]
                added = 1
                continue
Eric S. Raymond's avatar
Eric S. Raymond committed
264 265
            i = meth.find('_')
            j = meth[i+1:].find('_') + i + 1
Jeremy Hylton's avatar
Jeremy Hylton committed
266 267 268 269
            if j != -1 and meth[i+1:j] == 'error':
                proto = meth[:i]
                kind = meth[j+1:]
                try:
Eric S. Raymond's avatar
Eric S. Raymond committed
270
                    kind = int(kind)
Jeremy Hylton's avatar
Jeremy Hylton committed
271 272 273 274 275 276 277 278 279 280 281 282 283
                except ValueError:
                    pass
                dict = self.handle_error.get(proto, {})
                if dict.has_key(kind):
                    dict[kind].append(handler)
                else:
                    dict[kind] = [handler]
                self.handle_error[proto] = dict
                added = 1
                continue
        if added:
            self.handlers.append(handler)
            handler.add_parent(self)
284

Jeremy Hylton's avatar
Jeremy Hylton committed
285 286 287 288 289 290 291 292 293 294 295 296 297 298
    def __del__(self):
        self.close()

    def close(self):
        for handler in self.handlers:
            handler.close()
        self.handlers = []

    def _call_chain(self, chain, kind, meth_name, *args):
        # XXX raise an exception if no one else should try to handle
        # this url.  return None if you can't but someone else could.
        handlers = chain.get(kind, ())
        for handler in handlers:
            func = getattr(handler, meth_name)
Jeremy Hylton's avatar
Jeremy Hylton committed
299 300

            result = func(*args)
Jeremy Hylton's avatar
Jeremy Hylton committed
301 302 303 304
            if result is not None:
                return result

    def open(self, fullurl, data=None):
Fred Drake's avatar
Fred Drake committed
305
        # accept a URL or a Request object
306
        if isinstance(fullurl, types.StringTypes):
Fred Drake's avatar
Fred Drake committed
307
            req = Request(fullurl, data)
Jeremy Hylton's avatar
Jeremy Hylton committed
308 309 310 311
        else:
            req = fullurl
            if data is not None:
                req.add_data(data)
Fred Drake's avatar
Fred Drake committed
312
        assert isinstance(req, Request) # really only care about interface
313

Jeremy Hylton's avatar
Jeremy Hylton committed
314
        result = self._call_chain(self.handle_open, 'default',
315
                                  'default_open', req)
Jeremy Hylton's avatar
Jeremy Hylton committed
316 317 318
        if result:
            return result

Fred Drake's avatar
Fred Drake committed
319
        type_ = req.get_type()
Jeremy Hylton's avatar
Jeremy Hylton committed
320
        result = self._call_chain(self.handle_open, type_, type_ + \
Jeremy Hylton's avatar
Jeremy Hylton committed
321
                                  '_open', req)
Jeremy Hylton's avatar
Jeremy Hylton committed
322 323 324 325 326 327 328
        if result:
            return result

        return self._call_chain(self.handle_open, 'unknown',
                                'unknown_open', req)

    def error(self, proto, *args):
329
        if proto in ['http', 'https']:
330 331
            # XXX http[s] protocols are special-cased
            dict = self.handle_error['http'] # https is not different than http
Jeremy Hylton's avatar
Jeremy Hylton committed
332 333 334 335 336 337 338 339 340
            proto = args[2]  # YUCK!
            meth_name = 'http_error_%d' % proto
            http_err = 1
            orig_args = args
        else:
            dict = self.handle_error
            meth_name = proto + '_error'
            http_err = 0
        args = (dict, proto, meth_name) + args
Jeremy Hylton's avatar
Jeremy Hylton committed
341
        result = self._call_chain(*args)
Jeremy Hylton's avatar
Jeremy Hylton committed
342 343 344 345 346
        if result:
            return result

        if http_err:
            args = (dict, 'default', 'http_error_default') + orig_args
Jeremy Hylton's avatar
Jeremy Hylton committed
347
            return self._call_chain(*args)
Jeremy Hylton's avatar
Jeremy Hylton committed
348 349 350 351

# XXX probably also want an abstract factory that knows things like
 # the fact that a ProxyHandler needs to get inserted first.
# would also know when it makes sense to skip a superclass in favor of
352
 # a subclass and when it might make sense to include both
Jeremy Hylton's avatar
Jeremy Hylton committed
353 354 355 356 357 358 359 360 361 362 363

def build_opener(*handlers):
    """Create an opener object from a list of handlers.

    The opener will use several default handlers, including support
    for HTTP and FTP.  If there is a ProxyHandler, it must be at the
    front of the list of handlers.  (Yuck.)

    If any of the handlers passed as arguments are subclasses of the
    default handlers, the default handlers will not be used.
    """
364

Jeremy Hylton's avatar
Jeremy Hylton committed
365 366 367 368
    opener = OpenerDirector()
    default_classes = [ProxyHandler, UnknownHandler, HTTPHandler,
                       HTTPDefaultErrorHandler, HTTPRedirectHandler,
                       FTPHandler, FileHandler]
369 370
    if hasattr(httplib, 'HTTPS'):
        default_classes.append(HTTPSHandler)
Jeremy Hylton's avatar
Jeremy Hylton committed
371 372 373
    skip = []
    for klass in default_classes:
        for check in handlers:
374
            if inspect.isclass(check):
Jeremy Hylton's avatar
Jeremy Hylton committed
375 376
                if issubclass(check, klass):
                    skip.append(klass)
377 378
            elif isinstance(check, klass):
                skip.append(klass)
Jeremy Hylton's avatar
Jeremy Hylton committed
379 380 381 382 383 384 385
    for klass in skip:
        default_classes.remove(klass)

    for klass in default_classes:
        opener.add_handler(klass())

    for h in handlers:
386
        if inspect.isclass(h):
Jeremy Hylton's avatar
Jeremy Hylton committed
387 388 389 390 391 392 393 394 395 396 397 398
            h = h()
        opener.add_handler(h)
    return opener

class BaseHandler:
    def add_parent(self, parent):
        self.parent = parent
    def close(self):
        self.parent = None

class HTTPDefaultErrorHandler(BaseHandler):
    def http_error_default(self, req, fp, code, msg, hdrs):
Fred Drake's avatar
Fred Drake committed
399
        raise HTTPError(req.get_full_url(), code, msg, hdrs, fp)
Jeremy Hylton's avatar
Jeremy Hylton committed
400 401 402 403 404 405 406 407 408 409 410 411 412

class HTTPRedirectHandler(BaseHandler):
    # Implementation note: To avoid the server sending us into an
    # infinite loop, the request object needs to track what URLs we
    # have already seen.  Do this by adding a handler-specific
    # attribute to the Request object.
    def http_error_302(self, req, fp, code, msg, headers):
        if headers.has_key('location'):
            newurl = headers['location']
        elif headers.has_key('uri'):
            newurl = headers['uri']
        else:
            return
Jeremy Hylton's avatar
Jeremy Hylton committed
413 414
        newurl = urlparse.urljoin(req.get_full_url(), newurl)

Jeremy Hylton's avatar
Jeremy Hylton committed
415 416 417
        # XXX Probably want to forget about the state of the current
        # request, although that might interact poorly with other
        # handlers that also use handler-specific request attributes
418
        new = Request(newurl, req.get_data(), req.headers)
Jeremy Hylton's avatar
Jeremy Hylton committed
419 420
        new.error_302_dict = {}
        if hasattr(req, 'error_302_dict'):
421 422
            if len(req.error_302_dict)>10 or \
               req.error_302_dict.has_key(newurl):
Jeremy Hylton's avatar
Jeremy Hylton committed
423
                raise HTTPError(req.get_full_url(), code,
424
                                self.inf_msg + msg, headers, fp)
Jeremy Hylton's avatar
Jeremy Hylton committed
425 426
            new.error_302_dict.update(req.error_302_dict)
        new.error_302_dict[newurl] = newurl
427 428

        # Don't close the fp until we are sure that we won't use it
Tim Peters's avatar
Tim Peters committed
429
        # with HTTPError.
430 431 432
        fp.read()
        fp.close()

Jeremy Hylton's avatar
Jeremy Hylton committed
433 434 435 436 437
        return self.parent.open(new)

    http_error_301 = http_error_302

    inf_msg = "The HTTP server returned a redirect error that would" \
438
              "lead to an infinite loop.\n" \
Jeremy Hylton's avatar
Jeremy Hylton committed
439 440 441 442
              "The last 302 error message was:\n"

class ProxyHandler(BaseHandler):
    def __init__(self, proxies=None):
Fred Drake's avatar
Fred Drake committed
443 444 445 446 447
        if proxies is None:
            proxies = getproxies()
        assert hasattr(proxies, 'has_key'), "proxies must be a mapping"
        self.proxies = proxies
        for type, url in proxies.items():
448
            setattr(self, '%s_open' % type,
Fred Drake's avatar
Fred Drake committed
449 450
                    lambda r, proxy=url, type=type, meth=self.proxy_open: \
                    meth(r, proxy, type))
Jeremy Hylton's avatar
Jeremy Hylton committed
451 452

    def proxy_open(self, req, proxy, type):
Fred Drake's avatar
Fred Drake committed
453
        orig_type = req.get_type()
454 455 456 457
        type, r_type = splittype(proxy)
        host, XXX = splithost(r_type)
        if '@' in host:
            user_pass, host = host.split('@', 1)
Moshe Zadka's avatar
Moshe Zadka committed
458 459
            user_pass = base64.encodestring(unquote(user_pass)).strip()
            req.add_header('Proxy-Authorization', 'Basic '+user_pass)
460 461
        host = unquote(host)
        req.set_proxy(host, type)
Fred Drake's avatar
Fred Drake committed
462 463 464 465 466 467 468 469 470
        if orig_type == type:
            # let other handlers take care of it
            # XXX this only makes sense if the proxy is before the
            # other handlers
            return None
        else:
            # need to start over, because the other handlers don't
            # grok the proxy's URL type
            return self.parent.open(req)
Jeremy Hylton's avatar
Jeremy Hylton committed
471 472 473 474 475 476

# feature suggested by Duncan Booth
# XXX custom is not a good name
class CustomProxy:
    # either pass a function to the constructor or override handle
    def __init__(self, proto, func=None, proxy_addr=None):
Fred Drake's avatar
Fred Drake committed
477 478 479
        self.proto = proto
        self.func = func
        self.addr = proxy_addr
Jeremy Hylton's avatar
Jeremy Hylton committed
480 481

    def handle(self, req):
Fred Drake's avatar
Fred Drake committed
482 483
        if self.func and self.func(req):
            return 1
Jeremy Hylton's avatar
Jeremy Hylton committed
484 485

    def get_proxy(self):
Fred Drake's avatar
Fred Drake committed
486
        return self.addr
Jeremy Hylton's avatar
Jeremy Hylton committed
487 488 489

class CustomProxyHandler(BaseHandler):
    def __init__(self, *proxies):
Fred Drake's avatar
Fred Drake committed
490
        self.proxies = {}
Jeremy Hylton's avatar
Jeremy Hylton committed
491 492

    def proxy_open(self, req):
Fred Drake's avatar
Fred Drake committed
493 494 495 496 497 498 499 500 501 502
        proto = req.get_type()
        try:
            proxies = self.proxies[proto]
        except KeyError:
            return None
        for p in proxies:
            if p.handle(req):
                req.set_proxy(p.get_proxy())
                return self.parent.open(req)
        return None
Jeremy Hylton's avatar
Jeremy Hylton committed
503 504

    def do_proxy(self, p, req):
Fred Drake's avatar
Fred Drake committed
505
        return self.parent.open(req)
Jeremy Hylton's avatar
Jeremy Hylton committed
506 507

    def add_proxy(self, cpo):
Fred Drake's avatar
Fred Drake committed
508 509 510 511
        if self.proxies.has_key(cpo.proto):
            self.proxies[cpo.proto].append(cpo)
        else:
            self.proxies[cpo.proto] = [cpo]
Jeremy Hylton's avatar
Jeremy Hylton committed
512 513 514

class HTTPPasswordMgr:
    def __init__(self):
Fred Drake's avatar
Fred Drake committed
515
        self.passwd = {}
Jeremy Hylton's avatar
Jeremy Hylton committed
516 517

    def add_password(self, realm, uri, user, passwd):
Fred Drake's avatar
Fred Drake committed
518
        # uri could be a single URI or a sequence
519
        if isinstance(uri, types.StringTypes):
Fred Drake's avatar
Fred Drake committed
520 521 522 523 524
            uri = [uri]
        uri = tuple(map(self.reduce_uri, uri))
        if not self.passwd.has_key(realm):
            self.passwd[realm] = {}
        self.passwd[realm][uri] = (user, passwd)
Jeremy Hylton's avatar
Jeremy Hylton committed
525 526

    def find_user_password(self, realm, authuri):
Fred Drake's avatar
Fred Drake committed
527 528 529 530 531 532 533
        domains = self.passwd.get(realm, {})
        authuri = self.reduce_uri(authuri)
        for uris, authinfo in domains.items():
            for uri in uris:
                if self.is_suburi(uri, authuri):
                    return authinfo
        return None, None
Jeremy Hylton's avatar
Jeremy Hylton committed
534 535

    def reduce_uri(self, uri):
Fred Drake's avatar
Fred Drake committed
536 537 538 539 540 541
        """Accept netloc or URI and extract only the netloc and path"""
        parts = urlparse.urlparse(uri)
        if parts[1]:
            return parts[1], parts[2] or '/'
        else:
            return parts[2], '/'
Jeremy Hylton's avatar
Jeremy Hylton committed
542 543

    def is_suburi(self, base, test):
Fred Drake's avatar
Fred Drake committed
544 545 546 547 548
        """Check if test is below base in a URI tree

        Both args must be URIs in reduced form.
        """
        if base == test:
549
            return True
Fred Drake's avatar
Fred Drake committed
550
        if base[0] != test[0]:
551
            return False
552
        common = posixpath.commonprefix((base[1], test[1]))
Fred Drake's avatar
Fred Drake committed
553
        if len(common) == len(base[1]):
554 555
            return True
        return False
556

Jeremy Hylton's avatar
Jeremy Hylton committed
557

558 559 560 561 562 563 564 565 566 567 568
class HTTPPasswordMgrWithDefaultRealm(HTTPPasswordMgr):

    def find_user_password(self, realm, authuri):
        user, password = HTTPPasswordMgr.find_user_password(self,realm,authuri)
        if user is not None:
            return user, password
        return HTTPPasswordMgr.find_user_password(self, None, authuri)


class AbstractBasicAuthHandler:

Jeremy Hylton's avatar
Jeremy Hylton committed
569 570 571 572 573 574
    rx = re.compile('[ \t]*([^ \t]+)[ \t]+realm="([^"]*)"')

    # XXX there can actually be multiple auth-schemes in a
    # www-authenticate header.  should probably be a lot more careful
    # in parsing them to extract multiple alternatives

575 576 577 578
    def __init__(self, password_mgr=None):
        if password_mgr is None:
            password_mgr = HTTPPasswordMgr()
        self.passwd = password_mgr
Fred Drake's avatar
Fred Drake committed
579
        self.add_password = self.passwd.add_password
580

581 582 583
    def http_error_auth_reqed(self, authreq, host, req, headers):
        # XXX could be multiple headers
        authreq = headers.get(authreq, None)
Jeremy Hylton's avatar
Jeremy Hylton committed
584
        if authreq:
585
            mo = AbstractBasicAuthHandler.rx.match(authreq)
Jeremy Hylton's avatar
Jeremy Hylton committed
586 587
            if mo:
                scheme, realm = mo.groups()
Eric S. Raymond's avatar
Eric S. Raymond committed
588
                if scheme.lower() == 'basic':
589
                    return self.retry_http_basic_auth(host, req, realm)
Jeremy Hylton's avatar
Jeremy Hylton committed
590

591
    def retry_http_basic_auth(self, host, req, realm):
Jeremy Hylton's avatar
Jeremy Hylton committed
592 593
        user,pw = self.passwd.find_user_password(realm, host)
        if pw:
Fred Drake's avatar
Fred Drake committed
594
            raw = "%s:%s" % (user, pw)
595 596 597 598 599
            auth = 'Basic %s' % base64.encodestring(raw).strip()
            if req.headers.get(self.auth_header, None) == auth:
                return None
            req.add_header(self.auth_header, auth)
            return self.parent.open(req)
Jeremy Hylton's avatar
Jeremy Hylton committed
600 601 602
        else:
            return None

603
class HTTPBasicAuthHandler(AbstractBasicAuthHandler, BaseHandler):
Jeremy Hylton's avatar
Jeremy Hylton committed
604

605
    auth_header = 'Authorization'
Jeremy Hylton's avatar
Jeremy Hylton committed
606

607 608
    def http_error_401(self, req, fp, code, msg, headers):
        host = urlparse.urlparse(req.get_full_url())[1]
Tim Peters's avatar
Tim Peters committed
609
        return self.http_error_auth_reqed('www-authenticate',
610 611 612 613 614
                                          host, req, headers)


class ProxyBasicAuthHandler(AbstractBasicAuthHandler, BaseHandler):

615
    auth_header = 'Proxy-Authorization'
616 617 618

    def http_error_407(self, req, fp, code, msg, headers):
        host = req.get_host()
Tim Peters's avatar
Tim Peters committed
619
        return self.http_error_auth_reqed('proxy-authenticate',
620 621 622 623 624 625 626
                                          host, req, headers)


class AbstractDigestAuthHandler:

    def __init__(self, passwd=None):
        if passwd is None:
627
            passwd = HTTPPasswordMgr()
628
        self.passwd = passwd
Fred Drake's avatar
Fred Drake committed
629
        self.add_password = self.passwd.add_password
Jeremy Hylton's avatar
Jeremy Hylton committed
630

631
    def http_error_auth_reqed(self, authreq, host, req, headers):
632
        authreq = headers.get(self.auth_header, None)
Fred Drake's avatar
Fred Drake committed
633
        if authreq:
Eric S. Raymond's avatar
Eric S. Raymond committed
634
            kind = authreq.split()[0]
Fred Drake's avatar
Fred Drake committed
635 636
            if kind == 'Digest':
                return self.retry_http_digest_auth(req, authreq)
Jeremy Hylton's avatar
Jeremy Hylton committed
637 638

    def retry_http_digest_auth(self, req, auth):
Eric S. Raymond's avatar
Eric S. Raymond committed
639
        token, challenge = auth.split(' ', 1)
Fred Drake's avatar
Fred Drake committed
640 641 642
        chal = parse_keqv_list(parse_http_list(challenge))
        auth = self.get_authorization(req, chal)
        if auth:
643 644 645 646
            auth_val = 'Digest %s' % auth
            if req.headers.get(self.auth_header, None) == auth_val:
                return None
            req.add_header(self.auth_header, auth_val)
Fred Drake's avatar
Fred Drake committed
647 648
            resp = self.parent.open(req)
            return resp
Jeremy Hylton's avatar
Jeremy Hylton committed
649 650

    def get_authorization(self, req, chal):
Fred Drake's avatar
Fred Drake committed
651 652 653 654 655 656 657 658 659 660 661 662 663 664 665
        try:
            realm = chal['realm']
            nonce = chal['nonce']
            algorithm = chal.get('algorithm', 'MD5')
            # mod_digest doesn't send an opaque, even though it isn't
            # supposed to be optional
            opaque = chal.get('opaque', None)
        except KeyError:
            return None

        H, KD = self.get_algorithm_impls(algorithm)
        if H is None:
            return None

        user, pw = self.passwd.find_user_password(realm,
666
                                                  req.get_full_url())
Fred Drake's avatar
Fred Drake committed
667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692
        if user is None:
            return None

        # XXX not implemented yet
        if req.has_data():
            entdig = self.get_entity_digest(req.get_data(), chal)
        else:
            entdig = None

        A1 = "%s:%s:%s" % (user, realm, pw)
        A2 = "%s:%s" % (req.has_data() and 'POST' or 'GET',
                        # XXX selector: what about proxies and full urls
                        req.get_selector())
        respdig = KD(H(A1), "%s:%s" % (nonce, H(A2)))
        # XXX should the partial digests be encoded too?

        base = 'username="%s", realm="%s", nonce="%s", uri="%s", ' \
               'response="%s"' % (user, realm, nonce, req.get_selector(),
                                  respdig)
        if opaque:
            base = base + ', opaque="%s"' % opaque
        if entdig:
            base = base + ', digest="%s"' % entdig
        if algorithm != 'MD5':
            base = base + ', algorithm="%s"' % algorithm
        return base
Jeremy Hylton's avatar
Jeremy Hylton committed
693 694

    def get_algorithm_impls(self, algorithm):
Fred Drake's avatar
Fred Drake committed
695 696 697 698 699 700 701 702
        # lambdas assume digest modules are imported at the top level
        if algorithm == 'MD5':
            H = lambda x, e=encode_digest:e(md5.new(x).digest())
        elif algorithm == 'SHA':
            H = lambda x, e=encode_digest:e(sha.new(x).digest())
        # XXX MD5-sess
        KD = lambda s, d, H=H: H("%s:%s" % (s, d))
        return H, KD
Jeremy Hylton's avatar
Jeremy Hylton committed
703 704

    def get_entity_digest(self, data, chal):
Fred Drake's avatar
Fred Drake committed
705 706
        # XXX not implemented yet
        return None
Jeremy Hylton's avatar
Jeremy Hylton committed
707

708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731

class HTTPDigestAuthHandler(BaseHandler, AbstractDigestAuthHandler):
    """An authentication protocol defined by RFC 2069

    Digest authentication improves on basic authentication because it
    does not transmit passwords in the clear.
    """

    header = 'Authorization'

    def http_error_401(self, req, fp, code, msg, headers):
        host = urlparse.urlparse(req.get_full_url())[1]
        self.http_error_auth_reqed('www-authenticate', host, req, headers)


class ProxyDigestAuthHandler(BaseHandler, AbstractDigestAuthHandler):

    header = 'Proxy-Authorization'

    def http_error_407(self, req, fp, code, msg, headers):
        host = req.get_host()
        self.http_error_auth_reqed('proxy-authenticate', host, req, headers)


Jeremy Hylton's avatar
Jeremy Hylton committed
732 733 734
def encode_digest(digest):
    hexrep = []
    for c in digest:
Fred Drake's avatar
Fred Drake committed
735 736 737 738
        n = (ord(c) >> 4) & 0xf
        hexrep.append(hex(n)[-1])
        n = ord(c) & 0xf
        hexrep.append(hex(n)[-1])
Eric S. Raymond's avatar
Eric S. Raymond committed
739
    return ''.join(hexrep)
740 741


742 743 744
class AbstractHTTPHandler(BaseHandler):

    def do_open(self, http_class, req):
745
        host = req.get_host()
Jeremy Hylton's avatar
Jeremy Hylton committed
746 747 748
        if not host:
            raise URLError('no host given')

Jeremy Hylton's avatar
Jeremy Hylton committed
749
        try:
750
            h = http_class(host) # will parse host:port
Jeremy Hylton's avatar
Jeremy Hylton committed
751 752 753
            if req.has_data():
                data = req.get_data()
                h.putrequest('POST', req.get_selector())
Moshe Zadka's avatar
Moshe Zadka committed
754 755 756 757 758
                if not req.headers.has_key('Content-type'):
                    h.putheader('Content-type',
                                'application/x-www-form-urlencoded')
                if not req.headers.has_key('Content-length'):
                    h.putheader('Content-length', '%d' % len(data))
Jeremy Hylton's avatar
Jeremy Hylton committed
759 760 761 762
            else:
                h.putrequest('GET', req.get_selector())
        except socket.error, err:
            raise URLError(err)
763

Jeremy Hylton's avatar
Jeremy Hylton committed
764 765
        h.putheader('Host', host)
        for args in self.parent.addheaders:
Jeremy Hylton's avatar
Jeremy Hylton committed
766
            h.putheader(*args)
Fred Drake's avatar
Fred Drake committed
767 768
        for k, v in req.headers.items():
            h.putheader(k, v)
Jeremy Hylton's avatar
Jeremy Hylton committed
769 770
        h.endheaders()
        if req.has_data():
771
            h.send(data)
Jeremy Hylton's avatar
Jeremy Hylton committed
772 773 774 775 776 777 778 779

        code, msg, hdrs = h.getreply()
        fp = h.getfile()
        if code == 200:
            return addinfourl(fp, hdrs, req.get_full_url())
        else:
            return self.parent.error('http', req, fp, code, msg, hdrs)

780 781 782 783 784 785 786 787 788 789 790 791 792 793

class HTTPHandler(AbstractHTTPHandler):

    def http_open(self, req):
        return self.do_open(httplib.HTTP, req)


if hasattr(httplib, 'HTTPS'):
    class HTTPSHandler(AbstractHTTPHandler):

        def https_open(self, req):
            return self.do_open(httplib.HTTPS, req)


Jeremy Hylton's avatar
Jeremy Hylton committed
794 795
class UnknownHandler(BaseHandler):
    def unknown_open(self, req):
Fred Drake's avatar
Fred Drake committed
796
        type = req.get_type()
Jeremy Hylton's avatar
Jeremy Hylton committed
797 798 799 800 801 802
        raise URLError('unknown url type: %s' % type)

def parse_keqv_list(l):
    """Parse list of key=value strings where keys are not duplicated."""
    parsed = {}
    for elt in l:
Eric S. Raymond's avatar
Eric S. Raymond committed
803
        k, v = elt.split('=', 1)
Fred Drake's avatar
Fred Drake committed
804 805 806
        if v[0] == '"' and v[-1] == '"':
            v = v[1:-1]
        parsed[k] = v
Jeremy Hylton's avatar
Jeremy Hylton committed
807 808 809 810 811 812 813 814 815 816 817 818 819 820 821 822 823
    return parsed

def parse_http_list(s):
    """Parse lists as described by RFC 2068 Section 2.

    In particular, parse comman-separated lists where the elements of
    the list may include quoted-strings.  A quoted-string could
    contain a comma.
    """
    # XXX this function could probably use more testing

    list = []
    end = len(s)
    i = 0
    inquote = 0
    start = 0
    while i < end:
Fred Drake's avatar
Fred Drake committed
824
        cur = s[i:]
Eric S. Raymond's avatar
Eric S. Raymond committed
825 826
        c = cur.find(',')
        q = cur.find('"')
Fred Drake's avatar
Fred Drake committed
827 828 829 830 831 832 833 834 835 836 837 838 839 840 841 842 843
        if c == -1:
            list.append(s[start:])
            break
        if q == -1:
            if inquote:
                raise ValueError, "unbalanced quotes"
            else:
                list.append(s[start:i+c])
                i = i + c + 1
                continue
        if inquote:
            if q < c:
                list.append(s[start:i+c])
                i = i + c + 1
                start = i
                inquote = 0
            else:
844
                i = i + q
Fred Drake's avatar
Fred Drake committed
845 846 847 848 849 850 851 852
        else:
            if c < q:
                list.append(s[start:i+c])
                i = i + c + 1
                start = i
            else:
                inquote = 1
                i = i + q + 1
Eric S. Raymond's avatar
Eric S. Raymond committed
853
    return map(lambda x: x.strip(), list)
Jeremy Hylton's avatar
Jeremy Hylton committed
854 855 856 857

class FileHandler(BaseHandler):
    # Use local file or FTP depending on form of URL
    def file_open(self, req):
Fred Drake's avatar
Fred Drake committed
858 859 860 861 862 863
        url = req.get_selector()
        if url[:2] == '//' and url[2:3] != '/':
            req.type = 'ftp'
            return self.parent.open(req)
        else:
            return self.open_local_file(req)
Jeremy Hylton's avatar
Jeremy Hylton committed
864 865 866 867

    # names for the localhost
    names = None
    def get_names(self):
Fred Drake's avatar
Fred Drake committed
868
        if FileHandler.names is None:
869
            FileHandler.names = (socket.gethostbyname('localhost'),
Fred Drake's avatar
Fred Drake committed
870 871
                                 socket.gethostbyname(socket.gethostname()))
        return FileHandler.names
Jeremy Hylton's avatar
Jeremy Hylton committed
872 873 874

    # not entirely sure what the rules are here
    def open_local_file(self, req):
Fred Drake's avatar
Fred Drake committed
875 876
        host = req.get_host()
        file = req.get_selector()
877 878
        localfile = url2pathname(file)
        stats = os.stat(localfile)
879 880
        size = stats.st_size
        modified = rfc822.formatdate(stats.st_mtime)
881 882 883 884
        mtype = mimetypes.guess_type(file)[0]
        headers = mimetools.Message(StringIO(
            'Content-Type: %s\nContent-Length: %d\nLast-modified: %s\n' %
            (mtype or 'text/plain', size, modified)))
Fred Drake's avatar
Fred Drake committed
885 886 887 888
        if host:
            host, port = splitport(host)
        if not host or \
           (not port and socket.gethostbyname(host) in self.get_names()):
889
            return addinfourl(open(localfile, 'rb'),
Fred Drake's avatar
Fred Drake committed
890 891
                              headers, 'file:'+file)
        raise URLError('file not on local host')
Jeremy Hylton's avatar
Jeremy Hylton committed
892 893 894

class FTPHandler(BaseHandler):
    def ftp_open(self, req):
Fred Drake's avatar
Fred Drake committed
895 896 897 898
        host = req.get_host()
        if not host:
            raise IOError, ('ftp error', 'no host given')
        # XXX handle custom username & password
Jeremy Hylton's avatar
Jeremy Hylton committed
899 900 901 902
        try:
            host = socket.gethostbyname(host)
        except socket.error, msg:
            raise URLError(msg)
Fred Drake's avatar
Fred Drake committed
903 904 905 906 907
        host, port = splitport(host)
        if port is None:
            port = ftplib.FTP_PORT
        path, attrs = splitattr(req.get_selector())
        path = unquote(path)
Eric S. Raymond's avatar
Eric S. Raymond committed
908
        dirs = path.split('/')
Fred Drake's avatar
Fred Drake committed
909 910 911 912 913 914 915 916 917
        dirs, file = dirs[:-1], dirs[-1]
        if dirs and not dirs[0]:
            dirs = dirs[1:]
        user = passwd = '' # XXX
        try:
            fw = self.connect_ftp(user, passwd, host, port, dirs)
            type = file and 'I' or 'D'
            for attr in attrs:
                attr, value = splitattr(attr)
Eric S. Raymond's avatar
Eric S. Raymond committed
918
                if attr.lower() == 'type' and \
Fred Drake's avatar
Fred Drake committed
919
                   value in ('a', 'A', 'i', 'I', 'd', 'D'):
Eric S. Raymond's avatar
Eric S. Raymond committed
920
                    type = value.upper()
Fred Drake's avatar
Fred Drake committed
921
            fp, retrlen = fw.retrfile(file, type)
922 923 924 925
            headers = ""
            mtype = mimetypes.guess_type(req.get_full_url())[0]
            if mtype:
                headers += "Content-Type: %s\n" % mtype
Fred Drake's avatar
Fred Drake committed
926
            if retrlen is not None and retrlen >= 0:
927 928 929
                headers += "Content-Length: %d\n" % retrlen
            sf = StringIO(headers)
            headers = mimetools.Message(sf)
Fred Drake's avatar
Fred Drake committed
930 931 932
            return addinfourl(fp, headers, req.get_full_url())
        except ftplib.all_errors, msg:
            raise IOError, ('ftp error', msg), sys.exc_info()[2]
Jeremy Hylton's avatar
Jeremy Hylton committed
933 934 935 936 937 938 939 940 941 942 943 944 945 946

    def connect_ftp(self, user, passwd, host, port, dirs):
        fw = ftpwrapper(user, passwd, host, port, dirs)
##        fw.ftp.set_debuglevel(1)
        return fw

class CacheFTPHandler(FTPHandler):
    # XXX would be nice to have pluggable cache strategies
    # XXX this stuff is definitely not thread safe
    def __init__(self):
        self.cache = {}
        self.timeout = {}
        self.soonest = 0
        self.delay = 60
Fred Drake's avatar
Fred Drake committed
947
        self.max_conns = 16
Jeremy Hylton's avatar
Jeremy Hylton committed
948 949 950 951 952

    def setTimeout(self, t):
        self.delay = t

    def setMaxConns(self, m):
Fred Drake's avatar
Fred Drake committed
953
        self.max_conns = m
Jeremy Hylton's avatar
Jeremy Hylton committed
954 955 956 957 958 959 960 961

    def connect_ftp(self, user, passwd, host, port, dirs):
        key = user, passwd, host, port
        if self.cache.has_key(key):
            self.timeout[key] = time.time() + self.delay
        else:
            self.cache[key] = ftpwrapper(user, passwd, host, port, dirs)
            self.timeout[key] = time.time() + self.delay
Fred Drake's avatar
Fred Drake committed
962
        self.check_cache()
Jeremy Hylton's avatar
Jeremy Hylton committed
963 964 965
        return self.cache[key]

    def check_cache(self):
Fred Drake's avatar
Fred Drake committed
966
        # first check for old ones
Jeremy Hylton's avatar
Jeremy Hylton committed
967 968 969 970 971 972 973 974 975 976
        t = time.time()
        if self.soonest <= t:
            for k, v in self.timeout.items():
                if v < t:
                    self.cache[k].close()
                    del self.cache[k]
                    del self.timeout[k]
        self.soonest = min(self.timeout.values())

        # then check the size
Fred Drake's avatar
Fred Drake committed
977 978 979 980 981 982 983
        if len(self.cache) == self.max_conns:
            for k, v in self.timeout.items():
                if v == self.soonest:
                    del self.cache[k]
                    del self.timeout[k]
                    break
            self.soonest = min(self.timeout.values())
Jeremy Hylton's avatar
Jeremy Hylton committed
984 985 986

class GopherHandler(BaseHandler):
    def gopher_open(self, req):
Fred Drake's avatar
Fred Drake committed
987 988 989 990 991 992 993 994 995 996 997 998 999 1000
        host = req.get_host()
        if not host:
            raise GopherError('no host given')
        host = unquote(host)
        selector = req.get_selector()
        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(), req.get_full_url())
Jeremy Hylton's avatar
Jeremy Hylton committed
1001 1002 1003 1004 1005

#bleck! don't use this yet
class OpenerFactory:

    default_handlers = [UnknownHandler, HTTPHandler,
1006
                        HTTPDefaultErrorHandler, HTTPRedirectHandler,
Fred Drake's avatar
Fred Drake committed
1007
                        FTPHandler, FileHandler]
Jeremy Hylton's avatar
Jeremy Hylton committed
1008 1009 1010 1011 1012
    proxy_handlers = [ProxyHandler]
    handlers = []
    replacement_handlers = []

    def add_proxy_handler(self, ph):
Fred Drake's avatar
Fred Drake committed
1013
        self.proxy_handlers = self.proxy_handlers + [ph]
Jeremy Hylton's avatar
Jeremy Hylton committed
1014 1015

    def add_handler(self, h):
Fred Drake's avatar
Fred Drake committed
1016
        self.handlers = self.handlers + [h]
Jeremy Hylton's avatar
Jeremy Hylton committed
1017 1018

    def replace_handler(self, h):
Fred Drake's avatar
Fred Drake committed
1019
        pass
Jeremy Hylton's avatar
Jeremy Hylton committed
1020 1021

    def build_opener(self):
1022
        opener = OpenerDirector()
Fred Drake's avatar
Fred Drake committed
1023
        for ph in self.proxy_handlers:
1024
            if inspect.isclass(ph):
Fred Drake's avatar
Fred Drake committed
1025 1026
                ph = ph()
            opener.add_handler(ph)
Jeremy Hylton's avatar
Jeremy Hylton committed
1027 1028

if __name__ == "__main__":
1029
    # XXX some of the test code depends on machine configurations that
Jeremy Hylton's avatar
Jeremy Hylton committed
1030 1031 1032 1033
    # are internal to CNRI.   Need to set up a public server with the
    # right authentication configuration for test purposes.
    if socket.gethostname() == 'bitdiddle':
        localhost = 'bitdiddle.cnri.reston.va.us'
Jeremy Hylton's avatar
Jeremy Hylton committed
1034
    elif socket.gethostname() == 'bitdiddle.concentric.net':
Jeremy Hylton's avatar
Jeremy Hylton committed
1035 1036 1037 1038
        localhost = 'localhost'
    else:
        localhost = None
    urls = [
Fred Drake's avatar
Fred Drake committed
1039 1040 1041
        # Thanks to Fred for finding these!
        'gopher://gopher.lib.ncsu.edu/11/library/stacks/Alex',
        'gopher://gopher.vt.edu:10010/10/33',
Jeremy Hylton's avatar
Jeremy Hylton committed
1042

Fred Drake's avatar
Fred Drake committed
1043 1044
        'file:/etc/passwd',
        'file://nonsensename/etc/passwd',
1045
        'ftp://www.python.org/pub/python/misc/sousa.au',
Jeremy Hylton's avatar
Jeremy Hylton committed
1046
        'ftp://www.python.org/pub/tmp/blat',
Fred Drake's avatar
Fred Drake committed
1047 1048
        'http://www.espn.com/', # redirect
        'http://www.python.org/Spanish/Inquistion/',
1049
        ('http://www.python.org/cgi-bin/faqw.py',
Fred Drake's avatar
Fred Drake committed
1050 1051
         'query=pythonistas&querytype=simple&casefold=yes&req=search'),
        'http://www.python.org/',
1052
        'ftp://gatekeeper.research.compaq.com/pub/DEC/SRC/research-reports/00README-Legal-Rules-Regs',
Jeremy Hylton's avatar
Jeremy Hylton committed
1053 1054
            ]

1055 1056 1057 1058 1059 1060 1061
##    if localhost is not None:
##        urls = urls + [
##            'file://%s/etc/passwd' % localhost,
##            'http://%s/simple/' % localhost,
##            'http://%s/digest/' % localhost,
##            'http://%s/not/found.h' % localhost,
##            ]
Jeremy Hylton's avatar
Jeremy Hylton committed
1062

1063 1064 1065 1066 1067 1068
##        bauth = HTTPBasicAuthHandler()
##        bauth.add_password('basic_test_realm', localhost, 'jhylton',
##                           'password')
##        dauth = HTTPDigestAuthHandler()
##        dauth.add_password('digest_test_realm', localhost, 'jhylton',
##                           'password')
1069

Jeremy Hylton's avatar
Jeremy Hylton committed
1070 1071 1072 1073

    cfh = CacheFTPHandler()
    cfh.setTimeout(1)

1074 1075 1076 1077 1078 1079 1080 1081 1082 1083 1084
##    # XXX try out some custom proxy objects too!
##    def at_cnri(req):
##        host = req.get_host()
##        print host
##        if host[-18:] == '.cnri.reston.va.us':
##            return 1
##    p = CustomProxy('http', at_cnri, 'proxy.cnri.reston.va.us')
##    ph = CustomProxyHandler(p)

##    install_opener(build_opener(dauth, bauth, cfh, GopherHandler, ph))
    install_opener(build_opener(cfh, GopherHandler))
Jeremy Hylton's avatar
Jeremy Hylton committed
1085 1086

    for url in urls:
1087
        if isinstance(url, types.TupleType):
Jeremy Hylton's avatar
Jeremy Hylton committed
1088 1089 1090 1091 1092 1093 1094
            url, req = url
        else:
            req = None
        print url
        try:
            f = urlopen(url, req)
        except IOError, err:
Fred Drake's avatar
Fred Drake committed
1095 1096 1097
            print "IOError:", err
        except socket.error, err:
            print "socket.error:", err
Jeremy Hylton's avatar
Jeremy Hylton committed
1098 1099 1100 1101 1102 1103
        else:
            buf = f.read()
            f.close()
            print "read %d bytes" % len(buf)
        print
        time.sleep(0.1)