feat(headers): add strict-transport-security header
Strict-Transport-Security allows servers to inform user-agents that they'd like them to always contact the secure host (https) instead of the insecure one (http). Closes #589
This commit is contained in:
		
				
					committed by
					
						 Sean McArthur
						Sean McArthur
					
				
			
			
				
	
			
			
			
						parent
						
							9a85ea553f
						
					
				
				
					commit
					7c2e5124e6
				
			| @@ -44,6 +44,7 @@ pub use self::range::{Range, ByteRangeSpec}; | ||||
| pub use self::referer::Referer; | ||||
| pub use self::server::Server; | ||||
| pub use self::set_cookie::SetCookie; | ||||
| pub use self::strict_transport_security::StrictTransportSecurity; | ||||
| pub use self::transfer_encoding::TransferEncoding; | ||||
| pub use self::upgrade::{Upgrade, Protocol, ProtocolName}; | ||||
| pub use self::user_agent::UserAgent; | ||||
| @@ -356,6 +357,7 @@ mod range; | ||||
| mod referer; | ||||
| mod server; | ||||
| mod set_cookie; | ||||
| mod strict_transport_security; | ||||
| mod transfer_encoding; | ||||
| mod upgrade; | ||||
| mod user_agent; | ||||
|   | ||||
							
								
								
									
										195
									
								
								src/header/common/strict_transport_security.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										195
									
								
								src/header/common/strict_transport_security.rs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,195 @@ | ||||
