fix(ytmusic): reject stale cookie cache writes
This commit is contained in:
Vendored
+11
-2
@@ -36,6 +36,7 @@ type cookieBase struct {
|
|||||||
mu sync.Mutex
|
mu sync.Mutex
|
||||||
playlists []playlist.PlaylistInfo
|
playlists []playlist.PlaylistInfo
|
||||||
trackCache map[string][]playlist.Track
|
trackCache map[string][]playlist.Track
|
||||||
|
generation uint64
|
||||||
nextLoad uint64
|
nextLoad uint64
|
||||||
loadCancels map[uint64]context.CancelFunc
|
loadCancels map[uint64]context.CancelFunc
|
||||||
}
|
}
|
||||||
@@ -61,6 +62,7 @@ func (b *cookieBase) fetchPlaylists() ([]playlist.PlaylistInfo, error) {
|
|||||||
b.mu.Unlock()
|
b.mu.Unlock()
|
||||||
return res, nil
|
return res, nil
|
||||||
}
|
}
|
||||||
|
generation := b.generation
|
||||||
|
|
||||||
b.mu.Unlock()
|
b.mu.Unlock()
|
||||||
|
|
||||||
@@ -77,7 +79,9 @@ func (b *cookieBase) fetchPlaylists() ([]playlist.PlaylistInfo, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
b.mu.Lock()
|
b.mu.Lock()
|
||||||
b.playlists = pls
|
if b.generation == generation {
|
||||||
|
b.playlists = pls
|
||||||
|
}
|
||||||
b.mu.Unlock()
|
b.mu.Unlock()
|
||||||
return pls, nil
|
return pls, nil
|
||||||
}
|
}
|
||||||
@@ -88,6 +92,7 @@ func (b *cookieBase) fetchTracks(target string) ([]playlist.Track, error) {
|
|||||||
b.mu.Unlock()
|
b.mu.Unlock()
|
||||||
return cached, nil
|
return cached, nil
|
||||||
}
|
}
|
||||||
|
generation := b.generation
|
||||||
|
|
||||||
ctx, cancel := context.WithTimeout(context.Background(), cookiePlaylistLoadTimeout)
|
ctx, cancel := context.WithTimeout(context.Background(), cookiePlaylistLoadTimeout)
|
||||||
b.nextLoad++
|
b.nextLoad++
|
||||||
@@ -122,13 +127,16 @@ func (b *cookieBase) fetchTracks(target string) ([]playlist.Track, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
b.mu.Lock()
|
b.mu.Lock()
|
||||||
b.trackCache[target] = tracks
|
if b.generation == generation {
|
||||||
|
b.trackCache[target] = tracks
|
||||||
|
}
|
||||||
b.mu.Unlock()
|
b.mu.Unlock()
|
||||||
return tracks, nil
|
return tracks, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *cookieBase) refresh() {
|
func (b *cookieBase) refresh() {
|
||||||
b.mu.Lock()
|
b.mu.Lock()
|
||||||
|
b.generation++
|
||||||
for _, cancel := range b.loadCancels {
|
for _, cancel := range b.loadCancels {
|
||||||
cancel()
|
cancel()
|
||||||
}
|
}
|
||||||
@@ -140,6 +148,7 @@ func (b *cookieBase) refresh() {
|
|||||||
|
|
||||||
func (b *cookieBase) close() {
|
func (b *cookieBase) close() {
|
||||||
b.mu.Lock()
|
b.mu.Lock()
|
||||||
|
b.generation++
|
||||||
for _, cancel := range b.loadCancels {
|
for _, cancel := range b.loadCancels {
|
||||||
cancel()
|
cancel()
|
||||||
}
|
}
|
||||||
|
|||||||
+54
@@ -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) {
|
func TestCookieProviderSearchTracksHonorsCancellation(t *testing.T) {
|
||||||
if runtime.GOOS == "windows" {
|
if runtime.GOOS == "windows" {
|
||||||
t.Skip("skipping Unix shell script test on Windows")
|
t.Skip("skipping Unix shell script test on Windows")
|
||||||
|
|||||||
Reference in New Issue
Block a user