Skip to content

fix(model): long attachment names keep their extension and stay removable (#130, #131) - #174

Merged
DWAA1660 merged 3 commits into
AlienTechSoftware:mainfrom
DWAA1660:fix/attachment-name-length
Nov 12, 2026
Merged

DWAA1660 merged 3 commits into
AlienTechSoftware:mainfrom
DWAA1660:fix/attachment-name-length

Conversation

@DWAA1660

Copy link
Copy Markdown
Collaborator

What and why

Fixes #130 and #131. Both come from the same naming rule, so they're one PR: the fix for #131 has to build on the length rule #130 changes.

#130: long names lost their extension. names::safe_file_name cut the whole cleaned name with .chars().take(80). "x"×90 + ".pdf" was saved as 80 x characters with no extension, so media_kind called a long .png Other and the code view stopped recognising PDFs and Office files.

#131: a long name's second copy couldn't be removed. assets::unique_name added -2 after that cut. An 80-character ….txt became an 82-character …-2.txt, and assets::remove (and so page.detach) refused it, because re-sanitising it didn't give the same name back.

Changes in notecraft_model::names:

  • MAX_FILE_NAME (80) names the limit.
  • split_extension(name) splits off a real extension: up to 16 ASCII letters and digits after the last dot.
  • fit_file_name(stem, extension, suffix) joins the parts within the limit by cutting the stem, never the extension or suffix.
  • safe_file_name cleans as before (path parts, characters, reserved names), then fits through fit_file_name. Applying it again changes nothing, which is what remove relies on.

assets::unique_name now builds stem-N.ext with fit_file_name, so every copy, however many digits its suffix has, is a safe name that remove accepts.

How it was checked

  • New names::tests::a_long_attachment_name_keeps_its_extension:
    • .pdf, .png, .docx and .tar.gz keep their extension at exactly 80 characters, and a name already at the limit is kept
    • extensionless and space-containing names are cut as before
    • a reserved stem made safe still fits
    • safe_file_name is idempotent on a set of tricky names, including multibyte ones
  • New names::tests::a_suffixed_name_fits_and_stays_safe: suffixes -2, -10 and -1234 fit with .txt kept, and are safe names
  • New assets::tests::long_names_keep_their_type_and_every_copy_can_be_removed: through the real store, long PDF/PNG/DOCX keep their type (a long PNG is MediaKind::Image). Twelve copies of the issue's 80-character .txt all get distinct names and all can be removed.
  • cargo test -p notecraft-model -p notecraft-store -p notecraft-engine -p notecraft-import: pass
  • cargo test -p notecraft-ui-egui: all pass (302 unit, 200 UI)
  • cargo fmt --all -- --check, cargo clippy -p notecraft-model -p notecraft-store --all-targets -- -D warnings: clean

🤖 Generated with Claude Code

`safe_file_name` cut a cleaned name to 80 characters as a whole, so a
long `report.pdf` lost its `.pdf` and was no longer known as a PDF or a
picture (AlienTechSoftware#130). `unique_name` then added `-2` after that cut, making an
82-character name that `remove` refused as unsafe, so a second copy of a
long attachment could not be taken out again (AlienTechSoftware#131).

A name is now cut in its stem, before the extension, and the suffix of a
copy goes in through the same rule, so every name the writer makes is
one the remover accepts.

Fixes AlienTechSoftware#130
Fixes AlienTechSoftware#131

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@DWAA1660

Copy link
Copy Markdown
Collaborator Author

Audit finding: the new truncation can turn a valid filename into a reserved Windows device name. Reproduced by compiling this PR's actual crates/model/src/names.rs and calling safe_file_name:

CON-.txt -> CON.txt -> file-CON.txt
AUX-.png -> AUX.png -> file-AUX.png
COM1-.pdf -> COM1.pdf -> file-COM1.pdf

The second arrow is a second call on the result. Main preserves the original names. fit_file_name trims - after is_reserved_name has already checked the stem, so the output is both reserved and non-idempotent. A subsequent operation that validates the stored name can reject it.

Please validate the final truncated/trimmed stem for reserved names (including any safety prefix within the length budget), and add these cases to the idempotence regression. Holding this PR out of the merge batch until fixed.

@DWAA1660
DWAA1660 merged commit 430b731 into AlienTechSoftware:main Nov 12, 2026
10 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[BUG] Long attachment names lose their file extension when sanitized

1 participant