1#![cfg_attr(not(feature = "std"), no_std)]
2
3#[cfg(all(test, not(feature = "std")))]
28extern crate std;
29#[cfg(all(test, not(feature = "std")))]
30use std::{format, vec, vec::Vec};
31
32#[cfg(not(feature = "std"))]
33use core::{
34 error::Error,
35 fmt::{Debug, Display, Formatter},
36 net::IpAddr,
37 str::FromStr,
38};
39#[cfg(feature = "std")]
40use std::{
41 error::Error,
42 fmt::{Debug, Display, Formatter},
43 net::IpAddr,
44 str::FromStr,
45};
46use uncased::UncasedStr;
47
48#[derive(Debug)]
49pub enum RfcError {
50 InvalidIdentifier,
51 InvalidPort,
52 UnknownParameter,
53 MalformedParameter,
54}
55
56impl Display for RfcError {
57 fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
58 let str = match self {
59 RfcError::InvalidIdentifier => "Invalid node identifier",
60 RfcError::InvalidPort => "Invalid node port",
61 RfcError::UnknownParameter => "Unknown parameter name",
62 RfcError::MalformedParameter => "Parameter doesn't consist of key value pair",
63 };
64 write!(f, "{}", str)
65 }
66}
67
68impl Error for RfcError {}
69
70pub fn parse(header_value: &str) -> impl DoubleEndedIterator<Item = Result<Forwarded, RfcError>> {
72 header_value.split(',').map(str::trim).map(Forwarded::parse)
73}
74
75#[test]
76fn test_parse() {
77 assert_eq!(
78 parse("for=192.0.2.60;proto=http;by=203.0.113.43,for=192.168.10.10")
79 .collect::<Result<Vec<_>, _>>()
80 .unwrap(),
81 vec![
82 Forwarded {
83 forwarded_for: Some(NodeIdentifier::parse("192.0.2.60").unwrap()),
84 forwarded_by: Some(NodeIdentifier::parse("203.0.113.43").unwrap()),
85 protocol: Some("http"),
86 ..Default::default()
87 },
88 Forwarded {
89 forwarded_for: Some(NodeIdentifier::parse("192.168.10.10").unwrap()),
90 ..Default::default()
91 },
92 ]
93 )
94}
95
96#[derive(Debug, Default, PartialEq)]
97pub struct Forwarded<'a> {
98 pub forwarded_for: Option<NodeIdentifier<'a>>,
99 pub forwarded_by: Option<NodeIdentifier<'a>>,
100 pub host: Option<&'a str>,
101 pub protocol: Option<&'a str>,
102}
103
104impl<'a> Forwarded<'a> {
105 fn parse(forward: &'a str) -> Result<Self, RfcError> {
106 let mut result = Forwarded::default();
107
108 let parts = forward.split(';');
109
110 for part in parts {
111 if let Some(i) = part.find('=') {
112 let param = UncasedStr::new(&part[..i]);
113 let value = &part[i + 1..];
114 if param == "by" {
115 result.forwarded_by = Some(NodeIdentifier::parse(value.trim_matches('"'))?);
116 }
117 if param == "for" {
118 result.forwarded_for = Some(NodeIdentifier::parse(value.trim_matches('"'))?);
119 }
120 if param == "host" {
121 result.host = Some(value);
122 }
123 if param == "proto" {
124 result.protocol = Some(value);
125 }
126 } else {
127 return Err(RfcError::MalformedParameter);
128 }
129 }
130
131 Ok(result)
132 }
133}
134
135impl Display for Forwarded<'_> {
136 fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
137 let mut needs_delim = false;
138 if let Some(ident) = &self.forwarded_for {
139 if ident.display_needs_quote() {
140 write!(f, "for=\"{}\"", ident)?;
141 } else {
142 write!(f, "for={}", ident)?;
143 }
144 needs_delim = true;
145 }
146 if let Some(ident) = &self.forwarded_by {
147 if needs_delim {
148 write!(f, ";")?;
149 }
150
151 if ident.display_needs_quote() {
152 write!(f, "by=\"{}\"", ident)?;
153 } else {
154 write!(f, "by={}", ident)?;
155 }
156 needs_delim = true;
157 }
158 if let Some(ident) = &self.host {
159 if needs_delim {
160 write!(f, ";")?;
161 }
162
163 write!(f, "host={}", ident)?;
164 needs_delim = true;
165 }
166 if let Some(ident) = &self.protocol {
167 if needs_delim {
168 write!(f, ";")?;
169 }
170
171 write!(f, "proto={}", ident)?;
172 }
173
174 Ok(())
175 }
176}
177
178#[test]
179fn test_parse_forwarded() {
180 assert_eq!(
181 Forwarded {
182 forwarded_for: Some(NodeIdentifier::parse("1.2.3.4").unwrap()),
183 ..Default::default()
184 },
185 Forwarded::parse("for=1.2.3.4").unwrap()
186 );
187 assert_eq!(
188 Forwarded {
189 forwarded_for: Some(NodeIdentifier::parse("1.2.3.4").unwrap()),
190 ..Default::default()
191 },
192 Forwarded::parse("For=1.2.3.4").unwrap()
193 );
194 assert_eq!(
195 Forwarded {
196 forwarded_for: Some(NodeIdentifier::parse("1.2.3.4").unwrap()),
197 forwarded_by: Some(NodeIdentifier::parse("[1::1]:80").unwrap()),
198 host: Some("foo"),
199 protocol: Some("https")
200 },
201 Forwarded::parse("for=1.2.3.4;by=\"[1::1]:80\";host=foo;proto=https").unwrap()
202 );
203}
204
205#[test]
206fn test_display_forwarded() {
207 assert_eq!(
208 format!(
209 "{}",
210 Forwarded {
211 forwarded_for: Some(NodeIdentifier::parse("1.2.3.4").unwrap()),
212 ..Default::default()
213 }
214 ),
215 "for=1.2.3.4"
216 );
217 assert_eq!(
218 format!(
219 "{}",
220 Forwarded {
221 forwarded_for: Some(NodeIdentifier::parse("1.2.3.4").unwrap()),
222 forwarded_by: Some(NodeIdentifier::parse("[1::1]:80").unwrap()),
223 host: Some("foo"),
224 protocol: Some("https")
225 }
226 ),
227 "for=1.2.3.4;by=\"[1::1]:80\";host=foo;proto=https"
228 );
229}
230
231#[derive(Debug, Eq, PartialEq)]
232pub struct NodeIdentifier<'a> {
233 pub name: NodeName<'a>,
234 pub port: Option<u16>,
235}
236
237impl<'a> NodeIdentifier<'a> {
238 fn parse(name: &'a str) -> Result<Self, RfcError> {
239 match (name.rfind(':'), name.rfind(']')) {
240 (Some(delim), Some(ip6_end)) if delim > ip6_end => {
241 Self::parse_name_port(&name[0..delim], Some(&name[delim + 1..]))
242 }
243 (Some(delim), None) => Self::parse_name_port(&name[..delim], Some(&name[delim + 1..])),
244 _ => Self::parse_name_port(name, None),
245 }
246 }
247
248 fn parse_name_port(name: &'a str, port: Option<&str>) -> Result<Self, RfcError> {
249 Ok(NodeIdentifier {
250 name: NodeName::parse(name)?,
251 port: port
252 .map(u16::from_str)
253 .transpose()
254 .map_err(|_| RfcError::InvalidPort)?,
255 })
256 }
257
258 pub fn ip(&self) -> Option<&IpAddr> {
259 self.name.ip()
260 }
261
262 fn display_needs_quote(&self) -> bool {
264 self.port.is_some() || matches!(self.name, NodeName::Ip(IpAddr::V6(_)))
265 }
266}
267
268impl Display for NodeIdentifier<'_> {
269 fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
270 match self.port {
271 Some(port) => write!(f, "{}:{}", self.name, port),
272 None => write!(f, "{}", self.name),
273 }
274 }
275}
276
277#[test]
278fn test_parse_node_identifier() {
279 assert_eq!(
280 NodeIdentifier {
281 name: NodeName::Ip("1.2.3.4".parse().unwrap()),
282 port: None
283 },
284 NodeIdentifier::parse("1.2.3.4").unwrap()
285 );
286 assert_eq!(
287 NodeIdentifier {
288 name: NodeName::Ip("1.2.3.4".parse().unwrap()),
289 port: Some(8080)
290 },
291 NodeIdentifier::parse("1.2.3.4:8080").unwrap()
292 );
293 assert_eq!(
294 NodeIdentifier {
295 name: NodeName::Ip("2001:db8:cafe::17".parse().unwrap()),
296 port: Some(8080)
297 },
298 NodeIdentifier::parse("[2001:db8:cafe::17]:8080").unwrap()
299 );
300
301 assert!(matches!(
302 NodeIdentifier::parse("unknown:99999").unwrap_err(),
303 RfcError::InvalidPort
304 ));
305}
306
307#[test]
308fn test_node_identifier_ip() {
309 assert_eq!(
310 Some(&IpAddr::from([192, 0, 2, 42])),
311 NodeIdentifier::parse("192.0.2.42:31337").unwrap().ip()
312 );
313 assert_eq!(
314 Some(&IpAddr::from([0x2001, 0xdb8, 0, 0, 0, 0, 0, 0x45])),
315 NodeIdentifier::parse("[2001:db8::45]:31337").unwrap().ip()
316 );
317}
318
319#[test]
320fn test_display_node_identifier() {
321 assert_eq!(
322 format!(
323 "{}",
324 NodeIdentifier {
325 name: NodeName::Ip("1.2.3.4".parse().unwrap()),
326 port: None
327 }
328 ),
329 "1.2.3.4"
330 );
331 assert_eq!(
332 format!(
333 "{}",
334 NodeIdentifier {
335 name: NodeName::Ip("1.2.3.4".parse().unwrap()),
336 port: Some(8080)
337 }
338 ),
339 "1.2.3.4:8080"
340 );
341 assert_eq!(
342 format!(
343 "{}",
344 NodeIdentifier {
345 name: NodeName::Ip("2001:db8:cafe::17".parse().unwrap()),
346 port: Some(8080)
347 }
348 ),
349 "[2001:db8:cafe::17]:8080"
350 );
351}
352
353#[derive(Debug, Eq, PartialEq)]
354pub enum NodeName<'a> {
355 Ip(IpAddr),
356 Unknown,
357 Obfuscated(&'a str),
358}
359
360impl<'a> NodeName<'a> {
361 fn parse(name: &'a str) -> Result<Self, RfcError> {
362 match name {
363 "unknown" => Ok(NodeName::Unknown),
364 obfuscated if obfuscated.starts_with("_") => {
365 if obfuscated
366 .chars()
367 .all(|c| c.is_alphanumeric() || c == '.' || c == '_')
368 {
369 Ok(NodeName::Obfuscated(obfuscated))
370 } else {
371 Err(RfcError::InvalidIdentifier)
372 }
373 }
374 ip6 if ip6.starts_with('[') && ip6.ends_with(']') => ip6[1..ip6.len() - 1]
375 .parse()
376 .map(IpAddr::V6)
377 .map(NodeName::Ip)
378 .map_err(|_| RfcError::InvalidIdentifier),
379 ip4 => ip4
380 .parse()
381 .map(IpAddr::V4)
382 .map(NodeName::Ip)
383 .map_err(|_| RfcError::InvalidIdentifier),
384 }
385 }
386
387 pub fn ip(&self) -> Option<&IpAddr> {
388 if let NodeName::Ip(ip) = self {
389 Some(ip)
390 } else {
391 None
392 }
393 }
394}
395
396impl Display for NodeName<'_> {
397 fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
398 match self {
399 NodeName::Ip(IpAddr::V4(ip)) => write!(f, "{}", ip),
400 NodeName::Ip(IpAddr::V6(ip)) => write!(f, "[{}]", ip),
401 NodeName::Unknown => {
402 write!(f, "unknown")
403 }
404 NodeName::Obfuscated(name) => write!(f, "{}", name),
405 }
406 }
407}
408
409#[test]
410fn test_parse_node_name() {
411 assert_eq!(
412 NodeName::Ip("1.2.3.4".parse().unwrap()),
413 NodeName::parse("1.2.3.4").unwrap()
414 );
415 assert_eq!(
416 NodeName::Ip("2001:db8:cafe::17".parse().unwrap()),
417 NodeName::parse("[2001:db8:cafe::17]").unwrap()
418 );
419 assert_eq!(NodeName::Unknown, NodeName::parse("unknown").unwrap());
420 assert_eq!(
421 NodeName::Obfuscated("_FOO"),
422 NodeName::parse("_FOO").unwrap()
423 );
424
425 assert!(matches!(
427 NodeName::parse("_FOO-INVALID").unwrap_err(),
428 RfcError::InvalidIdentifier
429 ));
430 assert!(matches!(
431 NodeName::parse("FOO").unwrap_err(),
432 RfcError::InvalidIdentifier
433 ));
434
435 assert!(matches!(
437 NodeName::parse("2001:db8:cafe::17").unwrap_err(),
438 RfcError::InvalidIdentifier
439 ));
440}
441
442#[test]
443fn test_display_node_name() {
444 assert_eq!(
445 format!("{}", NodeName::Ip("1.2.3.4".parse().unwrap())),
446 "1.2.3.4"
447 );
448 assert_eq!(
449 format!("{}", NodeName::Ip("2001:db8:cafe::17".parse().unwrap())),
450 "[2001:db8:cafe::17]"
451 );
452 assert_eq!(format!("{}", NodeName::Unknown), "unknown");
453 assert_eq!(format!("{}", NodeName::Obfuscated("_FOO")), "_FOO");
454}