@@ -38,50 +38,21 @@ fn is_cloud_front_ip(ip: &IpAddr) -> bool {
3838 . any ( |trusted_proxy| trusted_proxy. contains ( * ip) )
3939}
4040
41+ /// Extracts the client IP address from the `X-Forwarded-For` header.
42+ ///
43+ /// This function will return the last valid non-CloudFront IP address in the
44+ /// `X-Forwarded-For` header, if any.
4145pub fn process_xff_headers ( headers : & HeaderMap ) -> Option < IpAddr > {
42- let mut xff_iter = headers. get_all ( X_FORWARDED_FOR ) . iter ( ) ;
43- let Some ( first_header) = xff_iter. next ( ) else {
44- debug ! ( target: "real_ip" , "No X-Forwarded-For header found" ) ;
45- return None ;
46- } ;
47-
48- let has_more_headers = xff_iter. next ( ) . is_some ( ) ;
49- if has_more_headers {
50- // This only happens for requests going directly to crates-io.herokuapp.com,
51- // since AWS CloudFront automatically merges these headers into one.
52- //
53- // The Heroku router has a bug where it currently (2023-10-25) appends
54- // the connecting IP to the **first** header instead of the last.
55- //
56- // In this specific scenario we will read the IP from the first header,
57- // instead of the last, to work around the Heroku bug. We also don't
58- // have to care about the trusted proxies, since the request was
59- // apparently sent to Heroku directly.
60-
61- debug ! ( target: "real_ip" , ?first_header, "Multiple X-Forwarded-For headers found, using the first one due to Heroku bug" ) ;
62-
63- parse_xff_header ( first_header)
64- . into_iter ( )
65- . filter_map ( |r| r. ok ( ) )
66- . next_back ( )
67- } else {
68- // If the request came in through CloudFront we only get a single,
69- // merged header.
70- //
71- // If the request came in through Heroku and only had a single header
72- // originally, then we also only get a single header.
73- //
74- // In this case return the right-most IP address that is not in the list
75- // of IPs from trusted proxies (i.e. CloudFront).
76-
77- debug ! ( target: "real_ip" , ?first_header, "Single X-Forwarded-For header found" ) ;
78-
79- parse_xff_header ( first_header)
80- . into_iter ( )
81- . filter_map ( |r| r. ok ( ) )
82- . filter ( |ip| !is_cloud_front_ip ( ip) )
83- . next_back ( )
84- }
46+ headers
47+ . get_all ( X_FORWARDED_FOR )
48+ . iter ( )
49+ . flat_map ( |header| {
50+ parse_xff_header ( header)
51+ . into_iter ( )
52+ . filter_map ( |r| r. ok ( ) )
53+ . filter ( |ip| !is_cloud_front_ip ( ip) )
54+ } )
55+ . next_back ( )
8556}
8657
8758/// Parses the content of an `X-Forwarded-For` header into a
@@ -142,11 +113,11 @@ mod tests {
142113 test ( vec ! [ b"1.1.1.1, 130.176.118.147" ] , Some ( "1.1.1.1" ) ) ;
143114 test ( vec ! [ b"1.1.1.1, 2.2.2.2, 130.176.118.147" ] , Some ( "2.2.2.2" ) ) ;
144115
145- // Heroku workaround
146- test ( vec ! [ b"1.1.1.1, 2.2.2.2" , b"3.3.3.3" ] , Some ( "2.2.2.2 " ) ) ;
116+ // Multiple headers behavior
117+ test ( vec ! [ b"1.1.1.1, 2.2.2.2" , b"3.3.3.3" ] , Some ( "3.3.3.3 " ) ) ;
147118 test (
148119 vec ! [ b"1.1.1.1, 130.176.118.147" , b"3.3.3.3" ] ,
149- Some ( "130.176.118.147 " ) ,
120+ Some ( "3.3.3.3 " ) ,
150121 ) ;
151122 }
152123
0 commit comments