forge
ExploreLog in

Tahinli/forge

public
0 watching
★ 0
2.6 KiB
1//! Star and unstar a repo: `POST /{owner}/{repo}/star` and `POST /{owner}/{repo}/unstar`. The
2//! viewer must be signed in, must be able to read the repo (otherwise 404, as for any hidden repo)
3//! and must send a valid CSRF token. The response is a 303 back to the page the viewer came from
4//! when that is a same-origin relative path, and to the repo home otherwise.
5
6use http::header::{HOST, REFERER};
7use serde::Deserialize;
8use topcoat::Result;
9use topcoat::context::{Cx, app_context};
10use topcoat::router::content::Form;
11use topcoat::router::error::{SeeOther, see_other};
12use topcoat::router::request::headers;
13use topcoat::router::{path_param, route};
14
15use crate::auth::{require_csrf, safe_next};
16use crate::pages::repo_ctx::load_repo;
17use crate::state::AppState;
18use crate::views::repo_href;
19
20path_param!(owner);
21path_param!(repo);
22
23#[derive(Deserialize)]
24struct StarForm {
25 #[serde(default)]
26 csrf: String,
27}
28
29#[route(POST "/{owner}/{repo}/star")]
30async fn star_post(cx: &Cx, Form(form): Form<StarForm>) -> Result<SeeOther> {
31 set_starred(cx, &form.csrf, true).await
32}
33
34#[route(POST "/{owner}/{repo}/unstar")]
35async fn unstar_post(cx: &Cx, Form(form): Form<StarForm>) -> Result<SeeOther> {
36 set_starred(cx, &form.csrf, false).await
37}
38
39async fn set_starred(cx: &Cx, csrf: &str, starred: bool) -> Result<SeeOther> {
40 let owner = path_param::<Owner>(cx);
41 let name = path_param::<Repo>(cx);
42 let ctx = load_repo(cx, owner, name).await?;
43 let (user, _) = require_csrf(cx, csrf).await?;
44 let state = app_context::<AppState>(cx);
45 if starred {
46 state.store.star(&user.id, &ctx.repo.id).await?;
47 } else {
48 state.store.unstar(&user.id, &ctx.repo.id).await?;
49 }
50 let home = repo_href(&ctx.repo.owner_name, &ctx.repo.name, "", "");
51 Ok(see_other(return_path(cx).unwrap_or(home)))
52}
53
54/// The Referer as a path to return to: its path and query when it points at this host, or the
55/// referer itself when it is already a local relative path. `None` for anything else, including a
56/// missing or foreign Referer.
57pub(crate) fn return_path(cx: &Cx) -> Option<String> {
58 let headers = headers(cx);
59 let referer = headers.get(REFERER)?.to_str().ok()?;
60 let candidate = match referer.parse::<http::Uri>().ok()? {
61 uri if uri.authority().is_some() => {
62 let host = headers.get(HOST)?.to_str().ok()?;
63 if !uri.authority()?.as_str().eq_ignore_ascii_case(host) {
64 return None;
65 }
66 uri.path_and_query()?.as_str().to_owned()
67 }
68 _ => referer.to_owned(),
69 };
70 (safe_next(Some(&candidate)) == candidate).then_some(candidate)
71}