Return 500 in case of Subsonic response marshalling errors

This commit is contained in:
Deluan 2024-02-16 19:41:53 -05:00
parent 9a051967f6
commit d8e1748928
2 changed files with 110 additions and 0 deletions

View File

@ -302,8 +302,12 @@ func sendResponse(w http.ResponseWriter, r *http.Request, payload *responses.Sub
w.Header().Set("Content-Type", "application/xml")
response, err = xml.Marshal(payload)
}
// This should never happen, but if it does, we need to know
if err != nil {
log.Error(r.Context(), "Error marshalling response", "format", f, err)
w.WriteHeader(http.StatusInternalServerError)
_, _ = w.Write([]byte("Internal Server Error: " + err.Error()))
return
}
if payload.Status == "ok" {
if log.IsGreaterOrEqualTo(log.LevelTrace) {

106
server/subsonic/api_test.go Normal file
View File

@ -0,0 +1,106 @@
package subsonic
import (
"encoding/json"
"encoding/xml"
"math"
"net/http"
"net/http/httptest"
"strings"
"github.com/navidrome/navidrome/server/subsonic/responses"
. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
)
var _ = Describe("sendResponse", func() {
var (
w *httptest.ResponseRecorder
r *http.Request
payload *responses.Subsonic
)
BeforeEach(func() {
w = httptest.NewRecorder()
r = httptest.NewRequest("GET", "/somepath", nil)
payload = &responses.Subsonic{
Status: "ok",
Version: "1.16.1",
}
})
Context("when format is JSON", func() {
It("should set Content-Type to application/json and return the correct body", func() {
q := r.URL.Query()
q.Add("f", "json")
r.URL.RawQuery = q.Encode()
sendResponse(w, r, payload)
Expect(w.Header().Get("Content-Type")).To(Equal("application/json"))
Expect(w.Body.String()).NotTo(BeEmpty())
var wrapper responses.JsonWrapper
err := json.Unmarshal(w.Body.Bytes(), &wrapper)
Expect(err).NotTo(HaveOccurred())
Expect(wrapper.Subsonic.Status).To(Equal(payload.Status))
Expect(wrapper.Subsonic.Version).To(Equal(payload.Version))
})
})
Context("when format is JSONP", func() {
It("should set Content-Type to application/javascript and return the correct callback body", func() {
q := r.URL.Query()
q.Add("f", "jsonp")
q.Add("callback", "testCallback")
r.URL.RawQuery = q.Encode()
sendResponse(w, r, payload)
Expect(w.Header().Get("Content-Type")).To(Equal("application/javascript"))
body := w.Body.String()
Expect(body).To(SatisfyAll(
ContainSubstring("testCallback("),
ContainSubstring(")"),
))
// Extract JSON from the JSONP response
jsonBody := body[strings.Index(body, "(")+1 : strings.LastIndex(body, ")")]
var wrapper responses.JsonWrapper
err := json.Unmarshal([]byte(jsonBody), &wrapper)
Expect(err).NotTo(HaveOccurred())
Expect(wrapper.Subsonic.Status).To(Equal(payload.Status))
})
})
Context("when format is XML or unspecified", func() {
It("should set Content-Type to application/xml and return the correct body", func() {
// No format specified, expecting XML by default
sendResponse(w, r, payload)
Expect(w.Header().Get("Content-Type")).To(Equal("application/xml"))
var subsonicResponse responses.Subsonic
err := xml.Unmarshal(w.Body.Bytes(), &subsonicResponse)
Expect(err).NotTo(HaveOccurred())
Expect(subsonicResponse.Status).To(Equal(payload.Status))
Expect(subsonicResponse.Version).To(Equal(payload.Version))
})
})
Context("when an error occurs during marshalling", func() {
It("should return HTTP 500", func() {
payload.Song = &responses.Child{
ReplayGain: responses.ReplayGain{TrackGain: math.Inf(1)},
} // This will cause an error when marshalling to JSON
q := r.URL.Query()
q.Add("f", "json")
r.URL.RawQuery = q.Encode()
sendResponse(w, r, payload)
Expect(w.Code).To(Equal(http.StatusInternalServerError))
body := w.Body.String()
Expect(body).To(ContainSubstring("Internal Server Error"))
})
})
})