"""固定Git commit归档与缓存来源证明;不把旧缓存当作已核实安装包。""" import datetime import hashlib import json import re from github_source import canonical_resource_source ARCHIVE_SCHEMA = 3 ARCHIVE_MAX_BYTES = 500 * 1024 * 1024 def archive_repository(value): """仅接收明确的owner/name标识,不能用元数据构造额外URL路径。""" if (not isinstance(value, str) or len(value) > 193 or not re.fullmatch(r'[A-Za-z0-9](?:[A-Za-z0-9_.-]{0,94}[A-Za-z0-9])?/[A-Za-z0-9](?:[A-Za-z0-9_.-]{0,94}[A-Za-z0-9])?', value) or '..' in value or '--' in value): raise ValueError('invalid archive storage identity') return value def commit_sha(value): if not isinstance(value, str) or not re.fullmatch(r'[0-9a-fA-F]{40}', value): raise ValueError('invalid commit identity') return value.lower() def commit_ref(value): if not isinstance(value, str) or not re.fullmatch(r'(?:[0-9a-fA-F]{7}|[0-9a-fA-F]{40})', value): raise ValueError('invalid commit reference') return value.lower() def source_signature(item, token): author = item.get('author') if not isinstance(author, str) or not author.strip(): raise ValueError('invalid archive author') source = [canonical_resource_source(item), author, token or ''] # 元数据只保存摘要,不保存或输出凭据。 return hashlib.sha256(json.dumps(source, ensure_ascii=True, separators=(',', ':')).encode()).hexdigest() def archive_path(item_id, sha, signature): if not isinstance(item_id, str) or not re.fullmatch(r'[A-Za-z0-9_-]{1,100}', item_id): raise ValueError('invalid archive item') if not isinstance(signature, str) or not re.fullmatch(r'[0-9a-f]{64}', signature): raise ValueError('invalid archive source') return f'zips/{item_id}_{commit_sha(sha)}_{signature[:16]}.zip' def cached_archive(item, token, entry, *, repo_id, now=None): """只有完整来源、commit、HF不可变提交及未过期元数据才允许命中。""" try: if not isinstance(entry, dict) or type(entry.get('archive_schema')) is not int or entry['archive_schema'] != ARCHIVE_SCHEMA: return None repository = archive_repository(repo_id) if entry.get('archive_repo_id') != repository: return None if entry.get('archive_storage_private') is not True: return None sha = commit_sha(entry.get('archive_commit')) if entry.get('version_commit') not in (None, sha): return None if entry.get('hash') != sha[:7]: return None signature = source_signature(item, token) if entry.get('archive_source') != signature: return None path = archive_path(item.get('id'), sha, signature) if entry.get('archive_path') != path: return None hf_commit = commit_sha(entry.get('archive_hf_commit')) size = entry.get('zip_size') if type(size) is not int or not 0 < size <= ARCHIVE_MAX_BYTES: return None cached_at = datetime.datetime.fromisoformat(entry['cached_at'].replace('Z', '+00:00')) if cached_at.tzinfo is None: return None elapsed = (now or datetime.datetime.now(datetime.timezone.utc)) - cached_at if not datetime.timedelta(minutes=-5) <= elapsed <= datetime.timedelta(days=30): return None return dict(commit=sha, path=path, hf_commit=hf_commit, size=size, repo_id=repository) except (ValueError, TypeError, KeyError, AttributeError): return None def publish_archive_metadata(versions, item_id, expected_hash, metadata): """锁内只更新目标缓存字段;晚到下载不能改变当前版本或其它资源。""" if not isinstance(versions, dict): raise ValueError('invalid version source') entry = versions.get(item_id) if isinstance(entry, str): entry = {'hash': entry} if not isinstance(entry, dict) or entry.get('hash') != commit_ref(expected_hash)[:7]: return False if (entry.get('version_commit') not in (None, metadata.get('archive_commit')) or entry.get('version_source') not in (None, metadata.get('archive_source'))): return False keys = ('archive_schema', 'archive_commit', 'archive_source', 'archive_path', 'archive_hf_commit', 'archive_storage_private', 'archive_repo_id', 'zip_size', 'cached_at') archive_repository(metadata['archive_repo_id']) entry = {**entry, **{key: metadata[key] for key in keys}} versions[item_id] = entry return True