product-os-proxy 0.0.19

Product OS : Proxy builds on the work of hudsucker, taking it to the next level with a man-in-the-middle proxy server that can tunnel traffic through a VPN utilising Product OS : VPN.
//! MITM (Man-In-The-Middle) proxy implementation.
//!
//! This module provides the core MITM proxy functionality, including:
//! - HTTP/HTTPS request/response interception
//! - WebSocket message handling
//! - TLS certificate generation and management
//! - Content encoding/decoding
//!
//! The main traits are [`HttpHandler`] and [`WebSocketHandler`], which allow
//! custom processing of intercepted traffic.

#[cfg(feature = "decoder")]
#[allow(dead_code)]
mod decoder;
mod proxy;
mod rewind;

pub mod certificate_authority;

use hyper::{Request, Response, Uri};
use std::future::Future;

use std::net::SocketAddr;
use tokio_tungstenite::tungstenite::Message;

pub(crate) use rewind::Rewind;

pub use hyper;
use product_os_http_body::BodyBytes as Body;
pub use product_os_http_body::BodyError as Error;

pub use tokio_rustls::rustls;
pub use tokio_tungstenite;

#[cfg(feature = "decoder")]
#[allow(unused_imports)]
pub use decoder::{decode_request, decode_response};

#[cfg(feature = "vpn")]
use hyper::header::{HeaderName, HeaderValue};
#[cfg(feature = "vpn")]
use product_os_http::HeaderMap;
#[cfg(feature = "vpn")]
use product_os_http_body::Bytes;
#[cfg(feature = "vpn")]
use product_os_request::ProductOSResponse;

pub use proxy::*;

/// Result type for request handling - either forward the request or bypass with a response.
///
/// This enum allows middleware to either:
/// - Continue processing the request normally ([`Request`](RequestOrResponse::Request))
/// - Short-circuit and return a response immediately ([`Response`](RequestOrResponse::Response))
#[derive(Debug)]
pub enum RequestOrResponse {
    /// HTTP Request - continue normal proxy flow
    Request(Request<Body>),
    /// HTTP Response - bypass proxy and return this response to client
    Response(Response<Body>),
}

/// Context information for HTTP request/response handling.
///
/// Contains metadata about the client connection that can be used
/// for logging, filtering, or custom processing logic.
#[derive(Clone, Debug, Eq, Hash, PartialEq)]
pub struct HttpContext {
    /// The socket address of the client that made the request
    pub client_addr: SocketAddr,
}

/// Context information for WebSocket message handling.
///
/// Indicates the direction of the WebSocket message flow.
#[derive(Clone, Debug, Eq, Hash, PartialEq)]
pub enum WebSocketContext {
    /// Message from client to server
    ClientToServer {
        /// Client socket address
        src: SocketAddr,
        /// Server URI
        dst: Uri,
    },
    /// Message from server to client
    ServerToClient {
        /// Server URI
        src: Uri,
        /// Client socket address
        dst: SocketAddr,
    },
}

