From f47d11b97bb4a4bf67c3f9aa47c203babf4a9c72 Mon Sep 17 00:00:00 2001 From: Pyfisch Date: Sat, 2 May 2015 19:27:54 +0200 Subject: [PATCH] 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. --- src/header/common/mod.rs | 2 +- src/header/common/upgrade.rs | 114 ++++++++++++++++++++++++++--------- 2 files changed, 87 insertions(+), 29 deletions(-) diff --git a/src/header/common/mod.rs b/src/header/common/mod.rs index cf8ef5dbd4..e03a50afc8 100644 --- a/src/header/common/mod.rs +++ b/src/header/common/mod.rs @@ -42,7 +42,7 @@ pub use self::referer::Referer; pub use self::server::Server; pub use self::set_cookie::SetCookie; 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::vary::Vary; diff --git a/src/header/common/upgrade.rs b/src/header/common/upgrade.rs index 48b72aa254..f51b0aae0f 100644 --- a/src/header/common/upgrade.rs +++ b/src/header/common/upgrade.rs @@ -1,9 +1,7 @@ -use std::fmt; +use std::fmt::{self, Display}; use std::str::FromStr; use unicase::UniCase; -use self::Protocol::{WebSocket, ProtocolExt}; - header! { #[doc="`Upgrade` header, defined in [RFC7230](http://tools.ietf.org/html/rfc7230#section-6.7)"] #[doc=""] @@ -31,47 +29,107 @@ header! { (Upgrade, "Upgrade") => (Protocol)+ test_upgrade { + // Testcase from the RFC test_header!( test1, vec![b"HTTP/2.0, SHTTP/1.3, IRC/6.9, RTA/x11"], - Some(HeaderField(vec![ - Protocol::ProtocolExt("HTTP/2.0".to_string()), - Protocol::ProtocolExt("SHTTP/1.3".to_string()), - Protocol::ProtocolExt("IRC/6.9".to_string()), - Protocol::ProtocolExt("RTA/x11".to_string()), + Some(Upgrade(vec![ + Protocol::new(ProtocolName::Http, Some("2.0".to_string())), + Protocol::new(ProtocolName::Unregistered("SHTTP".to_string()), Some("1.3".to_string())), + Protocol::new(ProtocolName::Unregistered("IRC".to_string()), Some("6.9".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 = 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. -// TODO: Parse version part seperately -#[derive(Clone, PartialEq, Debug)] -pub enum Protocol { - /// The websocket protocol. +/// A protocol name used to identify a spefic protocol. Names are case-sensitive +/// except for the `WebSocket` value. +#[derive(Clone, Debug, Eq, PartialEq)] +pub enum ProtocolName { + /// `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, - /// Some other less common protocol. - ProtocolExt(String), + /// `h2c` value, HTTP/2 over cleartext TCP + H2c, + /// Any other protocol name not known to hyper + Unregistered(String), } -impl FromStr for Protocol { +impl FromStr for ProtocolName { type Err = (); - fn from_str(s: &str) -> Result { - if UniCase(s) == UniCase("websocket") { - Ok(WebSocket) - } - else { - Ok(ProtocolExt(s.to_string())) - } + fn from_str(s: &str) -> Result { + Ok(match s { + "HTTP" => ProtocolName::Http, + "TLS" => ProtocolName::Tls, + "h2c" => ProtocolName::H2c, + _ => { + 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, +} + +impl Protocol { + /// Creates a new Protocol with the given name and version + pub fn new(name: ProtocolName, version: Option) -> Protocol { + Protocol { name: name, version: version } + } +} + +impl FromStr for Protocol { + type Err =(); + fn from_str(s: &str) -> Result { + 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()] });