From 2ae4c9ba06752ca444175d95ec0e7b9c63e99a00 Mon Sep 17 00:00:00 2001 From: Andrew Barnes Date: Sun, 12 Jul 2026 16:28:47 -0400 Subject: [PATCH] fix: decode encoded Windows drive separators --- packages/markitdown/src/markitdown/_uri_utils.py | 10 +++++++++- packages/markitdown/tests/test_module_misc.py | 9 ++++++++- 2 files changed, 17 insertions(+), 2 deletions(-) diff --git a/packages/markitdown/src/markitdown/_uri_utils.py b/packages/markitdown/src/markitdown/_uri_utils.py index 603da63e9..16eca825d 100644 --- a/packages/markitdown/src/markitdown/_uri_utils.py +++ b/packages/markitdown/src/markitdown/_uri_utils.py @@ -1,5 +1,6 @@ import base64 import os +import re from typing import Tuple, Dict from urllib.request import url2pathname from urllib.parse import urlparse, unquote_to_bytes @@ -12,7 +13,14 @@ def file_uri_to_path(file_uri: str) -> Tuple[str | None, str]: raise ValueError(f"Not a file URL: {file_uri}") netloc = parsed.netloc if parsed.netloc else None - path = os.path.abspath(url2pathname(parsed.path)) + uri_path = parsed.path + if os.name == "nt": + # ``url2pathname`` decodes escapes after detecting a drive prefix. A + # percent-encoded colon therefore looks root-relative during detection + # and becomes ``\C:\...`` only afterward, causing ``abspath`` to prepend + # the current drive. Decode just the drive separator before detection. + uri_path = re.sub(r"^/([A-Za-z])%3[aA](?=/|$)", r"/\1:", uri_path) + path = os.path.abspath(url2pathname(uri_path)) return netloc, path diff --git a/packages/markitdown/tests/test_module_misc.py b/packages/markitdown/tests/test_module_misc.py index 4d62e4919..2b9596101 100644 --- a/packages/markitdown/tests/test_module_misc.py +++ b/packages/markitdown/tests/test_module_misc.py @@ -226,7 +226,6 @@ def test_file_uris() -> None: netloc, path = file_uri_to_path(file_uri) assert netloc is None assert path == "/path/to/file.txt" - # Test file URI with no host file_uri = "file:/path/to/file.txt" netloc, path = file_uri_to_path(file_uri) @@ -252,6 +251,14 @@ def test_file_uris() -> None: assert path == "/path/to/file.txt" +@pytest.mark.skipif(os.name != "nt", reason="Windows drive-letter URI behavior") +def test_file_uri_with_percent_encoded_drive_colon() -> None: + _, unescaped_path = file_uri_to_path("file:///C:/Temp/example.md") + _, escaped_path = file_uri_to_path("file:///C%3A/Temp/example.md") + + assert escaped_path == unescaped_path + + def test_docx_comments() -> None: # Test DOCX processing, with comments and setting style_map on init markitdown_with_style_map = MarkItDown(style_map="comment-reference => ")