Parse raw HTTP headers into components needed for `SigV4` signing. Only host, content-type, and content-length are included in the `SigV4` signature. Signing all headers causes failures when the proxy or transport modifies unsigned-by-convention headers (Connection, Accept-Encoding, etc.) between signing and delivery. Header names are lowercased for comparison and stored in lowered form in `all_
(header_str: &str)
| 133 | /// case-insensitive header names, and this function is only used on the |
| 134 | /// `SigV4` signing path. |
| 135 | fn parse_request_parts(header_str: &str) -> RequestParts<'_> { |
| 136 | // Headers stripped entirely — the SDK re-generates auth headers, and |
| 137 | // `Expect` is handled by the proxy before forwarding. |
| 138 | const STRIP_HEADERS: &[&str] = &[ |
| 139 | "authorization", |
| 140 | "x-amz-date", |
| 141 | "x-amz-security-token", |
| 142 | "x-amz-content-sha256", |
| 143 | "expect", |
| 144 | ]; |
| 145 | // Headers forwarded but NOT signed — the proxy or transport may modify |
| 146 | // them between signing and delivery, which would invalidate the signature. |
| 147 | const UNSIGNED_HEADERS: &[&str] = &[ |
| 148 | "connection", |
| 149 | "accept-encoding", |
| 150 | "transfer-encoding", |
| 151 | "user-agent", |
| 152 | "amz-sdk-invocation-id", |
| 153 | "amz-sdk-request", |
| 154 | ]; |
| 155 | |
| 156 | let lines: Vec<&str> = header_str.split("\r\n").collect(); |
| 157 | |
| 158 | let (method, path, request_line) = |
| 159 | lines |
| 160 | .first() |
| 161 | .map_or(("GET", "/", "GET / HTTP/1.1"), |first_line| { |
| 162 | let parts: Vec<&str> = first_line.splitn(3, ' ').collect(); |
| 163 | if parts.len() >= 2 { |
| 164 | (parts[0], parts[1], *first_line) |
| 165 | } else { |
| 166 | ("GET", "/", *first_line) |
| 167 | } |
| 168 | }); |
| 169 | |
| 170 | let mut headers_to_sign: Vec<(String, String)> = Vec::new(); |
| 171 | let mut all_headers: Vec<(String, String)> = Vec::new(); |
| 172 | for line in lines.iter().skip(1) { |
| 173 | if line.is_empty() { |
| 174 | break; |
| 175 | } |
| 176 | if let Some((k, v)) = line.split_once(':') { |
| 177 | let lower = k.trim().to_ascii_lowercase(); |
| 178 | if STRIP_HEADERS.iter().any(|s| lower.starts_with(s)) { |
| 179 | continue; |
| 180 | } |
| 181 | all_headers.push((lower.clone(), v.trim().to_string())); |
| 182 | if !UNSIGNED_HEADERS.iter().any(|s| lower.starts_with(s)) { |
| 183 | headers_to_sign.push((lower, v.trim().to_string())); |
| 184 | } |
| 185 | } |
| 186 | } |
| 187 | |
| 188 | RequestParts { |
| 189 | method, |
| 190 | path, |
| 191 | request_line, |
| 192 | headers_to_sign, |
no test coverage detected