Skip to content

Commit 5c981b5

Browse files
committed
add gRPC contentview
1 parent ef6cdea commit 5c981b5

6 files changed

Lines changed: 127 additions & 31 deletions

File tree

mitmproxy-rs/mitmproxy_rs/contentviews.pyi

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,8 @@ class InteractiveContentview(Contentview):
1414
hex_dump: Contentview
1515
hex_stream: InteractiveContentview
1616
msgpack: InteractiveContentview
17-
protobuf: Contentview
17+
protobuf: InteractiveContentview
18+
grpc: InteractiveContentview
1819

1920
__all__ = [
2021
"Contentview",
@@ -23,4 +24,5 @@ __all__ = [
2324
"hex_stream",
2425
"msgpack",
2526
"protobuf",
27+
"grpc",
2628
]

mitmproxy-rs/src/contentview.rs

Lines changed: 16 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,21 @@
11
use mitmproxy::contentviews::{Metadata, Prettify, Reencode};
22
use pyo3::{exceptions::PyValueError, prelude::*};
33

4-
struct PythonMetadata(PyObject);
4+
pub struct PythonMetadata<'py>(Bound<'py, PyAny>);
55

6-
impl Metadata for PythonMetadata {
6+
impl Metadata for PythonMetadata<'_> {
77
fn content_type(&self) -> Option<String> {
8-
Python::with_gil(|py| {
9-
self.0
10-
.getattr(py, "content_type")
11-
.ok()?
12-
.extract::<String>(py)
13-
.ok()
14-
})
8+
self.0
9+
.getattr("content_type")
10+
.ok()?
11+
.extract::<String>()
12+
.ok()
13+
}
14+
}
15+
16+
impl<'py> FromPyObject<'py> for PythonMetadata<'py> {
17+
fn extract_bound(ob: &Bound<'py, PyAny>) -> PyResult<Self> {
18+
Ok(PythonMetadata(ob.clone()))
1519
}
1620
}
1721

@@ -36,17 +40,14 @@ impl Contentview {
3640
}
3741

