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>
This commit is contained in:
Craig Hesling
2025-09-02 02:27:34 -07:00
committed by Percy Wegmann
parent 0f3598b467
commit 2b9d055101
2 changed files with 11 additions and 5 deletions
+7 -1
View File
@@ -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 {