Skip to main content

borer_core/
masquerade.rs

1use anyhow::Context as _;
2use tokio::{
3    io::{AsyncRead, AsyncWrite, AsyncWriteExt as _, copy_bidirectional},
4    net::TcpStream,
5};
6
7/// Handles connections that should receive a masquerade response instead of proxying.
8pub struct MasqueradeConnection<T> {
9    inner: T,
10}
11
12impl<T> MasqueradeConnection<T>
13where
14    T: AsyncRead + AsyncWrite + Unpin,
15{
16    /// Create a masquerade connection wrapper around the underlying stream.
17    pub fn new(ts: T) -> Self {
18        Self { inner: ts }
19    }
20
21    /// Send the configured masquerade response or a default 404 response.
22    pub async fn handle(mut self) -> anyhow::Result<()> {
23        let stream = &mut self.inner;
24        let local_addr: Option<String> = None;
25
26        match local_addr {
27            Some(ref local_addr) => {
28                info!("requested proxy local {}", local_addr);
29                let mut local_ts = TcpStream::connect(local_addr)
30                    .await
31                    .context(format!("proxy can't connect addr {}", local_addr))?;
32                debug!("proxy connect success {:?}", local_addr);
33
34                copy_bidirectional(stream, &mut local_ts).await.ok();
35            }
36            None => {
37                info!("response not found");
38                Self::resp_html(stream).await
39            }
40        }
41
42        Ok(())
43    }
44    async fn resp_html(stream: &mut T) {
45        stream
46            .write_all(
47                &b"HTTP/1.0 404 Not Found\r\n\
48                Content-Type: text/plain; charset=utf-8\r\n\
49                Content-length: 13\r\n\r\n\
50                404 Not Found"[..],
51            )
52            .await
53            .unwrap();
54    }
55}
56
57#[cfg(test)]
58mod tests {
59    use tokio::io::AsyncReadExt;
60
61    use super::MasqueradeConnection;
62
63    #[tokio::test]
64    async fn handle_without_local_target_returns_404_response() {
65        let (client, mut peer) = tokio::io::duplex(256);
66
67        let task = tokio::spawn(async move { MasqueradeConnection::new(client).handle().await });
68
69        let mut buf = Vec::new();
70        peer.read_to_end(&mut buf).await.unwrap();
71        task.await.unwrap().unwrap();
72
73        let response = String::from_utf8(buf).unwrap();
74        assert!(response.starts_with("HTTP/1.0 404 Not Found\r\n"));
75        assert!(response.contains("Content-Type: text/plain; charset=utf-8\r\n"));
76        assert!(response.ends_with("404 Not Found"));
77    }
78}