blob: e79acf02d98806a08cb6e49997fd35e93f0b1b5a [file] [log] [blame]
Guido van Rossume7b146f2000-02-04 15:28:42 +00001"""Open an arbitrary URL.
2
3See the following document for more info on URLs:
4"Names and Addresses, URIs, URLs, URNs, URCs", at
5http://www.w3.org/pub/WWW/Addressing/Overview.html
6
7See also the HTTP spec (from which the error codes are derived):
8"HTTP - Hypertext Transfer Protocol", at
9http://www.w3.org/pub/WWW/Protocols/
10
11Related standards and specs:
12- RFC1808: the "relative URL" spec. (authoritative status)
13- RFC1738 - the "URL standard". (authoritative status)
14- RFC1630 - the "URI spec". (informational status)
15
16The object returned by URLopener().open(file) will differ per
17protocol. All you know is that is has methods read(), readline(),
18readlines(), fileno(), close() and info(). The read*(), fileno()
Fredrik Lundhb49f88b2000-09-24 18:51:25 +000019and close() methods work like those of open files.
Guido van Rossume7b146f2000-02-04 15:28:42 +000020The info() method returns a mimetools.Message object which can be
21used to query various info about the object, if available.
22(mimetools.Message objects are queried with the getheader() method.)
23"""
Guido van Rossum7c6ebb51994-03-22 12:05:32 +000024
Guido van Rossum7c395db1994-07-04 22:14:49 +000025import string
Guido van Rossum7c6ebb51994-03-22 12:05:32 +000026import socket
Jack Jansendc3e3f61995-12-15 13:22:13 +000027import os
Guido van Rossum3c8484e1996-11-20 22:02:24 +000028import sys
Martin v. Löwis1d994332000-12-03 18:30:10 +000029import types
Guido van Rossum7c6ebb51994-03-22 12:05:32 +000030
Guido van Rossumb2493f82000-12-15 15:01:37 +000031__version__ = '1.15' # XXX This version is not always updated :-(
Guido van Rossumf668d171997-06-06 21:11:11 +000032
Jeremy Hyltonf90b0021999-02-25 16:12:12 +000033MAXFTPCACHE = 10 # Trim the ftp cache beyond this size
Guido van Rossum6cb15a01995-06-22 19:00:13 +000034
Jack Jansendc3e3f61995-12-15 13:22:13 +000035# Helper for non-unix systems
36if os.name == 'mac':
Jeremy Hyltonf90b0021999-02-25 16:12:12 +000037 from macurl2path import url2pathname, pathname2url
Guido van Rossum7e7ca0b1998-03-26 21:01:39 +000038elif os.name == 'nt':
Fredrik Lundhb49f88b2000-09-24 18:51:25 +000039 from nturl2path import url2pathname, pathname2url
Jack Jansendc3e3f61995-12-15 13:22:13 +000040else:
Jeremy Hyltonf90b0021999-02-25 16:12:12 +000041 def url2pathname(pathname):
Guido van Rossum367ac801999-03-12 14:31:10 +000042 return unquote(pathname)
Jeremy Hyltonf90b0021999-02-25 16:12:12 +000043 def pathname2url(pathname):
Guido van Rossum367ac801999-03-12 14:31:10 +000044 return quote(pathname)
Guido van Rossum33add0a1998-12-18 15:25:22 +000045
Guido van Rossum7c6ebb51994-03-22 12:05:32 +000046# This really consists of two pieces:
47# (1) a class which handles opening of all sorts of URLs
48# (plus assorted utilities etc.)
49# (2) a set of functions for parsing URLs
50# XXX Should these be separated out into different modules?
51
52
53# Shortcut for basic usage
54_urlopener = None
Guido van Rossumbd013741996-12-10 16:00:28 +000055def urlopen(url, data=None):
Skip Montanaro79f1c172000-08-22 03:00:52 +000056 """urlopen(url [, data]) -> open file-like object"""
Jeremy Hyltonf90b0021999-02-25 16:12:12 +000057 global _urlopener
58 if not _urlopener:
59 _urlopener = FancyURLopener()
60 if data is None:
61 return _urlopener.open(url)
62 else:
63 return _urlopener.open(url, data)
Fred Drake316a7932000-08-24 01:01:26 +000064def urlretrieve(url, filename=None, reporthook=None, data=None):
Jeremy Hyltonf90b0021999-02-25 16:12:12 +000065 global _urlopener
66 if not _urlopener:
67 _urlopener = FancyURLopener()
Fred Drake316a7932000-08-24 01:01:26 +000068 return _urlopener.retrieve(url, filename, reporthook, data)
Guido van Rossum7c6ebb51994-03-22 12:05:32 +000069def urlcleanup():
Jeremy Hyltonf90b0021999-02-25 16:12:12 +000070 if _urlopener:
71 _urlopener.cleanup()
Guido van Rossum7c6ebb51994-03-22 12:05:32 +000072
73
Guido van Rossum7c6ebb51994-03-22 12:05:32 +000074ftpcache = {}
75class URLopener:
Guido van Rossume7b146f2000-02-04 15:28:42 +000076 """Class to open URLs.
77 This is a class rather than just a subroutine because we may need
78 more than one set of global protocol-specific options.
79 Note -- this is a base class for those who don't want the
80 automatic handling of errors type 302 (relocated) and 401
81 (authorization needed)."""
Guido van Rossum7c6ebb51994-03-22 12:05:32 +000082
Jeremy Hyltonf90b0021999-02-25 16:12:12 +000083 __tempfiles = None
Guido van Rossum29e77811996-11-27 19:39:58 +000084
Guido van Rossumba311382000-08-24 16:18:04 +000085 version = "Python-urllib/%s" % __version__
86
Jeremy Hyltonf90b0021999-02-25 16:12:12 +000087 # Constructor
Guido van Rossum09c8b6c1999-12-07 21:37:17 +000088 def __init__(self, proxies=None, **x509):
Jeremy Hyltonf90b0021999-02-25 16:12:12 +000089 if proxies is None:
90 proxies = getproxies()
91 assert hasattr(proxies, 'has_key'), "proxies must be a mapping"
92 self.proxies = proxies
Guido van Rossum09c8b6c1999-12-07 21:37:17 +000093 self.key_file = x509.get('key_file')
94 self.cert_file = x509.get('cert_file')
Guido van Rossumba311382000-08-24 16:18:04 +000095 self.addheaders = [('User-agent', self.version)]
Jeremy Hyltonf90b0021999-02-25 16:12:12 +000096 self.__tempfiles = []
97 self.__unlink = os.unlink # See cleanup()
98 self.tempcache = None
99 # Undocumented feature: if you assign {} to tempcache,
100 # it is used to cache files retrieved with
101 # self.retrieve(). This is not enabled by default
102 # since it does not work for changing documents (and I
103 # haven't got the logic to check expiration headers
104 # yet).
105 self.ftpcache = ftpcache
106 # Undocumented feature: you can use a different
107 # ftp cache by assigning to the .ftpcache member;
108 # in case you want logically independent URL openers
109 # XXX This is not threadsafe. Bah.
Guido van Rossum7c6ebb51994-03-22 12:05:32 +0000110
Jeremy Hyltonf90b0021999-02-25 16:12:12 +0000111 def __del__(self):
112 self.close()
Guido van Rossum7c6ebb51994-03-22 12:05:32 +0000113
Jeremy Hyltonf90b0021999-02-25 16:12:12 +0000114 def close(self):
115 self.cleanup()
Guido van Rossum7c6ebb51994-03-22 12:05:32 +0000116
Jeremy Hyltonf90b0021999-02-25 16:12:12 +0000117 def cleanup(self):
118 # This code sometimes runs when the rest of this module
119 # has already been deleted, so it can't use any globals
120 # or import anything.
121 if self.__tempfiles:
122 for file in self.__tempfiles:
123 try:
124 self.__unlink(file)
125 except:
126 pass
127 del self.__tempfiles[:]
128 if self.tempcache:
129 self.tempcache.clear()
Guido van Rossum7c6ebb51994-03-22 12:05:32 +0000130
Jeremy Hyltonf90b0021999-02-25 16:12:12 +0000131 def addheader(self, *args):
Guido van Rossume7b146f2000-02-04 15:28:42 +0000132 """Add a header to be used by the HTTP interface only
133 e.g. u.addheader('Accept', 'sound/basic')"""
Jeremy Hyltonf90b0021999-02-25 16:12:12 +0000134 self.addheaders.append(args)
Guido van Rossum7c6ebb51994-03-22 12:05:32 +0000135
Jeremy Hyltonf90b0021999-02-25 16:12:12 +0000136 # External interface
Jeremy Hyltonf90b0021999-02-25 16:12:12 +0000137 def open(self, fullurl, data=None):
Guido van Rossume7b146f2000-02-04 15:28:42 +0000138 """Use URLopener().open(file) instead of open(file, 'r')."""
Martin v. Löwis1d994332000-12-03 18:30:10 +0000139 fullurl = unwrap(toBytes(fullurl))
Jeremy Hyltonf90b0021999-02-25 16:12:12 +0000140 if self.tempcache and self.tempcache.has_key(fullurl):
141 filename, headers = self.tempcache[fullurl]
142 fp = open(filename, 'rb')
143 return addinfourl(fp, headers, fullurl)
Martin v. Löwis1d994332000-12-03 18:30:10 +0000144 urltype, url = splittype(fullurl)
145 if not urltype:
146 urltype = 'file'
147 if self.proxies.has_key(urltype):
148 proxy = self.proxies[urltype]
149 urltype, proxyhost = splittype(proxy)
Jeremy Hyltond52755f2000-10-02 23:04:02 +0000150 host, selector = splithost(proxyhost)
Jeremy Hyltonf90b0021999-02-25 16:12:12 +0000151 url = (host, fullurl) # Signal special case to open_*()
Jeremy Hyltond52755f2000-10-02 23:04:02 +0000152 else:
153 proxy = None
Martin v. Löwis1d994332000-12-03 18:30:10 +0000154 name = 'open_' + urltype
155 self.type = urltype
Jeremy Hyltonf90b0021999-02-25 16:12:12 +0000156 if '-' in name:
157 # replace - with _
Guido van Rossumb2493f82000-12-15 15:01:37 +0000158 name = '_'.join(name.split('-'))
Jeremy Hyltonf90b0021999-02-25 16:12:12 +0000159 if not hasattr(self, name):
Jeremy Hyltond52755f2000-10-02 23:04:02 +0000160 if proxy:
161 return self.open_unknown_proxy(proxy, fullurl, data)
Jeremy Hyltonf90b0021999-02-25 16:12:12 +0000162 else:
163 return self.open_unknown(fullurl, data)
164 try:
165 if data is None:
166 return getattr(self, name)(url)
167 else:
168 return getattr(self, name)(url, data)
169 except socket.error, msg:
170 raise IOError, ('socket error', msg), sys.exc_info()[2]
Guido van Rossum7c6ebb51994-03-22 12:05:32 +0000171
Jeremy Hyltonf90b0021999-02-25 16:12:12 +0000172 def open_unknown(self, fullurl, data=None):
Guido van Rossume7b146f2000-02-04 15:28:42 +0000173 """Overridable interface to open unknown URL type."""
Jeremy Hyltonf90b0021999-02-25 16:12:12 +0000174 type, url = splittype(fullurl)
175 raise IOError, ('url error', 'unknown url type', type)
Guido van Rossumca445401995-08-29 19:19:12 +0000176
Jeremy Hyltond52755f2000-10-02 23:04:02 +0000177 def open_unknown_proxy(self, proxy, fullurl, data=None):
178 """Overridable interface to open unknown URL type."""
179 type, url = splittype(fullurl)
180 raise IOError, ('url error', 'invalid proxy for %s' % type, proxy)
181
Jeremy Hyltonf90b0021999-02-25 16:12:12 +0000182 # External interface
Sjoerd Mullenderd7b86f02000-08-25 11:23:36 +0000183 def retrieve(self, url, filename=None, reporthook=None, data=None):
Guido van Rossume7b146f2000-02-04 15:28:42 +0000184 """retrieve(url) returns (filename, None) for a local object
185 or (tempfilename, headers) for a remote object."""
Martin v. Löwis1d994332000-12-03 18:30:10 +0000186 url = unwrap(toBytes(url))
Jeremy Hyltonf90b0021999-02-25 16:12:12 +0000187 if self.tempcache and self.tempcache.has_key(url):
188 return self.tempcache[url]
189 type, url1 = splittype(url)
190 if not filename and (not type or type == 'file'):
191 try:
192 fp = self.open_local_file(url1)
193 hdrs = fp.info()
194 del fp
195 return url2pathname(splithost(url1)[1]), hdrs
196 except IOError, msg:
197 pass
Fred Drake316a7932000-08-24 01:01:26 +0000198 fp = self.open(url, data)
Jeremy Hyltonf90b0021999-02-25 16:12:12 +0000199 headers = fp.info()
200 if not filename:
201 import tempfile
202 garbage, path = splittype(url)
203 garbage, path = splithost(path or "")
204 path, garbage = splitquery(path or "")
205 path, garbage = splitattr(path or "")
206 suffix = os.path.splitext(path)[1]
207 filename = tempfile.mktemp(suffix)
208 self.__tempfiles.append(filename)
209 result = filename, headers
210 if self.tempcache is not None:
211 self.tempcache[url] = result
212 tfp = open(filename, 'wb')
213 bs = 1024*8
214 size = -1
215 blocknum = 1
216 if reporthook:
217 if headers.has_key("content-length"):
218 size = int(headers["Content-Length"])
219 reporthook(0, bs, size)
220 block = fp.read(bs)
221 if reporthook:
222 reporthook(1, bs, size)
223 while block:
224 tfp.write(block)
225 block = fp.read(bs)
226 blocknum = blocknum + 1
227 if reporthook:
228 reporthook(blocknum, bs, size)
229 fp.close()
230 tfp.close()
231 del fp
232 del tfp
233 return result
Guido van Rossum7c6ebb51994-03-22 12:05:32 +0000234
Jeremy Hyltonf90b0021999-02-25 16:12:12 +0000235 # Each method named open_<type> knows how to open that type of URL
Guido van Rossum7c6ebb51994-03-22 12:05:32 +0000236
Jeremy Hyltonf90b0021999-02-25 16:12:12 +0000237 def open_http(self, url, data=None):
Guido van Rossume7b146f2000-02-04 15:28:42 +0000238 """Use HTTP protocol."""
Jeremy Hyltonf90b0021999-02-25 16:12:12 +0000239 import httplib
240 user_passwd = None
Martin v. Löwis1d994332000-12-03 18:30:10 +0000241 if type(url) is types.StringType:
Jeremy Hyltonf90b0021999-02-25 16:12:12 +0000242 host, selector = splithost(url)
243 if host:
244 user_passwd, host = splituser(host)
245 host = unquote(host)
246 realhost = host
247 else:
248 host, selector = url
249 urltype, rest = splittype(selector)
250 url = rest
251 user_passwd = None
Guido van Rossumb2493f82000-12-15 15:01:37 +0000252 if urltype.lower() != 'http':
Jeremy Hyltonf90b0021999-02-25 16:12:12 +0000253 realhost = None
254 else:
255 realhost, rest = splithost(rest)
256 if realhost:
257 user_passwd, realhost = splituser(realhost)
258 if user_passwd:
259 selector = "%s://%s%s" % (urltype, realhost, rest)
260 #print "proxy via http:", host, selector
261 if not host: raise IOError, ('http error', 'no host given')
262 if user_passwd:
263 import base64
Guido van Rossumb2493f82000-12-15 15:01:37 +0000264 auth = base64.encodestring(user_passwd).strip()
Jeremy Hyltonf90b0021999-02-25 16:12:12 +0000265 else:
266 auth = None
267 h = httplib.HTTP(host)
268 if data is not None:
269 h.putrequest('POST', selector)
270 h.putheader('Content-type', 'application/x-www-form-urlencoded')
271 h.putheader('Content-length', '%d' % len(data))
272 else:
273 h.putrequest('GET', selector)
274 if auth: h.putheader('Authorization', 'Basic %s' % auth)
275 if realhost: h.putheader('Host', realhost)
276 for args in self.addheaders: apply(h.putheader, args)
277 h.endheaders()
278 if data is not None:
279 h.send(data + '\r\n')
280 errcode, errmsg, headers = h.getreply()
281 fp = h.getfile()
282 if errcode == 200:
283 return addinfourl(fp, headers, "http:" + url)
284 else:
285 if data is None:
Jeremy Hyltonf90b0021999-02-25 16:12:12 +0000286 return self.http_error(url, fp, errcode, errmsg, headers)
Guido van Rossum29aab751999-03-09 19:31:21 +0000287 else:
288 return self.http_error(url, fp, errcode, errmsg, headers, data)
Guido van Rossumbbb0a051995-08-04 04:29:05 +0000289
Jeremy Hyltonf90b0021999-02-25 16:12:12 +0000290 def http_error(self, url, fp, errcode, errmsg, headers, data=None):
Guido van Rossume7b146f2000-02-04 15:28:42 +0000291 """Handle http errors.
292 Derived class can override this, or provide specific handlers
293 named http_error_DDD where DDD is the 3-digit error code."""
Jeremy Hyltonf90b0021999-02-25 16:12:12 +0000294 # First check if there's a specific handler for this error
295 name = 'http_error_%d' % errcode
296 if hasattr(self, name):
297 method = getattr(self, name)
298 if data is None:
Jeremy Hyltonf90b0021999-02-25 16:12:12 +0000299 result = method(url, fp, errcode, errmsg, headers)
Jeremy Hyltonb30f52a1999-02-25 16:14:58 +0000300 else:
301 result = method(url, fp, errcode, errmsg, headers, data)
Jeremy Hyltonf90b0021999-02-25 16:12:12 +0000302 if result: return result
Jeremy Hyltonb30f52a1999-02-25 16:14:58 +0000303 return self.http_error_default(url, fp, errcode, errmsg, headers)
Guido van Rossumbbb0a051995-08-04 04:29:05 +0000304
Jeremy Hyltonf90b0021999-02-25 16:12:12 +0000305 def http_error_default(self, url, fp, errcode, errmsg, headers):
Guido van Rossume7b146f2000-02-04 15:28:42 +0000306 """Default error handler: close the connection and raise IOError."""
Jeremy Hyltonf90b0021999-02-25 16:12:12 +0000307 void = fp.read()
308 fp.close()
309 raise IOError, ('http error', errcode, errmsg, headers)
Guido van Rossum7c6ebb51994-03-22 12:05:32 +0000310
Guido van Rossum09c8b6c1999-12-07 21:37:17 +0000311 if hasattr(socket, "ssl"):
Andrew M. Kuchling141e9892000-04-23 02:53:11 +0000312 def open_https(self, url, data=None):
Guido van Rossume7b146f2000-02-04 15:28:42 +0000313 """Use HTTPS protocol."""
Guido van Rossum09c8b6c1999-12-07 21:37:17 +0000314 import httplib
Fred Drake567ca8e2000-08-21 21:42:42 +0000315 user_passwd = None
Moshe Zadkab2a0a832001-01-08 07:09:25 +0000316 if type(url) is types.StringType:
Guido van Rossum09c8b6c1999-12-07 21:37:17 +0000317 host, selector = splithost(url)
Fred Drake567ca8e2000-08-21 21:42:42 +0000318 if host:
319 user_passwd, host = splituser(host)
320 host = unquote(host)
321 realhost = host
Guido van Rossum09c8b6c1999-12-07 21:37:17 +0000322 else:
323 host, selector = url
324 urltype, rest = splittype(selector)
Fred Drake567ca8e2000-08-21 21:42:42 +0000325 url = rest
326 user_passwd = None
Guido van Rossumb2493f82000-12-15 15:01:37 +0000327 if urltype.lower() != 'https':
Fred Drake567ca8e2000-08-21 21:42:42 +0000328 realhost = None
329 else:
Guido van Rossum09c8b6c1999-12-07 21:37:17 +0000330 realhost, rest = splithost(rest)
Fred Drake567ca8e2000-08-21 21:42:42 +0000331 if realhost:
332 user_passwd, realhost = splituser(realhost)
Guido van Rossum09c8b6c1999-12-07 21:37:17 +0000333 if user_passwd:
334 selector = "%s://%s%s" % (urltype, realhost, rest)
Andrew M. Kuchling7ad47922000-06-10 01:41:48 +0000335 #print "proxy via https:", host, selector
Guido van Rossum09c8b6c1999-12-07 21:37:17 +0000336 if not host: raise IOError, ('https error', 'no host given')
337 if user_passwd:
338 import base64
Guido van Rossumb2493f82000-12-15 15:01:37 +0000339 auth = base64.encodestring(user_passwd).strip()
Guido van Rossum09c8b6c1999-12-07 21:37:17 +0000340 else:
341 auth = None
342 h = httplib.HTTPS(host, 0,
343 key_file=self.key_file,
344 cert_file=self.cert_file)
Andrew M. Kuchling141e9892000-04-23 02:53:11 +0000345 if data is not None:
346 h.putrequest('POST', selector)
347 h.putheader('Content-type',
348 'application/x-www-form-urlencoded')
349 h.putheader('Content-length', '%d' % len(data))
350 else:
351 h.putrequest('GET', selector)
Guido van Rossum09c8b6c1999-12-07 21:37:17 +0000352 if auth: h.putheader('Authorization: Basic %s' % auth)
Fred Drake567ca8e2000-08-21 21:42:42 +0000353 if realhost: h.putheader('Host', realhost)
Guido van Rossum09c8b6c1999-12-07 21:37:17 +0000354 for args in self.addheaders: apply(h.putheader, args)
355 h.endheaders()
Andrew M. Kuchling43c5af02000-04-24 14:17:06 +0000356 if data is not None:
357 h.send(data + '\r\n')
Guido van Rossum09c8b6c1999-12-07 21:37:17 +0000358 errcode, errmsg, headers = h.getreply()
359 fp = h.getfile()
360 if errcode == 200:
361 return addinfourl(fp, headers, url)
362 else:
Fred Drake567ca8e2000-08-21 21:42:42 +0000363 if data is None:
364 return self.http_error(url, fp, errcode, errmsg, headers)
365 else:
Guido van Rossumb2493f82000-12-15 15:01:37 +0000366 return self.http_error(url, fp, errcode, errmsg, headers,
367 data)
Fred Drake567ca8e2000-08-21 21:42:42 +0000368
Jeremy Hyltonf90b0021999-02-25 16:12:12 +0000369 def open_gopher(self, url):
Guido van Rossume7b146f2000-02-04 15:28:42 +0000370 """Use Gopher protocol."""
Jeremy Hyltonf90b0021999-02-25 16:12:12 +0000371 import gopherlib
372 host, selector = splithost(url)
373 if not host: raise IOError, ('gopher error', 'no host given')
374 host = unquote(host)
375 type, selector = splitgophertype(selector)
376 selector, query = splitquery(selector)
377 selector = unquote(selector)
378 if query:
379 query = unquote(query)
380 fp = gopherlib.send_query(selector, query, host)
381 else:
382 fp = gopherlib.send_selector(selector, host)
383 return addinfourl(fp, noheaders(), "gopher:" + url)
Guido van Rossum7c6ebb51994-03-22 12:05:32 +0000384
Jeremy Hyltonf90b0021999-02-25 16:12:12 +0000385 def open_file(self, url):
Guido van Rossume7b146f2000-02-04 15:28:42 +0000386 """Use local file or FTP depending on form of URL."""
Jeremy Hyltonf90b0021999-02-25 16:12:12 +0000387 if url[:2] == '//' and url[2:3] != '/':
388 return self.open_ftp(url)
389 else:
390 return self.open_local_file(url)
Guido van Rossum7c6ebb51994-03-22 12:05:32 +0000391
Jeremy Hyltonf90b0021999-02-25 16:12:12 +0000392 def open_local_file(self, url):
Guido van Rossume7b146f2000-02-04 15:28:42 +0000393 """Use local file."""
Jeremy Hyltonf90b0021999-02-25 16:12:12 +0000394 import mimetypes, mimetools, StringIO
395 mtype = mimetypes.guess_type(url)[0]
396 headers = mimetools.Message(StringIO.StringIO(
397 'Content-Type: %s\n' % (mtype or 'text/plain')))
398 host, file = splithost(url)
399 if not host:
Guido van Rossum336a2011999-06-24 15:27:36 +0000400 urlfile = file
401 if file[:1] == '/':
402 urlfile = 'file://' + file
Jeremy Hyltonf90b0021999-02-25 16:12:12 +0000403 return addinfourl(open(url2pathname(file), 'rb'),
Guido van Rossum336a2011999-06-24 15:27:36 +0000404 headers, urlfile)
Jeremy Hyltonf90b0021999-02-25 16:12:12 +0000405 host, port = splitport(host)
406 if not port \
Fredrik Lundhb49f88b2000-09-24 18:51:25 +0000407 and socket.gethostbyname(host) in (localhost(), thishost()):
Guido van Rossum336a2011999-06-24 15:27:36 +0000408 urlfile = file
409 if file[:1] == '/':
410 urlfile = 'file://' + file
Jeremy Hyltonf90b0021999-02-25 16:12:12 +0000411 return addinfourl(open(url2pathname(file), 'rb'),
Guido van Rossum336a2011999-06-24 15:27:36 +0000412 headers, urlfile)
Jeremy Hyltonf90b0021999-02-25 16:12:12 +0000413 raise IOError, ('local file error', 'not on local host')
Guido van Rossum7c6ebb51994-03-22 12:05:32 +0000414
Jeremy Hyltonf90b0021999-02-25 16:12:12 +0000415 def open_ftp(self, url):
Guido van Rossume7b146f2000-02-04 15:28:42 +0000416 """Use FTP protocol."""
Jeremy Hyltonf90b0021999-02-25 16:12:12 +0000417 host, path = splithost(url)
418 if not host: raise IOError, ('ftp error', 'no host given')
419 host, port = splitport(host)
420 user, host = splituser(host)
421 if user: user, passwd = splitpasswd(user)
422 else: passwd = None
423 host = unquote(host)
424 user = unquote(user or '')
425 passwd = unquote(passwd or '')
426 host = socket.gethostbyname(host)
427 if not port:
428 import ftplib
429 port = ftplib.FTP_PORT
430 else:
431 port = int(port)
432 path, attrs = splitattr(path)
433 path = unquote(path)
Guido van Rossumb2493f82000-12-15 15:01:37 +0000434 dirs = path.split('/')
Jeremy Hyltonf90b0021999-02-25 16:12:12 +0000435 dirs, file = dirs[:-1], dirs[-1]
436 if dirs and not dirs[0]: dirs = dirs[1:]
Guido van Rossum5e006a31999-08-18 17:40:33 +0000437 if dirs and not dirs[0]: dirs[0] = '/'
Guido van Rossumb2493f82000-12-15 15:01:37 +0000438 key = user, host, port, '/'.join(dirs)
Jeremy Hyltonf90b0021999-02-25 16:12:12 +0000439 # XXX thread unsafe!
440 if len(self.ftpcache) > MAXFTPCACHE:
441 # Prune the cache, rather arbitrarily
442 for k in self.ftpcache.keys():
443 if k != key:
444 v = self.ftpcache[k]
445 del self.ftpcache[k]
446 v.close()
447 try:
448 if not self.ftpcache.has_key(key):
449 self.ftpcache[key] = \
450 ftpwrapper(user, passwd, host, port, dirs)
451 if not file: type = 'D'
452 else: type = 'I'
453 for attr in attrs:
454 attr, value = splitvalue(attr)
Guido van Rossumb2493f82000-12-15 15:01:37 +0000455 if attr.lower() == 'type' and \
Jeremy Hyltonf90b0021999-02-25 16:12:12 +0000456 value in ('a', 'A', 'i', 'I', 'd', 'D'):
Guido van Rossumb2493f82000-12-15 15:01:37 +0000457 type = value.upper()
Jeremy Hyltonf90b0021999-02-25 16:12:12 +0000458 (fp, retrlen) = self.ftpcache[key].retrfile(file, type)
459 if retrlen is not None and retrlen >= 0:
460 import mimetools, StringIO
461 headers = mimetools.Message(StringIO.StringIO(
462 'Content-Length: %d\n' % retrlen))
463 else:
464 headers = noheaders()
465 return addinfourl(fp, headers, "ftp:" + url)
466 except ftperrors(), msg:
467 raise IOError, ('ftp error', msg), sys.exc_info()[2]
Guido van Rossum7c6ebb51994-03-22 12:05:32 +0000468
Jeremy Hyltonf90b0021999-02-25 16:12:12 +0000469 def open_data(self, url, data=None):
Guido van Rossume7b146f2000-02-04 15:28:42 +0000470 """Use "data" URL."""
Jeremy Hyltonf90b0021999-02-25 16:12:12 +0000471 # ignore POSTed data
472 #
473 # syntax of data URLs:
474 # dataurl := "data:" [ mediatype ] [ ";base64" ] "," data
475 # mediatype := [ type "/" subtype ] *( ";" parameter )
476 # data := *urlchar
477 # parameter := attribute "=" value
478 import StringIO, mimetools, time
479 try:
Guido van Rossumb2493f82000-12-15 15:01:37 +0000480 [type, data] = url.split(',', 1)
Jeremy Hyltonf90b0021999-02-25 16:12:12 +0000481 except ValueError:
482 raise IOError, ('data error', 'bad data URL')
483 if not type:
484 type = 'text/plain;charset=US-ASCII'
Guido van Rossumb2493f82000-12-15 15:01:37 +0000485 semi = type.rfind(';')
Jeremy Hyltonf90b0021999-02-25 16:12:12 +0000486 if semi >= 0 and '=' not in type[semi:]:
487 encoding = type[semi+1:]
488 type = type[:semi]
489 else:
490 encoding = ''
491 msg = []
492 msg.append('Date: %s'%time.strftime('%a, %d %b %Y %T GMT',
493 time.gmtime(time.time())))
494 msg.append('Content-type: %s' % type)
495 if encoding == 'base64':
496 import base64
497 data = base64.decodestring(data)
498 else:
499 data = unquote(data)
500 msg.append('Content-length: %d' % len(data))
501 msg.append('')
502 msg.append(data)
Guido van Rossumb2493f82000-12-15 15:01:37 +0000503 msg = '\n'.join(msg)
Jeremy Hyltonf90b0021999-02-25 16:12:12 +0000504 f = StringIO.StringIO(msg)
505 headers = mimetools.Message(f, 0)
506 f.fileno = None # needed for addinfourl
507 return addinfourl(f, headers, url)
Guido van Rossum6d4d1c21998-03-12 14:32:55 +0000508
Guido van Rossum7c6ebb51994-03-22 12:05:32 +0000509
Guido van Rossumbbb0a051995-08-04 04:29:05 +0000510class FancyURLopener(URLopener):
Guido van Rossume7b146f2000-02-04 15:28:42 +0000511 """Derived class with handlers for errors we can handle (perhaps)."""
Guido van Rossumbbb0a051995-08-04 04:29:05 +0000512
Jeremy Hyltonf90b0021999-02-25 16:12:12 +0000513 def __init__(self, *args):
514 apply(URLopener.__init__, (self,) + args)
515 self.auth_cache = {}
Guido van Rossumbbb0a051995-08-04 04:29:05 +0000516
Jeremy Hyltonf90b0021999-02-25 16:12:12 +0000517 def http_error_default(self, url, fp, errcode, errmsg, headers):
Guido van Rossume7b146f2000-02-04 15:28:42 +0000518 """Default error handling -- don't raise an exception."""
Jeremy Hyltonf90b0021999-02-25 16:12:12 +0000519 return addinfourl(fp, headers, "http:" + url)
Guido van Rossumbbb0a051995-08-04 04:29:05 +0000520
Fredrik Lundhb49f88b2000-09-24 18:51:25 +0000521 def http_error_302(self, url, fp, errcode, errmsg, headers, data=None):
Guido van Rossume7b146f2000-02-04 15:28:42 +0000522 """Error 302 -- relocated (temporarily)."""
Jeremy Hyltonf90b0021999-02-25 16:12:12 +0000523 # XXX The server can force infinite recursion here!
524 if headers.has_key('location'):
525 newurl = headers['location']
526 elif headers.has_key('uri'):
527 newurl = headers['uri']
528 else:
529 return
530 void = fp.read()
531 fp.close()
Guido van Rossum3527f591999-03-29 20:23:41 +0000532 # In case the server sent a relative URL, join with original:
533 newurl = basejoin("http:" + url, newurl)
Guido van Rossum3c8baed2000-02-01 23:36:55 +0000534 if data is None:
535 return self.open(newurl)
536 else:
537 return self.open(newurl, data)
Guido van Rossumbbb0a051995-08-04 04:29:05 +0000538
Fredrik Lundhb49f88b2000-09-24 18:51:25 +0000539 def http_error_301(self, url, fp, errcode, errmsg, headers, data=None):
Guido van Rossume7b146f2000-02-04 15:28:42 +0000540 """Error 301 -- also relocated (permanently)."""
541 return self.http_error_302(url, fp, errcode, errmsg, headers, data)
Guido van Rossume6ad8911996-09-10 17:02:56 +0000542
Fredrik Lundhb49f88b2000-09-24 18:51:25 +0000543 def http_error_401(self, url, fp, errcode, errmsg, headers, data=None):
Guido van Rossume7b146f2000-02-04 15:28:42 +0000544 """Error 401 -- authentication required.
545 See this URL for a description of the basic authentication scheme:
546 http://www.ics.uci.edu/pub/ietf/http/draft-ietf-http-v10-spec-00.txt"""
Jeremy Hyltonf90b0021999-02-25 16:12:12 +0000547 if headers.has_key('www-authenticate'):
548 stuff = headers['www-authenticate']
549 import re
550 match = re.match('[ \t]*([^ \t]+)[ \t]+realm="([^"]*)"', stuff)
551 if match:
552 scheme, realm = match.groups()
Guido van Rossumb2493f82000-12-15 15:01:37 +0000553 if scheme.lower() == 'basic':
Tim Peterse1190062001-01-15 03:34:38 +0000554 name = 'retry_' + self.type + '_basic_auth'
555 if data is None:
556 return getattr(self,name)(url, realm)
557 else:
558 return getattr(self,name)(url, realm, data)
Guido van Rossumbbb0a051995-08-04 04:29:05 +0000559
Guido van Rossum3c8baed2000-02-01 23:36:55 +0000560 def retry_http_basic_auth(self, url, realm, data=None):
Jeremy Hyltonf90b0021999-02-25 16:12:12 +0000561 host, selector = splithost(url)
Guido van Rossumb2493f82000-12-15 15:01:37 +0000562 i = host.find('@') + 1
Jeremy Hyltonf90b0021999-02-25 16:12:12 +0000563 host = host[i:]
564 user, passwd = self.get_user_passwd(host, realm, i)
565 if not (user or passwd): return None
566 host = user + ':' + passwd + '@' + host
567 newurl = 'http://' + host + selector
Guido van Rossum3c8baed2000-02-01 23:36:55 +0000568 if data is None:
569 return self.open(newurl)
570 else:
571 return self.open(newurl, data)
Fredrik Lundhb49f88b2000-09-24 18:51:25 +0000572
Guido van Rossum3c8baed2000-02-01 23:36:55 +0000573 def retry_https_basic_auth(self, url, realm, data=None):
Tim Peterse1190062001-01-15 03:34:38 +0000574 host, selector = splithost(url)
575 i = host.find('@') + 1
576 host = host[i:]
577 user, passwd = self.get_user_passwd(host, realm, i)
578 if not (user or passwd): return None
579 host = user + ':' + passwd + '@' + host
580 newurl = '//' + host + selector
581 return self.open_https(newurl)
Guido van Rossumbbb0a051995-08-04 04:29:05 +0000582
Jeremy Hyltonf90b0021999-02-25 16:12:12 +0000583 def get_user_passwd(self, host, realm, clear_cache = 0):
Guido van Rossumb2493f82000-12-15 15:01:37 +0000584 key = realm + '@' + host.lower()
Jeremy Hyltonf90b0021999-02-25 16:12:12 +0000585 if self.auth_cache.has_key(key):
586 if clear_cache:
587 del self.auth_cache[key]
588 else:
589 return self.auth_cache[key]
590 user, passwd = self.prompt_user_passwd(host, realm)
591 if user or passwd: self.auth_cache[key] = (user, passwd)
592 return user, passwd
Guido van Rossumbbb0a051995-08-04 04:29:05 +0000593
Jeremy Hyltonf90b0021999-02-25 16:12:12 +0000594 def prompt_user_passwd(self, host, realm):
Guido van Rossume7b146f2000-02-04 15:28:42 +0000595 """Override this in a GUI environment!"""
Jeremy Hyltonf90b0021999-02-25 16:12:12 +0000596 import getpass
597 try:
598 user = raw_input("Enter username for %s at %s: " % (realm,
Fredrik Lundhb49f88b2000-09-24 18:51:25 +0000599 host))
Jeremy Hyltonf90b0021999-02-25 16:12:12 +0000600 passwd = getpass.getpass("Enter password for %s in %s at %s: " %
601 (user, realm, host))
602 return user, passwd
603 except KeyboardInterrupt:
604 print
605 return None, None
Guido van Rossumbbb0a051995-08-04 04:29:05 +0000606
607
Guido van Rossum7c6ebb51994-03-22 12:05:32 +0000608# Utility functions
609
Guido van Rossum7c6ebb51994-03-22 12:05:32 +0000610_localhost = None
611def localhost():
Guido van Rossume7b146f2000-02-04 15:28:42 +0000612 """Return the IP address of the magic hostname 'localhost'."""
Jeremy Hyltonf90b0021999-02-25 16:12:12 +0000613 global _localhost
614 if not _localhost:
615 _localhost = socket.gethostbyname('localhost')
616 return _localhost
Guido van Rossum7c6ebb51994-03-22 12:05:32 +0000617
Guido van Rossum7c6ebb51994-03-22 12:05:32 +0000618_thishost = None
619def thishost():
Guido van Rossume7b146f2000-02-04 15:28:42 +0000620 """Return the IP address of the current host."""
Jeremy Hyltonf90b0021999-02-25 16:12:12 +0000621 global _thishost
622 if not _thishost:
623 _thishost = socket.gethostbyname(socket.gethostname())
624 return _thishost
Guido van Rossum7c6ebb51994-03-22 12:05:32 +0000625
Guido van Rossum7c6ebb51994-03-22 12:05:32 +0000626_ftperrors = None
627def ftperrors():
Guido van Rossume7b146f2000-02-04 15:28:42 +0000628 """Return the set of errors raised by the FTP class."""
Jeremy Hyltonf90b0021999-02-25 16:12:12 +0000629 global _ftperrors
630 if not _ftperrors:
631 import ftplib
632 _ftperrors = ftplib.all_errors
633 return _ftperrors
Guido van Rossum7c6ebb51994-03-22 12:05:32 +0000634
Guido van Rossum7c6ebb51994-03-22 12:05:32 +0000635_noheaders = None
636def noheaders():
Guido van Rossume7b146f2000-02-04 15:28:42 +0000637 """Return an empty mimetools.Message object."""
Jeremy Hyltonf90b0021999-02-25 16:12:12 +0000638 global _noheaders
639 if not _noheaders:
640 import mimetools
641 import StringIO
642 _noheaders = mimetools.Message(StringIO.StringIO(), 0)
643 _noheaders.fp.close() # Recycle file descriptor
644 return _noheaders
Guido van Rossum7c6ebb51994-03-22 12:05:32 +0000645
646
647# Utility classes
648
Guido van Rossum7c6ebb51994-03-22 12:05:32 +0000649class ftpwrapper:
Guido van Rossume7b146f2000-02-04 15:28:42 +0000650 """Class used by open_ftp() for cache of open FTP connections."""
651
Jeremy Hyltonf90b0021999-02-25 16:12:12 +0000652 def __init__(self, user, passwd, host, port, dirs):
653 self.user = user
654 self.passwd = passwd
655 self.host = host
656 self.port = port
657 self.dirs = dirs
658 self.init()
Guido van Rossume7b146f2000-02-04 15:28:42 +0000659
Jeremy Hyltonf90b0021999-02-25 16:12:12 +0000660 def init(self):
661 import ftplib
662 self.busy = 0
663 self.ftp = ftplib.FTP()
664 self.ftp.connect(self.host, self.port)
665 self.ftp.login(self.user, self.passwd)
666 for dir in self.dirs:
667 self.ftp.cwd(dir)
Guido van Rossume7b146f2000-02-04 15:28:42 +0000668
Jeremy Hyltonf90b0021999-02-25 16:12:12 +0000669 def retrfile(self, file, type):
670 import ftplib
671 self.endtransfer()
672 if type in ('d', 'D'): cmd = 'TYPE A'; isdir = 1
673 else: cmd = 'TYPE ' + type; isdir = 0
674 try:
675 self.ftp.voidcmd(cmd)
676 except ftplib.all_errors:
677 self.init()
678 self.ftp.voidcmd(cmd)
679 conn = None
680 if file and not isdir:
681 # Use nlst to see if the file exists at all
682 try:
683 self.ftp.nlst(file)
684 except ftplib.error_perm, reason:
685 raise IOError, ('ftp error', reason), sys.exc_info()[2]
686 # Restore the transfer mode!
687 self.ftp.voidcmd(cmd)
688 # Try to retrieve as a file
689 try:
690 cmd = 'RETR ' + file
691 conn = self.ftp.ntransfercmd(cmd)
692 except ftplib.error_perm, reason:
Guido van Rossumb2493f82000-12-15 15:01:37 +0000693 if str(reason)[:3] != '550':
Jeremy Hyltonf90b0021999-02-25 16:12:12 +0000694 raise IOError, ('ftp error', reason), sys.exc_info()[2]
695 if not conn:
696 # Set transfer mode to ASCII!
697 self.ftp.voidcmd('TYPE A')
698 # Try a directory listing
699 if file: cmd = 'LIST ' + file
700 else: cmd = 'LIST'
701 conn = self.ftp.ntransfercmd(cmd)
702 self.busy = 1
703 # Pass back both a suitably decorated object and a retrieval length
704 return (addclosehook(conn[0].makefile('rb'),
Fredrik Lundhb49f88b2000-09-24 18:51:25 +0000705 self.endtransfer), conn[1])
Jeremy Hyltonf90b0021999-02-25 16:12:12 +0000706 def endtransfer(self):
707 if not self.busy:
708 return
709 self.busy = 0
710 try:
711 self.ftp.voidresp()
712 except ftperrors():
713 pass
Guido van Rossume7b146f2000-02-04 15:28:42 +0000714
Jeremy Hyltonf90b0021999-02-25 16:12:12 +0000715 def close(self):
716 self.endtransfer()
717 try:
718 self.ftp.close()
719 except ftperrors():
720 pass
Guido van Rossum7c6ebb51994-03-22 12:05:32 +0000721
Guido van Rossum7c6ebb51994-03-22 12:05:32 +0000722class addbase:
Guido van Rossume7b146f2000-02-04 15:28:42 +0000723 """Base class for addinfo and addclosehook."""
724
Jeremy Hyltonf90b0021999-02-25 16:12:12 +0000725 def __init__(self, fp):
726 self.fp = fp
727 self.read = self.fp.read
728 self.readline = self.fp.readline
Guido van Rossum09c8b6c1999-12-07 21:37:17 +0000729 if hasattr(self.fp, "readlines"): self.readlines = self.fp.readlines
730 if hasattr(self.fp, "fileno"): self.fileno = self.fp.fileno
Guido van Rossume7b146f2000-02-04 15:28:42 +0000731
Jeremy Hyltonf90b0021999-02-25 16:12:12 +0000732 def __repr__(self):
733 return '<%s at %s whose fp = %s>' % (self.__class__.__name__,
Fredrik Lundhb49f88b2000-09-24 18:51:25 +0000734 `id(self)`, `self.fp`)
Guido van Rossume7b146f2000-02-04 15:28:42 +0000735
Jeremy Hyltonf90b0021999-02-25 16:12:12 +0000736 def close(self):
737 self.read = None
738 self.readline = None
739 self.readlines = None
740 self.fileno = None
741 if self.fp: self.fp.close()
742 self.fp = None
Guido van Rossum7c6ebb51994-03-22 12:05:32 +0000743
Guido van Rossum7c6ebb51994-03-22 12:05:32 +0000744class addclosehook(addbase):
Guido van Rossume7b146f2000-02-04 15:28:42 +0000745 """Class to add a close hook to an open file."""
746
Jeremy Hyltonf90b0021999-02-25 16:12:12 +0000747 def __init__(self, fp, closehook, *hookargs):
748 addbase.__init__(self, fp)
749 self.closehook = closehook
750 self.hookargs = hookargs
Guido van Rossume7b146f2000-02-04 15:28:42 +0000751
Jeremy Hyltonf90b0021999-02-25 16:12:12 +0000752 def close(self):
Guido van Rossumc580dae2000-05-24 13:21:46 +0000753 addbase.close(self)
Jeremy Hyltonf90b0021999-02-25 16:12:12 +0000754 if self.closehook:
755 apply(self.closehook, self.hookargs)
756 self.closehook = None
757 self.hookargs = None
Guido van Rossum7c6ebb51994-03-22 12:05:32 +0000758
Guido van Rossum7c6ebb51994-03-22 12:05:32 +0000759class addinfo(addbase):
Guido van Rossume7b146f2000-02-04 15:28:42 +0000760 """class to add an info() method to an open file."""
761
Jeremy Hyltonf90b0021999-02-25 16:12:12 +0000762 def __init__(self, fp, headers):
763 addbase.__init__(self, fp)
764 self.headers = headers
Guido van Rossume7b146f2000-02-04 15:28:42 +0000765
Jeremy Hyltonf90b0021999-02-25 16:12:12 +0000766 def info(self):
767 return self.headers
Guido van Rossum7c6ebb51994-03-22 12:05:32 +0000768
Guido van Rossume6ad8911996-09-10 17:02:56 +0000769class addinfourl(addbase):
Guido van Rossume7b146f2000-02-04 15:28:42 +0000770 """class to add info() and geturl() methods to an open file."""
771
Jeremy Hyltonf90b0021999-02-25 16:12:12 +0000772 def __init__(self, fp, headers, url):
773 addbase.__init__(self, fp)
774 self.headers = headers
775 self.url = url
Guido van Rossume7b146f2000-02-04 15:28:42 +0000776
Jeremy Hyltonf90b0021999-02-25 16:12:12 +0000777 def info(self):
778 return self.headers
Guido van Rossume7b146f2000-02-04 15:28:42 +0000779
Jeremy Hyltonf90b0021999-02-25 16:12:12 +0000780 def geturl(self):
781 return self.url
Guido van Rossume6ad8911996-09-10 17:02:56 +0000782
Guido van Rossum7c6ebb51994-03-22 12:05:32 +0000783
Guido van Rossum7c6ebb51994-03-22 12:05:32 +0000784def basejoin(base, url):
Guido van Rossume7b146f2000-02-04 15:28:42 +0000785 """Utility to combine a URL with a base URL to form a new URL."""
Jeremy Hyltonf90b0021999-02-25 16:12:12 +0000786 type, path = splittype(url)
787 if type:
788 # if url is complete (i.e., it contains a type), return it
789 return url
790 host, path = splithost(path)
791 type, basepath = splittype(base) # inherit type from base
792 if host:
793 # if url contains host, just inherit type
794 if type: return type + '://' + host + path
795 else:
796 # no type inherited, so url must have started with //
797 # just return it
798 return url
799 host, basepath = splithost(basepath) # inherit host
Thomas Wouters7e474022000-07-16 12:04:32 +0000800 basepath, basetag = splittag(basepath) # remove extraneous cruft
Jeremy Hyltonf90b0021999-02-25 16:12:12 +0000801 basepath, basequery = splitquery(basepath) # idem
802 if path[:1] != '/':
803 # non-absolute path name
804 if path[:1] in ('#', '?'):
805 # path is just a tag or query, attach to basepath
806 i = len(basepath)
807 else:
808 # else replace last component
Guido van Rossumb2493f82000-12-15 15:01:37 +0000809 i = basepath.rfind('/')
Jeremy Hyltonf90b0021999-02-25 16:12:12 +0000810 if i < 0:
811 # basepath not absolute
812 if host:
813 # host present, make absolute
814 basepath = '/'
815 else:
816 # else keep non-absolute
817 basepath = ''
818 else:
819 # remove last file component
820 basepath = basepath[:i+1]
821 # Interpret ../ (important because of symlinks)
822 while basepath and path[:3] == '../':
823 path = path[3:]
Guido van Rossumb2493f82000-12-15 15:01:37 +0000824 i = basepath[:-1].rfind('/')
Jeremy Hyltonf90b0021999-02-25 16:12:12 +0000825 if i > 0:
826 basepath = basepath[:i+1]
827 elif i == 0:
828 basepath = '/'
829 break
830 else:
831 basepath = ''
Fredrik Lundhb49f88b2000-09-24 18:51:25 +0000832
Jeremy Hyltonf90b0021999-02-25 16:12:12 +0000833 path = basepath + path
834 if type and host: return type + '://' + host + path
835 elif type: return type + ':' + path
836 elif host: return '//' + host + path # don't know what this means
837 else: return path
Guido van Rossum7c6ebb51994-03-22 12:05:32 +0000838
839
Guido van Rossum7c395db1994-07-04 22:14:49 +0000840# Utilities to parse URLs (most of these return None for missing parts):
Sjoerd Mullendere0371b81995-11-10 10:36:07 +0000841# unwrap('<URL:type://host/path>') --> 'type://host/path'
Guido van Rossum7c6ebb51994-03-22 12:05:32 +0000842# splittype('type:opaquestring') --> 'type', 'opaquestring'
843# splithost('//host[:port]/path') --> 'host[:port]', '/path'
Guido van Rossum7c395db1994-07-04 22:14:49 +0000844# splituser('user[:passwd]@host[:port]') --> 'user[:passwd]', 'host[:port]'
845# splitpasswd('user:passwd') -> 'user', 'passwd'
Guido van Rossum7c6ebb51994-03-22 12:05:32 +0000846# splitport('host:port') --> 'host', 'port'
847# splitquery('/path?query') --> '/path', 'query'
848# splittag('/path#tag') --> '/path', 'tag'
Guido van Rossum7c395db1994-07-04 22:14:49 +0000849# splitattr('/path;attr1=value1;attr2=value2;...') ->
850# '/path', ['attr1=value1', 'attr2=value2', ...]
851# splitvalue('attr=value') --> 'attr', 'value'
Guido van Rossum7c6ebb51994-03-22 12:05:32 +0000852# splitgophertype('/Xselector') --> 'X', 'selector'
853# unquote('abc%20def') -> 'abc def'
854# quote('abc def') -> 'abc%20def')
855
Martin v. Löwis1d994332000-12-03 18:30:10 +0000856def toBytes(url):
857 """toBytes(u"URL") --> 'URL'."""
858 # Most URL schemes require ASCII. If that changes, the conversion
859 # can be relaxed
860 if type(url) is types.UnicodeType:
861 try:
862 url = url.encode("ASCII")
863 except UnicodeError:
Guido van Rossumb2493f82000-12-15 15:01:37 +0000864 raise UnicodeError("URL " + repr(url) +
865 " contains non-ASCII characters")
Martin v. Löwis1d994332000-12-03 18:30:10 +0000866 return url
867
Guido van Rossum7c6ebb51994-03-22 12:05:32 +0000868def unwrap(url):
Guido van Rossume7b146f2000-02-04 15:28:42 +0000869 """unwrap('<URL:type://host/path>') --> 'type://host/path'."""
Guido van Rossumb2493f82000-12-15 15:01:37 +0000870 url = url.strip()
Jeremy Hyltonf90b0021999-02-25 16:12:12 +0000871 if url[:1] == '<' and url[-1:] == '>':
Guido van Rossumb2493f82000-12-15 15:01:37 +0000872 url = url[1:-1].strip()
873 if url[:4] == 'URL:': url = url[4:].strip()
Jeremy Hyltonf90b0021999-02-25 16:12:12 +0000874 return url
Guido van Rossum7c6ebb51994-03-22 12:05:32 +0000875
Guido van Rossum332e1441997-09-29 23:23:46 +0000876_typeprog = None
Guido van Rossum7c6ebb51994-03-22 12:05:32 +0000877def splittype(url):
Guido van Rossume7b146f2000-02-04 15:28:42 +0000878 """splittype('type:opaquestring') --> 'type', 'opaquestring'."""
Jeremy Hyltonf90b0021999-02-25 16:12:12 +0000879 global _typeprog
880 if _typeprog is None:
881 import re
882 _typeprog = re.compile('^([^/:]+):')
Guido van Rossum332e1441997-09-29 23:23:46 +0000883
Jeremy Hyltonf90b0021999-02-25 16:12:12 +0000884 match = _typeprog.match(url)
885 if match:
886 scheme = match.group(1)
Fred Drake9e94afd2000-07-01 07:03:30 +0000887 return scheme.lower(), url[len(scheme) + 1:]
Jeremy Hyltonf90b0021999-02-25 16:12:12 +0000888 return None, url
Guido van Rossum7c6ebb51994-03-22 12:05:32 +0000889
Guido van Rossum332e1441997-09-29 23:23:46 +0000890_hostprog = None
Guido van Rossum7c6ebb51994-03-22 12:05:32 +0000891def splithost(url):
Guido van Rossume7b146f2000-02-04 15:28:42 +0000892 """splithost('//host[:port]/path') --> 'host[:port]', '/path'."""
Jeremy Hyltonf90b0021999-02-25 16:12:12 +0000893 global _hostprog
894 if _hostprog is None:
895 import re
Guido van Rossum3427c1f1999-07-01 23:20:56 +0000896 _hostprog = re.compile('^//([^/]*)(.*)$')
Guido van Rossum332e1441997-09-29 23:23:46 +0000897
Fredrik Lundhb49f88b2000-09-24 18:51:25 +0000898 match = _hostprog.match(url)
Jeremy Hyltonf90b0021999-02-25 16:12:12 +0000899 if match: return match.group(1, 2)
900 return None, url
Guido van Rossum7c6ebb51994-03-22 12:05:32 +0000901
Guido van Rossum332e1441997-09-29 23:23:46 +0000902_userprog = None
Guido van Rossum7c395db1994-07-04 22:14:49 +0000903def splituser(host):
Guido van Rossume7b146f2000-02-04 15:28:42 +0000904 """splituser('user[:passwd]@host[:port]') --> 'user[:passwd]', 'host[:port]'."""
Jeremy Hyltonf90b0021999-02-25 16:12:12 +0000905 global _userprog
906 if _userprog is None:
907 import re
908 _userprog = re.compile('^([^@]*)@(.*)$')
Guido van Rossum332e1441997-09-29 23:23:46 +0000909
Jeremy Hyltonf90b0021999-02-25 16:12:12 +0000910 match = _userprog.match(host)
Fred Drake567ca8e2000-08-21 21:42:42 +0000911 if match: return map(unquote, match.group(1, 2))
Jeremy Hyltonf90b0021999-02-25 16:12:12 +0000912 return None, host
Guido van Rossum7c395db1994-07-04 22:14:49 +0000913
Guido van Rossum332e1441997-09-29 23:23:46 +0000914_passwdprog = None
Guido van Rossum7c395db1994-07-04 22:14:49 +0000915def splitpasswd(user):
Guido van Rossume7b146f2000-02-04 15:28:42 +0000916 """splitpasswd('user:passwd') -> 'user', 'passwd'."""
Jeremy Hyltonf90b0021999-02-25 16:12:12 +0000917 global _passwdprog
918 if _passwdprog is None:
919 import re
920 _passwdprog = re.compile('^([^:]*):(.*)$')
Guido van Rossum332e1441997-09-29 23:23:46 +0000921
Jeremy Hyltonf90b0021999-02-25 16:12:12 +0000922 match = _passwdprog.match(user)
923 if match: return match.group(1, 2)
924 return user, None
Guido van Rossum7c395db1994-07-04 22:14:49 +0000925
Guido van Rossume7b146f2000-02-04 15:28:42 +0000926# splittag('/path#tag') --> '/path', 'tag'
Guido van Rossum332e1441997-09-29 23:23:46 +0000927_portprog = None
Guido van Rossum7c6ebb51994-03-22 12:05:32 +0000928def splitport(host):
Guido van Rossume7b146f2000-02-04 15:28:42 +0000929 """splitport('host:port') --> 'host', 'port'."""
Jeremy Hyltonf90b0021999-02-25 16:12:12 +0000930 global _portprog
931 if _portprog is None:
932 import re
933 _portprog = re.compile('^(.*):([0-9]+)$')
Guido van Rossum332e1441997-09-29 23:23:46 +0000934
Jeremy Hyltonf90b0021999-02-25 16:12:12 +0000935 match = _portprog.match(host)
936 if match: return match.group(1, 2)
937 return host, None
Guido van Rossum7c6ebb51994-03-22 12:05:32 +0000938
Guido van Rossum332e1441997-09-29 23:23:46 +0000939_nportprog = None
Guido van Rossum53725a21996-06-13 19:12:35 +0000940def splitnport(host, defport=-1):
Guido van Rossume7b146f2000-02-04 15:28:42 +0000941 """Split host and port, returning numeric port.
942 Return given default port if no ':' found; defaults to -1.
943 Return numerical port if a valid number are found after ':'.
944 Return None if ':' but not a valid number."""
Jeremy Hyltonf90b0021999-02-25 16:12:12 +0000945 global _nportprog
946 if _nportprog is None:
947 import re
948 _nportprog = re.compile('^(.*):(.*)$')
Guido van Rossum7e7ca0b1998-03-26 21:01:39 +0000949
Jeremy Hyltonf90b0021999-02-25 16:12:12 +0000950 match = _nportprog.match(host)
951 if match:
952 host, port = match.group(1, 2)
953 try:
Guido van Rossumb2493f82000-12-15 15:01:37 +0000954 if not port: raise ValueError, "no digits"
955 nport = int(port)
956 except ValueError:
Jeremy Hyltonf90b0021999-02-25 16:12:12 +0000957 nport = None
958 return host, nport
959 return host, defport
Guido van Rossum53725a21996-06-13 19:12:35 +0000960
Guido van Rossum332e1441997-09-29 23:23:46 +0000961_queryprog = None
Guido van Rossum7c6ebb51994-03-22 12:05:32 +0000962def splitquery(url):
Guido van Rossume7b146f2000-02-04 15:28:42 +0000963 """splitquery('/path?query') --> '/path', 'query'."""
Jeremy Hyltonf90b0021999-02-25 16:12:12 +0000964 global _queryprog
965 if _queryprog is None:
966 import re
967 _queryprog = re.compile('^(.*)\?([^?]*)$')
Guido van Rossum332e1441997-09-29 23:23:46 +0000968
Jeremy Hyltonf90b0021999-02-25 16:12:12 +0000969 match = _queryprog.match(url)
970 if match: return match.group(1, 2)
971 return url, None
Guido van Rossum7c6ebb51994-03-22 12:05:32 +0000972
Guido van Rossum332e1441997-09-29 23:23:46 +0000973_tagprog = None
Guido van Rossum7c6ebb51994-03-22 12:05:32 +0000974def splittag(url):
Guido van Rossume7b146f2000-02-04 15:28:42 +0000975 """splittag('/path#tag') --> '/path', 'tag'."""
Jeremy Hyltonf90b0021999-02-25 16:12:12 +0000976 global _tagprog
977 if _tagprog is None:
978 import re
979 _tagprog = re.compile('^(.*)#([^#]*)$')
Guido van Rossum7e7ca0b1998-03-26 21:01:39 +0000980
Jeremy Hyltonf90b0021999-02-25 16:12:12 +0000981 match = _tagprog.match(url)
982 if match: return match.group(1, 2)
983 return url, None
Guido van Rossum7c6ebb51994-03-22 12:05:32 +0000984
Guido van Rossum7c395db1994-07-04 22:14:49 +0000985def splitattr(url):
Guido van Rossume7b146f2000-02-04 15:28:42 +0000986 """splitattr('/path;attr1=value1;attr2=value2;...') ->
987 '/path', ['attr1=value1', 'attr2=value2', ...]."""
Guido van Rossumb2493f82000-12-15 15:01:37 +0000988 words = url.split(';')
Jeremy Hyltonf90b0021999-02-25 16:12:12 +0000989 return words[0], words[1:]
Guido van Rossum7c395db1994-07-04 22:14:49 +0000990
Guido van Rossum332e1441997-09-29 23:23:46 +0000991_valueprog = None
Guido van Rossum7c395db1994-07-04 22:14:49 +0000992def splitvalue(attr):
Guido van Rossume7b146f2000-02-04 15:28:42 +0000993 """splitvalue('attr=value') --> 'attr', 'value'."""
Jeremy Hyltonf90b0021999-02-25 16:12:12 +0000994 global _valueprog
995 if _valueprog is None:
996 import re
997 _valueprog = re.compile('^([^=]*)=(.*)$')
Guido van Rossum332e1441997-09-29 23:23:46 +0000998
Jeremy Hyltonf90b0021999-02-25 16:12:12 +0000999 match = _valueprog.match(attr)
1000 if match: return match.group(1, 2)
1001 return attr, None
Guido van Rossum7c395db1994-07-04 22:14:49 +00001002
Guido van Rossum7c6ebb51994-03-22 12:05:32 +00001003def splitgophertype(selector):
Guido van Rossume7b146f2000-02-04 15:28:42 +00001004 """splitgophertype('/Xselector') --> 'X', 'selector'."""
Jeremy Hyltonf90b0021999-02-25 16:12:12 +00001005 if selector[:1] == '/' and selector[1:2]:
1006 return selector[1], selector[2:]
1007 return None, selector
Guido van Rossum7c6ebb51994-03-22 12:05:32 +00001008
Guido van Rossum7c6ebb51994-03-22 12:05:32 +00001009def unquote(s):
Guido van Rossume7b146f2000-02-04 15:28:42 +00001010 """unquote('abc%20def') -> 'abc def'."""
Jeremy Hyltonf90b0021999-02-25 16:12:12 +00001011 mychr = chr
Guido van Rossumb2493f82000-12-15 15:01:37 +00001012 myatoi = int
1013 list = s.split('%')
Jeremy Hyltonf90b0021999-02-25 16:12:12 +00001014 res = [list[0]]
1015 myappend = res.append
1016 del list[0]
1017 for item in list:
1018 if item[1:2]:
1019 try:
1020 myappend(mychr(myatoi(item[:2], 16))
1021 + item[2:])
1022 except:
1023 myappend('%' + item)
1024 else:
1025 myappend('%' + item)
Guido van Rossumb2493f82000-12-15 15:01:37 +00001026 return "".join(res)
Guido van Rossum7c6ebb51994-03-22 12:05:32 +00001027
Guido van Rossum0564e121996-12-13 14:47:36 +00001028def unquote_plus(s):
Skip Montanaro79f1c172000-08-22 03:00:52 +00001029 """unquote('%7e/abc+def') -> '~/abc def'"""
Jeremy Hyltonf90b0021999-02-25 16:12:12 +00001030 if '+' in s:
1031 # replace '+' with ' '
Guido van Rossumb2493f82000-12-15 15:01:37 +00001032 s = ' '.join(s.split('+'))
Jeremy Hyltonf90b0021999-02-25 16:12:12 +00001033 return unquote(s)
Guido van Rossum0564e121996-12-13 14:47:36 +00001034
Fredrik Lundhb49f88b2000-09-24 18:51:25 +00001035always_safe = ('ABCDEFGHIJKLMNOPQRSTUVWXYZ'
Jeremy Hylton6102e292000-08-31 15:48:10 +00001036 'abcdefghijklmnopqrstuvwxyz'
Jeremy Hylton7ae51bf2000-09-14 16:59:07 +00001037 '0123456789' '_.-')
1038
1039_fast_safe_test = always_safe + '/'
1040_fast_safe = None
1041
1042def _fast_quote(s):
1043 global _fast_safe
1044 if _fast_safe is None:
1045 _fast_safe = {}
1046 for c in _fast_safe_test:
1047 _fast_safe[c] = c
1048 res = list(s)
1049 for i in range(len(res)):
1050 c = res[i]
1051 if not _fast_safe.has_key(c):
1052 res[i] = '%%%02x' % ord(c)
Guido van Rossumb2493f82000-12-15 15:01:37 +00001053 return ''.join(res)
Jeremy Hylton7ae51bf2000-09-14 16:59:07 +00001054
Guido van Rossum7c395db1994-07-04 22:14:49 +00001055def quote(s, safe = '/'):
Jeremy Hylton7ae51bf2000-09-14 16:59:07 +00001056 """quote('abc def') -> 'abc%20def'
Fredrik Lundhb49f88b2000-09-24 18:51:25 +00001057
Jeremy Hylton7ae51bf2000-09-14 16:59:07 +00001058 Each part of a URL, e.g. the path info, the query, etc., has a
1059 different set of reserved characters that must be quoted.
1060
1061 RFC 2396 Uniform Resource Identifiers (URI): Generic Syntax lists
1062 the following reserved characters.
1063
1064 reserved = ";" | "/" | "?" | ":" | "@" | "&" | "=" | "+" |
1065 "$" | ","
1066
1067 Each of these characters is reserved in some component of a URL,
1068 but not necessarily in all of them.
1069
1070 By default, the quote function is intended for quoting the path
1071 section of a URL. Thus, it will not encode '/'. This character
1072 is reserved, but in typical usage the quote function is being
1073 called on a path where the existing slash characters are used as
1074 reserved characters.
1075 """
Jeremy Hyltonf90b0021999-02-25 16:12:12 +00001076 safe = always_safe + safe
Jeremy Hylton7ae51bf2000-09-14 16:59:07 +00001077 if _fast_safe_test == safe:
1078 return _fast_quote(s)
Jeremy Hyltonf90b0021999-02-25 16:12:12 +00001079 res = list(s)
1080 for i in range(len(res)):
1081 c = res[i]
1082 if c not in safe:
1083 res[i] = '%%%02x' % ord(c)
Guido van Rossumb2493f82000-12-15 15:01:37 +00001084 return ''.join(res)
Guido van Rossum7c6ebb51994-03-22 12:05:32 +00001085
Jeremy Hylton7ae51bf2000-09-14 16:59:07 +00001086def quote_plus(s, safe = ''):
1087 """Quote the query fragment of a URL; replacing ' ' with '+'"""
Jeremy Hyltonf90b0021999-02-25 16:12:12 +00001088 if ' ' in s:
Guido van Rossumb2493f82000-12-15 15:01:37 +00001089 l = s.split(' ')
Jeremy Hyltonf90b0021999-02-25 16:12:12 +00001090 for i in range(len(l)):
1091 l[i] = quote(l[i], safe)
Guido van Rossumb2493f82000-12-15 15:01:37 +00001092 return '+'.join(l)
Jeremy Hyltonf90b0021999-02-25 16:12:12 +00001093 else:
1094 return quote(s, safe)
Guido van Rossum0564e121996-12-13 14:47:36 +00001095
Guido van Rossum810a3391998-07-22 21:33:23 +00001096def urlencode(dict):
Guido van Rossume7b146f2000-02-04 15:28:42 +00001097 """Encode a dictionary of form entries into a URL query string."""
1098 l = []
1099 for k, v in dict.items():
1100 k = quote_plus(str(k))
1101 v = quote_plus(str(v))
1102 l.append(k + '=' + v)
Guido van Rossumb2493f82000-12-15 15:01:37 +00001103 return '&'.join(l)
Guido van Rossum810a3391998-07-22 21:33:23 +00001104
Guido van Rossum442e7201996-03-20 15:33:11 +00001105# Proxy handling
Mark Hammond4f570b92000-07-26 07:04:38 +00001106def getproxies_environment():
1107 """Return a dictionary of scheme -> proxy server URL mappings.
1108
1109 Scan the environment for variables named <scheme>_proxy;
1110 this seems to be the standard convention. If you need a
1111 different way, you can pass a proxies dictionary to the
1112 [Fancy]URLopener constructor.
1113
1114 """
1115 proxies = {}
1116 for name, value in os.environ.items():
Guido van Rossumb2493f82000-12-15 15:01:37 +00001117 name = name.lower()
Mark Hammond4f570b92000-07-26 07:04:38 +00001118 if value and name[-6:] == '_proxy':
1119 proxies[name[:-6]] = value
1120 return proxies
1121
Guido van Rossum4163e701998-08-06 13:39:09 +00001122if os.name == 'mac':
Jeremy Hyltonf90b0021999-02-25 16:12:12 +00001123 def getproxies():
1124 """Return a dictionary of scheme -> proxy server URL mappings.
Guido van Rossum442e7201996-03-20 15:33:11 +00001125
Jeremy Hyltonf90b0021999-02-25 16:12:12 +00001126 By convention the mac uses Internet Config to store
1127 proxies. An HTTP proxy, for instance, is stored under
1128 the HttpProxy key.
Guido van Rossum442e7201996-03-20 15:33:11 +00001129
Jeremy Hyltonf90b0021999-02-25 16:12:12 +00001130 """
1131 try:
1132 import ic
1133 except ImportError:
1134 return {}
Fredrik Lundhb49f88b2000-09-24 18:51:25 +00001135
Jeremy Hyltonf90b0021999-02-25 16:12:12 +00001136 try:
1137 config = ic.IC()
1138 except ic.error:
1139 return {}
1140 proxies = {}
1141 # HTTP:
1142 if config.has_key('UseHTTPProxy') and config['UseHTTPProxy']:
1143 try:
1144 value = config['HTTPProxyHost']
1145 except ic.error:
1146 pass
1147 else:
1148 proxies['http'] = 'http://%s' % value
1149 # FTP: XXXX To be done.
1150 # Gopher: XXXX To be done.
1151 return proxies
Mark Hammond4f570b92000-07-26 07:04:38 +00001152
1153elif os.name == 'nt':
1154 def getproxies_registry():
Jeremy Hyltonf90b0021999-02-25 16:12:12 +00001155 """Return a dictionary of scheme -> proxy server URL mappings.
Mark Hammond4f570b92000-07-26 07:04:38 +00001156
1157 Win32 uses the registry to store proxies.
1158
Jeremy Hyltonf90b0021999-02-25 16:12:12 +00001159 """
1160 proxies = {}
Mark Hammond4f570b92000-07-26 07:04:38 +00001161 try:
1162 import _winreg
1163 except ImportError:
1164 # Std module, so should be around - but you never know!
1165 return proxies
1166 try:
Fredrik Lundhb49f88b2000-09-24 18:51:25 +00001167 internetSettings = _winreg.OpenKey(_winreg.HKEY_CURRENT_USER,
1168 r'Software\Microsoft\Windows\CurrentVersion\Internet Settings')
Mark Hammond4f570b92000-07-26 07:04:38 +00001169 proxyEnable = _winreg.QueryValueEx(internetSettings,
1170 'ProxyEnable')[0]
1171 if proxyEnable:
1172 # Returned as Unicode but problems if not converted to ASCII
1173 proxyServer = str(_winreg.QueryValueEx(internetSettings,
1174 'ProxyServer')[0])
Fredrik Lundhb49f88b2000-09-24 18:51:25 +00001175 if '=' in proxyServer:
1176 # Per-protocol settings
Mark Hammond4f570b92000-07-26 07:04:38 +00001177 for p in proxyServer.split(';'):
Fredrik Lundhb49f88b2000-09-24 18:51:25 +00001178 protocol, address = p.split('=', 1)
Mark Hammond4f570b92000-07-26 07:04:38 +00001179 proxies[protocol] = '%s://%s' % (protocol, address)
Fredrik Lundhb49f88b2000-09-24 18:51:25 +00001180 else:
1181 # Use one setting for all protocols
1182 if proxyServer[:5] == 'http:':
1183 proxies['http'] = proxyServer
1184 else:
1185 proxies['http'] = 'http://%s' % proxyServer
1186 proxies['ftp'] = 'ftp://%s' % proxyServer
Mark Hammond4f570b92000-07-26 07:04:38 +00001187 internetSettings.Close()
1188 except (WindowsError, ValueError, TypeError):
1189 # Either registry key not found etc, or the value in an
1190 # unexpected format.
1191 # proxies already set up to be empty so nothing to do
1192 pass
Jeremy Hyltonf90b0021999-02-25 16:12:12 +00001193 return proxies
Guido van Rossum442e7201996-03-20 15:33:11 +00001194
Mark Hammond4f570b92000-07-26 07:04:38 +00001195 def getproxies():
1196 """Return a dictionary of scheme -> proxy server URL mappings.
1197
1198 Returns settings gathered from the environment, if specified,
1199 or the registry.
1200
1201 """
1202 return getproxies_environment() or getproxies_registry()
1203else:
1204 # By default use environment variables
1205 getproxies = getproxies_environment
1206
Guido van Rossum442e7201996-03-20 15:33:11 +00001207
Guido van Rossum7c6ebb51994-03-22 12:05:32 +00001208# Test and time quote() and unquote()
1209def test1():
Jeremy Hyltonf90b0021999-02-25 16:12:12 +00001210 import time
1211 s = ''
1212 for i in range(256): s = s + chr(i)
1213 s = s*4
1214 t0 = time.time()
1215 qs = quote(s)
1216 uqs = unquote(qs)
1217 t1 = time.time()
1218 if uqs != s:
1219 print 'Wrong!'
1220 print `s`
1221 print `qs`
1222 print `uqs`
1223 print round(t1 - t0, 3), 'sec'
Guido van Rossum7c6ebb51994-03-22 12:05:32 +00001224
1225
Guido van Rossum9ab96d41998-09-28 14:07:00 +00001226def reporthook(blocknum, blocksize, totalsize):
1227 # Report during remote transfers
Guido van Rossumb2493f82000-12-15 15:01:37 +00001228 print "Block number: %d, Block size: %d, Total size: %d" % (
1229 blocknum, blocksize, totalsize)
Guido van Rossum9ab96d41998-09-28 14:07:00 +00001230
Guido van Rossum7c6ebb51994-03-22 12:05:32 +00001231# Test program
Guido van Rossum23490151998-06-25 02:39:00 +00001232def test(args=[]):
Jeremy Hyltonf90b0021999-02-25 16:12:12 +00001233 if not args:
1234 args = [
1235 '/etc/passwd',
1236 'file:/etc/passwd',
1237 'file://localhost/etc/passwd',
1238 'ftp://ftp.python.org/etc/passwd',
1239## 'gopher://gopher.micro.umn.edu/1/',
1240 'http://www.python.org/index.html',
1241 ]
Guido van Rossum09c8b6c1999-12-07 21:37:17 +00001242 if hasattr(URLopener, "open_https"):
1243 args.append('https://synergy.as.cmu.edu/~geek/')
Jeremy Hyltonf90b0021999-02-25 16:12:12 +00001244 try:
1245 for url in args:
1246 print '-'*10, url, '-'*10
1247 fn, h = urlretrieve(url, None, reporthook)
Guido van Rossumb2493f82000-12-15 15:01:37 +00001248 print fn
Jeremy Hyltonf90b0021999-02-25 16:12:12 +00001249 if h:
1250 print '======'
1251 for k in h.keys(): print k + ':', h[k]
1252 print '======'
1253 fp = open(fn, 'rb')
1254 data = fp.read()
1255 del fp
1256 if '\r' in data:
1257 table = string.maketrans("", "")
Guido van Rossumb2493f82000-12-15 15:01:37 +00001258 data = data.translate(table, "\r")
Jeremy Hyltonf90b0021999-02-25 16:12:12 +00001259 print data
1260 fn, h = None, None
1261 print '-'*40
1262 finally:
1263 urlcleanup()
Guido van Rossum7c6ebb51994-03-22 12:05:32 +00001264
Guido van Rossum23490151998-06-25 02:39:00 +00001265def main():
Jeremy Hyltonf90b0021999-02-25 16:12:12 +00001266 import getopt, sys
1267 try:
1268 opts, args = getopt.getopt(sys.argv[1:], "th")
1269 except getopt.error, msg:
1270 print msg
1271 print "Use -h for help"
1272 return
1273 t = 0
1274 for o, a in opts:
1275 if o == '-t':
1276 t = t + 1
1277 if o == '-h':
1278 print "Usage: python urllib.py [-t] [url ...]"
1279 print "-t runs self-test;",
1280 print "otherwise, contents of urls are printed"
1281 return
1282 if t:
1283 if t > 1:
1284 test1()
1285 test(args)
1286 else:
1287 if not args:
1288 print "Use -h for help"
1289 for url in args:
1290 print urlopen(url).read(),
Guido van Rossum23490151998-06-25 02:39:00 +00001291
Guido van Rossum7c6ebb51994-03-22 12:05:32 +00001292# Run test program when run as a script
1293if __name__ == '__main__':
Jeremy Hyltonf90b0021999-02-25 16:12:12 +00001294 main()