Skip to content

Commit 9a2605a

Browse files
committed
Merge pull request #165 from hyperium/expires
Adds CacheControl, Expires, and LastModified headers
2 parents e193303 + f182f53 commit 9a2605a

File tree

7 files changed

+309
-44
lines changed

7 files changed

+309
-44
lines changed

src/header/common/cache_control.rs

+165
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,165 @@
1+
use std::fmt;
2+
use std::str::FromStr;
3+
use header::{Header, HeaderFormat};
4+
use super::util::{from_one_comma_delimited, fmt_comma_delimited};
5+
6+
/// The Cache-Control header.
7+
#[deriving(PartialEq, Clone, Show)]
8+
pub struct CacheControl(pub Vec<CacheDirective>);
9+
10+
deref!(CacheControl -> Vec<CacheDirective>)
11+
12+
impl Header for CacheControl {
13+
fn header_name(_: Option<CacheControl>) -> &'static str {
14+
"Cache-Control"
15+
}
16+
17+
fn parse_header(raw: &[Vec<u8>]) -> Option<CacheControl> {
18+
let directives = raw.iter()
19+
.filter_map(|line| from_one_comma_delimited(line[]))
20+
.collect::<Vec<Vec<CacheDirective>>>()
21+
.concat_vec();
22+
if directives.len() > 0 {
23+
Some(CacheControl(directives))
24+
} else {
25+
None
26+
}
27+
}
28+
}
29+
30+
impl HeaderFormat for CacheControl {
31+
fn fmt_header(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
32+
fmt_comma_delimited(fmt, self[])
33+
}
34+
}
35+
36+
/// CacheControl contains a list of these directives.
37+
#[deriving(PartialEq, Clone)]
38+
pub enum CacheDirective {
39+
/// "no-cache"
40+
NoCache,
41+
/// "no-store"
42+
NoStore,
43+
/// "no-transform"
44+
NoTransform,
45+
/// "only-if-cached"
46+
OnlyIfCached,
47+
48+
// request directives
49+
/// "max-age=delta"
50+
MaxAge(uint),
51+
/// "max-stale=delta"
52+
MaxStale(uint),
53+
/// "min-fresh=delta"
54+
MinFresh(uint),
55+
56+
// response directives
57+
/// "must-revalidate"
58+
MustRevalidate,
59+
/// "public"
60+
Public,
61+
/// "private"
62+
Private,
63+
/// "proxy-revalidate"
64+
ProxyRevalidate,
65+
/// "s-maxage=delta"
66+
SMaxAge(uint),
67+
68+
/// Extension directives. Optionally include an argument.
69+
Extension(String, Option<String>)
70+
}
71+
72+
impl fmt::Show for CacheDirective {
73+
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
74+
use self::CacheDirective::*;
75+
match *self {
76+
NoCache => "no-cache",
77+
NoStore => "no-store",
78+
NoTransform => "no-transform",
79+
OnlyIfCached => "only-if-cached",
80+
81+
MaxAge(secs) => return write!(f, "max-age={}", secs),
82+
MaxStale(secs) => return write!(f, "max-stale={}", secs),
83+
MinFresh(secs) => return write!(f, "min-fresh={}", secs),
84+
85+
MustRevalidate => "must-revalidate",
86+
Public => "public",
87+
Private => "private",
88+
ProxyRevalidate => "proxy-revalidate",
89+
SMaxAge(secs) => return write!(f, "s-maxage={}", secs),
90+
91+
Extension(ref name, None) => name[],
92+
Extension(ref name, Some(ref arg)) => return write!(f, "{}={}", name, arg),
93+
94+
}.fmt(f)
95+
}
96+
}
97+
98+
impl FromStr for CacheDirective {
99+
fn from_str(s: &str) -> Option<CacheDirective> {
100+
use self::CacheDirective::*;
101+
match s {
102+
"no-cache" => Some(NoCache),
103+
"no-store" => Some(NoStore),
104+
"no-transform" => Some(NoTransform),
105+
"only-if-cached" => Some(OnlyIfCached),
106+
"must-revalidate" => Some(MustRevalidate),
107+
"public" => Some(Public),
108+
"private" => Some(Private),
109+
"proxy-revalidate" => Some(ProxyRevalidate),
110+
"" => None,
111+
_ => match s.find('=') {
112+
Some(idx) if idx+1 < s.len() => match (s[..idx], s[idx+1..].trim_chars('"')) {
113+
("max-age" , secs) => from_str::<uint>(secs).map(MaxAge),
114+
("max-stale", secs) => from_str::<uint>(secs).map(MaxStale),
115+
("min-fresh", secs) => from_str::<uint>(secs).map(MinFresh),
116+
("s-maxage", secs) => from_str::<uint>(secs).map(SMaxAge),
117+
(left, right) => Some(Extension(left.into_string(), Some(right.into_string())))
118+
},
119+
Some(_) => None,
120+
None => Some(Extension(s.into_string(), None))
121+
}
122+
}
123+
}
124+
}
125+
126+
#[cfg(test)]
127+
mod tests {
128+
use header::Header;
129+
use super::*;
130+
131+
#[test]
132+
fn test_parse_multiple_headers() {
133+
let cache = Header::parse_header(&[b"no-cache".to_vec(), b"private".to_vec()]);
134+
assert_eq!(cache, Some(CacheControl(vec![CacheDirective::NoCache,
135+
CacheDirective::Private])))
136+
}
137+
138+
#[test]
139+
fn test_parse_argument() {
140+
let cache = Header::parse_header(&[b"max-age=100, private".to_vec()]);
141+
assert_eq!(cache, Some(CacheControl(vec![CacheDirective::MaxAge(100),
142+
CacheDirective::Private])))
143+
}
144+
145+
#[test]
146+
fn test_parse_quote_form() {
147+
let cache = Header::parse_header(&[b"max-age=\"200\"".to_vec()]);
148+
assert_eq!(cache, Some(CacheControl(vec![CacheDirective::MaxAge(200)])))
149+
}
150+
151+
#[test]
152+
fn test_parse_extension() {
153+
let cache = Header::parse_header(&[b"foo, bar=baz".to_vec()]);
154+
assert_eq!(cache, Some(CacheControl(vec![CacheDirective::Extension("foo".to_string(), None),
155+
CacheDirective::Extension("bar".to_string(), Some("baz".to_string()))])))
156+
}
157+
158+
#[test]
159+
fn test_parse_bad_syntax() {
160+
let cache: Option<CacheControl> = Header::parse_header(&[b"foo=".to_vec()]);
161+
assert_eq!(cache, None)
162+
}
163+
}
164+
165+
bench_header!(normal, CacheControl, { vec![b"no-cache, private".to_vec(), b"max-age=100".to_vec()] })

