diff --git a/Makefile b/Makefile index f213b4d..0740ac2 100644 --- a/Makefile +++ b/Makefile @@ -28,6 +28,7 @@ xpi: logos @mkdir -p $(XPI_UNSIGNED_DIR) $(XPI_SIGNED_DIR) @rm -f $(XPI_OUTPUT) @cd webextension && zip -q -9 "../$(XPI_OUTPUT)" $(EXTENSION_FILES) + @python3 scripts/release/validate_xpi.py "$(XPI_OUTPUT)" webextension/manifest.json @echo "Created $(XPI_OUTPUT)" check-tools: diff --git a/XPI/signed/LinkLog-dummy.xpi b/XPI/signed/LinkLog-dummy.xpi new file mode 100644 index 0000000..e3a1bd7 Binary files /dev/null and b/XPI/signed/LinkLog-dummy.xpi differ diff --git a/XPI/unsigned/LinkLog-0.1.0.xpi b/XPI/unsigned/LinkLog-0.1.0.xpi index f5e851f..e3a1bd7 100644 Binary files a/XPI/unsigned/LinkLog-0.1.0.xpi and b/XPI/unsigned/LinkLog-0.1.0.xpi differ diff --git a/scripts/release/validate_release.py b/scripts/release/validate_release.py index 12740c7..3faed8f 100644 --- a/scripts/release/validate_release.py +++ b/scripts/release/validate_release.py @@ -30,6 +30,11 @@ def main() -> None: if frontend_version != extension_version: fail(f'frontend version {frontend_version} does not match extension version {extension_version}') + gecko_settings = manifest.get('browser_specific_settings', {}).get('gecko', {}) + data_permissions = gecko_settings.get('data_collection_permissions') + if data_permissions != {'required': ['websiteActivity'], 'optional': []}: + fail('Firefox data_collection_permissions must require websiteActivity and have no optional categories') + settings = SETTINGS_PATH.read_text() match = re.search(r"version: str = os\.getenv\('LINKLOG_VERSION', '([^']+)'\)", settings) if not match: @@ -52,7 +57,7 @@ def main() -> None: fail('signed XPI contains a corrupt member') updates = json.loads(UPDATES_PATH.read_text()) - addon_id = manifest['browser_specific_settings']['gecko']['id'] + addon_id = gecko_settings['id'] update_entries = updates.get('addons', {}).get(addon_id, {}).get('updates', []) if not any(entry.get('version') == extension_version for entry in update_entries): fail(f'webextension/updates.json has no update entry for {extension_version}') diff --git a/scripts/release/validate_xpi.py b/scripts/release/validate_xpi.py new file mode 100644 index 0000000..1a87b51 --- /dev/null +++ b/scripts/release/validate_xpi.py @@ -0,0 +1,62 @@ +#!/usr/bin/env python3 +"""Validate an unsigned LinkLog XPI produced by the Makefile.""" + +import json +import sys +import zipfile +from pathlib import Path + + +REQUIRED_FILES = { + 'manifest.json', + 'logo.svg', + 'icon-16.png', + 'icon-32.png', + 'icon-48.png', + 'icon-96.png', + 'options.css', + 'options.html', + 'options.js', + 'popup.css', + 'popup.html', + 'popup.js', +} + + +def fail(message: str) -> None: + raise SystemExit(f'XPI validation failed: {message}') + + +def main() -> None: + if len(sys.argv) != 3: + fail('usage: validate_xpi.py ') + + xpi_path = Path(sys.argv[1]) + source_manifest_path = Path(sys.argv[2]) + try: + source_manifest = json.loads(source_manifest_path.read_text()) + with zipfile.ZipFile(xpi_path) as archive: + names = set(archive.namelist()) + corrupt_member = archive.testzip() + if corrupt_member is not None: + fail(f'corrupt archive member: {corrupt_member}') + if 'manifest.json' not in names: + fail('manifest.json is missing') + packaged_manifest = json.loads(archive.read('manifest.json')) + except (OSError, zipfile.BadZipFile, json.JSONDecodeError) as error: + fail(str(error)) + + missing_files = REQUIRED_FILES - names + if missing_files: + fail(f'missing required files: {", ".join(sorted(missing_files))}') + unexpected_files = names - REQUIRED_FILES + if any(name.startswith('__MACOSX/') or name == '.DS_Store' for name in unexpected_files): + fail('archive contains macOS metadata') + if packaged_manifest != source_manifest: + fail('packaged manifest does not match webextension/manifest.json') + + print(f'validated unsigned XPI {xpi_path}') + + +if __name__ == '__main__': + main() \ No newline at end of file diff --git a/webextension/manifest.json b/webextension/manifest.json index dd3412a..52d6c9d 100644 --- a/webextension/manifest.json +++ b/webextension/manifest.json @@ -27,6 +27,10 @@ "gecko": { "id": "linklog@kolkman.org", "strict_min_version": "109.0", + "data_collection_permissions": { + "required": ["websiteActivity"], + "optional": [] + }, "update_url": "https://git.kolkman.org/olaf/Link-Log/raw/branch/main/webextension/updates.json" } },