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