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:
		| @@ -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<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. | ||||
| // 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<Protocol, ()> { | ||||
|         if UniCase(s) == UniCase("websocket") { | ||||
|             Ok(WebSocket) | ||||
|         } | ||||
|         else { | ||||
|             Ok(ProtocolExt(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() | ||||
|     fn from_str(s: &str) -> Result<ProtocolName, ()> { | ||||
|         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 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()] }); | ||||
|   | ||||
		Reference in New Issue
	
	Block a user