Fetch and parse the SHA256SUMS.txt file. Returns a dict mapping filename → sha256 hex digest.
(version: str)
| 113 | |
| 114 | |
| 115 | def _fetch_checksums(version: str) -> dict[str, str]: |
| 116 | """Fetch and parse the SHA256SUMS.txt file. |
| 117 | |
| 118 | Returns a dict mapping filename → sha256 hex digest. |
| 119 | """ |
| 120 | url = get_checksums_url(version) |
| 121 | last_exc: Exception | None = None |
| 122 | for attempt in range(_MAX_RETRIES): |
| 123 | try: |
| 124 | with urlopen(url, timeout=30) as response: |
| 125 | text = response.read().decode("utf-8") |
| 126 | break |
| 127 | except (HTTPError, URLError) as exc: |
| 128 | last_exc = exc |
| 129 | if attempt < _MAX_RETRIES - 1: |
| 130 | time.sleep(2**attempt) |
| 131 | else: |
| 132 | raise RuntimeError( |
| 133 | f"Failed to download checksums from {url}: {last_exc}\n\n" |
| 134 | "If you are in an offline or firewalled environment, set " |
| 135 | "COPILOT_CLI_PATH to point to a manually-installed binary." |
| 136 | ) from last_exc |
| 137 | |
| 138 | checksums: dict[str, str] = {} |
| 139 | for line in text.strip().splitlines(): |
| 140 | parts = line.split() |
| 141 | if len(parts) == 2: |
| 142 | digest, filename = parts |
| 143 | # Some formats use *filename (binary mode indicator) |
| 144 | checksums[filename.lstrip("*")] = digest |
| 145 | return checksums |
| 146 | |
| 147 | |
| 148 | def _verify_checksum(data: bytes, expected_hash: str, filename: str) -> None: |
no test coverage detected
searching dependent graphs…