* webdav: overwrite on MOVE when the Overwrite header is missing
* webdav: defer a MOVE destination's delete into the rename
x/net removes an existing destination before Rename, so a refused or
failed move — a CanRename rejection, a vanished parent, a transient
filer error — loses the target entirely. For an overwriting MOVE onto
an existing destination, run the request against a filesystem that
skips the destination RemoveAll and lets the filer's rename replace the
target inside one transaction instead; a file vs. collection clash
still takes the delete + rename path. Rename itself now tolerates an
existing target, which the atomic filer rename already handles.
* webdav: only default Overwrite when the header has no values
An Overwrite header sent with an empty first value makes Header.Get
return "", which the defaulting treated as absent and rewrote to T —
overriding a client's explicit F in a later value. Check the value list
instead so a present-but-empty header is left alone.
* webdav: move a collection onto a non-empty one via delete + rename
deferredDeleteFS skips the destination RemoveAll so the filer rename can
replace the target in one transaction, but the filer only replaces an
empty collection: moveSelfEntry deletes the target non-recursively and
refuses ErrNonEmptyFolder. Detect a non-empty collection destination and
take the depth-infinity delete + rename path moveFiles would have run.
* webdav: park a clashing MOVE target aside until the rename lands
A refused rename (cross-collection move, bucket target) after deleting
the destination lost its contents; the target now renames to a hidden
sibling first and is restored when the winning rename fails.
Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
* webdav: restore a parked MOVE target under a detached context
A client disconnect cancels the request context mid-move, which also
canceled the restore and stranded the destination under its hidden
sibling name; the restore and parked-entry removal now run under
context.WithoutCancel with a bounded deadline.
Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
* webdav: bound the parked MOVE target's restore after the move
The detached restore deadline started before the source rename, so a
long move could exhaust it; each detached context now starts where its
work begins. A failed parked-entry removal retries briefly and its
warning names the parked sibling for recovery.
Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
* webdav: restore a parked MOVE target without replacing a new claimant
A client can create the destination name while the old target is parked, so the restore rename must fail instead of replacing that new entry.
* webdav: propagate RemoveAll's context to the filer delete
The parked-target cleanup runs under a bounded detached context, but the delete RPC discarded it; passing ctx through makes the deadline effective.
---------
Co-authored-by: user.email <chrislusf@users.noreply.github.com>
Co-authored-by: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
* webdav: refuse MKCOL and PUT under a missing folder with 409
RFC 4918 9.3 and 9.7.1 say a MKCOL or PUT whose parent collection does
not exist must fail with 409 Conflict. The filer creates missing parents,
so both succeeded and made the intermediate folders. Check the parent in
Mkdir and in OpenFile with O_CREATE and return os.ErrNotExist, which
x/net/webdav turns into 409.
* webdav: check the PUT parent before an overwrite deletes the target
OpenFile deleted an existing destination before the new parent lookup,
so a PUT whose parent check failed left the old file gone.
Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
* webdav: report a missing rename source as not-exist
Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
* webdav: refuse MOVE to a missing destination folder
AtomicRenameEntry grows any missing parents itself, so a MOVE to
/missing/file created /missing; check the destination parent first.
Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
* webdav: answer 409 for COPY and MOVE under a missing collection
The webdav handler reports a failed directory Mkdir or Rename as 403,
so the destination parent is checked at the request layer instead.
Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
* webdav: defer foreign-host MOVE and COPY destinations to the 502 handler
* webdav: only check the destination parent for absolute paths
* webdav: cover foreign-host and relative MOVE destinations in the guard test
---------
Co-authored-by: user.email <chrislusf@users.noreply.github.com>
Co-authored-by: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
* webdav: copy a file onto a collection URL without losing the collection
An overwriting COPY of a file to dir/ removed dir, then OpenFile refused
the name ending in / and the request failed with 403, leaving neither.
Drop the trailing slash from the destination when the source is a file.
* webdav: cover file-over-collection COPY through the real handler
Generated with [Devin](https://devin.ai)
Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
* webdav: keep a root destination out of the slash trim
A file COPY or MOVE onto / would rewrite the Destination to an empty
path, turning the existing refusal into x/net's bad-gateway error; leave
the root untouched so RemoveAll refuses it as before.
---------
Co-authored-by: user.email <chrislusf@users.noreply.github.com>
Co-authored-by: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
* webdav: delete a folder with its contents
* webdav: guard the recursive delete against stale and confined paths
- propagate stat lookup errors instead of returning an error-bearing
FileInfo: OpenFile and DELETE would otherwise take the recursive
removeAll on a path whose type was never confirmed, wiping a
directory on a transient filer error
- refuse wrappedFs.Rename from or to the served root, matching the
RemoveAll guard and webdav.Dir
- normalize trailing slashes in removeAll so the delete names the
entry itself, and refuse the root for every caller
* webdav: keep file-only deletes shallow and refuse ancestor destinations
- removeAll used by OpenFile and failed-upload cleanup now deletes
non-recursively, so a folder swapped in after the stat check is not
emptied by a pending file delete
- MOVE and COPY whose destination contains the source are refused
before the handler removes the destination, which would otherwise
take the source down with it
* webdav: also refuse a destination inside the source
a MOVE or COPY into its own subtree recurses into the destination it
just created; refuse it with the same guard instead of burning the
recursion budget for a 500
* webdav: allow a shallow copy into the source subtree
a COPY with Depth: 0 creates only the destination collection and never
reads children, so it is safe inside the source; only deep copies and
moves are refused there
* webdav: refuse a copy/move whose source is unreadable
* webdav: cover the missing-source refusal in the guard test
* webdav: 500 on a source lookup failure, 404 only when absent
---------
Co-authored-by: Chris Lu <chris.lu@gmail.com>
Co-authored-by: Chris Lu <chrislusf@users.noreply.github.com>
* webdav: create a folder under its own name
* webdav: reuse NormalizePath for the MKCOL entry split
---------
Co-authored-by: Chris Lu <chris.lu@gmail.com>
filer: clean up manifest resolve error propagation and add webdav test (#78)
Drop GitHub issue references from comments and trim verbose comments.
Replace the viewFromChunksOrErr helper with the existing
NonOverlappingVisibleIntervals + ViewFromVisibleIntervals at the stream
call sites, and add a WebDavFile.Read regression test for the manifest
resolution failure path.
Co-authored-by: Chris Lu <chrislusf@users.noreply.github.com>
* webdav: name the entry, not its path, in a listing
DAV:displayname carried the full path of every entry. A client that
takes displayname for the child's name - Windows Explorer does - then
looks for /dir/name under /dir and finds nothing, so a folder shows up
empty while the root, where the two spellings differ only by a leading
slash, still lists.
Readdir now builds its entries with toFileInfo like stat does, so a
listing and a lookup describe a child the same way, and the wrapper that
was trimming the sub-folder back off a name goes away with it.
Claude-Session: https://claude.ai/code/session_01XCeuCWpF9xo9CfyHvCQE9c
* webdav: derive an ETag when nothing hashed the entry
Uploads through this gateway carry no content MD5, so filer.ETag comes
back empty and every file in a PROPFIND answered with an empty
DAV:getetag, which is not a valid entity-tag. Report it as unimplemented
instead, the way the sub-folder wrapper already did, and webdav falls
back to modification time and size. The wrapper's copy went with it - it
swallowed the stat error a caller was meant to see.
Claude-Session: https://claude.ai/code/session_01XCeuCWpF9xo9CfyHvCQE9c