Skip to content

Commit

Permalink
feat: allow script interception over compressed body
Browse files Browse the repository at this point in the history
  • Loading branch information
XOR-op committed Oct 18, 2023
1 parent 3c4a8c5 commit 0794a97
Show file tree
Hide file tree
Showing 5 changed files with 123 additions and 23 deletions.
42 changes: 40 additions & 2 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions boltconn/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ axum = { version = "0.6.18", features = ["ws"] }
base64 = "0.21.0"
boltapi = { path = "../boltapi" }
boringtun = "0.6.0"
brotli = "3.4.0"
byteorder = "1.4.3"
bytes = "1.2.1"
chrono = { version = "0.4.31", default-features = false, features = ["clock", "std"] }
Expand All @@ -27,6 +28,7 @@ colored = "2.0.0"
dashmap = "5.5.3"
fast-socks5 = "0.9.1"
fastrand = "2.0.0"
flate2 = "1.0.28"
flume = "0.11.0"
futures = "0.3.25"
http = "0.2.8"
Expand Down
2 changes: 1 addition & 1 deletion boltconn/src/cli/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -137,7 +137,7 @@ pub(crate) async fn controller_main(args: ProgramArgs) -> ! {
match create(init) {
Ok(_) => exit(0),
Err(err) => {
eprintln!("{}", err);
eprintln!("Error occurred: {}", err);
exit(-1)
}
}
Expand Down
2 changes: 1 addition & 1 deletion boltconn/src/intercept/intercept_modifier.rs
Original file line number Diff line number Diff line change
Expand Up @@ -282,6 +282,7 @@ impl Modifier for InterceptModifier {
.ok_or_else(|| anyhow!("no id"))?
.1;

// For large body, we skip the manipulation
let mut whole_data = if self.result.capture_response || self.result.contains_script {
Self::read_at_most(body, self.size_limit).await?
} else {
Expand Down Expand Up @@ -326,7 +327,6 @@ impl Modifier for InterceptModifier {
NetworkAddr::Raw(addr) => addr.ip().to_string(),
NetworkAddr::DomainName { domain_name, .. } => domain_name.clone(),
};
// For large body, we skip the manipulation
self.contents.push(
(
req,
Expand Down
98 changes: 79 additions & 19 deletions boltconn/src/intercept/script_engine.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ use rquickjs::class::Trace;
use rquickjs::{Class, Context, Ctx, FromJs, IntoJs, Object, Runtime, Value};
use std::collections::hash_map::Entry;
use std::collections::HashMap;
use std::io::Read;

#[derive(Debug, Clone)]
struct HttpData {
Expand Down Expand Up @@ -99,29 +100,57 @@ impl ScriptEngine {
status: Option<u16>,
headers: &HeaderMap,
field: &str,
) -> Option<(Option<u16>, HeaderMap, Option<String>)> {
) -> Option<(Option<u16>, HeaderMap, Option<Bytes>)> {
let runtime = Runtime::new().ok()?;
let ctx = Context::full(&runtime).ok()?;
let header = {
let mut header = HashMap::new();
for (k, v) in headers.iter() {
let key = k.to_string();
match header.entry(key) {
Entry::Vacant(e) => {
e.insert(v.to_str().ok()?.to_string());
}
Entry::Occupied(mut e) => e
.get_mut()
.push_str(format!(", {}", v.to_str().ok()?).as_str()),
};
}
header
};
// decompress
let body = if let Some(compress) = header.get("content-encoding") {
data.and_then(|d| {
let mut v = vec![];
match compress.as_str() {
"gzip" => {
flate2::read::GzDecoder::new(d.as_ref())
.read_to_end(&mut v)
.ok()?;
}
"deflate" => {
flate2::read::DeflateDecoder::new(d.as_ref())
.read_to_end(&mut v)
.ok()?;
}
"br" => {
brotli::Decompressor::new(d.as_ref(), d.len())
.read_to_end(&mut v)
.ok()?;
}
_ => None?,
}
String::from_utf8(v).ok()
})
} else {
data.and_then(|d| String::from_utf8(d.to_vec()).ok())
};
let js_data = HttpData {
url: url.to_string(),
method,
header: {
let mut header = HashMap::new();
for (k, v) in headers.iter() {
let key = k.to_string();
match header.entry(key) {
Entry::Vacant(e) => {
e.insert(v.to_str().ok()?.to_string());
}
Entry::Occupied(mut e) => e
.get_mut()
.push_str(format!(", {}", v.to_str().ok()?).as_str()),
};
}
header
},
header,
status,
body: data.and_then(|d| String::from_utf8(d.to_vec()).ok()),
body,
};
let r = match ctx.with(|ctx| -> Result<HttpData, rquickjs::Error> {
// init console
Expand Down Expand Up @@ -159,7 +188,7 @@ impl ScriptEngine {
}
}) {
Err(e) => {
tracing::trace!(
tracing::warn!(
"Failed to run script {} for {}: {}",
self.name
.clone()
Expand All @@ -176,7 +205,38 @@ impl ScriptEngine {
for (k, v) in r.header {
header.insert(HeaderName::from_bytes(k.as_bytes()).ok()?, v.parse().ok()?);
}
Some((r.status, header, r.body))
// recompress
let body = if let Some(compress) = header.get("content-encoding") {
r.body.and_then(|d| {
let mut buf = vec![];
match compress.to_str().ok()? {
"gzip" => {
let r =
flate2::read::GzEncoder::new(d.as_bytes(), flate2::Compression::none())
.read_to_end(&mut buf);
r.ok()?;
}
"deflate" => {
flate2::read::DeflateEncoder::new(
d.as_bytes(),
flate2::Compression::none(),
)
.read_to_end(&mut buf)
.ok()?;
}
"br" => {
brotli::CompressorReader::new(d.as_bytes(), d.as_bytes().len(), 0, 22)
.read_to_end(&mut buf)
.ok()?;
}
_ => None?,
};
Some(Bytes::from(buf))
})
} else {
r.body.map(Bytes::from)
};
Some((r.status, header, body))
}

pub fn try_rewrite_req(
Expand Down

0 comments on commit 0794a97

Please sign in to comment.