Skip to content

fix(providers): stop trim_text from corrupting listed names around XML entities (LIVE-1) - #536

Merged
axpnet merged 3 commits into
mainfrom
fix/xml-trimtext-listing-names
Jul 30, 2026
Merged

fix(providers): stop trim_text from corrupting listed names around XML entities (LIVE-1)#536
axpnet merged 3 commits into
mainfrom
fix/xml-trimtext-listing-names

Conversation

@axpnet

@axpnet axpnet commented Jul 30, 2026

Copy link
Copy Markdown
Member

Closes LIVE-1, the blocker of the independent pre-release audit (PR #535, security-evidence §8.1).

The bug

Every name-bearing quick-xml reader was configured trim_text(true). For a key like sp ace & ünïcodé.txt the server sends <Key>sp ace &amp; ünïcodé.txt</Key>; quick-xml emits Text("sp ace ") + GeneralRef("amp") + Text(" ünïcodé.txt") and each fragment was trimmed, so listings showed sp ace&ünïcodé.txt. Upload stores the correct name, so GET of the listed name 404'd, and sync --direction download --delete planned delete_local on the user's real file (data-loss class, live-verified on release code against the axpbuntu lab).

The fix

17 readers across 4 providers moved to the same pattern: trim_text(false), whitespace-only indentation fragments skipped, Key/name fragments accumulate raw with GeneralRef expansion via xml_text::xml_entity_to_str, scalar fields (sizes, dates, etags, markers, quota, hrefs) trimmed once at consumption.

  • s3.rs: ListObjectsV2, extract_xml_tag, all-keys pagination, find, ListObjectVersions, tagging, batch-delete errors, trash page (8 readers)
  • webdav.rs: single-file PROPFIND, Nextcloud trashbin, main PROPFIND listing, extract_xml_properties (4)
  • azure.rs: blob list, deleted-blobs list, XML error parse (3)
  • jottacloud.rs: device names, mountpoint names (2); folder/trash listings verified safe (names arrive as attributes via attr_value) and annotated as such
  • sts.rs deliberately untouched (credentials are base64, no entities)

Verification

  • 5 new regression tests: entity-adjacent whitespace round-trips exactly (S3 versions page + batch-delete errors, WebDAV PROPFIND displayname + href fallback, Jottacloud device/mountpoint names)
  • cargo test --lib providers:: 810/810 green; clippy clean; fmt applied
  • LIVE on axpbuntu lab with a release CLI built from this branch:
    • MinIO + Nextcloud: put /ls /get of raw & name2.txt and sp ace & ünïcodé.txt round-trip with sha256 intact; the listing shows the real names
    • sync --direction download --delete --dry-run on both: plan is downloads-only, zero delete_local (the cascade is dead)

Agent: Kimi L2

Summary by CodeRabbit

  • Bug Fixes
    • Improved compatibility with Azure Blob Storage, Amazon S3, Jottacloud, and WebDAV responses containing special characters or entity-encoded text.
    • Corrected file, folder, device, mount point, metadata, and error names so spaces and characters are preserved accurately.
    • Improved pagination handling and normalization of dates, sizes, tags, and other response details.
    • Added safeguards for more reliable parsing of listings, versions, trash contents, and batch-operation results.

axpnet added 2 commits July 30, 2026 23:05
…ted keys

quick-xml emits Text fragments around GeneralRef events; with
trim_text(true) each fragment was trimmed, so a key like 'a & b.txt'
was listed as 'a&b.txt' (or split into bogus fragments). Upload stores
the real name, listing returned the mangled one, GET of the listed
name 404'd, and 'sync --direction download --delete' planned
delete_local on the user's real file.

All eight S3 readers now run trim_text(false): whitespace-only
indentation fragments are skipped, Key/Prefix fragments accumulate
raw with GeneralRef expansion, and scalar fields (Size, ETag,
LastModified, tokens, markers, IsLatest) are trimmed once at
consumption.

Live-verified failing before this fix on MinIO and Nextcloud WebDAV
(axpbuntu lab), FTPS unaffected.

Agent: Kimi L2
…ent whitespace

Same LIVE-1 class as the s3.rs fix: name-bearing quick-xml readers now
run trim_text(false), accumulate Text fragments raw with GeneralRef
expansion, skip whitespace-only indentation fragments, and trim scalar
fields once at consumption (sizes, dates, etags, markers, quota values,
hrefs). jottacloud folder/trash listings keep trim_text(true) with a
comment: their names arrive as attributes via xml_text::attr_value and
never touch Text events.

Regression tests: entity-adjacent whitespace round-trips exactly on
WebDAV PROPFIND displaynames and Jottacloud device/mountpoint names
(the S3 versions/batch-delete cases ride the previous commit).

cargo test --lib providers:: 810/810 green; clippy clean.

Agent: Kimi L2
@snyk-io

snyk-io Bot commented Jul 30, 2026

Copy link
Copy Markdown

Snyk checks have passed. No issues have been found so far.

Status Scan Engine Critical High Medium Low Total (0)
Open Source Security 0 0 0 0 0 issues

💻 Catch issues earlier using the plugins for VS Code, JetBrains IDEs, Visual Studio, and Eclipse.

@coderabbitai

coderabbitai Bot commented Jul 30, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

@axpnet, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 21 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 370bb5ed-c237-4412-8641-7307a2eca0f2

📥 Commits

Reviewing files that changed from the base of the PR and between 403d565 and b792b43.

📒 Files selected for processing (3)
  • src-tauri/src/providers/azure.rs
  • src-tauri/src/providers/s3.rs
  • src-tauri/src/providers/webdav.rs
📝 Walkthrough

Walkthrough

Updated Azure, Jottacloud, S3, and WebDAV XML parsers to preserve meaningful whitespace adjacent to XML entities while skipping indentation-only fragments. Final scalar fields and pagination markers are trimmed, with regression tests added for affected name and error parsing.

Changes

XML parsing normalization

Layer / File(s) Summary
Azure XML parser normalization
src-tauri/src/providers/azure.rs
Azure error, blob listing, deleted-blob listing, and pagination parsing preserve entity-adjacent text and trim finalized values.
Jottacloud name reconstruction
src-tauri/src/providers/jottacloud.rs
Device and mountpoint names accumulate text and entity events before final trimming, with regression coverage and clarifying comments.
S3 XML parser normalization
src-tauri/src/providers/s3.rs
Listing, tagging, search, version, and batch-delete parsers accumulate text and entity fragments before finalizing fields and pagination markers.
WebDAV property parsing
src-tauri/src/providers/webdav.rs
PROPFIND and trashbin parsing preserve meaningful text fragments while trimming decoded paths, metadata, and extracted properties.

Estimated code review effort: 4 (Complex) | ~45 minutes

Possibly related PRs

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and accurately summarizes the main change: fixing provider XML parsing so trim_text no longer corrupts names around entities.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/xml-trimtext-listing-names

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

axpnet added a commit that referenced this pull request Jul 30, 2026

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src-tauri/src/providers/s3.rs (1)

1174-1189: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

One root cause: the "skip whitespace-only fragment" filter is unconditional, so it still deletes name payload. A whitespace-only Text event is indentation only when no name-bearing element is open; between two entity refs (a& &b) or adjacent to one at an element edge (" &x") it is part of the name. jottacloud.rs gets this right — it accumulates every fragment while in_name and trims once at the end tag; the other readers should adopt the same shape (or gate the skip on the current element).

  • src-tauri/src/providers/s3.rs#L1174-L1189: skip only when current_tag/context is not Contents/Key or CommonPrefixes/Prefix.
  • src-tauri/src/providers/s3.rs#L2330-L2341: skip only when neither inside_key nor inside_next_token.
  • src-tauri/src/providers/s3.rs#L3764-L3786: skip only when not in_contents && find_tag == "Key" and not in_next_tok.
  • src-tauri/src/providers/s3.rs#L4218-L4223: skip only when not accumulating Key inside <Version> (and not in a marker element).
  • src-tauri/src/providers/s3.rs#L4912-L4928: skip only when neither in_key nor in_value.
  • src-tauri/src/providers/s3.rs#L5303-L5333: skip only when current_tag != "Key".
  • src-tauri/src/providers/s3.rs#L5475-L5480: skip only when not accumulating Key inside <Version>/<DeleteMarker>.
  • src-tauri/src/providers/s3.rs#L5612-L5658: extend the regression tests with a whitespace-only interior fragment (a&amp; &amp;b.txt) and a leading space before an entity, which the current filter still corrupts.
  • src-tauri/src/providers/azure.rs#L519-L524: skip only when state is not BlobName/BlobPrefixName.
  • src-tauri/src/providers/azure.rs#L2281-L2286: skip only when not in_blob && tag_name == "Name".
  • src-tauri/src/providers/webdav.rs#L1506-L1519: skip only when the tag is not href/trashbin-filename/trashbin-original-location.
  • src-tauri/src/providers/webdav.rs#L1899-L1917: skip only when the tag is not href/displayname.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src-tauri/src/providers/s3.rs` around lines 1174 - 1189, The whitespace-only
Text-fragment filter must preserve name payload while still ignoring
indentation: in src-tauri/src/providers/s3.rs#L1174-L1189 gate skipping on
current_tag/context outside Contents/Key and CommonPrefixes/Prefix; at
`#L2330-L2341` gate on neither inside_key nor inside_next_token; at `#L3764-L3786`
gate on neither the Key-in-Contents nor next-token accumulation; at `#L4218-L4223`
gate on neither Version Key accumulation nor marker elements; at `#L4912-L4928`
gate on neither in_key nor in_value; at `#L5303-L5333` gate on current_tag not
being Key; and at `#L5475-L5480` gate on neither Version/DeleteMarker Key
accumulation. Extend the regression tests at
src-tauri/src/providers/s3.rs#L5612-L5658 for whitespace-only interior fragments
and leading whitespace before an entity. Apply equivalent state/tag-aware gating
in src-tauri/src/providers/azure.rs#L519-L524 (state outside
BlobName/BlobPrefixName) and `#L2281-L2286` (not in_blob with tag_name Name), and
in src-tauri/src/providers/webdav.rs#L1506-L1519 (tags outside
href/trashbin-filename/trashbin-original-location) and `#L1899-L1917` (tags
outside href/displayname), preserving all fragments while the relevant name
element is open and trimming only at the completed value.
🧹 Nitpick comments (1)
src-tauri/src/providers/s3.rs (1)

