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) }