|
|
@@ -243,17 +243,40 @@ def parse_composer_lock(path: Path, components): # composer.lock (JSON)
|
|
|
add(components, "composer", meta.get("name"), meta.get("version"), path)
|
|
|
|
|
|
|
|
|
-def parse_cargo_lock(path: Path, components): # Cargo.lock (TOML; needs py3.11+ tomllib)
|
|
|
+# Cargo.lock is TOML, but parsed line-wise rather than with tomllib: tomllib is
|
|
|
+# 3.11+, and this script must run on whatever python is on PATH. Skipping Cargo on
|
|
|
+# 3.10 made the scan report *clean* on an exposed tree — a silent false negative,
|
|
|
+# the one failure mode a security scanner must not have. Same tradeoff (and reason)
|
|
|
+# as pypi-ops/scripts/publish-preflight.sh. Safe because the file is machine-
|
|
|
+# generated by cargo with a rigid shape: a flat sequence of [[package]] tables,
|
|
|
+# each with `name = "..."` / `version = "..."` on their own lines, values never
|
|
|
+# multi-line. Only [[package]] counts — the top-level `version = 3|4` lockfile-
|
|
|
+# format key sits before any table, and [[patch.unused]] / [metadata] tables carry
|
|
|
+# name/version keys that are NOT installed components.
|
|
|
+CARGO_TABLE_RE = re.compile(r"^\s*\[\[?([^\]]+)\]\]?\s*$")
|
|
|
+CARGO_KV_RE = re.compile(r'^\s*(name|version)\s*=\s*"([^"]*)"\s*$')
|
|
|
+
|
|
|
+
|
|
|
+def parse_cargo_lock(path: Path, components): # Cargo.lock (TOML subset; see above)
|
|
|
try:
|
|
|
- import tomllib
|
|
|
- except ImportError:
|
|
|
- return # tomllib is 3.11+; skip Cargo on older pythons
|
|
|
- try:
|
|
|
- doc = tomllib.loads(read_text_tolerant(path))
|
|
|
- except Exception: # OSError or tomllib.TOMLDecodeError
|
|
|
+ lines = read_text_tolerant(path).splitlines()
|
|
|
+ except OSError:
|
|
|
return
|
|
|
- for pkg in doc.get("package", []):
|
|
|
- add(components, "cargo", pkg.get("name"), pkg.get("version"), path)
|
|
|
+ in_pkg, name, ver = False, None, None
|
|
|
+ for line in lines:
|
|
|
+ table = CARGO_TABLE_RE.match(line)
|
|
|
+ if table:
|
|
|
+ if in_pkg:
|
|
|
+ add(components, "cargo", name, ver, path)
|
|
|
+ in_pkg, name, ver = table.group(1) == "package", None, None
|
|
|
+ elif in_pkg:
|
|
|
+ kv = CARGO_KV_RE.match(line)
|
|
|
+ if kv and kv.group(1) == "name":
|
|
|
+ name = kv.group(2)
|
|
|
+ elif kv:
|
|
|
+ ver = kv.group(2)
|
|
|
+ if in_pkg: # flush the final table (no trailing header to trigger it)
|
|
|
+ add(components, "cargo", name, ver, path)
|
|
|
|
|
|
|
|
|
def parse_go_sum(path: Path, components): # go.sum lines: "<module> <version>[/go.mod] <hash>"
|