source file: /Library/Python/2.3/site-packages/CherryPy-3.0.1-py2.3.egg/cherrypy/lib/static.py
file stats: 119 lines, 17 executed: 14.3% covered
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)