rama_net/forwarded/element/
mod.rs1use core::fmt;
2use core::net::{IpAddr, Ipv4Addr};
3use core::net::{Ipv6Addr, SocketAddr};
4
5use crate::std::collections::BTreeMap;
6use crate::std::string::String;
7use crate::std::vec::Vec;
8
9use super::{ForwardedProtocol, ForwardedVersion, NodeId};
10use crate::address::{Domain, HostWithOptPort};
11use crate::address::{Host, HostWithPort, SocketAddress};
12
13use rama_core::error::BoxError;
14
15mod parser;
16#[doc(inline)]
17pub(crate) use parser::{parse_one_plus_forwarded_elements, parse_single_forwarded_element};
18
19#[derive(Debug, Clone, PartialEq, Eq)]
20pub struct ForwardedElement {
24 by_node: Option<NodeId>,
25 for_node: Option<NodeId>,
26 authority: Option<ForwardedAuthority>,
27 proto: Option<ForwardedProtocol>,
28 proto_version: Option<ForwardedVersion>,
29
30 extensions: Option<BTreeMap<String, ExtensionValue>>,
36}
37
38#[derive(Debug, Clone, PartialEq, Eq)]
39struct ExtensionValue {
40 value: String,
41 quoted: bool,
42}
43
44#[derive(Debug, Clone, PartialEq, Eq, Hash)]
45pub struct ForwardedAuthority(pub HostWithOptPort);
47
48impl ForwardedAuthority {
49 #[must_use]
51 #[inline(always)]
52 pub const fn new(host: Host) -> Self {
53 Self(HostWithOptPort::new(host))
54 }
55
56 #[must_use]
58 #[inline(always)]
59 pub const fn new_with_port(host: Host, port: u16) -> Self {
60 Self(HostWithOptPort::new_with_port(host, port))
61 }
62}
63
64impl From<Host> for ForwardedAuthority {
65 #[inline(always)]
66 fn from(value: Host) -> Self {
67 Self::new(value)
68 }
69}
70
71impl From<Domain> for ForwardedAuthority {
72 #[inline(always)]
73 fn from(value: Domain) -> Self {
74 Self::new(value.into())
75 }
76}
77
78impl From<IpAddr> for ForwardedAuthority {
79 #[inline(always)]
80 fn from(value: IpAddr) -> Self {
81 Self::new(value.into())
82 }
83}
84
85impl From<Ipv4Addr> for ForwardedAuthority {
86 #[inline(always)]
87 fn from(value: Ipv4Addr) -> Self {
88 Self::new(value.into())
89 }
90}
91
92impl From<[u8; 4]> for ForwardedAuthority {
93 #[inline(always)]
94 fn from(value: [u8; 4]) -> Self {
95 Self::new(Host::Address(value.into()))
96 }
97}
98
99impl From<[u8; 16]> for ForwardedAuthority {
100 #[inline(always)]
101 fn from(value: [u8; 16]) -> Self {
102 Self::new(Host::Address(value.into()))
103 }
104}
105
106impl From<Ipv6Addr> for ForwardedAuthority {
107 #[inline(always)]
108 fn from(value: Ipv6Addr) -> Self {
109 Self::new(value.into())
110 }
111}
112
113impl From<SocketAddr> for ForwardedAuthority {
114 #[inline(always)]
115 fn from(value: SocketAddr) -> Self {
116 Self(HostWithOptPort {
117 host: Host::Address(value.ip()),
118 port: crate::address::OptPort::Set(value.port()),
119 })
120 }
121}
122
123impl From<SocketAddress> for ForwardedAuthority {
124 #[inline(always)]
125 fn from(value: SocketAddress) -> Self {
126 Self(HostWithOptPort {
127 host: Host::Address(value.ip_addr),
128 port: crate::address::OptPort::Set(value.port),
129 })
130 }
131}
132
133impl From<HostWithOptPort> for ForwardedAuthority {
134 #[inline(always)]
135 fn from(value: HostWithOptPort) -> Self {
136 Self(value)
137 }
138}
139
140impl From<HostWithPort> for ForwardedAuthority {
141 #[inline(always)]
142 fn from(value: HostWithPort) -> Self {
143 Self::new(value.into())
144 }
145}
146
147impl ForwardedElement {
148 pub fn merge(&mut self, other: Self) -> &mut Self {
150 if let Some(by_node) = other.by_node {
151 self.by_node = Some(by_node);
152 }
153 if let Some(for_node) = other.for_node {
154 self.for_node = Some(for_node);
155 }
156 if let Some(authority) = other.authority {
157 self.authority = Some(authority);
158 }
159 if let Some(proto) = other.proto {
160 self.proto = Some(proto);
161 }
162 if let Some(extensions) = other.extensions {
163 match &mut self.extensions {
164 Some(map) => {
165 map.extend(extensions);
166 }
167 None => {
168 self.extensions = Some(extensions);
169 }
170 }
171 }
172 self
173 }
174
175 #[must_use]
177 pub fn authority(&self) -> Option<HostWithOptPort> {
178 self.authority.as_ref().map(|authority| authority.0.clone())
179 }
180
181 pub fn new_forwarded_host(authority: impl Into<ForwardedAuthority>) -> Self {
184 Self {
185 by_node: None,
186 for_node: None,
187 authority: Some(authority.into()),
188 proto: None,
189 proto_version: None,
190 extensions: None,
191 }
192 }
193
194 rama_utils::macros::generate_set_and_with! {
195 pub fn forwarded_host(mut self, authority: impl Into<ForwardedAuthority>) -> Self {
198 self.authority = Some(authority.into());
199 self
200 }
201 }
202
203 #[must_use]
205 pub fn forwarded_host(&self) -> Option<&ForwardedAuthority> {
206 self.authority.as_ref()
207 }
208
209 pub fn new_forwarded_for(node_id: impl Into<NodeId>) -> Self {
213 Self {
214 by_node: None,
215 for_node: Some(node_id.into()),
216 authority: None,
217 proto: None,
218 proto_version: None,
219 extensions: None,
220 }
221 }
222
223 rama_utils::macros::generate_set_and_with! {
224 pub fn forwarded_for(mut self, node_id: impl Into<NodeId>) -> Self {
227 self.for_node = Some(node_id.into());
228 self
229 }
230 }
231
232 #[must_use]
234 pub fn forwarded_for(&self) -> Option<&NodeId> {
235 self.for_node.as_ref()
236 }
237
238 pub fn new_forwarded_by(node_id: impl Into<NodeId>) -> Self {
242 Self {
243 by_node: Some(node_id.into()),
244 for_node: None,
245 authority: None,
246 proto: None,
247 proto_version: None,
248 extensions: None,
249 }
250 }
251
252 rama_utils::macros::generate_set_and_with! {
253 pub fn forwarded_by(mut self, node_id: impl Into<NodeId>) -> Self {
256 self.by_node = Some(node_id.into());
257 self
258 }
259 }
260
261 #[must_use]
263 pub fn forwarded_by(&self) -> Option<&NodeId> {
264 self.by_node.as_ref()
265 }
266
267 #[must_use]
270 pub fn new_forwarded_proto(protocol: ForwardedProtocol) -> Self {
271 Self {
272 by_node: None,
273 for_node: None,
274 authority: None,
275 proto: Some(protocol),
276 proto_version: None,
277 extensions: None,
278 }
279 }
280
281 rama_utils::macros::generate_set_and_with! {
282 pub fn forwarded_proto(mut self, protocol: ForwardedProtocol) -> Self {
284 self.proto = Some(protocol);
285 self
286 }
287 }
288
289 #[must_use]
291 pub fn forwarded_proto(&self) -> Option<ForwardedProtocol> {
292 self.proto.clone()
293 }
294
295 #[must_use]
298 pub fn new_forwarded_version(version: ForwardedVersion) -> Self {
299 Self {
300 by_node: None,
301 for_node: None,
302 authority: None,
303 proto: None,
304 proto_version: Some(version),
305 extensions: None,
306 }
307 }
308
309 rama_utils::macros::generate_set_and_with! {
310 pub fn forwarded_version(mut self, version: ForwardedVersion) -> Self {
312 self.proto_version = Some(version);
313 self
314 }
315 }
316
317 #[must_use]
319 pub fn forwarded_version(&self) -> Option<ForwardedVersion> {
320 self.proto_version
321 }
322}
323
324impl fmt::Display for ForwardedAuthority {
325 #[inline(always)]
326 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
327 self.0.fmt(f)
328 }
329}
330
331impl fmt::Display for ForwardedElement {
332 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
333 let mut separator = "";
334
335 if let Some(ref by_node) = self.by_node {
336 write!(f, "by=")?;
337 let quoted =
338 by_node.has_any_port() || by_node.ip().map(|ip| ip.is_ipv6()).unwrap_or_default();
339 if quoted {
340 write!(f, r##""{by_node}""##)?;
341 } else {
342 by_node.fmt(f)?;
343 }
344 separator = ";";
345 }
346
347 if let Some(ref for_node) = self.for_node {
348 write!(f, "{separator}for=")?;
349 let quoted =
350 for_node.has_any_port() || for_node.ip().map(|ip| ip.is_ipv6()).unwrap_or_default();
351 if quoted {
352 write!(f, r##""{for_node}""##)?;
353 } else {
354 for_node.fmt(f)?;
355 }
356 separator = ";";
357 }
358
359 if let Some(ref authority) = self.authority {
360 write!(f, "{separator}host=")?;
361 let quoted = authority.0.port.is_explicit()
365 || matches!(authority.0.host, Host::Address(IpAddr::V6(_)));
366 if quoted {
367 write!(f, r##""{authority}""##)?;
368 } else {
369 authority.fmt(f)?;
370 }
371 separator = ";";
372 }
373
374 if let Some(ref proto) = self.proto {
375 write!(f, "{separator}proto=")?;
376 proto.fmt(f)?;
377 }
378
379 Ok(())
380 }
381}
382
383impl core::str::FromStr for ForwardedElement {
384 type Err = BoxError;
385
386 fn from_str(s: &str) -> Result<Self, Self::Err> {
387 parse_single_forwarded_element(s.as_bytes())
388 }
389}
390
391impl TryFrom<String> for ForwardedElement {
392 type Error = BoxError;
393
394 fn try_from(s: String) -> Result<Self, Self::Error> {
395 parse_single_forwarded_element(s.as_bytes())
396 }
397}
398
399impl TryFrom<&str> for ForwardedElement {
400 type Error = BoxError;
401
402 fn try_from(s: &str) -> Result<Self, Self::Error> {
403 parse_single_forwarded_element(s.as_bytes())
404 }
405}
406
407impl TryFrom<Vec<u8>> for ForwardedElement {
408 type Error = BoxError;
409
410 fn try_from(bytes: Vec<u8>) -> Result<Self, Self::Error> {
411 parse_single_forwarded_element(bytes.as_ref())
412 }
413}
414
415impl TryFrom<&[u8]> for ForwardedElement {
416 type Error = BoxError;
417
418 fn try_from(bytes: &[u8]) -> Result<Self, Self::Error> {
419 parse_single_forwarded_element(bytes)
420 }
421}
422
423impl core::str::FromStr for ForwardedAuthority {
424 type Err = BoxError;
425
426 fn from_str(s: &str) -> Result<Self, Self::Err> {
427 let address = HostWithOptPort::from_str(s)?;
428 Ok(Self(address))
429 }
430}
431
432#[cfg(test)]
433mod tests {
434 use super::*;
435
436 #[test]
437 fn test_forwarded_element_parse_invalid() {
438 for s in [
439 "",
440 "foobar",
441 "127.0.0.1",
442 "⌨️",
443 "for=_foo;for=_bar",
444 "for=foo,proto=http",
445 ] {
446 if let Ok(el) = ForwardedElement::try_from(s) {
447 panic!("unexpected parse success: input {s}: {el:?}");
448 }
449 }
450 }
451
452 #[test]
453 fn test_forwarded_element_parse_happy_spec() {
454 for (s, expected) in [
455 (
456 r##"for="_gazonk""##,
457 ForwardedElement::new_forwarded_for(NodeId::try_from("_gazonk").unwrap()),
458 ),
459 (
460 r##"For="[2001:db8:cafe::17]:4711""##,
461 ForwardedElement::new_forwarded_for(
462 NodeId::try_from("[2001:db8:cafe::17]:4711").unwrap(),
463 ),
464 ),
465 (
466 r##"For="[2001:db8:cafe::17]:4711";proto=http"##,
467 ForwardedElement {
468 by_node: None,
469 for_node: Some(NodeId::try_from("[2001:db8:cafe::17]:4711").unwrap()),
470 authority: None,
471 proto: Some(ForwardedProtocol::HTTP),
472 proto_version: None,
473 extensions: None,
474 },
475 ),
476 (
477 r##"For="[2001:db8:cafe::17]:4711";proto=http;foo=bar"##,
478 ForwardedElement {
479 by_node: None,
480 for_node: Some(NodeId::try_from("[2001:db8:cafe::17]:4711").unwrap()),
481 authority: None,
482 proto: Some(ForwardedProtocol::HTTP),
483 proto_version: None,
484 extensions: Some(
485 [(
486 "foo".to_owned(),
487 ExtensionValue {
488 value: "bar".to_owned(),
489 quoted: false,
490 },
491 )]
492 .into_iter()
493 .collect(),
494 ),
495 },
496 ),
497 (
498 r##"for=192.0.2.60;proto=http;by=203.0.113.43"##,
499 ForwardedElement {
500 by_node: Some(NodeId::try_from("203.0.113.43").unwrap()),
501 for_node: Some(NodeId::try_from("192.0.2.60").unwrap()),
502 authority: None,
503 proto: Some(ForwardedProtocol::HTTP),
504 proto_version: None,
505 extensions: None,
506 },
507 ),
508 ] {
509 let element = match ForwardedElement::try_from(s) {
510 Ok(el) => el,
511 Err(err) => panic!("failed to parse happy spec el '{s}': {err}"),
512 };
513 assert_eq!(element, expected, "input: {s}");
514 }
515 }
516
517 #[test]
522 fn regression_forwarded_quoted_pair_rfc7230() {
523 let el = ForwardedElement::try_from(r#"for=_a;ext="x\"y""#).unwrap();
526 let ext = el
527 .extensions
528 .as_ref()
529 .expect("extensions parsed")
530 .get("ext")
531 .expect("ext present");
532 assert_eq!(ext.value, r#"x"y"#);
533 assert!(ext.quoted);
534
535 let el = ForwardedElement::try_from(r#"for=_a;ext="x\\y""#).unwrap();
537 let ext = el.extensions.unwrap().remove("ext").unwrap();
538 assert_eq!(ext.value, r#"x\y"#);
539
540 ForwardedElement::try_from(r#"for=_a;ext="abc\"#).unwrap_err();
543 }
544
545 #[test]
550 fn regression_forwarded_obs_text_in_qdtext() {
551 let el = ForwardedElement::try_from("for=_a;ext=\"café\"").unwrap();
553 let ext = el.extensions.unwrap().remove("ext").unwrap();
554 assert_eq!(ext.value, "café");
555 ForwardedElement::try_from("for=_a;ext=café").unwrap_err();
557 }
558
559 #[test]
563 fn regression_forwarded_ows_handles_htab() {
564 let el = ForwardedElement::try_from("for=_a;\tproto=http").unwrap();
566 assert_eq!(el.forwarded_proto(), Some(ForwardedProtocol::HTTP));
567 let s = "for=_a;\tproto=http,\tfor=_b";
569 let (first, others) = parse_one_plus_forwarded_elements(s.as_bytes()).unwrap();
570 assert_eq!(first.forwarded_for().unwrap().to_string(), "_a");
571 assert_eq!(others.len(), 1);
572 assert_eq!(others[0].forwarded_for().unwrap().to_string(), "_b");
573 }
574
575 #[test]
580 fn regression_forwarded_rejects_ipv6_zone_id() {
581 for s in [r#"for="[fe80::1%eth0]""#, r#"for="[fe80::1%25eth0]:80""#] {
582 assert!(
583 ForwardedElement::try_from(s).is_err(),
584 "forwarded element should reject zone-id input {s:?}",
585 );
586 }
587 }
588}