Skip to content

Commit

Permalink
feat(headers): add origin header
Browse files Browse the repository at this point in the history
Add an Origin header so users may properly send CORS requests

Addresses #651
  • Loading branch information
puhrez committed Jul 11, 2016
1 parent b47affd commit 64881ae
Show file tree
Hide file tree
Showing 3 changed files with 146 additions and 38 deletions.
70 changes: 32 additions & 38 deletions src/header/common/host.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
use header::{Header, HeaderFormat};
use std::fmt;
use std::str::FromStr;
use header::parsing::from_one_raw_str;
use url::idna::domain_to_unicode;

/// The `Host` header.
///
Expand Down Expand Up @@ -47,44 +49,7 @@ impl Header for Host {
}

fn parse_header(raw: &[Vec<u8>]) -> ::Result<Host> {
from_one_raw_str(raw).and_then(|mut s: String| {
// FIXME: use rust-url to parse this
// https://github.com/servo/rust-url/issues/42
let idx = {
let slice = &s[..];
let mut chars = slice.chars();
chars.next();
if chars.next().unwrap() == '[' {
match slice.rfind(']') {
Some(idx) => {
if slice.len() > idx + 2 {
Some(idx + 1)
} else {
None
}
}
None => return Err(::Error::Header) // this is a bad ipv6 address...
}
} else {
slice.rfind(':')
}
};

let port = match idx {
Some(idx) => s[idx + 1..].parse().ok(),
None => None
};

match idx {
Some(idx) => s.truncate(idx),
None => ()
}

Ok(Host {
hostname: s,
port: port
})
})
from_one_raw_str(raw)
}
}

Expand All @@ -97,6 +62,35 @@ impl HeaderFormat for Host {
}
}

impl fmt::Display for Host {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
self.fmt_header(f)
}
}

impl FromStr for Host {
type Err = ::Error;

fn from_str(s: &str) -> ::Result<Host> {
let (host_port, res) = domain_to_unicode(s);
if res.is_err() {
return Err(::Error::Header)
}
let idx = host_port.rfind(':');
let port = idx.and_then(
|idx| s[idx + 1..].parse().ok()
);
let hostname = match idx {
None => host_port,
Some(idx) => host_port[..idx].to_owned()
};
Ok(Host {
hostname: hostname,
port: port
})
}
}

#[cfg(test)]
mod tests {
use super::Host;
Expand Down
2 changes: 2 additions & 0 deletions src/header/common/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ pub use self::if_unmodified_since::IfUnmodifiedSince;
pub use self::if_range::IfRange;
pub use self::last_modified::LastModified;
pub use self::location::Location;
pub use self::origin::Origin;
pub use self::pragma::Pragma;
pub use self::prefer::{Prefer, Preference};
pub use self::preference_applied::PreferenceApplied;
Expand Down Expand Up @@ -406,6 +407,7 @@ mod if_range;
mod if_unmodified_since;
mod last_modified;
mod location;
mod origin;
mod pragma;
mod prefer;
mod preference_applied;
Expand Down
112 changes: 112 additions & 0 deletions src/header/common/origin.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
use header::{Header, Host, HeaderFormat};
use std::fmt;
use std::str::FromStr;
use header::parsing::from_one_raw_str;

/// The `Origin` header.
///
/// The `Origin` header is a version of the `Referer` header that is used for all HTTP fetches and `POST`s whose CORS flag is set.
/// This header is often used to inform recipients of the security context of where the request was initiated.
///
///
/// Following the spec, https://fetch.spec.whatwg.org/#origin-header, the value of this header is composed of
/// a String (scheme), header::Host (host/port)
///
/// # Examples
/// ```
/// use hyper::header::{Headers, Origin};
///
/// let mut headers = Headers::new();
/// headers.set(
/// Origin::new("http", "hyper.rs", None)
/// );
/// ```
/// ```
/// use hyper::header::{Headers, Origin};
///
/// let mut headers = Headers::new();
/// headers.set(
/// Origin::new("https", "wikipedia.org", Some(443))
/// );
/// ```

#[derive(Clone, Debug)]
pub struct Origin {
/// The scheme, such as http or https
pub scheme: String,
/// The host, such as Host{hostname: "hyper.rs".to_owned(), port: None}
pub host: Host,
}

impl Origin {
pub fn new<S: Into<String>, H: Into<String>>(scheme: S, hostname: H, port: Option<u16>) -> Origin{
Origin {
scheme: scheme.into(),
host: Host {
hostname: hostname.into(),
port: port
}
}
}
}

impl Header for Origin {
fn header_name() -> &'static str {
static NAME: &'static str = "Origin";
NAME
}

fn parse_header(raw: &[Vec<u8>]) -> ::Result<Origin> {
from_one_raw_str(raw)
}
}

impl FromStr for Origin {
type Err = ::Error;

fn from_str(s: &str) -> ::Result<Origin> {
let idx = match s.find("://") {
Some(idx) => idx,
None => return Err(::Error::Header)
};
// idx + 3 because thats how long "://" is
let (scheme, etc) = (&s[..idx], &s[idx + 3..]);
let host = try!(Host::from_str(etc));


Ok(Origin{
scheme: scheme.to_owned(),
host: host
})
}
}

impl HeaderFormat for Origin {
fn fmt_header(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{}://{}", self.scheme, self.host)
}
}

impl PartialEq for Origin {
fn eq(&self, other: &Origin) -> bool {
self.scheme == other.scheme && self.host == other.host
}
}


#[cfg(test)]
mod tests {
use super::Origin;
use header::Header;

#[test]
fn test_origin() {
let origin = Header::parse_header([b"http://foo.com".to_vec()].as_ref());
assert_eq!(origin.ok(), Some(Origin::new("http", "foo.com", None)));

let origin = Header::parse_header([b"https://foo.com:443".to_vec()].as_ref());
assert_eq!(origin.ok(), Some(Origin::new("https", "foo.com", Some(443))));
}
}

bench_header!(bench, Origin, { vec![b"https://foo.com".to_vec()] });

0 comments on commit 64881ae

Please sign in to comment.