chtypes 2.0.0.dev12__py3-none-any.whl → 2.0.0.dev13__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- chtypes/_ocifetch/_ensure.py +36 -3
- chtypes/_ocifetch/_oci.py +41 -0
- {chtypes-2.0.0.dev12.dist-info → chtypes-2.0.0.dev13.dist-info}/METADATA +1 -1
- {chtypes-2.0.0.dev12.dist-info → chtypes-2.0.0.dev13.dist-info}/RECORD +7 -7
- {chtypes-2.0.0.dev12.dist-info → chtypes-2.0.0.dev13.dist-info}/WHEEL +0 -0
- {chtypes-2.0.0.dev12.dist-info → chtypes-2.0.0.dev13.dist-info}/entry_points.txt +0 -0
- {chtypes-2.0.0.dev12.dist-info → chtypes-2.0.0.dev13.dist-info}/licenses/LICENSE +0 -0
chtypes/_ocifetch/_ensure.py
CHANGED
|
@@ -75,6 +75,7 @@ from chtypes._ocifetch._layout import (
|
|
|
75
75
|
from chtypes._ocifetch._lock import Lock, LockPin, load_lock, new_lock, save_lock
|
|
76
76
|
from chtypes._ocifetch._oci import (
|
|
77
77
|
Descriptor,
|
|
78
|
+
copy_blob_verified,
|
|
78
79
|
fetch_blob_bytes,
|
|
79
80
|
fetch_blob_to_path,
|
|
80
81
|
fetch_manifest_by_digest,
|
|
@@ -86,6 +87,8 @@ from chtypes._ocifetch._oci import (
|
|
|
86
87
|
resolve_platform_manifest,
|
|
87
88
|
spelling_components,
|
|
88
89
|
validate_spelling,
|
|
90
|
+
verify_body_matches_digest,
|
|
91
|
+
verify_json_descriptor,
|
|
89
92
|
version_within_request,
|
|
90
93
|
)
|
|
91
94
|
from chtypes._ocifetch._referrers import ReferrerListing, discover_referrers, read_listing
|
|
@@ -1346,6 +1349,7 @@ def _verify_and_install_from_local_blobs(
|
|
|
1346
1349
|
trusted_keys: tuple[TrustedKey, ...],
|
|
1347
1350
|
visible_only: bool = False,
|
|
1348
1351
|
referrers: dict[str, list[tuple[str, dict]]] | None = None,
|
|
1352
|
+
manifest_size: int | None = None,
|
|
1349
1353
|
) -> VerifiedRecord | None:
|
|
1350
1354
|
"""docs/guides/fetch-v1.md §1/§6: "A pre-seeded layout has entries in
|
|
1351
1355
|
`index.json` with no corresponding `unpacked/` directory yet; the first
|
|
@@ -1357,7 +1361,14 @@ def _verify_and_install_from_local_blobs(
|
|
|
1357
1361
|
`source_root/blobs/sha256/*`, never the network. Returns `None` — never
|
|
1358
1362
|
raises — for a missing, unreadable or untrusted entry, the same
|
|
1359
1363
|
tolerant shape `_find_verified_signature` uses for a network referrer:
|
|
1360
|
-
a bad pre-seed is "nothing usable here," not a crash.
|
|
1364
|
+
a bad pre-seed is "nothing usable here," not a crash.
|
|
1365
|
+
|
|
1366
|
+
The manifest blob is checked against `manifest_digest` (and against
|
|
1367
|
+
`manifest_size`, the index descriptor's size, when the caller has one)
|
|
1368
|
+
before it is parsed, and the layer blob against the manifest's layer
|
|
1369
|
+
descriptor (size and sha256) before it is decompressed: the same
|
|
1370
|
+
checks a download gets (§7.3), so no byte the signature does not cover
|
|
1371
|
+
reaches the install."""
|
|
1361
1372
|
blobs_dir = source_root / "blobs" / "sha256"
|
|
1362
1373
|
if not blobs_dir.is_dir():
|
|
1363
1374
|
return None
|
|
@@ -1366,7 +1377,15 @@ def _verify_and_install_from_local_blobs(
|
|
|
1366
1377
|
if not manifest_path.is_file():
|
|
1367
1378
|
return None
|
|
1368
1379
|
try:
|
|
1369
|
-
|
|
1380
|
+
manifest_bytes = manifest_path.read_bytes()
|
|
1381
|
+
verify_body_matches_digest(manifest_bytes, manifest_digest, what="pre-seeded manifest")
|
|
1382
|
+
if manifest_size is not None:
|
|
1383
|
+
verify_json_descriptor(
|
|
1384
|
+
manifest_bytes,
|
|
1385
|
+
Descriptor(media_type="", digest=manifest_digest, size=manifest_size),
|
|
1386
|
+
what="pre-seeded manifest",
|
|
1387
|
+
)
|
|
1388
|
+
manifest_doc = json.loads(manifest_bytes)
|
|
1370
1389
|
layer_desc = manifest_layer_descriptor(manifest_doc)
|
|
1371
1390
|
except (ValueError, OSError, FetchError):
|
|
1372
1391
|
return None
|
|
@@ -1415,9 +1434,18 @@ def _verify_and_install_from_local_blobs(
|
|
|
1415
1434
|
scratch = _scratch_root(cache_write_root)
|
|
1416
1435
|
tmp_dir = tempfile.mkdtemp(dir=str(scratch))
|
|
1417
1436
|
try:
|
|
1437
|
+
# Size and sha256 against the layer descriptor BEFORE decompressing
|
|
1438
|
+
# (§7.3), on a copy in scratch, which is then the file unpacked.
|
|
1439
|
+
verified_layer = os.path.join(tmp_dir, "layer")
|
|
1440
|
+
copy_blob_verified(
|
|
1441
|
+
str(layer_path),
|
|
1442
|
+
layer_desc,
|
|
1443
|
+
verified_layer,
|
|
1444
|
+
what=f"pre-seeded layer {layer_desc.digest}",
|
|
1445
|
+
)
|
|
1418
1446
|
unpacked_dir = os.path.join(tmp_dir, "unpacked")
|
|
1419
1447
|
unpack_tar_zst(
|
|
1420
|
-
|
|
1448
|
+
verified_layer,
|
|
1421
1449
|
unpacked_dir,
|
|
1422
1450
|
library_name=predicate["library"],
|
|
1423
1451
|
library_sha256=predicate["library_sha256"],
|
|
@@ -1469,11 +1497,15 @@ def _verify_preseeded_entries(roots: Sequence[Path], trusted_keys: tuple[Trusted
|
|
|
1469
1497
|
candidates: list[str] = []
|
|
1470
1498
|
candidate_layers: set[str] = set()
|
|
1471
1499
|
manifests_read: dict[str, bytes] = {}
|
|
1500
|
+
sizes: dict[str, int] = {}
|
|
1472
1501
|
for m in index_doc.get("manifests") or []:
|
|
1473
1502
|
digest = m.get("digest")
|
|
1474
1503
|
if not digest or digest in already_verified:
|
|
1475
1504
|
continue
|
|
1476
1505
|
candidates.append(digest)
|
|
1506
|
+
size = m.get("size")
|
|
1507
|
+
if isinstance(size, int) and not isinstance(size, bool):
|
|
1508
|
+
sizes[digest] = size
|
|
1477
1509
|
try:
|
|
1478
1510
|
manifest_hex = parse_digest(digest)
|
|
1479
1511
|
manifest_bytes = (blobs_dir / manifest_hex).read_bytes()
|
|
@@ -1495,6 +1527,7 @@ def _verify_preseeded_entries(roots: Sequence[Path], trusted_keys: tuple[Trusted
|
|
|
1495
1527
|
trusted_keys=trusted_keys,
|
|
1496
1528
|
visible_only=True,
|
|
1497
1529
|
referrers=referrers,
|
|
1530
|
+
manifest_size=sizes.get(digest),
|
|
1498
1531
|
)
|
|
1499
1532
|
if record is not None:
|
|
1500
1533
|
already_verified.add(digest)
|
chtypes/_ocifetch/_oci.py
CHANGED
|
@@ -26,6 +26,7 @@ from chtypes._ocifetch._http import (
|
|
|
26
26
|
|
|
27
27
|
__all__ = [
|
|
28
28
|
"Descriptor",
|
|
29
|
+
"copy_blob_verified",
|
|
29
30
|
"fetch_blob_bytes",
|
|
30
31
|
"fetch_blob_to_path",
|
|
31
32
|
"fetch_manifest_by_digest",
|
|
@@ -143,6 +144,46 @@ def verify_body_matches_digest(body: bytes, digest: str, *, what: str) -> None:
|
|
|
143
144
|
)
|
|
144
145
|
|
|
145
146
|
|
|
147
|
+
def copy_blob_verified(src_path: str, descriptor: Descriptor, dest_path: str, *, what: str) -> None:
|
|
148
|
+
"""Copy a LOCAL blob file (a pre-seeded layout's `blobs/sha256/<hex>`)
|
|
149
|
+
to `dest_path`, streaming, and check the copy's size and sha256 against
|
|
150
|
+
`descriptor` BEFORE the caller does anything else with it — the local
|
|
151
|
+
counterpart of `fetch_blob_to_path`, with the same §7.3 rule ("check
|
|
152
|
+
size and sha256 before decompressing"). The caller unpacks the copy it
|
|
153
|
+
verified, never the source file, so the bytes checked are the bytes
|
|
154
|
+
used. Reads at most `descriptor.size + 1` bytes; removes `dest_path` on
|
|
155
|
+
any failure."""
|
|
156
|
+
expected = parse_digest(descriptor.digest)
|
|
157
|
+
hasher = hashlib.sha256()
|
|
158
|
+
size = 0
|
|
159
|
+
try:
|
|
160
|
+
with open(src_path, "rb") as src, open(dest_path, "wb") as dst:
|
|
161
|
+
while size <= descriptor.size:
|
|
162
|
+
chunk = src.read(min(1 << 20, descriptor.size + 1 - size))
|
|
163
|
+
if not chunk:
|
|
164
|
+
break
|
|
165
|
+
hasher.update(chunk)
|
|
166
|
+
dst.write(chunk)
|
|
167
|
+
size += len(chunk)
|
|
168
|
+
if size > descriptor.size:
|
|
169
|
+
raise ArtifactCorruptError(
|
|
170
|
+
f"{what}: larger than the descriptor's size {descriptor.size}"
|
|
171
|
+
)
|
|
172
|
+
if size != descriptor.size:
|
|
173
|
+
raise ArtifactCorruptError(
|
|
174
|
+
f"{what}: size {size} disagreed with the descriptor's {descriptor.size}"
|
|
175
|
+
)
|
|
176
|
+
actual = hasher.hexdigest()
|
|
177
|
+
if actual != expected:
|
|
178
|
+
raise ArtifactCorruptError(f"{what}: sha256 {actual} disagreed with {expected}")
|
|
179
|
+
except BaseException:
|
|
180
|
+
try:
|
|
181
|
+
os.unlink(dest_path)
|
|
182
|
+
except OSError:
|
|
183
|
+
pass
|
|
184
|
+
raise
|
|
185
|
+
|
|
186
|
+
|
|
146
187
|
def resolve_platform_manifest(index_doc: dict, platform_key: str) -> Descriptor:
|
|
147
188
|
"""Pick the one manifest descriptor in an OCI image index matching
|
|
148
189
|
`platform_key` (e.g. "linux-arm64"), refusing a duplicate or absent
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: chtypes
|
|
3
|
-
Version: 2.0.0.
|
|
3
|
+
Version: 2.0.0.dev13
|
|
4
4
|
Summary: Python binding for the chtypes artifacts: ClickHouse's own type system, per version, behind the frozen chs_* C ABI
|
|
5
5
|
Project-URL: Homepage, https://github.com/wave-rf/chtypes
|
|
6
6
|
Project-URL: Repository, https://github.com/wave-rf/chtypes
|
|
@@ -22,7 +22,7 @@ chtypes/_ocifetch/__init__.py,sha256=Nwmn1_JqkRSy-1q8182RT5m4HJfbdJgr54it7_TKvBg
|
|
|
22
22
|
chtypes/_ocifetch/_channel.py,sha256=1MuIj5O8N1d8OdTN4Rx4zf3kc2Nh1hjx4o5BIE6JgmA,20484
|
|
23
23
|
chtypes/_ocifetch/_constants.py,sha256=Gk-h6UkPg5dvNlhkr8z-UY4Y866unUPOSpE8ZwJvoJw,6287
|
|
24
24
|
chtypes/_ocifetch/_dsse.py,sha256=nYkjmRefmVTonH1KUADNNUl0J9sh2C3fg-0WEyURjcc,9838
|
|
25
|
-
chtypes/_ocifetch/_ensure.py,sha256=
|
|
25
|
+
chtypes/_ocifetch/_ensure.py,sha256=0v-tGzC4P0oLtADN-YBHzAMWaqOc2bYincxwUAhWtyg,81190
|
|
26
26
|
chtypes/_ocifetch/_errors.py,sha256=SE0zcTUr1fSvSH7HiLGjAInYvD_im8_SyeZNf-VKzXI,7594
|
|
27
27
|
chtypes/_ocifetch/_faults.py,sha256=HpoB9tJW7aLRiJdCFnspzkwhpFATZ6f6hGi49Mbfnhs,7609
|
|
28
28
|
chtypes/_ocifetch/_goldens.py,sha256=8MicgeJSRd4WZkaEsdujRnOa7twQvbT-wfSMPZaD-ms,4191
|
|
@@ -30,15 +30,15 @@ chtypes/_ocifetch/_hold.py,sha256=AmXKLFWdPEmZ5fuxKbTKbzYn-Gue9AyMfYTquQ0qvOs,48
|
|
|
30
30
|
chtypes/_ocifetch/_http.py,sha256=3D8WZXAObw-IiZqLSh9GEys2EKOEE2i9CVfUqjLh3E0,29013
|
|
31
31
|
chtypes/_ocifetch/_layout.py,sha256=ezEoVrsSMhvDhQ4U04hsGdu1-_ISZQ4V6zg4gGIChTQ,21300
|
|
32
32
|
chtypes/_ocifetch/_lock.py,sha256=14_uex9qXdOht-5FSrSvFvd99-gB7DmI3bDuxXs4YU8,5530
|
|
33
|
-
chtypes/_ocifetch/_oci.py,sha256=
|
|
33
|
+
chtypes/_ocifetch/_oci.py,sha256=Xc0JEShGemdTlu2LviOBFSMTcjwCu9uP6jdG0qlKO80,15677
|
|
34
34
|
chtypes/_ocifetch/_prune.py,sha256=gPToHNkOOVjCWA-lowJHnGpguqOFXklUOQs7PAGfhBQ,10203
|
|
35
35
|
chtypes/_ocifetch/_referrers.py,sha256=SQ8BMVzzA1085azzfAA0Nw3GrHPjeu98smasKyS609A,10530
|
|
36
36
|
chtypes/_ocifetch/_resolve.py,sha256=Qv8xFUdOR-Kb_JqmUeDiQ4_Xv2ETuNFpmoDckMp7eV8,11239
|
|
37
37
|
chtypes/_ocifetch/_retired.py,sha256=mqnI9RA7UK0A5RthCeLlBeVAu5SHbtwKFwxsSZk5e6A,7018
|
|
38
38
|
chtypes/_ocifetch/_revocation.py,sha256=DdKRjFp1eFAxVD7DZHK1bj3_sWVLlaEkVEVx6JyV2Zs,23710
|
|
39
39
|
chtypes/_ocifetch/_unpack.py,sha256=Cn4-up63KL63L8pnkS5zwJ6VbbHPSigpCnmOdcHJ4RI,5609
|
|
40
|
-
chtypes-2.0.0.
|
|
41
|
-
chtypes-2.0.0.
|
|
42
|
-
chtypes-2.0.0.
|
|
43
|
-
chtypes-2.0.0.
|
|
44
|
-
chtypes-2.0.0.
|
|
40
|
+
chtypes-2.0.0.dev13.dist-info/METADATA,sha256=W3xyv6dfQ9wI53cfWtQWioHnctgRuShzOSDZC5H3Jv4,11365
|
|
41
|
+
chtypes-2.0.0.dev13.dist-info/WHEEL,sha256=2iZrJkDIkeDh0k9SikKMqFuRP-ua6mb4Ir1AsER4BEw,87
|
|
42
|
+
chtypes-2.0.0.dev13.dist-info/entry_points.txt,sha256=TdNcluWBR0VOt5mVFROsp-NiVPAdR3EHo0UsHlMC0r0,50
|
|
43
|
+
chtypes-2.0.0.dev13.dist-info/licenses/LICENSE,sha256=z8d0m5b2O9McPEK1xHG_dWgUBT6EfBDz6wA0F7xSPTA,11358
|
|
44
|
+
chtypes-2.0.0.dev13.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|