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 logging
|
||||||
import json
|
import json
|
||||||
import tornado
|
import tornado
|
||||||
|
import tornado.iostream
|
||||||
|
import tornado.httputil
|
||||||
from inspect import isclass
|
from inspect import isclass
|
||||||
from tornado.escape import url_unescape
|
from tornado.escape import url_unescape
|
||||||
from tornado.routing import Rule, PathMatches, AnyMatches
|
from tornado.routing import Rule, PathMatches, AnyMatches
|
||||||
|
@ -391,6 +393,92 @@ class FileRequestHandler(AuthorizedFileHandler):
|
||||||
raise tornado.web.HTTPError(e.status_code, str(e))
|
raise tornado.web.HTTPError(e.status_code, str(e))
|
||||||
self.finish({'result': filename})
|
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):
|
def should_return_304(self):
|
||||||
# Disable file caching
|
# Disable file caching
|
||||||
return False
|
return False
|
||||||
|
|
Loading…
Reference in New Issue