| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111 |
- """Fixed skill transfer operations; installation never executes package code."""
- import base64
- import hashlib
- import json
- import os
- from pathlib import Path, PurePosixPath
- import re
- import shutil
- import stat
- import sys
- import tempfile
- MAX_FILE = 1024 * 1024
- MAX_PACKAGE = 4 * MAX_FILE
- MAX_FILES = 128
- ROOT = Path('/opt/yaoyao-skills')
- def path_name(value):
- if not isinstance(value, str) or len(value) > 512:
- raise ValueError('技能文件路径无效')
- path = PurePosixPath(value)
- if not value or path.is_absolute() or str(path) != value or any(part.startswith('.') or re.search(r'[\x00-\x1f\x7f\\:*?"<>|]', part) for part in path.parts):
- raise ValueError('技能文件必须使用包内相对路径')
- return value
- def fingerprint(files):
- digest = hashlib.sha256()
- for name in sorted(files):
- digest.update(name.encode() + b'\0' + hashlib.sha256(files[name]).digest())
- return digest.hexdigest()
- def install(body):
- namespace, revision = body.get('namespace'), body.get('revision')
- if any(not isinstance(value, str) or not re.fullmatch(r'[a-f0-9]{64}', value) for value in (namespace, revision)):
- raise ValueError('技能版本无效')
- encoded = body.get('files')
- if not isinstance(encoded, dict) or not 0 < len(encoded) <= MAX_FILES:
- raise ValueError('技能文件数量超过限制')
- files = {}
- for name, data in encoded.items():
- path_name(name)
- if not isinstance(data, str) or len(data) > MAX_FILE * 4 // 3 + 4:
- raise ValueError('技能文件超过限制')
- files[name] = base64.b64decode(data, validate=True)
- if 'SKILL.md' not in files or sum(map(len, files.values())) > MAX_PACKAGE or any(len(data) > MAX_FILE for data in files.values()) or fingerprint(files) != revision:
- raise ValueError('技能包不完整或版本不匹配')
- # ROOT and all descendants are root-owned. The cua user cannot substitute
- # parents, change modes or overwrite a previously installed revision.
- for directory in (ROOT, ROOT / namespace):
- if directory.is_symlink():
- raise ValueError('技能存储路径不能重定向')
- directory.mkdir(exist_ok=True, mode=0o755)
- if directory.stat().st_uid != 0 or directory.stat().st_mode & 0o022:
- raise ValueError('技能目录权限无效')
- target = ROOT / namespace / revision
- if target.is_symlink():
- raise ValueError('技能版本路径不能重定向')
- if not target.exists():
- stage = Path(tempfile.mkdtemp(prefix='.install-', dir=target.parent))
- try:
- for name, data in files.items():
- path = stage / name
- path.parent.mkdir(parents=True, exist_ok=True)
- path.write_bytes(data)
- path.chmod(0o444)
- for root, dirs, _ in os.walk(stage):
- Path(root).chmod(0o555)
- os.replace(stage, target)
- finally:
- if stage.exists():
- for root, _, _ in os.walk(stage):
- Path(root).chmod(0o755)
- shutil.rmtree(stage)
- return {'path': str(target), 'revision': revision}
- def collect(directory):
- root = Path(directory)
- if not root.is_absolute() or root.is_symlink() or root.resolve() != root or not root.is_dir():
- raise ValueError('草稿必须是隔离环境中的真实绝对目录')
- files = {}
- for parent, dirs, names in os.walk(root, followlinks=False):
- if any((Path(parent) / name).is_symlink() for name in dirs):
- raise ValueError('技能草稿不能包含符号链接')
- for name in sorted(names):
- path = Path(parent) / name
- relative = path_name(path.relative_to(root).as_posix())
- fd = os.open(path, os.O_RDONLY | os.O_NOFOLLOW | os.O_NONBLOCK)
- with os.fdopen(fd, 'rb') as stream:
- info = os.fstat(stream.fileno())
- if not stat.S_ISREG(info.st_mode) or info.st_size > MAX_FILE:
- raise ValueError('技能文件必须为限额内的普通文件')
- files[relative] = stream.read(MAX_FILE + 1)
- if len(files) > MAX_FILES or sum(map(len, files.values())) > MAX_PACKAGE:
- raise ValueError('技能包超过限制')
- if 'SKILL.md' not in files:
- raise ValueError('草稿缺少 SKILL.md')
- return {'files': {name: base64.b64encode(data).decode() for name, data in files.items()}, 'revision': fingerprint(files)}
- if __name__ == '__main__':
- if sys.argv[1] == 'install':
- result = install(json.load(sys.stdin))
- elif sys.argv[1] == 'collect':
- result = collect(sys.argv[2])
- else:
- raise ValueError('未知技能传输操作')
- print(json.dumps(result))
|