diff --git a/MethodenAnalyser3.py b/MethodenAnalyser3.py index 847be23..a459af6 100644 --- a/MethodenAnalyser3.py +++ b/MethodenAnalyser3.py @@ -1,6 +1,7 @@ import argparse import ast import builtins +import codecs import collections import datetime import difflib @@ -1092,7 +1093,7 @@ def analyze_file(path: str) -> AnalysisResult: # Datei lesen try: - with open(path, "r", encoding="utf-8") as f: + with open(path, "r", encoding="utf-8-sig") as f: code = f.read() except UnicodeDecodeError: try: @@ -1611,8 +1612,10 @@ def auto_fix_unused_imports(output_widget: scrolledtext.ScrolledText, status_wid # Datei lesen mit Encoding-Fallback — erkanntes Encoding für Schreibzugriff merken detected_encoding = "utf-8" original_bytes = pathlib.Path(_last_analysis_path).read_bytes() + if original_bytes.startswith(codecs.BOM_UTF8): + detected_encoding = "utf-8-sig" try: - original_text = original_bytes.decode("utf-8") + original_text = original_bytes.decode(detected_encoding) except UnicodeDecodeError: detected_encoding = "latin-1" original_text = original_bytes.decode("latin-1") diff --git a/README.md b/README.md index 96db3f3..728cb6b 100644 --- a/README.md +++ b/README.md @@ -5,7 +5,7 @@

Version 3.0.2 CI Status - Tests 100% Green + Tests 100% Green Python 3.10-3.13 Cross-Platform GUI Desktop + CLI + PWA @@ -173,7 +173,7 @@ MethodenAnalyser enforces 10 strict architectural, runtime, and security invaria | **INV-ENCODING-04** | Lossless Multi-Encoding Resilience | Default UTF-8 reading with automatic Latin-1 fallback. Never corrupts original character encodings, UTF-8 BOMs, or German umlauts. | | **INV-ISOLATION-05** | Loopback Web Companion Isolation | Local helper companion binds strictly to `127.0.0.1` by default. Deliberate `--host 0.0.0.0` LAN test mode requires explicit user configuration. | | **INV-TRAVERSAL-06** | Anti-Path-Traversal & ZIP Safety | Web companion rejects path traversal (`..`), bounds member counts, and enforces maximum archive byte limits to prevent ZIP bombs. | -| **INV-AUTOFIX-07** | Reversible Auto-Fix with Backup | Auto-Fix requires explicit confirmation, rechecks current source usage and publishes a byte-exact backup without replacing existing copies (`.bak`, `.bak.1`, …), then atomically replaces the source from a flushed temporary file. Detected source changes abort the write; encoding, newline style and file mode are retained. | +| **INV-AUTOFIX-07** | Reversible Auto-Fix with Backup | Auto-Fix requires explicit confirmation, rechecks current source usage and publishes a byte-exact backup without replacing existing copies (`.bak`, `.bak.1`, …), then atomically replaces the source from a flushed temporary file. Detected source changes abort the write; encoding (including a UTF-8 BOM), newline style and file mode are retained. | | **INV-PLATFORM-08** | Multi-Platform Parity | 100% standard library core verified across Windows Server 2025, Ubuntu Linux, and macOS 26. | | **INV-SYNC-09** | Cloud-Sync & Multi-Agent Lock Safety | Fails closed on lock detection (`LOCK.*`), ignores cloud sync conflict files (`*-conflict-*`), preventing concurrency races. | | **INV-SLA-10** | 48h Security SLA & 5-Day Triage | Strict vulnerability commitment: 48-hour initial response SLA and 5-business-day triage commitment with coordinated disclosure. | diff --git a/README_de.md b/README_de.md index c2b96c0..a8e85b1 100644 --- a/README_de.md +++ b/README_de.md @@ -5,7 +5,7 @@

