feat(headers): Parse Upgrade header protocols further

Parses protocols into a name and a value part matching the RFC.
An enum contains all registered or known protocols, but contains
an extension variant.

Closes #480

BREAKING CHANGE: Upgrade header Protocol changed.
This commit is contained in:
Pyfisch
2015-05-02 19:27:54 +02:00
parent 1426a4ce34
commit f47d11b97b
2 changed files with 90 additions and 32 deletions

View File

@@ -42,7 +42,7 @@ pub use self::referer::Referer;
pub use self::server::Server; pub use self::server::Server;
pub use self::set_cookie::SetCookie; pub use self::set_cookie::SetCookie;
pub use self::transfer_encoding::TransferEncoding; pub use self::transfer_encoding::TransferEncoding;
pub use self::upgrade::{Upgrade, Protocol}; pub use self::upgrade::{Upgrade, Protocol, ProtocolName};
pub use self::user_agent::UserAgent; pub use self::user_agent::UserAgent;
pub use self::vary::Vary; pub use self::vary::Vary;

View File

@@ -1,9 +1,7 @@
use std::fmt; use std::fmt::{self, Display};
use std::str::FromStr; use std::str::FromStr;
use unicase::UniCase; use unicase::UniCase;
use self::Protocol::{WebSocket, ProtocolExt};
header! { header! {
#[doc="`Upgrade` header, defined in [RFC7230](http://tools.ietf.org/html/rfc7230#section-6.7)"] #[doc="`Upgrade` header, defined in [RFC7230](http://tools.ietf.org/html/rfc7230#section-6.7)"]
#[doc=""] #[doc=""]
@@ -31,47 +29,107 @@ header! {
(Upgrade, "Upgrade") => (Protocol)+ (Upgrade, "Upgrade") => (Protocol)+
test_upgrade { test_upgrade {
// Testcase from the RFC
test_header!( test_header!(
test1, test1,
vec![b"HTTP/2.0, SHTTP/1.3, IRC/6.9, RTA/x11"], vec![b"HTTP/2.0, SHTTP/1.3, IRC/6.9, RTA/x11"],
Some(HeaderField(vec![ Some(Upgrade(vec![
Protocol::ProtocolExt("HTTP/2.0".to_string()), Protocol::new(ProtocolName::Http, Some("2.0".to_string())),
Protocol::ProtocolExt("SHTTP/1.3".to_string()), Protocol::new(ProtocolName::Unregistered("SHTTP".to_string()), Some("1.3".to_string())),
Protocol::ProtocolExt("IRC/6.9".to_string()), Protocol::new(ProtocolName::Unregistered("IRC".to_string()), Some("6.9".to_string())),
Protocol::ProtocolExt("RTA/x11".to_string()), Protocol::new(ProtocolName::Unregistered("RTA".to_string()), Some("x11".to_string())),
]))); ])));
// Own tests
test_header!(
test2, vec![b"websocket"],
Some(Upgrade(vec![Protocol::new(ProtocolName::WebSocket, None)])));
#[test]
fn test3() {
let x: Option<Upgrade> = Header::parse_header(&[b"WEbSOCKet".to_vec()]);
assert_eq!(x, Some(Upgrade(vec![Protocol::new(ProtocolName::WebSocket, None)])));
}
} }
} }
/// Protocol values that can appear in the Upgrade header. /// A protocol name used to identify a spefic protocol. Names are case-sensitive
// TODO: Parse version part seperately /// except for the `WebSocket` value.
#[derive(Clone, PartialEq, Debug)] #[derive(Clone, Debug, Eq, PartialEq)]
pub enum Protocol { pub enum ProtocolName {
/// The websocket protocol. /// `HTTP` value, Hypertext Transfer Protocol
Http,
/// `TLS` value, Transport Layer Security [RFC2817](http://tools.ietf.org/html/rfc2817)
Tls,
/// `WebSocket` value, matched case insensitively,Web Socket Protocol
/// [RFC6455](http://tools.ietf.org/html/rfc6455)
WebSocket, WebSocket,
/// Some other less common protocol. /// `h2c` value, HTTP/2 over cleartext TCP
ProtocolExt(String), H2c,
/// Any other protocol name not known to hyper
Unregistered(String),
} }
impl FromStr for Protocol { impl FromStr for ProtocolName {
type Err = (); type Err = ();
fn from_str(s: &str) -> Result<Protocol, ()> { fn from_str(s: &str) -> Result<ProtocolName, ()> {
if UniCase(s) == UniCase("websocket") { Ok(match s {
Ok(WebSocket) "HTTP" => ProtocolName::Http,
} "TLS" => ProtocolName::Tls,
else { "h2c" => ProtocolName::H2c,
Ok(ProtocolExt(s.to_string())) _ => {
} if UniCase(s) == UniCase("websocket") {
} ProtocolName::WebSocket
} } else {
ProtocolName::Unregistered(s.to_string())
impl fmt::Display for Protocol { }
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { }
write!(fmt, "{}", match *self {
WebSocket => "websocket",
ProtocolExt(ref s) => s.as_ref()
}) })
} }
} }
impl Display for ProtocolName {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
f.write_str(match *self {
ProtocolName::Http => "HTTP",
ProtocolName::Tls => "TLS",
ProtocolName::WebSocket => "websocket",
ProtocolName::H2c => "h2c",
ProtocolName::Unregistered(ref s) => s,
})
}
}
/// Protocols that appear in the `Upgrade` header field
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct Protocol {
/// The protocol identifier
pub name: ProtocolName,
/// The optional version of the protocol, often in the format "DIGIT.DIGIT" (e.g.. "1.2")
pub version: Option<String>,
}
impl Protocol {
/// Creates a new Protocol with the given name and version
pub fn new(name: ProtocolName, version: Option<String>) -> Protocol {
Protocol { name: name, version: version }
}
}
impl FromStr for Protocol {
type Err =();
fn from_str(s: &str) -> Result<Protocol, ()> {
let mut parts = s.splitn(2, '/');
Ok(Protocol::new(try!(parts.next().unwrap().parse()), parts.next().map(|x| x.to_string())))
}
}
impl Display for Protocol {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
try!(fmt::Display::fmt(&self.name, f));
if let Some(ref version) = self.version {
try!(write!(f, "/{}", version));
}
Ok(())
}
}
bench_header!(bench, Upgrade, { vec![b"HTTP/2.0, RTA/x11, websocket".to_vec()] }); bench_header!(bench, Upgrade, { vec![b"HTTP/2.0, RTA/x11, websocket".to_vec()] });