Skip to content

Commit 4a74dac

Browse files
committed
make contentviews interactive
1 parent 7c539a1 commit 4a74dac

File tree

7 files changed

+236
-77
lines changed

7 files changed

+236
-77
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: 44 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,31 +1,65 @@
1-
use anyhow::Result;
2-
use mitmproxy::contentviews::Contentview;
1+
use anyhow::{anyhow, Result};
2+
use mitmproxy::contentviews::{Prettify, Reencode};
33
use pyo3::prelude::*;
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 {
8+
impl Contentview {
99
pub fn new<'py>(
1010
py: Python<'py>,
11-
contentview: &'static dyn Contentview,
11+
contentview: &'static dyn Prettify,
1212
) -> PyResult<Bound<'py, Self>> {
13-
PyContentview(contentview).into_pyobject(py)
13+
Contentview(contentview).into_pyobject(py)
1414
}
1515
}
1616

1717
#[pymethods]
18-
impl PyContentview {
18+
impl Contentview {
19+
/// The name of this contentview.
1920
#[getter]
2021
pub fn name(&self) -> &str {
2122
self.0.name()
2223
}
2324

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

2830
fn __repr__(&self) -> PyResult<String> {
29-
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}"))
57+
}
58+
59+
fn __repr__(self_: PyRef<'_, Self>) -> PyResult<String> {
60+
Ok(format!(
61+
"<mitmproxy_rs.contentview.InteractiveContentview: {}>",
62+
self_.as_super().name()
63+
))
3064
}
3165
}

mitmproxy-rs/src/lib.rs

Lines changed: 26 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@ 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

@@ -85,12 +87,17 @@ mod mitmproxy_rs {
8587
#[pymodule]
8688
mod contentviews {
8789
use super::*;
88-
use crate::contentview::PyContentview;
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("hex", PyContentview::new(m.py(), &HexStream())?)
98+
m.add_contentview(&HexDump)?;
99+
m.add_interactive_contentview(&HexStream)?;
100+
Ok(())
94101
}
95102
}
96103

@@ -118,3 +125,19 @@ mod mitmproxy_rs {
118125
Ok(())
119126
}
120127
}
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+
}

src/contentviews/mod.rs

Lines changed: 34 additions & 63 deletions
Original file line numberDiff line numberDiff line change
@@ -1,81 +1,52 @@
1+
mod hex_dump;
2+
mod hex_stream;
3+
14
use anyhow::Result;
2-
use pretty_hex::{HexConfig, PrettyHex};
3-
use std::num::ParseIntError;
5+
use std::fmt::{Display, Formatter};
6+
7+
pub use hex_dump::HexDump;
8+
pub use hex_stream::HexStream;
49

510
#[derive(Debug)]
6-
pub enum SerializeError {
11+
pub enum ReencodeError {
712
InvalidFormat(String),
813
}
914

10-
pub trait Contentview: Send + Sync {
11-
fn name(&self) -> &str;
12-
fn deserialize(&self, data: Vec<u8>) -> Result<String>;
13-
}
14-
15-
pub trait SerializableContentview: Contentview {
16-
fn serialize(&self, data: String) -> Result<Vec<u8>, SerializeError>;
17-
}
18-
19-
#[derive(Default)]
20-
pub struct HexStream();
21-
22-
impl Contentview for HexStream {
23-
fn name(&self) -> &str {
24-
"HexStream"
15+
impl Display for ReencodeError {
16+
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
17+
match self {
18+
ReencodeError::InvalidFormat(e) => {
19+
write!(f, "invalid format: {}", e)
20+
}
21+
}
2522
}
23+
}
2624

27-
fn deserialize(&self, data: Vec<u8>) -> Result<String> {
28-
Ok(data
29-
.hex_conf(HexConfig {
30-
title: false,
31-
ascii: false,
32-
width: 0,
33-
group: 0,
34-
chunk: 0,
35-
max_bytes: usize::MAX,
36-
display_offset: 0,
37-
})
38-
.to_string())
39-
}
25+
#[derive(Debug)]
26+
pub enum PrettifyError {
27+
Generic(String),
4028
}
4129

42-
impl SerializableContentview for HexStream {
43-
fn serialize(&self, data: String) -> Result<Vec<u8>, SerializeError> {
44-
(0..data.len())
45-
.step_by(2)
46-
.map(|i| u8::from_str_radix(&data[i..i + 2], 16))
47-
.collect::<Result<Vec<u8>, ParseIntError>>()
48-
.map_err(|e| {
49-
SerializeError::InvalidFormat(format!("Failed to parse hex string: {}", e))
50-
})
30+
impl Display for PrettifyError {
31+
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
32+
match self {
33+
PrettifyError::Generic(e) => {
34+
write!(f, "deserialize error: {}", e)
35+
}
36+
}
5137
}
5238
}
5339

54-
#[cfg(test)]
55-
mod tests {
56-
use super::*;
40+
pub trait Prettify: Send + Sync {
41+
fn name(&self) -> &str;
5742

58-
#[test]
59-
fn test_hexstream_deserialize() {
60-
let hex_stream = HexStream::default();
61-
let data = b"foo".to_vec();
62-
let result = hex_stream.deserialize(data).unwrap();
63-
assert_eq!(result, "666f6f");
43+
fn instance_name(&self) -> String {
44+
self.name().to_lowercase().replace(" ", "_")
6445
}
6546

66-
#[test]
67-
fn test_hexstream_deserialize_empty() {
68-
let hex_stream = HexStream::default();
69-
let data = vec![];
70-
let result = hex_stream.deserialize(data).unwrap();
71-
assert_eq!(result, "");
72-
}
47+
fn deserialize(&self, data: Vec<u8>) -> Result<String, PrettifyError>;
48+
}
7349

74-
#[test]
75-
fn test_hexstream_serialize() {
76-
let hex_stream = HexStream::default();
77-
let data = "666f6f".to_string();
78-
let result = hex_stream.serialize(data).unwrap();
79-
assert_eq!(result, b"foo");
80-
}
50+
pub trait Reencode: Send + Sync {
51+
fn serialize(&self, data: String) -> Result<Vec<u8>, ReencodeError>;
8152
}

0 commit comments

Comments
 (0)