Version 3.0.2 CI-Status - Tests 100% Grün + Tests 100% Grün Python 3.10-3.13 Plattformübergreifend GUI Desktop + CLI + PWA @@ -173,7 +173,7 @@ MethodenAnalyser garantiert 10 verbindliche Architektur-, Laufzeit- und Sicherhe | **INV-ENCODING-04** | Verlustfreie Multi-Encoding-Resilienz | Liest Dateien standardmäßig als UTF-8 mit automatischem Latin-1 Fallback. Verändert niemals Original-Encodings oder deutsche Umlaute. | | **INV-ISOLATION-05** | Loopback Web-Begleiter-Isolation | Der lokale Hilfsserver bindet sich standardmäßig ausschließlich an `127.0.0.1`. Der LAN-Modus `--host 0.0.0.0` erfordert explizite Aktivierung. | | **INV-TRAVERSAL-06** | Pfad-Traversal- & ZIP-Sicherheit | Der Web-Begleiter blockiert Directory-Traversal (`..`), begrenzt Dateizahlen und unkomprimierte Byte-Mengen zur Abwehr von ZIP-Bomben. | -| **INV-AUTOFIX-07** | Reversibler Auto-Fix mit Backup | Auto-Fix erfordert eine explizite Bestätigung, prüft die aktuelle Verwendung erneut und veröffentlicht eine bytegenaue Sicherung ohne bestehende Backups zu überschreiben (`.bak`, `.bak.1`, …) und ersetzt die Quelle anschließend atomar aus einer vollständig geschriebenen temporären Datei. Erkannte Quelländerungen brechen den Schreibvorgang ab; Kodierung, Zeilenumbruchstil und Dateimodus bleiben erhalten. | +| **INV-AUTOFIX-07** | Reversibler Auto-Fix mit Backup | Auto-Fix erfordert eine explizite Bestätigung, prüft die aktuelle Verwendung erneut und veröffentlicht eine bytegenaue Sicherung ohne bestehende Backups zu überschreiben (`.bak`, `.bak.1`, …) und ersetzt die Quelle anschließend atomar aus einer vollständig geschriebenen temporären Datei. Erkannte Quelländerungen brechen den Schreibvorgang ab; Kodierung einschließlich UTF-8-BOM, Zeilenumbruchstil und Dateimodus bleiben erhalten. | | **INV-PLATFORM-08** | Plattformübergreifende Parität | Vollständige Standardbibliothek-Kompatibilität, verifiziert auf Windows Server 2025, Ubuntu Linux und macOS 26. | | **INV-SYNC-09** | Cloud-Sync- & Lock-Resilienz | Beachtet Dateisperren (`LOCK.*`) im Fail-Closed-Verfahren und ignoriert Cloud-Sync-Konfliktdateien (`*-conflict-*`). | | **INV-SLA-10** | 48h Sicherheits-SLA & 5-Tage-Triage | Verbindliches Sicherheitsversprechen: Eingangsbestätigung binnen 48 Stunden, Triage und Behebungsplan binnen 5 Werktagen. | diff --git a/llms.txt b/llms.txt index ea5b237..8098dbc 100644 --- a/llms.txt +++ b/llms.txt @@ -35,7 +35,7 @@ The tool strictly adheres to 10 architectural and security invariants: - INV-ENCODING-04: Lossless Multi-Encoding Resilience (UTF-8 default with Latin-1 fallback) - INV-ISOLATION-05: Loopback Web Companion Isolation (binds to 127.0.0.1 by default) - INV-TRAVERSAL-06: Path Traversal & ZIP Safety Bounds (blocks `..` and bounds member sizes) -- INV-AUTOFIX-07: Reversible Auto-Fix with `.bak` Backup; current source is rechecked after confirmation, and only approved imports that remain unused are removed. Import spans preserve neighboring statements; empty suites retain pass, and the result is syntax-checked before writing. Auto-Fix stages and flushes complete byte payloads, exclusively publishes numbered backups via same-directory hard links and atomically replaces the source. Detected source changes abort; original bytes remain in the backup. Unsupported backup publication fails before source replacement. +- INV-AUTOFIX-07: Reversible Auto-Fix with `.bak` Backup; current source is rechecked after confirmation, and only approved imports that remain unused are removed. Import spans preserve neighboring statements; empty suites retain pass, and the result is syntax-checked before writing. Auto-Fix stages and flushes complete byte payloads, exclusively publishes numbered backups via same-directory hard links and atomically replaces the source. Detected source changes abort; original bytes remain in the backup. Unsupported backup publication fails before source replacement. File analysis accepts a leading UTF-8 BOM; Auto-Fix preserves the signature and byte-exact recovery copy. - INV-PLATFORM-08: Cross-Platform Parity across Windows, Linux, and macOS - INV-SYNC-09: Cloud-Sync & Multi-Agent Lock Resilience (fail-closed against `LOCK.*`) - INV-SLA-10: 48h Initial Response & 5-Business-Day Triage SLA diff --git a/tests/test_utf8_bom.py b/tests/test_utf8_bom.py new file mode 100644 index 0000000..32cd6ea --- /dev/null +++ b/tests/test_utf8_bom.py @@ -0,0 +1,57 @@ +"""UTF-8 BOM is a valid Python signature and must survive import cleanup.""" +import codecs +import json +import subprocess +import sys +import warnings +from pathlib import Path +from unittest.mock import Mock + +import pytest + +import MethodenAnalyser3 as m + + +@pytest.mark.parametrize("newline", ["\n", "\r\n", "\r"]) +def test_analysis_accepts_utf8_bom_without_fallback(tmp_path, newline): + path = tmp_path / "signed.py" + source = newline.join(["import os", "label = 'Grüße'", "print(label)", ""]) + path.write_bytes(source.encode("utf-8-sig")) + with warnings.catch_warnings(record=True) as caught: + result = m.analyze_file(str(path)) + assert not caught + assert result.unused_imports == ["os"] + + +@pytest.mark.parametrize("source,expected", [ + ("import os\r\nanswer = 42\r\n", "answer = 42\r\n"), + ("label = 'Grüße'; import os; answer = 42\n", "label = 'Grüße'; pass; answer = 42\n"), + ("import os\n", ""), +]) +def test_auto_fix_preserves_bom_and_exact_backup(monkeypatch, tmp_path, source, expected): + path = tmp_path / "signed.py" + raw = source.encode("utf-8-sig") + path.write_bytes(raw) + monkeypatch.setattr(m, "_last_analysis_path", str(path)) + monkeypatch.setattr(m, "_last_analysis_result", m.analyze_source(source)) + dialogs = Mock() + dialogs.askyesno.return_value = True + monkeypatch.setattr(m, "messagebox", dialogs) + m.auto_fix_unused_imports(Mock()) + dialogs.showerror.assert_not_called() + assert path.read_bytes() == expected.encode("utf-8-sig") + assert path.read_bytes().startswith(codecs.BOM_UTF8) + assert path.with_suffix(".py.bak").read_bytes() == raw + assert not list(tmp_path.glob(".*.autofix-*")) + + +def test_cli_reports_bom_file_findings(tmp_path): + path = tmp_path / "signed.py" + path.write_bytes("import os\nprint('Grüße')\n".encode("utf-8-sig")) + report = tmp_path / "report.json" + script = Path(m.__file__).resolve() + result = subprocess.run([sys.executable, str(script), "--lang", "de", "--file", str(path), + "--json-output", str(report)], capture_output=True, check=False) + assert result.returncode == m.EXIT_FINDINGS, result.stderr + payload = json.loads(report.read_text(encoding="utf-8")) + assert payload["unused_imports"] == {"signed.py": ["os"]}