Skip to content

Commit 20baa49

Browse files
committed
make contentviews interactive
1 parent 0e81f26 commit 20baa49

File tree

7 files changed

+244
-86
lines changed

7 files changed

+244
-86
lines changed

mitmproxy-rs/mitmproxy_rs/__init__.pyi

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ from __future__ import annotations
22

33
from typing import Any, Literal
44
from typing import final, overload, TypeVar
5-
from . import certs, dns, local, process_info, tun, udp, wireguard
5+
from . import certs, contentviews, dns, local, process_info, tun, udp, wireguard
66

77
T = TypeVar("T")
88

@@ -57,6 +57,7 @@ class Stream:
5757

5858
__all__ = [
5959
"certs",
60+
"contentviews",
6061
"dns",
6162
"local",
6263
"process_info",
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
from typing import ClassVar, final
2+
3+
class Contentview:
4+
name: ClassVar[str]
5+
6+
def deserialize(self, data: bytes) -> str:
7+
pass
8+
9+
@final
10+
class InteractiveContentview(Contentview):
11+
def serialize(self, data: str) -> bytes:
12+
pass
13+
14+
hex_dump: Contentview
15+
hex_stream: InteractiveContentview
16+
17+
__all__ = [
18+
"Contentview",
19+
"InteractiveContentview",
20+
"hex_dump",
21+
"hex_stream",
22+
]

mitmproxy-rs/src/contentview.rs

Lines changed: 49 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,32 +1,65 @@
1+
use anyhow::{anyhow, Result};
2+
use mitmproxy::contentviews::{Prettify, Reencode};
13
use pyo3::prelude::*;
2-
use anyhow::{Result};
3-
use mitmproxy::contentviews::Contentview;
44

5-
#[pyclass]
6-
pub struct PyContentview(&'static dyn Contentview);
5+
#[pyclass(frozen, module = "mitmproxy_rs.contentviews", subclass)]
6+
pub struct Contentview(&'static dyn Prettify);
77

8-
impl PyContentview {
9-
pub fn new<'py>(py: Python<'py>, contentview: &'static dyn Contentview) -> PyResult<Bound<'py, Self>> {
10-
PyContentview(contentview).into_pyobject(py)
8+
impl Contentview {
9+
pub fn new<'py>(
10+
py: Python<'py>,
11+
contentview: &'static dyn Prettify,
12+
) -> PyResult<Bound<'py, Self>> {
13+
Contentview(contentview).into_pyobject(py)
1114
}
1215
}
1316

1417
#[pymethods]
15-
impl PyContentview {
16-
18+
impl Contentview {
19+
/// The name of this contentview.
1720
#[getter]
1821
pub fn name(&self) -> &str {
1922
self.0.name()
2023
}
2124

22-
pub fn deserialize<'py>(
23-
&self,
24-
data: Vec<u8>
25-
) -> Result<String> {
26-
self.0.deserialize(data)
25+
/// Pretty-print an (encoded) message.
26+
pub fn deserialize<'py>(&self, data: Vec<u8>) -> Result<String> {
27+
self.0.deserialize(data).map_err(|e| anyhow!("{e}"))
2728
}
2829

2930
fn __repr__(&self) -> PyResult<String> {
30-
Ok(format!("<{} Contentview>", self.0.name()))
31+
Ok(format!(
32+
"<mitmproxy_rs.contentview.Contentview: {}>",
33+
self.0.name()
34+
))
35+
}
36+
}
37+
38+
#[pyclass(frozen, module = "mitmproxy_rs.contentviews", extends=Contentview)]
39+
pub struct InteractiveContentview(&'static dyn Reencode);
40+
41+
impl InteractiveContentview {
42+
/// Argument passed twice because of https://github.com/rust-lang/rust/issues/65991
43+
pub fn new<'py, T: Prettify + Reencode>(
44+
py: Python<'py>,
45+
cv: &'static T,
46+
) -> PyResult<Bound<'py, Self>> {
47+
let cls =
48+
PyClassInitializer::from(Contentview(cv)).add_subclass(InteractiveContentview(cv));
49+
Bound::new(py, cls)
50+
}
51+
}
52+
53+
#[pymethods]
54+
impl InteractiveContentview {
55+
pub fn serialize<'py>(&self, data: String) -> Result<Vec<u8>> {
56+
self.0.serialize(data).map_err(|e| anyhow!("{e}"))
3157
}
32-
}
58+
59+
fn __repr__(self_: PyRef<'_, Self>) -> PyResult<String> {
60+
Ok(format!(
61+
"<mitmproxy_rs.contentview.InteractiveContentview: {}>",
62+
self_.as_super().name()
63+
))
64+
}
65+
}

mitmproxy-rs/src/lib.rs

Lines changed: 27 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,17 +2,19 @@ extern crate core;
22

33
use std::sync::RwLock;
44

5+
use crate::contentview::{Contentview, InteractiveContentview};
6+
use mitmproxy::contentviews::{Prettify, Reencode};
57
use once_cell::sync::Lazy;
68
use pyo3::{exceptions::PyException, prelude::*};
79

10+
mod contentview;
811
mod dns_resolver;
912
mod process_info;
1013
mod server;
1114
mod stream;
1215
pub mod task;
1316
mod udp_client;
1417
mod util;
15-
mod contentview;
1618

1719
static LOGGER_INITIALIZED: Lazy<RwLock<bool>> = Lazy::new(|| RwLock::new(false));
1820

