use crate::error::Result;
use bytes::Bytes;
use slinger::{Body, Request, Response};
use std::fmt;
use std::net::SocketAddr;
use std::sync::Arc;
use std::time::{SystemTime, UNIX_EPOCH};
#[derive(Clone)]
pub struct MitmRequest {
pub source: Option<SocketAddr>,
pub destination: String,
pub timestamp: u64,
is_http: bool,
pub request: Request,
}
impl MitmRequest {
pub fn new(destination: impl Into<String>, request: Request) -> Self {
Self {
source: None,
destination: destination.into(),
timestamp: SystemTime::now()
.duration_since(UNIX_EPOCH)
.map(|d| d.as_millis() as u64)
.unwrap_or(0),
is_http: true,
request,
}
}
pub fn with_source(source: SocketAddr, destination: impl Into<String>, request: Request) -> Self {
Self {
source: Some(source),
destination: destination.into(),
timestamp: SystemTime::now()
.duration_since(UNIX_EPOCH)
.map(|d| d.as_millis() as u64)
.unwrap_or(0),
is_http: true,
request,
}
}
pub fn raw_tcp(destination: impl Into<String>, body: impl Into<Bytes>) -> Self {
let request = Request {
body: Some(Body::from(body.into())),
..Default::default()
};
Self {
source: None,
destination: destination.into(),
timestamp: SystemTime::now()
.duration_since(UNIX_EPOCH)
.map(|d| d.as_millis() as u64)
.unwrap_or(0),
is_http: false,
request,
}
}
pub fn raw_tcp_with_source(
source: SocketAddr,
destination: impl Into<String>,
body: impl Into<Bytes>,
) -> Self {
let request = Request {
body: Some(Body::from(body.into())),
..Default::default()
};
Self {
source: Some(source),
destination: destination.into(),
timestamp: SystemTime::now()
.duration_since(UNIX_EPOCH)
.map(|d| d.as_millis() as u64)
.unwrap_or(0),
is_http: false,
request,
}
}
pub fn source(&self) -> Option<SocketAddr> {
self.source
}
pub fn destination(&self) -> &str {
&self.destination
}
pub fn timestamp(&self) -> u64 {
self.timestamp
}
pub fn request(&self) -> &Request {
&self.request
}
pub fn request_mut(&mut self) -> &mut Request {
&mut self.request
}
pub fn body(&self) -> Option<&Body> {
self.request.body.as_ref()
}
pub fn set_body(&mut self, body: impl Into<Bytes>) {
self.request.body = Some(Body::from(body.into()));
}
pub fn is_http(&self) -> bool {
self.is_http
}
}
impl fmt::Debug for MitmRequest {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("MitmRequest")
.field("source", &self.source)
.field("destination", &self.destination)
.field("timestamp", &self.timestamp)
.field("is_http", &self.is_http())
.field("request", &self.request)
.finish()
}
}
#[derive(Clone)]
pub struct MitmResponse {
pub source: String,
pub destination: Option<SocketAddr>,
pub timestamp: u64,
is_http: bool,
pub response: Response,
}
impl MitmResponse {
pub fn new(source: impl Into<String>, response: Response) -> Self {
Self {
source: source.into(),
destination: None,
timestamp: SystemTime::now()
.duration_since(UNIX_EPOCH)
.map(|d| d.as_millis() as u64)
.unwrap_or(0),
is_http: true,
response,
}
}
pub fn with_destination(
source: impl Into<String>,
destination: SocketAddr,
response: Response,
) -> Self {
Self {
source: source.into(),
destination: Some(destination),
timestamp: SystemTime::now()
.duration_since(UNIX_EPOCH)
.map(|d| d.as_millis() as u64)
.unwrap_or(0),
is_http: true,
response,
}
}
pub fn raw_tcp(source: impl Into<String>, body: impl Into<Bytes>) -> Self {
let response = Response {
body: Some(Body::from(body.into())),
..Default::default()
};
Self {
source: source.into(),
destination: None,
timestamp: SystemTime::now()
.duration_since(UNIX_EPOCH)
.map(|d| d.as_millis() as u64)
.unwrap_or(0),
is_http: false,
response,
}
}
pub fn raw_tcp_with_destination(
source: impl Into<String>,
destination: SocketAddr,
body: impl Into<Bytes>,
) -> Self {
let response = Response {
body: Some(Body::from(body.into())),
..Default::default()
};
Self {
source: source.into(),
destination: Some(destination),
timestamp: SystemTime::now()
.duration_since(UNIX_EPOCH)
.map(|d| d.as_millis() as u64)
.unwrap_or(0),
is_http: false,
response,
}
}
pub fn source(&self) -> &str {
&self.source
}
pub fn destination(&self) -> Option<SocketAddr> {
self.destination
}
pub fn timestamp(&self) -> u64 {
self.timestamp
}
pub fn response(&self) -> &Response {
&self.response
}
pub fn response_mut(&mut self) -> &mut Response {
&mut self.response
}
pub fn body(&self) -> Option<&Body> {
self.response.body.as_ref()
}
pub fn set_body(&mut self, body: impl Into<Bytes>) {
self.response.body = Some(Body::from(body.into()));
}
pub fn is_http(&self) -> bool {
self.is_http
}
}
impl fmt::Debug for MitmResponse {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("MitmResponse")
.field("source", &self.source)
.field("destination", &self.destination)
.field("timestamp", &self.timestamp)
.field("is_http", &self.is_http())
.field("response", &self.response)
.finish()
}
}
#[async_trait::async_trait]
pub trait RequestInterceptor: Send + Sync {
async fn intercept_request(&self, request: MitmRequest) -> Result<Option<MitmRequest>>;
}
#[async_trait::async_trait]
pub trait ResponseInterceptor: Send + Sync {
async fn intercept_response(&self, response: MitmResponse) -> Result<Option<MitmResponse>>;
}
pub struct InterceptorHandler {
request_interceptors: Vec<Arc<dyn RequestInterceptor>>,
response_interceptors: Vec<Arc<dyn ResponseInterceptor>>,
}
impl InterceptorHandler {
pub fn new() -> Self {
Self {
request_interceptors: Vec::new(),
response_interceptors: Vec::new(),
}
}
pub fn add_request_interceptor(&mut self, interceptor: Arc<dyn RequestInterceptor>) {
self.request_interceptors.push(interceptor);
}
pub fn add_response_interceptor(&mut self, interceptor: Arc<dyn ResponseInterceptor>) {
self.response_interceptors.push(interceptor);
}
pub async fn process_request(&self, mut request: MitmRequest) -> Result<Option<MitmRequest>> {
for interceptor in &self.request_interceptors {
match interceptor.intercept_request(request).await? {
Some(modified) => request = modified,
None => return Ok(None), }
}
Ok(Some(request))
}
pub async fn process_response(&self, mut response: MitmResponse) -> Result<Option<MitmResponse>> {
for interceptor in &self.response_interceptors {
match interceptor.intercept_response(response).await? {
Some(modified) => response = modified,
None => return Ok(None), }
}
Ok(Some(response))
}
pub fn has_interceptors(&self) -> bool {
!self.request_interceptors.is_empty() || !self.response_interceptors.is_empty()
}
}
impl Default for InterceptorHandler {
fn default() -> Self {
Self::new()
}
}
pub struct Interceptor;
impl Interceptor {
pub fn logging() -> LoggingInterceptor {
LoggingInterceptor
}
}
pub struct LoggingInterceptor;
#[async_trait::async_trait]
impl RequestInterceptor for LoggingInterceptor {
async fn intercept_request(&self, request: MitmRequest) -> Result<Option<MitmRequest>> {
if request.is_http() {
tracing::info!(
"[MITM] HTTP Request: {} {}",
request.request().method(),
request.request().uri()
);
for (name, value) in request.request().headers() {
tracing::info!(" {}: {:?}", name, value);
}
} else {
tracing::info!(
"[MITM] TCP Request to {}: {} bytes",
request.destination(),
request.body().map(|b| b.len()).unwrap_or(0)
);
}
if let Some(source) = request.source() {
tracing::info!(" From: {}", source);
}
tracing::info!(" Timestamp: {}", request.timestamp());
Ok(Some(request))
}
}
#[async_trait::async_trait]
impl ResponseInterceptor for LoggingInterceptor {
async fn intercept_response(&self, response: MitmResponse) -> Result<Option<MitmResponse>> {
if response.is_http() {
tracing::info!(
"[MITM] HTTP Response: {}",
response.response().status_code()
);
for (name, value) in response.response().headers() {
tracing::info!(" {}: {:?}", name, value);
}
} else {
tracing::info!(
"[MITM] TCP Response from {}: {} bytes",
response.source(),
response.body().map(|b| b.len()).unwrap_or(0)
);
}
if let Some(destination) = response.destination() {
tracing::info!(" To: {}", destination);
}
tracing::info!(" Timestamp: {}", response.timestamp());
Ok(Some(response))
}
}