1
2
3
4 """
5 This file is part of the web2py Web Framework
6 Copyrighted by Massimo Di Pierro <mdipierro@cs.depaul.edu>
7 License: LGPLv3 (http://www.gnu.org/licenses/lgpl.html)
8 """
9
10 import os
11 import stat
12 import time
13 import re
14 import errno
15 import rewrite
16 from gluon.http import HTTP
17 from gluon.contenttype import contenttype
18
19
20 regex_start_range = re.compile('\d+(?=\-)')
21 regex_stop_range = re.compile('(?<=\-)\d+')
22
23 DEFAULT_CHUNK_SIZE = 64 * 1024
24
25
27 offset = 0
28 while bytes is None or offset < bytes:
29 if not bytes is None and bytes - offset < chunk_size:
30 chunk_size = bytes - offset
31 data = stream.read(chunk_size)
32 length = len(data)
33 if not length:
34 break
35 else:
36 yield data
37 if length < chunk_size:
38 break
39 offset += length
40 stream.close()
41
42
51 if error_message is None:
52 error_message = rewrite.THREAD_LOCAL.routes.error_message % 'invalid request'
53 try:
54 fp = open(static_file)
55 except IOError, e:
56 if e[0] == errno.EISDIR:
57 raise HTTP(403, error_message, web2py_error='file is a directory')
58 elif e[0] == errno.EACCES:
59 raise HTTP(403, error_message, web2py_error='inaccessible file')
60 else:
61 raise HTTP(404, error_message, web2py_error='invalid file')
62 else:
63 fp.close()
64 stat_file = os.stat(static_file)
65 fsize = stat_file[stat.ST_SIZE]
66 modified = stat_file[stat.ST_MTIME]
67 mtime = time.strftime('%a, %d %b %Y %H:%M:%S GMT', time.gmtime(modified))
68 headers.setdefault('Content-Type', contenttype(static_file))
69 headers.setdefault('Last-Modified', mtime)
70 headers.setdefault('Pragma', 'cache')
71 headers.setdefault('Cache-Control', 'private')
72
73
74 if status == 200:
75 if request and request.env.http_if_modified_since == mtime:
76 raise HTTP(304, **{'Content-Type': headers['Content-Type']})
77
78 elif request and request.env.http_range:
79 start_items = regex_start_range.findall(request.env.http_range)
80 if not start_items:
81 start_items = [0]
82 stop_items = regex_stop_range.findall(request.env.http_range)
83 if not stop_items or int(stop_items[0]) > fsize - 1:
84 stop_items = [fsize - 1]
85 part = (int(start_items[0]), int(stop_items[0]), fsize)
86 bytes = part[1] - part[0] + 1
87 try:
88 stream = open(static_file, 'rb')
89 except IOError, e:
90 if e[0] in (errno.EISDIR, errno.EACCES):
91 raise HTTP(403)
92 else:
93 raise HTTP(404)
94 stream.seek(part[0])
95 headers['Content-Range'] = 'bytes %i-%i/%i' % part
96 headers['Content-Length'] = '%i' % bytes
97 status = 206
98
99 if status != 206:
100 enc = request.env.http_accept_encoding
101 if enc and 'gzip' in enc and not 'Content-Encoding' in headers:
102 gzipped = static_file + '.gz'
103 if os.path.isfile(gzipped) and os.path.getmtime(gzipped) >= modified:
104 static_file = gzipped
105 fsize = os.path.getsize(gzipped)
106 headers['Content-Encoding'] = 'gzip'
107 headers['Vary'] = 'Accept-Encoding'
108 try:
109 stream = open(static_file, 'rb')
110 except IOError, e:
111
112 if e[0] in (errno.EISDIR, errno.EACCES):
113 raise HTTP(403)
114 else:
115 raise HTTP(404)
116 headers['Content-Length'] = fsize
117 bytes = None
118 if request and request.env.web2py_use_wsgi_file_wrapper:
119 wrapped = request.env.wsgi_file_wrapper(stream, chunk_size)
120 else:
121 wrapped = streamer(stream, chunk_size=chunk_size, bytes=bytes)
122 raise HTTP(status, wrapped, **headers)
123