156
									
								
								src/header/common/etag.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										156
									
								
								src/header/common/etag.rs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,156 @@ | |||||||
|  | use header::{Header, HeaderFormat}; | ||||||
|  | use std::fmt::{mod}; | ||||||
|  | use super::util::from_one_raw_str; | ||||||
|  |  | ||||||
|  | /// The `Etag` header. | ||||||
|  | /// | ||||||
|  | /// An Etag consists of a string enclosed by two literal double quotes. | ||||||
|  | /// Preceding the first double quote is an optional weakness indicator, | ||||||
|  | /// which always looks like this: W/ | ||||||
|  | /// See also: https://tools.ietf.org/html/rfc7232#section-2.3 | ||||||
|  | #[deriving(Clone, PartialEq, Show)] | ||||||
|  | pub struct Etag { | ||||||
|  |     /// Weakness indicator for the tag | ||||||
|  |     pub weak: bool, | ||||||
|  |     /// The opaque string in between the DQUOTEs | ||||||
|  |     pub tag: String | ||||||
|  | } | ||||||
|  |  | ||||||
|  | impl Header for Etag { | ||||||
|  |     fn header_name(_: Option<Etag>) -> &'static str { | ||||||
|  |         "Etag" | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     fn parse_header(raw: &[Vec<u8>]) -> Option<Etag> { | ||||||
|  |         // check that each char in the slice is either: | ||||||
|  |         // 1. %x21, or | ||||||
|  |         // 2. in the range %x23 to %x7E, or | ||||||
|  |         // 3. in the range %x80 to %xFF | ||||||
|  |         fn check_slice_validity(slice: &str) -> bool { | ||||||
|  |             for c in slice.bytes() { | ||||||
|  |                 match c { | ||||||
|  |                     b'\x21' | b'\x23' ... b'\x7e' | b'\x80' ... b'\xff' => (), | ||||||
|  |                     _ => { return false; } | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |             true | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |  | ||||||
|  |         from_one_raw_str(raw).and_then(|s: String| { | ||||||
|  |             let length: uint = s.len(); | ||||||
|  |             let slice = s[]; | ||||||
|  |  | ||||||
|  |             // Early exits: | ||||||
|  |             // 1. The string is empty, or, | ||||||
|  |             // 2. it doesn't terminate in a DQUOTE. | ||||||
|  |             if slice.is_empty() || !slice.ends_with("\"") { | ||||||
|  |                 return None; | ||||||
|  |             } | ||||||
|  |  | ||||||
|  |             // The etag is weak if its first char is not a DQUOTE. | ||||||
|  |             if slice.char_at(0) == '"' { | ||||||
|  |                 // No need to check if the last char is a DQUOTE, | ||||||
|  |                 // we already did that above. | ||||||
|  |                 if check_slice_validity(slice.slice_chars(1, length-1)) { | ||||||
|  |                     return Some(Etag { | ||||||
|  |                         weak: false, | ||||||
|  |                         tag: slice.slice_chars(1, length-1).into_string() | ||||||
|  |                     }); | ||||||
|  |                 } else { | ||||||
|  |                     return None; | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |  | ||||||
|  |             if slice.slice_chars(0, 3) == "W/\"" { | ||||||
|  |                 if check_slice_validity(slice.slice_chars(3, length-1)) { | ||||||
|  |                     return Some(Etag { | ||||||
|  |                         weak: true, | ||||||
|  |                         tag: slice.slice_chars(3, length-1).into_string() | ||||||
|  |                     }); | ||||||
|  |                 } else { | ||||||
|  |                     return None; | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |  | ||||||
|  |             None | ||||||
|  |         }) | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | impl HeaderFormat for Etag { | ||||||
|  |     fn fmt_header(&self, fmt: &mut fmt::Formatter) -> fmt::Result { | ||||||
|  |         if self.weak { | ||||||
|  |             try!(fmt.write(b"W/")); | ||||||
|  |         } | ||||||
|  |         write!(fmt, "\"{}\"", self.tag) | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | #[cfg(test)] | ||||||
|  | mod tests { | ||||||
|  |     use super::Etag; | ||||||
|  |     use header::Header; | ||||||
|  |  | ||||||
|  |     #[test] | ||||||
|  |     fn test_etag_successes() { | ||||||
|  |         // Expected successes | ||||||
|  |         let mut etag: Option<Etag>; | ||||||
|  |  | ||||||
|  |         etag = Header::parse_header([b"\"foobar\"".to_vec()].as_slice()); | ||||||
|  |         assert_eq!(etag, Some(Etag { | ||||||
|  |             weak: false, | ||||||
|  |             tag: "foobar".into_string() | ||||||
|  |         })); | ||||||
|  |  | ||||||
|  |         etag = Header::parse_header([b"\"\"".to_vec()].as_slice()); | ||||||
|  |         assert_eq!(etag, Some(Etag { | ||||||
|  |             weak: false, | ||||||
|  |             tag: "".into_string() | ||||||
|  |         })); | ||||||
|  |  | ||||||
|  |         etag = Header::parse_header([b"W/\"weak-etag\"".to_vec()].as_slice()); | ||||||
|  |         assert_eq!(etag, Some(Etag { | ||||||
|  |             weak: true, | ||||||
|  |             tag: "weak-etag".into_string() | ||||||
|  |         })); | ||||||
|  |  | ||||||
|  |         etag = Header::parse_header([b"W/\"\x65\x62\"".to_vec()].as_slice()); | ||||||
|  |         assert_eq!(etag, Some(Etag { | ||||||
|  |             weak: true, | ||||||
|  |             tag: "\u0065\u0062".into_string() | ||||||
|  |         })); | ||||||
|  |  | ||||||
|  |         etag = Header::parse_header([b"W/\"\"".to_vec()].as_slice()); | ||||||
|  |         assert_eq!(etag, Some(Etag { | ||||||
|  |             weak: true, | ||||||
|  |             tag: "".into_string() | ||||||
|  |         })); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     #[test] | ||||||
|  |     fn test_etag_failures() { | ||||||
|  |         // Expected failures | ||||||
|  |         let mut etag: Option<Etag>; | ||||||
|  |  | ||||||
|  |         etag = Header::parse_header([b"no-dquotes".to_vec()].as_slice()); | ||||||
|  |         assert_eq!(etag, None); | ||||||
|  |  | ||||||
|  |         etag = Header::parse_header([b"w/\"the-first-w-is-case-sensitive\"".to_vec()].as_slice()); | ||||||
|  |         assert_eq!(etag, None); | ||||||
|  |  | ||||||
|  |         etag = Header::parse_header([b"".to_vec()].as_slice()); | ||||||
|  |         assert_eq!(etag, None); | ||||||
|  |  | ||||||
|  |         etag = Header::parse_header([b"\"unmatched-dquotes1".to_vec()].as_slice()); | ||||||
|  |         assert_eq!(etag, None); | ||||||
|  |  | ||||||
|  |         etag = Header::parse_header([b"unmatched-dquotes2\"".to_vec()].as_slice()); | ||||||
|  |         assert_eq!(etag, None); | ||||||
|  |  | ||||||
|  |         etag = Header::parse_header([b"matched-\"dquotes\"".to_vec()].as_slice()); | ||||||
|  |         assert_eq!(etag, None); | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | bench_header!(bench, Etag, { vec![b"W/\"nonemptytag\"".to_vec()] }) | ||||||
| @@ -14,6 +14,7 @@ pub use self::connection::Connection; | |||||||
| pub use self::content_length::ContentLength; | pub use self::content_length::ContentLength; | ||||||
| pub use self::content_type::ContentType; | pub use self::content_type::ContentType; | ||||||
| pub use self::date::Date; | pub use self::date::Date; | ||||||
|  | pub use self::etag::Etag; | ||||||
| pub use self::expires::Expires; | pub use self::expires::Expires; | ||||||
| pub use self::host::Host; | pub use self::host::Host; | ||||||
| pub use self::last_modified::LastModified; | pub use self::last_modified::LastModified; | ||||||
| @@ -94,6 +95,9 @@ pub mod content_type; | |||||||
| /// Exposes the Date header. | /// Exposes the Date header. | ||||||
| pub mod date; | pub mod date; | ||||||
|  |  | ||||||
|  | /// Exposes the Etag header. | ||||||
|  | pub mod etag; | ||||||
|  |  | ||||||
| /// Exposes the Expires header. | /// Exposes the Expires header. | ||||||
| pub mod expires; | pub mod expires; | ||||||
|  |  | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user