cloudfront_logs/types/
mod.rs

1pub(crate) use std::{marker::PhantomData, net::IpAddr, sync::Arc, time::Duration};
2use std::{
3    net::{Ipv4Addr, SocketAddr},
4    str::FromStr,
5};
6
7/// Marker for which validate the log line before parsing
8#[derive(Debug, Clone, Copy, PartialEq)]
9pub struct Validated;
10
11/// Marker for which does not validate the log line before parsing
12#[derive(Debug, Clone, Copy, PartialEq)]
13pub struct Unvalidated;
14
15#[cfg(feature = "chrono")]
16pub use chrono::{Datelike, NaiveDate, NaiveDateTime, NaiveTime, Timelike};
17
18#[cfg(feature = "time")]
19pub use time::{Date, OffsetDateTime, Time, UtcOffset};
20
21#[derive(Debug, Clone, PartialEq, strum::Display, strum::AsRefStr, strum::EnumString)]
22pub enum EdgeResultType {
23    Hit,
24    RefreshHit,
25    Miss,
26    LimitExceeded,
27    CapacityExceeded,
28    Error,
29    Redirect,
30
31    // AWS' docs forgot something to mention
32    LambdaGeneratedResponse,
33
34    // catch-all in case AWS' docs forgot something to mention
35    #[strum(default)]
36    Other(String),
37}
38
39#[derive(Debug, Clone, PartialEq, strum::Display, strum::AsRefStr, strum::EnumString)]
40pub enum DetailedEdgeResultType {
41    // same as EdgeResultType
42    Hit,
43    RefreshHit,
44    Miss,
45    LimitExceeded,
46    CapacityExceeded,
47    Error,
48    Redirect,
49
50    // AWS' docs forgot something to mention
51    LambdaGeneratedResponse,
52
53    // origin shield used
54    OriginShieldHit,
55
56    // origin request lambda@edge
57    MissGeneratedResponse,
58
59    // errors if EdgeResultType is Error
60    AbortedOrigin,
61    ClientCommError,
62    ClientGeoBlocked,
63    ClientHungUpRequest,
64    InvalidRequest,
65    InvalidRequestBlocked,
66    InvalidRequestCertificate,
67    InvalidRequestHeader,
68    InvalidRequestMethod,
69    OriginCommError,
70    OriginConnectError,
71    OriginContentRangeLengthError,
72    OriginDnsError,
73    OriginError,
74    OriginHeaderTooBigError,
75    OriginInvalidResponseError,
76    OriginReadError,
77    OriginWriteError,
78    OriginZeroSizeObjectError,
79    SlowReaderOriginError,
80
81    // catch-all in case AWS' docs forgot something to mention
82    #[strum(default)]
83    Other(String),
84}
85
86#[derive(Debug, Clone, Copy, PartialEq, strum::Display, strum::AsRefStr, strum::EnumString)]
87pub enum CsProtocol {
88    #[strum(serialize = "http")]
89    Http,
90    #[strum(serialize = "https")]
91    Https,
92    #[strum(serialize = "ws")]
93    Ws,
94    #[strum(serialize = "wss")]
95    Wss,
96}
97
98#[derive(Debug, Clone, Copy, PartialEq, strum::Display, strum::AsRefStr, strum::EnumString)]
99pub enum CsProtocolVersion {
100    #[strum(serialize = "HTTP/3.0")]
101    HTTP3_0,
102    #[strum(serialize = "HTTP/2.0")]
103    HTTP2_0,
104    #[strum(serialize = "HTTP/1.1")]
105    HTTP1_1,
106    #[strum(serialize = "HTTP/1.0")]
107    HTTP1_0,
108    #[strum(serialize = "HTTP/0.9")]
109    HTTP0_9,
110}
111
112// todo: <https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/secure-connections-supported-viewer-protocols-ciphers.html>
113
114#[derive(Debug, Clone, Copy, PartialEq, strum::Display, strum::AsRefStr, strum::EnumString)]
115pub enum SslProtocol {
116    #[strum(serialize = "TLSv1.3")]
117    TLSv1_3,
118    #[strum(serialize = "TLSv1.2")]
119    TLSv1_2,
120    #[strum(serialize = "TLSv1.1")]
121    TLSv1_1,
122    #[strum(serialize = "TLSv1")]
123    TLSv1_0,
124    #[strum(serialize = "SSLv3")]
125    SSLv3,
126}
127
128/// CloudFront seems to return one of three types of "IPs" if the field is set:
129/// * IP address (e.g. 1.2.3.4, 2001:db8:85a3:8d3:1319:8a2e:370:7348)
130/// * Socket address (e.g. 1.2.3.4:6969)
131/// * "Unknown"
132#[derive(Debug, Clone, PartialEq)]
133pub enum Addressable {
134    IpAddr(IpAddr),
135    Socket(SocketAddr),
136    Unknown,
137}
138
139impl From<IpAddr> for Addressable {
140    fn from(ip: IpAddr) -> Self {
141        Self::IpAddr(ip)
142    }
143}
144
145impl From<SocketAddr> for Addressable {
146    fn from(socket: SocketAddr) -> Self {
147        Self::Socket(socket)
148    }
149}
150
151impl TryFrom<&str> for Addressable {
152    type Error = &'static str;
153
154    fn try_from(input: &str) -> Result<Self, Self::Error> {
155        if input == "unknown" {
156            return Ok(Self::Unknown);
157        }
158        let maybe_ip = input.parse::<IpAddr>();
159        if let Ok(ip) = maybe_ip {
160            return Ok(Self::IpAddr(ip));
161        } else {
162            // special case: leading zeros (0123.045.067.089)
163            if input.starts_with('0') && input.contains('.') {
164                let octets = input
165                    .splitn(4, '.')
166                    .filter_map(|s| s.parse::<u8>().ok())
167                    .collect::<Vec<u8>>();
168                if octets.len() == 4 {
169                    return Ok(Self::IpAddr(IpAddr::V4(Ipv4Addr::new(
170                        octets[0], octets[1], octets[2], octets[3],
171                    ))));
172                }
173            }
174        }
175        input
176            .parse::<SocketAddr>()
177            .map(Self::Socket)
178            .map_err(|_e| "invalid X-Forwarded-For IP/socket address")
179    }
180}
181
182impl FromStr for Addressable {
183    type Err = &'static str;
184
185    fn from_str(input: &str) -> Result<Self, Self::Err> {
186        Self::try_from(input)
187    }
188}
189
190/// A list of [`Addressable`] items used in the `x-forwarded-for` header field
191///
192/// See [`Addressable`] for more details, especially why we cannot simply use IPv4/IPv6 only.
193#[derive(Debug, Clone, PartialEq)]
194pub struct ForwardedForAddrs(pub Vec<Addressable>);
195
196impl TryFrom<&str> for ForwardedForAddrs {
197    type Error = &'static str;
198
199    #[inline]
200    fn try_from(input: &str) -> Result<Self, Self::Error> {
201        const ESCAPED_SPACE: &str = "\\x20";
202        const ESCAPED_SPACE_LEN: usize = ESCAPED_SPACE.len();
203
204        let addresses: Vec<Addressable> = input
205            .split(',')
206            .map(|address| {
207                // Note: CloudFront logs use escaped strings for X-Forwarded-For IP lists
208                let trimmed = address.trim();
209                if trimmed.starts_with(ESCAPED_SPACE) {
210                    &trimmed[ESCAPED_SPACE_LEN..]
211                } else {
212                    trimmed
213                }
214            })
215            // .filter(|address| !address.is_empty())
216            .map(str::parse)
217            .collect::<Result<Vec<Addressable>, _>>()
218            .map_err(|_e| "invalid X-Forwarded-For IP(s)")?;
219        Ok(Self(addresses))
220    }
221}
222
223impl FromStr for ForwardedForAddrs {
224    type Err = &'static str;
225
226    fn from_str(input: &str) -> Result<Self, Self::Err> {
227        Self::try_from(input)
228    }
229}