From 62d4acc4d64beb839c3e851e780445865d951b2e Mon Sep 17 00:00:00 2001 From: JB Date: Mon, 13 Oct 2025 16:07:00 -0700 Subject: [PATCH] feat: Improve EPUB path normalization by deduplicating segments and handling backslashes --- abogen/web/routes.py | 20 +++++++++++++++++++- abogen/web/templates/reader_embed.html | 16 +++++++++++++++- 2 files changed, 34 insertions(+), 2 deletions(-) diff --git a/abogen/web/routes.py b/abogen/web/routes.py index c7c20bd..8ef94e2 100644 --- a/abogen/web/routes.py +++ b/abogen/web/routes.py @@ -155,7 +155,12 @@ def _normalize_epub_path(base_dir: str, href: str) -> str: sanitized_lower = sanitized.lower() if normalized_base: base_lower = normalized_base.lower() - if sanitized_lower.startswith(base_lower + "/"): + prefix = base_lower + "/" + if sanitized_lower.startswith(prefix): + remainder = sanitized[len(prefix):] + if remainder.lower().startswith(prefix): + sanitized = remainder + sanitized_lower = sanitized.lower() base_dir = "" elif sanitized_lower == base_lower: base_dir = "" @@ -164,6 +169,19 @@ def _normalize_epub_path(base_dir: str, href: str) -> str: normalized = posixpath.normpath(combined) if normalized in {"", "."}: return "" + normalized = normalized.replace("\\", "/") + segments = [segment for segment in normalized.split("/") if segment and segment != "."] + if not segments: + return "" + deduped: List[str] = [] + last_lower: Optional[str] = None + for segment in segments: + segment_lower = segment.lower() + if last_lower == segment_lower: + continue + deduped.append(segment) + last_lower = segment_lower + normalized = "/".join(deduped) if normalized.startswith("../") or normalized == "..": return "" return normalized diff --git a/abogen/web/templates/reader_embed.html b/abogen/web/templates/reader_embed.html index 49f83f1..35a57d9 100644 --- a/abogen/web/templates/reader_embed.html +++ b/abogen/web/templates/reader_embed.html @@ -216,7 +216,21 @@ if (typeof value !== 'string') { return value; } - return value.replace(/^[\/]+/, ''); + const normalized = value.replace(/\\/g, '/').replace(/^[\/]+/, ''); + if (!normalized) { + return ''; + } + const segments = normalized.split('/').filter((segment) => segment && segment !== '.'); + if (!segments.length) { + return ''; + } + const deduped = []; + segments.forEach((segment) => { + if (!deduped.length || deduped[deduped.length - 1].toLowerCase() !== segment.toLowerCase()) { + deduped.push(segment); + } + }); + return deduped.join('/'); }; const normalizeHref = (value) => {