diff --git a/src/header/common/authorization.rs b/src/header/common/authorization.rs new file mode 100644 index 00000000..7cc13334 --- /dev/null +++ b/src/header/common/authorization.rs @@ -0,0 +1,180 @@ +use std::fmt::{mod, Show}; +use std::from_str::FromStr; +use std::str::from_utf8; +use serialize::base64::{ToBase64, FromBase64, Standard, Config}; +use header::{Header, HeaderFormat}; + +/// The `Authorization` header field. +#[deriving(Clone, PartialEq, Show)] +pub struct Authorization(pub S); + +impl Header for Authorization { + fn header_name(_: Option>) -> &'static str { + "Authorization" + } + + fn parse_header(raw: &[Vec]) -> Option> { + if raw.len() == 1 { + match (from_utf8(unsafe { raw[].unsafe_get(0)[] }), Scheme::scheme(None::)) { + (Some(header), Some(scheme)) + if header.starts_with(scheme) && header.len() > scheme.len() + 1 => { + from_str::(header[scheme.len() + 1..]).map(|s| Authorization(s)) + }, + (Some(header), None) => from_str::(header).map(|s| Authorization(s)), + _ => None + } + } else { + None + } + } +} + +impl HeaderFormat for Authorization { + fn fmt_header(&self, fmt: &mut fmt::Formatter) -> fmt::Result { + match Scheme::scheme(None::) { + Some(scheme) => try!(write!(fmt, "{} ", scheme)), + None => () + }; + self.0.fmt_scheme(fmt) + } +} + +/// An Authorization scheme to be used in the header. +pub trait Scheme: FromStr + Send + Sync { + /// An optional Scheme name. + /// + /// For example, `Basic asdf` has the name `Basic`. The Option is + /// just a marker that can be removed once UFCS is completed. + fn scheme(Option) -> Option<&'static str>; + /// Format the Scheme data into a header value. + fn fmt_scheme(&self, &mut fmt::Formatter) -> fmt::Result; +} + +impl Scheme for String { + fn scheme(_: Option) -> Option<&'static str> { + None + } + + fn fmt_scheme(&self, f: &mut fmt::Formatter) -> fmt::Result { + self.fmt(f) + } +} + +/// Credential holder for Basic Authentication +#[deriving(Clone, PartialEq, Show)] +pub struct Basic { + /// The username as a possibly empty string + pub username: String, + /// The password. `None` if the `:` delimiter character was not + /// part of the parsed input. + pub password: Option +} + +impl Scheme for Basic { + fn scheme(_: Option) -> Option<&'static str> { + Some("Basic") + } + + fn fmt_scheme(&self, f: &mut fmt::Formatter) -> fmt::Result { + //FIXME: serialize::base64 could use some Show implementation, so + //that we don't have to allocate a new string here just to write it + //to the formatter. + let mut text = self.username.clone(); + text.push(':'); + if let Some(ref pass) = self.password { + text.push_str(pass[]); + } + text.as_bytes().to_base64(Config { + char_set: Standard, + pad: true, + line_length: None + }).fmt(f) + } +} + +impl FromStr for Basic { + fn from_str(s: &str) -> Option { + match s.from_base64() { + Ok(decoded) => match String::from_utf8(decoded) { + Ok(text) => { + let mut parts = text[].split(':'); + let user = match parts.next() { + Some(part) => part.into_string(), + None => return None + }; + let password = match parts.next() { + Some(part) => Some(part.into_string()), + None => None + }; + Some(Basic { + username: user, + password: password + }) + }, + Err(e) => { + debug!("Basic::from_utf8 error={}", e); + None + } + }, + Err(e) => { + debug!("Basic::from_base64 error={}", e); + None + } + } + } +} + +#[cfg(test)] +mod tests { + use std::io::MemReader; + use super::{Authorization, Basic}; + use super::super::super::{Headers}; + + fn mem(s: &str) -> MemReader { + MemReader::new(s.as_bytes().to_vec()) + } + + #[test] + fn test_raw_auth() { + let mut headers = Headers::new(); + headers.set(Authorization("foo bar baz".into_string())); + assert_eq!(headers.to_string(), "Authorization: foo bar baz\r\n".to_string()); + } + + #[test] + fn test_raw_auth_parse() { + let headers = Headers::from_raw(&mut mem("Authorization: foo bar baz\r\n\r\n")).unwrap(); + assert_eq!(headers.get::>().unwrap().0[], "foo bar baz"); + } + + #[test] + fn test_basic_auth() { + let mut headers = Headers::new(); + headers.set(Authorization(Basic { username: "Aladdin".into_string(), password: Some("open sesame".into_string()) })); + assert_eq!(headers.to_string(), "Authorization: Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ==\r\n".into_string()); + } + + #[test] + fn test_basic_auth_no_password() { + let mut headers = Headers::new(); + headers.set(Authorization(Basic { username: "Aladdin".to_string(), password: None })); + assert_eq!(headers.to_string(), "Authorization: Basic QWxhZGRpbjo=\r\n".to_string()); + } + + #[test] + fn test_basic_auth_parse() { + let headers = Headers::from_raw(&mut mem("Authorization: Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ==\r\n\r\n")).unwrap(); + let auth = headers.get::>().unwrap(); + assert_eq!(auth.0.username[], "Aladdin"); + assert_eq!(auth.0.password, Some("open sesame".to_string())); + } + + #[test] + fn test_basic_auth_parse_no_password() { + let headers = Headers::from_raw(&mut mem("Authorization: Basic QWxhZGRpbjo=\r\n\r\n")).unwrap(); + let auth = headers.get::>().unwrap(); + assert_eq!(auth.0.username.as_slice(), "Aladdin"); + assert_eq!(auth.0.password, Some("".to_string())); + } + +} diff --git a/src/header/common/mod.rs b/src/header/common/mod.rs index e7fad24f..d177903a 100644 --- a/src/header/common/mod.rs +++ b/src/header/common/mod.rs @@ -7,6 +7,7 @@ //! is used, such as `ContentType(pub Mime)`. pub use self::accept::Accept; +pub use self::authorization::Authorization; pub use self::connection::Connection; pub use self::content_length::ContentLength; pub use self::content_type::ContentType; @@ -56,8 +57,8 @@ pub mod user_agent; /// Exposes the Location header. pub mod location; -pub mod util; - +/// Exposes the Authorization header. +pub mod authorization; fn from_comma_delimited(raw: &[Vec]) -> Option> { if raw.len() != 1 { @@ -85,3 +86,4 @@ fn fmt_comma_delimited(fmt: &mut fmt::Formatter, parts: &[T]) -> fmt::R } Ok(()) } +pub mod util; diff --git a/src/header/common/util.rs b/src/header/common/util.rs index 943578fa..f332e946 100644 --- a/src/header/common/util.rs +++ b/src/header/common/util.rs @@ -9,7 +9,7 @@ pub fn from_one_raw_str(raw: &[Vec]) -> Option { return None; } // we JUST checked that raw.len() == 1, so raw[0] WILL exist. - match from_utf8(unsafe { raw.as_slice().unsafe_get(0).as_slice() }) { + match from_utf8(unsafe { raw[].unsafe_get(0)[] }) { Some(s) => FromStr::from_str(s), None => None } diff --git a/src/lib.rs b/src/lib.rs index 9d4d4b16..f174a4b8 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,4 +1,5 @@ -#![feature(macro_rules, phase, default_type_params, if_let, slicing_syntax)] +#![feature(macro_rules, phase, default_type_params, if_let, slicing_syntax, + tuple_indexing)] #![deny(missing_docs)] #![deny(warnings)] #![experimental] @@ -125,6 +126,7 @@ //! implement `Reader` and can be read to get the data out of a `Response`. //! +extern crate serialize; extern crate time; extern crate url; extern crate openssl;