headers-ext 0.0.4

typed HTTP headers
Documentation
//! Authorization header and types.

use base64;
use bytes::Bytes;

use {HeaderValue};
use util::HeaderValueString;

/// `Authorization` header, defined in [RFC7235](https://tools.ietf.org/html/rfc7235#section-4.2)
///
/// The `Authorization` header field allows a user agent to authenticate
/// itself with an origin server -- usually, but not necessarily, after
/// receiving a 401 (Unauthorized) response.  Its value consists of
/// credentials containing the authentication information of the user
/// agent for the realm of the resource being requested.
///
/// # ABNF
///
/// ```text
/// Authorization = credentials
/// ```
///
/// # Example values
/// * `Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ==`
/// * `Bearer fpKL54jvWmEGVoRdCNjG`
///
/// # Examples
///
/// ```
/// # extern crate headers_ext as headers;
/// use headers::Authorization;
///
/// let basic = Authorization::basic("Aladdin", "open sesame");
/// let bearer = Authorization::bearer("some-opaque-token").unwrap();
/// ```
///
#[derive(Clone, PartialEq, Debug)]
pub struct Authorization<C: Credentials>(pub C);

impl Authorization<Basic> {
    /// Create a `Basic` authorization header.
    pub fn basic(username: &str, password: &str) -> Self {
        let colon_pos = username.len();
        let decoded = format!("{}:{}", username, password);

        Authorization(Basic {
            decoded,
            colon_pos,
        })
    }
}

impl Authorization<Bearer> {
    /// Try to create a `Bearer` authorization header.
    pub fn bearer(token: &str) -> Result<Self, InvalidBearerToken>  {
        HeaderValueString::from_string(format!("Bearer {}", token))
            .map(|val| Authorization(Bearer(val)))
            .ok_or_else(|| InvalidBearerToken { _inner: () })
    }
}

impl<C: Credentials> ::Header for Authorization<C> {
    const 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.starts_with(C::SCHEME.as_bytes())
                    && slice.len() > C::SCHEME.len()
                    && slice[C::SCHEME.len()] == b' ' {
                    C::decode(val)
                        .map(Authorization)
                } else {
                    None
                }
            })
            .ok_or_else(::Error::invalid)
    }

    fn encode<E: Extend<::HeaderValue>>(&self, values: &mut E) {
        let value = self.0.encode();
        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));
    }
}

/// Credentials to be used in the `Authorization` header.
pub trait Credentials: Sized {
    /// The scheme identify the format of these credentials.
    ///
    /// This is the static string that always prefixes the actual credentials,
    /// like `"Basic"` in basic authorization.
    const SCHEME: &'static str;

    /// Try to decode the credentials from the `HeaderValue`.
    ///
    /// The `SCHEME` will be the first part of the `value`.
    fn decode(value: &HeaderValue) -> Option<Self>;

    /// Encode the credentials to a `HeaderValue`.
    ///
    /// The `SCHEME` must be the first part of the `value`.
    fn encode(&self) -> HeaderValue;
}

/// Credential holder for Basic Authentication
#[derive(Clone, PartialEq, Debug)]
pub struct Basic {
    decoded: String,
    colon_pos: usize,
}

impl Basic {
    /// View the decoded username.
    pub fn username(&self) -> &str {
        &self.decoded[..self.colon_pos]
    }

    /// View the decoded password.
    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().starts_with(b"Basic "),
            "HeaderValue to decode should start with \"Basic ..\", received = {:?}",
            value,
        );

        let bytes = base64::decode(&value.as_bytes()["Basic ".len()..]).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 ");
        base64::encode_config_buf(&self.decoded, base64::STANDARD, &mut encoded);

        let bytes = Bytes::from(encoded);
        HeaderValue::from_shared(bytes)
            .expect("base64 encoding is always a valid HeaderValue")
    }
}

#[derive(Clone, PartialEq, Debug)]
/// Token holder for Bearer Authentication, most often seen with oauth
pub struct Bearer(HeaderValueString);

impl Bearer {
    /// View the token part as a `&str`.
    pub fn token(&self) -> &str {
        &self.0.as_str()["Bearer ".len() ..]
    }
}

impl Credentials for Bearer {
    const SCHEME: &'static str = "Bearer";

    fn decode(value: &HeaderValue) -> Option<Self> {
        debug_assert!(
            value.as_bytes().starts_with(b"Bearer "),
            "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 headers_core::HeaderMapExt;
    use http::header::HeaderMap;
    use super::{Authorization, Basic, Bearer};
    use super::super::{test_decode, test_encode};

    #[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_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");
    }
}

//bench_header!(raw, Authorization<String>, { vec![b"foo bar baz".to_vec()] });
//bench_header!(basic, Authorization<Basic>, { vec![b"Basic QWxhZGRpbjpuIHNlc2FtZQ==".to_vec()] });
//bench_header!(bearer, Authorization<Bearer>, { vec![b"Bearer fpKL54jvWmEGVoRdCNjG".to_vec()] });