Download Install Tutorial Docs FAQ Tools WikiLicense Team IRC Planet Involvement Shop Book

root/branches/cp3-wsgi-remix/lib/static.py

Revision 1243 (checked in by fumanchu, 2 years ago)

Lots of mixedCase to lower_with_underscores.

  • Property svn:eol-style set to native
Line 
1 import mimetools
2 import mimetypes
3 mimetypes.init()
4 mimetypes.types_map['.dwg']='image/x-dwg'
5 mimetypes.types_map['.ico']='image/x-icon'
6
7 import os
8 import re
9 import time
10 import urllib
11
12 import cherrypy
13 from cherrypy.lib import cptools, http
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_ype arg, if provided.
20     If not provided, the Content-Type will be guessed by its extension.
21     
22     If disposition is not None, the Content-Disposition header will be set
23     to "<disposition>; filename=<name>". If name is None, it will be set
24     to the basename of path. If disposition is None, no Content-Disposition
25     header will be written.
26     """
27    
28     response = cherrypy.response
29    
30     # If path is relative, users should fix it by making path absolute.
31     # That is, CherryPy should not guess where the application root is.
32     # It certainly should *not* use cwd (since CP may be invoked from a
33     # variety of paths). If using tools.static, you can make your relative
34     # paths become absolute by supplying a value for "tools.static.root".
35     if not os.path.isabs(path):
36         raise ValueError("'%s' is not an absolute path." % path)
37    
38     try:
39         stat = os.stat(path)
40     except OSError:
41         if cherrypy.config.get('log_file_not_found', False):
42             cherrypy.log("    NOT FOUND file: %s" % path, "DEBUG")
43         raise cherrypy.NotFound()
44    
45     if os.path.isdir(path):
46         # Let the caller deal with it as they like.
47         raise cherrypy.NotFound()
48    
49     # Set the Last-Modified response header, so that
50     # modified-since validation code can work.
51     response.headers['Last-Modified'] = http.HTTPDate(stat.st_mtime)
52     cptools.validate_since()
53    
54     if content_type is None:
55         # Set content-type based on filename extension
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     # Set Content-Length and use an iterable (file object)
70     #   this way CP won't load the whole file in memory
71     c_len = stat.st_size
72     bodyfile = open(path, 'rb')
73    
74     # HTTP/1.0 didn't have Range/Accept-Ranges headers, or the 206 code
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                 # Return a single-part response.
85                 start, stop = r[0]
86                 r_len = stop - start
87                 response.status = "206 Partial Content"
88                 response.headers['Content-Range'] = ("bytes %s-%s/%s" %
89                                                        (start, stop - 1, c_len))
90                 response.headers['Content-Length'] = r_len
91                 bodyfile.seek(start)
92                 response.body = bodyfile.read(r_len)
93             else:
94                 # Return a multipart/byteranges response.
95                 response.status = "206 Partial Content"
96                 boundary = mimetools.choose_boundary()
97                 ct = "multipart/byteranges; boundary=%s" % boundary
98                 response.headers['Content-Type'] = ct
99 ##                del response.headers['Content-Length']
100                
101                 def file_ranges():
102                     for start, stop in r:
103                         yield "--" + boundary
104                         yield "\nContent-type: %s" % content_type
105                         yield ("\nContent-range: bytes %s-%s/%s\n\n"
106                                % (start, stop - 1, c_len))
107                         bodyfile.seek(start)
108                         yield bodyfile.read((stop + 1) - start)
109                         yield "\n"
110                     # Final boundary
111                     yield "--" + boundary
112                 response.body = file_ranges()
113         else:
114             response.headers['Content-Length'] = c_len
115             response.body = bodyfile
116     else:
117         response.headers['Content-Length'] = c_len
118         response.body = bodyfile
119     return response.body
120
121 def serve_download(path, name=None):
122     """Serve 'path' as an application/x-download attachment."""
123     # This is such a common idiom I felt it deserved its own wrapper.
124     return serve_file(path, "application/x-download", "attachment", name)
125
126
127 def _attempt(filename, content_types):
128     try:
129         # you can set the content types for a
130         # complete directory per extension
131         content_type = None
132         if content_types:
133             r, ext = os.path.splitext(filename)
134             content_type = content_types.get(ext[1:], None)
135         serve_file(filename, content_type=content_type)
136         return True
137     except cherrypy.NotFound:
138         # If we didn't find the static file, continue handling the
139         # request. We might find a dynamic handler instead.
140         return False
141
142 def staticdir(section, dir, root="", match="", content_types=None, index=""):
143     """Serve a static resource from the given (root +) dir."""
144     if match and not re.search(match, cherrypy.request.path_info):
145         return False
146    
147     # If dir is relative, make absolute using "root".
148     if not os.path.isabs(dir):
149         if not root:
150             msg = "Static dir requires an absolute dir (or root)."
151             raise cherrypy.WrongConfigValue(msg)
152         dir = os.path.join(root, dir)
153    
154     # Determine where we are in the object tree relative to 'section'
155     # (where the static tool was defined).
156     if section == 'global':
157         section = "/"
158     section = section.rstrip(r"\/")
159     branch = cherrypy.request.path_info[len(section) + 1:]
160     branch = urllib.unquote(branch.lstrip(r"\/"))
161    
162     # If branch is "", filename will end in a slash
163     filename = os.path.join(dir, branch)
164    
165     # There's a chance that the branch pulled from the URL might
166     # have ".." or similar uplevel attacks in it. Check that the final
167     # filename is a child of dir.
168     if not os.path.normpath(filename).startswith(os.path.normpath(dir)):
169         raise cherrypy.HTTPError(403) # Forbidden
170    
171     handled = _attempt(filename, content_types)
172     if not handled:
173         # Check for an index file if a folder was requested.
174         if index and filename[-1] in (r"\/"):
175             handled = _attempt(os.path.join(filename, index), content_types)
176     return handled
177
178 def staticfile(filename, root=None, match="", content_types=None):
179     """Serve a static resource from the given (root +) filename."""
180     if match and not re.search(match, cherrypy.request.path_info):
181         return False
182    
183     # If filename is relative, make absolute using "root".
184     if not os.path.isabs(filename):
185         if not root:
186             msg = "Static tool requires an absolute filename (got '%s')." % filename
187             raise cherrypy.WrongConfigValue(msg)
188         filename = os.path.join(root, filename)
189    
190     return _attempt(filename, content_types)
Note: See TracBrowser for help on using the browser.

Hosted by WebFaction

Log in as guest/cpguest to create tickets