Skip to content

Commit a8404bd

Browse files
committed
feat: better body processing
- add extension to path for saving the body - decompressing while streaming the body - improve markdown rendering of the body
1 parent bf793ae commit a8404bd

15 files changed

+326
-308
lines changed

Cargo.lock

Lines changed: 5 additions & 12 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,6 @@ keywords = ["proxy", "mitm", "https", "http"]
1212

1313
[dependencies]
1414
anyhow = "1.0"
15-
async-compression = { version = "0.4.6", features = ["brotli", "gzip", "zstd", "deflate", "tokio"] }
1615
base64 = "0.22.0"
1716
bytes = "1.5"
1817
clap = { version = "4.5.1", features = ["derive"] }
@@ -29,7 +28,6 @@ hyper-tungstenite = "0.14.0"
2928
indexmap = { version = "2.2.5", features = ["serde"] }
3029
moka = { version = "0.12.5", features = ["future"] }
3130
pin-project-lite = "0.2.13"
32-
pretty-hex = "0.4.1"
3331
rand = "0.8.5"
3432
rcgen = { version = "0.13.0", default-features = false, features = ["x509-parser", "pem", "ring"] }
3533
rsa = "0.9.6"
@@ -49,8 +47,12 @@ ratatui = { version = "0.28.1", features = ["unstable-rendered-line-info"] }
4947
unicode-width = "0.2.0"
5048
log = "0.4.22"
5149
simplelog = "0.12.2"
50+
flate2 = "1.0.34"
51+
brotli = "6.0.0"
52+
zstd = "0.13.2"
5253

5354
[dev-dependencies]
55+
async-compression = { version = "0.4.12", features = ["gzip", "tokio"] }
5456
async-http-proxy = { version = "1.2.5", features = ["runtime-tokio"] }
5557
insta = "1.36.1"
5658
pretty_assertions = "1.4.0"

assets/index.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -367,7 +367,7 @@
367367
* @property {?number} status
368368
* @property {?number} size
369369
* @property {?number} time
370-
* @property {?string} mime
370+
* @property {string} mime
371371
*/
372372

