fix(dav): Do not respond version/trashbin download requests with 404 due to ChunkingV2Plugin#61680
fix(dav): Do not respond version/trashbin download requests with 404 due to ChunkingV2Plugin#61680DerDreschner wants to merge 1 commit into
Conversation
|
/backport to stable34 |
|
/backport to stable33 |
|
/backport to stable32 |
|
/backport to stable31 |
|
/backport to stable30 |
|
/backport to stable29 |
|
/backport to stable28 |
|
/backport to stable27 |
|
/backport to stable26 |
c2e19e8 to
22503bd
Compare
22503bd to
7434491
Compare
…due to ChunkingV2Plugin ChunkingV2Plugin::beforeGet eagerly resolves the request path during beforeMethod:GET to block reading intermediate chunked uploads. App-provided DAV collections (versions, trashbin) are attached to the root lazily in a beforeMethod:* closure in Server.php, while uploads is registered eagerly. When beforeGet runs before that closure, getNodeForPath() throws NotFound and aborts the whole request, turning every GET under /dav/versions/ and /dav/trashbin/ into "404 File not found: versions in 'root'". PROPFIND is unaffected, since nothing resolves the path that early for it. This does not currently surface on master only by accident of listener ordering: beforeGet and the collection closure share the default priority, and because beforeGet is registered as a first-class callable (a Closure), the wildcard closure happens to sort before it, so the collections are already attached by the time beforeGet resolves the path. That ordering is not guaranteed -- it depends on how equal-priority listeners are tie-broken -- so the unguarded resolution is a latent fault that any reordering can expose. On stable 28 it is already broken, because the backport registered the handler as an array callable, which tie-breaks the other way and runs beforeGet first. Catch NotFound in beforeGet and bail out: a path that cannot be resolved is by definition not an intermediate upload. This removes the dependency on listener ordering entirely and makes the handler consistent with beforePut()/beforeMove()/beforeDelete(), which already swallow NotFound for the same reason. Add a ChunkingV2Plugin unit test (the NotFound case is the regression guard) and a file-versions integration scenario covering a real version download. Assisted-by: ClaudeCode:claude-opus-4-8[1m] Signed-off-by: David Dreschner <david.dreschner@nextcloud.com>
7434491 to
0136d82
Compare
|
Failing Cypress run is unrelated to this change |
How so? I don't understand. |
It's PHP internal stuff about what wins when an array and a closure have the same priority. This is an easy description of the issue made by Claude, the Why the registration style changes the order
That type rule is deterministic: an array callable is always "less than" a closure. So the interesting case is the realistic one — in NC30 only vs. the all-array (NC29) form, where the same listeners keep their intended registration order: The single closure gets shoved to a different slot purely because object always sorts after array. Since Sabre's sort isn't a proper stable/consistent ordering when the keys tie, the "shape" of the callable silently decides who runs first — and that reorders the GET pipeline, which is what produces the DAV-versions 404. |
|
Here is a demo that Claude made to illustrate the issue: https://onlinephp.io/c/3690dc |
Summary.
This PR fixes an issue in the
ChunkingV2Pluginthat was introduced with #59780 . It's hard to spot, as it's being hidden in Nextcloud 30 and newer by using a different way of registering to thebeforeMethod:GETsabre event.Nextcloud 30 and newer uses the following way:
$server->on('beforeMethod:GET', $this->beforeGet(...));Nextcloud 29 and older uses the following way:
$server->on('beforeMethod:GET', [$this, 'beforeGet']);This slight change has an effect on the order of the plugins being executed. If the
ChunkingV2Pluginis being run too soon - which is the case on Nextcloud 29 and older -, the file versions and trash bin items can't be downloaded due to a 404 being returned. This isn't the case once the PR is merged, as theChunkingV2Pluginhandles that case gracefully now.This is a summary of the situation by AI:
ChunkingV2Plugin::beforeGet eagerly resolves the request path during beforeMethod:GET to block reading intermediate chunked uploads. App-provided DAV collections (versions, trashbin) are attached to the root lazily in a beforeMethod:* closure in Server.php, while uploads is registered eagerly. When beforeGet runs before that closure, getNodeForPath() throws NotFound and aborts the whole request, turning every GET under /dav/versions/ and /dav/trashbin/ into "404 File not found: versions in 'root'". PROPFIND is unaffected, since nothing resolves the path that early for it.
This does not currently surface on master only by accident of listener ordering: beforeGet and the collection closure share the default priority, and because beforeGet is registered as a first-class callable (a Closure), the wildcard closure happens to sort before it, so the collections are already attached by the time beforeGet resolves the path. That ordering is not guaranteed -- it depends on how equal-priority listeners are tie-broken -- so the unguarded resolution is a latent fault that any reordering can expose. On stable 28 it is already broken, because the backport registered the handler as an array callable, which tie-breaks the other way and runs beforeGet first.
Checklist
3. to review, feature component)stable32)AI (if applicable)