Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(headers): Origin Header 0.9.x #853

Merged
merged 1 commit into from
Jul 12, 2016
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
feat(headers): add origin header
Add an Origin header so users may properly send CORS requests

Addresses #651
  • Loading branch information
puhrez committed Jul 11, 2016
commit 64881ae05458f06261b2e7d0f790184678cc42b9
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);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice! I had noticed that fixme and was going to file a separate issue, but you win!

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,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Clever.

A downside is that to set the Origin, you need to also have the Host type imported... Makes me think maybe the properties be private, with accessor methods?

Copy link
Contributor Author

@puhrez puhrez Jul 10, 2016

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So I see the reason for a constructor, by why obstruct access?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It might not be important yet. The idea I had was to have scheme(), hostname(), and port() accessors. By keeping the fields themselves private, it doesn't matter if we keep using Host internally, or some other field scheme.

But all the other headers are currently wide open. I'll consider a accessor pass before 1.0.

}

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()] });