diff --git a/server/subsonic/album_lists.go b/server/subsonic/album_lists.go index 58075d01d..dafdc898c 100644 --- a/server/subsonic/album_lists.go +++ b/server/subsonic/album_lists.go @@ -47,8 +47,8 @@ func (c *AlbumListController) getAlbumList(r *http.Request) (engine.Entries, err return nil, errors.New("Not implemented!") } - offset := ParamInt(r, "offset", 0) - size := utils.MinInt(ParamInt(r, "size", 10), 500) + offset := utils.ParamInt(r, "offset", 0) + size := utils.MinInt(utils.ParamInt(r, "size", 10), 500) albums, err := listFunc(r.Context(), offset, size) if err != nil { @@ -132,8 +132,8 @@ func (c *AlbumListController) GetNowPlaying(w http.ResponseWriter, r *http.Reque } func (c *AlbumListController) GetRandomSongs(w http.ResponseWriter, r *http.Request) (*responses.Subsonic, error) { - size := utils.MinInt(ParamInt(r, "size", 10), 500) - genre := ParamString(r, "genre") + size := utils.MinInt(utils.ParamInt(r, "size", 10), 500) + genre := utils.ParamString(r, "genre") songs, err := c.listGen.GetRandomSongs(r.Context(), size, genre) if err != nil { diff --git a/server/subsonic/api.go b/server/subsonic/api.go index 1baa8b633..ee76444e0 100644 --- a/server/subsonic/api.go +++ b/server/subsonic/api.go @@ -9,6 +9,7 @@ import ( "github.com/deluan/navidrome/engine" "github.com/deluan/navidrome/log" "github.com/deluan/navidrome/server/subsonic/responses" + "github.com/deluan/navidrome/utils" "github.com/go-chi/chi" ) @@ -163,7 +164,7 @@ func SendError(w http.ResponseWriter, r *http.Request, err error) { } func SendResponse(w http.ResponseWriter, r *http.Request, payload *responses.Subsonic) { - f := ParamString(r, "f") + f := utils.ParamString(r, "f") var response []byte switch f { case "json": @@ -172,7 +173,7 @@ func SendResponse(w http.ResponseWriter, r *http.Request, payload *responses.Sub response, _ = json.Marshal(wrapper) case "jsonp": w.Header().Set("Content-Type", "application/javascript") - callback := ParamString(r, "callback") + callback := utils.ParamString(r, "callback") wrapper := &responses.JsonWrapper{Subsonic: *payload} data, _ := json.Marshal(wrapper) response = []byte(fmt.Sprintf("%s(%s)", callback, data)) diff --git a/server/subsonic/browsing.go b/server/subsonic/browsing.go index d2c0ea057..7be8f7609 100644 --- a/server/subsonic/browsing.go +++ b/server/subsonic/browsing.go @@ -59,8 +59,8 @@ func (c *BrowsingController) getArtistIndex(r *http.Request, musicFolderId strin } func (c *BrowsingController) GetIndexes(w http.ResponseWriter, r *http.Request) (*responses.Subsonic, error) { - musicFolderId := ParamString(r, "musicFolderId") - ifModifiedSince := ParamTime(r, "ifModifiedSince", time.Time{}) + musicFolderId := utils.ParamString(r, "musicFolderId") + ifModifiedSince := utils.ParamTime(r, "ifModifiedSince", time.Time{}) res, err := c.getArtistIndex(r, musicFolderId, ifModifiedSince) if err != nil { @@ -73,7 +73,7 @@ func (c *BrowsingController) GetIndexes(w http.ResponseWriter, r *http.Request) } func (c *BrowsingController) GetArtists(w http.ResponseWriter, r *http.Request) (*responses.Subsonic, error) { - musicFolderId := ParamString(r, "musicFolderId") + musicFolderId := utils.ParamString(r, "musicFolderId") res, err := c.getArtistIndex(r, musicFolderId, time.Time{}) if err != nil { return nil, err @@ -85,7 +85,7 @@ func (c *BrowsingController) GetArtists(w http.ResponseWriter, r *http.Request) } func (c *BrowsingController) GetMusicDirectory(w http.ResponseWriter, r *http.Request) (*responses.Subsonic, error) { - id := ParamString(r, "id") + id := utils.ParamString(r, "id") dir, err := c.browser.Directory(r.Context(), id) switch { case err == model.ErrNotFound: @@ -102,7 +102,7 @@ func (c *BrowsingController) GetMusicDirectory(w http.ResponseWriter, r *http.Re } func (c *BrowsingController) GetArtist(w http.ResponseWriter, r *http.Request) (*responses.Subsonic, error) { - id := ParamString(r, "id") + id := utils.ParamString(r, "id") dir, err := c.browser.Artist(r.Context(), id) switch { case err == model.ErrNotFound: @@ -119,7 +119,7 @@ func (c *BrowsingController) GetArtist(w http.ResponseWriter, r *http.Request) ( } func (c *BrowsingController) GetAlbum(w http.ResponseWriter, r *http.Request) (*responses.Subsonic, error) { - id := ParamString(r, "id") + id := utils.ParamString(r, "id") dir, err := c.browser.Album(r.Context(), id) switch { case err == model.ErrNotFound: @@ -136,7 +136,7 @@ func (c *BrowsingController) GetAlbum(w http.ResponseWriter, r *http.Request) (* } func (c *BrowsingController) GetSong(w http.ResponseWriter, r *http.Request) (*responses.Subsonic, error) { - id := ParamString(r, "id") + id := utils.ParamString(r, "id") song, err := c.browser.GetSong(r.Context(), id) switch { case err == model.ErrNotFound: diff --git a/server/subsonic/helpers.go b/server/subsonic/helpers.go index fed54bfc8..43136fccd 100644 --- a/server/subsonic/helpers.go +++ b/server/subsonic/helpers.go @@ -5,8 +5,6 @@ import ( "mime" "net/http" "strconv" - "strings" - "time" "github.com/deluan/navidrome/consts" "github.com/deluan/navidrome/engine" @@ -20,7 +18,7 @@ func NewResponse() *responses.Subsonic { } func RequiredParamString(r *http.Request, param string, msg string) (string, error) { - p := ParamString(r, param) + p := utils.ParamString(r, param) if p == "" { return "", NewError(responses.ErrorMissingParameter, msg) } @@ -28,83 +26,19 @@ func RequiredParamString(r *http.Request, param string, msg string) (string, err } func RequiredParamStrings(r *http.Request, param string, msg string) ([]string, error) { - ps := ParamStrings(r, param) + ps := utils.ParamStrings(r, param) if len(ps) == 0 { return nil, NewError(responses.ErrorMissingParameter, msg) } return ps, nil } -func ParamString(r *http.Request, param string) string { - return r.URL.Query().Get(param) -} - -func ParamStrings(r *http.Request, param string) []string { - return r.URL.Query()[param] -} - -func ParamTimes(r *http.Request, param string) []time.Time { - pStr := ParamStrings(r, param) - times := make([]time.Time, len(pStr)) - for i, t := range pStr { - ti, err := strconv.ParseInt(t, 10, 64) - if err == nil { - times[i] = utils.ToTime(ti) - } - } - return times -} - -func ParamTime(r *http.Request, param string, def time.Time) time.Time { - v := ParamString(r, param) - if v == "" { - return def - } - value, err := strconv.ParseInt(v, 10, 64) - if err != nil { - return def - } - return utils.ToTime(value) -} - func RequiredParamInt(r *http.Request, param string, msg string) (int, error) { - p := ParamString(r, param) + p := utils.ParamString(r, param) if p == "" { return 0, NewError(responses.ErrorMissingParameter, msg) } - return ParamInt(r, param, 0), nil -} - -func ParamInt(r *http.Request, param string, def int) int { - v := ParamString(r, param) - if v == "" { - return def - } - value, err := strconv.ParseInt(v, 10, 32) - if err != nil { - return def - } - return int(value) -} - -func ParamInts(r *http.Request, param string) []int { - pStr := ParamStrings(r, param) - ints := make([]int, 0, len(pStr)) - for _, s := range pStr { - i, err := strconv.ParseInt(s, 10, 32) - if err == nil { - ints = append(ints, int(i)) - } - } - return ints -} - -func ParamBool(r *http.Request, param string, def bool) bool { - p := ParamString(r, param) - if p == "" { - return def - } - return strings.Index("/true/on/1/", "/"+p+"/") != -1 + return utils.ParamInt(r, param, 0), nil } type SubsonicError struct { diff --git a/server/subsonic/media_annotation.go b/server/subsonic/media_annotation.go index eba94f14e..9a6fab347 100644 --- a/server/subsonic/media_annotation.go +++ b/server/subsonic/media_annotation.go @@ -9,6 +9,7 @@ import ( "github.com/deluan/navidrome/log" "github.com/deluan/navidrome/model" "github.com/deluan/navidrome/server/subsonic/responses" + "github.com/deluan/navidrome/utils" ) type MediaAnnotationController struct { @@ -49,9 +50,9 @@ func (c *MediaAnnotationController) SetRating(w http.ResponseWriter, r *http.Req } func (c *MediaAnnotationController) Star(w http.ResponseWriter, r *http.Request) (*responses.Subsonic, error) { - ids := ParamStrings(r, "id") - albumIds := ParamStrings(r, "albumId") - artistIds := ParamStrings(r, "artistId") + ids := utils.ParamStrings(r, "id") + albumIds := utils.ParamStrings(r, "albumId") + artistIds := utils.ParamStrings(r, "artistId") if len(ids)+len(albumIds)+len(artistIds) == 0 { return nil, NewError(responses.ErrorMissingParameter, "Required id parameter is missing") } @@ -84,9 +85,9 @@ func (c *MediaAnnotationController) star(ctx context.Context, starred bool, ids } func (c *MediaAnnotationController) Unstar(w http.ResponseWriter, r *http.Request) (*responses.Subsonic, error) { - ids := ParamStrings(r, "id") - albumIds := ParamStrings(r, "albumId") - artistIds := ParamStrings(r, "artistId") + ids := utils.ParamStrings(r, "id") + albumIds := utils.ParamStrings(r, "albumId") + artistIds := utils.ParamStrings(r, "artistId") if len(ids)+len(albumIds)+len(artistIds) == 0 { return nil, NewError(responses.ErrorMissingParameter, "Required id parameter is missing") } @@ -106,14 +107,14 @@ func (c *MediaAnnotationController) Scrobble(w http.ResponseWriter, r *http.Requ if err != nil { return nil, err } - times := ParamTimes(r, "time") + times := utils.ParamTimes(r, "time") if len(times) > 0 && len(times) != len(ids) { return nil, NewError(responses.ErrorGeneric, "Wrong number of timestamps: %d, should be %d", len(times), len(ids)) } - submission := ParamBool(r, "submission", true) + submission := utils.ParamBool(r, "submission", true) playerId := 1 // TODO Multiple players, based on playerName/username/clientIP(?) - playerName := ParamString(r, "c") - username := ParamString(r, "u") + playerName := utils.ParamString(r, "c") + username := utils.ParamString(r, "u") log.Debug(r, "Scrobbling tracks", "ids", ids, "times", times, "submission", submission) for i, id := range ids { diff --git a/server/subsonic/media_retrieval.go b/server/subsonic/media_retrieval.go index 48b7170d5..38543c3da 100644 --- a/server/subsonic/media_retrieval.go +++ b/server/subsonic/media_retrieval.go @@ -9,6 +9,7 @@ import ( "github.com/deluan/navidrome/model" "github.com/deluan/navidrome/server/subsonic/responses" "github.com/deluan/navidrome/static" + "github.com/deluan/navidrome/utils" ) type MediaRetrievalController struct { @@ -36,7 +37,7 @@ func (c *MediaRetrievalController) GetCoverArt(w http.ResponseWriter, r *http.Re if err != nil { return nil, err } - size := ParamInt(r, "size", 0) + size := utils.ParamInt(r, "size", 0) err = c.cover.Get(r.Context(), id, size, w) diff --git a/server/subsonic/middlewares.go b/server/subsonic/middlewares.go index eb504bf5e..5459fffb3 100644 --- a/server/subsonic/middlewares.go +++ b/server/subsonic/middlewares.go @@ -11,6 +11,7 @@ import ( "github.com/deluan/navidrome/log" "github.com/deluan/navidrome/model" "github.com/deluan/navidrome/server/subsonic/responses" + "github.com/deluan/navidrome/utils" ) func postFormToQueryParams(next http.Handler) http.Handler { @@ -36,7 +37,7 @@ func checkRequiredParameters(next http.Handler) http.Handler { requiredParameters := []string{"u", "v", "c"} for _, p := range requiredParameters { - if ParamString(r, p) == "" { + if utils.ParamString(r, p) == "" { msg := fmt.Sprintf(`Missing required parameter "%s"`, p) log.Warn(r, msg) SendError(w, r, NewError(responses.ErrorMissingParameter, msg)) @@ -44,9 +45,9 @@ func checkRequiredParameters(next http.Handler) http.Handler { } } - user := ParamString(r, "u") - client := ParamString(r, "c") - version := ParamString(r, "v") + user := utils.ParamString(r, "u") + client := utils.ParamString(r, "c") + version := utils.ParamString(r, "v") ctx := r.Context() ctx = context.WithValue(ctx, "username", user) ctx = context.WithValue(ctx, "client", client) @@ -61,11 +62,11 @@ func checkRequiredParameters(next http.Handler) http.Handler { func authenticate(users engine.Users) func(next http.Handler) http.Handler { return func(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - username := ParamString(r, "u") - pass := ParamString(r, "p") - token := ParamString(r, "t") - salt := ParamString(r, "s") - jwt := ParamString(r, "jwt") + username := utils.ParamString(r, "u") + pass := utils.ParamString(r, "p") + token := utils.ParamString(r, "t") + salt := utils.ParamString(r, "s") + jwt := utils.ParamString(r, "jwt") usr, err := users.Authenticate(r.Context(), username, pass, token, salt, jwt) if err == model.ErrInvalidAuth { diff --git a/server/subsonic/playlists.go b/server/subsonic/playlists.go index 079bd3d26..0f517d3ee 100644 --- a/server/subsonic/playlists.go +++ b/server/subsonic/playlists.go @@ -9,6 +9,7 @@ import ( "github.com/deluan/navidrome/log" "github.com/deluan/navidrome/model" "github.com/deluan/navidrome/server/subsonic/responses" + "github.com/deluan/navidrome/utils" ) type PlaylistsController struct { @@ -61,9 +62,9 @@ func (c *PlaylistsController) GetPlaylist(w http.ResponseWriter, r *http.Request } func (c *PlaylistsController) CreatePlaylist(w http.ResponseWriter, r *http.Request) (*responses.Subsonic, error) { - songIds := ParamStrings(r, "songId") - playlistId := ParamString(r, "playlistId") - name := ParamString(r, "name") + songIds := utils.ParamStrings(r, "songId") + playlistId := utils.ParamString(r, "playlistId") + name := utils.ParamString(r, "name") if playlistId == "" && name == "" { return nil, errors.New("Required parameter name is missing") } @@ -96,8 +97,8 @@ func (c *PlaylistsController) UpdatePlaylist(w http.ResponseWriter, r *http.Requ if err != nil { return nil, err } - songsToAdd := ParamStrings(r, "songIdToAdd") - songIndexesToRemove := ParamInts(r, "songIndexToRemove") + songsToAdd := utils.ParamStrings(r, "songIdToAdd") + songIndexesToRemove := utils.ParamInts(r, "songIndexToRemove") var pname *string if len(r.URL.Query()["name"]) > 0 { diff --git a/server/subsonic/searching.go b/server/subsonic/searching.go index 607860140..d95de3d38 100644 --- a/server/subsonic/searching.go +++ b/server/subsonic/searching.go @@ -7,6 +7,7 @@ import ( "github.com/deluan/navidrome/engine" "github.com/deluan/navidrome/log" "github.com/deluan/navidrome/server/subsonic/responses" + "github.com/deluan/navidrome/utils" ) type SearchingController struct { @@ -34,12 +35,12 @@ func (c *SearchingController) getParams(r *http.Request) (*searchParams, error) if err != nil { return nil, err } - sp.artistCount = ParamInt(r, "artistCount", 20) - sp.artistOffset = ParamInt(r, "artistOffset", 0) - sp.albumCount = ParamInt(r, "albumCount", 20) - sp.albumOffset = ParamInt(r, "albumOffset", 0) - sp.songCount = ParamInt(r, "songCount", 20) - sp.songOffset = ParamInt(r, "songOffset", 0) + sp.artistCount = utils.ParamInt(r, "artistCount", 20) + sp.artistOffset = utils.ParamInt(r, "artistOffset", 0) + sp.albumCount = utils.ParamInt(r, "albumCount", 20) + sp.albumOffset = utils.ParamInt(r, "albumOffset", 0) + sp.songCount = utils.ParamInt(r, "songCount", 20) + sp.songOffset = utils.ParamInt(r, "songOffset", 0) return sp, nil } diff --git a/server/subsonic/stream.go b/server/subsonic/stream.go index 1682a9cde..7837b7e12 100644 --- a/server/subsonic/stream.go +++ b/server/subsonic/stream.go @@ -5,6 +5,7 @@ import ( "github.com/deluan/navidrome/engine" "github.com/deluan/navidrome/server/subsonic/responses" + "github.com/deluan/navidrome/utils" ) type StreamController struct { @@ -20,8 +21,8 @@ func (c *StreamController) Stream(w http.ResponseWriter, r *http.Request) (*resp if err != nil { return nil, err } - maxBitRate := ParamInt(r, "maxBitRate", 0) - format := ParamString(r, "format") + maxBitRate := utils.ParamInt(r, "maxBitRate", 0) + format := utils.ParamString(r, "format") ms, err := c.streamer.NewStream(r.Context(), id, maxBitRate, format) if err != nil { diff --git a/utils/request_helpers.go b/utils/request_helpers.go new file mode 100644 index 000000000..156e534a6 --- /dev/null +++ b/utils/request_helpers.go @@ -0,0 +1,72 @@ +package utils + +import ( + "net/http" + "strconv" + "strings" + "time" +) + +func ParamString(r *http.Request, param string) string { + return r.URL.Query().Get(param) +} + +func ParamStrings(r *http.Request, param string) []string { + return r.URL.Query()[param] +} + +func ParamTimes(r *http.Request, param string) []time.Time { + pStr := ParamStrings(r, param) + times := make([]time.Time, len(pStr)) + for i, t := range pStr { + ti, err := strconv.ParseInt(t, 10, 64) + if err == nil { + times[i] = ToTime(ti) + } + } + return times +} + +func ParamTime(r *http.Request, param string, def time.Time) time.Time { + v := ParamString(r, param) + if v == "" { + return def + } + value, err := strconv.ParseInt(v, 10, 64) + if err != nil { + return def + } + return ToTime(value) +} + +func ParamInt(r *http.Request, param string, def int) int { + v := ParamString(r, param) + if v == "" { + return def + } + value, err := strconv.ParseInt(v, 10, 32) + if err != nil { + return def + } + return int(value) +} + +func ParamInts(r *http.Request, param string) []int { + pStr := ParamStrings(r, param) + ints := make([]int, 0, len(pStr)) + for _, s := range pStr { + i, err := strconv.ParseInt(s, 10, 32) + if err == nil { + ints = append(ints, int(i)) + } + } + return ints +} + +func ParamBool(r *http.Request, param string, def bool) bool { + p := ParamString(r, param) + if p == "" { + return def + } + return strings.Index("/true/on/1/", "/"+p+"/") != -1 +}