Skip to content

Adds CacheControl, Expires, and LastModified headers #165

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
Dec 3, 2014
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
165 changes: 165 additions & 0 deletions src/header/common/cache_control.rs
Original file line number Diff line number Diff line change
@@ -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()] })
45 changes: 6 additions & 39 deletions src/header/common/date.rs
Original file line number Diff line number Diff line change
@@ -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.
Expand All @@ -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)
Expand All @@ -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)
}
}

Expand Down
42 changes: 42 additions & 0 deletions src/header/common/expires.rs
Original file line number Diff line number Diff line change
@@ -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 changes: 42 additions & 0 deletions src/header/common/last_modified.rs
Original file line number Diff line number Diff line change
@@ -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()] })
12 changes: 12 additions & 0 deletions src/header/common/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -81,6 +84,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;

Expand All @@ -96,9 +102,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;

Expand Down
5 changes: 2 additions & 3 deletions src/header/common/transfer_encoding.rs
Original file line number Diff line number Diff line change
Expand Up @@ -76,14 +76,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[])
}
}

Expand Down
Loading