Files

93 lines
2.8 KiB
Go

package subsonic
import (
"net/http"
"strings"
)
type handlerFunc func(rc *Context) *apiError
// registry maps a bare endpoint name (no "/rest/" prefix, no ".view"
// suffix) to its handler. Populated by init()-time registrations in each
// endpoint group's file (system.go, browsing.go, ...) via register().
var registry = map[string]handlerFunc{}
func register(name string, fn handlerFunc) {
if _, exists := registry[name]; exists {
panic("subsonic: duplicate handler registration for " + name)
}
registry[name] = fn
}
// Router returns the /rest/* handler. All authentication, protocol
// version validation, parameter parsing, and response formatting is
// centralized here — individual endpoint handlers never touch any of it
// themselves, and never write directly to the ResponseWriter except the
// small set of binary/streaming endpoints (stream, download, hls,
// getCoverArt, getAvatar, getCaptions) which set rc.Handled.
func (s *Server) Router() http.Handler {
mux := http.NewServeMux()
mux.HandleFunc("/rest/", s.dispatch)
return mux
}
func (s *Server) dispatch(w http.ResponseWriter, r *http.Request) {
name := strings.TrimPrefix(r.URL.Path, "/rest/")
name = strings.TrimSuffix(name, ".view")
if name == "" {
http.NotFound(w, r)
return
}
// Subsonic clients may send parameters via query string or (for
// endpoints with large/binary bodies, or just by client preference)
// POST form encoding; ParseForm merges both into r.Form.
if err := r.ParseForm(); err != nil {
writeResponse(w, "xml", "", errorResponse(newError(ErrGeneric, "could not parse request")))
return
}
p := params{values: r.Form}
format := p.Get("f")
if format == "" {
format = "xml"
}
callback := p.Get("callback")
if format == "jsonp" && !jsonpCallbackRE.MatchString(callback) {
// A malformed/malicious callback name must never reach the
// response body; fail the request instead of sanitizing it.
writeResponse(w, "json", "", errorResponse(newError(ErrGeneric, "invalid callback parameter")))
return
}
handler, ok := registry[name]
if !ok {
writeResponse(w, format, callback, errorResponse(newError(ErrNotFound, "Unknown Subsonic API endpoint: "+name)))
return
}
if verr := checkVersion(p.Get("v")); verr != nil {
writeResponse(w, format, callback, errorResponse(verr))
return
}
user, aerr := authenticate(s.DB, s.EncryptionKey, p)
if aerr != nil {
writeResponse(w, format, callback, errorResponse(aerr))
return
}
rc := &Context{W: w, R: r, Server: s, User: user, Params: p, Format: format, Callback: callback}
if herr := handler(rc); herr != nil {
writeResponse(w, format, callback, errorResponse(herr))
return
}
if rc.Handled {
return
}
if rc.Response == nil {
rc.Response = newResponse()
}
writeResponse(w, format, callback, rc.Response)
}