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:
		| @@ -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; | ||||||
|  |  | ||||||
|   | |||||||
| @@ -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, ()> { | ||||||
|  |         Ok(match s { | ||||||
|  |             "HTTP" => ProtocolName::Http, | ||||||
|  |             "TLS" => ProtocolName::Tls, | ||||||
|  |             "h2c" => ProtocolName::H2c, | ||||||
|  |             _ => { | ||||||
|                 if UniCase(s) == UniCase("websocket") { |                 if UniCase(s) == UniCase("websocket") { | ||||||
|             Ok(WebSocket) |                     ProtocolName::WebSocket | ||||||
|         } |                 } else { | ||||||
|         else { |                     ProtocolName::Unregistered(s.to_string()) | ||||||
|             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() |  | ||||||
|         }) |         }) | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  |  | ||||||
|  | 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()] }); | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user