/// Trait for handling HTTP requests and responses in the proxy.
///
/// Implement this trait to create custom middleware that can inspect, modify,
/// or block HTTP traffic flowing through the proxy.
///
/// All methods have default implementations that pass traffic through unchanged.
///
/// # Examples
///
/// ```ignore
/// use product_os_proxy::mitm::{HttpHandler, HttpContext, RequestOrResponse};
/// use product_os_http::{Request, Response};
/// use product_os_http_body::BodyBytes as Body;
/// use std::future::Future;
///
/// #[derive(Clone)]
/// struct LoggingHandler;
///
/// impl HttpHandler for LoggingHandler {
///     fn handle_request(
///         &mut self,
///         ctx: &HttpContext,
///         request: Request<Body>,
///     ) -> impl Future<Output = RequestOrResponse> + Send {
///         async {
///             RequestOrResponse::Request(request)
///         }
///     }
///     
///     fn handle_response(
///         &mut self,
///         _ctx: &HttpContext,
///         _original_request: Request<Body>,
///         response: Response<Body>,
///     ) -> impl Future<Output = Response<Body>> + Send {
///         async { response }
///     }
/// }
/// ```
pub trait HttpHandler: Clone + Send + Sync + 'static {
    /// Handle an HTTP request before it's forwarded to the destination.
    ///
    /// # Arguments
    ///
    /// * `ctx` - Context information about the client connection
    /// * `request` - The HTTP request to process
    ///
    /// # Returns
    ///
    /// * `RequestOrResponse::Request(req)` - Forward the (possibly modified) request
    /// * `RequestOrResponse::Response(res)` - Return a response immediately without forwarding
    ///
    /// # Default
    ///
    /// Passes the request through unchanged.
    fn handle_request(
        &mut self,
        _ctx: &HttpContext,
        request: Request<Body>,
    ) -> impl Future<Output = RequestOrResponse> + Send {
        async { RequestOrResponse::Request(request) }
    }

    /// Handle an HTTP response before it's returned to the client.
    ///
    /// # Arguments
    ///
    /// * `ctx` - Context information about the client connection
    /// * `original_request` - The original request (for reference)
    /// * `response` - The HTTP response from the server
    ///
    /// # Returns
    ///
    /// The (possibly modified) response to return to the client
    ///
    /// # Default
    ///
    /// Passes the response through unchanged.
    fn handle_response(
        &mut self,
        _ctx: &HttpContext,
        _: Request<Body>, // original_request
        response: Response<Body>,
    ) -> impl Future<Output = Response<Body>> + Send {
        async { response }
    }

    #[cfg(feature = "vpn")]
    #[allow(dead_code)]
    fn handle_product_os_response(
        &mut self,
        _ctx: &HttpContext,
        _: Request<Body>, // original_request
        response: ProductOSResponse<Body>,
    ) -> impl Future<Output = Response<Body>> + Send {
        async {
            let status = response
                .try_status()
                .unwrap_or(hyper::StatusCode::INTERNAL_SERVER_ERROR);

            let mut headers = HeaderMap::new();
            for (name, value) in &response.try_get_headers().unwrap_or_default() {
                let header_name = match HeaderName::from_bytes(name.as_bytes()) {
                    Ok(name) => name,
                    Err(e) => {
                        tracing::error!("Error with header kind: {:?}", e);
                        continue;
                    }
                };
                let header_value = match HeaderValue::from_str(value.as_str()) {
                    Ok(v) => v,
                    Err(e) => {
                        tracing::error!("Invalid header value for '{}': {:?}", name, e);
                        continue;
                    }
                };
                headers.insert(header_name, header_value);
            }

            let Some((_, mut body)) = response.try_parts() else {
                return Response::builder()
                    .status(hyper::StatusCode::INTERNAL_SERVER_ERROR)
                    .body(Body::empty())
                    .unwrap_or_else(|_| Response::new(Body::empty()));
            };
            let body_bytes = match body.as_bytes().await {
                Ok(bytes) => bytes.to_vec(),
                Err(e) => {
                    tracing::error!("Error converting body to bytes: {:?}", e);
                    Vec::new()
                }
            };

            let response_body = Body::new(Bytes::from(body_bytes));

            let mut response_to_return = Response::builder();

            let mut last_name: Option<HeaderName> = None;
            for (name, value) in headers {
                let header_name = match name {
                    Some(n) => {
                        last_name = Some(n.clone());
                        n
                    }
                    None => match &last_name {
                        Some(n) => n.clone(),
                        None => continue,
                    },
                };
                response_to_return = response_to_return.header(header_name, value);
            }

            response_to_return = response_to_return.status(status);
            response_to_return
                .body(response_body)
                .unwrap_or_else(|_| Response::new(Body::empty()))
        }
    }

    /// Determines whether to intercept this request for MITM processing.
    ///
    /// # Arguments
    ///
    /// * `ctx` - Context information about the client connection
    /// * `req` - The HTTP request to evaluate
    ///
    /// # Returns
    ///
    /// * `true` - Intercept and process this request (default)
    /// * `false` - Pass through without interception
    ///
    /// # Default
    ///
    /// Always returns `true` (intercept all requests).
    fn should_intercept(
        &mut self,
        _ctx: &HttpContext,
        _req: &Request<Body>,
    ) -> impl Future<Output = bool> + Send {
        async { true }
    }
}

/// Trait for handling WebSocket messages in the proxy.
///
/// Implement this trait to create custom middleware that can inspect, modify,
/// or block WebSocket messages flowing through the proxy.
///
/// # Examples
///
/// ```ignore
/// use product_os_proxy::mitm::{WebSocketHandler, WebSocketContext};
/// use tokio_tungstenite::tungstenite::Message;
/// use std::future::Future;
///
/// #[derive(Clone)]
/// struct LoggingWebSocketHandler;
///
/// impl WebSocketHandler for LoggingWebSocketHandler {
///     fn handle_message(
///         &mut self,
///         _ctx: &WebSocketContext,
///         message: Message,
///     ) -> impl Future<Output = Option<Message>> + Send {
///         async { Some(message) }
///     }
/// }
/// ```
pub trait WebSocketHandler: Clone + Send + Sync + 'static {
    /// Handle a WebSocket message.
    ///
    /// # Arguments
    ///
    /// * `ctx` - Context indicating message direction (client-to-server or server-to-client)
    /// * `message` - The WebSocket message to process
    ///
    /// # Returns
    ///
    /// * `Some(message)` - Forward the (possibly modified) message
    /// * `None` - Drop the message (don't forward)
    ///
    /// # Default
    ///
    /// Passes all messages through unchanged.
    fn handle_message(
        &mut self,
        _ctx: &WebSocketContext,
        message: Message,
    ) -> impl Future<Output = Option<Message>> + Send {
        async { Some(message) }
    }
}