| 661 | } |
| 662 | |
| 663 | function parseRangeHeader( |
| 664 | raw: string | undefined, |
| 665 | size: number, |
| 666 | ): { start: number; end: number; length: number } | null { |
| 667 | if (raw === undefined) return null; |
| 668 | if (!raw.startsWith('bytes=')) return null; |
| 669 | const spec = raw.slice('bytes='.length); |
| 670 | if (spec.includes(',')) return null; |
| 671 | const dash = spec.indexOf('-'); |
| 672 | if (dash < 0) return null; |
| 673 | const leftRaw = spec.slice(0, dash); |
| 674 | const rightRaw = spec.slice(dash + 1); |
| 675 | if (leftRaw === '' && rightRaw === '') return null; |
| 676 | let start: number; |
| 677 | let end: number; |
| 678 | if (leftRaw === '') { |
| 679 | |
| 680 | const suffix = Number.parseInt(rightRaw, 10); |
| 681 | if (!Number.isFinite(suffix) || suffix <= 0) return null; |
| 682 | start = Math.max(0, size - suffix); |
| 683 | end = size - 1; |
| 684 | } else { |
| 685 | const a = Number.parseInt(leftRaw, 10); |
| 686 | if (!Number.isFinite(a) || a < 0) return null; |
| 687 | start = a; |
| 688 | if (rightRaw === '') { |
| 689 | end = size - 1; |
| 690 | } else { |
| 691 | const b = Number.parseInt(rightRaw, 10); |
| 692 | if (!Number.isFinite(b) || b < a) return null; |
| 693 | end = Math.min(b, size - 1); |
| 694 | } |
| 695 | } |
| 696 | if (start >= size || start > end) return null; |
| 697 | return { start, end, length: end - start + 1 }; |
| 698 | } |
| 699 | |
| 700 | function sanitizeFilename(rel: string): string { |
| 701 | const segs = rel.split('/'); |