src/header/common/date.rs

+6-39
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
1-
use header::{Header, HeaderFormat};
21
use std::fmt::{mod, Show};
3-
use super::util::from_one_raw_str;
42
use std::str::FromStr;
5-
use time::{Tm, strptime};
3+
use time::Tm;
4+
use header::{Header, HeaderFormat};
5+
use super::util::{from_one_raw_str, tm_from_str};
66

77
// Egh, replace as soon as something better than time::Tm exists.
88
/// The `Date` header field.
@@ -21,17 +21,10 @@ impl Header for Date {
2121
}
2222
}
2323

24+
2425
impl HeaderFormat for Date {
2526
fn fmt_header(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
26-
self.fmt(fmt)
27-
}
28-
}
29-
30-
impl fmt::Show for Date {
31-
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
32-
let Date(ref tm) = *self;
33-
// bummer that tm.strftime allocates a string. It would nice if it
34-
// returned a Show instead, since I don't need the String here
27+
let tm = **self;
3528
match tm.tm_utcoff {
3629
0 => tm.rfc822().fmt(fmt),
3730
_ => tm.to_utc().rfc822().fmt(fmt)
@@ -40,34 +33,8 @@ impl fmt::Show for Date {
4033
}
4134

4235
impl FromStr for Date {
43-
// Prior to 1995, there were three different formats commonly used by
44-
// servers to communicate timestamps. For compatibility with old
45-
// implementations, all three are defined here. The preferred format is
46-
// a fixed-length and single-zone subset of the date and time
47-
// specification used by the Internet Message Format [RFC5322].
48-
//
49-
// HTTP-date = IMF-fixdate / obs-date
50-
//
51-
// An example of the preferred format is
52-
//
53-
// Sun, 06 Nov 1994 08:49:37 GMT ; IMF-fixdate
54-
//
55-
// Examples of the two obsolete formats are
56-
//
57-
// Sunday, 06-Nov-94 08:49:37 GMT ; obsolete RFC 850 format
58-
// Sun Nov 6 08:49:37 1994 ; ANSI C's asctime() format
59-
//
60-
// A recipient that parses a timestamp value in an HTTP header field
61-
// MUST accept all three HTTP-date formats. When a sender generates a
62-
// header field that contains one or more timestamps defined as
63-
// HTTP-date, the sender MUST generate those timestamps in the
64-
// IMF-fixdate format.
6536
fn from_str(s: &str) -> Option<Date> {
66-
strptime(s, "%a, %d %b %Y %T %Z").or_else(|_| {
67-
strptime(s, "%A, %d-%b-%y %T %Z")
68-
}).or_else(|_| {
69-
strptime(s, "%c")
70-
}).ok().map(|tm| Date(tm))
37+
tm_from_str(s).map(Date)
7138
}
7239
}
7340

src/header/common/expires.rs

+42
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
use std::fmt::{mod, Show};
2+
use std::str::FromStr;
3+
use time::Tm;
4+
use header::{Header, HeaderFormat};
5+
use super::util::{from_one_raw_str, tm_from_str};
6+
7+
/// The `Expires` header field.
8+
#[deriving(PartialEq, Clone)]
9+
pub struct Expires(pub Tm);
10+
11+
deref!(Expires -> Tm)
12+
13+
impl Header for Expires {
14+
fn header_name(_: Option<Expires>) -> &'static str {
15+
"Expires"
16+
}
17+
18+
fn parse_header(raw: &[Vec<u8>]) -> Option<Expires> {
19+
from_one_raw_str(raw)
20+
}
21+
}
22+
23+
24+
impl HeaderFormat for Expires {
25+
fn fmt_header(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
26+
let tm = **self;
27+
match tm.tm_utcoff {
28+
0 => tm.rfc822().fmt(fmt),
29+
_ => tm.to_utc().rfc822().fmt(fmt)
30+
}
31+
}
32+
}
33+
34+
impl FromStr for Expires {
35+
fn from_str(s: &str) -> Option<Expires> {
36+
tm_from_str(s).map(Expires)
37+
}
38+
}
39+
40+
bench_header!(imf_fixdate, Expires, { vec![b"Sun, 07 Nov 1994 08:48:37 GMT".to_vec()] })
41+
bench_header!(rfc_850, Expires, { vec![b"Sunday, 06-Nov-94 08:49:37 GMT".to_vec()] })
42+
bench_header!(asctime, Expires, { vec![b"Sun Nov 6 08:49:37 1994".to_vec()] })

src/header/common/last_modified.rs

+42
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
use std::fmt::{mod, Show};
2+
use std::str::FromStr;
3+
use time::Tm;
4+
use header::{Header, HeaderFormat};
5+
use super::util::{from_one_raw_str, tm_from_str};
6+
7+
/// The `LastModified` header field.
8+
#[deriving(PartialEq, Clone)]
9+
pub struct LastModified(pub Tm);
10+
11+
deref!(LastModified -> Tm)
12+
13+
impl Header for LastModified {
14+
fn header_name(_: Option<LastModified>) -> &'static str {
15+
"Last-Modified"
16+
}
17+
18+
fn parse_header(raw: &[Vec<u8>]) -> Option<LastModified> {
19+
from_one_raw_str(raw)
20+
}
21+
}
22+
23+
24+
impl HeaderFormat for LastModified {
25+
fn fmt_header(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
26+
let tm = **self;
27+
match tm.tm_utcoff {
28+
0 => tm.rfc822().fmt(fmt),
29+
_ => tm.to_utc().rfc822().fmt(fmt)
30+
}
31+
}
32+
}
33+
34+
impl FromStr for LastModified {
35+
fn from_str(s: &str) -> Option<LastModified> {
36+
tm_from_str(s).map(LastModified)
37+
}
38+
}
39+
40+
bench_header!(imf_fixdate, LastModified, { vec![b"Sun, 07 Nov 1994 08:48:37 GMT".to_vec()] })
41+
bench_header!(rfc_850, LastModified, { vec![b"Sunday, 06-Nov-94 08:49:37 GMT".to_vec()] })
42+
bench_header!(asctime, LastModified, { vec![b"Sun Nov 6 08:49:37 1994".to_vec()] })

