93 lines
2.8 KiB
Go
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)
|
|
}
|