From d51ccbf6ce2758dc24a16a5dc4ffcd113f9eb5cc Mon Sep 17 00:00:00 2001 From: xuu33030 <268775543+xuu33030@users.noreply.github.com> Date: Sat, 19 Sep 2026 01:04:05 +0800 Subject: [PATCH] fix(cli): handle non-UTF-8 source files --- CHANGELOG.md | 4 ++++ maithili_dsl/cli.py | 12 ++++++++++-- tests/test_cli.py | 29 +++++++++++++++++++++++++++++ tests/test_security.py | 14 ++++++++++++++ 4 files changed, 57 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f310d88..430cc2d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +### Fixed +- Non-UTF-8 `.dmai` files now return a dedicated CLI error instead of a + raw Python traceback. Closes #27. + ## [0.4.0] — 2026-07-23 First release published to PyPI since 0.2.0 (0.3.0 was an internal diff --git a/maithili_dsl/cli.py b/maithili_dsl/cli.py index 6516e5a..0e31cde 100644 --- a/maithili_dsl/cli.py +++ b/maithili_dsl/cli.py @@ -103,6 +103,7 @@ def _validate_imports(python_code, translated_modules): EXIT_LINT_ERROR = 4 EXIT_IMPORT_BLOCKED = 5 EXIT_RUNTIME_ERROR = 6 +EXIT_BAD_ENCODING = 7 def run_dmai_file(file_path): @@ -116,8 +117,15 @@ def run_dmai_file(file_path): print(f"Error: File '{file_path}' not found.") return EXIT_NOT_FOUND - with open(path, 'r', encoding='utf-8') as file: - maithili_code = file.read() + try: + with open(path, 'r', encoding='utf-8') as file: + maithili_code = file.read() + except UnicodeDecodeError: + print("⚠️ त्रुटि: फाइल UTF-8 में नहि अछि।") + return EXIT_BAD_ENCODING + except OSError: + print("⚠️ त्रुटि: फाइल पढ़ल नहि जा सकल।") + return EXIT_NOT_FOUND errors = lint_maithili_code(maithili_code) if errors: diff --git a/tests/test_cli.py b/tests/test_cli.py index ce1d080..02bd71d 100644 --- a/tests/test_cli.py +++ b/tests/test_cli.py @@ -8,6 +8,7 @@ import pytest from maithili_dsl.cli import ( + EXIT_BAD_ENCODING, EXIT_IMPORT_BLOCKED, EXIT_LINT_ERROR, EXIT_NOT_FOUND, @@ -30,6 +31,34 @@ def test_missing_file_returns_not_found(capsys): assert "not found" in captured.out.lower() +def test_non_utf8_file_returns_bad_encoding(tmp_path, capsys): + path = tmp_path / "invalid.dmai" + path.write_bytes(b"\xff\xfe") + + code = run_dmai_file(str(path)) + captured = capsys.readouterr() + + assert code == EXIT_BAD_ENCODING + assert "UTF-8" in captured.out + assert "Traceback" not in captured.out + + +def test_unreadable_file_returns_not_found(tmp_path, monkeypatch, capsys): + path = tmp_path / "unreadable.dmai" + path.write_text("छपाउ(\"hello\")\n", encoding="utf-8") + + def deny_read(*args, **kwargs): + raise PermissionError("private path details") + + monkeypatch.setattr("builtins.open", deny_read) + code = run_dmai_file(str(path)) + captured = capsys.readouterr() + + assert code == EXIT_NOT_FOUND + assert "फाइल पढ़ल नहि जा सकल" in captured.out + assert "private path details" not in captured.out + + def test_lint_error_returns_lint_exit_code(tmp_dmai_file, capsys): # The leading = triggers the "'=' चिह्नक पहिले" lint error. path = tmp_dmai_file("= ५\n") diff --git a/tests/test_security.py b/tests/test_security.py index 8620de5..0a2c105 100644 --- a/tests/test_security.py +++ b/tests/test_security.py @@ -22,6 +22,7 @@ _validate_imports, run_dmai_file, translate_imports, + EXIT_BAD_ENCODING, EXIT_OK, EXIT_IMPORT_BLOCKED, EXIT_RUNTIME_ERROR, @@ -291,3 +292,16 @@ def test_actual_dangerous_code_outside_strings_still_blocked(): transpiled = transpile_maithili_code(code) errors = _validate_imports(transpiled, translated_modules=set()) assert errors, f"raw 'import os' must be blocked by import validator: {errors}" + + +@pytest.mark.security +def test_invalid_encoding_is_rejected_before_execution(tmp_path, capsys): + """Malformed source must fail closed without reaching the sandbox.""" + path = tmp_path / "invalid.dmai" + path.write_bytes(b"\xff\xfe") + + code = run_dmai_file(str(path)) + captured = capsys.readouterr() + + assert code == EXIT_BAD_ENCODING + assert "UTF-8" in captured.out