Constraint: Public skills are published only by explicit administrator action unless they are tracked third-party market sources. Confidence: high Scope-risk: narrow Directive: Keep private/internal skills out of the public marketplace and preserve normal incremental market Git history. Tested: Marketplace validation passed.
53 lines
2.1 KiB
Python
53 lines
2.1 KiB
Python
"""Unpack or repack an OOXML document without shell-specific ZIP commands."""
|
|
|
|
import argparse
|
|
import os
|
|
import tempfile
|
|
import zipfile
|
|
from pathlib import Path, PurePosixPath
|
|
|
|
|
|
def unpack(source, destination):
|
|
root = Path(destination).resolve()
|
|
if root.exists() and any(root.iterdir()):
|
|
raise ValueError("Unpack destination must be empty")
|
|
with zipfile.ZipFile(source) as archive:
|
|
for info in archive.infolist():
|
|
name = PurePosixPath(info.filename.replace("\\", "/"))
|
|
if name.is_absolute() or ".." in name.parts or any(":" in part for part in name.parts):
|
|
raise ValueError(f"Unsafe ZIP entry: {info.filename}")
|
|
if (info.external_attr >> 16) & 0o170000 == 0o120000:
|
|
raise ValueError(f"ZIP symlink is not supported: {info.filename}")
|
|
archive.extractall(root)
|
|
|
|
|
|
def pack(source, destination):
|
|
root = Path(source).resolve()
|
|
output = Path(destination).resolve()
|
|
if output.is_relative_to(root):
|
|
raise ValueError("Output must be outside the unpacked directory")
|
|
if not (root / "[Content_Types].xml").is_file():
|
|
raise ValueError("Source must be the package root containing [Content_Types].xml")
|
|
output.parent.mkdir(parents=True, exist_ok=True)
|
|
fd, temporary = tempfile.mkstemp(suffix=output.suffix, dir=output.parent)
|
|
os.close(fd)
|
|
try:
|
|
with zipfile.ZipFile(temporary, "w", zipfile.ZIP_DEFLATED) as archive:
|
|
for file in sorted(root.rglob("*")):
|
|
if file.is_symlink():
|
|
raise ValueError(f"Package symlink is not supported: {file}")
|
|
if file.is_file():
|
|
archive.write(file, file.relative_to(root).as_posix())
|
|
os.replace(temporary, output)
|
|
finally:
|
|
Path(temporary).unlink(missing_ok=True)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
parser = argparse.ArgumentParser(description=__doc__)
|
|
parser.add_argument("action", choices=("unpack", "pack"))
|
|
parser.add_argument("input")
|
|
parser.add_argument("output")
|
|
args = parser.parse_args()
|
|
{"unpack": unpack, "pack": pack}[args.action](args.input, args.output)
|