fix(ytmusic): reject stale cookie cache writes

This commit is contained in:
Bjarne Øverli
2026-08-20 19:47:01 +02:00
parent f3a7d643f3
commit 9edbcc2107
2 changed files with 65 additions and 2 deletions
+11 -2
View File
@@ -36,6 +36,7 @@ type cookieBase struct {
mu sync.Mutex
playlists []playlist.PlaylistInfo
trackCache map[string][]playlist.Track
generation uint64
nextLoad uint64
loadCancels map[uint64]context.CancelFunc
}
@@ -61,6 +62,7 @@ func (b *cookieBase) fetchPlaylists() ([]playlist.PlaylistInfo, error) {
b.mu.Unlock()
return res, nil
}
generation := b.generation
b.mu.Unlock()
@@ -77,7 +79,9 @@ func (b *cookieBase) fetchPlaylists() ([]playlist.PlaylistInfo, error) {
}
b.mu.Lock()
b.playlists = pls
if b.generation == generation {
b.playlists = pls
}
b.mu.Unlock()
return pls, nil
}
@@ -88,6 +92,7 @@ func (b *cookieBase) fetchTracks(target string) ([]playlist.Track, error) {
b.mu.Unlock()
return cached, nil
}
generation := b.generation
ctx, cancel := context.WithTimeout(context.Background(), cookiePlaylistLoadTimeout)
b.nextLoad++
@@ -122,13 +127,16 @@ func (b *cookieBase) fetchTracks(target string) ([]playlist.Track, error) {
}
b.mu.Lock()
b.trackCache[target] = tracks
if b.generation == generation {
b.trackCache[target] = tracks
}
b.mu.Unlock()
return tracks, nil
}
func (b *cookieBase) refresh() {
b.mu.Lock()
b.generation++
for _, cancel := range b.loadCancels {
cancel()
}
@@ -140,6 +148,7 @@ func (b *cookieBase) refresh() {
func (b *cookieBase) close() {
b.mu.Lock()
b.generation++
for _, cancel := range b.loadCancels {
cancel()
}
+54
View File
@@ -335,6 +335,60 @@ func TestCookieProviderStopsTrackLoad(t *testing.T) {
}
}
func TestCookieProviderRefreshRejectsStaleResults(t *testing.T) {
t.Run("playlists", func(t *testing.T) {
base := newCookieBase("firefox")
started := make(chan struct{})
release := make(chan struct{})
base.fetchFn = func(string) ([]playlist.PlaylistInfo, error) {
close(started)
<-release
return []playlist.PlaylistInfo{{ID: "private"}}, nil
}
done := make(chan struct{})
go func() {
_, _ = base.fetchPlaylists()
close(done)
}()
<-started
base.refresh()
close(release)
<-done
base.mu.Lock()
defer base.mu.Unlock()
if base.playlists != nil {
t.Fatalf("stale playlists cached after refresh: %v", base.playlists)
}
})
t.Run("tracks", func(t *testing.T) {
base := newCookieBase("firefox")
started := make(chan struct{})
release := make(chan struct{})
base.resolveFn = func(context.Context, string, int, int, ...string) ([]playlist.Track, int, error) {
close(started)
<-release
return []playlist.Track{{Title: "Private"}}, 1, nil
}
done := make(chan struct{})
go func() {
_, _ = base.fetchTracks("private")
close(done)
}()
<-started
base.refresh()
close(release)
<-done
base.mu.Lock()
defer base.mu.Unlock()
if _, ok := base.trackCache["private"]; ok {
t.Fatal("stale tracks cached after refresh")
}
})
}
func TestCookieProviderSearchTracksHonorsCancellation(t *testing.T) {
if runtime.GOOS == "windows" {
t.Skip("skipping Unix shell script test on Windows")