Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(994)

Side by Side Diff: third_party/cherrypy/lib/static.py

Issue 9368042: Add CherryPy to third_party. (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/tools/build/
Patch Set: '' Created 8 years, 10 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
« no previous file with comments | « third_party/cherrypy/lib/sessions.py ('k') | third_party/cherrypy/lib/xmlrpcutil.py » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
Property Changes:
Added: svn:eol-style
+ LF
OLDNEW
(Empty)
1 try:
2 from io import UnsupportedOperation
3 except ImportError:
4 UnsupportedOperation = object()
5 import logging
6 import mimetypes
7 mimetypes.init()
8 mimetypes.types_map['.dwg']='image/x-dwg'
9 mimetypes.types_map['.ico']='image/x-icon'
10 mimetypes.types_map['.bz2']='application/x-bzip2'
11 mimetypes.types_map['.gz']='application/x-gzip'
12
13 import os
14 import re
15 import stat
16 import time
17
18 import cherrypy
19 from cherrypy._cpcompat import ntob, unquote
20 from cherrypy.lib import cptools, httputil, file_generator_limited
21
22
23 def serve_file(path, content_type=None, disposition=None, name=None, debug=False ):
24 """Set status, headers, and body in order to serve the given path.
25
26 The Content-Type header will be set to the content_type arg, if provided.
27 If not provided, the Content-Type will be guessed by the file extension
28 of the 'path' argument.
29
30 If disposition is not None, the Content-Disposition header will be set
31 to "<disposition>; filename=<name>". If name is None, it will be set
32 to the basename of path. If disposition is None, no Content-Disposition
33 header will be written.
34 """
35
36 response = cherrypy.serving.response
37
38 # If path is relative, users should fix it by making path absolute.
39 # That is, CherryPy should not guess where the application root is.
40 # It certainly should *not* use cwd (since CP may be invoked from a
41 # variety of paths). If using tools.staticdir, you can make your relative
42 # paths become absolute by supplying a value for "tools.staticdir.root".
43 if not os.path.isabs(path):
44 msg = "'%s' is not an absolute path." % path
45 if debug:
46 cherrypy.log(msg, 'TOOLS.STATICFILE')
47 raise ValueError(msg)
48
49 try:
50 st = os.stat(path)
51 except OSError:
52 if debug:
53 cherrypy.log('os.stat(%r) failed' % path, 'TOOLS.STATIC')
54 raise cherrypy.NotFound()
55
56 # Check if path is a directory.
57 if stat.S_ISDIR(st.st_mode):
58 # Let the caller deal with it as they like.
59 if debug:
60 cherrypy.log('%r is a directory' % path, 'TOOLS.STATIC')
61 raise cherrypy.NotFound()
62
63 # Set the Last-Modified response header, so that
64 # modified-since validation code can work.
65 response.headers['Last-Modified'] = httputil.HTTPDate(st.st_mtime)
66 cptools.validate_since()
67
68 if content_type is None:
69 # Set content-type based on filename extension
70 ext = ""
71 i = path.rfind('.')
72 if i != -1:
73 ext = path[i:].lower()
74 content_type = mimetypes.types_map.get(ext, None)
75 if content_type is not None:
76 response.headers['Content-Type'] = content_type
77 if debug:
78 cherrypy.log('Content-Type: %r' % content_type, 'TOOLS.STATIC')
79
80 cd = None
81 if disposition is not None:
82 if name is None:
83 name = os.path.basename(path)
84 cd = '%s; filename="%s"' % (disposition, name)
85 response.headers["Content-Disposition"] = cd
86 if debug:
87 cherrypy.log('Content-Disposition: %r' % cd, 'TOOLS.STATIC')
88
89 # Set Content-Length and use an iterable (file object)
90 # this way CP won't load the whole file in memory
91 content_length = st.st_size
92 fileobj = open(path, 'rb')
93 return _serve_fileobj(fileobj, content_type, content_length, debug=debug)
94
95 def serve_fileobj(fileobj, content_type=None, disposition=None, name=None,
96 debug=False):
97 """Set status, headers, and body in order to serve the given file object.
98
99 The Content-Type header will be set to the content_type arg, if provided.
100
101 If disposition is not None, the Content-Disposition header will be set
102 to "<disposition>; filename=<name>". If name is None, 'filename' will
103 not be set. If disposition is None, no Content-Disposition header will
104 be written.
105
106 CAUTION: If the request contains a 'Range' header, one or more seek()s will
107 be performed on the file object. This may cause undesired behavior if
108 the file object is not seekable. It could also produce undesired results
109 if the caller set the read position of the file object prior to calling
110 serve_fileobj(), expecting that the data would be served starting from that
111 position.
112 """
113
114 response = cherrypy.serving.response
115
116 try:
117 st = os.fstat(fileobj.fileno())
118 except AttributeError:
119 if debug:
120 cherrypy.log('os has no fstat attribute', 'TOOLS.STATIC')
121 content_length = None
122 except UnsupportedOperation:
123 content_length = None
124 else:
125 # Set the Last-Modified response header, so that
126 # modified-since validation code can work.
127 response.headers['Last-Modified'] = httputil.HTTPDate(st.st_mtime)
128 cptools.validate_since()
129 content_length = st.st_size
130
131 if content_type is not None:
132 response.headers['Content-Type'] = content_type
133 if debug:
134 cherrypy.log('Content-Type: %r' % content_type, 'TOOLS.STATIC')
135
136 cd = None
137 if disposition is not None:
138 if name is None:
139 cd = disposition
140 else:
141 cd = '%s; filename="%s"' % (disposition, name)
142 response.headers["Content-Disposition"] = cd
143 if debug:
144 cherrypy.log('Content-Disposition: %r' % cd, 'TOOLS.STATIC')
145
146 return _serve_fileobj(fileobj, content_type, content_length, debug=debug)
147
148 def _serve_fileobj(fileobj, content_type, content_length, debug=False):
149 """Internal. Set response.body to the given file object, perhaps ranged."""
150 response = cherrypy.serving.response
151
152 # HTTP/1.0 didn't have Range/Accept-Ranges headers, or the 206 code
153 request = cherrypy.serving.request
154 if request.protocol >= (1, 1):
155 response.headers["Accept-Ranges"] = "bytes"
156 r = httputil.get_ranges(request.headers.get('Range'), content_length)
157 if r == []:
158 response.headers['Content-Range'] = "bytes */%s" % content_length
159 message = "Invalid Range (first-byte-pos greater than Content-Length )"
160 if debug:
161 cherrypy.log(message, 'TOOLS.STATIC')
162 raise cherrypy.HTTPError(416, message)
163
164 if r:
165 if len(r) == 1:
166 # Return a single-part response.
167 start, stop = r[0]
168 if stop > content_length:
169 stop = content_length
170 r_len = stop - start
171 if debug:
172 cherrypy.log('Single part; start: %r, stop: %r' % (start, st op),
173 'TOOLS.STATIC')
174 response.status = "206 Partial Content"
175 response.headers['Content-Range'] = (
176 "bytes %s-%s/%s" % (start, stop - 1, content_length))
177 response.headers['Content-Length'] = r_len
178 fileobj.seek(start)
179 response.body = file_generator_limited(fileobj, r_len)
180 else:
181 # Return a multipart/byteranges response.
182 response.status = "206 Partial Content"
183 try:
184 # Python 3
185 from email.generator import _make_boundary as choose_boundar y
186 except ImportError:
187 # Python 2
188 from mimetools import choose_boundary
189 boundary = choose_boundary()
190 ct = "multipart/byteranges; boundary=%s" % boundary
191 response.headers['Content-Type'] = ct
192 if "Content-Length" in response.headers:
193 # Delete Content-Length header so finalize() recalcs it.
194 del response.headers["Content-Length"]
195
196 def file_ranges():
197 # Apache compatibility:
198 yield ntob("\r\n")
199
200 for start, stop in r:
201 if debug:
202 cherrypy.log('Multipart; start: %r, stop: %r' % (sta rt, stop),
203 'TOOLS.STATIC')
204 yield ntob("--" + boundary, 'ascii')
205 yield ntob("\r\nContent-type: %s" % content_type, 'ascii ')
206 yield ntob("\r\nContent-range: bytes %s-%s/%s\r\n\r\n"
207 % (start, stop - 1, content_length), 'ascii')
208 fileobj.seek(start)
209 for chunk in file_generator_limited(fileobj, stop-start) :
210 yield chunk
211 yield ntob("\r\n")
212 # Final boundary
213 yield ntob("--" + boundary + "--", 'ascii')
214
215 # Apache compatibility:
216 yield ntob("\r\n")
217 response.body = file_ranges()
218 return response.body
219 else:
220 if debug:
221 cherrypy.log('No byteranges requested', 'TOOLS.STATIC')
222
223 # Set Content-Length and use an iterable (file object)
224 # this way CP won't load the whole file in memory
225 response.headers['Content-Length'] = content_length
226 response.body = fileobj
227 return response.body
228
229 def serve_download(path, name=None):
230 """Serve 'path' as an application/x-download attachment."""
231 # This is such a common idiom I felt it deserved its own wrapper.
232 return serve_file(path, "application/x-download", "attachment", name)
233
234
235 def _attempt(filename, content_types, debug=False):
236 if debug:
237 cherrypy.log('Attempting %r (content_types %r)' %
238 (filename, content_types), 'TOOLS.STATICDIR')
239 try:
240 # you can set the content types for a
241 # complete directory per extension
242 content_type = None
243 if content_types:
244 r, ext = os.path.splitext(filename)
245 content_type = content_types.get(ext[1:], None)
246 serve_file(filename, content_type=content_type, debug=debug)
247 return True
248 except cherrypy.NotFound:
249 # If we didn't find the static file, continue handling the
250 # request. We might find a dynamic handler instead.
251 if debug:
252 cherrypy.log('NotFound', 'TOOLS.STATICFILE')
253 return False
254
255 def staticdir(section, dir, root="", match="", content_types=None, index="",
256 debug=False):
257 """Serve a static resource from the given (root +) dir.
258
259 match
260 If given, request.path_info will be searched for the given
261 regular expression before attempting to serve static content.
262
263 content_types
264 If given, it should be a Python dictionary of
265 {file-extension: content-type} pairs, where 'file-extension' is
266 a string (e.g. "gif") and 'content-type' is the value to write
267 out in the Content-Type response header (e.g. "image/gif").
268
269 index
270 If provided, it should be the (relative) name of a file to
271 serve for directory requests. For example, if the dir argument is
272 '/home/me', the Request-URI is 'myapp', and the index arg is
273 'index.html', the file '/home/me/myapp/index.html' will be sought.
274 """
275 request = cherrypy.serving.request
276 if request.method not in ('GET', 'HEAD'):
277 if debug:
278 cherrypy.log('request.method not GET or HEAD', 'TOOLS.STATICDIR')
279 return False
280
281 if match and not re.search(match, request.path_info):
282 if debug:
283 cherrypy.log('request.path_info %r does not match pattern %r' %
284 (request.path_info, match), 'TOOLS.STATICDIR')
285 return False
286
287 # Allow the use of '~' to refer to a user's home directory.
288 dir = os.path.expanduser(dir)
289
290 # If dir is relative, make absolute using "root".
291 if not os.path.isabs(dir):
292 if not root:
293 msg = "Static dir requires an absolute dir (or root)."
294 if debug:
295 cherrypy.log(msg, 'TOOLS.STATICDIR')
296 raise ValueError(msg)
297 dir = os.path.join(root, dir)
298
299 # Determine where we are in the object tree relative to 'section'
300 # (where the static tool was defined).
301 if section == 'global':
302 section = "/"
303 section = section.rstrip(r"\/")
304 branch = request.path_info[len(section) + 1:]
305 branch = unquote(branch.lstrip(r"\/"))
306
307 # If branch is "", filename will end in a slash
308 filename = os.path.join(dir, branch)
309 if debug:
310 cherrypy.log('Checking file %r to fulfill %r' %
311 (filename, request.path_info), 'TOOLS.STATICDIR')
312
313 # There's a chance that the branch pulled from the URL might
314 # have ".." or similar uplevel attacks in it. Check that the final
315 # filename is a child of dir.
316 if not os.path.normpath(filename).startswith(os.path.normpath(dir)):
317 raise cherrypy.HTTPError(403) # Forbidden
318
319 handled = _attempt(filename, content_types)
320 if not handled:
321 # Check for an index file if a folder was requested.
322 if index:
323 handled = _attempt(os.path.join(filename, index), content_types)
324 if handled:
325 request.is_index = filename[-1] in (r"\/")
326 return handled
327
328 def staticfile(filename, root=None, match="", content_types=None, debug=False):
329 """Serve a static resource from the given (root +) filename.
330
331 match
332 If given, request.path_info will be searched for the given
333 regular expression before attempting to serve static content.
334
335 content_types
336 If given, it should be a Python dictionary of
337 {file-extension: content-type} pairs, where 'file-extension' is
338 a string (e.g. "gif") and 'content-type' is the value to write
339 out in the Content-Type response header (e.g. "image/gif").
340
341 """
342 request = cherrypy.serving.request
343 if request.method not in ('GET', 'HEAD'):
344 if debug:
345 cherrypy.log('request.method not GET or HEAD', 'TOOLS.STATICFILE')
346 return False
347
348 if match and not re.search(match, request.path_info):
349 if debug:
350 cherrypy.log('request.path_info %r does not match pattern %r' %
351 (request.path_info, match), 'TOOLS.STATICFILE')
352 return False
353
354 # If filename is relative, make absolute using "root".
355 if not os.path.isabs(filename):
356 if not root:
357 msg = "Static tool requires an absolute filename (got '%s')." % file name
358 if debug:
359 cherrypy.log(msg, 'TOOLS.STATICFILE')
360 raise ValueError(msg)
361 filename = os.path.join(root, filename)
362
363 return _attempt(filename, content_types, debug=debug)
OLDNEW
« no previous file with comments | « third_party/cherrypy/lib/sessions.py ('k') | third_party/cherrypy/lib/xmlrpcutil.py » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698