1362-1397: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

extract_xml_tag now slurps nested element text and never resets acc on a non-matching close.

While inside_target is set, every descendant Text is appended, so extract_xml_tag(body, "Error") on <Error><Code>X</Code><Message>Y</Message></Error> returns XY rather than nothing. Current call sites only pass leaf tags (UploadId, ETag, Code, Message), so this is latent rather than live — worth a depth guard or restricting accumulation to the immediate child text if the helper is reused.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src-tauri/src/providers/s3.rs` around lines 1362 - 1397, The extract_xml_tag
helper incorrectly accumulates descendant text for matching parent elements and
leaves the accumulator active across nested closes. Update extract_xml_tag to
track nesting depth or otherwise collect text only from the target element’s
immediate content, while preserving entity handling and returning trimmed
leaf-tag text for existing callers.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Outside diff comments:
In `@src-tauri/src/providers/s3.rs`:
- Around line 1174-1189: The whitespace-only Text-fragment filter must preserve
name payload while still ignoring indentation: in
src-tauri/src/providers/s3.rs#L1174-L1189 gate skipping on current_tag/context
outside Contents/Key and CommonPrefixes/Prefix; at `#L2330-L2341` gate on neither
inside_key nor inside_next_token; at `#L3764-L3786` gate on neither the
Key-in-Contents nor next-token accumulation; at `#L4218-L4223` gate on neither
Version Key accumulation nor marker elements; at `#L4912-L4928` gate on neither
in_key nor in_value; at `#L5303-L5333` gate on current_tag not being Key; and at
`#L5475-L5480` gate on neither Version/DeleteMarker Key accumulation. Extend the
regression tests at src-tauri/src/providers/s3.rs#L5612-L5658 for
whitespace-only interior fragments and leading whitespace before an entity.
Apply equivalent state/tag-aware gating in
src-tauri/src/providers/azure.rs#L519-L524 (state outside
BlobName/BlobPrefixName) and `#L2281-L2286` (not in_blob with tag_name Name), and
in src-tauri/src/providers/webdav.rs#L1506-L1519 (tags outside
href/trashbin-filename/trashbin-original-location) and `#L1899-L1917` (tags
outside href/displayname), preserving all fragments while the relevant name
element is open and trimming only at the completed value.

