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}