Let a Working Folder delete a basename.

CheckDNS needs that to Remove Domain without a second storage path.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-09-28 19:31:36 +02:00
co-authored by Cursor
parent 6560dffa69
commit 300484114f
4 changed files with 65 additions and 2 deletions
+2 -2
View File
@@ -9,7 +9,7 @@ The root of that Nextcloud user's files tree. Working Folder paths are relative
_Avoid_: absolute WebDAV URLs, numeric file IDs
**Working Folder**:
The single folder an ExApp uses for the current operation. Its path is relative to the User Files Root. Each ExApp defines what files inside that folder mean; this Library only opens, lists, reads, and writes there.
The single folder an ExApp uses for the current operation. Its path is relative to the User Files Root. Each ExApp defines what files inside that folder mean; this Library only opens, lists, reads, writes, and removes there.
_Avoid_: catalog folder, zones directory, app-specific suffixes in Library code
**Saved Default**:
@@ -21,5 +21,5 @@ One inbound ExApp request bound to one Working Folder. App-icon launch uses the
_Avoid_: silently switching folders, creating a visit path that was not saved as default
**Files seam**:
The `Root` and `Folder` interfaces: open or ensure a relative path, then list, read, write, and test existence. Local disk and WebDAV are two implementations of the same seam.
The `Root` and `Folder` interfaces: open or ensure a relative path, then list, read, write, remove, and test existence. Local disk and WebDAV are two implementations of the same seam.
_Avoid_: coupling the seam to a specific on-disk layout beyond basename rules
+14
View File
@@ -32,6 +32,8 @@ type Folder interface {
Write(name string, data []byte) error
// Exists reports whether name is a non-directory file in the Working Folder.
Exists(name string) (bool, error)
// Remove deletes a basename in the Working Folder.
Remove(name string) error
}
// Root is User Files Root–relative folder access.
@@ -181,6 +183,18 @@ func (f localFolder) Exists(name string) (bool, error) {
return !info.IsDir(), nil
}
// Remove implements Folder for a local directory.
func (f localFolder) Remove(name string) error {
if err := RequireBasename(name); err != nil {
return err
}
err := os.Remove(filepath.Join(f.dir, name))
if err != nil && os.IsNotExist(err) {
return fmt.Errorf("%w: %s", ErrNotExist, name)
}
return err
}
// RequireBasename rejects path segments that are not a single basename.
func RequireBasename(name string) error {
if name == "" || name != path.Base(name) || strings.ContainsAny(name, `/\`) || strings.Contains(name, "..") {
+35
View File
@@ -335,6 +335,41 @@ func (f webdavFolder) Write(name string, data []byte) error {
return fmt.Errorf("webdav PUT %s: %s", name, res.Status)
}
// Remove implements Folder via WebDAV DELETE.
func (f webdavFolder) Remove(name string) error {
u, err := f.childURL(name)
if err != nil {
return err
}
req, err := http.NewRequest(http.MethodDelete, u, nil)
if err != nil {
return err
}
for k, vs := range f.root.Cred.AuthHeaders() {
for _, v := range vs {
req.Header.Set(k, v)
}
}
res, err := f.root.client().Do(req)
if err != nil {
return err
}
defer res.Body.Close()
if _, err := io.Copy(io.Discard, res.Body); err != nil {
return err
}
switch res.StatusCode {
case http.StatusOK, http.StatusNoContent, http.StatusAccepted:
return nil
case http.StatusNotFound:
return fmt.Errorf("%w: %s", ErrNotExist, name)
case http.StatusForbidden, http.StatusUnauthorized:
return fmt.Errorf("%w", ErrForbidden)
default:
return fmt.Errorf("webdav DELETE %s: %s", name, res.Status)
}
}
// Exists implements Folder via WebDAV HEAD (falls back to GET when needed).
func (f webdavFolder) Exists(name string) (bool, error) {
u, err := f.childURL(name)
+14
View File
@@ -56,6 +56,13 @@ func TestWebDAVReadWriteListExists(t *testing.T) {
return
}
w.WriteHeader(http.StatusOK)
case http.MethodDelete:
if _, ok := store[path]; !ok {
http.NotFound(w, r)
return
}
delete(store, path)
w.WriteHeader(http.StatusNoContent)
case "MKCOL":
dirs[strings.TrimSuffix(path, "/")] = true
w.WriteHeader(http.StatusCreated)
@@ -102,6 +109,13 @@ func TestWebDAVReadWriteListExists(t *testing.T) {
if !found {
t.Fatalf("list: %+v", entries)
}
if err := folder.Remove("note.txt"); err != nil {
t.Fatal(err)
}
ok, err = folder.Exists("note.txt")
if err != nil || ok {
t.Fatalf("after remove: ok=%v err=%v", ok, err)
}
}
func TestWebDAVOpenMissingIsNotExist(t *testing.T) {