fix(nc/webdav): trash restore refuses MOVE onto a live destination

When the client sends `MOVE /trashbin/{id}` with a `Destination` header,
handle_restore now resolves the destination path and returns 412
Precondition Failed if a live file or folder already sits there —
matching Sabre/DAV and the NC desktop client's expectation. There is
no `Overwrite: T` workflow for trash restore in either reference
implementation (silently replacing a live file with an undeleted one
is a footgun), so the refusal is unconditional.

The destination header is extracted at the dispatch site as an owned
String so the future stays Send-compatible (`&Request<Body>` is not
Sync because the body trait object is Send-only).

`extract_nc_subpath_from_dest` is promoted to `pub` so trashbin_handler
can share the same URL parser as handle_move.
This commit is contained in:
Edouard Vanbelle
2026-06-17 01:44:24 +02:00
parent f9de7ac596
commit f62cf0b65f
2 changed files with 42 additions and 4 deletions
+4 -2
View File
@@ -737,7 +737,9 @@ async fn handle_mkcol(
let segments: Vec<&str> = subpath.split('/').filter(|s| !s.is_empty()).collect();
if segments.is_empty() {
return Err(AppError::bad_request("MKCOL on the user root is not allowed"));
return Err(AppError::bad_request(
"MKCOL on the user root is not allowed",
));
}
let (target_name, parent_segments) = segments.split_last().expect("checked non-empty above");
@@ -1061,7 +1063,7 @@ async fn handle_move(
/// Only accepts relative paths or absolute URLs whose path starts with the
/// expected DAV prefix. For full URLs the host is ignored — the path alone is
/// used — so an attacker cannot redirect the server to a different host.
fn extract_nc_subpath_from_dest(dest: &str, username: &str) -> Option<String> {
pub fn extract_nc_subpath_from_dest(dest: &str, username: &str) -> Option<String> {
let prefix = format!("/remote.php/dav/files/{}/", username);
// For full URLs, extract the path portion (everything after the authority).
let path = if dest.starts_with("http://") || dest.starts_with("https://") {