From: David Wolever Date: Tue, 21 May 2013 04:37:35 +0000 (-0400) Subject: Merge master X-Git-Tag: v3.1.0~56^2~2 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=96e0ea552ffc16a84ddb0adb2aab8f0c8654b79f;p=thirdparty%2Ftornado.git Merge master --- 96e0ea552ffc16a84ddb0adb2aab8f0c8654b79f diff --cc tornado/web.py index 0ffd9e164,8d90f027f..51c652c3e --- a/tornado/web.py +++ b/tornado/web.py @@@ -1729,36 -1777,81 +1778,82 @@@ class StaticFileHandler(RequestHandler) self.get(path, include_body=False) def get(self, path, include_body=True): - path = self.parse_url_path(path) - abspath = os.path.abspath(os.path.join(self.root, path)) - # os.path.abspath strips a trailing / - # it needs to be temporarily added back for requests to root/ - if not (abspath + os.path.sep).startswith(self.root): - raise HTTPError(403, "%s is not in root static directory", path) - if os.path.isdir(abspath) and self.default_filename is not None: - # need to look at the request.path here for when path is empty - # but there is some prefix to the path that was already - # trimmed by the routing - if not self.request.path.endswith("/"): - self.redirect(self.request.path + "/") + # Set up our path instance variables. + self.path = self.parse_url_path(path) + del path # make sure we don't refer to path instead of self.path again + absolute_path = self.get_absolute_path(self.settings, self.path) + self.absolute_path = self.validate_absolute_path(absolute_path) + if self.absolute_path is None: + return + + self.modified = self.get_modified_time() + self.set_headers() + + if self.should_return_304(): + self.set_status(304) + return + + request_range = None + range_header = self.request.headers.get("Range") + if range_header: + request_range = httputil._parse_request_range(range_header) + if not request_range: + self.set_status(416) # Range Not Satisfiable + self.set_header("Content-Type", "text/plain") + self.write("The provided Range header is not valid: %r\n" + "Note: multiple ranges are not supported." + % range_header) return - abspath = os.path.join(abspath, self.default_filename) - if not os.path.exists(abspath): - raise HTTPError(404) - if not os.path.isfile(abspath): - raise HTTPError(403, "%s is not a file", path) - stat_result = os.stat(abspath) - modified = datetime.datetime.utcfromtimestamp(stat_result[stat.ST_MTIME]) + if request_range: + start, end = request_range + size = self.get_content_size() + if start < 0: + start += size - self.set_status(206) # Partial Content ++ if size != (end or size) - (start or 0): ++ self.set_status(206) # Partial Content + self.set_header("Content-Range", + httputil._get_content_range(start, end, size)) + else: + start = end = None + content = self.get_content(self.absolute_path, start, end) + if isinstance(content, bytes_type): + content = [content] + content_length = 0 + for chunk in content: + if include_body: + self.write(chunk) + else: + content_length += len(chunk) + if not include_body: + assert self.request.method == "HEAD" + self.set_header("Content-Length", content_length) + + def compute_etag(self): + """Sets the ``Etag`` header based on static url version. + + This allows efficient ``If-None-Match`` checks against cached + versions, and sends the correct ``Etag`` for a partial response + (i.e. the same ``Etag`` as the full file). + """ + version_hash = self._get_cached_version(self.absolute_path) + if not version_hash: + return None + return '"%s"' %(version_hash, ) - self.set_header("Last-Modified", modified) + def set_headers(self): + """Sets the content and caching headers on the response.""" + self.set_header("Accept-Ranges", "bytes") + self.set_etag_header() - mime_type, encoding = mimetypes.guess_type(abspath) - if mime_type: - self.set_header("Content-Type", mime_type) + if self.modified is not None: + self.set_header("Last-Modified", self.modified) - cache_time = self.get_cache_time(path, modified, mime_type) + content_type = self.get_content_type() + if content_type: + self.set_header("Content-Type", content_type) + cache_time = self.get_cache_time(self.path, self.modified, content_type) if cache_time > 0: self.set_header("Expires", datetime.datetime.utcnow() + datetime.timedelta(seconds=cache_time))