| use std::fmt; | ||||
| use std::str::{self, FromStr}; | ||||
|  | ||||
| use unicase::UniCase; | ||||
|  | ||||
| use header::{Header, HeaderFormat, parsing}; | ||||
|  | ||||
| /// `StrictTransportSecurity` header, defined in [RFC6797](https://tools.ietf.org/html/rfc6797) | ||||
| /// | ||||
| /// This specification defines a mechanism enabling web sites to declare | ||||
| /// themselves accessible only via secure connections and/or for users to be | ||||
| /// able to direct their user agent(s) to interact with given sites only over | ||||
| /// secure connections.  This overall policy is referred to as HTTP Strict | ||||
| /// Transport Security (HSTS).  The policy is declared by web sites via the | ||||
| /// Strict-Transport-Security HTTP response header field and/or by other means, | ||||
| /// such as user agent configuration, for example. | ||||
| /// | ||||
| /// # ABNF | ||||
| /// | ||||
| /// ```plain | ||||
| ///      [ directive ]  *( ";" [ directive ] ) | ||||
| /// | ||||
| ///      directive                 = directive-name [ "=" directive-value ] | ||||
| ///      directive-name            = token | ||||
| ///      directive-value           = token | quoted-string | ||||
| /// | ||||
| /// ``` | ||||
| /// | ||||
| /// # Example values | ||||
| /// * `max-age=31536000` | ||||
| /// * `max-age=15768000 ; includeSubDomains` | ||||
| /// | ||||
| /// # Example | ||||
| /// ``` | ||||
| /// # extern crate hyper; | ||||
| /// # fn main() { | ||||
| /// use hyper::header::{Headers, StrictTransportSecurity}; | ||||
| /// | ||||
| /// let mut headers = Headers::new(); | ||||
| /// | ||||
| /// headers.set( | ||||
| ///    StrictTransportSecurity::including_subdomains(31536000u64) | ||||
| /// ); | ||||
| /// # } | ||||
| /// ``` | ||||
| #[derive(Clone, PartialEq, Debug)] | ||||
| pub struct StrictTransportSecurity { | ||||
|     /// Signals the UA that the HSTS Policy applies to this HSTS Host as well as | ||||
|     /// any subdomains of the host's domain name. | ||||
|     pub include_subdomains: bool, | ||||
|  | ||||
|     /// Specifies the number of seconds, after the reception of the STS header | ||||
|     /// field, during which the UA regards the host (from whom the message was | ||||
|     /// received) as a Known HSTS Host. | ||||
|     pub max_age: u64 | ||||
| } | ||||
|  | ||||
| impl StrictTransportSecurity { | ||||
|     /// Create an STS header that includes subdomains | ||||
|     pub fn including_subdomains(max_age: u64) -> StrictTransportSecurity { | ||||
|         StrictTransportSecurity { | ||||
|             max_age: max_age, | ||||
|             include_subdomains: true | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     /// Create an STS header that excludes subdomains | ||||
|     pub fn excluding_subdomains(max_age: u64) -> StrictTransportSecurity { | ||||
|         StrictTransportSecurity { | ||||
|             max_age: max_age, | ||||
|             include_subdomains: false | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | ||||
| enum Directive { | ||||
|     MaxAge(u64), | ||||
|     IncludeSubdomains, | ||||
|     Unknown | ||||
| } | ||||
|  | ||||
| impl FromStr for StrictTransportSecurity { | ||||
|     type Err = ::Error; | ||||
|  | ||||
|     fn from_str(s: &str) -> ::Result<StrictTransportSecurity> { | ||||
|         s.split(';') | ||||
|             .map(str::trim) | ||||
|             .map(|sub| if UniCase(sub) == UniCase("includeSubdomains") { | ||||
|                 Ok(Directive::IncludeSubdomains) | ||||
|             } else { | ||||
|                 let mut sub = sub.splitn(2, '='); | ||||
|                 match (sub.next(), sub.next()) { | ||||
|                     (Some(left), Some(right)) | ||||
|                     if UniCase(left.trim()) == UniCase("max-age") => { | ||||
|                         right | ||||
|                             .trim() | ||||
|                             .trim_matches('"') | ||||
|                             .parse() | ||||
|                             .map(Directive::MaxAge) | ||||
|                     }, | ||||
|                     _ => Ok(Directive::Unknown) | ||||
|                 } | ||||
|             }) | ||||
|             .fold(Ok((None, None)), |res, dir| match (res, dir) { | ||||
|                 (Ok((None, sub)), Ok(Directive::MaxAge(age))) => Ok((Some(age), sub)), | ||||
|                 (Ok((age, None)), Ok(Directive::IncludeSubdomains)) => Ok((age, Some(()))), | ||||
|                 (Ok((Some(_), _)), Ok(Directive::MaxAge(_))) => Err(::Error::Header), | ||||
|                 (Ok((_, Some(_))), Ok(Directive::IncludeSubdomains)) => Err(::Error::Header), | ||||
|                 (_, Err(_)) => Err(::Error::Header), | ||||
|                 (res, _) => res | ||||
|             }) | ||||
|             .and_then(|res| match res { | ||||
|                 (Some(age), sub) => Ok(StrictTransportSecurity { | ||||
|                     max_age: age, | ||||
|                     include_subdomains: sub.is_some() | ||||
|                 }), | ||||
|                 _ => Err(::Error::Header) | ||||
|             }) | ||||
|     } | ||||
| } | ||||
|  | ||||
| impl Header for StrictTransportSecurity { | ||||
|     fn header_name() -> &'static str { | ||||
|         "Strict-Transport-Security" | ||||
|     } | ||||
|  | ||||
|     fn parse_header(raw: &[Vec<u8>]) -> ::Result<StrictTransportSecurity> { | ||||
|         parsing::from_one_raw_str(raw) | ||||
|     } | ||||
| } | ||||
|  | ||||
| impl HeaderFormat for StrictTransportSecurity { | ||||
|     fn fmt_header(&self, f: &mut fmt::Formatter) -> fmt::Result { | ||||
|         if self.include_subdomains { | ||||
|             write!(f, "max-age={}; includeSubdomains", self.max_age) | ||||
|         } else { | ||||
|             write!(f, "max-age={}", self.max_age) | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | ||||
| #[cfg(test)] | ||||
| mod tests { | ||||
|     use super::StrictTransportSecurity; | ||||
|     use header::Header; | ||||
|  | ||||
|     #[test] | ||||
|     fn test_parse_max_age() { | ||||
|         let h = Header::parse_header(&[b"max-age=31536000".to_vec()][..]); | ||||
|         assert_eq!(h.ok(), Some(StrictTransportSecurity { include_subdomains: false, max_age: 31536000u64 })); | ||||
|     } | ||||
|  | ||||
|     #[test] | ||||
|     fn test_parse_max_age_no_value() { | ||||
|         let h: ::Result<StrictTransportSecurity> = Header::parse_header(&[b"max-age".to_vec()][..]); | ||||
|         assert!(h.is_err()); | ||||
|     } | ||||
|  | ||||
|     #[test] | ||||
|     fn test_parse_quoted_max_age() { | ||||
|         let h = Header::parse_header(&[b"max-age=\"31536000\"".to_vec()][..]); | ||||
|         assert_eq!(h.ok(), Some(StrictTransportSecurity { include_subdomains: false, max_age: 31536000u64 })); | ||||
|     } | ||||
|  | ||||
|     #[test] | ||||
|     fn test_parse_spaces_max_age() { | ||||
|         let h = Header::parse_header(&[b"max-age = 31536000".to_vec()][..]); | ||||
|         assert_eq!(h.ok(), Some(StrictTransportSecurity { include_subdomains: false, max_age: 31536000u64 })); | ||||
|     } | ||||
|  | ||||
|     #[test] | ||||
|     fn test_parse_include_subdomains() { | ||||
|         let h = Header::parse_header(&[b"max-age=15768000 ; includeSubDomains".to_vec()][..]); | ||||
|         assert_eq!(h.ok(), Some(StrictTransportSecurity { include_subdomains: true, max_age: 15768000u64 })); | ||||
|     } | ||||
|  | ||||
|     #[test] | ||||
|     fn test_parse_no_max_age() { | ||||
|         let h: ::Result<StrictTransportSecurity> = Header::parse_header(&[b"includeSubDomains".to_vec()][..]); | ||||
|         assert!(h.is_err()); | ||||
|     } | ||||
|  | ||||
|     #[test] | ||||
|     fn test_parse_max_age_nan() { | ||||
|         let h: ::Result<StrictTransportSecurity> = Header::parse_header(&[b"max-age = derp".to_vec()][..]); | ||||
|         assert!(h.is_err()); | ||||
|     } | ||||
|  | ||||
|     #[test] | ||||
|     fn test_parse_duplicate_directives() { | ||||
|         assert!(StrictTransportSecurity::parse_header(&[b"max-age=100; max-age=5; max-age=0".to_vec()][..]).is_err()); | ||||
|     } | ||||
| } | ||||
|  | ||||
| bench_header!(bench, StrictTransportSecurity, { vec![b"max-age=15768000 ; includeSubDomains".to_vec()] }); | ||||
		Reference in New Issue
	
	Block a user