| 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 |
|
|---|
| 31 |
|
|---|
| 32 |
|
|---|
| 33 |
|
|---|
| 34 |
|
|---|
| 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 |
|
|---|
| 47 |
raise cherrypy.NotFound() |
|---|
| 48 |
|
|---|
| 49 |
|
|---|
| 50 |
|
|---|
| 51 |
response.headers['Last-Modified'] = http.HTTPDate(stat.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 = stat.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 |
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 |
|
|---|
| 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 |
|
|---|
| 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 |
|
|---|
| 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 |
|
|---|
| 124 |
return serve_file(path, "application/x-download", "attachment", name) |
|---|
| 125 |
|
|---|
| 126 |
|
|---|
| 127 |
def _attempt(filename, content_types): |
|---|
| 128 |
try: |
|---|
| 129 |
|
|---|
| 130 |
|
|---|
| 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 |
|
|---|
| 139 |
|
|---|
| 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 |
|
|---|
| 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 |
|
|---|
| 155 |
|
|---|
| 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 |
|
|---|
| 163 |
filename = os.path.join(dir, branch) |
|---|
| 164 |
|
|---|
| 165 |
|
|---|
| 166 |
|
|---|
| 167 |
|
|---|
| 168 |
if not os.path.normpath(filename).startswith(os.path.normpath(dir)): |
|---|
| 169 |
raise cherrypy.HTTPError(403) |
|---|
| 170 |
|
|---|
| 171 |
handled = _attempt(filename, content_types) |
|---|
| 172 |
if not handled: |
|---|
| 173 |
|
|---|
| 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 |
|
|---|
| 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) |
|---|