From 2026a8f4579b1db0f6e5e7b11ac33c13969adb6c Mon Sep 17 00:00:00 2001 From: Tyler Murphy Date: Wed, 1 Feb 2023 20:34:22 -0500 Subject: static serve refactor --- public/css/home.css | 2 +- public/js/main.js | 4 +- src/admin.rs | 129 ------------------------ src/api/admin.rs | 3 +- src/api/image.rs | 54 ----------- src/api/mod.rs | 19 ++-- src/api/pages.rs | 77 --------------- src/api/users.rs | 8 +- src/console.rs | 264 -------------------------------------------------- src/database/users.rs | 2 +- src/main.rs | 39 +++----- src/public/admin.rs | 129 ++++++++++++++++++++++++ src/public/console.rs | 264 ++++++++++++++++++++++++++++++++++++++++++++++++++ src/public/file.rs | 69 +++++++++++++ src/public/mod.rs | 47 +++++++++ src/public/pages.rs | 65 +++++++++++++ src/types/extract.rs | 63 ++++++------ src/types/http.rs | 23 +---- src/types/user.rs | 2 +- 19 files changed, 641 insertions(+), 622 deletions(-) delete mode 100644 src/admin.rs delete mode 100644 src/api/image.rs delete mode 100644 src/api/pages.rs delete mode 100644 src/console.rs create mode 100644 src/public/admin.rs create mode 100644 src/public/console.rs create mode 100644 src/public/file.rs create mode 100644 src/public/mod.rs create mode 100644 src/public/pages.rs diff --git a/public/css/home.css b/public/css/home.css index 60dc3a5..f467aba 100644 --- a/public/css/home.css +++ b/public/css/home.css @@ -80,7 +80,7 @@ body { } .icons { - background-image: url('/images/icons.png'); + background-image: url('/image/icons.png'); display: inline-block; width: 18px; height: 18px; diff --git a/public/js/main.js b/public/js/main.js index 5737173..ffbc1f3 100644 --- a/public/js/main.js +++ b/public/js/main.js @@ -33,11 +33,11 @@ function remove(id) { } function pfp(id) { - return `` + return `` } function banner(id) { - return `` + return `` } const months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', diff --git a/src/admin.rs b/src/admin.rs deleted file mode 100644 index 1da2f1e..0000000 --- a/src/admin.rs +++ /dev/null @@ -1,129 +0,0 @@ -use axum::response::Response; -use lazy_static::lazy_static; -use rand::{distributions::Alphanumeric, Rng}; -use tokio::sync::Mutex; - -use crate::{ - console::{self, sanatize}, - types::{http::ResponseCode, post::Post, session::Session, user::User}, -}; - -lazy_static! { - static ref SECRET: Mutex = Mutex::new(String::new()); -} - -pub fn new_secret() -> String { - rand::thread_rng() - .sample_iter(&Alphanumeric) - .take(32) - .map(char::from) - .collect() -} - -pub async fn get_secret() -> String { - let mut secret = SECRET.lock().await; - if secret.is_empty() { - *secret = new_secret(); - } - secret.clone() -} - -pub async fn regen_secret() -> String { - let mut secret = SECRET.lock().await; - *secret = new_secret(); - secret.clone() -} - -pub fn generate_users() -> Response { - let users = match User::reterieve_all() { - Ok(users) => users, - Err(err) => return err, - }; - - let mut html = r#" - - User ID - First Name - Last Name - Email - Password - Gender - Date - Day - Month - Year - - "# - .to_string(); - - for user in users { - html.push_str( - &format!("{}{}{}{}{}{}{}{}{}{}", - user.user_id, sanatize(&user.firstname), sanatize(&user.lastname), sanatize(&user.email), sanatize(&user.password), - sanatize(&user.gender), user.date, user.day, user.month, user.year - ) - ); - } - - ResponseCode::Success.text(&html) -} - -pub fn generate_posts() -> Response { - let posts = match Post::reterieve_all() { - Ok(posts) => posts, - Err(err) => return err, - }; - - let mut html = r#" - - Post ID - User ID - Content - Likes - Comments - Date - - "# - .to_string(); - - for post in posts { - let Ok(likes) = serde_json::to_string(&post.likes) else { continue }; - let Ok(comments) = serde_json::to_string(&post.comments) else { continue }; - - html.push_str(&format!( - "{}{}{}{}{}{}", - post.post_id, - post.user_id, - sanatize(&post.content), - console::beautify(&likes), - console::beautify(&comments), - post.date - )); - } - - ResponseCode::Success.text(&html) -} - -pub fn generate_sessions() -> Response { - let sessions = match Session::reterieve_all() { - Ok(sessions) => sessions, - Err(err) => return err, - }; - - let mut html = r#" - - User ID - Token - - "# - .to_string(); - - for session in sessions { - html.push_str(&format!( - "{}{}", - session.user_id, session.token - )); - } - - ResponseCode::Success.text(&html) -} diff --git a/src/api/admin.rs b/src/api/admin.rs index 7de5fc0..a23d20f 100644 --- a/src/api/admin.rs +++ b/src/api/admin.rs @@ -5,7 +5,8 @@ use serde::Deserialize; use tower_cookies::{Cookie, Cookies}; use crate::{ - admin, database, + database, + public::admin, types::{ extract::{AdminUser, Check, CheckResult, Json}, http::ResponseCode, diff --git a/src/api/image.rs b/src/api/image.rs deleted file mode 100644 index 84eccc7..0000000 --- a/src/api/image.rs +++ /dev/null @@ -1,54 +0,0 @@ -use axum::{extract::Query, response::Response, routing::get, Router, http::StatusCode}; -use serde::Deserialize; - -use crate::types::http::ResponseCode; - - - -#[derive(Deserialize)] -struct AvatarRequest { - user_id: u64, -} - -async fn avatar(params: Option>) -> Response { - - let Some(params) = params else { - return ResponseCode::BadRequest.text("Missing query paramaters"); - }; - - let custom = format!("/image/custom/avatar/{}.png", params.user_id); - let default = format!("/image/default/{}.png", params.user_id % 25); - - let file = ResponseCode::Success.file(&custom).await; - if file.status() != StatusCode::OK { - return ResponseCode::Success.file(&default).await - } - file -} - -#[derive(Deserialize)] -struct BannerRequest { - user_id: u64, -} - -async fn banner(params: Option>) -> Response { - - let Some(params) = params else { - return ResponseCode::BadRequest.text("Missing query paramaters"); - }; - - let custom = format!("/image/custom/banner/{}.png", params.user_id); - - let file = ResponseCode::Success.file(&custom).await; - if file.status() != StatusCode::OK { - return ResponseCode::NotFound.text("User does not have a custom banner") - } - file -} - - -pub fn router() -> Router { - Router::new() - .route("/avatar", get(avatar)) - .route("/banner", get(banner)) -} diff --git a/src/api/mod.rs b/src/api/mod.rs index adc19d7..9efcefc 100644 --- a/src/api/mod.rs +++ b/src/api/mod.rs @@ -1,14 +1,17 @@ use crate::types::extract::RouterURI; -use axum::{Extension, Router, BoxError, error_handling::HandleErrorLayer}; +use axum::{error_handling::HandleErrorLayer, BoxError, Extension, Router}; use tower::ServiceBuilder; -use tower_governor::{governor::GovernorConfigBuilder, GovernorLayer, errors::display_error, key_extractor::SmartIpKeyExtractor}; +use tower_governor::{ + errors::display_error, governor::GovernorConfigBuilder, key_extractor::SmartIpKeyExtractor, + GovernorLayer, +}; -pub mod admin; -pub mod auth; -pub mod pages; -pub mod posts; -pub mod users; -pub mod image; +mod admin; +mod auth; +mod posts; +mod users; + +pub use auth::RegistrationRequet; pub fn router() -> Router { let governor_conf = Box::new( diff --git a/src/api/pages.rs b/src/api/pages.rs deleted file mode 100644 index 4ed2e49..0000000 --- a/src/api/pages.rs +++ /dev/null @@ -1,77 +0,0 @@ -use axum::{ - response::{IntoResponse, Redirect, Response}, - routing::get, - Router, -}; - -use crate::{ - console, - types::{ - extract::{AuthorizedUser, Log}, - http::ResponseCode, - }, -}; - -async fn root(user: Option, _: Log) -> Response { - if user.is_some() { - Redirect::to("/home").into_response() - } else { - Redirect::to("/login").into_response() - } -} - -async fn login(user: Option, _: Log) -> Response { - if user.is_some() { - Redirect::to("/home").into_response() - } else { - ResponseCode::Success.file("/login.html").await - } -} - -async fn home(user: Option, _: Log) -> Response { - if user.is_none() { - Redirect::to("/login").into_response() - } else { - ResponseCode::Success.file("/home.html").await - } -} - -async fn people(user: Option, _: Log) -> Response { - if user.is_none() { - Redirect::to("/login").into_response() - } else { - ResponseCode::Success.file("/people.html").await - } -} - -async fn profile(user: Option, _: Log) -> Response { - if user.is_none() { - Redirect::to("/login").into_response() - } else { - ResponseCode::Success.file("/profile.html").await - } -} - -async fn console() -> Response { - console::generate().await -} - -async fn admin() -> Response { - ResponseCode::Success.file("/admin.html").await -} - -async fn wordpress(_: Log) -> Response { - ResponseCode::ImATeapot.text("Hello i am a teapot owo") -} - -pub fn router() -> Router { - Router::new() - .route("/", get(root)) - .route("/login", get(login)) - .route("/home", get(home)) - .route("/people", get(people)) - .route("/profile", get(profile)) - .route("/console", get(console)) - .route("/wp-admin", get(wordpress)) - .route("/admin", get(admin)) -} diff --git a/src/api/users.rs b/src/api/users.rs index 83a0d4e..e3c992b 100644 --- a/src/api/users.rs +++ b/src/api/users.rs @@ -3,7 +3,11 @@ use crate::types::{ http::ResponseCode, user::User, }; -use axum::{response::Response, routing::{post, put}, Router}; +use axum::{ + response::Response, + routing::{post, put}, + Router, +}; use serde::Deserialize; #[derive(Deserialize)] @@ -64,7 +68,6 @@ async fn load_self(AuthorizedUser(user): AuthorizedUser) -> Response { } async fn avatar(AuthorizedUser(user): AuthorizedUser, Png(img): Png) -> Response { - let path = format!("./public/image/custom/avatar/{}.png", user.user_id); if img.save(path).is_err() { @@ -75,7 +78,6 @@ async fn avatar(AuthorizedUser(user): AuthorizedUser, Png(img): Png) -> Response } async fn banner(AuthorizedUser(user): AuthorizedUser, Png(img): Png) -> Response { - let path = format!("./public/image/custom/banner/{}.png", user.user_id); if img.save(path).is_err() { diff --git a/src/console.rs b/src/console.rs deleted file mode 100644 index 16bf4a3..0000000 --- a/src/console.rs +++ /dev/null @@ -1,264 +0,0 @@ -use axum::{ - http::{Method, Uri}, - response::Response, -}; -use lazy_static::lazy_static; -use serde::Serialize; -use serde_json::{ser::Formatter, Value}; -use std::{collections::VecDeque, io, net::IpAddr}; -use tokio::sync::Mutex; - -use crate::types::http::ResponseCode; - -struct LogMessage { - ip: IpAddr, - method: Method, - uri: Uri, - path: String, - body: String, -} - -impl ToString for LogMessage { - fn to_string(&self) -> String { - let mut ip = self.ip.to_string(); - if ip.contains("::ffff:") { - ip = ip.as_str()[7..].to_string(); - } - let color = match self.method { - Method::GET => "#3fe04f", - Method::POST => "#853fe0", - Method::PATCH => "#e0773f", - Method::PUT => "#e0cb3f", - Method::HEAD => "#3f75e0", - Method::DELETE => "#e04c3f", - Method::CONNECT => "#3fe0ad", - Method::TRACE => "#e03fc5", - Method::OPTIONS => "#423fe0", - _ => "white", - }; - format!("
{} {} {}{} {}
", - ip, color, self.method, self.path, sanatize(&self.uri.to_string()), self.body) - } -} - -lazy_static! { - static ref LOG: Mutex> = Mutex::new(VecDeque::with_capacity(200)); -} - -pub async fn log(ip: IpAddr, method: Method, uri: Uri, path: Option, body: Option) { - let path = path.unwrap_or_default(); - let body = body.unwrap_or_default(); - - if path == "/api/admin" { - return; - } - - tracing::info!("{} {} {}{} {}", &ip, &method, &path, &uri, &body); - - let message = LogMessage { - ip, - method, - uri, - path, - body: beautify(&body), - }; - - let mut lock = LOG.lock().await; - if lock.len() > 200 { - lock.pop_back(); - } - lock.push_front(message); -} - -struct HtmlFormatter; -impl Formatter for HtmlFormatter { - fn write_null(&mut self, writer: &mut W) -> io::Result<()> - where - W: ?Sized + io::Write, - { - writer.write_all(b"null") - } - - fn write_bool(&mut self, writer: &mut W, value: bool) -> io::Result<()> - where - W: ?Sized + io::Write, - { - let s = if value { - b"true" as &[u8] - } else { - b"false" as &[u8] - }; - writer.write_all(s) - } - - fn write_i8(&mut self, writer: &mut W, value: i8) -> io::Result<()> - where - W: ?Sized + io::Write, - { - let buff = format!("{value}"); - writer.write_all(buff.as_bytes()) - } - - fn write_i16(&mut self, writer: &mut W, value: i16) -> io::Result<()> - where - W: ?Sized + io::Write, - { - let buff = format!("{value}"); - writer.write_all(buff.as_bytes()) - } - - fn write_i32(&mut self, writer: &mut W, value: i32) -> io::Result<()> - where - W: ?Sized + io::Write, - { - let buff = format!("{value}"); - writer.write_all(buff.as_bytes()) - } - - fn write_i64(&mut self, writer: &mut W, value: i64) -> io::Result<()> - where - W: ?Sized + io::Write, - { - let buff = format!("{value}"); - writer.write_all(buff.as_bytes()) - } - - fn write_u8(&mut self, writer: &mut W, value: u8) -> io::Result<()> - where - W: ?Sized + io::Write, - { - let buff = format!("{value}"); - writer.write_all(buff.as_bytes()) - } - - fn write_u16(&mut self, writer: &mut W, value: u16) -> io::Result<()> - where - W: ?Sized + io::Write, - { - let buff = format!("{value}"); - writer.write_all(buff.as_bytes()) - } - - fn write_u32(&mut self, writer: &mut W, value: u32) -> io::Result<()> - where - W: ?Sized + io::Write, - { - let buff = format!("{value}"); - writer.write_all(buff.as_bytes()) - } - - fn write_u64(&mut self, writer: &mut W, value: u64) -> io::Result<()> - where - W: ?Sized + io::Write, - { - let buff = format!("{value}"); - writer.write_all(buff.as_bytes()) - } - - fn write_f32(&mut self, writer: &mut W, value: f32) -> io::Result<()> - where - W: ?Sized + io::Write, - { - let buff = format!("{value}"); - writer.write_all(buff.as_bytes()) - } - - fn write_f64(&mut self, writer: &mut W, value: f64) -> io::Result<()> - where - W: ?Sized + io::Write, - { - let buff = format!("{value}"); - writer.write_all(buff.as_bytes()) - } - - fn begin_string(&mut self, writer: &mut W) -> io::Result<()> - where - W: ?Sized + io::Write, - { - writer.write_all(b"\"") - } - - fn end_string(&mut self, writer: &mut W) -> io::Result<()> - where - W: ?Sized + io::Write, - { - writer.write_all(b"\"") - } - - fn begin_object_key(&mut self, writer: &mut W, first: bool) -> io::Result<()> - where - W: ?Sized + io::Write, - { - if first { - writer.write_all(b"") - } else { - writer.write_all(b",") - } - } - - fn end_object_key(&mut self, writer: &mut W) -> io::Result<()> - where - W: ?Sized + io::Write, - { - writer.write_all(b"") - } -} - -pub fn sanatize(input: &str) -> String { - input - .replace('&', "&") - .replace('<', "<") - .replace('>', ">") -} - -pub fn beautify(body: &str) -> String { - let body = sanatize(body); - - if body.is_empty() { - return String::new(); - } - let Ok(mut json) = serde_json::from_str::(&body) else { - return body - }; - if json["password"].is_string() { - json["password"] = Value::String("********".to_owned()); - } - let mut writer: Vec = Vec::with_capacity(128); - let mut serializer = serde_json::Serializer::with_formatter(&mut writer, HtmlFormatter); - if json.serialize(&mut serializer).is_err() { - return body; - } - String::from_utf8_lossy(&writer).to_string() -} - -pub async fn generate() -> Response { - let lock = LOG.lock().await; - - let mut html = r#" - - - - - - - - - XSSBook - Console - - - -
- "# - .to_string(); - - for message in lock.iter() { - html.push_str(&message.to_string()); - } - - html.push_str(""); - - ResponseCode::Success.html(&html) -} diff --git a/src/database/users.rs b/src/database/users.rs index 27c3b7f..8045bc4 100644 --- a/src/database/users.rs +++ b/src/database/users.rs @@ -2,7 +2,7 @@ use rusqlite::{OptionalExtension, Row}; use std::time::{Duration, SystemTime, UNIX_EPOCH}; use tracing::instrument; -use crate::{api::auth::RegistrationRequet, database, types::user::User}; +use crate::{api::RegistrationRequet, database, types::user::User}; pub fn init() -> Result<(), rusqlite::Error> { let sql = " diff --git a/src/main.rs b/src/main.rs index e8f17b0..8a8e9d8 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,38 +1,25 @@ use axum::{ body::HttpBody, - http::{Request, StatusCode}, + extract::DefaultBodyLimit, + http::Request, middleware::{self, Next}, response::Response, - RequestExt, Router, extract::DefaultBodyLimit, + RequestExt, Router, }; -use std::{net::SocketAddr, process::exit, fs}; +use public::console; +use std::{fs, net::SocketAddr, process::exit}; use tower_cookies::CookieManagerLayer; use tracing::{error, info, metadata::LevelFilter}; use tracing_subscriber::{ filter::filter_fn, prelude::__tracing_subscriber_SubscriberExt, util::SubscriberInitExt, Layer, }; -use types::{http::ResponseCode, extract::RequestIp}; +use types::extract::RequestIp; -use crate::api::{pages, image}; - -mod admin; mod api; -mod console; mod database; +mod public; mod types; -async fn serve(req: Request, next: Next) -> Response -where - B: Send + Sync + 'static + HttpBody, -{ - let uri = req.uri(); - let file = ResponseCode::Success.file(&uri.to_string()).await; - if file.status() != StatusCode::OK { - return next.run(req).await; - } - file -} - async fn log(mut req: Request, next: Next) -> Response where B: Send + Sync + 'static + HttpBody, @@ -47,7 +34,7 @@ where } async fn not_found() -> Response { - ResponseCode::NotFound.file("/404.html").await + public::serve("/404.html").await } #[tokio::main] @@ -69,16 +56,16 @@ async fn main() { }; fs::create_dir_all("./public/image/custom").expect("Coudn't make custom data directory"); - fs::create_dir_all("./public/image/custom/avatar").expect("Coudn't make custom avatar directory"); - fs::create_dir_all("./public/image/custom/banner").expect("Coudn't make custom banner directory"); + fs::create_dir_all("./public/image/custom/avatar") + .expect("Coudn't make custom avatar directory"); + fs::create_dir_all("./public/image/custom/banner") + .expect("Coudn't make custom banner directory"); let app = Router::new() .fallback(not_found) .layer(middleware::from_fn(log)) - .layer(middleware::from_fn(serve)) - .nest("/", pages::router()) + .nest("/", public::router()) .nest("/api", api::router()) - .nest("/cdn", image::router()) .layer(CookieManagerLayer::new()) .layer(DefaultBodyLimit::max(512_000)); diff --git a/src/public/admin.rs b/src/public/admin.rs new file mode 100644 index 0000000..1da2f1e --- /dev/null +++ b/src/public/admin.rs @@ -0,0 +1,129 @@ +use axum::response::Response; +use lazy_static::lazy_static; +use rand::{distributions::Alphanumeric, Rng}; +use tokio::sync::Mutex; + +use crate::{ + console::{self, sanatize}, + types::{http::ResponseCode, post::Post, session::Session, user::User}, +}; + +lazy_static! { + static ref SECRET: Mutex = Mutex::new(String::new()); +} + +pub fn new_secret() -> String { + rand::thread_rng() + .sample_iter(&Alphanumeric) + .take(32) + .map(char::from) + .collect() +} + +pub async fn get_secret() -> String { + let mut secret = SECRET.lock().await; + if secret.is_empty() { + *secret = new_secret(); + } + secret.clone() +} + +pub async fn regen_secret() -> String { + let mut secret = SECRET.lock().await; + *secret = new_secret(); + secret.clone() +} + +pub fn generate_users() -> Response { + let users = match User::reterieve_all() { + Ok(users) => users, + Err(err) => return err, + }; + + let mut html = r#" + + User ID + First Name + Last Name + Email + Password + Gender + Date + Day + Month + Year + + "# + .to_string(); + + for user in users { + html.push_str( + &format!("{}{}{}{}{}{}{}{}{}{}", + user.user_id, sanatize(&user.firstname), sanatize(&user.lastname), sanatize(&user.email), sanatize(&user.password), + sanatize(&user.gender), user.date, user.day, user.month, user.year + ) + ); + } + + ResponseCode::Success.text(&html) +} + +pub fn generate_posts() -> Response { + let posts = match Post::reterieve_all() { + Ok(posts) => posts, + Err(err) => return err, + }; + + let mut html = r#" + + Post ID + User ID + Content + Likes + Comments + Date + + "# + .to_string(); + + for post in posts { + let Ok(likes) = serde_json::to_string(&post.likes) else { continue }; + let Ok(comments) = serde_json::to_string(&post.comments) else { continue }; + + html.push_str(&format!( + "{}{}{}{}{}{}", + post.post_id, + post.user_id, + sanatize(&post.content), + console::beautify(&likes), + console::beautify(&comments), + post.date + )); + } + + ResponseCode::Success.text(&html) +} + +pub fn generate_sessions() -> Response { + let sessions = match Session::reterieve_all() { + Ok(sessions) => sessions, + Err(err) => return err, + }; + + let mut html = r#" + + User ID + Token + + "# + .to_string(); + + for session in sessions { + html.push_str(&format!( + "{}{}", + session.user_id, session.token + )); + } + + ResponseCode::Success.text(&html) +} diff --git a/src/public/console.rs b/src/public/console.rs new file mode 100644 index 0000000..16bf4a3 --- /dev/null +++ b/src/public/console.rs @@ -0,0 +1,264 @@ +use axum::{ + http::{Method, Uri}, + response::Response, +}; +use lazy_static::lazy_static; +use serde::Serialize; +use serde_json::{ser::Formatter, Value}; +use std::{collections::VecDeque, io, net::IpAddr}; +use tokio::sync::Mutex; + +use crate::types::http::ResponseCode; + +struct LogMessage { + ip: IpAddr, + method: Method, + uri: Uri, + path: String, + body: String, +} + +impl ToString for LogMessage { + fn to_string(&self) -> String { + let mut ip = self.ip.to_string(); + if ip.contains("::ffff:") { + ip = ip.as_str()[7..].to_string(); + } + let color = match self.method { + Method::GET => "#3fe04f", + Method::POST => "#853fe0", + Method::PATCH => "#e0773f", + Method::PUT => "#e0cb3f", + Method::HEAD => "#3f75e0", + Method::DELETE => "#e04c3f", + Method::CONNECT => "#3fe0ad", + Method::TRACE => "#e03fc5", + Method::OPTIONS => "#423fe0", + _ => "white", + }; + format!("
{} {} {}{} {}
", + ip, color, self.method, self.path, sanatize(&self.uri.to_string()), self.body) + } +} + +lazy_static! { + static ref LOG: Mutex> = Mutex::new(VecDeque::with_capacity(200)); +} + +pub async fn log(ip: IpAddr, method: Method, uri: Uri, path: Option, body: Option) { + let path = path.unwrap_or_default(); + let body = body.unwrap_or_default(); + + if path == "/api/admin" { + return; + } + + tracing::info!("{} {} {}{} {}", &ip, &method, &path, &uri, &body); + + let message = LogMessage { + ip, + method, + uri, + path, + body: beautify(&body), + }; + + let mut lock = LOG.lock().await; + if lock.len() > 200 { + lock.pop_back(); + } + lock.push_front(message); +} + +struct HtmlFormatter; +impl Formatter for HtmlFormatter { + fn write_null(&mut self, writer: &mut W) -> io::Result<()> + where + W: ?Sized + io::Write, + { + writer.write_all(b"null") + } + + fn write_bool(&mut self, writer: &mut W, value: bool) -> io::Result<()> + where + W: ?Sized + io::Write, + { + let s = if value { + b"true" as &[u8] + } else { + b"false" as &[u8] + }; + writer.write_all(s) + } + + fn write_i8(&mut self, writer: &mut W, value: i8) -> io::Result<()> + where + W: ?Sized + io::Write, + { + let buff = format!("{value}"); + writer.write_all(buff.as_bytes()) + } + + fn write_i16(&mut self, writer: &mut W, value: i16) -> io::Result<()> + where + W: ?Sized + io::Write, + { + let buff = format!("{value}"); + writer.write_all(buff.as_bytes()) + } + + fn write_i32(&mut self, writer: &mut W, value: i32) -> io::Result<()> + where + W: ?Sized + io::Write, + { + let buff = format!("{value}"); + writer.write_all(buff.as_bytes()) + } + + fn write_i64(&mut self, writer: &mut W, value: i64) -> io::Result<()> + where + W: ?Sized + io::Write, + { + let buff = format!("{value}"); + writer.write_all(buff.as_bytes()) + } + + fn write_u8(&mut self, writer: &mut W, value: u8) -> io::Result<()> + where + W: ?Sized + io::Write, + { + let buff = format!("{value}"); + writer.write_all(buff.as_bytes()) + } + + fn write_u16(&mut self, writer: &mut W, value: u16) -> io::Result<()> + where + W: ?Sized + io::Write, + { + let buff = format!("{value}"); + writer.write_all(buff.as_bytes()) + } + + fn write_u32(&mut self, writer: &mut W, value: u32) -> io::Result<()> + where + W: ?Sized + io::Write, + { + let buff = format!("{value}"); + writer.write_all(buff.as_bytes()) + } + + fn write_u64(&mut self, writer: &mut W, value: u64) -> io::Result<()> + where + W: ?Sized + io::Write, + { + let buff = format!("{value}"); + writer.write_all(buff.as_bytes()) + } + + fn write_f32(&mut self, writer: &mut W, value: f32) -> io::Result<()> + where + W: ?Sized + io::Write, + { + let buff = format!("{value}"); + writer.write_all(buff.as_bytes()) + } + + fn write_f64(&mut self, writer: &mut W, value: f64) -> io::Result<()> + where + W: ?Sized + io::Write, + { + let buff = format!("{value}"); + writer.write_all(buff.as_bytes()) + } + + fn begin_string(&mut self, writer: &mut W) -> io::Result<()> + where + W: ?Sized + io::Write, + { + writer.write_all(b"\"") + } + + fn end_string(&mut self, writer: &mut W) -> io::Result<()> + where + W: ?Sized + io::Write, + { + writer.write_all(b"\"") + } + + fn begin_object_key(&mut self, writer: &mut W, first: bool) -> io::Result<()> + where + W: ?Sized + io::Write, + { + if first { + writer.write_all(b"") + } else { + writer.write_all(b",") + } + } + + fn end_object_key(&mut self, writer: &mut W) -> io::Result<()> + where + W: ?Sized + io::Write, + { + writer.write_all(b"") + } +} + +pub fn sanatize(input: &str) -> String { + input + .replace('&', "&") + .replace('<', "<") + .replace('>', ">") +} + +pub fn beautify(body: &str) -> String { + let body = sanatize(body); + + if body.is_empty() { + return String::new(); + } + let Ok(mut json) = serde_json::from_str::(&body) else { + return body + }; + if json["password"].is_string() { + json["password"] = Value::String("********".to_owned()); + } + let mut writer: Vec = Vec::with_capacity(128); + let mut serializer = serde_json::Serializer::with_formatter(&mut writer, HtmlFormatter); + if json.serialize(&mut serializer).is_err() { + return body; + } + String::from_utf8_lossy(&writer).to_string() +} + +pub async fn generate() -> Response { + let lock = LOG.lock().await; + + let mut html = r#" + + + + + + + + + XSSBook - Console + + + +
+ "# + .to_string(); + + for message in lock.iter() { + html.push_str(&message.to_string()); + } + + html.push_str(""); + + ResponseCode::Success.html(&html) +} diff --git a/src/public/file.rs b/src/public/file.rs new file mode 100644 index 0000000..b54ef25 --- /dev/null +++ b/src/public/file.rs @@ -0,0 +1,69 @@ +use axum::{ + extract::{Path, Query}, + http::StatusCode, + response::Response, +}; +use serde::Deserialize; + +use crate::types::http::ResponseCode; + +use super::console; + +pub async fn js(Path(path): Path) -> Response { + let path = format!("/js/{}", path); + super::serve(&path).await +} + +pub async fn css(Path(path): Path) -> Response { + let path = format!("/css/{}", path); + super::serve(&path).await +} + +pub async fn fonts(Path(path): Path) -> Response { + let path = format!("/fonts/{}", path); + super::serve(&path).await +} + +pub async fn image(Path(path): Path) -> Response { + let path = format!("/image/{}", path); + super::serve(&path).await +} + +#[derive(Deserialize)] +pub struct AvatarRequest { + user_id: u64, +} + +pub async fn avatar(params: Option>) -> Response { + let Some(params) = params else { + return ResponseCode::BadRequest.text("Missing query paramaters"); + }; + + let custom = format!("/image/custom/avatar/{}.png", params.user_id); + let default = format!("/image/default/{}.png", params.user_id % 25); + + let file = super::serve(&custom).await; + if file.status() != StatusCode::OK { + return super::serve(&default).await; + } + file +} + +#[derive(Deserialize)] +pub struct BannerRequest { + user_id: u64, +} + +pub async fn banner(params: Option>) -> Response { + let Some(params) = params else { + return ResponseCode::BadRequest.text("Missing query paramaters"); + }; + + let custom = format!("/image/custom/banner/{}.png", params.user_id); + + let file = super::serve(&custom).await; + if file.status() != StatusCode::OK { + return ResponseCode::NotFound.text("User does not have a custom banner"); + } + file +} diff --git a/src/public/mod.rs b/src/public/mod.rs new file mode 100644 index 0000000..cf8156d --- /dev/null +++ b/src/public/mod.rs @@ -0,0 +1,47 @@ +use axum::{ + body::Body, + http::{Request, StatusCode}, + response::{IntoResponse, Response}, + routing::get, + Router, +}; +use tower::ServiceExt; +use tower_http::services::ServeFile; + +use crate::types::http::ResponseCode; + +pub mod admin; +pub mod console; +pub mod file; +pub mod pages; + +pub fn router() -> Router { + Router::new() + .nest("/", pages::router()) + .route("/js/*path", get(file::js)) + .route("/css/*path", get(file::css)) + .route("/fonts/*path", get(file::fonts)) + .route("/image/*path", get(file::image)) + .route("/image/avatar", get(file::avatar)) + .route("/image/banner", get(file::banner)) +} + +pub async fn serve(path: &str) -> Response { + if !path.chars().any(|c| c == '.') { + return ResponseCode::BadRequest.text("Invalid file path"); + } + + let path = format!("public{path}"); + let file = ServeFile::new(path); + + let Ok(res) = file.oneshot(Request::new(Body::empty())).await else { + tracing::error!("Error while fetching file"); + return ResponseCode::InternalServerError.text("Error while fetching file"); + }; + + if res.status() != StatusCode::OK { + return ResponseCode::NotFound.text("File not found"); + } + + res.into_response() +} diff --git a/src/public/pages.rs b/src/public/pages.rs new file mode 100644 index 0000000..1614d81 --- /dev/null +++ b/src/public/pages.rs @@ -0,0 +1,65 @@ +use axum::{ + response::{IntoResponse, Redirect, Response}, + routing::get, + Router, +}; + +use crate::{ + public::console, + types::{ + extract::{AuthorizedUser, Log}, + http::ResponseCode, + }, +}; + +async fn root(user: Option, _: Log) -> Response { + if user.is_some() { + Redirect::to("/home").into_response() + } else { + Redirect::to("/login").into_response() + } +} + +async fn login(user: Option, _: Log) -> Response { + if user.is_some() { + Redirect::to("/home").into_response() + } else { + super::serve("/login.html").await + } +} + +async fn home(_: Log) -> Response { + super::serve("/home.html").await +} + +async fn people(_: Log) -> Response { + super::serve("/people.html").await +} + +async fn profile(_: Log) -> Response { + super::serve("/profile.html").await +} + +async fn console() -> Response { + console::generate().await +} + +async fn admin() -> Response { + super::serve("/admin.html").await +} + +async fn wordpress(_: Log) -> Response { + ResponseCode::ImATeapot.text("Hello i am a teapot owo") +} + +pub fn router() -> Router { + Router::new() + .route("/", get(root)) + .route("/login", get(login)) + .route("/home", get(home)) + .route("/people", get(people)) + .route("/profile", get(profile)) + .route("/console", get(console)) + .route("/wp-admin", get(wordpress)) + .route("/admin", get(admin)) +} diff --git a/src/types/extract.rs b/src/types/extract.rs index 4d7ac51..1258ef9 100644 --- a/src/types/extract.rs +++ b/src/types/extract.rs @@ -1,20 +1,24 @@ -use std::{io::{Read, Cursor}, net::{IpAddr, SocketAddr}}; +use std::{ + io::{Cursor, Read}, + net::{IpAddr, SocketAddr}, +}; use axum::{ async_trait, body::HttpBody, - extract::{FromRequest, FromRequestParts, ConnectInfo}, + extract::{ConnectInfo, FromRequest, FromRequestParts}, http::{request::Parts, Request}, response::Response, BoxError, RequestExt, }; use bytes::Bytes; -use image::{io::Reader, ImageFormat, DynamicImage}; +use image::{io::Reader, DynamicImage, ImageFormat}; use serde::de::DeserializeOwned; use tower_cookies::Cookies; use crate::{ - admin, console, + public::admin, + public::console, types::{ http::{ResponseCode, Result}, session::Session, @@ -32,41 +36,43 @@ where type Rejection = Response; async fn from_request_parts(parts: &mut Parts, _state: &S) -> Result { - let headers = &parts.headers; - let forwardedfor = headers.get("x-forwarded-for") - .and_then(|h| h.to_str().ok()) - .and_then(|h| - h.split(',') - .rev() - .find_map(|s| s.trim().parse::().ok()) - ); + let forwardedfor = headers + .get("x-forwarded-for") + .and_then(|h| h.to_str().ok()) + .and_then(|h| { + h.split(',') + .rev() + .find_map(|s| s.trim().parse::().ok()) + }); if let Some(forwardedfor) = forwardedfor { - return Ok(RequestIp(forwardedfor)) + return Ok(RequestIp(forwardedfor)); } - let realip = headers.get("x-real-ip") - .and_then(|hv| hv.to_str().ok()) - .and_then(|s| s.parse::().ok()); + let realip = headers + .get("x-real-ip") + .and_then(|hv| hv.to_str().ok()) + .and_then(|s| s.parse::().ok()); if let Some(realip) = realip { - return Ok(RequestIp(realip)) + return Ok(RequestIp(realip)); } - let realip = headers.get("x-real-ip") - .and_then(|hv| hv.to_str().ok()) - .and_then(|s| s.parse::().ok()); + let realip = headers + .get("x-real-ip") + .and_then(|hv| hv.to_str().ok()) + .and_then(|s| s.parse::().ok()); if let Some(realip) = realip { - return Ok(RequestIp(realip)) + return Ok(RequestIp(realip)); } let info = parts.extensions.get::>(); if let Some(info) = info { - return Ok(RequestIp(info.0.ip())) + return Ok(RequestIp(info.0.ip())); } Err(ResponseCode::Forbidden.text("You have no ip")) @@ -163,7 +169,6 @@ where type Rejection = Response; async fn from_request(req: Request, state: &S) -> Result { - let bytes = match read_body(req, state).await { Ok(body) => body, Err(err) => return Err(err), @@ -171,7 +176,7 @@ where let mut reader = Reader::new(Cursor::new(bytes)); reader.set_format(ImageFormat::Png); - + let Ok(img) = reader.decode() else { return Err(ResponseCode::BadRequest.text("Failed to decode png image")) }; @@ -238,7 +243,6 @@ where B::Error: Into, S: Send + Sync, { - let Ok(RequestIp(ip)) = req.extract_parts::().await else { tracing::error!("Failed to read client ip"); return Err(ResponseCode::InternalServerError.text("Failed to read client ip")); @@ -255,14 +259,7 @@ where return Err(ResponseCode::BadRequest.text("Request can be at most 512kb")); }; - console::log( - ip, - method, - uri, - Some(path.to_string()), - None, - ) - .await; + console::log(ip, method, uri, Some(path.to_string()), None).await; Ok(bytes.bytes().flatten().collect()) } diff --git a/src/types/http.rs b/src/types/http.rs index 8524b15..06674ca 100644 --- a/src/types/http.rs +++ b/src/types/http.rs @@ -1,11 +1,8 @@ use axum::{ - body::Body, headers::HeaderName, - http::{HeaderValue, Request, StatusCode}, + http::{HeaderValue, StatusCode}, response::{IntoResponse, Response}, }; -use tower::ServiceExt; -use tower_http::services::ServeFile; use tracing::instrument; #[derive(Debug)] @@ -58,24 +55,6 @@ impl ResponseCode { ); res } - - #[instrument()] - pub async fn file(self, path: &str) -> Response { - if !path.chars().any(|c| c == '.') { - return Self::BadRequest.text("Folders cannot be served"); - } - let path = format!("public{path}"); - let svc = ServeFile::new(path); - let Ok(mut res) = svc.oneshot(Request::new(Body::empty())).await else { - tracing::error!("Error while fetching file"); - return Self::InternalServerError.text("Error while fetching file"); - }; - if res.status() != StatusCode::OK { - return Self::NotFound.text("File not found"); - } - *res.status_mut() = self.code(); - res.into_response() - } } pub type Result = std::result::Result; diff --git a/src/types/user.rs b/src/types/user.rs index 2bffa52..835b675 100644 --- a/src/types/user.rs +++ b/src/types/user.rs @@ -1,7 +1,7 @@ use serde::{Deserialize, Serialize}; use tracing::instrument; -use crate::api::auth::RegistrationRequet; +use crate::api::RegistrationRequet; use crate::database; use crate::types::http::{ResponseCode, Result}; -- cgit v1.2.3-freya