use base64::engine::general_purpose::STANDARD as ENGINE;
use base64::Engine;
use bytes::Bytes;
use util::HeaderValueString;
use HeaderValue;
#[derive(Clone, PartialEq, Debug)]
pub struct Authorization<C: Credentials>(pub C);
impl Authorization<Basic> {
pub fn basic(username: &str, password: &str) -> Self {
let colon_pos = username.len();
let decoded = format!("{}:{}", username, password);
Authorization(Basic { decoded, colon_pos })
}
pub fn username(&self) -> &str {
self.0.username()
}
pub fn password(&self) -> &str {
self.0.password()
}
}
impl Authorization<Bearer> {
pub fn bearer(token: &str) -> Result<Self, InvalidBearerToken> {
HeaderValueString::from_string(format!("Bearer {}", token))
.map(|val| Authorization(Bearer(val)))
.ok_or_else(|| InvalidBearerToken { _inner: () })
}
pub fn token(&self) -> &str {
self.0.token()
}
}
impl<C: Credentials> ::Header for Authorization<C> {
fn name() -> &'static ::HeaderName {
&::http::header::AUTHORIZATION
}
fn decode<'i, I: Iterator<Item = &'i HeaderValue>>(values: &mut I) -> Result<Self, ::Error> {
values
.next()
.and_then(|val| {
let slice = val.as_bytes();
if slice.len() > C::SCHEME.len()
&& slice[C::SCHEME.len()] == b' '
&& slice[..C::SCHEME.len()].eq_ignore_ascii_case(C::SCHEME.as_bytes())
{
C::decode(val).map(Authorization)
} else {
None
}
})
.ok_or_else(::Error::invalid)
}
fn encode<E: Extend<::HeaderValue>>(&self, values: &mut E) {
let mut value = self.0.encode();
value.set_sensitive(true);
debug_assert!(
value.as_bytes().starts_with(C::SCHEME.as_bytes()),
"Credentials::encode should include its scheme: scheme = {:?}, encoded = {:?}",
C::SCHEME,
value,
);
values.extend(::std::iter::once(value));
}
}
pub trait Credentials: Sized {
const SCHEME: &'static str;
fn decode(value: &HeaderValue) -> Option<Self>;
fn encode(&self) -> HeaderValue;
}
#[derive(Clone, PartialEq, Debug)]
pub struct Basic {
decoded: String,
colon_pos: usize,
}
impl Basic {
pub fn username(&self) -> &str {
&self.decoded[..self.colon_pos]
}
pub fn password(&self) -> &str {
&self.decoded[self.colon_pos + 1..]
}
}
impl Credentials for Basic {
const SCHEME: &'static str = "Basic";
fn decode(value: &HeaderValue) -> Option<Self> {
debug_assert!(
value.as_bytes()[..Self::SCHEME.len()].eq_ignore_ascii_case(Self::SCHEME.as_bytes()),
"HeaderValue to decode should start with \"Basic ..\", received = {:?}",
value,
);
let bytes = &value.as_bytes()["Basic ".len()..];
let non_space_pos = bytes.iter().position(|b| *b != b' ')?;
let bytes = &bytes[non_space_pos..];
let bytes = ENGINE.decode(bytes).ok()?;
let decoded = String::from_utf8(bytes).ok()?;
let colon_pos = decoded.find(':')?;
Some(Basic { decoded, colon_pos })
}
fn encode(&self) -> HeaderValue {
let mut encoded = String::from("Basic ");
ENGINE.encode_string(&self.decoded, &mut encoded);
let bytes = Bytes::from(encoded);
HeaderValue::from_maybe_shared(bytes)
.expect("base64 encoding is always a valid HeaderValue")
}
}
#[derive(Clone, PartialEq, Debug)]
pub struct Bearer(HeaderValueString);
impl Bearer {
pub fn token(&self) -> &str {
self.0.as_str()["Bearer ".len()..].trim_start()
}
}
impl Credentials for Bearer {
const SCHEME: &'static str = "Bearer";
fn decode(value: &HeaderValue) -> Option<Self> {
debug_assert!(
value.as_bytes()[..Self::SCHEME.len()].eq_ignore_ascii_case(Self::SCHEME.as_bytes()),
"HeaderValue to decode should start with \"Bearer ..\", received = {:?}",
value,
);
HeaderValueString::from_val(value).ok().map(Bearer)
}
fn encode(&self) -> HeaderValue {
(&self.0).into()
}
}
error_type!(InvalidBearerToken);
#[cfg(test)]
mod tests {
use super::super::{test_decode, test_encode};
use super::{Authorization, Basic, Bearer};
use http::header::HeaderMap;
use HeaderMapExt;
#[test]
fn basic_encode() {
let auth = Authorization::basic("Aladdin", "open sesame");
let headers = test_encode(auth);
assert_eq!(
headers["authorization"],
"Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ==",
);
}
#[test]
fn basic_roundtrip() {
let auth = Authorization::basic("Aladdin", "open sesame");
let mut h = HeaderMap::new();
h.typed_insert(auth.clone());
assert_eq!(h.typed_get(), Some(auth));
}
#[test]
fn basic_encode_no_password() {
let auth = Authorization::basic("Aladdin", "");
let headers = test_encode(auth);
assert_eq!(headers["authorization"], "Basic QWxhZGRpbjo=",);
}
#[test]
fn basic_decode() {
let auth: Authorization<Basic> =
test_decode(&["Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ=="]).unwrap();
assert_eq!(auth.0.username(), "Aladdin");
assert_eq!(auth.0.password(), "open sesame");
}
#[test]
fn basic_decode_case_insensitive() {
let auth: Authorization<Basic> =
test_decode(&["basic QWxhZGRpbjpvcGVuIHNlc2FtZQ=="]).unwrap();
assert_eq!(auth.0.username(), "Aladdin");
assert_eq!(auth.0.password(), "open sesame");
}
#[test]
fn basic_decode_extra_whitespaces() {
let auth: Authorization<Basic> =
test_decode(&["Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ=="]).unwrap();
assert_eq!(auth.0.username(), "Aladdin");
assert_eq!(auth.0.password(), "open sesame");
}
#[test]
fn basic_decode_no_password() {
let auth: Authorization<Basic> = test_decode(&["Basic QWxhZGRpbjo="]).unwrap();
assert_eq!(auth.0.username(), "Aladdin");
assert_eq!(auth.0.password(), "");
}
#[test]
fn bearer_encode() {
let auth = Authorization::bearer("fpKL54jvWmEGVoRdCNjG").unwrap();
let headers = test_encode(auth);
assert_eq!(headers["authorization"], "Bearer fpKL54jvWmEGVoRdCNjG",);
}
#[test]
fn bearer_decode() {
let auth: Authorization<Bearer> = test_decode(&["Bearer fpKL54jvWmEGVoRdCNjG"]).unwrap();
assert_eq!(auth.0.token().as_bytes(), b"fpKL54jvWmEGVoRdCNjG");
}
#[test]
fn bearer_decode_case_insensitive() {
let auth: Authorization<Bearer> = test_decode(&["bearer fpKL54jvWmEGVoRdCNjG"]).unwrap();
assert_eq!(auth.0.token().as_bytes(), b"fpKL54jvWmEGVoRdCNjG");
}
#[test]
fn bearer_decode_extra_whitespaces() {
let auth: Authorization<Bearer> = test_decode(&["Bearer fpKL54jvWmEGVoRdCNjG"]).unwrap();
assert_eq!(auth.0.token().as_bytes(), b"fpKL54jvWmEGVoRdCNjG");
}
}