static.py (9032B)
1 #Embedded file name: C:\BuildAgent\work\20d73f1b24eed06e\eve\release\V24.01\carbon\common\lib\cherrypy\lib\static.py 2 import logging 3 import mimetypes 4 mimetypes.init() 5 mimetypes.types_map['.dwg'] = 'image/x-dwg' 6 mimetypes.types_map['.ico'] = 'image/x-icon' 7 mimetypes.types_map['.bz2'] = 'application/x-bzip2' 8 mimetypes.types_map['.gz'] = 'application/x-gzip' 9 import os 10 import re 11 import stat 12 import time 13 import cherrypy 14 from cherrypy._cpcompat import ntob, unquote 15 from cherrypy.lib import cptools, httputil, file_generator_limited 16 17 def serve_file(path, content_type = None, disposition = None, name = None, debug = False): 18 response = cherrypy.serving.response 19 if not os.path.isabs(path): 20 msg = "'%s' is not an absolute path." % path 21 if debug: 22 cherrypy.log(msg, 'TOOLS.STATICFILE') 23 raise ValueError(msg) 24 try: 25 st = os.stat(path) 26 except OSError: 27 if debug: 28 cherrypy.log('os.stat(%r) failed' % path, 'TOOLS.STATIC') 29 raise cherrypy.NotFound() 30 31 if stat.S_ISDIR(st.st_mode): 32 if debug: 33 cherrypy.log('%r is a directory' % path, 'TOOLS.STATIC') 34 raise cherrypy.NotFound() 35 response.headers['Last-Modified'] = httputil.HTTPDate(st.st_mtime) 36 cptools.validate_since() 37 if content_type is None: 38 ext = '' 39 i = path.rfind('.') 40 if i != -1: 41 ext = path[i:].lower() 42 content_type = mimetypes.types_map.get(ext, None) 43 if content_type is not None: 44 response.headers['Content-Type'] = content_type 45 if debug: 46 cherrypy.log('Content-Type: %r' % content_type, 'TOOLS.STATIC') 47 cd = None 48 if disposition is not None: 49 if name is None: 50 name = os.path.basename(path) 51 cd = '%s; filename="%s"' % (disposition, name) 52 response.headers['Content-Disposition'] = cd 53 if debug: 54 cherrypy.log('Content-Disposition: %r' % cd, 'TOOLS.STATIC') 55 content_length = st.st_size 56 fileobj = open(path, 'rb') 57 return _serve_fileobj(fileobj, content_type, content_length, debug=debug) 58 59 60 def serve_fileobj(fileobj, content_type = None, disposition = None, name = None, debug = False): 61 response = cherrypy.serving.response 62 try: 63 st = os.fstat(fileobj.fileno()) 64 except AttributeError: 65 if debug: 66 cherrypy.log('os has no fstat attribute', 'TOOLS.STATIC') 67 content_length = None 68 else: 69 response.headers['Last-Modified'] = httputil.HTTPDate(st.st_mtime) 70 cptools.validate_since() 71 content_length = st.st_size 72 73 if content_type is not None: 74 response.headers['Content-Type'] = content_type 75 if debug: 76 cherrypy.log('Content-Type: %r' % content_type, 'TOOLS.STATIC') 77 cd = None 78 if disposition is not None: 79 if name is None: 80 cd = disposition 81 else: 82 cd = '%s; filename="%s"' % (disposition, name) 83 response.headers['Content-Disposition'] = cd 84 if debug: 85 cherrypy.log('Content-Disposition: %r' % cd, 'TOOLS.STATIC') 86 return _serve_fileobj(fileobj, content_type, content_length, debug=debug) 87 88 89 def _serve_fileobj(fileobj, content_type, content_length, debug = False): 90 response = cherrypy.serving.response 91 request = cherrypy.serving.request 92 if request.protocol >= (1, 1): 93 response.headers['Accept-Ranges'] = 'bytes' 94 r = httputil.get_ranges(request.headers.get('Range'), content_length) 95 if r == []: 96 response.headers['Content-Range'] = 'bytes */%s' % content_length 97 message = 'Invalid Range (first-byte-pos greater than Content-Length)' 98 if debug: 99 cherrypy.log(message, 'TOOLS.STATIC') 100 raise cherrypy.HTTPError(416, message) 101 if r: 102 if len(r) == 1: 103 start, stop = r[0] 104 if stop > content_length: 105 stop = content_length 106 r_len = stop - start 107 if debug: 108 cherrypy.log('Single part; start: %r, stop: %r' % (start, stop), 'TOOLS.STATIC') 109 response.status = '206 Partial Content' 110 response.headers['Content-Range'] = 'bytes %s-%s/%s' % (start, stop - 1, content_length) 111 response.headers['Content-Length'] = r_len 112 fileobj.seek(start) 113 response.body = file_generator_limited(fileobj, r_len) 114 else: 115 response.status = '206 Partial Content' 116 from mimetools import choose_boundary 117 boundary = choose_boundary() 118 ct = 'multipart/byteranges; boundary=%s' % boundary 119 response.headers['Content-Type'] = ct 120 if 'Content-Length' in response.headers: 121 del response.headers['Content-Length'] 122 123 def file_ranges(): 124 yield ntob('\r\n') 125 for start, stop in r: 126 if debug: 127 cherrypy.log('Multipart; start: %r, stop: %r' % (start, stop), 'TOOLS.STATIC') 128 yield ntob('--' + boundary, 'ascii') 129 yield ntob('\r\nContent-type: %s' % content_type, 'ascii') 130 yield ntob('\r\nContent-range: bytes %s-%s/%s\r\n\r\n' % (start, stop - 1, content_length), 'ascii') 131 fileobj.seek(start) 132 for chunk in file_generator_limited(fileobj, stop - start): 133 yield chunk 134 135 yield ntob('\r\n') 136 137 yield ntob('--' + boundary + '--', 'ascii') 138 yield ntob('\r\n') 139 140 response.body = file_ranges() 141 return response.body 142 if debug: 143 cherrypy.log('No byteranges requested', 'TOOLS.STATIC') 144 response.headers['Content-Length'] = content_length 145 response.body = fileobj 146 return response.body 147 148 149 def serve_download(path, name = None): 150 return serve_file(path, 'application/x-download', 'attachment', name) 151 152 153 def _attempt(filename, content_types, debug = False): 154 if debug: 155 cherrypy.log('Attempting %r (content_types %r)' % (filename, content_types), 'TOOLS.STATICDIR') 156 try: 157 content_type = None 158 if content_types: 159 r, ext = os.path.splitext(filename) 160 content_type = content_types.get(ext[1:], None) 161 serve_file(filename, content_type=content_type, debug=debug) 162 return True 163 except cherrypy.NotFound: 164 if debug: 165 cherrypy.log('NotFound', 'TOOLS.STATICFILE') 166 return False 167 168 169 def staticdir(section, dir, root = '', match = '', content_types = None, index = '', debug = False): 170 request = cherrypy.serving.request 171 if request.method not in ('GET', 'HEAD'): 172 if debug: 173 cherrypy.log('request.method not GET or HEAD', 'TOOLS.STATICDIR') 174 return False 175 if match and not re.search(match, request.path_info): 176 if debug: 177 cherrypy.log('request.path_info %r does not match pattern %r' % (request.path_info, match), 'TOOLS.STATICDIR') 178 return False 179 dir = os.path.expanduser(dir) 180 if not os.path.isabs(dir): 181 if not root: 182 msg = 'Static dir requires an absolute dir (or root).' 183 if debug: 184 cherrypy.log(msg, 'TOOLS.STATICDIR') 185 raise ValueError(msg) 186 dir = os.path.join(root, dir) 187 if section == 'global': 188 section = '/' 189 section = section.rstrip('\\/') 190 branch = request.path_info[len(section) + 1:] 191 branch = unquote(branch.lstrip('\\/')) 192 filename = os.path.join(dir, branch) 193 if debug: 194 cherrypy.log('Checking file %r to fulfill %r' % (filename, request.path_info), 'TOOLS.STATICDIR') 195 if not os.path.normpath(filename).startswith(os.path.normpath(dir)): 196 raise cherrypy.HTTPError(403) 197 handled = _attempt(filename, content_types) 198 if not handled: 199 if index: 200 handled = _attempt(os.path.join(filename, index), content_types) 201 if handled: 202 request.is_index = filename[-1] in '\\/' 203 return handled 204 205 206 def staticfile(filename, root = None, match = '', content_types = None, debug = False): 207 request = cherrypy.serving.request 208 if request.method not in ('GET', 'HEAD'): 209 if debug: 210 cherrypy.log('request.method not GET or HEAD', 'TOOLS.STATICFILE') 211 return False 212 if match and not re.search(match, request.path_info): 213 if debug: 214 cherrypy.log('request.path_info %r does not match pattern %r' % (request.path_info, match), 'TOOLS.STATICFILE') 215 return False 216 if not os.path.isabs(filename): 217 if not root: 218 msg = "Static tool requires an absolute filename (got '%s')." % filename 219 if debug: 220 cherrypy.log(msg, 'TOOLS.STATICFILE') 221 raise ValueError(msg) 222 filename = os.path.join(root, filename) 223 return _attempt(filename, content_types, debug=debug)