Return bool from XHTTP matching header check

check_matching_headers built an io::Error with PermissionDenied that no
caller inspected: dispatch discarded it for a 404 and the HTTP/1
pre-check only needed some error to close the connection. Returning a
bool makes the callers branch on the result directly, and the HTTP/1
path now builds the error hyper needs at its own call site.
This commit is contained in:
cfal
2026-10-22 05:36:27 +00:00
parent b5a220e08c
commit 4a5b4eaad9
2 changed files with 16 additions and 25 deletions
+7 -4
View File
@@ -3907,9 +3907,10 @@ impl State {
request: &Request<()>,
body: Reader,
) -> Result<(Body, bool), Rejection> {
wire::check_matching_headers(&self.config, request.headers())
.inspect_err(|error| log::debug!("XHTTP request rejected: {error}"))
.map_err(|_| StatusCode::NOT_FOUND)?;
if !wire::check_matching_headers(&self.config, request.headers()) {
log::debug!("XHTTP request rejected: headers do not match");
return Err(StatusCode::NOT_FOUND.into());
}
match wire::classify(&self.config, &self.path, request)? {
Kind::Options => Ok((Body::Empty, false)),
Kind::One => self.stream_one(body).map_err(Into::into),
@@ -4387,7 +4388,9 @@ impl State {
if !self.config.close_on_http1_error {
return Ok(());
}
wire::check_matching_headers(&self.config, request.headers())?;
if !wire::check_matching_headers(&self.config, request.headers()) {
return Err(io::Error::other("XHTTP request headers do not match"));
}
if !percent_encoding::percent_decode_str(request.uri().path())
.take(self.path.base.len())
.eq(self.path.base.bytes())
+9 -21
View File
@@ -108,21 +108,14 @@ pub enum Kind {
Packet(String, u64),
}
pub fn check_matching_headers(config: &XhttpServerConfig, headers: &HeaderMap) -> io::Result<()> {
for (name, expected) in &config.matching_headers {
pub fn check_matching_headers(config: &XhttpServerConfig, headers: &HeaderMap) -> bool {
config.matching_headers.iter().all(|(name, expected)| {
let mut values = headers.get_all(name.as_str()).iter();
let matches = values
values
.next()
.is_some_and(|value| bool::from(value.as_bytes().ct_eq(expected.as_bytes())))
&& values.next().is_none();
if !matches {
return Err(io::Error::new(
io::ErrorKind::PermissionDenied,
"XHTTP request headers do not match",
));
}
}
Ok(())
&& values.next().is_none()
})
}
fn query_padding(query: &str) -> Option<String> {
@@ -308,7 +301,7 @@ mod tests {
"protocol": {"type": "socks"}
}))
.unwrap();
assert!(check_matching_headers(&config, &HeaderMap::new()).is_ok());
assert!(check_matching_headers(&config, &HeaderMap::new()));
config
.matching_headers
.insert("X-Token".into(), "Secret".into());
@@ -319,10 +312,10 @@ mod tests {
let mut headers = HeaderMap::new();
headers.insert("x-token", "Secret".parse().unwrap());
assert!(check_matching_headers(&config, &headers).is_err());
assert!(!check_matching_headers(&config, &headers));
headers.insert("x-empty", "".parse().unwrap());
headers.insert("x-extra", "allowed".parse().unwrap());
assert!(check_matching_headers(&config, &headers).is_ok());
assert!(check_matching_headers(&config, &headers));
for values in [
vec![],
vec![""],
@@ -337,12 +330,7 @@ mod tests {
for value in values {
headers.append("x-token", value.parse().unwrap());
}
assert_eq!(
check_matching_headers(&config, &headers)
.unwrap_err()
.kind(),
io::ErrorKind::PermissionDenied
);
assert!(!check_matching_headers(&config, &headers));
}
}