---

Nitpick comments:
In `@src-tauri/src/providers/s3.rs`:
- Around line 1362-1397: The extract_xml_tag helper incorrectly accumulates
descendant text for matching parent elements and leaves the accumulator active
across nested closes. Update extract_xml_tag to track nesting depth or otherwise
collect text only from the target element’s immediate content, while preserving
entity handling and returning trimmed leaf-tag text for existing callers.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 300db377-cdef-4d7d-915a-0bdcb004f467

📥 Commits

Reviewing files that changed from the base of the PR and between af109a0 and 403d565.

📒 Files selected for processing (4)
  • src-tauri/src/providers/azure.rs
  • src-tauri/src/providers/jottacloud.rs
  • src-tauri/src/providers/s3.rs
  • src-tauri/src/providers/webdav.rs

…name elements

CodeRabbit follow-up on the trim_text fix: the skip-whitespace-only-fragment
filter was unconditional, so a Text(" ") fragment between two entity refs
(a&amp; &amp;b.txt) or at an element edge ( &amp;x.txt) was still dropped,
rebuilding the key with a hole. Gate the skip at all 11 listing-reader sites
so the fragment is preserved whenever a payload-bearing element is open and
skipped only when it is pure pretty-print indentation.

Also add a nesting-depth guard to extract_xml_tag so a container tag no
longer concatenates descendant text; leaf-tag callers are unchanged.

Regression tests: a&amp; &amp;b.txt -> a& &b.txt and  &amp;x.txt ->  &x.txt
across the s3, azure and webdav parsers, with pretty-print indentation
still ignored.

Agent: Kimi L2
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.

1 participant