Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 21 additions & 0 deletions tests/test_safe_output_path.py
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,27 @@ def test_wrong_extension_is_replaced(self):
tools._safe_output_path("plan.exe", ".pdf"), self.save_dir / "plan.pdf"
)

def test_longer_real_extension_is_replaced(self):
self.assertEqual(
tools._safe_output_path("plan.jpeg", ".pdf"), self.save_dir / "plan.pdf"
)

def test_dotted_sheet_name_keeps_its_tail(self):
# The tail is not an extension, so it stays in the name.
self.assertEqual(
tools._safe_output_path("Sheet A.1", ".dwg"),
self.save_dir / "Sheet A.1.dwg",
)
self.assertEqual(
tools._safe_output_path("Sheet 1.A", ".dwg"),
self.save_dir / "Sheet 1.A.dwg",
)

def test_dotted_sheet_names_do_not_share_a_file(self):
first = tools._safe_output_path("Sheet A.1", ".dwg")
second = tools._safe_output_path("Sheet A.2", ".dwg")
self.assertNotEqual(first, second)

def test_subfolder_is_allowed_and_created(self):
target = tools._safe_output_path("floor-1/plan.dwg", ".dwg")
self.assertEqual(target, self.save_dir / "floor-1" / "plan.dwg")
Expand Down
11 changes: 10 additions & 1 deletion tools.py
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,12 @@ def _color_index(color: str | int | None) -> int | None:
)


# A dot is read as a file extension only when two to four ASCII letters follow
# it (.dwg, .pdf, .exe, .jpeg). Any other tail, such as "Sheet A.1" or
# "Sheet 1.A", is part of the name and is kept.
_EXTENSION_SHAPE = re.compile(r"\.[A-Za-z]{2,4}")


def _safe_output_path(filename: str, extension: str) -> Path:
"""Resolve filename inside the one folder the server may write to."""
candidate = Path(str(filename).strip().strip('"').strip("'"))
Expand All @@ -69,7 +75,10 @@ def _safe_output_path(filename: str, extension: str) -> Path:
if not candidate.name:
raise AcadError("A file name is required.")
if candidate.suffix.lower() != extension:
candidate = candidate.with_suffix(extension)
if _EXTENSION_SHAPE.fullmatch(candidate.suffix):
candidate = candidate.with_suffix(extension)
else:
candidate = candidate.with_name(candidate.name + extension)

resolved = (config.SAVE_DIR / candidate).resolve()
# A path comparison, not a string prefix: "...\Out-other" starts with
Expand Down
Loading