From 67bde14d037987a5bafb787b46765b86a6ac284c Mon Sep 17 00:00:00 2001 From: freedom-winds Date: Mon, 17 Aug 2026 00:21:08 +0800 Subject: [PATCH] fix(ffi): preserve header order when replacing values --- src/ext/mod.rs | 19 +++++++++----- src/ffi/http_types.rs | 61 +++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 73 insertions(+), 7 deletions(-) diff --git a/src/ext/mod.rs b/src/ext/mod.rs index 0fccb5ffec..f7b328f80b 100644 --- a/src/ext/mod.rs +++ b/src/ext/mod.rs @@ -223,14 +223,19 @@ impl OriginalHeaderOrder { } pub(crate) fn insert(&mut self, name: HeaderName) { - if !self.num_entries.contains_key(&name) { - let idx = 0; - self.num_entries.insert(name.clone(), 1); - self.entry_order.push((name, idx)); + self.num_entries.insert(name.clone(), 1); + + if let Some(first_position) = self + .entry_order + .iter() + .position(|(entry_name, _)| entry_name == &name) + { + self.entry_order + .retain(|(entry_name, _)| entry_name != &name); + self.entry_order.insert(first_position, (name, 0)); + } else { + self.entry_order.push((name, 0)); } - // Replacing an already existing element does not - // change ordering, so we only care if its the first - // header name encountered } pub(crate) fn append(&mut self, name: N) diff --git a/src/ffi/http_types.rs b/src/ffi/http_types.rs index a9c6cf6b0f..2044e037bd 100644 --- a/src/ffi/http_types.rs +++ b/src/ffi/http_types.rs @@ -704,4 +704,65 @@ mod tests { HYPER_ITER_CONTINUE } } + #[cfg(all(feature = "http1", feature = "ffi"))] + #[test] + fn test_headers_set_replaces_multi_value_header_in_original_order() { + let mut headers = hyper_headers::default(); + + add_header(&mut headers, b"Set-CookiE", b"a=b"); + add_header(&mut headers, b"Content-Encoding", b"gzip"); + add_header(&mut headers, b"SET-COOKIE", b"c=d"); + add_header(&mut headers, b"X-After", b"present"); + + let name = b"Set-Cookie"; + let value = b"replacement"; + assert!(matches!( + hyper_headers_set( + &mut headers, + name.as_ptr(), + name.len(), + value.as_ptr(), + value.len(), + ), + hyper_code::HYPERE_OK + )); + + let mut output = Vec::::new(); + hyper_headers_foreach(&headers, concat, &mut output as *mut _ as *mut c_void); + + assert_eq!( + output, + b"Set-Cookie: replacement\r\nContent-Encoding: gzip\r\nX-After: present\r\n" + ); + } + + fn add_header(headers: &mut hyper_headers, name: &[u8], value: &[u8]) { + assert!(matches!( + hyper_headers_add( + headers, + name.as_ptr(), + name.len(), + value.as_ptr(), + value.len(), + ), + hyper_code::HYPERE_OK + )); + } + + extern "C" fn concat( + output: *mut c_void, + name: *const u8, + name_len: usize, + value: *const u8, + value_len: usize, + ) -> c_int { + unsafe { + let output = &mut *(output as *mut Vec); + output.extend(std::slice::from_raw_parts(name, name_len)); + output.extend(b": "); + output.extend(std::slice::from_raw_parts(value, value_len)); + output.extend(b"\r\n"); + } + HYPER_ITER_CONTINUE + } }