Files

60 lines
2.1 KiB
Go

package subsonic
import (
"encoding/json"
"encoding/xml"
"net/http"
"regexp"
)
// jsonEnvelope is the {"subsonic-response": {...}} wrapper the JSON
// format requires around the same Response struct XML uses directly.
type jsonEnvelope struct {
Response *Response `json:"subsonic-response"`
}
// jsonpCallbackRE matches a safe JavaScript identifier (including dotted
// paths like "foo.bar", which some clients use). Anything else is
// rejected outright rather than interpolated into the response body,
// which would otherwise be a script-injection vector.
var jsonpCallbackRE = regexp.MustCompile(`^[A-Za-z_$][A-Za-z0-9_$]*(\.[A-Za-z_$][A-Za-z0-9_$]*)*$`)
// writeResponse serializes resp in the format requested by the client
// (f=xml, the default; f=json; f=jsonp with a validated callback) and
// writes it with the correct Content-Type. It never returns an error to
// the caller — formatting failures are logged-and-swallowed rather than
// leaking a broken partial body or a Go panic through the wire.
func writeResponse(w http.ResponseWriter, format, callback string, resp *Response) {
switch format {
case "json":
w.Header().Set("Content-Type", "application/json")
_ = json.NewEncoder(w).Encode(jsonEnvelope{Response: resp})
case "jsonp":
w.Header().Set("Content-Type", "application/javascript")
body, err := json.Marshal(jsonEnvelope{Response: resp})
if err != nil {
return
}
if !jsonpCallbackRE.MatchString(callback) {
// Should already be rejected before we get here; fail safe
// by falling back to a fixed, harmless callback name rather
// than ever writing an unvalidated callback into the body.
callback = "callback"
}
_, _ = w.Write([]byte(callback + "("))
_, _ = w.Write(body)
_, _ = w.Write([]byte(")"))
default:
w.Header().Set("Content-Type", "application/xml; charset=utf-8")
_, _ = w.Write([]byte(xml.Header))
_ = xml.NewEncoder(w).Encode(resp)
}
}
func errorResponse(err *apiError) *Response {
r := newResponse()
r.Status = "failed"
r.Error = &Error{Code: err.Code, Message: err.Message, HelpURL: err.HelpURL}
return r
}