Phase 2: Subsonic REST API framework (auth, response formatting, error handling, routing) plus user permission model
This commit is contained in:
@@ -0,0 +1,59 @@
|
||||
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}
|
||||
return r
|
||||
}
|
||||
Reference in New Issue
Block a user