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, HelpURL: err.HelpURL} return r }