Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
236 changes: 215 additions & 21 deletions Cargo.lock

Large diffs are not rendered by default.

6 changes: 4 additions & 2 deletions Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
[workspace]
members = [
".",
"mitmproxy-contentviews",
"mitmproxy-highlight",
"mitmproxy-rs",
"mitmproxy-linux",
"mitmproxy-linux-ebpf",
Expand All @@ -11,6 +13,8 @@ members = [
]
default-members = [
".",
"mitmproxy-contentviews",
"mitmproxy-highlight",
"mitmproxy-rs",
"mitmproxy-linux",
"mitmproxy-linux-ebpf-common",
Expand Down Expand Up @@ -53,7 +57,6 @@ publish.workspace = true
[dependencies]
anyhow = { version = "1.0.97", features = ["backtrace"] }
log = "0.4.27"
once_cell = "1"
pretty-hex = "0.4.1"
smoltcp = "0.12"
tokio = { version = "1.44.1", features = ["macros", "net", "rt-multi-thread", "sync", "time", "io-util", "process"] }
Expand Down Expand Up @@ -109,7 +112,6 @@ rand = "0.9"
criterion = "0.5.1"
hickory-server = "0.25.1"


[[bench]]
name = "process"
harness = false
Expand Down
4 changes: 4 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,10 @@ This repository contains mitmproxy's Rust bits, most notably:
### Structure

- [`src/`](./src): The `mitmproxy` crate containing most of the "meat".
- [`mitmproxy-contentviews/`](./mitmproxy-contentviews):
Pretty-printers for (HTTP) message bodies.
- [`mitmproxy-highlight/`](./mitmproxy-highlight):
Syntax highlighting backend for mitmproxy and mitmdump.
- [`mitmproxy-rs/`](./mitmproxy-rs): The `mitmproxy-rs` Python package,
which provides Python bindings for the Rust crate using [PyO3](https://pyo3.rs/).
Source and binary distributions are available [on PyPI](https://pypi.org/project/mitmproxy-rs/).
Expand Down
33 changes: 33 additions & 0 deletions mitmproxy-contentviews/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
[package]
name = "mitmproxy-contentviews"
license = "MIT"
authors.workspace = true
version.workspace = true
repository.workspace = true
edition.workspace = true
rust-version.workspace = true
publish.workspace = true

[lints]
workspace = true

[dependencies]
anyhow = { version = "1.0.97", features = ["backtrace"] }
log = "0.4.27"
data-encoding = "2.8.0"
pretty-hex = "0.4.1"
mitmproxy-highlight = { path = "../mitmproxy-highlight" }
serde = { version = "1.0", features = ["derive"] }
serde_yaml = "0.9"
rmp-serde = "1.1"
protobuf = "3.7.2"
regex = "1.10.3"
flate2 = "1.0"
protobuf-parse = "3.7"

[dev-dependencies]
criterion = "0.5.1"

[[bench]]
name = "contentviews"
harness = false
60 changes: 60 additions & 0 deletions mitmproxy-contentviews/benches/contentviews.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
use criterion::{black_box, criterion_group, criterion_main, Criterion};
use mitmproxy_contentviews::{test::TestMetadata, MsgPack, Prettify, Protobuf, Reencode};

fn criterion_benchmark(c: &mut Criterion) {
c.bench_function("protobuf-prettify", |b| {
b.iter(|| {
Protobuf.prettify(black_box(b"\n\x13gRPC testing server\x12\x07\n\x05Index\x12\x07\n\x05Empty\x12\x0c\n\nDummyUnary\x12\x0f\n\rSpecificError\x12\r\n\x0bRandomError\x12\x0e\n\x0cHeadersUnary\x12\x11\n\x0fNoResponseUnary"), &TestMetadata::default()).unwrap()
})
});

c.bench_function("protobuf-reencode", |b| {
b.iter(|| {
Protobuf.reencode(
black_box("1: gRPC testing server\n2:\n- 1: Index\n- 1: Empty\n- 1: DummyUnary\n- 1: SpecificError\n- 1: RandomError\n- 1: HeadersUnary\n- 1: NoResponseUnary\n"),
&TestMetadata::default()
).unwrap()
})
});

const TEST_MSGPACK: &[u8] = &[
0x83, // map with 3 elements
0xa4, 0x6e, 0x61, 0x6d, 0x65, // "name"
0xa8, 0x4a, 0x6f, 0x68, 0x6e, 0x20, 0x44, 0x6f, 0x65, // "John Doe"
0xa3, 0x61, 0x67, 0x65, // "age"
0x1e, // 30
0xa4, 0x74, 0x61, 0x67, 0x73, // "tags"
0x92, // array with 2 elements
0xa9, 0x64, 0x65, 0x76, 0x65, 0x6c, 0x6f, 0x70, 0x65, 0x72, // "developer"
0xa4, 0x72, 0x75, 0x73, 0x74, // "rust"
];
c.bench_function("msgpack-prettify", |b| {
b.iter(|| {
MsgPack
.prettify(black_box(TEST_MSGPACK), &TestMetadata::default())
.unwrap()
})
});

c.bench_function("msgpack-reencode", |b| {
b.iter(|| {
MsgPack
.reencode(
black_box(
"\
name: John Doe\n\
age: 30\n\
tags:\n\
- developer\n\
- rust\n\
",
),
&TestMetadata::default(),
)
.unwrap()
})
});
}

criterion_group!(benches, criterion_benchmark);
criterion_main!(benches);
55 changes: 55 additions & 0 deletions mitmproxy-contentviews/src/hex_dump.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
use crate::hex_stream::is_binary;
use crate::{Metadata, Prettify};
use pretty_hex::{HexConfig, PrettyHex};

pub struct HexDump;

impl Prettify for HexDump {
fn name(&self) -> &'static str {
"Hex Dump"
}

fn prettify(&self, data: &[u8], _metadata: &dyn Metadata) -> anyhow::Result<String> {
Ok(format!(
"{:?}",
data.hex_conf(HexConfig {
title: false,
ascii: true,
width: 16,
group: 4,
chunk: 1,
max_bytes: usize::MAX,
display_offset: 0,
})
))
}

fn render_priority(&self, data: &[u8], _metadata: &dyn Metadata) -> f64 {
if is_binary(data) {
0.5
} else {
0.0
}
}
}

#[cfg(test)]
mod tests {
use super::*;
use crate::test::TestMetadata;

#[test]
fn prettify_simple() {
let result = HexDump.prettify(b"abcd", &TestMetadata::default()).unwrap();
assert_eq!(
result,
"0000: 61 62 63 64 abcd"
);
}

#[test]
fn prettify_empty() {
let result = HexDump.prettify(b"", &TestMetadata::default()).unwrap();
assert_eq!(result, "");
}
}
89 changes: 89 additions & 0 deletions mitmproxy-contentviews/src/hex_stream.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
use crate::{Metadata, Prettify, Reencode};
use anyhow::{Context, Result};

pub struct HexStream;

pub(crate) fn is_binary(data: &[u8]) -> bool {
if data.is_empty() {
return false;
}
let ratio = data
.iter()
.take(100)
.filter(|&&b| b < 9 || (13 < b && b < 32) || b > 126)
.count() as f64
/ data.len().min(100) as f64;

ratio > 0.3
}

impl Prettify for HexStream {
fn name(&self) -> &'static str {
"Hex Stream"
}

fn prettify(&self, data: &[u8], _metadata: &dyn Metadata) -> Result<String> {
Ok(data_encoding::HEXLOWER.encode(data))
}

fn render_priority(&self, data: &[u8], _metadata: &dyn Metadata) -> f64 {
if is_binary(data) {
0.4
} else {
0.0
}
}
}

impl Reencode for HexStream {
fn reencode(&self, data: &str, _metadata: &dyn Metadata) -> Result<Vec<u8>> {
let data = data.trim_end_matches(['\n', '\r']);
if data.len() % 2 != 0 {
anyhow::bail!("Invalid hex string: uneven number of characters");
}
data_encoding::HEXLOWER_PERMISSIVE
.decode(data.as_bytes())
.context("Invalid hex string")
}
}

#[cfg(test)]
mod tests {
use super::*;
use crate::test::TestMetadata;

#[test]
fn test_hex_stream() {
let result = HexStream
.prettify(b"foo", &TestMetadata::default())
.unwrap();
assert_eq!(result, "666f6f");
}

#[test]
fn test_hex_stream_empty() {
let result = HexStream.prettify(b"", &TestMetadata::default()).unwrap();
assert_eq!(result, "");
}

#[test]
fn test_hex_stream_reencode() {
let data = "666f6f";
let result = HexStream.reencode(data, &TestMetadata::default()).unwrap();
assert_eq!(result, b"foo");
}

#[test]
fn test_hex_stream_reencode_with_newlines() {
let data = "666f6f\r\n";
let result = HexStream.reencode(data, &TestMetadata::default()).unwrap();
assert_eq!(result, b"foo");
}

#[test]
fn test_hex_stream_reencode_uneven_chars() {
let data = "666f6";
let result = HexStream.reencode(data, &TestMetadata::default());
assert!(result.is_err());
}
}
Loading