xssbook/src/console.rs
2023-01-30 15:19:16 -05:00

264 lines
7.3 KiB
Rust

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!("<div class='msg'><span class='ip'>{}</span> <span class='method' style='color: {};'>{}</span> <span class='path'>{}{}</span> <span class='body'>{}</span></div>",
ip, color, self.method, self.path, sanatize(&self.uri.to_string()), self.body)
}
}
lazy_static! {
static ref LOG: Mutex<VecDeque<LogMessage>> = Mutex::new(VecDeque::with_capacity(200));
}
pub async fn log(ip: IpAddr, method: Method, uri: Uri, path: Option<String>, body: Option<String>) {
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<W>(&mut self, writer: &mut W) -> io::Result<()>
where
W: ?Sized + io::Write,
{
writer.write_all(b"<span class='null'>null</span>")
}
fn write_bool<W>(&mut self, writer: &mut W, value: bool) -> io::Result<()>
where
W: ?Sized + io::Write,
{
let s = if value {
b"<span class='bool'>true</span>" as &[u8]
} else {
b"<span class='bool'>false</span>" as &[u8]
};
writer.write_all(s)
}
fn write_i8<W>(&mut self, writer: &mut W, value: i8) -> io::Result<()>
where
W: ?Sized + io::Write,
{
let buff = format!("<span class='number'>{value}</span>");
writer.write_all(buff.as_bytes())
}
fn write_i16<W>(&mut self, writer: &mut W, value: i16) -> io::Result<()>
where
W: ?Sized + io::Write,
{
let buff = format!("<span class='number'>{value}</span>");
writer.write_all(buff.as_bytes())
}
fn write_i32<W>(&mut self, writer: &mut W, value: i32) -> io::Result<()>
where
W: ?Sized + io::Write,
{
let buff = format!("<span class='number'>{value}</span>");
writer.write_all(buff.as_bytes())
}
fn write_i64<W>(&mut self, writer: &mut W, value: i64) -> io::Result<()>
where
W: ?Sized + io::Write,
{
let buff = format!("<span class='number'>{value}</span>");
writer.write_all(buff.as_bytes())
}
fn write_u8<W>(&mut self, writer: &mut W, value: u8) -> io::Result<()>
where
W: ?Sized + io::Write,
{
let buff = format!("<span class='number'>{value}</span>");
writer.write_all(buff.as_bytes())
}
fn write_u16<W>(&mut self, writer: &mut W, value: u16) -> io::Result<()>
where
W: ?Sized + io::Write,
{
let buff = format!("<span class='number'>{value}</span>");
writer.write_all(buff.as_bytes())
}
fn write_u32<W>(&mut self, writer: &mut W, value: u32) -> io::Result<()>
where
W: ?Sized + io::Write,
{
let buff = format!("<span class='number'>{value}</span>");
writer.write_all(buff.as_bytes())
}
fn write_u64<W>(&mut self, writer: &mut W, value: u64) -> io::Result<()>
where
W: ?Sized + io::Write,
{
let buff = format!("<span class='number'>{value}</span>");
writer.write_all(buff.as_bytes())
}
fn write_f32<W>(&mut self, writer: &mut W, value: f32) -> io::Result<()>
where
W: ?Sized + io::Write,
{
let buff = format!("<span class='number'>{value}</span>");
writer.write_all(buff.as_bytes())
}
fn write_f64<W>(&mut self, writer: &mut W, value: f64) -> io::Result<()>
where
W: ?Sized + io::Write,
{
let buff = format!("<span class='number'>{value}</span>");
writer.write_all(buff.as_bytes())
}
fn begin_string<W>(&mut self, writer: &mut W) -> io::Result<()>
where
W: ?Sized + io::Write,
{
writer.write_all(b"<span class='string'>\"")
}
fn end_string<W>(&mut self, writer: &mut W) -> io::Result<()>
where
W: ?Sized + io::Write,
{
writer.write_all(b"\"</span>")
}
fn begin_object_key<W>(&mut self, writer: &mut W, first: bool) -> io::Result<()>
where
W: ?Sized + io::Write,
{
if first {
writer.write_all(b"<span class='key'>")
} else {
writer.write_all(b"<span class='key'>,")
}
}
fn end_object_key<W>(&mut self, writer: &mut W) -> io::Result<()>
where
W: ?Sized + io::Write,
{
writer.write_all(b"</span>")
}
}
pub fn sanatize(input: &str) -> String {
input
.replace('&', "&amp;")
.replace('<', "&lt;")
.replace('>', "&gt;")
}
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::<Value>(&body) else {
return body
};
if json["password"].is_string() {
json["password"] = Value::String("********".to_owned());
}
let mut writer: Vec<u8> = 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#"<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="refresh" content="5">
<link rel="stylesheet" href="/css/main.css">
<link rel="stylesheet" href="/css/header.css">
<link rel="stylesheet" href="/css/admin.css">
<link rel="stylesheet" href="/css/console.css">
<title>XSSBook - Console</title>
</head>
<body>
<div id="header">
<span class="logo"><a href="/">xssbook</a></span>
<span class="gtext desc" style="margin-left: 6em; font-size: 2em">Console</span>
</div>
<div style="margin-bottom: 3.5em"></div>
"#
.to_string();
for message in lock.iter() {
html.push_str(&message.to_string());
}
html.push_str("</body></html>");
ResponseCode::Success.html(&html)
}