//! Star and unstar a repo: `POST /{owner}/{repo}/star` and `POST /{owner}/{repo}/unstar`. The //! viewer must be signed in, must be able to read the repo (otherwise 404, as for any hidden repo) //! and must send a valid CSRF token. The response is a 303 back to the page the viewer came from //! when that is a same-origin relative path, and to the repo home otherwise. use http::header::{HOST, REFERER}; use serde::Deserialize; use topcoat::Result; use topcoat::context::{Cx, app_context}; use topcoat::router::content::Form; use topcoat::router::error::{SeeOther, see_other}; use topcoat::router::request::headers; use topcoat::router::{path_param, route}; use crate::auth::{require_csrf, safe_next}; use crate::pages::repo_ctx::load_repo; use crate::state::AppState; use crate::views::repo_href; path_param!(owner); path_param!(repo); #[derive(Deserialize)] struct StarForm { #[serde(default)] csrf: String, } #[route(POST "/{owner}/{repo}/star")] async fn star_post(cx: &Cx, Form(form): Form) -> Result { set_starred(cx, &form.csrf, true).await } #[route(POST "/{owner}/{repo}/unstar")] async fn unstar_post(cx: &Cx, Form(form): Form) -> Result { set_starred(cx, &form.csrf, false).await } async fn set_starred(cx: &Cx, csrf: &str, starred: bool) -> Result { let owner = path_param::(cx); let name = path_param::(cx); let ctx = load_repo(cx, owner, name).await?; let (user, _) = require_csrf(cx, csrf).await?; let state = app_context::(cx); if starred { state.store.star(&user.id, &ctx.repo.id).await?; } else { state.store.unstar(&user.id, &ctx.repo.id).await?; } let home = repo_href(&ctx.repo.owner_name, &ctx.repo.name, "", ""); Ok(see_other(return_path(cx).unwrap_or(home))) } /// The Referer as a path to return to: its path and query when it points at this host, or the /// referer itself when it is already a local relative path. `None` for anything else, including a /// missing or foreign Referer. pub(crate) fn return_path(cx: &Cx) -> Option { let headers = headers(cx); let referer = headers.get(REFERER)?.to_str().ok()?; let candidate = match referer.parse::().ok()? { uri if uri.authority().is_some() => { let host = headers.get(HOST)?.to_str().ok()?; if !uri.authority()?.as_str().eq_ignore_ascii_case(host) { return None; } uri.path_and_query()?.as_str().to_owned() } _ => referer.to_owned(), }; (safe_next(Some(&candidate)) == candidate).then_some(candidate) }