9cb9e88678
Co-authored-by: L. Kärkkäinen <98187+Tronic@users.noreply.github.com> Co-authored-by: L. Karkkainen <tronic@users.noreply.github.com>
79 lines
2.6 KiB
Python
79 lines
2.6 KiB
Python
from __future__ import annotations
|
|
|
|
from sanic.exceptions import (
|
|
HeaderNotFound,
|
|
InvalidRangeType,
|
|
RangeNotSatisfiable,
|
|
)
|
|
|
|
|
|
class ContentRangeHandler:
|
|
"""
|
|
A mechanism to parse and process the incoming request headers to
|
|
extract the content range information.
|
|
|
|
:param request: Incoming api request
|
|
:param stats: Stats related to the content
|
|
|
|
:type request: :class:`sanic.request.Request`
|
|
:type stats: :class:`posix.stat_result`
|
|
|
|
:ivar start: Content Range start
|
|
:ivar end: Content Range end
|
|
:ivar size: Length of the content
|
|
:ivar total: Total size identified by the :class:`posix.stat_result`
|
|
instance
|
|
:ivar ContentRangeHandler.headers: Content range header ``dict``
|
|
"""
|
|
|
|
__slots__ = ("start", "end", "size", "total", "headers")
|
|
|
|
def __init__(self, request, stats):
|
|
self.total = stats.st_size
|
|
_range = request.headers.getone("range", None)
|
|
if _range is None:
|
|
raise HeaderNotFound("Range Header Not Found")
|
|
unit, _, value = tuple(map(str.strip, _range.partition("=")))
|
|
if unit != "bytes":
|
|
raise InvalidRangeType(
|
|
"%s is not a valid Range Type" % (unit,), self
|
|
)
|
|
start_b, _, end_b = tuple(map(str.strip, value.partition("-")))
|
|
try:
|
|
self.start = int(start_b) if start_b else None
|
|
except ValueError:
|
|
raise RangeNotSatisfiable(
|
|
"'%s' is invalid for Content Range" % (start_b,), self
|
|
)
|
|
try:
|
|
self.end = int(end_b) if end_b else None
|
|
except ValueError:
|
|
raise RangeNotSatisfiable(
|
|
"'%s' is invalid for Content Range" % (end_b,), self
|
|
)
|
|
if self.end is None:
|
|
if self.start is None:
|
|
raise RangeNotSatisfiable(
|
|
"Invalid for Content Range parameters", self
|
|
)
|
|
else:
|
|
# this case represents `Content-Range: bytes 5-`
|
|
self.end = self.total - 1
|
|
else:
|
|
if self.start is None:
|
|
# this case represents `Content-Range: bytes -5`
|
|
self.start = self.total - self.end
|
|
self.end = self.total - 1
|
|
if self.start >= self.end:
|
|
raise RangeNotSatisfiable(
|
|
"Invalid for Content Range parameters", self
|
|
)
|
|
self.size = self.end - self.start + 1
|
|
self.headers = {
|
|
"Content-Range": "bytes %s-%s/%s"
|
|
% (self.start, self.end, self.total)
|
|
}
|
|
|
|
def __bool__(self):
|
|
return self.size > 0
|