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) }