mirror of
https://github.com/Matysh/houseplan-card
synced 2026-07-31 08:28:31 +00:00
fix: collision names must survive the sanitiser the content view applies
unique_filename produced 'manual (2).pdf'; HouseplanContentView sanitises the name in the REQUEST too, turning ' (2)' into '_2_', so the file was written and then 404'd. The same pattern was already in files/migrate, so a rebind that hit a name collision has been producing dead links. Both use the shared helper now, with '-2', which round-trips sanitize_filename — asserted.
This commit is contained in:
@@ -35,7 +35,11 @@ def unique_filename(directory: Path, name: str) -> str:
|
||||
stem, suffix = safe, ""
|
||||
i = 2
|
||||
while True:
|
||||
candidate = f"{stem} ({i}){'.' + suffix if suffix else ''}"
|
||||
# "-2", not " (2)": the content view sanitizes the name in the REQUEST
|
||||
# too, and a space or a bracket there turns into "_", so a file called
|
||||
# "manual (2).pdf" was written and then never served. Only characters
|
||||
# that survive sanitize_filename may be used to build a name.
|
||||
candidate = f"{stem}-{i}{'.' + suffix if suffix else ''}"
|
||||
if not (directory / candidate).exists():
|
||||
return candidate
|
||||
i += 1
|
||||
|
||||
@@ -20,7 +20,7 @@ from .const import (
|
||||
CONTENT_URL, FILES_DIR, MAX_SIGN_PATHS, PLANS_DIR, PLANS_URL,
|
||||
)
|
||||
from .auth import may_write
|
||||
from .plans import collect_attachments, collect_plans
|
||||
from .plans import collect_attachments, collect_plans, unique_filename
|
||||
from .store import HouseplanData, get_data, get_entry
|
||||
from .validation import (
|
||||
CONFIG_SCHEMA, LAYOUT_SCHEMA, MAX_CONFIG_BYTES, MAX_PLAN_BYTES,
|
||||
@@ -193,15 +193,10 @@ async def ws_files_migrate(hass: HomeAssistant, connection, msg: dict[str, Any])
|
||||
for f in sorted(src.iterdir()):
|
||||
if not f.is_file():
|
||||
continue
|
||||
target = dst / f.name
|
||||
if target.exists():
|
||||
# a different file already owns this name — do NOT silently
|
||||
# point the url at it; give the copy a unique name instead
|
||||
stem, suffix = f.stem, f.suffix
|
||||
i = 2
|
||||
while (dst / f"{stem} ({i}){suffix}").exists():
|
||||
i += 1
|
||||
target = dst / f"{stem} ({i}){suffix}"
|
||||
# a different file may already own this name — do NOT silently point
|
||||
# the url at it; the shared helper picks a free one, using only
|
||||
# characters the content view will accept back in a request
|
||||
target = dst / unique_filename(dst, f.name)
|
||||
shutil.copy2(str(f), str(target))
|
||||
mapping[f.name] = target.name
|
||||
return mapping
|
||||
|
||||
Reference in New Issue
Block a user