24 import mimetypes |
24 import mimetypes |
25 import os |
25 import os |
26 import re |
26 import re |
27 import socket |
27 import socket |
28 import stat |
28 import stat |
29 import urllib |
29 |
30 import urllib2 |
30 from . import ( |
31 |
31 util, |
32 addclosehook = urllib.addclosehook |
32 ) |
33 addinfourl = urllib.addinfourl |
33 |
34 splitattr = urllib.splitattr |
34 urlerr = util.urlerr |
35 splitpasswd = urllib.splitpasswd |
35 urlreq = util.urlreq |
36 splitport = urllib.splitport |
36 |
37 splituser = urllib.splituser |
37 addclosehook = urlreq.addclosehook |
38 unquote = urllib.unquote |
38 addinfourl = urlreq.addinfourl |
|
39 splitattr = urlreq.splitattr |
|
40 splitpasswd = urlreq.splitpasswd |
|
41 splitport = urlreq.splitport |
|
42 splituser = urlreq.splituser |
|
43 unquote = urlreq.unquote |
39 |
44 |
40 class RangeError(IOError): |
45 class RangeError(IOError): |
41 """Error raised when an unsatisfiable range is requested.""" |
46 """Error raised when an unsatisfiable range is requested.""" |
42 pass |
47 pass |
43 |
48 |
44 class HTTPRangeHandler(urllib2.BaseHandler): |
49 class HTTPRangeHandler(urlreq.basehandler): |
45 """Handler that enables HTTP Range headers. |
50 """Handler that enables HTTP Range headers. |
46 |
51 |
47 This was extremely simple. The Range header is a HTTP feature to |
52 This was extremely simple. The Range header is a HTTP feature to |
48 begin with so all this class does is tell urllib2 that the |
53 begin with so all this class does is tell urllib2 that the |
49 "206 Partial Content" response from the HTTP server is what we |
54 "206 Partial Content" response from the HTTP server is what we |
52 Example: |
57 Example: |
53 import urllib2 |
58 import urllib2 |
54 import byterange |
59 import byterange |
55 |
60 |
56 range_handler = range.HTTPRangeHandler() |
61 range_handler = range.HTTPRangeHandler() |
57 opener = urllib2.build_opener(range_handler) |
62 opener = urlreq.buildopener(range_handler) |
58 |
63 |
59 # install it |
64 # install it |
60 urllib2.install_opener(opener) |
65 urlreq.installopener(opener) |
61 |
66 |
62 # create Request and set Range header |
67 # create Request and set Range header |
63 req = urllib2.Request('http://www.python.org/') |
68 req = urlreq.request('http://www.python.org/') |
64 req.header['Range'] = 'bytes=30-50' |
69 req.header['Range'] = 'bytes=30-50' |
65 f = urllib2.urlopen(req) |
70 f = urlreq.urlopen(req) |
66 """ |
71 """ |
67 |
72 |
68 def http_error_206(self, req, fp, code, msg, hdrs): |
73 def http_error_206(self, req, fp, code, msg, hdrs): |
69 # 206 Partial Content Response |
74 # 206 Partial Content Response |
70 r = urllib.addinfourl(fp, hdrs, req.get_full_url()) |
75 r = urlreq.addinfourl(fp, hdrs, req.get_full_url()) |
71 r.code = code |
76 r.code = code |
72 r.msg = msg |
77 r.msg = msg |
73 return r |
78 return r |
74 |
79 |
75 def http_error_416(self, req, fp, code, msg, hdrs): |
80 def http_error_416(self, req, fp, code, msg, hdrs): |
202 buf = self.fo.read(bufsize) |
207 buf = self.fo.read(bufsize) |
203 if len(buf) != bufsize: |
208 if len(buf) != bufsize: |
204 raise RangeError('Requested Range Not Satisfiable') |
209 raise RangeError('Requested Range Not Satisfiable') |
205 pos += bufsize |
210 pos += bufsize |
206 |
211 |
207 class FileRangeHandler(urllib2.FileHandler): |
212 class FileRangeHandler(urlreq.filehandler): |
208 """FileHandler subclass that adds Range support. |
213 """FileHandler subclass that adds Range support. |
209 This class handles Range headers exactly like an HTTP |
214 This class handles Range headers exactly like an HTTP |
210 server would. |
215 server would. |
211 """ |
216 """ |
212 def open_local_file(self, req): |
217 def open_local_file(self, req): |
213 host = req.get_host() |
218 host = req.get_host() |
214 file = req.get_selector() |
219 file = req.get_selector() |
215 localfile = urllib.url2pathname(file) |
220 localfile = urlreq.url2pathname(file) |
216 stats = os.stat(localfile) |
221 stats = os.stat(localfile) |
217 size = stats[stat.ST_SIZE] |
222 size = stats[stat.ST_SIZE] |
218 modified = email.Utils.formatdate(stats[stat.ST_MTIME]) |
223 modified = email.Utils.formatdate(stats[stat.ST_MTIME]) |
219 mtype = mimetypes.guess_type(file)[0] |
224 mtype = mimetypes.guess_type(file)[0] |
220 if host: |
225 if host: |
221 host, port = urllib.splitport(host) |
226 host, port = urlreq.splitport(host) |
222 if port or socket.gethostbyname(host) not in self.get_names(): |
227 if port or socket.gethostbyname(host) not in self.get_names(): |
223 raise urllib2.URLError('file not on local host') |
228 raise urlerr.urlerror('file not on local host') |
224 fo = open(localfile,'rb') |
229 fo = open(localfile,'rb') |
225 brange = req.headers.get('Range', None) |
230 brange = req.headers.get('Range', None) |
226 brange = range_header_to_tuple(brange) |
231 brange = range_header_to_tuple(brange) |
227 assert brange != () |
232 assert brange != () |
228 if brange: |
233 if brange: |
234 size = (lb - fb) |
239 size = (lb - fb) |
235 fo = RangeableFileObject(fo, (fb, lb)) |
240 fo = RangeableFileObject(fo, (fb, lb)) |
236 headers = email.message_from_string( |
241 headers = email.message_from_string( |
237 'Content-Type: %s\nContent-Length: %d\nLast-Modified: %s\n' % |
242 'Content-Type: %s\nContent-Length: %d\nLast-Modified: %s\n' % |
238 (mtype or 'text/plain', size, modified)) |
243 (mtype or 'text/plain', size, modified)) |
239 return urllib.addinfourl(fo, headers, 'file:'+file) |
244 return urlreq.addinfourl(fo, headers, 'file:'+file) |
240 |
245 |
241 |
246 |
242 # FTP Range Support |
247 # FTP Range Support |
243 # Unfortunately, a large amount of base FTP code had to be copied |
248 # Unfortunately, a large amount of base FTP code had to be copied |
244 # from urllib and urllib2 in order to insert the FTP REST command. |
249 # from urllib and urllib2 in order to insert the FTP REST command. |
245 # Code modifications for range support have been commented as |
250 # Code modifications for range support have been commented as |
246 # follows: |
251 # follows: |
247 # -- range support modifications start/end here |
252 # -- range support modifications start/end here |
248 |
253 |
249 class FTPRangeHandler(urllib2.FTPHandler): |
254 class FTPRangeHandler(urlreq.ftphandler): |
250 def ftp_open(self, req): |
255 def ftp_open(self, req): |
251 host = req.get_host() |
256 host = req.get_host() |
252 if not host: |
257 if not host: |
253 raise IOError('ftp error', 'no host given') |
258 raise IOError('ftp error', 'no host given') |
254 host, port = splitport(host) |
259 host, port = splitport(host) |
268 passwd = unquote(passwd or '') |
273 passwd = unquote(passwd or '') |
269 |
274 |
270 try: |
275 try: |
271 host = socket.gethostbyname(host) |
276 host = socket.gethostbyname(host) |
272 except socket.error as msg: |
277 except socket.error as msg: |
273 raise urllib2.URLError(msg) |
278 raise urlerr.urlerror(msg) |
274 path, attrs = splitattr(req.get_selector()) |
279 path, attrs = splitattr(req.get_selector()) |
275 dirs = path.split('/') |
280 dirs = path.split('/') |
276 dirs = map(unquote, dirs) |
281 dirs = map(unquote, dirs) |
277 dirs, file = dirs[:-1], dirs[-1] |
282 dirs, file = dirs[:-1], dirs[-1] |
278 if dirs and not dirs[0]: |
283 if dirs and not dirs[0]: |
332 |
337 |
333 def connect_ftp(self, user, passwd, host, port, dirs): |
338 def connect_ftp(self, user, passwd, host, port, dirs): |
334 fw = ftpwrapper(user, passwd, host, port, dirs) |
339 fw = ftpwrapper(user, passwd, host, port, dirs) |
335 return fw |
340 return fw |
336 |
341 |
337 class ftpwrapper(urllib.ftpwrapper): |
342 class ftpwrapper(urlreq.ftpwrapper): |
338 # range support note: |
343 # range support note: |
339 # this ftpwrapper code is copied directly from |
344 # this ftpwrapper code is copied directly from |
340 # urllib. The only enhancement is to add the rest |
345 # urllib. The only enhancement is to add the rest |
341 # argument and pass it on to ftp.ntransfercmd |
346 # argument and pass it on to ftp.ntransfercmd |
342 def retrfile(self, file, type, rest=None): |
347 def retrfile(self, file, type, rest=None): |