redlib/src/settings.rs

264 lines
7.9 KiB
Rust
Raw Normal View History

#![allow(clippy::cmp_owned)]
2021-03-17 15:30:33 -07:00
use std::collections::HashMap;
2021-01-05 18:04:49 -08:00
// CRATES
2021-03-17 15:30:33 -07:00
use crate::server::ResponseExt;
use crate::subreddit::join_until_size_limit;
2021-02-24 21:29:23 -08:00
use crate::utils::{redirect, template, Preferences};
2021-03-17 15:30:33 -07:00
use cookie::Cookie;
use futures_lite::StreamExt;
use hyper::{Body, Request, Response};
2024-10-02 23:43:13 +02:00
use rinja::Template;
2021-01-05 18:04:49 -08:00
use time::{Duration, OffsetDateTime};
// STRUCTS
#[derive(Template)]
2021-01-07 08:38:05 -08:00
#[template(path = "settings.html")]
2021-01-05 18:04:49 -08:00
struct SettingsTemplate {
2021-01-08 17:50:03 -08:00
prefs: Preferences,
url: String,
2021-01-05 18:04:49 -08:00
}
// CONSTANTS
const PREFS: [&str; 18] = [
"theme",
"front_page",
"layout",
"wide",
"comment_sort",
"post_sort",
"blur_spoiler",
"show_nsfw",
"blur_nsfw",
"use_hls",
"hide_hls_notification",
2021-10-25 21:27:55 -07:00
"autoplay_videos",
2024-05-13 23:49:59 +02:00
"hide_sidebar_and_summary",
"fixed_navbar",
2023-01-01 21:39:38 -05:00
"hide_awards",
"hide_score",
2023-01-12 09:46:56 +01:00
"disable_visit_reddit_confirmation",
"video_quality",
];
2021-01-05 18:04:49 -08:00
// FUNCTIONS
// Retrieve cookies from request "Cookie" header
2021-03-17 15:30:33 -07:00
pub async fn get(req: Request<Body>) -> Result<Response<Body>, String> {
let url = req.uri().to_string();
2024-01-19 20:16:17 -05:00
Ok(template(&SettingsTemplate {
2023-01-01 21:39:38 -05:00
prefs: Preferences::new(&req),
2021-11-14 18:51:36 -08:00
url,
2024-01-19 20:16:17 -05:00
}))
2021-01-05 18:04:49 -08:00
}
// Set cookies using response "Set-Cookie" header
2021-03-17 15:30:33 -07:00
pub async fn set(req: Request<Body>) -> Result<Response<Body>, String> {
// Split the body into parts
let (parts, mut body) = req.into_parts();
// Grab existing cookies
2024-01-19 20:16:17 -05:00
let _cookies: Vec<Cookie<'_>> = parts
2021-05-20 12:24:06 -07:00
.headers
.get_all("Cookie")
.iter()
.filter_map(|header| Cookie::parse(header.to_str().unwrap_or_default()).ok())
.collect();
2021-03-17 15:30:33 -07:00
// 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::<HashMap<_, _>>();
2024-01-19 20:16:17 -05:00
let mut response = redirect("/settings");
2021-01-05 18:04:49 -08:00
for &name in &PREFS {
2021-03-17 15:30:33 -07:00
match form.get(name) {
2021-05-20 12:24:06 -07:00
Some(value) => response.insert_cookie(
2023-12-26 16:24:53 -05:00
Cookie::build((name.to_owned(), value.clone()))
2021-01-08 17:35:04 -08:00
.path("/")
.http_only(true)
.expires(OffsetDateTime::now_utc() + Duration::weeks(52))
2023-12-26 16:24:53 -05:00
.into(),
2021-01-08 17:35:04 -08:00
),
2021-05-20 12:24:06 -07:00
None => response.remove_cookie(name.to_string()),
2021-01-08 17:35:04 -08:00
};
}
2021-01-07 08:38:05 -08:00
2021-05-20 12:24:06 -07:00
Ok(response)
2021-01-05 18:04:49 -08:00
}
fn set_cookies_method(req: Request<Body>, remove_cookies: bool) -> Response<Body> {
2021-03-17 15:30:33 -07:00
// Split the body into parts
let (parts, _) = req.into_parts();
// Grab existing cookies
2024-01-19 20:16:17 -05:00
let _cookies: Vec<Cookie<'_>> = parts
2021-05-20 12:24:06 -07:00
.headers
.get_all("Cookie")
.iter()
.filter_map(|header| Cookie::parse(header.to_str().unwrap_or_default()).ok())
.collect();
2021-03-17 15:30:33 -07:00
let query = parts.uri.query().unwrap_or_default().as_bytes();
2021-03-17 15:30:33 -07:00
let form = url::form_urlencoded::parse(query).collect::<HashMap<_, _>>();
let path = match form.get("redirect") {
Some(value) => format!("/{}", value.replace("%26", "&").replace("%23", "#")),
2021-02-13 15:02:38 -08:00
None => "/".to_string(),
};
2024-01-19 20:16:17 -05:00
let mut response = redirect(&path);
for name in PREFS {
2021-03-17 15:30:33 -07:00
match form.get(name) {
2021-05-20 12:24:06 -07:00
Some(value) => response.insert_cookie(
2023-12-26 16:24:53 -05:00
Cookie::build((name.to_owned(), value.clone()))
.path("/")
.http_only(true)
.expires(OffsetDateTime::now_utc() + Duration::weeks(52))
2023-12-26 16:24:53 -05:00
.into(),
),
None => {
if remove_cookies {
2021-09-09 17:28:55 -07:00
response.remove_cookie(name.to_string());
}
}
};
}
// Get subscriptions/filters to restore from query string
let subscriptions = form.get("subscriptions");
let filters = form.get("filters");
// We can't search through the cookies directly like in subreddit.rs, so instead we have to make a string out of the request's headers to search through
let cookies_string = parts
.headers
.get("cookie")
.map(|hv| hv.to_str().unwrap_or("").to_string()) // Return String
.unwrap_or_else(String::new); // Return an empty string if None
// If there are subscriptions to restore set them and delete any old subscriptions cookies, otherwise delete them all
if subscriptions.is_some() {
let sub_list: Vec<String> = subscriptions.expect("Subscriptions").split('+').map(str::to_string).collect();
// Start at 0 to keep track of what number we need to start deleting old subscription cookies from
let mut subscriptions_number_to_delete_from = 0;
// Starting at 0 so we handle the subscription cookie without a number first
for (subscriptions_number, list) in join_until_size_limit(&sub_list).into_iter().enumerate() {
let subscriptions_cookie = if subscriptions_number == 0 {
"subscriptions".to_string()
} else {
format!("subscriptions{}", subscriptions_number)
};
response.insert_cookie(
Cookie::build((subscriptions_cookie, list))
.path("/")
.http_only(true)
.expires(OffsetDateTime::now_utc() + Duration::weeks(52))
.into(),
);
subscriptions_number_to_delete_from += 1;
}
// While subscriptionsNUMBER= is in the string of cookies add a response removing that cookie
while cookies_string.contains(&format!("subscriptions{subscriptions_number_to_delete_from}=")) {
// Remove that subscriptions cookie
response.remove_cookie(format!("subscriptions{subscriptions_number_to_delete_from}"));
// Increment subscriptions cookie number
subscriptions_number_to_delete_from += 1;
}
} else {
// Remove unnumbered subscriptions cookie
response.remove_cookie("subscriptions".to_string());
// Starts at one to deal with the first numbered subscription cookie and onwards
let mut subscriptions_number_to_delete_from = 1;
// While subscriptionsNUMBER= is in the string of cookies add a response removing that cookie
while cookies_string.contains(&format!("subscriptions{subscriptions_number_to_delete_from}=")) {
// Remove that subscriptions cookie
response.remove_cookie(format!("subscriptions{subscriptions_number_to_delete_from}"));
// Increment subscriptions cookie number
subscriptions_number_to_delete_from += 1;
}
}
// If there are filters to restore set them and delete any old filters cookies, otherwise delete them all
if filters.is_some() {
let filters_list: Vec<String> = filters.expect("Filters").split('+').map(str::to_string).collect();
// Start at 0 to keep track of what number we need to start deleting old subscription cookies from
let mut filters_number_to_delete_from = 0;
// Starting at 0 so we handle the subscription cookie without a number first
for (filters_number, list) in join_until_size_limit(&filters_list).into_iter().enumerate() {
let filters_cookie = if filters_number == 0 {
"filters".to_string()
} else {
format!("filters{}", filters_number)
};
response.insert_cookie(
Cookie::build((filters_cookie, list))
.path("/")
.http_only(true)
.expires(OffsetDateTime::now_utc() + Duration::weeks(52))
.into(),
);
filters_number_to_delete_from += 1;
}
// While filtersNUMBER= is in the string of cookies add a response removing that cookie
while cookies_string.contains(&format!("filters{filters_number_to_delete_from}=")) {
// Remove that filters cookie
response.remove_cookie(format!("filters{filters_number_to_delete_from}"));
// Increment filters cookie number
filters_number_to_delete_from += 1;
}
} else {
// Remove unnumbered filters cookie
response.remove_cookie("filters".to_string());
// Starts at one to deal with the first numbered subscription cookie and onwards
let mut filters_number_to_delete_from = 1;
// While filtersNUMBER= is in the string of cookies add a response removing that cookie
while cookies_string.contains(&format!("filters{filters_number_to_delete_from}=")) {
// Remove that sfilters cookie
response.remove_cookie(format!("filters{filters_number_to_delete_from}"));
// Increment filters cookie number
filters_number_to_delete_from += 1;
}
}
2021-05-20 12:24:06 -07:00
response
}
// Set cookies using response "Set-Cookie" header
pub async fn restore(req: Request<Body>) -> Result<Response<Body>, String> {
Ok(set_cookies_method(req, true))
}
pub async fn update(req: Request<Body>) -> Result<Response<Body>, String> {
Ok(set_cookies_method(req, false))
}