60 lines
2.1 KiB
Go
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
|
|
}
|