From 3c4f5375ed3441e038d20fb266b06583c560edf5 Mon Sep 17 00:00:00 2001 From: mkelvers Date: Fri, 26 Jun 2026 23:56:29 +0200 Subject: [PATCH] test: add proxy target rejection tests --- .../playback/handler/proxy_target_test.go | 78 +++++++++++++++++++ internal/playback/progress_service_test.go | 27 +++++++ 2 files changed, 105 insertions(+) create mode 100644 internal/playback/handler/proxy_target_test.go diff --git a/internal/playback/handler/proxy_target_test.go b/internal/playback/handler/proxy_target_test.go new file mode 100644 index 00000000..679dcf99 --- /dev/null +++ b/internal/playback/handler/proxy_target_test.go @@ -0,0 +1,78 @@ +package handler + +import ( + "context" + "io" + "mal/internal/domain" + "net/http" + "net/http/httptest" + "strings" + "testing" + + "github.com/gin-gonic/gin" +) + +type proxyTargetPlaybackService struct { + targetURL string + referer string +} + +func (s proxyTargetPlaybackService) BuildWatchData(context.Context, int, []string, string, string, string) (domain.WatchPageData, error) { + return domain.WatchPageData{}, nil +} + +func (s proxyTargetPlaybackService) SaveProgress(context.Context, string, int64, int, float64) error { + return nil +} + +func (s proxyTargetPlaybackService) CompleteAnime(context.Context, string, int64) error { + return nil +} + +func (s proxyTargetPlaybackService) SignProxyToken(string, string, string) (string, error) { + return "token", nil +} + +func (s proxyTargetPlaybackService) ResolveProxyToken(string, string) (string, string, error) { + return s.targetURL, s.referer, nil +} + +func (s proxyTargetPlaybackService) UpsertSkipSegmentOverride(context.Context, string, int64, int, string, float64, float64) error { + return nil +} + +type recordingRoundTripper struct { + called bool +} + +func (rt *recordingRoundTripper) RoundTrip(*http.Request) (*http.Response, error) { + rt.called = true + return &http.Response{ + StatusCode: http.StatusOK, + Header: make(http.Header), + Body: io.NopCloser(strings.NewReader("WEBVTT\n")), + }, nil +} + +func TestHandleProxySubtitleRejectsUnsafeTargetBeforeFetch(t *testing.T) { + rt := &recordingRoundTripper{} + h := &PlaybackHandler{ + svc: proxyTargetPlaybackService{targetURL: "http://127.0.0.1/subtitle.vtt"}, + proxyClient: &http.Client{Transport: rt}, + subtitleCache: newSubtitleCache(0, 1), + } + + req := httptest.NewRequestWithContext(context.Background(), http.MethodGet, "/watch/proxy/subtitle?token=token", nil) + rec := httptest.NewRecorder() + gin.SetMode(gin.TestMode) + router := gin.New() + router.GET("/watch/proxy/subtitle", h.HandleProxySubtitle) + router.ServeHTTP(rec, req) + + if rec.Code != http.StatusBadGateway { + t.Fatalf("status = %d, want %d", rec.Code, http.StatusBadGateway) + } + if rt.called { + t.Fatal("proxy client was called for unsafe target") + } +} diff --git a/internal/playback/progress_service_test.go b/internal/playback/progress_service_test.go index 99ba4c72..635cc444 100644 --- a/internal/playback/progress_service_test.go +++ b/internal/playback/progress_service_test.go @@ -189,6 +189,33 @@ func TestPlaybackServiceProxyTokenDisabled(t *testing.T) { } } +func TestPlaybackServiceSignProxyTokenRejectsUnsafeTargets(t *testing.T) { + svc := &playbackService{proxyTokenKey: "secret", proxyTokens: newProxyTokenStore()} + + tests := []struct { + name string + targetURL string + }{ + {name: "loopback ipv4", targetURL: "http://127.0.0.1/video.m3u8"}, + {name: "private ipv4", targetURL: "https://10.0.0.4/segment.ts"}, + {name: "loopback ipv6", targetURL: "http://[::1]/subtitle.vtt"}, + {name: "unsupported scheme", targetURL: "file:///tmp/video.m3u8"}, + {name: "missing host", targetURL: "https:///segment.ts"}, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + token, err := svc.SignProxyToken(tt.targetURL, "", "stream") + if err == nil { + t.Fatalf("SignProxyToken(%q) error = nil, token = %q", tt.targetURL, token) + } + if token != "" { + t.Fatalf("SignProxyToken(%q) token = %q, want empty", tt.targetURL, token) + } + }) + } +} + type fakePlaybackRepository struct { inTxCalled bool getAnimeErr error