Skip to content

fix(propfind): don't let PROPFIND parsing/rebuilding failures break the whole file listing - #1992

Open
oboeglen wants to merge 2 commits into
nextcloud:mainfrom
oboeglen:fix/propfind-defensive-error-handling
Open

fix(propfind): don't let PROPFIND parsing/rebuilding failures break the whole file listing#1992
oboeglen wants to merge 2 commits into
nextcloud:mainfrom
oboeglen:fix/propfind-defensive-error-handling

Conversation

@oboeglen

@oboeglen oboeglen commented Aug 7, 2026

Copy link
Copy Markdown

Fixes #1991.

This started out as a single defensive fix and grew into two related, but distinct, fixes for the same underlying problem area (the request from a reviewer to actually narrow down the root cause, see below).

Commit 1 - don't let a PROPFIND parsing failure break the whole file listing

usePropFindInterceptor() unconditionally parses every PROPFIND response as XML (parseXML(body)) before it even checks whether the result contains any e2ee node. On an instance where the app is enabled but no folder is actually end-to-end encrypted, this parsing step can throw (Invalid response: No root multistatus found, full repro and console trace in #1991), and that exception was never caught - it propagated all the way up through the middleware chain and broke the entire file listing, for every single folder, not just encrypted ones.

Wraps the interceptor's processing in a try/catch so that whatever goes wrong here, it can no longer take down plain, non-e2ee file browsing - worst case, e2ee placeholders are just left unresolved for that one response and it's logged, instead of Files becoming completely unusable.

Commit 2 - keep XMLBuilder options in sync with the parser's

I wasn't able to pin down the exact root cause of the original issue when I opened this PR, and kept seeing the same class of error (Invalid tag name: text, thrown downstream in Nextcloud's own DAV client, after our interceptor had already returned) on an instance with the fix from commit 1 already deployed - meaning parseXML() itself was succeeding, and something we rebuild afterwards was the problem, not the original response.

Root cause: parseXML() (from the webdav package) parses every response with textNodeName: 'text' and attributeNamePrefix: '@' - its own defaults (see getParser() in source/tools/dav.ts). The response is then rebuilt with a bare new XMLBuilder(), which instead falls back to fast-xml-parser's own defaults: textNodeName: '#text', attributeNamePrefix: '@_' and ignoreAttributes: true.

fast-xml-parser only wraps a node's text content in an object under textNodeName - instead of returning it as a plain string - when that node also has to carry other keys alongside it, e.g. an attribute. With the mismatched options above, the builder doesn't recognise the wrapped text sitting under 'text' as special and serialises it as a literal <text> child element instead (plus an invalid <@attr> tag for the attribute itself), corrupting the rebuilt response. Nextcloud's own DAV client then fails to parse that back with Invalid tag name: text.

Fix: a XML_BUILDER_OPTIONS constant mirroring the parser's options, used for the XMLBuilder call.

Added a test reproducing the corruption - with a synthetic attribute, since I could not find a real Nextcloud DAV property that currently ships a non-namespace attribute on a text-bearing element (the one pre-existing case in our own fixtures, x1:share-permissions's inline xmlns:x1, is filtered out by the parser as a namespace declaration rather than kept as data, so it doesn't trigger this). I want to be upfront about that: I can't point to a specific property that hits this today, only demonstrate that the option mismatch itself is real and that the round trip breaks the moment any node carries an attribute alongside text, regardless of whether something already does. The alignment should hold either way.

All existing tests still pass, plus the two new ones (ts:check/lint/test all green locally).

Happy to adjust either commit if someone can point me at a way to reproduce the exact original parseXML() failure from #1991 locally, or at a real DAV property that hits the XMLBuilder mismatch - neither commit claims to be a full explanation of everything reported in #1991, just concrete bugs found in this middleware along the way.

usePropFindInterceptor() unconditionally parses every single PROPFIND
response as XML before deciding whether it contains any e2ee node,
even on instances where e2ee is enabled but no folder is actually
encrypted. If that parsing fails for any reason, the exception was
never caught, so it propagated all the way up and broke the entire
file listing for every single folder - not just e2ee ones.

Wrap the interceptor body in a try/catch: on any processing failure,
log it and let the original, unmodified response pass through instead
of taking down the request. Worst case e2ee placeholders are left
unresolved for that one response, instead of Files being completely
unusable.

Reported in nextcloud#1991 ("Invalid response: No root multistatus found" on
every PROPFIND, reproducible in a fresh private-browsing session, on
an instance with e2ee enabled but zero encrypted folders in use). The
exact reason parseXML() fails for this specific setup is not fully
pinned down - possibly related to the fast-xml-parser/webdav
dependency bump in 2.2.2 - but regardless of that root cause, a
parsing hiccup in this middleware should never be able to break
plain, non-e2ee file browsing.

Fixes nextcloud#1991
usePropFindInterceptor() parses every PROPFIND response with parseXML()
from the webdav package, which uses textNodeName: 'text' and
attributeNamePrefix: '@' (its own defaults, see getParser() in
source/tools/dav.ts). The response is then rebuilt with a bare
new XMLBuilder(), which instead falls back to fast-xml-parser's own
defaults: textNodeName: '#text', attributeNamePrefix: '@_' and
ignoreAttributes: true.

fast-xml-parser only wraps a node's text content in an object under
textNodeName - instead of returning it as a plain string - when that
node also has to carry other keys alongside it, e.g. an attribute. With
mismatched options, the builder does not recognise the wrapped text
under 'text' as special and serialises it as a literal <text> child
element instead (plus an invalid <@attr> tag for the attribute itself),
corrupting the rebuilt response. Nextcloud's own DAV client then fails
to parse that back with 'Invalid tag name: text' - the try/catch added
in the previous commit does not help here since parseXML() itself
succeeds, it is the later re-parse of our own malformed output,
downstream, that fails.

Add XML_BUILDER_OPTIONS mirroring the parser's options and use it for
the XMLBuilder call. Added a test reproducing the corruption with a
synthetic attribute (no known Nextcloud DAV property currently ships
a non-namespace attribute on a text-bearing element - the one
pre-existing case in our fixtures, x1:share-permissions's inline
xmlns:x1, is filtered out by the parser as a namespace declaration
rather than kept as data - but the option alignment must hold
regardless of whether one exists in the wild today).

Signed-off-by: Olivier <oboeglen@users.noreply.github.com>
@oboeglen oboeglen changed the title fix(propfind): do not let a PROPFIND parsing failure break the whole file listing fix(propfind): don't let PROPFIND parsing/rebuilding failures break the whole file listing Aug 20, 2026
@github-actions

Copy link
Copy Markdown
Contributor

Hello there,
Thank you so much for taking the time and effort to create a pull request to our Nextcloud project.

We hope that the review process is going smooth and is helpful for you. We want to ensure your pull request is reviewed to your satisfaction. If you have a moment, our community management team would very much appreciate your feedback on your experience with this PR review process.

Your feedback is valuable to us as we continuously strive to improve our community developer experience. Please take a moment to complete our short survey by clicking on the following link: https://cloud.nextcloud.com/apps/forms/s/i9Ago4EQRZ7TWxjfmeEpPkf6

Thank you for contributing to Nextcloud and we hope to hear from you soon!

(If you believe you should not receive this message, you can add yourself to the blocklist.)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

PROPFIND interceptor breaks all file listings ("Invalid response: No root multistatus found") when E2EE is enabled but unused (v2.2.2)

1 participant