app: restrict static file size to the detected content length
Some static files, such as logs, can change size during a request. This results in a content length mismatch and error. Cap the amount read based on the original content length. Signed-off-by: Eric Callahan <arksine.code@gmail.com>
This commit is contained in:
parent
087240aa67
commit
daf3b202c3
|
@ -9,6 +9,8 @@ import mimetypes
|
|||
import logging
|
||||
import json
|
||||
import tornado
|
||||
import tornado.iostream
|
||||
import tornado.httputil
|
||||
from inspect import isclass
|
||||
from tornado.escape import url_unescape
|
||||
from tornado.routing import Rule, PathMatches, AnyMatches
|
||||
|
@ -391,6 +393,92 @@ class FileRequestHandler(AuthorizedFileHandler):
|
|||
raise tornado.web.HTTPError(e.status_code, str(e))
|
||||
self.finish({'result': filename})
|
||||
|
||||
async def get(self, path: str, include_body: bool = True) -> None:
|
||||
# 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.root, self.path)
|
||||
self.absolute_path = self.validate_absolute_path(
|
||||
self.root, 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:
|
||||
# As per RFC 2616 14.16, if an invalid Range header is specified,
|
||||
# the request will be treated as if the header didn't exist.
|
||||
request_range = tornado.httputil._parse_request_range(range_header)
|
||||
|
||||
size = self.get_content_size()
|
||||
if request_range:
|
||||
start, end = request_range
|
||||
if start is not None and start < 0:
|
||||
start += size
|
||||
if start < 0:
|
||||
start = 0
|
||||
if (
|
||||
start is not None
|
||||
and (start >= size or (end is not None and start >= end))
|
||||
) or end == 0:
|
||||
# As per RFC 2616 14.35.1, a range is not satisfiable only: if
|
||||
# the first requested byte is equal to or greater than the
|
||||
# content, or when a suffix with length 0 is specified.
|
||||
# https://tools.ietf.org/html/rfc7233#section-2.1
|
||||
# A byte-range-spec is invalid if the last-byte-pos value is
|
||||
# present and less than the first-byte-pos.
|
||||
self.set_status(416) # Range Not Satisfiable
|
||||
self.set_header("Content-Type", "text/plain")
|
||||
self.set_header("Content-Range", "bytes */%s" % (size,))
|
||||
return
|
||||
if end is not None and end > size:
|
||||
# Clients sometimes blindly use a large range to limit their
|
||||
# download size; cap the endpoint at the actual file size.
|
||||
end = size
|
||||
# Note: only return HTTP 206 if less than the entire range has been
|
||||
# requested. Not only is this semantically correct, but Chrome
|
||||
# refuses to play audio if it gets an HTTP 206 in response to
|
||||
# ``Range: bytes=0-``.
|
||||
if size != (end or size) - (start or 0):
|
||||
self.set_status(206) # Partial Content
|
||||
self.set_header(
|
||||
"Content-Range", tornado.httputil._get_content_range(
|
||||
start, end, size)
|
||||
)
|
||||
else:
|
||||
start = end = None
|
||||
|
||||
if start is not None and end is not None:
|
||||
content_length = end - start
|
||||
elif end is not None:
|
||||
content_length = end
|
||||
elif start is not None:
|
||||
content_length = size - start
|
||||
else:
|
||||
end = size
|
||||
content_length = size
|
||||
self.set_header("Content-Length", content_length)
|
||||
|
||||
if include_body:
|
||||
content = self.get_content(self.absolute_path, start, end)
|
||||
if isinstance(content, bytes):
|
||||
content = [content]
|
||||
for chunk in content:
|
||||
try:
|
||||
self.write(chunk)
|
||||
await self.flush()
|
||||
except tornado.iostream.StreamClosedError:
|
||||
return
|
||||
else:
|
||||
assert self.request.method == "HEAD"
|
||||
|
||||
def should_return_304(self):
|
||||
# Disable file caching
|
||||
return False
|
||||
|
|
Loading…
Reference in New Issue