use http::{HeaderMap, Request, header::AUTHORIZATION};
use crate::{Error, header::extract_single_header_text};
const BEARER_SCHEME: &str = "Bearer";
const BASIC_SCHEME: &str = "Basic";
const SCHEME_SEPARATOR: char = ' ';
pub fn extract_header_authorization(headers: &HeaderMap) -> Result<Option<&str>, Error> {
extract_single_header_text(headers, &AUTHORIZATION)
}
pub fn extract_request_authorization<B>(request: &Request<B>) -> Result<Option<&str>, Error> {
extract_header_authorization(request.headers())
}
pub fn extract_header_bearer_token(headers: &HeaderMap) -> Result<Option<&str>, Error> {
Ok(extract_header_authorization(headers)?
.and_then(|value| extract_scheme_credentials(value, BEARER_SCHEME)))
}
pub fn extract_request_bearer_token<B>(request: &Request<B>) -> Result<Option<&str>, Error> {
extract_header_bearer_token(request.headers())
}
pub fn extract_header_basic_credentials(headers: &HeaderMap) -> Result<Option<&str>, Error> {
Ok(extract_header_authorization(headers)?
.and_then(|value| extract_scheme_credentials(value, BASIC_SCHEME)))
}
pub fn extract_request_basic_credentials<B>(request: &Request<B>) -> Result<Option<&str>, Error> {
extract_header_basic_credentials(request.headers())
}
fn extract_scheme_credentials<'a>(value: &'a str, scheme: &str) -> Option<&'a str> {
let candidate = value.get(..scheme.len())?;
if !candidate.eq_ignore_ascii_case(scheme) {
return None;
}
value.get(scheme.len()..)?.strip_prefix(SCHEME_SEPARATOR)
}
#[cfg(test)]
mod tests {
use http::{HeaderMap, HeaderValue, Request, header::AUTHORIZATION};
use super::*;
#[test]
fn extracts_raw_and_empty_authorization_values() {
let mut headers = HeaderMap::new();
assert_eq!(extract_header_authorization(&headers).unwrap(), None);
headers.insert(AUTHORIZATION, "Bearer opaque-secret".parse().unwrap());
assert_eq!(
extract_header_authorization(&headers).unwrap(),
Some("Bearer opaque-secret")
);
headers.insert(AUTHORIZATION, "".parse().unwrap());
assert_eq!(extract_header_authorization(&headers).unwrap(), Some(""));
}
#[test]
fn rejects_duplicate_and_non_text_values_without_echoing_them() {
let mut duplicate = HeaderMap::new();
duplicate.append(AUTHORIZATION, "Bearer first-secret".parse().unwrap());
duplicate.append(AUTHORIZATION, "Basic second-secret".parse().unwrap());
let error = extract_header_authorization(&duplicate).unwrap_err();
assert!(matches!(error, Error::DuplicateHeader { .. }));
assert!(!format!("{error:?}").contains("secret"));
let mut non_text = HeaderMap::new();
non_text.insert(AUTHORIZATION, HeaderValue::from_bytes(&[0xff]).unwrap());
assert!(matches!(
extract_header_authorization(&non_text),
Err(Error::InvalidHeader { .. })
));
}
#[test]
fn routes_bearer_without_validating_or_normalizing_credentials() {
assert_eq!(extract_scheme_credentials("Bearer🦀", BEARER_SCHEME), None);
let mut headers = HeaderMap::new();
assert_eq!(extract_header_bearer_token(&headers).unwrap(), None);
headers.insert(AUTHORIZATION, "Basic encoded".parse().unwrap());
assert_eq!(extract_header_bearer_token(&headers).unwrap(), None);
headers.insert(AUTHORIZATION, "bEaReR opaque==".parse().unwrap());
assert_eq!(
extract_header_bearer_token(&headers).unwrap(),
Some("opaque==")
);
headers.insert(AUTHORIZATION, "Bearer keep-space".parse().unwrap());
assert_eq!(
extract_header_bearer_token(&headers).unwrap(),
Some(" keep-space")
);
headers.insert(AUTHORIZATION, "Bearer ".parse().unwrap());
assert_eq!(extract_header_bearer_token(&headers).unwrap(), Some(""));
headers.insert(AUTHORIZATION, "Bearer".parse().unwrap());
assert_eq!(extract_header_bearer_token(&headers).unwrap(), None);
assert_eq!(
extract_scheme_credentials("Béarer token", BEARER_SCHEME),
None
);
assert_eq!(extract_scheme_credentials("🦀", BEARER_SCHEME), None);
}
#[test]
fn routes_basic_without_decoding_credentials() {
let mut headers = HeaderMap::new();
headers.insert(AUTHORIZATION, "Bearer token".parse().unwrap());
assert_eq!(extract_header_basic_credentials(&headers).unwrap(), None);
headers.insert(AUTHORIZATION, "bAsIc plain-credentials".parse().unwrap());
assert_eq!(
extract_header_basic_credentials(&headers).unwrap(),
Some("plain-credentials")
);
headers.insert(AUTHORIZATION, "Basic ".parse().unwrap());
assert_eq!(
extract_header_basic_credentials(&headers).unwrap(),
Some("")
);
headers.insert(AUTHORIZATION, "Basic keep-space".parse().unwrap());
assert_eq!(
extract_header_basic_credentials(&headers).unwrap(),
Some(" keep-space")
);
headers.insert(AUTHORIZATION, "Basic".parse().unwrap());
assert_eq!(extract_header_basic_credentials(&headers).unwrap(), None);
}
#[test]
fn requires_a_space_between_scheme_and_credentials() {
let mut headers = HeaderMap::new();
headers.insert(AUTHORIZATION, "BearerAA".parse().unwrap());
assert_eq!(extract_header_bearer_token(&headers).unwrap(), None);
headers.insert(AUTHORIZATION, "Bearer AA".parse().unwrap());
assert_eq!(extract_header_bearer_token(&headers).unwrap(), Some("AA"));
headers.insert(AUTHORIZATION, "BasicAA".parse().unwrap());
assert_eq!(extract_header_basic_credentials(&headers).unwrap(), None);
headers.insert(AUTHORIZATION, "Basic AA".parse().unwrap());
assert_eq!(
extract_header_basic_credentials(&headers).unwrap(),
Some("AA")
);
}
#[test]
fn request_entry_points_delegate_to_headers() {
let request = Request::builder()
.header(AUTHORIZATION, "Bearer unparsed credentials")
.body(())
.unwrap();
assert_eq!(
extract_request_authorization(&request).unwrap(),
Some("Bearer unparsed credentials")
);
assert_eq!(
extract_request_bearer_token(&request).unwrap(),
Some("unparsed credentials")
);
assert_eq!(extract_request_basic_credentials(&request).unwrap(), None);
let basic = Request::builder()
.header(AUTHORIZATION, "Basic encoded")
.body(())
.unwrap();
assert_eq!(
extract_request_basic_credentials(&basic).unwrap(),
Some("encoded")
);
}
}