mirror of
https://git.hiabuto.net/C3MA/CCMA.git
synced 2026-09-11 21:54:18 +02:00
Add JSON integrity hash checks
This commit is contained in:
@@ -1,17 +1,56 @@
|
||||
import hashlib
|
||||
import json
|
||||
import os
|
||||
import tempfile
|
||||
from copy import deepcopy
|
||||
from pathlib import Path
|
||||
from typing import Any
|
||||
|
||||
CONTENT_HASH_FIELD = "content_hash"
|
||||
|
||||
|
||||
def _hashable_copy(data: Any, *, hash_field: str = CONTENT_HASH_FIELD) -> Any:
|
||||
if isinstance(data, dict):
|
||||
return {key: _hashable_copy(value, hash_field=hash_field) for key, value in data.items() if key != hash_field}
|
||||
if isinstance(data, list):
|
||||
return [_hashable_copy(item, hash_field=hash_field) for item in data]
|
||||
return data
|
||||
|
||||
|
||||
def compute_json_content_hash(data: Any, *, hash_field: str = CONTENT_HASH_FIELD) -> str:
|
||||
payload = json.dumps(
|
||||
_hashable_copy(data, hash_field=hash_field),
|
||||
ensure_ascii=False,
|
||||
sort_keys=True,
|
||||
separators=(",", ":"),
|
||||
).encode("utf-8")
|
||||
return hashlib.sha256(payload).hexdigest()
|
||||
|
||||
|
||||
def attach_json_content_hash(data: Any, *, hash_field: str = CONTENT_HASH_FIELD) -> Any:
|
||||
cloned = deepcopy(data)
|
||||
if isinstance(cloned, dict):
|
||||
cloned[hash_field] = compute_json_content_hash(cloned, hash_field=hash_field)
|
||||
return cloned
|
||||
|
||||
|
||||
def json_content_hash_matches(data: Any, *, hash_field: str = CONTENT_HASH_FIELD) -> bool:
|
||||
if not isinstance(data, dict):
|
||||
return True
|
||||
stored = str(data.get(hash_field, "")).strip()
|
||||
if not stored:
|
||||
return False
|
||||
return stored == compute_json_content_hash(data, hash_field=hash_field)
|
||||
|
||||
|
||||
def write_json_atomic(path: Path, data: Any) -> None:
|
||||
path.parent.mkdir(parents=True, exist_ok=True)
|
||||
descriptor, temporary_name = tempfile.mkstemp(prefix=f".{path.name}.", suffix=".tmp", dir=path.parent)
|
||||
temporary = Path(temporary_name)
|
||||
payload = attach_json_content_hash(data)
|
||||
try:
|
||||
with os.fdopen(descriptor, "w", encoding="utf-8", newline="\n") as handle:
|
||||
json.dump(data, handle, ensure_ascii=False, indent=2)
|
||||
json.dump(payload, handle, ensure_ascii=False, indent=2)
|
||||
handle.write("\n")
|
||||
handle.flush()
|
||||
os.fsync(handle.fileno())
|
||||
|
||||
Reference in New Issue
Block a user