| 19 | return (self.root / path).resolve() |
| 20 | |
| 21 | def send_file(self, head_only=False): |
| 22 | file_path = self.translate_path() |
| 23 | try: |
| 24 | file_path.relative_to(self.root) |
| 25 | except ValueError: |
| 26 | self.send_error(403) |
| 27 | return |
| 28 | if not file_path.is_file(): |
| 29 | self.send_error(404) |
| 30 | return |
| 31 | |
| 32 | size = file_path.stat().st_size |
| 33 | start = 0 |
| 34 | end = size - 1 |
| 35 | status = 200 |
| 36 | range_header = self.headers.get("Range") |
| 37 | if range_header and range_header.startswith("bytes="): |
| 38 | status = 206 |
| 39 | range_spec = range_header[len("bytes="):].split(",", 1)[0] |
| 40 | first, _, last = range_spec.partition("-") |
| 41 | if first: |
| 42 | start = int(first) |
| 43 | if last: |
| 44 | end = int(last) |
| 45 | elif last: |
| 46 | suffix_length = int(last) |
| 47 | start = max(size - suffix_length, 0) |
| 48 | end = size - 1 |
| 49 | end = min(end, size - 1) |
| 50 | if start > end or start >= size: |
| 51 | self.send_response(416) |
| 52 | self.send_header("Content-Range", f"bytes */{size}") |
| 53 | self.end_headers() |
| 54 | return |
| 55 | |
| 56 | length = end - start + 1 if size else 0 |
| 57 | self.send_response(status) |
| 58 | self.send_header("Accept-Ranges", "bytes") |
| 59 | self.send_header("Content-Length", str(length)) |
| 60 | if status == 206: |
| 61 | self.send_header("Content-Range", f"bytes {start}-{end}/{size}") |
| 62 | self.end_headers() |
| 63 | if head_only: |
| 64 | return |
| 65 | with file_path.open("rb") as file: |
| 66 | file.seek(start) |
| 67 | remaining = length |
| 68 | while remaining: |
| 69 | chunk = file.read(min(1024 * 64, remaining)) |
| 70 | if not chunk: |
| 71 | break |
| 72 | self.wfile.write(chunk) |
| 73 | remaining -= len(chunk) |
| 74 | |
| 75 | def do_GET(self): |
| 76 | self.send_file(False) |