3842
/// Pretty-print an (encoded) message.
39-
pub fn prettify(&self, data: Vec<u8>, metadata: PyObject) -> PyResult<String> {
40-
let metadata = PythonMetadata(metadata);
41-
43+
pub fn prettify(&self, data: Vec<u8>, metadata: PythonMetadata) -> PyResult<String> {
4244
self.0
4345
.prettify(&data, &metadata)
4446
.map_err(|e| PyValueError::new_err(e.to_string()))
4547
}
4648

4749
/// Return the priority of this view for rendering data.
48-
pub fn render_priority(&self, data: Vec<u8>, metadata: PyObject) -> PyResult<f64> {
49-
let metadata = PythonMetadata(metadata);
50+
pub fn render_priority(&self, data: Vec<u8>, metadata: PythonMetadata) -> PyResult<f64> {
5051
Ok(self.0.render_priority(&data, &metadata))
5152
}
5253

@@ -85,9 +86,7 @@ impl InteractiveContentview {
8586

8687
#[pymethods]
8788
impl InteractiveContentview {
88-
pub fn reencode(&self, data: &str, metadata: PyObject) -> PyResult<Vec<u8>> {
89-
let metadata = PythonMetadata(metadata);
90-
89+
pub fn reencode(&self, data: &str, metadata: PythonMetadata) -> PyResult<Vec<u8>> {
9190
self.0
9291
.reencode(data, &metadata)
9392
.map_err(|e| PyValueError::new_err(e.to_string()))

mitmproxy-rs/src/lib.rs

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -88,18 +88,19 @@ mod mitmproxy_rs {
8888
#[pymodule]
8989
mod contentviews {
9090
use super::*;
91-
//#[pymodule_export]
92-
//use crate::contentview::Contentview;
93-
//#[pymodule_export]
94-
//use crate::contentview::InteractiveContentview;
95-
use mitmproxy::contentviews::{HexDump, HexStream, MsgPack, Protobuf};
91+
#[pymodule_export]
92+
use crate::contentview::Contentview;
93+
#[pymodule_export]
94+
use crate::contentview::InteractiveContentview;
95+
use mitmproxy::contentviews::{HexDump, HexStream, MsgPack, Protobuf, GRPC};
9696

9797
#[pymodule_init]
9898
fn init(m: &Bound<'_, PyModule>) -> PyResult<()> {
9999
m.add_contentview(&HexDump)?;
100100
m.add_interactive_contentview(&HexStream)?;
101101
m.add_interactive_contentview(&MsgPack)?;
102-
m.add_contentview(&Protobuf)?;
102+
m.add_interactive_contentview(&Protobuf)?;
103+
m.add_interactive_contentview(&GRPC)?;
103104
Ok(())
104105
}
105106
}

src/contentviews/grpc.rs

Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
use crate::contentviews::{Metadata, Prettify, Protobuf, Reencode};
2+
use crate::syntax_highlight::Language;
3+
use anyhow::{bail, Context, Result};
4+
use serde::Deserialize;
5+
use serde_yaml::Value;
6+
7+
pub struct GRPC;
8+
9+
impl Prettify for GRPC {
10+
fn name(&self) -> &'static str {
11+
"gRPC"
12+
}
13+
14+
fn syntax_highlight(&self) -> Language {
15+
Language::Yaml
16+
}
17+
18+
fn prettify(&self, mut data: &[u8], metadata: &dyn Metadata) -> Result<String> {
19+
let mut protos = vec![];
20+
21+
while !data.is_empty() {
22+
let compressed = match data[0] {
23+
0 => false,
24+
1 => true,
25+
_ => bail!("invalid gRPC: first byte is not a boolean"),
26+
};
27+
let len = match data.get(1..5) {
28+
Some(x) => u32::from_be_bytes(x.try_into()?) as usize,
29+
_ => bail!("invalid gRPC: first byte is not a boolean"),
30+
};
31+
let Some(proto) = data.get(5..5 + len) else {
32+
bail!("Invald gRPC: not enough data")
33+
};
34+
if compressed {
35+
todo!();
36+
}
37+
protos.push(proto);
38+
data = &data[5 + len..];
39+
}
40+
41+
let prettified = protos
42+
.into_iter()
43+
.map(|proto| Protobuf.prettify(proto, metadata))
44+
.collect::<Result<Vec<String>>>()?;
45+
Ok(prettified.join("\n---\n\n"))
46+
}
47+
48+
fn render_priority(&self, _data: &[u8], metadata: &dyn Metadata) -> f64 {
49+
let Some(ct) = metadata.content_type() else {
50+
return 0.0;
51+
};
52+
match ct.as_str() {
53+
"application/grpc" => 2.0,
54+
"application/grpc+proto" => 2.0,
55+
"application/prpc" => 2.0,
56+
_ => 0.0,
57+
}
58+
}
59+
}
60+
61+
impl Reencode for GRPC {
62+
fn reencode(&self, data: &str, metadata: &dyn Metadata) -> Result<Vec<u8>> {
63+
let mut ret = vec![];
64+
for document in serde_yaml::Deserializer::from_str(data) {
65+
let value = Value::deserialize(document).context("Invalid YAML")?;
66+
let proto = Protobuf::reencode_yaml(value, metadata)?;
67+
ret.push(0); // compressed
68+
ret.extend(u32::to_be_bytes(proto.len() as u32));
69+
ret.extend(proto);
70+
}
71+
Ok(ret)
72+
}
73+
}
74+
75+
#[cfg(test)]
76+
mod tests {
77+
use super::*;
78+
use crate::contentviews::TestMetadata;
79+
80+
#[test]
81+
fn test_grpc() {
82+
let result = GRPC.prettify(b"foo", &TestMetadata::default()).unwrap();
83+
assert_eq!(result, "666f6f");
84+
}
85+
}

src/contentviews/mod.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
mod grpc;
12
mod hex_dump;
23
mod hex_stream;
34
mod msgpack;
@@ -6,6 +7,7 @@ mod protobuf;
67
use anyhow::Result;
78

89
use crate::syntax_highlight;
10+
pub use grpc::GRPC;
911
pub use hex_dump::HexDump;
1012
pub use hex_stream::HexStream;
1113
pub use msgpack::MsgPack;

src/contentviews/protobuf.rs

Lines changed: 14 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
use crate::contentviews::{Metadata, Prettify, Reencode};
2+
use crate::syntax_highlight::Language;
23
use anyhow::{bail, Context, Result};
34
use protobuf::descriptor::field_descriptor_proto::Label::LABEL_REPEATED;
45
use protobuf::descriptor::field_descriptor_proto::Type;
@@ -54,10 +55,14 @@ impl Prettify for Protobuf {
5455
"Protocol Buffer"
5556
}
5657

58+
fn syntax_highlight(&self) -> Language {
59+
Language::Yaml
60+
}
61+
5762
fn prettify(&self, data: &[u8], _metadata: &dyn Metadata) -> Result<String> {
5863
// Check if data is empty first
5964
if data.is_empty() {
60-
bail!("Empty protobuf data");
65+
return Ok("{} # empty protobuf message".to_string());
6166
}
6267

6368
let existing = Empty::descriptor();
@@ -79,13 +84,9 @@ impl Prettify for Protobuf {
7984
}
8085

8186
impl Reencode for Protobuf {
82-
fn reencode(&self, data: &str, _metadata: &dyn Metadata) -> Result<Vec<u8>> {
83-
let descriptor = Empty::descriptor();
84-
let message = descriptor.new_instance();
85-
87+
fn reencode(&self, data: &str, metadata: &dyn Metadata) -> Result<Vec<u8>> {
8688
let value: Value = serde_yaml::from_str(data).context("Invalid YAML")?;
87-
88-
Self::merge_yaml_into_message(value, message)
89+
Self::reencode_yaml(value, metadata)
8990
}
9091
}
9192

@@ -143,6 +144,12 @@ fn int_value(n: Number, field: Option<&FieldDescriptor>) -> UnknownValue {
143144
}
144145

145146
impl Protobuf {
147+
pub(super) fn reencode_yaml(value: Value, _metadata: &dyn Metadata) -> Result<Vec<u8>> {
148+
let descriptor = Empty::descriptor();
149+
let message = descriptor.new_instance();
150+
Self::merge_yaml_into_message(value, message)
151+
}
152+
146153
fn merge_yaml_into_message(value: Value, mut message: Box<dyn MessageDyn>) -> Result<Vec<u8>> {
147154
let Value::Mapping(mapping) = value else {
148155
bail!("YAML is not a mapping");

0 commit comments

Comments
 (0)