373373
/**

src/server.rs

Lines changed: 65 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -3,16 +3,16 @@ use crate::{
33
filter::{is_match_title, is_match_type, TitleFilter},
44
rewind::Rewind,
55
state::State,
6-
traffic::Traffic,
6+
traffic::{extract_mime, to_ext_name, Traffic},
77
};
88

99
use anyhow::{anyhow, Context as _, Result};
1010
use bytes::Bytes;
1111
use futures_util::{stream, Sink, SinkExt, Stream, StreamExt, TryStreamExt};
1212
use http::{
1313
header::{
14-
CACHE_CONTROL, CONNECTION, CONTENT_DISPOSITION, CONTENT_LENGTH, CONTENT_TYPE,
15-
PROXY_AUTHORIZATION,
14+
CACHE_CONTROL, CONNECTION, CONTENT_DISPOSITION, CONTENT_ENCODING, CONTENT_LENGTH,
15+
CONTENT_TYPE, PROXY_AUTHORIZATION,
1616
},
1717
uri::{Authority, Scheme},
1818
HeaderValue,
@@ -35,7 +35,7 @@ use pin_project_lite::pin_project;
3535
use serde::Serialize;
3636
use std::{
3737
fs::File,
38-
io::Write,
38+
io::{Read, Write},
3939
path::PathBuf,
4040
pin::Pin,
4141
process,
@@ -59,7 +59,7 @@ const CERT_INDEX: &str = include_str!("../assets/install-certificate.html");
5959

6060
type Request = hyper::Request<Incoming>;
6161
type Response = hyper::Response<BoxBody<Bytes, anyhow::Error>>;
62-
type TrafficDoneSender = mpsc::UnboundedSender<usize>;
62+
type TrafficDoneSender = mpsc::UnboundedSender<(usize, u64)>;
6363

6464
pub struct ServerBuilder {
6565
ca: CertificateAuthority,
@@ -168,8 +168,8 @@ impl Server {
168168
}
169169
});
170170
tokio::spawn(async move {
171-
while let Some(id) = traffic_done_rx.recv().await {
172-
self.state.done_traffic(id).await;
171+
while let Some((id, raw_size)) = traffic_done_rx.recv().await {
172+
self.state.done_traffic(id, raw_size).await;
173173
}
174174
});
175175
Ok(stop_tx)
@@ -296,7 +296,8 @@ impl Server {
296296
} else {
297297
None
298298
};
299-
let req_body = BodyWrapper::new(req.into_body(), req_body_file, None);
299+
300+
let req_body = BodyWrapper::new(req.into_body(), req_body_file, "".into(), None);
300301

301302
let proxy_req = match builder.body(req_body) {
302303
Ok(v) => v,
@@ -798,7 +799,11 @@ impl Server {
798799

799800
let mut res = Response::default();
800801

802+
let mut encoding = String::new();
801803
for (key, value) in proxy_res_headers.iter() {
804+
if key == CONTENT_ENCODING {
805+
encoding = value.to_str().map(|v| v.to_string()).unwrap_or_default();
806+
}
802807
res.headers_mut().insert(key.clone(), value.clone());
803808
}
804809

@@ -823,7 +828,8 @@ impl Server {
823828
let res_body = BodyWrapper::new(
824829
proxy_res.into_body(),
825830
res_body_file,
826-
Some((traffic.id, traffic_done_tx)),
831+
encoding,
832+
Some((traffic.gid, traffic_done_tx)),
827833
);
828834

829835
*res.body_mut() = BoxBody::new(res_body);
@@ -841,16 +847,18 @@ impl Server {
841847
let mut res = Response::default();
842848
*res.status_mut() = StatusCode::INTERNAL_SERVER_ERROR;
843849

844-
let traffic_id = traffic.id;
850+
let gid = traffic.gid;
845851
traffic.add_error(error.to_string());
846852
self.state.add_traffic(traffic).await;
847-
self.state.done_traffic(traffic_id).await;
853+
self.state.done_traffic(gid, 0).await;
848854

849855
Ok(res)
850856
}
851857

852858
fn req_body_file(&self, traffic: &mut Traffic) -> Result<File> {
853-
let path = self.temp_dir.join(format!("{}-req", traffic.id));
859+
let mime = extract_mime(&traffic.req_headers);
860+
let ext_name = to_ext_name(mime);
861+
let path = self.temp_dir.join(format!("{}-req{ext_name}", traffic.gid));
854862
let file = File::create(&path).with_context(|| {
855863
format!(
856864
"Failed to create file '{}' to store request body",
@@ -862,7 +870,9 @@ impl Server {
862870
}
863871

864872
fn res_body_file(&self, traffic: &mut Traffic) -> Result<File> {
865-
let path = self.temp_dir.join(format!("{}-res", traffic.id));
873+
let mime = extract_mime(&traffic.res_headers);
874+
let ext_name = to_ext_name(mime);
875+
let path = self.temp_dir.join(format!("{}-res{ext_name}", traffic.gid));
866876
let file = File::create(&path).with_context(|| {
867877
format!(
868878
"Failed to create file '{}' to store response body",
@@ -887,12 +897,14 @@ pin_project! {
887897
#[pin]
888898
inner: B,
889899
file: Option<File>,
900+
encoding: String,
890901
traffic_done: Option<(usize, TrafficDoneSender)>,
902+
raw_size: u64,
891903
}
892904
impl<B> PinnedDrop for BodyWrapper<B> {
893905
fn drop(this: Pin<&mut Self>) {
894906
if let Some((id, traffic_done_tx)) = this.traffic_done.as_ref() {
895-
let _ = traffic_done_tx.send(*id);
907+
let _ = traffic_done_tx.send((*id, this.raw_size));
896908
}
897909
}
898910
}
@@ -902,12 +914,15 @@ impl<B> BodyWrapper<B> {
902914
pub fn new(
903915
inner: B,
904916
file: Option<File>,
917+
encoding: String,
905918
traffic_done: Option<(usize, TrafficDoneSender)>,
906919
) -> Self {
907920
Self {
908921
inner,
909922
file,
923+
encoding,
910924
traffic_done,
925+
raw_size: 0,
911926
}
912927
}
913928
}
@@ -927,9 +942,12 @@ where
927942
match Pin::new(&mut this.inner).poll_frame(cx) {
928943
Poll::Ready(Some(Ok(frame))) => match frame.into_data() {
929944
Ok(data) => {
930-
if let Some(file) = this.file.as_mut() {
931-
let _ = file.write_all(&data);
932-
}
945+
if let Ok(new_data) = decompress(&data, this.encoding) {
946+
if let Some(file) = this.file.as_mut() {
947+
let _ = file.write_all(&new_data);
948+
}
949+
};
950+
*this.raw_size += data.len() as u64;
933951
Poll::Ready(Some(Ok(Frame::data(data))))
934952
}
935953
Err(e) => Poll::Ready(Some(Ok(e))),
@@ -982,3 +1000,33 @@ fn ignore_tungstenite_error(err: &tungstenite::Error) -> bool {
9821000
)
9831001
)
9841002
}
1003+
1004+
fn decompress(data: &[u8], encoding: &str) -> Result<Vec<u8>> {
1005+
match encoding {
1006+
"gzip" => {
1007+
let mut decoder = flate2::read::GzDecoder::new(data);
1008+
let mut decompressed = Vec::new();
1009+
decoder.read_to_end(&mut decompressed)?;
1010+
Ok(decompressed)
1011+
}
1012+
"deflate" => {
1013+
let mut decoder = flate2::read::DeflateDecoder::new(data);
1014+
let mut decompressed = Vec::new();
1015+
decoder.read_to_end(&mut decompressed)?;
1016+
Ok(decompressed)
1017+
}
1018+
"br" => {
1019+
let mut decoder = brotli::Decompressor::new(data, 4096);
1020+
let mut decompressed = Vec::new();
1021+
decoder.read_to_end(&mut decompressed)?;
1022+
Ok(decompressed)
1023+
}
1024+
"zstd" => {
1025+
let mut decoder = zstd::stream::Decoder::new(data)?;
1026+
let mut decompressed = Vec::new();
1027+
decoder.read_to_end(&mut decompressed)?;
1028+
Ok(decompressed)
1029+
}
1030+
_ => Ok(data.to_vec()),
1031+
}
1032+
}

0 commit comments

Comments
 (0)