Skip to content

Commit 66ab114

Browse files
authored
feat: restore header case-insensitivity (#41)
1 parent 2b51f12 commit 66ab114

File tree

4 files changed

+35
-39
lines changed

4 files changed

+35
-39
lines changed

__test__/handler.spec.mjs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -78,8 +78,9 @@ test('Support request and response headers', async (t) => {
7878
'index.php': `<?php
7979
$headers = apache_request_headers();
8080
header("X-Test: Hello, from PHP!");
81-
// TODO: Does PHP expect headers be returned to uppercase?
82-
echo $headers["X-Test"];
81+
// apache_request_headers is specified to contain whichever casing was
82+
// received from the client, so just use lang_handler's lowercase value.
83+
echo $headers["x-test"];
8384
?>`
8485
})
8586
t.teardown(() => mockroot.clean())

__test__/headers.spec.mjs

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -62,12 +62,12 @@ test('includes iterator methods', (t) => {
6262
const entries = Array.from(headers.entries())
6363
.sort((a, b) => a[0].localeCompare(b[0]))
6464
t.deepEqual(entries, [
65-
['Accept', 'application/json'],
66-
['Content-Type', 'application/json']
65+
['accept', 'application/json'],
66+
['content-type', 'application/json']
6767
])
6868

6969
const keys = Array.from(headers.keys()).sort()
70-
t.deepEqual(keys, ['Accept', 'Content-Type'])
70+
t.deepEqual(keys, ['accept', 'content-type'])
7171

7272
const values = Array.from(headers.values()).sort()
7373
t.deepEqual(values, ['application/json', 'application/json'])
@@ -77,7 +77,7 @@ test('includes iterator methods', (t) => {
7777
seen.push([name, values, map])
7878
})
7979
t.deepEqual(seen.sort((a, b) => a[0].localeCompare(b[0])), [
80-
['Accept', 'application/json', headers],
81-
['Content-Type', 'application/json', headers]
80+
['accept', 'application/json', headers],
81+
['content-type', 'application/json', headers]
8282
])
8383
})

crates/lang_handler/src/headers.rs

Lines changed: 23 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
use std::collections::{hash_map::Entry, HashMap};
22

33
/// Represents a single HTTP header value or multiple values for the same header.
4-
#[derive(Debug, Clone)]
4+
#[derive(Debug, Clone, Eq, PartialEq, Hash)]
55
pub enum Header {
66
/// A single value for a header.
77
Single(String),
@@ -62,9 +62,7 @@ impl Headers {
6262
where
6363
K: AsRef<str>,
6464
{
65-
self
66-
.0
67-
.contains_key(key.as_ref() /*.to_lowercase().as_str()*/)
65+
self.0.contains_key(key.as_ref().to_lowercase().as_str())
6866
}
6967

7068
/// Returns the last single value associated with a header field.
@@ -83,7 +81,7 @@ impl Headers {
8381
where
8482
K: AsRef<str>,
8583
{
86-
match self.0.get(key.as_ref() /*.to_lowercase().as_str()*/) {
84+
match self.0.get(key.as_ref().to_lowercase().as_str()) {
8785
Some(Header::Single(value)) => Some(value.clone()),
8886
Some(Header::Multiple(values)) => values.last().cloned(),
8987
None => None,
@@ -112,7 +110,7 @@ impl Headers {
112110
where
113111
K: AsRef<str>,
114112
{
115-
match self.0.get(key.as_ref() /*.to_lowercase().as_str()*/) {
113+
match self.0.get(key.as_ref().to_lowercase().as_str()) {
116114
Some(Header::Single(value)) => vec![value.clone()],
117115
Some(Header::Multiple(values)) => values.clone(),
118116
None => Vec::new(),
@@ -143,12 +141,10 @@ impl Headers {
143141
where
144142
K: AsRef<str>,
145143
{
146-
let result = self.get_all(key).join(",");
147-
if result.is_empty() {
148-
None
149-
} else {
150-
Some(result)
151-
}
144+
self
145+
.0
146+
.get(key.as_ref().to_lowercase().as_str())
147+
.map(|v| v.into())
152148
}
153149

154150
/// Sets a header field, replacing any existing values.
@@ -168,10 +164,9 @@ impl Headers {
168164
K: Into<String>,
169165
V: Into<String>,
170166
{
171-
self.0.insert(
172-
key.into(), /*.to_lowercase()*/
173-
Header::Single(value.into()),
174-
);
167+
self
168+
.0
169+
.insert(key.into().to_lowercase(), Header::Single(value.into()));
175170
}
176171

177172
/// Add a header with the given value without replacing existing ones.
@@ -194,7 +189,7 @@ impl Headers {
194189
K: Into<String>,
195190
V: Into<String>,
196191
{
197-
let key = key.into()/*.to_lowercase()*/;
192+
let key = key.into().to_lowercase();
198193
let value = value.into();
199194

200195
match self.0.entry(key) {
@@ -234,7 +229,7 @@ impl Headers {
234229
where
235230
K: AsRef<str>,
236231
{
237-
self.0.remove(key.as_ref() /*.to_lowercase().as_str()*/);
232+
self.0.remove(key.as_ref().to_lowercase().as_str());
238233
}
239234

240235
/// Clears all headers.
@@ -292,14 +287,21 @@ impl Headers {
292287
/// # Examples
293288
///
294289
/// ```
295-
/// # use lang_handler::Headers;
290+
/// # use lang_handler::{Headers, Header};
296291
/// let mut headers = Headers::new();
297-
/// headers.set("Accept", "text/plain");
298-
/// headers.set("Accept", "application/json");
292+
/// headers.add("Accept", "text/plain");
293+
/// headers.add("Accept", "application/json");
299294
///
300295
/// for (key, values) in headers.iter() {
301296
/// println!("{}: {:?}", key, values);
302297
/// }
298+
///
299+
/// # assert_eq!(headers.iter().collect::<Vec<(&String, &Header)>>(), vec![
300+
/// # (&"accept".to_string(), &Header::Multiple(vec![
301+
/// # "text/plain".to_string(),
302+
/// # "application/json".to_string()
303+
/// # ]))
304+
/// # ]);
303305
/// ```
304306
pub fn iter(&self) -> impl Iterator<Item = (&String, &Header)> {
305307
self.0.iter()

crates/php/src/sapi.rs

Lines changed: 4 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,6 @@ use ext_php_rs::{
2424
use once_cell::sync::OnceCell;
2525

2626
use crate::{EmbedRequestError, EmbedStartError, RequestContext};
27-
use lang_handler::Header;
2827

2928
// This is a helper to ensure that PHP is initialized and deinitialized at the
3029
// appropriate times.
@@ -381,16 +380,10 @@ pub extern "C" fn sapi_module_register_server_variables(vars: *mut ext_php_rs::t
381380
Ok::<(), EmbedRequestError>(())
382381
.and_then(|_| {
383382
for (key, values) in headers.iter() {
384-
let maybe_header = match values {
385-
Header::Single(header) => Some(header),
386-
Header::Multiple(headers) => headers.first(),
387-
};
388-
389-
if let Some(header) = maybe_header {
390-
let upper = key.to_ascii_uppercase();
391-
let cgi_key = format!("HTTP_{}", upper.replace("-", "_"));
392-
env_var(vars, cgi_key, header)?;
393-
}
383+
let value_string: String = values.into();
384+
let upper = key.to_ascii_uppercase();
385+
let cgi_key = format!("HTTP_{}", upper.replace("-", "_"));
386+
env_var(vars, cgi_key, value_string)?;
394387
}
395388

396389
let globals = SapiGlobals::get();

0 commit comments

Comments
 (0)