summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorCraig Hesling <craig@hesling.com>2025-09-02 02:27:34 -0700
committerPercy Wegmann <ox.to.a.cart@gmail.com>2025-09-03 14:28:22 -0500
commit2b9d055101a0a2731af9ef5d2caf513bfb7da75e (patch)
tree35a429a8ee50531f7893907e4264bc1164eb5b2c
parent0f3598b46741cbd0c005dc7d95c6e24fc8cf1924 (diff)
downloadtailscale-2b9d055101a0a2731af9ef5d2caf513bfb7da75e.tar.xz
tailscale-2b9d055101a0a2731af9ef5d2caf513bfb7da75e.zip
drive: fix StatCache mishandling of paths with spaces
Fix "file not found" errors when WebDAV clients access files/dirs inside directories with spaces. The issue occurred because StatCache was mixing URL-escaped and unescaped paths, causing cache key mismatches. Specifically, StatCache.set() parsed WebDAV responses containing URL-escaped paths (ex. "Dir%20Space/file1.txt") and stored them alongside unescaped cache keys (ex. "Dir Space/file1.txt"). This mismatch prevented StatCache.get() from correctly determining whether a child file existed. See https://github.com/tailscale/tailscale/issues/13632#issuecomment-3243522449 for the full explanation of the issue. The decision to keep all paths references unescaped inside the StatCache is consistent with net/http.Request.URL.Path and rewrite.go (sole consumer) Update unit test to detect this directory space mishandling. Fixes tailscale#13632 Signed-off-by: Craig Hesling <craig@hesling.com>
-rw-r--r--drive/driveimpl/compositedav/stat_cache.go8
-rw-r--r--drive/driveimpl/compositedav/stat_cache_test.go8
2 files changed, 11 insertions, 5 deletions
diff --git a/drive/driveimpl/compositedav/stat_cache.go b/drive/driveimpl/compositedav/stat_cache.go
index fc57ff064..36463fe7e 100644
--- a/drive/driveimpl/compositedav/stat_cache.go
+++ b/drive/driveimpl/compositedav/stat_cache.go
@@ -8,6 +8,7 @@ import (
"encoding/xml"
"log"
"net/http"
+ "net/url"
"sync"
"time"
@@ -165,7 +166,12 @@ func (c *StatCache) set(name string, depth int, ce *cacheEntry) {
children = make(map[string]*cacheEntry, len(ms.Responses)-1)
for i := 0; i < len(ms.Responses); i++ {
response := ms.Responses[i]
- name := shared.Normalize(response.Href)
+ name, err := url.PathUnescape(response.Href)
+ if err != nil {
+ log.Printf("statcache.set child parse error: %s", err)
+ return
+ }
+ name = shared.Normalize(name)
raw := marshalMultiStatus(response)
entry := newCacheEntry(ce.Status, raw)
if i == 0 {
diff --git a/drive/driveimpl/compositedav/stat_cache_test.go b/drive/driveimpl/compositedav/stat_cache_test.go
index fa63457a2..baa4fdda2 100644
--- a/drive/driveimpl/compositedav/stat_cache_test.go
+++ b/drive/driveimpl/compositedav/stat_cache_test.go
@@ -16,12 +16,12 @@ import (
"tailscale.com/tstest"
)
-var parentPath = "/parent"
+var parentPath = "/parent with spaces"
-var childPath = "/parent/child.txt"
+var childPath = "/parent with spaces/child.txt"
var parentResponse = `<D:response>
-<D:href>/parent/</D:href>
+<D:href>/parent%20with%20spaces/</D:href>
<D:propstat>
<D:prop>
<D:getlastmodified>Mon, 29 Apr 2024 19:52:23 GMT</D:getlastmodified>
@@ -36,7 +36,7 @@ var parentResponse = `<D:response>
var childResponse = `
<D:response>
-<D:href>/parent/child.txt</D:href>
+<D:href>/parent%20with%20spaces/child.txt</D:href>
<D:propstat>
<D:prop>
<D:getlastmodified>Mon, 29 Apr 2024 19:52:23 GMT</D:getlastmodified>