src/header/common/mod.rs

+12
Original file line numberDiff line numberDiff line change
@@ -8,12 +8,15 @@
88
99
pub use self::accept::Accept;
1010
pub use self::authorization::Authorization;
11+
pub use self::cache_control::CacheControl;
1112
pub use self::cookie::Cookies;
1213
pub use self::connection::Connection;
1314
pub use self::content_length::ContentLength;
1415
pub use self::content_type::ContentType;
1516
pub use self::date::Date;
17+
pub use self::expires::Expires;
1618
pub use self::host::Host;
19+
pub use self::last_modified::LastModified;
1720
pub use self::location::Location;
1821
pub use self::transfer_encoding::TransferEncoding;
1922
pub use self::upgrade::Upgrade;
@@ -72,6 +75,9 @@ pub mod accept;
7275
/// Exposes the Authorization header.
7376
pub mod authorization;
7477

78+
/// Exposes the CacheControl header.
79+
pub mod cache_control;
80+
7581
/// Exposes the Cookie header.
7682
pub mod cookie;
7783

@@ -87,9 +93,15 @@ pub mod content_type;
8793
/// Exposes the Date header.
8894
pub mod date;
8995

96+
/// Exposes the Expires header.
97+
pub mod expires;
98+
9099
/// Exposes the Host header.
91100
pub mod host;
92101

102+
/// Exposes the LastModified header.
103+
pub mod last_modified;
104+
93105
/// Exposes the Location header.
94106
pub mod location;
95107

src/header/common/transfer_encoding.rs

+2-3
Original file line numberDiff line numberDiff line change
@@ -77,14 +77,13 @@ impl Header for TransferEncoding {
7777
}
7878

7979
fn parse_header(raw: &[Vec<u8>]) -> Option<TransferEncoding> {
80-
from_comma_delimited(raw).map(|vec| TransferEncoding(vec))
80+
from_comma_delimited(raw).map(TransferEncoding)
8181
}
8282
}
8383

8484
impl HeaderFormat for TransferEncoding {
8585
fn fmt_header(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
86-
let TransferEncoding(ref parts) = *self;
87-
fmt_comma_delimited(fmt, parts[])
86+
fmt_comma_delimited(fmt, self[])
8887
}
8988
}
9089

0 commit comments

Comments
 (0)