Example #1
0
def parse_content_range(content_range: str) -> tuple:
    """
    Args:
        content_range: the content-range or range header to parse.

    Returns:
        Offset and limit arguments for the command.
    """
    try:
        range_type, range_count = content_range.split(' ', 1)

        range_count_arr = range_count.split('/')
        range_begin, range_end = range_count_arr[0].split('-', 1)

        offset = int(range_begin)
        limit = int(range_end) - offset

        if range_type != 'items' or range_end < range_begin or limit < 0 or offset < 0:
            raise Exception

    except Exception:
        raise RequestedRangeNotSatisfiable(
            description=f'Range header: {content_range}')

    return offset, limit
Example #2
0
    def _extract_range(cls, req, blocks):
        """Convert byte range into block an performs validity check"""
        # accept only single part range
        val = req.headers['Range']
        match = RANGE_RE.match(val)
        if match is None:
            raise RequestedRangeNotSatisfiable()
        start = int(match.group(1))
        end = int(match.group(2))
        if start >= end:
            raise RequestedRangeNotSatisfiable()

        def check_range(value):
            block, remainder = divmod(value, BLOCKSIZE)
            if remainder or block < 0 or (blocks and block > blocks):
                raise RequestedRangeNotSatisfiable()
            return block

        block_start = check_range(start)
        block_end = check_range(end + 1)  # Check Range RFC

        return start, end, block_start, block_end
Example #3
0
    def _process_range_request(self,
                               environ,
                               complete_length=None,
                               accept_ranges=None):
        """Handle Range Request related headers (RFC7233).  If `Accept-Ranges`
        header is valid, and Range Request is processable, we set the headers
        as described by the RFC, and wrap the underlying response in a
        RangeWrapper.

        Returns ``True`` if Range Request can be fulfilled, ``False`` otherwise.

        :raises: :class:`~werkzeug.exceptions.RequestedRangeNotSatisfiable`
                 if `Range` header could not be parsed or satisfied.
        """
        from werkzeug.exceptions import RequestedRangeNotSatisfiable
        if accept_ranges is None:
            return False
        self.headers['Accept-Ranges'] = accept_ranges
        if not self._is_range_request_processable(
                environ) or complete_length is None:
            return False
        parsed_range = parse_range_header(environ.get('HTTP_RANGE'))
        if parsed_range is None:
            raise RequestedRangeNotSatisfiable(complete_length)
        range_tuple = parsed_range.range_for_length(complete_length)
        content_range_header = parsed_range.to_content_range_header(
            complete_length)
        if range_tuple is None or content_range_header is None:
            raise RequestedRangeNotSatisfiable(complete_length)
        content_length = range_tuple[1] - range_tuple[0]
        # Be sure not to send 206 response
        # if requested range is the full content.
        if content_length != complete_length:
            self.headers['Content-Length'] = content_length
            self.content_range = content_range_header
            self.status_code = 206
            self._wrap_response(range_tuple[0], content_length)
            return True
        return False
Example #4
0
    async def make_conditional(self,
                               request_range: Optional[Range],
                               max_partial_size: Optional[int] = None) -> None:
        """Make the response conditional to the

        Arguments:
            request_range: The range as requested by the request.
            max_partial_size: The maximum length the server is willing
                to serve in a single response. Defaults to unlimited.

        """
        self.accept_ranges = "bytes"  # Advertise this ability
        if request_range is None or len(
                request_range.ranges) == 0:  # Not a conditional request
            return

        if request_range.units != "bytes" or len(request_range.ranges) > 1:
            raise RequestedRangeNotSatisfiable()

        begin, end = request_range.ranges[0]
        try:
            complete_length = await self.response.make_conditional(  # type: ignore
                begin, end, max_partial_size)
        except AttributeError:
            self.response = self.data_body_class(
                await self.response.convert_to_sequence())
            return await self.make_conditional(request_range, max_partial_size)
        else:
            self.content_length = self.response.end - self.response.begin  # type: ignore
            if self.content_length != complete_length:
                self.content_range = ContentRange(
                    request_range.units,
                    self.response.begin,  # type: ignore
                    self.response.end - 1,  # type: ignore
                    complete_length,
                )
                self.status_code = 206
Example #5
0
 def check_range(value):
     block, remainder = divmod(value, BLOCKSIZE)
     if remainder or block < 0 or (blocks and block > blocks):
         raise RequestedRangeNotSatisfiable()
     return block
Example #6
0
def serve_fileobj(fileobj, headers, content_length):
    status_code = 200
    headers["Accept-Ranges"] = "bytes"

    r = httputil.get_ranges(headers.get('Range'), content_length)

    if r == []:
        headers['Content-Range'] = "bytes */{0}".format(content_length)
        message = "Invalid Range (first-byte-pos greater than Content-Length)"
        raise RequestedRangeNotSatisfiable(message)

    if not r:
        headers['Content-Length'] = content_length
        return fileobj, headers, status_code

    # Return a multipart/byteranges response.
    status_code = 206

    if len(r) == 1:
        # Return a single-part response.
        start, stop = r[0]
        if stop > content_length:
            stop = content_length
        r_len = stop - start

        headers['Content-Range'] = "bytes {0}-{1}/{2}".format(
            start, stop - 1, content_length)
        headers['Content-Length'] = r_len
        fileobj.seek(start)
        body = file_generator_limited(fileobj, r_len)
        return body, headers, status_code

    try:
        # Python 3
        from email.generator import _make_boundary as make_boundary
    except ImportError:
        # Python 2
        from mimetools import choose_boundary as make_boundary

    boundary = make_boundary()
    content_type = "multipart/byteranges; boundary={0}".format(boundary)
    headers['Content-Type'] = content_type
    if "Content-Length" in headers:
        del headers["Content-Length"]

    def file_ranges():
        for start, stop in r:
            yield to_unicode("--" + boundary)
            yield to_unicode("\r\nContent-type: {0}".format(content_type))
            yield to_unicode(
                "\r\nContent-range: bytes {0}-{1}/{2}\r\n\r\n".format(
                    start, stop - 1, content_length))
            fileobj.seek(start)

            gen = file_generator_limited(fileobj, stop - start)
            for chunk in gen:
                yield chunk
            yield to_unicode("\r\n")

        yield to_unicode("--" + boundary + "--")

    body = file_ranges()
    return body, headers, status_code
Example #7
0
def _raise_if_invalid_range(begin: int, end: int, size: int) -> None:
    if begin >= end or abs(begin) > size or end > size:
        raise RequestedRangeNotSatisfiable()