diff --git a/src/api.rs b/src/api.rs index a48591d..cdce98f 100644 --- a/src/api.rs +++ b/src/api.rs @@ -249,7 +249,7 @@ fn get_endpoints(db: &Arc, index_channel: &Arc) -> Mou lastfm_router.get( "/scrobble", move |request: &mut Request| self::lastfm_scrobble(request, scrobble_db.deref()), - "auth", + "scrobble", ); auth_api_mount.mount("/lastfm/", lastfm_router); diff --git a/src/rocket_api.rs b/src/rocket_api.rs index d496290..ec4f3f4 100644 --- a/src/rocket_api.rs +++ b/src/rocket_api.rs @@ -1,16 +1,19 @@ use rocket::http::{Cookie, Cookies, RawStr, Status}; use rocket::request::{self, FromParam, FromRequest, Request}; +use rocket::response::content::Html; use rocket::{Outcome, State}; use rocket_contrib::json::Json; use std::fs::File; -use std::path::PathBuf; use std::ops::Deref; +use std::path::PathBuf; +use std::str; use std::sync::Arc; use config::{self, Config, Preferences}; use db::DB; use errors; use index; +use lastfm; use playlist; use serve; use thumbnails; @@ -45,6 +48,10 @@ pub fn get_routes() -> Vec { save_playlist, read_playlist, delete_playlist, + lastfm_link, + lastfm_unlink, + lastfm_now_playing, + lastfm_scrobble, ] } @@ -97,18 +104,18 @@ struct VFSPathBuf { impl<'r> FromParam<'r> for VFSPathBuf { type Error = &'r RawStr; - fn from_param(param: &'r RawStr) -> Result { + fn from_param(param: &'r RawStr) -> Result { let decoded_path = param.percent_decode_lossy(); - Ok(VFSPathBuf{ - path_buf: PathBuf::from(decoded_path.into_owned()) + Ok(VFSPathBuf { + path_buf: PathBuf::from(decoded_path.into_owned()), }) - } + } } impl From for PathBuf { - fn from(vfs_path_buf: VFSPathBuf) -> Self { - vfs_path_buf.path_buf.clone() - } + fn from(vfs_path_buf: VFSPathBuf) -> Self { + vfs_path_buf.path_buf.clone() + } } #[derive(Serialize)] @@ -198,10 +205,11 @@ fn auth( mut cookies: Cookies, ) -> Result, errors::Error> { user::auth::(&db, &credentials.username, &credentials.password)?; - cookies.add_private(Cookie::new( - SESSION_FIELD_USERNAME, - credentials.username.clone(), - )); + cookies.add_private( + Cookie::build(SESSION_FIELD_USERNAME, credentials.username.clone()) + .same_site(rocket::http::SameSite::Lax) + .finish(), + ); let auth_output = AuthOutput { admin: user::is_admin::(&db, &credentials.username)?, @@ -257,19 +265,30 @@ fn recent(db: State, _auth: Auth) -> Result>, err } #[get("/search")] -fn search_root(db: State, _auth: Auth) -> Result>, errors::Error> { +fn search_root( + db: State, + _auth: Auth, +) -> Result>, errors::Error> { let result = index::search(db.deref(), "")?; Ok(Json(result)) } #[get("/search/")] -fn search(db: State, _auth: Auth, query: String) -> Result>, errors::Error> { +fn search( + db: State, + _auth: Auth, + query: String, +) -> Result>, errors::Error> { let result = index::search(db.deref(), &query)?; Ok(Json(result)) } #[get("/serve/")] -fn serve(db: State, _auth: Auth, path: VFSPathBuf) -> Result, errors::Error> { +fn serve( + db: State, + _auth: Auth, + path: VFSPathBuf, +) -> Result, errors::Error> { let db: &DB = db.deref(); let vfs = db.get_vfs()?; let real_path = vfs.virtual_to_real(&path.into() as &PathBuf)?; @@ -290,8 +309,10 @@ struct ListPlaylistsEntry { } #[get("/playlists")] -fn list_playlists(db: State, auth: Auth) -> Result>, errors::Error> { - +fn list_playlists( + db: State, + auth: Auth, +) -> Result>, errors::Error> { let playlist_names = playlist::list_playlists(&auth.username, db.deref())?; let playlists: Vec = playlist_names .into_iter() @@ -307,13 +328,22 @@ struct SavePlaylistInput { } #[put("/playlist/", data = "")] -fn save_playlist(db: State, auth: Auth, name: String, playlist: Json) -> Result<(), errors::Error> { +fn save_playlist( + db: State, + auth: Auth, + name: String, + playlist: Json, +) -> Result<(), errors::Error> { playlist::save_playlist(&name, &auth.username, &playlist.tracks, db.deref())?; Ok(()) } #[get("/playlist/")] -fn read_playlist(db: State, auth: Auth, name: String) -> Result>, errors::Error> { +fn read_playlist( + db: State, + auth: Auth, + name: String, +) -> Result>, errors::Error> { let songs = playlist::read_playlist(&name, &auth.username, db.deref())?; Ok(Json(songs)) } @@ -323,3 +353,51 @@ fn delete_playlist(db: State, auth: Auth, name: String) -> Result<(), errors playlist::delete_playlist(&name, &auth.username, db.deref())?; Ok(()) } + +#[put("/lastfm/now_playing/")] +fn lastfm_now_playing(db: State, auth: Auth, path: VFSPathBuf) -> Result<(), errors::Error> { + lastfm::now_playing(db.deref(), &auth.username, &path.into() as &PathBuf)?; + Ok(()) +} + +#[post("/lastfm/scrobble/")] +fn lastfm_scrobble(db: State, auth: Auth, path: VFSPathBuf) -> Result<(), errors::Error> { + lastfm::scrobble(db.deref(), &auth.username, &path.into() as &PathBuf)?; + Ok(()) +} + +#[get("/lastfm/link?&")] +fn lastfm_link( + db: State, + auth: Auth, + token: String, + content: String, +) -> Result, errors::Error> { + lastfm::link(db.deref(), &auth.username, &token)?; + + // Percent decode + let base64_content = match RawStr::from_str(&content).percent_decode() { + Ok(s) => s, + Err(_) => return Err(errors::Error::from(errors::ErrorKind::EncodingError).into()), + }; + + // Base64 decode + let popup_content = match base64::decode(base64_content.as_bytes()) { + Ok(c) => c, + Err(_) => return Err(errors::Error::from(errors::ErrorKind::EncodingError).into()), + }; + + // UTF-8 decode + let popup_content_string = match str::from_utf8(&popup_content) { + Ok(s) => s, + Err(_) => return Err(errors::Error::from(errors::ErrorKind::EncodingError).into()), + }; + + Ok(Html(popup_content_string.to_string())) +} + +#[delete("/lastfm/link")] +fn lastfm_unlink(db: State, auth: Auth) -> Result<(), errors::Error> { + lastfm::unlink(db.deref(), &auth.username)?; + Ok(()) +}