fix(propfind): don't let PROPFIND parsing/rebuilding failures break the whole file listing - #1992
fix(propfind): don't let PROPFIND parsing/rebuilding failures break the whole file listing#1992oboeglen wants to merge 2 commits into
Conversation
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>
|
Hello there, 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.) |
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/catchso 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
XMLBuilderoptions in sync with the parser'sI 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 - meaningparseXML()itself was succeeding, and something we rebuild afterwards was the problem, not the original response.Root cause:
parseXML()(from thewebdavpackage) parses every response withtextNodeName: 'text'andattributeNamePrefix: '@'- its own defaults (seegetParser()insource/tools/dav.ts). The response is then rebuilt with a barenew XMLBuilder(), which instead falls back tofast-xml-parser's own defaults:textNodeName: '#text',attributeNamePrefix: '@_'andignoreAttributes: true.fast-xml-parseronly wraps a node's text content in an object undertextNodeName- 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 withInvalid tag name: text.Fix: a
XML_BUILDER_OPTIONSconstant mirroring the parser's options, used for theXMLBuildercall.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 inlinexmlns: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/testall 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 theXMLBuildermismatch - neither commit claims to be a full explanation of everything reported in #1991, just concrete bugs found in this middleware along the way.