From 288006c56483b6b85610098ba64946d3ec2612d3 Mon Sep 17 00:00:00 2001 From: Deluan Date: Thu, 5 Feb 2026 17:19:01 -0500 Subject: [PATCH] fix(transcoding): enforce POST method for GetTranscodeDecision and handle non-POST requests Signed-off-by: Deluan --- server/subsonic/transcode.go | 8 +++++- server/subsonic/transcode_test.go | 41 +++++++++++++++++++++++++++---- 2 files changed, 43 insertions(+), 6 deletions(-) diff --git a/server/subsonic/transcode.go b/server/subsonic/transcode.go index 267ca1c36..633adbfb6 100644 --- a/server/subsonic/transcode.go +++ b/server/subsonic/transcode.go @@ -134,7 +134,13 @@ func convertBitrateValues(bpsValues []string) []string { // GetTranscodeDecision handles the OpenSubsonic getTranscodeDecision endpoint. // It receives client capabilities and returns a decision on whether to direct play or transcode. -func (api *Router) GetTranscodeDecision(_ http.ResponseWriter, r *http.Request) (*responses.Subsonic, error) { +func (api *Router) GetTranscodeDecision(w http.ResponseWriter, r *http.Request) (*responses.Subsonic, error) { + if r.Method != http.MethodPost { + w.Header().Set("Allow", "POST") + http.Error(w, "Method Not Allowed", http.StatusMethodNotAllowed) + return nil, nil + } + ctx := r.Context() p := req.Params(r) diff --git a/server/subsonic/transcode_test.go b/server/subsonic/transcode_test.go index 970539d9c..7999709d4 100644 --- a/server/subsonic/transcode_test.go +++ b/server/subsonic/transcode_test.go @@ -31,20 +31,29 @@ var _ = Describe("Transcode endpoints", func() { }) Describe("GetTranscodeDecision", func() { + It("returns 405 for non-POST requests", func() { + r := newGetRequest("mediaId=123", "mediaType=song") + resp, err := router.GetTranscodeDecision(w, r) + Expect(err).ToNot(HaveOccurred()) + Expect(resp).To(BeNil()) + Expect(w.Code).To(Equal(http.StatusMethodNotAllowed)) + Expect(w.Header().Get("Allow")).To(Equal("POST")) + }) + It("returns error when mediaId is missing", func() { - r := newGetRequest("mediaType=song") + r := newJSONPostRequest("mediaType=song", "{}") _, err := router.GetTranscodeDecision(w, r) Expect(err).To(HaveOccurred()) }) It("returns error when mediaType is missing", func() { - r := newGetRequest("mediaId=123") + r := newJSONPostRequest("mediaId=123", "{}") _, err := router.GetTranscodeDecision(w, r) Expect(err).To(HaveOccurred()) }) It("returns error for unsupported mediaType", func() { - r := newGetRequest("mediaId=123", "mediaType=podcast") + r := newJSONPostRequest("mediaId=123&mediaType=podcast", "{}") _, err := router.GetTranscodeDecision(w, r) Expect(err).To(HaveOccurred()) Expect(err.Error()).To(ContainSubstring("not yet supported")) @@ -52,11 +61,33 @@ var _ = Describe("Transcode endpoints", func() { It("returns error when media file not found", func() { mockMFRepo.SetError(true) - r := newGetRequest("mediaId=notfound", "mediaType=song") + r := newJSONPostRequest("mediaId=notfound&mediaType=song", "{}") _, err := router.GetTranscodeDecision(w, r) Expect(err).To(HaveOccurred()) }) + It("handles empty body gracefully", func() { + mockMFRepo.SetData(model.MediaFiles{ + {ID: "song-1", Suffix: "mp3", Codec: "MP3", BitRate: 320, Channels: 2, SampleRate: 44100}, + }) + mockTD.decision = &core.Decision{ + MediaID: "song-1", + CanDirectPlay: false, + SourceStream: core.StreamDetails{ + Container: "mp3", Codec: "mp3", Bitrate: 320, + SampleRate: 44100, Channels: 2, + }, + } + mockTD.token = "empty-body-token" + + r := newJSONPostRequest("mediaId=song-1&mediaType=song", "") + resp, err := router.GetTranscodeDecision(w, r) + + Expect(err).ToNot(HaveOccurred()) + Expect(resp.TranscodeDecision).ToNot(BeNil()) + Expect(resp.TranscodeDecision.TranscodeParams).To(Equal("empty-body-token")) + }) + It("returns a valid decision response", func() { mockMFRepo.SetData(model.MediaFiles{ {ID: "song-1", Suffix: "mp3", Codec: "MP3", BitRate: 320, Channels: 2, SampleRate: 44100}, @@ -107,7 +138,7 @@ var _ = Describe("Transcode endpoints", func() { } mockTD.token = "transcode-token" - r := newGetRequest("mediaId=song-2", "mediaType=song") + r := newJSONPostRequest("mediaId=song-2&mediaType=song", "{}") resp, err := router.GetTranscodeDecision(w, r) Expect(err).ToNot(HaveOccurred())