diff --git a/.github/workflows/qt6-tests.yml b/.github/workflows/qt6-tests.yml index 54c17470..46a142b8 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 vermin requests || true + pip install pyfakefs PySide6 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 2e5f8c61..ba3b8a1f 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 @@ -154,7 +154,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" @@ -168,7 +168,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 9716868b..bfc94781 100644 --- a/AddonCatalogCacheCreator.py +++ b/AddonCatalogCacheCreator.py @@ -39,7 +39,7 @@ import requests import subprocess from typing import List -import xml.etree.ElementTree +from xml.etree.ElementTree import ParseError as XmlParseError import zipfile import AddonCatalog @@ -282,7 +282,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 a8d320bd..674d8b78 100644 --- a/Resources/translations/run_translation_cycle.py +++ b/Resources/translations/run_translation_cycle.py @@ -36,7 +36,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" @@ -85,6 +85,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: @@ -136,6 +139,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 953acd37..42ebaa94 100644 --- a/addonmanager_utilities.py +++ b/addonmanager_utilities.py @@ -423,6 +423,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 b100afde..949c31b9 100644 --- a/addonmanager_workers_startup.py +++ b/addonmanager_workers_startup.py @@ -27,7 +27,7 @@ import json import os from typing import List -import xml.etree.ElementTree +from xml.etree.ElementTree import ParseError as XmlParseError import zipfile from PySideWrapper import QtCore @@ -134,7 +134,7 @@ def _get_custom_addons(self): repo.installed_version = repo.installed_metadata.version repo.updated_timestamp = os.path.getmtime(md_file) repo.verify_url_and_branch(addon["url"], addon["branch"]) - except xml.etree.ElementTree.ParseError: + except XmlParseError: fci.Console.PrintWarning( f"An invalid or corrupted package.xml file was installed for custom addon {name}... ignoring the bad data.\n" ) @@ -512,14 +512,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 f7f210e4..4ec2756e 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