Skip to main content

ppp/
lib.rs

1//! A Proxy Protocol Parser written in Rust.
2//! Supports both text and binary versions of the header protocol.
3
4mod ip;
5
6pub mod v1;
7pub mod v2;
8
9/// The canonical way to determine when a streamed header should be retried in a streaming context.
10/// The protocol states that servers may choose to support partial headers or to close the connection if the header is not present all at once.
11pub trait PartialResult {
12    /// Tests whether this `Result` is successful or whether the error is terminal.
13    /// A terminal error will not result in a success even with more bytes.
14    /// Retrying with the same -- or more -- input will not change the result.
15    fn is_complete(&self) -> bool {
16        !self.is_incomplete()
17    }
18
19    /// Tests whether this `Result` is incomplete.
20    /// An action that leads to an incomplete result may have a different result with more bytes.
21    /// Retrying with the same input will not change the result.
22    fn is_incomplete(&self) -> bool;
23}
24
25impl<'a, T, E: PartialResult> PartialResult for Result<T, E> {
26    fn is_incomplete(&self) -> bool {
27        match self {
28            Ok(_) => false,
29            Err(error) => error.is_incomplete(),
30        }
31    }
32}
33
34impl<'a> PartialResult for v1::ParseError {
35    fn is_incomplete(&self) -> bool {
36        matches!(
37            self,
38            v1::ParseError::Partial
39                | v1::ParseError::MissingPrefix
40                | v1::ParseError::MissingProtocol
41                | v1::ParseError::MissingSourceAddress
42                | v1::ParseError::MissingDestinationAddress
43                | v1::ParseError::MissingSourcePort
44                | v1::ParseError::MissingDestinationPort
45                | v1::ParseError::MissingNewLine
46        )
47    }
48}
49
50impl<'a> PartialResult for v1::BinaryParseError {
51    fn is_incomplete(&self) -> bool {
52        match self {
53            v1::BinaryParseError::Parse(error) => error.is_incomplete(),
54            _ => false,
55        }
56    }
57}
58
59impl<'a> PartialResult for v2::ParseError {
60    fn is_incomplete(&self) -> bool {
61        matches!(
62            self,
63            v2::ParseError::Incomplete(..) | v2::ParseError::Partial(..)
64        )
65    }
66}
67
68/// An enumeration of the supported header version's parse results.
69/// Useful for parsing either version 1 or version 2 of the PROXY protocol.
70///
71/// ## Examples
72/// ```rust
73/// use ppp::{HeaderResult, PartialResult, v1, v2};
74///
75/// let input = "PROXY UNKNOWN\r\n";
76/// let header = HeaderResult::parse(input.as_bytes());
77///
78/// assert_eq!(header, Ok(v1::Header::new(input, v1::Addresses::Unknown)).into());
79/// ```
80#[derive(Debug, PartialEq)]
81#[must_use = "this `HeaderResult` may contain a V1 or V2 `Err` variant, which should be handled"]
82pub enum HeaderResult<'a> {
83    V1(Result<v1::Header<'a>, v1::BinaryParseError>),
84    V2(Result<v2::Header<'a>, v2::ParseError>),
85}
86
87impl<'a> From<Result<v1::Header<'a>, v1::BinaryParseError>> for HeaderResult<'a> {
88    fn from(result: Result<v1::Header<'a>, v1::BinaryParseError>) -> Self {
89        HeaderResult::V1(result)
90    }
91}
92
93impl<'a> From<Result<v2::Header<'a>, v2::ParseError>> for HeaderResult<'a> {
94    fn from(result: Result<v2::Header<'a>, v2::ParseError>) -> Self {
95        HeaderResult::V2(result)
96    }
97}
98
99impl<'a> PartialResult for HeaderResult<'a> {
100    fn is_incomplete(&self) -> bool {
101        match self {
102            Self::V1(result) => result.is_incomplete(),
103            Self::V2(result) => result.is_incomplete(),
104        }
105    }
106}
107
108impl<'a> HeaderResult<'a> {
109    /// Parses a PROXY protocol version 2 `Header`.
110    /// If the input is not a valid version 2 `Header`, attempts to parse a version 1 `Header`.  
111    pub fn parse(input: &'a [u8]) -> HeaderResult<'a> {
112        let header = v2::Header::try_from(input);
113
114        if header.is_complete() && header.is_err() {
115            v1::Header::try_from(input).into()
116        } else {
117            header.into()
118        }
119    }
120}