use std::collections::HashMap; // CRATES use crate::server::ResponseExt; use crate::utils::{redirect, template, Preferences}; use askama::Template; use cookie::Cookie; use futures_lite::StreamExt; use hyper::{Body, Request, Response}; use time::{Duration, OffsetDateTime}; // STRUCTS #[derive(Template)] #[template(path = "settings.html")] struct SettingsTemplate { prefs: Preferences, url: String, } // CONSTANTS const PREFS: [&str; 12] = [ "theme", "front_page", "layout", "wide", "comment_sort", "post_sort", "show_nsfw", "blur_nsfw", "use_hls", "hide_hls_notification", "autoplay_videos", "hide_awards", ]; // FUNCTIONS // Retrieve cookies from request "Cookie" header pub async fn get(req: Request) -> Result, String> { let url = req.uri().to_string(); template(SettingsTemplate { prefs: Preferences::new(&req), url, }) } // Set cookies using response "Set-Cookie" header pub async fn set(req: Request) -> Result, String> { // Split the body into parts let (parts, mut body) = req.into_parts(); // Grab existing cookies let _cookies: Vec = parts .headers .get_all("Cookie") .iter() .filter_map(|header| Cookie::parse(header.to_str().unwrap_or_default()).ok()) .collect(); // Aggregate the body... // let whole_body = hyper::body::aggregate(req).await.map_err(|e| e.to_string())?; let body_bytes = body .try_fold(Vec::new(), |mut data, chunk| { data.extend_from_slice(&chunk); Ok(data) }) .await .map_err(|e| e.to_string())?; let form = url::form_urlencoded::parse(&body_bytes).collect::>(); let mut response = redirect("/settings".to_string()); for &name in &PREFS { match form.get(name) { Some(value) => response.insert_cookie( Cookie::build(name.to_owned(), value.clone()) .path("/") .http_only(true) .expires(OffsetDateTime::now_utc() + Duration::weeks(52)) .finish(), ), None => response.remove_cookie(name.to_string()), }; } Ok(response) } fn set_cookies_method(req: Request, remove_cookies: bool) -> Response { // Split the body into parts let (parts, _) = req.into_parts(); // Grab existing cookies let _cookies: Vec = parts .headers .get_all("Cookie") .iter() .filter_map(|header| Cookie::parse(header.to_str().unwrap_or_default()).ok()) .collect(); let query = parts.uri.query().unwrap_or_default().as_bytes(); let form = url::form_urlencoded::parse(query).collect::>(); let path = match form.get("redirect") { Some(value) => format!("/{}", value.replace("%26", "&").replace("%23", "#")), None => "/".to_string(), }; let mut response = redirect(path); for name in [PREFS.to_vec(), vec!["subscriptions", "filters"]].concat() { match form.get(name) { Some(value) => response.insert_cookie( Cookie::build(name.to_owned(), value.clone()) .path("/") .http_only(true) .expires(OffsetDateTime::now_utc() + Duration::weeks(52)) .finish(), ), None => { if remove_cookies { response.remove_cookie(name.to_string()); } } }; } response } // Set cookies using response "Set-Cookie" header pub async fn restore(req: Request) -> Result, String> { Ok(set_cookies_method(req, true)) } pub async fn update(req: Request) -> Result, String> { Ok(set_cookies_method(req, false)) }