Skip to content

Commit ea85308

Browse files
author
Sebastian Braun
committed
fix(converter): cap doc_name length to avoid Windows path-length errors
1 parent ff54396 commit ea85308

2 files changed

Lines changed: 48 additions & 1 deletion

File tree

‎openkb/converter.py‎

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,11 +48,24 @@ def _registry_path(path: Path, kb_dir: Path) -> str:
4848

4949
_SAFE_STEM_RE = re.compile(r"[^\w\-]+")
5050
_SUFFIX_LEN = 8
51+
_MAX_STEM_LEN = 40
5152

5253

5354
def _sanitize_stem(stem: str) -> str:
55+
"""Sanitize and cap ``stem``, appending a hash suffix if truncated.
56+
57+
``doc_name`` (this function's return value) can appear twice in the
58+
per-add staging path (staging dir name + images subdir), so an
59+
unbounded stem risks Windows' ~260-char path limit. The suffix is a
60+
hash of the FULL cleaned stem (not just the truncated prefix) so two
61+
different overlong stems sharing the same prefix don't collide.
62+
"""
5463
normalized = unicodedata.normalize("NFKC", stem)
55-
return _SAFE_STEM_RE.sub("-", normalized).strip("-") or "document"
64+
cleaned = _SAFE_STEM_RE.sub("-", normalized).strip("-") or "document"
65+
if len(cleaned) > _MAX_STEM_LEN:
66+
digest = hashlib.sha256(cleaned.encode("utf-8")).hexdigest()[:_SUFFIX_LEN]
67+
cleaned = f"{cleaned[:_MAX_STEM_LEN].rstrip('-')}-{digest}"
68+
return cleaned
5669

5770

5871
def _name_taken(candidate: str, registry: HashRegistry) -> bool:

‎tests/test_converter.py‎

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -217,6 +217,40 @@ def test_outside_kb_is_absolute_posix(self, kb_dir, tmp_path_factory):
217217
assert result.startswith("/")
218218

219219

220+
# ---------------------------------------------------------------------------
221+
# _sanitize_stem
222+
# ---------------------------------------------------------------------------
223+
224+
225+
class TestSanitizeStem:
226+
def test_short_stem_is_unchanged(self):
227+
from openkb.converter import _sanitize_stem
228+
229+
assert _sanitize_stem("report") == "report"
230+
231+
def test_long_stem_is_capped_with_hash_suffix(self):
232+
from openkb.converter import _MAX_STEM_LEN, _sanitize_stem
233+
234+
stem = "bulkQuery_result_" + "a" * 60
235+
result = _sanitize_stem(stem)
236+
assert len(result) == _MAX_STEM_LEN + 1 + 8 # prefix + "-" + 8-hex digest
237+
assert result.startswith(stem[:_MAX_STEM_LEN])
238+
239+
def test_long_stem_truncation_is_deterministic(self):
240+
from openkb.converter import _sanitize_stem
241+
242+
stem = "x" * 100
243+
assert _sanitize_stem(stem) == _sanitize_stem(stem)
244+
245+
def test_different_long_stems_with_same_prefix_do_not_collide(self):
246+
from openkb.converter import _MAX_STEM_LEN, _sanitize_stem
247+
248+
prefix = "a" * _MAX_STEM_LEN
249+
first = _sanitize_stem(prefix + "-one")
250+
second = _sanitize_stem(prefix + "-two")
251+
assert first != second
252+
253+
220254
# ---------------------------------------------------------------------------
221255
# resolve_doc_name
222256
# ---------------------------------------------------------------------------

0 commit comments

Comments
 (0)