1 import mimetypes
2 mimetypes.init()
3 mimetypes.types_map['.dwg']='image/x-dwg'
4 mimetypes.types_map['.ico']='image/x-icon'
5
6 import os
7 import re
8 import stat
9 import time
10 import urllib
11
12 import cherrypy
13 from cherrypy.lib import cptools, http, file_generator_limited
14
15
16 -def serve_file(path, content_type=None, disposition=None, name=None):
17 """Set status, headers, and body in order to serve the given file.
18
19 The Content-Type header will be set to the content_type arg, if provided.
20 If not provided, the Content-Type will be guessed by the file extension
21 of the 'path' argument.
22
23 If disposition is not None, the Content-Disposition header will be set
24 to "<disposition>; filename=<name>". If name is None, it will be set
25 to the basename of path. If disposition is None, no Content-Disposition
26 header will be written.
27 """
28
29 response = cherrypy.response
30
31
32
33
34
35
36 if not os.path.isabs(path):
37 raise ValueError("'%s' is not an absolute path." % path)
38
39 try:
40 st = os.stat(path)
41 except OSError:
42 raise cherrypy.NotFound()
43
44
45 if stat.S_ISDIR(st.st_mode):
46
47 raise cherrypy.NotFound()
48
49
50
51 response.headers['Last-Modified'] = http.HTTPDate(st.st_mtime)
52 cptools.validate_since()
53
54 if content_type is None:
55
56 ext = ""
57 i = path.rfind('.')
58 if i != -1:
59 ext = path[i:].lower()
60 content_type = mimetypes.types_map.get(ext, "text/plain")
61 response.headers['Content-Type'] = content_type
62
63 if disposition is not None:
64 if name is None:
65 name = os.path.basename(path)
66 cd = '%s; filename="%s"' % (disposition, name)
67 response.headers["Content-Disposition"] = cd
68
69
70
71 c_len = st.st_size
72 bodyfile = open(path, 'rb')
73
74
75 if cherrypy.request.protocol >= (1, 1):
76 response.headers["Accept-Ranges"] = "bytes"
77 r = http.get_ranges(cherrypy.request.headers.get('Range'), c_len)
78 if r == []:
79 response.headers['Content-Range'] = "bytes */%s" % c_len
80 message = "Invalid Range (first-byte-pos greater than Content-Length)"
81 raise cherrypy.HTTPError(416, message)
82 if r:
83 if len(r) == 1:
84
85 start, stop = r[0]
86 if stop > c_len:
87 stop = c_len
88 r_len = stop - start
89 response.status = "206 Partial Content"
90 response.headers['Content-Range'] = ("bytes %s-%s/%s" %
91 (start, stop - 1, c_len))
92 response.headers['Content-Length'] = r_len
93 bodyfile.seek(start)
94 response.body = file_generator_limited(bodyfile, r_len)
95 else:
96
97 response.status = "206 Partial Content"
98 import mimetools
99 boundary = mimetools.choose_boundary()
100 ct = "multipart/byteranges; boundary=%s" % boundary
101 response.headers['Content-Type'] = ct
102 if response.headers.has_key("Content-Length"):
103
104 del response.headers["Content-Length"]
105
106 def file_ranges():
107
108 yield "\r\n"
109
110 for start, stop in r:
111 yield "--" + boundary
112 yield "\r\nContent-type: %s" % content_type
113 yield ("\r\nContent-range: bytes %s-%s/%s\r\n\r\n"
114 % (start, stop - 1, c_len))
115 bodyfile.seek(start)
116 for chunk in file_generator_limited(bodyfile, stop-start):
117 yield chunk
118 yield "\r\n"
119
120 yield "--" + boundary + "--"
121
122
123 yield "\r\n"
124 response.body = file_ranges()
125 else:
126 response.headers['Content-Length'] = c_len
127 response.body = bodyfile
128 else:
129 response.headers['Content-Length'] = c_len
130 response.body = bodyfile
131 return response.body
132
134 """Serve 'path' as an application/x-download attachment."""
135
136 return serve_file(path, "application/x-download", "attachment", name)
137
138
140 try:
141
142
143 content_type = None
144 if content_types:
145 r, ext = os.path.splitext(filename)
146 content_type = content_types.get(ext[1:], None)
147 serve_file(filename, content_type=content_type)
148 return True
149 except cherrypy.NotFound:
150
151
152 return False
153
154 -def staticdir(section, dir, root="", match="", content_types=None, index=""):
155 """Serve a static resource from the given (root +) dir.
156
157 If 'match' is given, request.path_info will be searched for the given
158 regular expression before attempting to serve static content.
159
160 If content_types is given, it should be a Python dictionary of
161 {file-extension: content-type} pairs, where 'file-extension' is
162 a string (e.g. "gif") and 'content-type' is the value to write
163 out in the Content-Type response header (e.g. "image/gif").
164
165 If 'index' is provided, it should be the (relative) name of a file to
166 serve for directory requests. For example, if the dir argument is
167 '/home/me', the Request-URI is 'myapp', and the index arg is
168 'index.html', the file '/home/me/myapp/index.html' will be sought.
169 """
170 if cherrypy.request.method not in ('GET', 'HEAD'):
171 return False
172
173 if match and not re.search(match, cherrypy.request.path_info):
174 return False
175
176
177 dir = os.path.expanduser(dir)
178
179
180 if not os.path.isabs(dir):
181 if not root:
182 msg = "Static dir requires an absolute dir (or root)."
183 raise ValueError(msg)
184 dir = os.path.join(root, dir)
185
186
187
188 if section == 'global':
189 section = "/"
190 section = section.rstrip(r"\/")
191 branch = cherrypy.request.path_info[len(section) + 1:]
192 branch = urllib.unquote(branch.lstrip(r"\/"))
193
194
195 filename = os.path.join(dir, branch)
196
197
198
199
200 if not os.path.normpath(filename).startswith(os.path.normpath(dir)):
201 raise cherrypy.HTTPError(403)
202
203 handled = _attempt(filename, content_types)
204 if not handled:
205
206 if index:
207 handled = _attempt(os.path.join(filename, index), content_types)
208 if handled:
209 cherrypy.request.is_index = filename[-1] in (r"\/")
210 return handled
211
212 -def staticfile(filename, root=None, match="", content_types=None):
213 """Serve a static resource from the given (root +) filename.
214
215 If 'match' is given, request.path_info will be searched for the given
216 regular expression before attempting to serve static content.
217
218 If content_types is given, it should be a Python dictionary of
219 {file-extension: content-type} pairs, where 'file-extension' is
220 a string (e.g. "gif") and 'content-type' is the value to write
221 out in the Content-Type response header (e.g. "image/gif").
222 """
223 if cherrypy.request.method not in ('GET', 'HEAD'):
224 return False
225
226 if match and not re.search(match, cherrypy.request.path_info):
227 return False
228
229
230 if not os.path.isabs(filename):
231 if not root:
232 msg = "Static tool requires an absolute filename (got '%s')." % filename
233 raise ValueError(msg)
234 filename = os.path.join(root, filename)
235
236 return _attempt(filename, content_types)
237