From 6750db404b51f94f7a44709fdd3a18c13c1b6bbe Mon Sep 17 00:00:00 2001 From: Chris Hennes Date: Sun, 22 Feb 2026 15:59:11 -0600 Subject: [PATCH 1/2] Cleanup reported issues (cherry picked from commit 157e586cc699aa501865684308ef44f71466519d) --- .github/workflows/qt6-tests.yml | 2 +- Addon.py | 6 +++--- AddonCatalog.py | 6 +++--- AddonCatalogCacheCreator.py | 4 ++-- Resources/translations/run_translation_cycle.py | 10 +++++++++- addonmanager_icon_utilities.py | 8 +------- addonmanager_metadata.py | 8 +------- addonmanager_utilities.py | 5 +++++ addonmanager_workers_startup.py | 10 ++-------- package.xml | 1 + 10 files changed, 28 insertions(+), 32 deletions(-) diff --git a/.github/workflows/qt6-tests.yml b/.github/workflows/qt6-tests.yml index 2dda6cff..e026e58b 100644 --- a/.github/workflows/qt6-tests.yml +++ b/.github/workflows/qt6-tests.yml @@ -66,7 +66,7 @@ jobs: - name: Install Python dependencies run: | python -m pip install --upgrade pip - pip install pyfakefs "PySide6<6.11" vermin requests || true + pip install pyfakefs "PySide6<6.11" vermin requests defusedxml || true - name: Run App tests run: | diff --git a/Addon.py b/Addon.py index edea74f3..e1acda96 100644 --- a/Addon.py +++ b/Addon.py @@ -29,7 +29,7 @@ from typing import Dict, Set, List, Optional from threading import Lock from enum import IntEnum, auto -import xml.etree.ElementTree +from xml.etree.ElementTree import ParseError as XmlParseError try: import importlib.metadata as importlib_metadata @@ -320,7 +320,7 @@ def load_metadata_file(self, file: str) -> None: if os.path.exists(file): try: metadata = MetadataReader.from_file(file) - except xml.etree.ElementTree.ParseError: + except XmlParseError: fci.Console.PrintWarning( "An invalid or corrupted package.xml file was found in the cache for" ) @@ -339,7 +339,7 @@ def _load_installed_metadata(self) -> None: if os.path.isfile(installed_metadata_path): try: self.installed_metadata = MetadataReader.from_file(installed_metadata_path) - except xml.etree.ElementTree.ParseError: + except XmlParseError: fci.Console.PrintWarning( "An invalid or corrupted package.xml file was found in installation of" ) diff --git a/AddonCatalog.py b/AddonCatalog.py index ec140df0..ab930e97 100644 --- a/AddonCatalog.py +++ b/AddonCatalog.py @@ -25,7 +25,7 @@ import base64 import datetime import os -import xml.etree.ElementTree +from xml.etree.ElementTree import ParseError as XmlParseError from dataclasses import dataclass import json from hashlib import sha256 @@ -157,7 +157,7 @@ def instantiate_addon(self, addon_id: str) -> Addon: if self.metadata: try: self._load_addon_metadata(addon, self.metadata) - except xml.etree.ElementTree.ParseError: + except XmlParseError: fci.Console.PrintWarning( "An invalid or corrupted package.xml file was installed " f"for {addon.display_name}\n" @@ -171,7 +171,7 @@ def instantiate_addon(self, addon_id: str) -> Addon: try: package_file = os.path.join(fci.DataPaths().mod_dir, addon_id, "package.xml") addon.installed_metadata = MetadataReader.from_file(package_file) - except (FileNotFoundError, xml.etree.ElementTree.ParseError, RuntimeError): + except (FileNotFoundError, XmlParseError, RuntimeError): pass # If there was an error, just ignore it, no metadata is not fatal most_recent_mtime = AddonCatalogEntry.most_recent_mtime(addon_id) diff --git a/AddonCatalogCacheCreator.py b/AddonCatalogCacheCreator.py index 90fad047..b663aeea 100644 --- a/AddonCatalogCacheCreator.py +++ b/AddonCatalogCacheCreator.py @@ -37,7 +37,7 @@ import re import requests import subprocess -import xml.etree.ElementTree +from xml.etree.ElementTree import ParseError as XmlParseError import zipfile import AddonCatalog @@ -309,7 +309,7 @@ def generate_cache_entry_from_package_xml( metadata = addonmanager_metadata.MetadataReader.from_bytes( cache_entry.package_xml.encode("utf-8") ) - except xml.etree.ElementTree.ParseError: + except XmlParseError: print(f"ERROR: Failed to parse XML from {path_to_package_xml}") return None except RuntimeError: diff --git a/Resources/translations/run_translation_cycle.py b/Resources/translations/run_translation_cycle.py index a4a57328..72c05420 100644 --- a/Resources/translations/run_translation_cycle.py +++ b/Resources/translations/run_translation_cycle.py @@ -35,7 +35,7 @@ import time from functools import lru_cache from urllib.parse import quote_plus -from urllib.request import Request, urlopen, urlretrieve +from urllib.request import Request, urlopen, urlretrieve, urlparse CROWDIN_API_URL = "https://api.crowdin.com/api/v2" CROWDIN_API_PROJECT_ID = "freecad-addons" @@ -84,6 +84,9 @@ def _make_api_req(self, url, extra_headers=None, method="GET", data=None): headers["Content-Type"] = "application/json" data = json.dumps(data).encode("utf-8") + parsed_url = urlparse(url) + if parsed_url.scheme != "https": + raise Exception("API requests must be made over HTTPS") request = Request(url, headers=headers, method=method, data=data) request_result = urlopen(request) if request_result.getcode() >= 300: @@ -135,6 +138,11 @@ def status(self): def download(self, build_id): filename = f"{self.project_identifier}.zip" response = self._make_project_api_req(f"/translations/builds/{build_id}/download") + + parsed_url = urlparse(response["url"]) + if parsed_url.scheme != "https": + raise Exception("API requests must be made over HTTPS") + urlretrieve(response["url"], filename) print("download of " + filename + " complete") diff --git a/addonmanager_icon_utilities.py b/addonmanager_icon_utilities.py index 1e6bc7b4..ff38bafc 100644 --- a/addonmanager_icon_utilities.py +++ b/addonmanager_icon_utilities.py @@ -19,6 +19,7 @@ # # ################################################################################ +import defusedxml.ElementTree as ET import re import os import struct @@ -26,13 +27,6 @@ from PySideWrapper import QtCore, QtGui, QtSvg -try: - # If this system provides a secure parser, use that: - import defusedxml.ElementTree as ET -except ImportError: - # Otherwise fall back to the Python standard parser - import xml.etree.ElementTree as ET - from Addon import Addon import addonmanager_freecad_interface as fci diff --git a/addonmanager_metadata.py b/addonmanager_metadata.py index a8462bd9..86e3f7e3 100644 --- a/addonmanager_metadata.py +++ b/addonmanager_metadata.py @@ -25,18 +25,12 @@ from __future__ import annotations from dataclasses import dataclass, field +import defusedxml.ElementTree as ET from enum import IntEnum, auto from typing import Tuple, Dict, List, Optional from addonmanager_licenses import get_license_manager -try: - # If this system provides a secure parser, use that: - import defusedxml.ElementTree as ET -except ImportError: - # Otherwise fall back to the Python standard parser - import xml.etree.ElementTree as ET - @dataclass class Contact: diff --git a/addonmanager_utilities.py b/addonmanager_utilities.py index 97226a4c..c736fc0b 100644 --- a/addonmanager_utilities.py +++ b/addonmanager_utilities.py @@ -424,6 +424,11 @@ def blocking_get(url: str, method=None) -> bytes: succeeded, or an empty string if it failed, or returned no data. The method argument is provided mainly for testing purposes.""" p = b"" + parse_result = urllib.parse.urlparse(url) + if parse_result.scheme != "http" and parse_result.scheme != "https": + raise ValueError( + f"Invalid URL scheme: {parse_result.scheme} (only http and https are supported)" + ) if ( fci.FreeCADGui and method is None diff --git a/addonmanager_workers_startup.py b/addonmanager_workers_startup.py index 0ca6cb63..672d820b 100644 --- a/addonmanager_workers_startup.py +++ b/addonmanager_workers_startup.py @@ -587,14 +587,8 @@ def check_macro(macro_wrapper: Addon) -> None: macro_wrapper.set_status(Addon.Status.CANNOT_CHECK) return - try: - hasher1 = hashlib.sha1(usedforsecurity=False) - hasher2 = hashlib.sha1(usedforsecurity=False) - except TypeError: - # To continue to support Python 3.8, we need to fall back if the usedforsecurity - # is not available. This code should be removed when we drop support for 3.8. - hasher1 = hashlib.sha1() - hasher2 = hashlib.sha1() + hasher1 = hashlib.sha1(usedforsecurity=False) + hasher2 = hashlib.sha1(usedforsecurity=False) hasher1.update(macro_wrapper.macro.code.encode("utf-8")) new_sha1 = hasher1.hexdigest() test_file_one = os.path.join(fci.DataPaths().macro_dir, macro_wrapper.macro.filename) diff --git a/package.xml b/package.xml index 0b2ff4c9..abefffd3 100644 --- a/package.xml +++ b/package.xml @@ -22,6 +22,7 @@ FreeCAD prior to 1.1 can see the InitGui.py file and run it. --> AddonManager ./ + defusedxml From 6cfa9b0d5982ad5fe0ea270b1810fe2b2c665f50 Mon Sep 17 00:00:00 2001 From: Chris Hennes Date: Wed, 13 May 2026 14:59:28 -0500 Subject: [PATCH 2/2] Address review comments (cherry picked from commit 6ea54077a3eb5a6494f0483a009a399430fb7eaf) --- Resources/translations/run_translation_cycle.py | 4 ++-- addonmanager_utilities.py | 6 ++---- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/Resources/translations/run_translation_cycle.py b/Resources/translations/run_translation_cycle.py index 72c05420..ed543623 100644 --- a/Resources/translations/run_translation_cycle.py +++ b/Resources/translations/run_translation_cycle.py @@ -34,8 +34,8 @@ import tempfile import time from functools import lru_cache -from urllib.parse import quote_plus -from urllib.request import Request, urlopen, urlretrieve, urlparse +from urllib.parse import quote_plus, urlparse +from urllib.request import Request, urlopen, urlretrieve CROWDIN_API_URL = "https://api.crowdin.com/api/v2" CROWDIN_API_PROJECT_ID = "freecad-addons" diff --git a/addonmanager_utilities.py b/addonmanager_utilities.py index c736fc0b..4ee88662 100644 --- a/addonmanager_utilities.py +++ b/addonmanager_utilities.py @@ -425,10 +425,8 @@ def blocking_get(url: str, method=None) -> bytes: provided mainly for testing purposes.""" p = b"" parse_result = urllib.parse.urlparse(url) - if parse_result.scheme != "http" and parse_result.scheme != "https": - raise ValueError( - f"Invalid URL scheme: {parse_result.scheme} (only http and https are supported)" - ) + if parse_result.scheme != "https": + raise ValueError(f"Invalid URL scheme: {parse_result.scheme} (only https is supported)") if ( fci.FreeCADGui and method is None