1use crate::{
2 headers::{HeaderName, HeaderValue, Headers, ToHeaderValues, FORWARDED},
3 parse_utils::{parse_quoted_string, parse_token},
4};
5use std::{borrow::Cow, convert::TryFrom, fmt::Write, net::IpAddr};
6
7const X_FORWARDED_FOR: HeaderName = HeaderName::from_lowercase_str("x-forwarded-for");
9const X_FORWARDED_PROTO: HeaderName = HeaderName::from_lowercase_str("x-forwarded-proto");
10const X_FORWARDED_BY: HeaderName = HeaderName::from_lowercase_str("x-forwarded-by");
11
12#[derive(Debug, Clone, Default, PartialEq, Eq)]
15pub struct Forwarded<'a> {
16 by: Option<Cow<'a, str>>,
17 forwarded_for: Vec<Cow<'a, str>>,
18 host: Option<Cow<'a, str>>,
19 proto: Option<Cow<'a, str>>,
20}
21
22impl<'a> Forwarded<'a> {
23 pub fn from_headers(headers: &'a impl AsRef<Headers>) -> Result<Option<Self>, ParseError> {
75 if let Some(forwarded) = Self::from_forwarded_header(headers)? {
76 Ok(Some(forwarded))
77 } else {
78 Self::from_x_headers(headers)
79 }
80 }
81
82 pub fn from_forwarded_header(
107 headers: &'a impl AsRef<Headers>,
108 ) -> Result<Option<Self>, ParseError> {
109 if let Some(headers) = headers.as_ref().get(FORWARDED) {
110 Ok(Some(Self::parse(headers.as_ref().as_str())?))
111 } else {
112 Ok(None)
113 }
114 }
115
116 pub fn from_x_headers(headers: &'a impl AsRef<Headers>) -> Result<Option<Self>, ParseError> {
142 let headers = headers.as_ref();
143
144 let forwarded_for: Vec<Cow<'a, str>> = headers
145 .get(X_FORWARDED_FOR)
146 .map(|hv| {
147 hv.as_str()
148 .split(',')
149 .map(|v| {
150 let v = v.trim();
151 match v.parse::<IpAddr>().ok() {
152 Some(IpAddr::V6(v6)) => Cow::Owned(format!(r#"[{}]"#, v6)),
153 _ => Cow::Borrowed(v),
154 }
155 })
156 .collect()
157 })
158 .unwrap_or_default();
159
160 let by = headers
161 .get(X_FORWARDED_BY)
162 .map(|hv| Cow::Borrowed(hv.as_str()));
163
164 let proto = headers
165 .get(X_FORWARDED_PROTO)
166 .map(|p| Cow::Borrowed(p.as_str()));
167
168 if !forwarded_for.is_empty() || by.is_some() || proto.is_some() {
169 Ok(Some(Self {
170 forwarded_for,
171 by,
172 proto,
173 host: None,
174 }))
175 } else {
176 Ok(None)
177 }
178 }
179
180 pub fn parse(input: &'a str) -> Result<Self, ParseError> {
197 let mut input = input;
198 let mut forwarded = Forwarded::new();
199
200 while !input.is_empty() {
201 input = if starts_with_ignore_case("for=", input) {
202 forwarded.parse_for(input)?
203 } else {
204 forwarded.parse_forwarded_pair(input)?
205 }
206 }
207
208 Ok(forwarded)
209 }
210
211 fn parse_forwarded_pair(&mut self, input: &'a str) -> Result<&'a str, ParseError> {
212 let (key, value, rest) = match parse_token(input) {
213 (Some(key), rest) if rest.starts_with('=') => match parse_value(&rest[1..]) {
214 (Some(value), rest) => Some((key, value, rest)),
215 (None, _) => None,
216 },
217 _ => None,
218 }
219 .ok_or_else(|| ParseError::new("parse error in forwarded-pair"))?;
220
221 match key {
222 "by" => {
223 if self.by.is_some() {
224 return Err(ParseError::new("parse error, duplicate `by` key"));
225 }
226 self.by = Some(value);
227 }
228
229 "host" => {
230 if self.host.is_some() {
231 return Err(ParseError::new("parse error, duplicate `host` key"));
232 }
233 self.host = Some(value);
234 }
235
236 "proto" => {
237 if self.proto.is_some() {
238 return Err(ParseError::new("parse error, duplicate `proto` key"));
239 }
240 self.proto = Some(value);
241 }
242
243 _ => { }
244 }
245
246 match rest.strip_prefix(';') {
247 Some(rest) => Ok(rest),
248 None => Ok(rest),
249 }
250 }
251
252 fn parse_for(&mut self, input: &'a str) -> Result<&'a str, ParseError> {
253 let mut rest = input;
254
255 loop {
256 rest = match match_ignore_case("for=", rest) {
257 (true, rest) => rest,
258 (false, _) => return Err(ParseError::new("http list must start with for=")),
259 };
260
261 let (value, rest_) = parse_value(rest);
262 rest = rest_;
263
264 if let Some(value) = value {
265 self.forwarded_for.push(value);
267 } else {
268 return Err(ParseError::new("for= without valid value"));
269 }
270
271 match rest.chars().next() {
272 Some(',') => {
274 rest = rest[1..].trim_start();
275 }
276
277 Some(';') => return Ok(&rest[1..]),
279
280 None => return Ok(rest),
282
283 _ => return Err(ParseError::new("unexpected character after for= section")),
285 }
286 }
287 }
288
289 pub fn into_owned(self) -> Forwarded<'static> {
292 Forwarded {
293 by: self.by.map(|by| Cow::Owned(by.into_owned())),
294 forwarded_for: self
295 .forwarded_for
296 .into_iter()
297 .map(|ff| Cow::Owned(ff.into_owned()))
298 .collect(),
299 host: self.host.map(|h| Cow::Owned(h.into_owned())),
300 proto: self.proto.map(|p| Cow::Owned(p.into_owned())),
301 }
302 }
303
304 pub fn apply(&self, mut headers: impl AsMut<Headers>) {
318 headers.as_mut().insert(FORWARDED, self);
319 }
320
321 pub fn value(&self) -> Result<String, std::fmt::Error> {
335 let mut buf = String::new();
336 if let Some(by) = self.by() {
337 write!(&mut buf, "by={};", by)?;
338 }
339
340 buf.push_str(
341 &self
342 .forwarded_for
343 .iter()
344 .map(|f| format!("for={}", format_value(f)))
345 .collect::<Vec<_>>()
346 .join(", "),
347 );
348
349 buf.push(';');
350
351 if let Some(host) = self.host() {
352 write!(&mut buf, "host={};", host)?;
353 }
354
355 if let Some(proto) = self.proto() {
356 write!(&mut buf, "proto={};", proto)?;
357 }
358
359 buf.pop();
361
362 Ok(buf)
363 }
364
365 pub fn new() -> Self {
367 Self::default()
368 }
369
370 pub fn add_for(&mut self, forwarded_for: impl Into<Cow<'a, str>>) {
372 self.forwarded_for.push(forwarded_for.into());
373 }
374
375 pub fn forwarded_for(&self) -> Vec<&str> {
377 self.forwarded_for.iter().map(|x| x.as_ref()).collect()
378 }
379
380 pub fn set_host(&mut self, host: impl Into<Cow<'a, str>>) {
382 self.host = Some(host.into());
383 }
384
385 pub fn host(&self) -> Option<&str> {
387 self.host.as_deref()
388 }
389
390 pub fn set_proto(&mut self, proto: impl Into<Cow<'a, str>>) {
392 self.proto = Some(proto.into())
393 }
394
395 pub fn proto(&self) -> Option<&str> {
397 self.proto.as_deref()
398 }
399
400 pub fn set_by(&mut self, by: impl Into<Cow<'a, str>>) {
402 self.by = Some(by.into());
403 }
404
405 pub fn by(&self) -> Option<&str> {
407 self.by.as_deref()
408 }
409}
410
411fn parse_value(input: &str) -> (Option<Cow<'_, str>>, &str) {
412 match parse_token(input) {
413 (Some(token), rest) => (Some(Cow::Borrowed(token)), rest),
414 (None, rest) => parse_quoted_string(rest),
415 }
416}
417
418fn format_value(input: &str) -> Cow<'_, str> {
419 match parse_token(input) {
420 (_, "") => input.into(),
421 _ => {
422 let mut string = String::from("\"");
423 for ch in input.chars() {
424 if let '\\' | '"' = ch {
425 string.push('\\');
426 }
427 string.push(ch);
428 }
429 string.push('"');
430 string.into()
431 }
432 }
433}
434
435fn match_ignore_case<'a>(start: &'static str, input: &'a str) -> (bool, &'a str) {
436 let len = start.len();
437 if input[..len].eq_ignore_ascii_case(start) {
438 (true, &input[len..])
439 } else {
440 (false, input)
441 }
442}
443
444fn starts_with_ignore_case(start: &'static str, input: &str) -> bool {
445 if start.len() <= input.len() {
446 let len = start.len();
447 input[..len].eq_ignore_ascii_case(start)
448 } else {
449 false
450 }
451}
452
453impl std::fmt::Display for Forwarded<'_> {
454 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
455 f.write_str(&self.value()?)
456 }
457}
458
459impl ToHeaderValues for Forwarded<'_> {
460 type Iter = std::option::IntoIter<HeaderValue>;
461 fn to_header_values(&self) -> crate::Result<Self::Iter> {
462 self.value()?.to_header_values()
463 }
464}
465
466impl ToHeaderValues for &Forwarded<'_> {
467 type Iter = std::option::IntoIter<HeaderValue>;
468 fn to_header_values(&self) -> crate::Result<Self::Iter> {
469 self.value()?.to_header_values()
470 }
471}
472
473#[derive(Debug, Clone)]
474pub struct ParseError(&'static str);
475impl ParseError {
476 pub fn new(msg: &'static str) -> Self {
477 Self(msg)
478 }
479}
480
481impl std::error::Error for ParseError {}
482impl std::fmt::Display for ParseError {
483 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
484 write!(f, "unable to parse forwarded header: {}", self.0)
485 }
486}
487
488impl<'a> TryFrom<&'a str> for Forwarded<'a> {
489 type Error = ParseError;
490 fn try_from(value: &'a str) -> Result<Self, Self::Error> {
491 Self::parse(value)
492 }
493}
494
495#[cfg(test)]
496mod tests {
497 use super::*;
498 use crate::{Method::Get, Request, Response, Result};
499 use url::Url;
500
501 #[test]
502 fn starts_with_ignore_case_can_handle_short_inputs() {
503 assert!(!starts_with_ignore_case("helloooooo", "h"));
504 }
505
506 #[test]
507 fn parsing_for() -> Result<()> {
508 assert_eq!(
509 Forwarded::parse(r#"for="_gazonk""#)?.forwarded_for(),
510 vec!["_gazonk"]
511 );
512 assert_eq!(
513 Forwarded::parse(r#"For="[2001:db8:cafe::17]:4711""#)?.forwarded_for(),
514 vec!["[2001:db8:cafe::17]:4711"]
515 );
516
517 assert_eq!(
518 Forwarded::parse("for=192.0.2.60;proto=http;by=203.0.113.43")?.forwarded_for(),
519 vec!["192.0.2.60"]
520 );
521
522 assert_eq!(
523 Forwarded::parse("for=192.0.2.43, for=198.51.100.17")?.forwarded_for(),
524 vec!["192.0.2.43", "198.51.100.17"]
525 );
526
527 assert_eq!(
528 Forwarded::parse(r#"for=192.0.2.43,for="[2001:db8:cafe::17]",for=unknown"#)?
529 .forwarded_for(),
530 Forwarded::parse(r#"for=192.0.2.43, for="[2001:db8:cafe::17]", for=unknown"#)?
531 .forwarded_for()
532 );
533
534 assert_eq!(
535 Forwarded::parse(
536 r#"for=192.0.2.43,for="this is a valid quoted-string, \" \\",for=unknown"#
537 )?
538 .forwarded_for(),
539 vec![
540 "192.0.2.43",
541 r#"this is a valid quoted-string, " \"#,
542 "unknown"
543 ]
544 );
545
546 Ok(())
547 }
548
549 #[test]
550 fn basic_parse() -> Result<()> {
551 let forwarded = Forwarded::parse("for=client.com;by=proxy.com;host=host.com;proto=https")?;
552
553 assert_eq!(forwarded.by(), Some("proxy.com"));
554 assert_eq!(forwarded.forwarded_for(), vec!["client.com"]);
555 assert_eq!(forwarded.host(), Some("host.com"));
556 assert_eq!(forwarded.proto(), Some("https"));
557 assert!(matches!(forwarded, Forwarded { .. }));
558 Ok(())
559 }
560
561 #[test]
562 fn bad_parse() {
563 let err = Forwarded::parse("by=proxy.com;for=client;host=example.com;host").unwrap_err();
564 assert_eq!(
565 err.to_string(),
566 "unable to parse forwarded header: parse error in forwarded-pair"
567 );
568
569 let err = Forwarded::parse("by;for;host;proto").unwrap_err();
570 assert_eq!(
571 err.to_string(),
572 "unable to parse forwarded header: parse error in forwarded-pair"
573 );
574
575 let err = Forwarded::parse("for=for, key=value").unwrap_err();
576 assert_eq!(
577 err.to_string(),
578 "unable to parse forwarded header: http list must start with for="
579 );
580
581 let err = Forwarded::parse(r#"for="unterminated string"#).unwrap_err();
582 assert_eq!(
583 err.to_string(),
584 "unable to parse forwarded header: for= without valid value"
585 );
586
587 let err = Forwarded::parse(r#"for=, for=;"#).unwrap_err();
588 assert_eq!(
589 err.to_string(),
590 "unable to parse forwarded header: for= without valid value"
591 );
592 }
593
594 #[test]
595 fn bad_parse_from_headers() -> Result<()> {
596 let mut response = Response::new(200);
597 response.append_header("forwarded", "uh oh");
598 assert_eq!(
599 Forwarded::from_headers(&response).unwrap_err().to_string(),
600 "unable to parse forwarded header: parse error in forwarded-pair"
601 );
602
603 let response = Response::new(200);
604 assert!(Forwarded::from_headers(&response)?.is_none());
605 Ok(())
606 }
607
608 #[test]
609 fn from_x_headers() -> Result<()> {
610 let mut request = Request::new(Get, Url::parse("http://_/")?);
611 request.append_header(X_FORWARDED_FOR, "192.0.2.43, 2001:db8:cafe::17");
612 request.append_header(X_FORWARDED_PROTO, "gopher");
613 let forwarded = Forwarded::from_headers(&request)?.unwrap();
614 assert_eq!(
615 forwarded.to_string(),
616 r#"for=192.0.2.43, for="[2001:db8:cafe::17]";proto=gopher"#
617 );
618 Ok(())
619 }
620
621 #[test]
622 fn formatting_edge_cases() {
623 let mut forwarded = Forwarded::new();
624 forwarded.add_for(r#"quote: " backslash: \"#);
625 forwarded.add_for(";proto=https");
626 assert_eq!(
627 forwarded.to_string(),
628 r#"for="quote: \" backslash: \\", for=";proto=https""#
629 );
630 }
631
632 #[test]
633 fn parse_edge_cases() -> Result<()> {
634 let forwarded =
635 Forwarded::parse(r#"for=";", for=",", for="\"", for=unquoted;by=";proto=https""#)?;
636 assert_eq!(forwarded.forwarded_for(), vec![";", ",", "\"", "unquoted"]);
637 assert_eq!(forwarded.by(), Some(";proto=https"));
638 assert!(forwarded.proto().is_none());
639
640 let forwarded = Forwarded::parse("proto=https")?;
641 assert_eq!(forwarded.proto(), Some("https"));
642 Ok(())
643 }
644
645 #[test]
646 fn owned_parse() -> Result<()> {
647 let forwarded =
648 Forwarded::parse("for=client;by=proxy.com;host=example.com;proto=https")?.into_owned();
649
650 assert_eq!(forwarded.by(), Some("proxy.com"));
651 assert_eq!(forwarded.forwarded_for(), vec!["client"]);
652 assert_eq!(forwarded.host(), Some("example.com"));
653 assert_eq!(forwarded.proto(), Some("https"));
654 assert!(matches!(forwarded, Forwarded { .. }));
655 Ok(())
656 }
657
658 #[test]
659 fn from_request() -> Result<()> {
660 let mut request = Request::new(Get, Url::parse("http://_/")?);
661 request.append_header("Forwarded", "for=for");
662
663 let forwarded = Forwarded::from_headers(&request)?.unwrap();
664 assert_eq!(forwarded.forwarded_for(), vec!["for"]);
665
666 Ok(())
667 }
668
669 #[test]
670 fn owned_can_outlive_request() -> Result<()> {
671 let forwarded = {
672 let mut request = Request::new(Get, Url::parse("http://_/")?);
673 request.append_header("Forwarded", "for=for;by=by;host=host;proto=proto");
674 Forwarded::from_headers(&request)?.unwrap().into_owned()
675 };
676 assert_eq!(forwarded.by(), Some("by"));
677 Ok(())
678 }
679
680 #[test]
681 fn round_trip() -> Result<()> {
682 let inputs = [
683 "for=client,for=b,for=c;by=proxy.com;host=example.com;proto=https",
684 "by=proxy.com;proto=https;host=example.com;for=a,for=b",
685 ];
686 for input in inputs {
687 let forwarded = Forwarded::parse(input).map_err(|_| crate::Error::new_adhoc(input))?;
688 let header = forwarded.to_header_values()?.next().unwrap();
689 let parsed = Forwarded::parse(header.as_str())?;
690 assert_eq!(forwarded, parsed);
691 }
692 Ok(())
693 }
694}