ideamans_hudsucker/
lib.rs

1#![cfg_attr(docsrs, feature(doc_cfg))]
2
3//! Hudsucker is a MITM HTTP/S proxy that allows you to:
4//!
5//! - Modify HTTP/S requests
6//! - Modify HTTP/S responses
7//! - Modify WebSocket messages
8//!
9//! ## Features
10//!
11//! - `decoder`: Enables [`decode_request`] and [`decode_response`] helpers
12//!   (enabled by default).
13//! - `full`: Enables all features.
14//! - `http2`: Enables HTTP/2 support.
15//! - `native-tls-client`: Enables
16//!   [`ProxyBuilder::with_native_tls_connector`](builder::ProxyBuilder::with_native_tls_connector).
17//! - `openssl-ca`: Enables
18//!   [`OpensslAuthority`](certificate_authority::OpensslAuthority).
19//! - `rcgen-ca`: Enables
20//!   [`RcgenAuthority`](certificate_authority::RcgenAuthority) (enabled by
21//!   default).
22//! - `rustls-client`: Enables
23//!   [`ProxyBuilder::with_rustls_connector`](builder::ProxyBuilder::with_rustls_connector)
24//!   (enabled by default).
25
26mod body;
27#[cfg(feature = "decoder")]
28mod decoder;
29mod error;
30mod noop;
31mod proxy;
32mod rewind;
33
34pub mod certificate_authority;
35
36use futures::{Sink, SinkExt, Stream, StreamExt};
37use hyper::{Request, Response, StatusCode, Uri};
38use std::net::SocketAddr;
39use tokio_tungstenite::tungstenite::{self, Message};
40use tracing::error;
41
42pub use futures;
43pub use hyper;
44pub use hyper_util;
45#[cfg(feature = "openssl-ca")]
46pub use openssl;
47#[cfg(feature = "rcgen-ca")]
48pub use rcgen;
49pub use tokio_rustls::rustls;
50pub use tokio_tungstenite;
51
52pub use body::Body;
53#[cfg(feature = "decoder")]
54pub use decoder::{decode_request, decode_response};
55pub use error::Error;
56pub use noop::*;
57pub use proxy::*;
58
59/// Enum representing either an HTTP request or response.
60#[derive(Debug)]
61pub enum RequestOrResponse {
62    /// HTTP Request
63    Request(Request<Body>),
64    /// HTTP Response
65    Response(Response<Body>),
66}
67
68impl From<Request<Body>> for RequestOrResponse {
69    fn from(req: Request<Body>) -> Self {
70        Self::Request(req)
71    }
72}
73
74impl From<Response<Body>> for RequestOrResponse {
75    fn from(res: Response<Body>) -> Self {
76        Self::Response(res)
77    }
78}
79
80/// Context for HTTP requests and responses.
81///
82/// Contains information about the request being processed. This context is
83/// passed to both `handle_request` and `handle_response`, allowing handlers to
84/// correlate requests with their responses, which is especially useful for
85/// HTTP/2 multiplexing.
86#[derive(Clone, Debug, Eq, Hash, PartialEq)]
87pub struct HttpContext {
88    /// Address of the client that is sending the request.
89    pub client_addr: SocketAddr,
90    /// HTTP method of the request.
91    pub request_method: hyper::Method,
92    /// URI of the request.
93    pub request_uri: Uri,
94}
95
96/// Context for websocket messages.
97#[derive(Clone, Debug, Eq, Hash, PartialEq)]
98pub enum WebSocketContext {
99    #[non_exhaustive]
100    ClientToServer {
101        /// Address of the client.
102        src: SocketAddr,
103        /// URI of the server.
104        dst: Uri,
105    },
106    #[non_exhaustive]
107    ServerToClient {
108        /// URI of the server.
109        src: Uri,
110        /// Address of the client.
111        dst: SocketAddr,
112    },
113}
114
115/// Handler for HTTP requests and responses.
116///
117/// Each request/response pair is passed to the same instance of the handler.
118pub trait HttpHandler: Clone + Send + Sync + 'static {
119    /// This handler will be called for each HTTP request. It can either return
120    /// a modified request, or a response. If a request is returned, it will
121    /// be sent to the upstream server. If a response is returned, it will
122    /// be sent to the client.
123    fn handle_request(
124        &mut self,
125        _ctx: &HttpContext,
126        req: Request<Body>,
127    ) -> impl Future<Output = RequestOrResponse> + Send {
128        async { req.into() }
129    }
130
131    /// This handler will be called for each HTTP response. It can modify a
132    /// response before it is forwarded to the client.
133    fn handle_response(
134        &mut self,
135        _ctx: &HttpContext,
136        res: Response<Body>,
137    ) -> impl Future<Output = Response<Body>> + Send {
138        async { res }
139    }
140
141    /// This handler will be called if a proxy request fails. Default response
142    /// is a 502 Bad Gateway.
143    fn handle_error(
144        &mut self,
145        _ctx: &HttpContext,
146        err: hyper_util::client::legacy::Error,
147    ) -> impl Future<Output = Response<Body>> + Send {
148        async move {
149            error!("Failed to forward request: {}", err);
150            Response::builder()
151                .status(StatusCode::BAD_GATEWAY)
152                .body(Body::empty())
153                .expect("Failed to build response")
154        }
155    }
156
157    /// Whether a CONNECT request should be intercepted. Defaults to `true` for
158    /// all requests.
159    fn should_intercept(
160        &mut self,
161        _ctx: &HttpContext,
162        _req: &Request<Body>,
163    ) -> impl Future<Output = bool> + Send {
164        async { true }
165    }
166}
167
168/// Handler for WebSocket messages.
169///
170/// Messages sent over the same WebSocket Stream are passed to the same instance
171/// of the handler.
172pub trait WebSocketHandler: Clone + Send + Sync + 'static {
173    /// This handler is responsible for forwarding WebSocket messages from a
174    /// Stream to a Sink and recovering from any potential errors.
175    fn handle_websocket(
176        mut self,
177        ctx: WebSocketContext,
178        mut stream: impl Stream<Item = Result<Message, tungstenite::Error>> + Unpin + Send + 'static,
179        mut sink: impl Sink<Message, Error = tungstenite::Error> + Unpin + Send + 'static,
180    ) -> impl Future<Output = ()> + Send {
181        async move {
182            while let Some(message) = stream.next().await {
183                match message {
184                    Ok(message) => {
185                        let Some(message) = self.handle_message(&ctx, message).await else {
186                            continue;
187                        };
188
189                        match sink.send(message).await {
190                            Err(tungstenite::Error::ConnectionClosed) => (),
191                            Err(e) => error!("WebSocket send error: {}", e),
192                            _ => (),
193                        }
194                    }
195                    Err(e) => {
196                        error!("WebSocket message error: {}", e);
197
198                        match sink.send(Message::Close(None)).await {
199                            Err(tungstenite::Error::ConnectionClosed) => (),
200                            Err(e) => error!("WebSocket close error: {}", e),
201                            _ => (),
202                        };
203
204                        break;
205                    }
206                }
207            }
208        }
209    }
210
211    /// This handler will be called for each WebSocket message. It can return an
212    /// optional modified message. If None is returned the message will not
213    /// be forwarded.
214    fn handle_message(
215        &mut self,
216        _ctx: &WebSocketContext,
217        message: Message,
218    ) -> impl Future<Output = Option<Message>> + Send {
219        async { Some(message) }
220    }
221}