(cls, http_range: str, file_size: int)
| 455 | |
| 456 | @classmethod |
| 457 | def _parse_range_header(cls, http_range: str, file_size: int) -> list[tuple[int, int]]: |
| 458 | ranges: list[tuple[int, int]] = [] |
| 459 | try: |
| 460 | units, range_ = http_range.split("=", 1) |
| 461 | except ValueError: |
| 462 | raise MalformedRangeHeader() |
| 463 | |
| 464 | units = units.strip().lower() |
| 465 | |
| 466 | if units != "bytes": |
| 467 | raise MalformedRangeHeader("Only support bytes range") |
| 468 | |
| 469 | ranges = cls._parse_ranges(range_, file_size) |
| 470 | |
| 471 | if len(ranges) == 0: |
| 472 | raise MalformedRangeHeader("Range header: range must be requested") |
| 473 | |
| 474 | if any(not (0 <= start < file_size) for start, _ in ranges): |
| 475 | raise RangeNotSatisfiable(file_size) |
| 476 | |
| 477 | if any(start > end for start, end in ranges): |
| 478 | raise MalformedRangeHeader("Range header: start must be less than end") |
| 479 | |
| 480 | if len(ranges) == 1: |
| 481 | return ranges |
| 482 | |
| 483 | # Merge overlapping ranges |
| 484 | ranges.sort() |
| 485 | result: list[tuple[int, int]] = [ranges[0]] |
| 486 | for start, end in ranges[1:]: |
| 487 | last_start, last_end = result[-1] |
| 488 | if start <= last_end: |
| 489 | result[-1] = (last_start, max(last_end, end)) |
| 490 | else: |
| 491 | result.append((start, end)) |
| 492 | |
| 493 | return result |
| 494 | |
| 495 | @classmethod |
| 496 | def _parse_ranges(cls, range_: str, file_size: int) -> list[tuple[int, int]]: |
no test coverage detected