use crate::{Request, Response};
use bytes::Bytes;
#[derive(Clone, Debug)]
pub struct RedirectRecord {
pub should_redirect: bool,
pub next: Option<http::Uri>,
}
#[derive(Debug, Default, Clone)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
pub struct HTTPRecord {
#[cfg_attr(
feature = "schema",
schemars(
title = "HTTP request",
description = "Structured representation of the HTTP request",
example = r#"{
"method": "GET",
"uri": "https://example.com/api",
"headers": {"Accept": "application/json"}
}"#
)
)]
pub request: Request,
#[cfg_attr(
feature = "serde",
serde(
with = "crate::serde_schema::bytes_serde",
skip_serializing_if = "Bytes::is_empty"
)
)]
#[cfg_attr(
feature = "schema",
schemars(
with = "Bytes",
title = "raw request bytes",
description = "Original raw bytes of the HTTP request for debugging purposes",
example = "GET /api HTTP/1.1\r\nHost: example.com\r\nAccept: application/json\r\n\r\n"
)
)]
pub raw_request: Bytes,
#[cfg_attr(
feature = "schema",
schemars(
title = "HTTP response",
description = "Structured representation of the HTTP response",
example = r#"{
"status_code": 200,
"headers": {"Content-Type": "application/json"},
"body": "{\"data\":\"value\"}"
}"#
)
)]
pub response: Response,
#[cfg_attr(
feature = "serde",
serde(
with = "crate::serde_schema::bytes_serde",
skip_serializing_if = "Bytes::is_empty"
)
)]
#[cfg_attr(
feature = "schema",
schemars(
with = "Bytes",
title = "raw response bytes",
description = "Original raw bytes of the HTTP response for debugging purposes",
example = "HTTP/1.1 200 OK\r\nContent-Type: application/json\r\n\r\n{\"data\":\"value\"}"
)
)]
pub raw_response: Bytes,
}
impl HTTPRecord {
pub(crate) fn record_request(&mut self, irq: &Request) {
self.raw_request = Bytes::from(irq);
self.request = irq.clone();
}
pub(crate) fn record_response(&mut self, irp: &Response) {
self.raw_response = Bytes::from(irp);
self.response = irp.clone();
}
}
#[derive(Clone, Debug)]
pub struct CommandRecord {
pub command: String,
}
impl From<&Request> for CommandRecord {
fn from(value: &Request) -> Self {
let uri = value.uri();
let https = uri.scheme() == Some(&http::uri::Scheme::HTTPS);
let host = uri.host().unwrap_or("127.0.0.1");
let port = uri
.port_u16()
.unwrap_or(if https { 443 } else { 80 })
.to_string();
let raw = Bytes::from(value);
let command = if let Some(_raw) = value.raw_request() {
let lines = raw
.split(|b| b == &0xA)
.map(|line| line.strip_suffix(&[0xD]).unwrap_or(line))
.map(|line| [line, &[0xD, 0xA]].concat());
let mut command = String::from("printf");
command.push(' ');
for line in lines {
command.push_str(&bash_escape(
&Bytes::from(line.to_vec()).escape_ascii().to_string(),
));
command.push_str("\\\r\n");
}
command.push('|');
let mut nc_cmd = vec!["ncat"];
if https {
nc_cmd.push("--ssl")
}
nc_cmd.push(host);
nc_cmd.push(&port);
command.push_str(&nc_cmd.join(" "));
CommandRecord { command }
} else {
let mut curl_cmd = vec!["curl"];
curl_cmd.push("-X");
curl_cmd.push(value.method().as_str());
if https {
curl_cmd.push("-k");
}
curl_cmd.push("--compressed\\\r\n");
let mut command = curl_cmd.join(" ");
for (k, v) in value.headers() {
command.push_str(" -H ");
let header = format!("{}: {}", k.as_str(), v.to_str().unwrap_or_default());
command.push_str(&bash_escape(&header));
command.push_str("\\\r\n");
}
if let Some(body) = value.body() {
command.push_str(" -d ");
command.push_str(&format!(
"'{}'\\\r\n",
Bytes::from(body.to_vec()).escape_ascii()
));
}
command.push(' ');
command.push_str(&bash_escape(&uri.to_string()));
CommandRecord { command }
};
command
}
}
impl std::fmt::Display for CommandRecord {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
std::fmt::Display::fmt(&self.command, f)
}
}
fn bash_escape(s: &str) -> String {
format!("'{}'", s.replace('\'', "\\'"))
}