Merge pull request #165 from hyperium/expires
Adds CacheControl, Expires, and LastModified headers
This commit is contained in:
		
							
								
								
									
										165
									
								
								src/header/common/cache_control.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										165
									
								
								src/header/common/cache_control.rs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,165 @@ | ||||
| use std::fmt; | ||||
| use std::str::FromStr; | ||||
| use header::{Header, HeaderFormat}; | ||||
| use super::util::{from_one_comma_delimited, fmt_comma_delimited}; | ||||
|  | ||||
| /// The Cache-Control header. | ||||
| #[deriving(PartialEq, Clone, Show)] | ||||
| pub struct CacheControl(pub Vec<CacheDirective>); | ||||
|  | ||||
| deref!(CacheControl -> Vec<CacheDirective>) | ||||
|  | ||||
| impl Header for CacheControl { | ||||
|     fn header_name(_: Option<CacheControl>) -> &'static str { | ||||
|         "Cache-Control" | ||||
|     } | ||||
|  | ||||
|     fn parse_header(raw: &[Vec<u8>]) -> Option<CacheControl> { | ||||
|         let directives = raw.iter() | ||||
|             .filter_map(|line| from_one_comma_delimited(line[])) | ||||
|             .collect::<Vec<Vec<CacheDirective>>>() | ||||
|             .concat_vec(); | ||||
|         if directives.len() > 0 { | ||||
|             Some(CacheControl(directives)) | ||||
|         } else { | ||||
|             None | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | ||||
| impl HeaderFormat for CacheControl { | ||||
|     fn fmt_header(&self, fmt: &mut fmt::Formatter) -> fmt::Result { | ||||
|         fmt_comma_delimited(fmt, self[]) | ||||
|     } | ||||
| } | ||||
|  | ||||
| /// CacheControl contains a list of these directives. | ||||
| #[deriving(PartialEq, Clone)] | ||||
| pub enum CacheDirective { | ||||
|     /// "no-cache" | ||||
|     NoCache, | ||||
|     /// "no-store" | ||||
|     NoStore, | ||||
|     /// "no-transform" | ||||
|     NoTransform, | ||||
|     /// "only-if-cached" | ||||
|     OnlyIfCached, | ||||
|  | ||||
|     // request directives | ||||
|     /// "max-age=delta" | ||||
|     MaxAge(uint), | ||||
|     /// "max-stale=delta" | ||||
|     MaxStale(uint), | ||||
|     /// "min-fresh=delta" | ||||
|     MinFresh(uint), | ||||
|  | ||||
|     // response directives | ||||
|     /// "must-revalidate" | ||||
|     MustRevalidate, | ||||
|     /// "public" | ||||
|     Public, | ||||
|     /// "private" | ||||
|     Private, | ||||
|     /// "proxy-revalidate" | ||||
|     ProxyRevalidate, | ||||
|     /// "s-maxage=delta" | ||||
|     SMaxAge(uint), | ||||
|  | ||||
|     /// Extension directives. Optionally include an argument. | ||||
|     Extension(String, Option<String>) | ||||
| } | ||||
|  | ||||
| impl fmt::Show for CacheDirective { | ||||
|     fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { | ||||
|         use self::CacheDirective::*; | ||||
|         match *self { | ||||
|             NoCache => "no-cache", | ||||
|             NoStore => "no-store", | ||||
|             NoTransform => "no-transform", | ||||
|             OnlyIfCached => "only-if-cached", | ||||
|  | ||||
|             MaxAge(secs) => return write!(f, "max-age={}", secs), | ||||
|             MaxStale(secs) => return write!(f, "max-stale={}", secs), | ||||
|             MinFresh(secs) => return write!(f, "min-fresh={}", secs), | ||||
|  | ||||
|             MustRevalidate => "must-revalidate", | ||||
|             Public => "public", | ||||
|             Private => "private", | ||||
|             ProxyRevalidate => "proxy-revalidate", | ||||
|             SMaxAge(secs) => return write!(f, "s-maxage={}", secs), | ||||
|  | ||||
|             Extension(ref name, None) => name[], | ||||
|             Extension(ref name, Some(ref arg)) => return write!(f, "{}={}", name, arg), | ||||
|  | ||||
|         }.fmt(f) | ||||
|     } | ||||
| } | ||||
|  | ||||
| impl FromStr for CacheDirective { | ||||
|     fn from_str(s: &str) -> Option<CacheDirective> { | ||||
|         use self::CacheDirective::*; | ||||
|         match s { | ||||
|             "no-cache" => Some(NoCache), | ||||
|             "no-store" => Some(NoStore), | ||||
|             "no-transform" => Some(NoTransform), | ||||
|             "only-if-cached" => Some(OnlyIfCached), | ||||
|             "must-revalidate" => Some(MustRevalidate), | ||||
|             "public" => Some(Public), | ||||
|             "private" => Some(Private), | ||||
|             "proxy-revalidate" => Some(ProxyRevalidate), | ||||
|             "" => None, | ||||
|             _ => match s.find('=') { | ||||
|                 Some(idx) if idx+1 < s.len() => match (s[..idx], s[idx+1..].trim_chars('"')) { | ||||
|                     ("max-age" , secs) => from_str::<uint>(secs).map(MaxAge), | ||||
|                     ("max-stale", secs) => from_str::<uint>(secs).map(MaxStale), | ||||
|                     ("min-fresh", secs) => from_str::<uint>(secs).map(MinFresh), | ||||
|                     ("s-maxage", secs) => from_str::<uint>(secs).map(SMaxAge), | ||||
|                     (left, right) => Some(Extension(left.into_string(), Some(right.into_string()))) | ||||
|                 }, | ||||
|                 Some(_) => None, | ||||
|                 None => Some(Extension(s.into_string(), None)) | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | ||||
| #[cfg(test)] | ||||
| mod tests { | ||||
|     use header::Header; | ||||
|     use super::*; | ||||
|  | ||||
|     #[test] | ||||
|     fn test_parse_multiple_headers() { | ||||
|         let cache = Header::parse_header(&[b"no-cache".to_vec(), b"private".to_vec()]); | ||||
|         assert_eq!(cache, Some(CacheControl(vec![CacheDirective::NoCache, | ||||
|                                                  CacheDirective::Private]))) | ||||
|     } | ||||
|  | ||||
|     #[test] | ||||
|     fn test_parse_argument() { | ||||
|         let cache = Header::parse_header(&[b"max-age=100, private".to_vec()]); | ||||
|         assert_eq!(cache, Some(CacheControl(vec![CacheDirective::MaxAge(100), | ||||
|                                                  CacheDirective::Private]))) | ||||
|     } | ||||
|  | ||||
|     #[test] | ||||
|     fn test_parse_quote_form() { | ||||
|         let cache = Header::parse_header(&[b"max-age=\"200\"".to_vec()]); | ||||
|         assert_eq!(cache, Some(CacheControl(vec![CacheDirective::MaxAge(200)]))) | ||||
|     } | ||||
|  | ||||
|     #[test] | ||||
|     fn test_parse_extension() { | ||||
|         let cache = Header::parse_header(&[b"foo, bar=baz".to_vec()]); | ||||
|         assert_eq!(cache, Some(CacheControl(vec![CacheDirective::Extension("foo".to_string(), None), | ||||
|                                                  CacheDirective::Extension("bar".to_string(), Some("baz".to_string()))]))) | ||||
|     } | ||||
|  | ||||
|     #[test] | ||||
|     fn test_parse_bad_syntax() { | ||||
|         let cache: Option<CacheControl> = Header::parse_header(&[b"foo=".to_vec()]); | ||||
|         assert_eq!(cache, None) | ||||
|     } | ||||
| } | ||||
|  | ||||
| bench_header!(normal, CacheControl, { vec![b"no-cache, private".to_vec(), b"max-age=100".to_vec()] }) | ||||
| @@ -1,8 +1,8 @@ | ||||
| use header::{Header, HeaderFormat}; | ||||
| use std::fmt::{mod, Show}; | ||||
| use super::util::from_one_raw_str; | ||||
| use std::str::FromStr; | ||||
| use time::{Tm, strptime}; | ||||
| use time::Tm; | ||||
| use header::{Header, HeaderFormat}; | ||||
| use super::util::{from_one_raw_str, tm_from_str}; | ||||
|  | ||||
| // Egh, replace as soon as something better than time::Tm exists. | ||||
| /// The `Date` header field. | ||||
| @@ -21,17 +21,10 @@ impl Header for Date { | ||||
|     } | ||||
| } | ||||
|  | ||||
|  | ||||
| impl HeaderFormat for Date { | ||||
|     fn fmt_header(&self, fmt: &mut fmt::Formatter) -> fmt::Result { | ||||
|         self.fmt(fmt) | ||||
|     } | ||||
| } | ||||
|  | ||||
| impl fmt::Show for Date { | ||||
|     fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { | ||||
|         let Date(ref tm) = *self; | ||||
|         // bummer that tm.strftime allocates a string. It would nice if it | ||||
|         // returned a Show instead, since I don't need the String here | ||||
|         let tm = **self; | ||||
|         match tm.tm_utcoff { | ||||
|             0 => tm.rfc822().fmt(fmt), | ||||
|             _ => tm.to_utc().rfc822().fmt(fmt) | ||||
| @@ -40,34 +33,8 @@ impl fmt::Show for Date { | ||||
| } | ||||
|  | ||||
| impl FromStr for Date { | ||||
|     //    Prior to 1995, there were three different formats commonly used by | ||||
|     //   servers to communicate timestamps.  For compatibility with old | ||||
|     //   implementations, all three are defined here.  The preferred format is | ||||
|     //   a fixed-length and single-zone subset of the date and time | ||||
|     //   specification used by the Internet Message Format [RFC5322]. | ||||
|     // | ||||
|     //     HTTP-date    = IMF-fixdate / obs-date | ||||
|     // | ||||
|     //   An example of the preferred format is | ||||
|     // | ||||
|     //     Sun, 06 Nov 1994 08:49:37 GMT    ; IMF-fixdate | ||||
|     // | ||||
|     //   Examples of the two obsolete formats are | ||||
|     // | ||||
|     //     Sunday, 06-Nov-94 08:49:37 GMT   ; obsolete RFC 850 format | ||||
|     //     Sun Nov  6 08:49:37 1994         ; ANSI C's asctime() format | ||||
|     // | ||||
|     //   A recipient that parses a timestamp value in an HTTP header field | ||||
|     //   MUST accept all three HTTP-date formats.  When a sender generates a | ||||
|     //   header field that contains one or more timestamps defined as | ||||
|     //   HTTP-date, the sender MUST generate those timestamps in the | ||||
|     //   IMF-fixdate format. | ||||
|     fn from_str(s: &str) -> Option<Date> { | ||||
|         strptime(s, "%a, %d %b %Y %T %Z").or_else(|_| { | ||||
|             strptime(s, "%A, %d-%b-%y %T %Z") | ||||
|         }).or_else(|_| { | ||||
|             strptime(s, "%c") | ||||
|         }).ok().map(|tm| Date(tm)) | ||||
|         tm_from_str(s).map(Date) | ||||
|     } | ||||
| } | ||||
|  | ||||
|   | ||||
							
								
								
									
										42
									
								
								src/header/common/expires.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										42
									
								
								src/header/common/expires.rs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,42 @@ | ||||
| use std::fmt::{mod, Show}; | ||||
| use std::str::FromStr; | ||||
| use time::Tm; | ||||
| use header::{Header, HeaderFormat}; | ||||
| use super::util::{from_one_raw_str, tm_from_str}; | ||||
|  | ||||
| /// The `Expires` header field. | ||||
| #[deriving(PartialEq, Clone)] | ||||
| pub struct Expires(pub Tm); | ||||
|  | ||||
| deref!(Expires -> Tm) | ||||
|  | ||||
| impl Header for Expires { | ||||
|     fn header_name(_: Option<Expires>) -> &'static str { | ||||
|         "Expires" | ||||
|     } | ||||
|  | ||||
|     fn parse_header(raw: &[Vec<u8>]) -> Option<Expires> { | ||||
|         from_one_raw_str(raw) | ||||
|     } | ||||
| } | ||||
|  | ||||
|  | ||||
| impl HeaderFormat for Expires { | ||||
|     fn fmt_header(&self, fmt: &mut fmt::Formatter) -> fmt::Result { | ||||
|         let tm = **self; | ||||
|         match tm.tm_utcoff { | ||||
|             0 => tm.rfc822().fmt(fmt), | ||||
|             _ => tm.to_utc().rfc822().fmt(fmt) | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | ||||
| impl FromStr for Expires { | ||||
|     fn from_str(s: &str) -> Option<Expires> { | ||||
|         tm_from_str(s).map(Expires) | ||||
|     } | ||||
| } | ||||
|  | ||||
| bench_header!(imf_fixdate, Expires, { vec![b"Sun, 07 Nov 1994 08:48:37 GMT".to_vec()] }) | ||||
| bench_header!(rfc_850, Expires, { vec![b"Sunday, 06-Nov-94 08:49:37 GMT".to_vec()] }) | ||||
| bench_header!(asctime, Expires, { vec![b"Sun Nov  6 08:49:37 1994".to_vec()] }) | ||||
							
								
								
									
										42
									
								
								src/header/common/last_modified.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										42
									
								
								src/header/common/last_modified.rs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,42 @@ | ||||
| use std::fmt::{mod, Show}; | ||||
| use std::str::FromStr; | ||||
| use time::Tm; | ||||
| use header::{Header, HeaderFormat}; | ||||
| use super::util::{from_one_raw_str, tm_from_str}; | ||||
|  | ||||
| /// The `LastModified` header field. | ||||
| #[deriving(PartialEq, Clone)] | ||||
| pub struct LastModified(pub Tm); | ||||
|  | ||||
| deref!(LastModified -> Tm) | ||||
|  | ||||
| impl Header for LastModified { | ||||
|     fn header_name(_: Option<LastModified>) -> &'static str { | ||||
|         "Last-Modified" | ||||
|     } | ||||
|  | ||||
|     fn parse_header(raw: &[Vec<u8>]) -> Option<LastModified> { | ||||
|         from_one_raw_str(raw) | ||||
|     } | ||||
| } | ||||
|  | ||||
|  | ||||
| impl HeaderFormat for LastModified { | ||||
|     fn fmt_header(&self, fmt: &mut fmt::Formatter) -> fmt::Result { | ||||
|         let tm = **self; | ||||
|         match tm.tm_utcoff { | ||||
|             0 => tm.rfc822().fmt(fmt), | ||||
|             _ => tm.to_utc().rfc822().fmt(fmt) | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | ||||
| impl FromStr for LastModified { | ||||
|     fn from_str(s: &str) -> Option<LastModified> { | ||||
|         tm_from_str(s).map(LastModified) | ||||
|     } | ||||
| } | ||||
|  | ||||
| bench_header!(imf_fixdate, LastModified, { vec![b"Sun, 07 Nov 1994 08:48:37 GMT".to_vec()] }) | ||||
| bench_header!(rfc_850, LastModified, { vec![b"Sunday, 06-Nov-94 08:49:37 GMT".to_vec()] }) | ||||
| bench_header!(asctime, LastModified, { vec![b"Sun Nov  6 08:49:37 1994".to_vec()] }) | ||||
| @@ -8,12 +8,15 @@ | ||||
|  | ||||
| pub use self::accept::Accept; | ||||
| pub use self::authorization::Authorization; | ||||
| pub use self::cache_control::CacheControl; | ||||
| pub use self::cookie::Cookies; | ||||
| pub use self::connection::Connection; | ||||
| pub use self::content_length::ContentLength; | ||||
| pub use self::content_type::ContentType; | ||||
| pub use self::date::Date; | ||||
| pub use self::expires::Expires; | ||||
| pub use self::host::Host; | ||||
| pub use self::last_modified::LastModified; | ||||
| pub use self::location::Location; | ||||
| pub use self::transfer_encoding::TransferEncoding; | ||||
| pub use self::upgrade::Upgrade; | ||||
| @@ -72,6 +75,9 @@ pub mod accept; | ||||
| /// Exposes the Authorization header. | ||||
| pub mod authorization; | ||||
|  | ||||
| /// Exposes the CacheControl header. | ||||
| pub mod cache_control; | ||||
|  | ||||
| /// Exposes the Cookie header. | ||||
| pub mod cookie; | ||||
|  | ||||
| @@ -87,9 +93,15 @@ pub mod content_type; | ||||
| /// Exposes the Date header. | ||||
| pub mod date; | ||||
|  | ||||
| /// Exposes the Expires header. | ||||
| pub mod expires; | ||||
|  | ||||
| /// Exposes the Host header. | ||||
| pub mod host; | ||||
|  | ||||
| /// Exposes the LastModified header. | ||||
| pub mod last_modified; | ||||
|  | ||||
| /// Exposes the Location header. | ||||
| pub mod location; | ||||
|  | ||||
|   | ||||
| @@ -77,14 +77,13 @@ impl Header for TransferEncoding { | ||||
|     } | ||||
|  | ||||
|     fn parse_header(raw: &[Vec<u8>]) -> Option<TransferEncoding> { | ||||
|         from_comma_delimited(raw).map(|vec| TransferEncoding(vec)) | ||||
|         from_comma_delimited(raw).map(TransferEncoding) | ||||
|     } | ||||
| } | ||||
|  | ||||
| impl HeaderFormat for TransferEncoding { | ||||
|     fn fmt_header(&self, fmt: &mut fmt::Formatter) -> fmt::Result { | ||||
|         let TransferEncoding(ref parts) = *self; | ||||
|         fmt_comma_delimited(fmt, parts[]) | ||||
|         fmt_comma_delimited(fmt, self[]) | ||||
|     } | ||||
| } | ||||
|  | ||||
|   | ||||
| @@ -2,6 +2,7 @@ | ||||
|  | ||||
| use std::str::{FromStr, from_utf8}; | ||||
| use std::fmt::{mod, Show}; | ||||
| use time::{Tm, strptime}; | ||||
|  | ||||
| /// Reads a single raw string when parsing a header | ||||
| pub fn from_one_raw_str<T: FromStr>(raw: &[Vec<u8>]) -> Option<T> { | ||||
| @@ -15,13 +16,19 @@ pub fn from_one_raw_str<T: FromStr>(raw: &[Vec<u8>]) -> Option<T> { | ||||
|     } | ||||
| } | ||||
|  | ||||
| /// Reads a comma-delimited raw string into a Vec. | ||||
| /// Reads a comma-delimited raw header into a Vec. | ||||
| #[inline] | ||||
| pub fn from_comma_delimited<T: FromStr>(raw: &[Vec<u8>]) -> Option<Vec<T>> { | ||||
|     if raw.len() != 1 { | ||||
|         return None; | ||||
|     } | ||||
|     // we JUST checked that raw.len() == 1, so raw[0] WILL exist. | ||||
|     match from_utf8(unsafe { raw.as_slice().unsafe_get(0).as_slice() }) { | ||||
|     from_one_comma_delimited(unsafe { raw.as_slice().unsafe_get(0).as_slice() }) | ||||
| } | ||||
|  | ||||
| /// Reads a comma-delimited raw string into a Vec. | ||||
| pub fn from_one_comma_delimited<T: FromStr>(raw: &[u8]) -> Option<Vec<T>> { | ||||
|     match from_utf8(raw) { | ||||
|         Some(s) => { | ||||
|             Some(s.as_slice() | ||||
|                  .split([',', ' '].as_slice()) | ||||
| @@ -43,3 +50,34 @@ pub fn fmt_comma_delimited<T: Show>(fmt: &mut fmt::Formatter, parts: &[T]) -> fm | ||||
|     } | ||||
|     Ok(()) | ||||
| } | ||||
|  | ||||
| /// Get a Tm from HTTP date formats. | ||||
| //    Prior to 1995, there were three different formats commonly used by | ||||
| //   servers to communicate timestamps.  For compatibility with old | ||||
| //   implementations, all three are defined here.  The preferred format is | ||||
| //   a fixed-length and single-zone subset of the date and time | ||||
| //   specification used by the Internet Message Format [RFC5322]. | ||||
| // | ||||
| //     HTTP-date    = IMF-fixdate / obs-date | ||||
| // | ||||
| //   An example of the preferred format is | ||||
| // | ||||
| //     Sun, 06 Nov 1994 08:49:37 GMT    ; IMF-fixdate | ||||
| // | ||||
| //   Examples of the two obsolete formats are | ||||
| // | ||||
| //     Sunday, 06-Nov-94 08:49:37 GMT   ; obsolete RFC 850 format | ||||
| //     Sun Nov  6 08:49:37 1994         ; ANSI C's asctime() format | ||||
| // | ||||
| //   A recipient that parses a timestamp value in an HTTP header field | ||||
| //   MUST accept all three HTTP-date formats.  When a sender generates a | ||||
| //   header field that contains one or more timestamps defined as | ||||
| //   HTTP-date, the sender MUST generate those timestamps in the | ||||
| //   IMF-fixdate format. | ||||
| pub fn tm_from_str(s: &str) -> Option<Tm> { | ||||
|     strptime(s, "%a, %d %b %Y %T %Z").or_else(|_| { | ||||
|         strptime(s, "%A, %d-%b-%y %T %Z") | ||||
|     }).or_else(|_| { | ||||
|         strptime(s, "%c") | ||||
|     }).ok() | ||||
| } | ||||
|   | ||||
		Reference in New Issue
	
	Block a user