Rewrite credential placeholders in an HTTP header block. Resolves `openshell:resolve:env:*` placeholders in the request line (path segments and query parameter values), header values (including Basic auth tokens), and returns a `RewriteResult` with the rewritten bytes and a redacted target for logging. Returns `Err` if any placeholder is detected but cannot be resolved (fail-closed behavior).
(
raw: &[u8],
resolver: Option<&SecretResolver>,
)
| 925 | /// Returns `Err` if any placeholder is detected but cannot be resolved |
| 926 | /// (fail-closed behavior). |
| 927 | pub fn rewrite_http_header_block( |
| 928 | raw: &[u8], |
| 929 | resolver: Option<&SecretResolver>, |
| 930 | ) -> Result<RewriteResult, UnresolvedPlaceholderError> { |
| 931 | let Some(resolver) = resolver else { |
| 932 | return Ok(RewriteResult { |
| 933 | rewritten: raw.to_vec(), |
| 934 | redacted_target: None, |
| 935 | }); |
| 936 | }; |
| 937 | |
| 938 | let Some(header_end) = raw.windows(4).position(|w| w == b"\r\n\r\n").map(|p| p + 4) else { |
| 939 | return Ok(RewriteResult { |
| 940 | rewritten: raw.to_vec(), |
| 941 | redacted_target: None, |
| 942 | }); |
| 943 | }; |
| 944 | |
| 945 | let header_str = String::from_utf8_lossy(&raw[..header_end]); |
| 946 | let mut lines = header_str.split("\r\n"); |
| 947 | let Some(request_line) = lines.next() else { |
| 948 | return Ok(RewriteResult { |
| 949 | rewritten: raw.to_vec(), |
| 950 | redacted_target: None, |
| 951 | }); |
| 952 | }; |
| 953 | |
| 954 | // Rewrite request line (path + query params) |
| 955 | let rl_result = rewrite_request_line(request_line, resolver)?; |
| 956 | |
| 957 | let mut output = Vec::with_capacity(raw.len()); |
| 958 | output.extend_from_slice(rl_result.line.as_bytes()); |
| 959 | output.extend_from_slice(b"\r\n"); |
| 960 | |
| 961 | for line in lines { |
| 962 | if line.is_empty() { |
| 963 | break; |
| 964 | } |
| 965 | |
| 966 | output.extend_from_slice(rewrite_header_line_checked(line, resolver)?.as_bytes()); |
| 967 | output.extend_from_slice(b"\r\n"); |
| 968 | } |
| 969 | |
| 970 | output.extend_from_slice(b"\r\n"); |
| 971 | output.extend_from_slice(&raw[header_end..]); |
| 972 | |
| 973 | // Fail-closed scan: check for any remaining unresolved placeholders or |
| 974 | // provider-shaped aliases in both raw and percent-decoded header bytes. |
| 975 | let output_header = String::from_utf8_lossy(&output[..output.len().min(header_end + 256)]); |
| 976 | if contains_reserved_credential_marker(&output_header) { |
| 977 | return Err(UnresolvedPlaceholderError { location: "header" }); |
| 978 | } |
| 979 | |
| 980 | Ok(RewriteResult { |
| 981 | rewritten: output, |
| 982 | redacted_target: rl_result.redacted_target, |
| 983 | }) |
| 984 | } |