Package cherrypy :: Package lib :: Module static
[hide private]
[frames] | no frames]

Source Code for Module cherrypy.lib.static

  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 stat 
 10  import time 
 11  import urllib 
 12   
 13  import cherrypy 
 14  from cherrypy.lib import cptools, http 
 15   
 16   
17 -def serve_file(path, content_type=None, disposition=None, name=None):
18 """Set status, headers, and body in order to serve the given file. 19 20 The Content-Type header will be set to the content_type arg, if provided. 21 If not provided, the Content-Type will be guessed by its extension. 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 # If path is relative, users should fix it by making path absolute. 32 # That is, CherryPy should not guess where the application root is. 33 # It certainly should *not* use cwd (since CP may be invoked from a 34 # variety of paths). If using tools.static, you can make your relative 35 # paths become absolute by supplying a value for "tools.static.root". 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 # Check if path is a directory. 45 if stat.S_ISDIR(st.st_mode): 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(st.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 = st.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 if response.headers.has_key("Content-Length"): 100 # Delete Content-Length header so finalize() recalcs it. 101 del response.headers["Content-Length"] 102 103 def file_ranges(): 104 # Apache compatibility: 105 yield "\r\n" 106 107 for start, stop in r: 108 yield "--" + boundary 109 yield "\r\nContent-type: %s" % content_type 110 yield ("\r\nContent-range: bytes %s-%s/%s\r\n\r\n" 111 % (start, stop - 1, c_len)) 112 bodyfile.seek(start) 113 yield bodyfile.read(stop - start) 114 yield "\r\n" 115 # Final boundary 116 yield "--" + boundary + "--" 117 118 # Apache compatibility: 119 yield "\r\n"
120 response.body = file_ranges() 121 else: 122 response.headers['Content-Length'] = c_len 123 response.body = bodyfile 124 else: 125 response.headers['Content-Length'] = c_len 126 response.body = bodyfile 127 return response.body 128
129 -def serve_download(path, name=None):
130 """Serve 'path' as an application/x-download attachment.""" 131 # This is such a common idiom I felt it deserved its own wrapper. 132 return serve_file(path, "application/x-download", "attachment", name)
133 134
135 -def _attempt(filename, content_types):
136 try: 137 # you can set the content types for a 138 # complete directory per extension 139 content_type = None 140 if content_types: 141 r, ext = os.path.splitext(filename) 142 content_type = content_types.get(ext[1:], None) 143 serve_file(filename, content_type=content_type) 144 return True 145 except cherrypy.NotFound: 146 # If we didn't find the static file, continue handling the 147 # request. We might find a dynamic handler instead. 148 return False
149
150 -def staticdir(section, dir, root="", match="", content_types=None, index=""):
151 """Serve a static resource from the given (root +) dir.""" 152 if match and not re.search(match, cherrypy.request.path_info): 153 return False 154 155 # If dir is relative, make absolute using "root". 156 if not os.path.isabs(dir): 157 if not root: 158 msg = "Static dir requires an absolute dir (or root)." 159 raise ValueError(msg) 160 dir = os.path.join(root, dir) 161 162 # Determine where we are in the object tree relative to 'section' 163 # (where the static tool was defined). 164 if section == 'global': 165 section = "/" 166 section = section.rstrip(r"\/") 167 branch = cherrypy.request.path_info[len(section) + 1:] 168 branch = urllib.unquote(branch.lstrip(r"\/")) 169 170 # If branch is "", filename will end in a slash 171 filename = os.path.join(dir, branch) 172 173 # There's a chance that the branch pulled from the URL might 174 # have ".." or similar uplevel attacks in it. Check that the final 175 # filename is a child of dir. 176 if not os.path.normpath(filename).startswith(os.path.normpath(dir)): 177 raise cherrypy.HTTPError(403) # Forbidden 178 179 handled = _attempt(filename, content_types) 180 if not handled: 181 # Check for an index file if a folder was requested. 182 if index and filename[-1] in (r"\/"): 183 handled = _attempt(os.path.join(filename, index), content_types) 184 return handled
185
186 -def staticfile(filename, root=None, match="", content_types=None):
187 """Serve a static resource from the given (root +) filename.""" 188 if match and not re.search(match, cherrypy.request.path_info): 189 return False 190 191 # If filename is relative, make absolute using "root". 192 if not os.path.isabs(filename): 193 if not root: 194 msg = "Static tool requires an absolute filename (got '%s')." % filename 195 raise ValueError(msg) 196 filename = os.path.join(root, filename) 197 198 return _attempt(filename, content_types)
199