@@ -84,16 +86,18 @@ mod mitmproxy_rs {
8486

8587
#[pymodule]
8688
mod contentviews {
87-
use crate::contentview::PyContentview;
8889
use super::*;
89-
use mitmproxy::contentviews::*;
90+
#[pymodule_export]
91+
use crate::contentview::Contentview;
92+
#[pymodule_export]
93+
use crate::contentview::InteractiveContentview;
94+
use mitmproxy::contentviews::{HexDump, HexStream};
9095

9196
#[pymodule_init]
9297
fn init(m: &Bound<'_, PyModule>) -> PyResult<()> {
93-
m.add(
94-
"hex",
95-
PyContentview::new(m.py(), &HexStream())?
96-
)
98+
m.add_contentview(&HexDump)?;
99+
m.add_interactive_contentview(&HexStream)?;
100+
Ok(())
97101
}
98102
}
99103

@@ -121,3 +125,19 @@ mod mitmproxy_rs {
121125
Ok(())
122126
}
123127
}
128+
129+
trait AddContentview {
130+
fn add_contentview<T: Prettify>(&self, cv: &'static T) -> PyResult<()>;
131+
fn add_interactive_contentview<T: Prettify + Reencode>(&self, i: &'static T) -> PyResult<()>;
132+
}
133+
134+
impl AddContentview for Bound<'_, PyModule> {
135+
fn add_contentview<T: Prettify>(&self, cv: &'static T) -> PyResult<()> {
136+
let view = Contentview::new(self.py(), cv)?;
137+
self.add(cv.instance_name(), view)
138+
}
139+
fn add_interactive_contentview<T: Prettify + Reencode>(&self, cv: &'static T) -> PyResult<()> {
140+
let view = InteractiveContentview::new(self.py(), cv)?;
141+
self.add(cv.instance_name(), view)
142+
}
143+
}

src/contentviews/hex_dump.rs

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
use crate::contentviews::{Prettify, PrettifyError};
2+
use pretty_hex::{HexConfig, PrettyHex};
3+
4+
pub struct HexDump;
5+
6+
impl Prettify for HexDump {
7+
fn name(&self) -> &'static str {
8+
"Hex Dump"
9+
}
10+
11+
fn deserialize(&self, data: Vec<u8>) -> Result<String, PrettifyError> {
12+
Ok(format!(
13+
"{:?}",
14+
data.hex_conf(HexConfig {
15+
title: false,
16+
ascii: true,
17+
width: 16,
18+
group: 4,
19+
chunk: 1,
20+
max_bytes: usize::MAX,
21+
display_offset: 0,
22+
})
23+
))
24+
}
25+
}
26+
27+
#[cfg(test)]
28+
mod tests {
29+
use super::*;
30+
31+
#[test]
32+
fn test_hexdump_deserialize() {
33+
let data = b"abcd".to_vec();
34+
let result = HexDump.deserialize(data).unwrap();
35+
assert_eq!(
36+
result,
37+
"0000: 61 62 63 64 abcd"
38+
);
39+
}
40+
41+
#[test]
42+
fn test_hexdump_deserialize_empty() {
43+
let data = vec![];
44+
let result = HexDump.deserialize(data).unwrap();
45+
assert_eq!(result, "");
46+
}
47+
}

src/contentviews/hex_stream.rs

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
use crate::contentviews::{Prettify, PrettifyError, Reencode, ReencodeError};
2+
use pretty_hex::{HexConfig, PrettyHex};
3+
use std::num::ParseIntError;
4+
5+
pub struct HexStream;
6+
7+
impl Prettify for HexStream {
8+
fn name(&self) -> &'static str {
9+
"Hex Stream"
10+
}
11+
12+
fn deserialize(&self, data: Vec<u8>) -> Result<String, PrettifyError> {
13+
Ok(data
14+
.hex_conf(HexConfig {
15+
title: false,
16+
ascii: false,
17+
width: 0,
18+
group: 0,
19+
chunk: 0,
20+
max_bytes: usize::MAX,
21+
display_offset: 0,
22+
})
23+
.to_string())
24+
}
25+
}
26+
27+
impl Reencode for HexStream {
28+
fn serialize(&self, data: String) -> anyhow::Result<Vec<u8>, ReencodeError> {
29+
(0..data.len())
30+
.step_by(2)
31+
.map(|i| u8::from_str_radix(&data[i..i + 2], 16))
32+
.collect::<anyhow::Result<Vec<u8>, ParseIntError>>()
33+
.map_err(|e| ReencodeError::InvalidFormat(e.to_string()))
34+
}
35+
}
36+
37+
#[cfg(test)]
38+
mod tests {
39+
use super::*;
40+
41+
#[test]
42+
fn test_hexstream_deserialize() {
43+
let data = b"foo".to_vec();
44+
let result = HexStream.deserialize(data).unwrap();
45+
assert_eq!(result, "666f6f");
46+
}
47+
48+
#[test]
49+
fn test_hexstream_deserialize_empty() {
50+
let data = vec![];
51+
let result = HexStream.deserialize(data).unwrap();
52+
assert_eq!(result, "");
53+
}
54+
55+
#[test]
56+
fn test_hexstream_serialize() {
57+
let data = "666f6f".to_string();
58+
let result = HexStream.serialize(data).unwrap();
59+
assert_eq!(result, b"foo");
60+
}
61+
}

0 commit comments

Comments
 (0)