1 use std::time::SystemTime;
2 
3 use super::{ETag, LastModified};
4 use util::{EntityTag, HttpDate};
5 use HeaderValue;
6 
7 /// `If-Range` header, defined in [RFC7233](http://tools.ietf.org/html/rfc7233#section-3.2)
8 ///
9 /// If a client has a partial copy of a representation and wishes to have
10 /// an up-to-date copy of the entire representation, it could use the
11 /// Range header field with a conditional GET (using either or both of
12 /// If-Unmodified-Since and If-Match.)  However, if the precondition
13 /// fails because the representation has been modified, the client would
14 /// then have to make a second request to obtain the entire current
15 /// representation.
16 ///
17 /// The `If-Range` header field allows a client to \"short-circuit\" the
18 /// second request.  Informally, its meaning is as follows: if the
19 /// representation is unchanged, send me the part(s) that I am requesting
20 /// in Range; otherwise, send me the entire representation.
21 ///
22 /// # ABNF
23 ///
24 /// ```text
25 /// If-Range = entity-tag / HTTP-date
26 /// ```
27 ///
28 /// # Example values
29 ///
30 /// * `Sat, 29 Oct 1994 19:43:31 GMT`
31 /// * `\"xyzzy\"`
32 ///
33 /// # Examples
34 ///
35 /// ```
36 /// # extern crate headers;
37 /// use headers::IfRange;
38 /// use std::time::{SystemTime, Duration};
39 ///
40 /// let fetched = SystemTime::now() - Duration::from_secs(60 * 60 * 24);
41 /// let if_range = IfRange::date(fetched);
42 /// ```
43 #[derive(Clone, Debug, PartialEq)]
44 pub struct IfRange(IfRange_);
45 
46 derive_header! {
47     IfRange(_),
48     name: IF_RANGE
49 }
50 
51 impl IfRange {
52     /// Create an `IfRange` header with an entity tag.
etag(tag: ETag) -> IfRange53     pub fn etag(tag: ETag) -> IfRange {
54         IfRange(IfRange_::EntityTag(tag.0))
55     }
56 
57     /// Create an `IfRange` header with a date value.
date(time: SystemTime) -> IfRange58     pub fn date(time: SystemTime) -> IfRange {
59         IfRange(IfRange_::Date(time.into()))
60     }
61 
62     /// Checks if the resource has been modified, or if the range request
63     /// can be served.
is_modified(&self, etag: Option<&ETag>, last_modified: Option<&LastModified>) -> bool64     pub fn is_modified(&self, etag: Option<&ETag>, last_modified: Option<&LastModified>) -> bool {
65         match self.0 {
66             IfRange_::Date(since) => last_modified.map(|time| since < time.0).unwrap_or(true),
67             IfRange_::EntityTag(ref entity) => etag.map(|etag| !etag.0.strong_eq(entity)).unwrap_or(true),
68         }
69     }
70 }
71 
72 #[derive(Clone, Debug, PartialEq)]
73 enum IfRange_ {
74     /// The entity-tag the client has of the resource
75     EntityTag(EntityTag),
76     /// The date when the client retrieved the resource
77     Date(HttpDate),
78 }
79 
80 impl ::util::TryFromValues for IfRange_ {
try_from_values<'i, I>(values: &mut I) -> Result<Self, ::Error> where I: Iterator<Item = &'i HeaderValue>,81     fn try_from_values<'i, I>(values: &mut I) -> Result<Self, ::Error>
82     where
83         I: Iterator<Item = &'i HeaderValue>,
84     {
85         values
86             .next()
87             .and_then(|val| {
88                 if let Some(tag) = EntityTag::from_val(val) {
89                     return Some(IfRange_::EntityTag(tag));
90                 }
91 
92                 let date = HttpDate::from_val(val)?;
93                 Some(IfRange_::Date(date))
94             })
95             .ok_or_else(::Error::invalid)
96     }
97 }
98 
99 impl<'a> From<&'a IfRange_> for HeaderValue {
from(if_range: &'a IfRange_) -> HeaderValue100     fn from(if_range: &'a IfRange_) -> HeaderValue {
101         match *if_range {
102             IfRange_::EntityTag(ref tag) => tag.into(),
103             IfRange_::Date(ref date) => date.into(),
104         }
105     }
106 }
107 
108 /*
109 #[cfg(test)]
110 mod tests {
111     use std::str;
112     use *;
113     use super::IfRange as HeaderField;
114     test_header!(test1, vec![b"Sat, 29 Oct 1994 19:43:31 GMT"]);
115     test_header!(test2, vec![b"\"xyzzy\""]);
116     test_header!(test3, vec![b"this-is-invalid"], None::<IfRange>);
117 }
118 */
119 
120 #[cfg(test)]
121 mod tests {
122     use super::*;
123 
124     #[test]
test_is_modified_etag()125     fn test_is_modified_etag() {
126         let etag = ETag::from_static("\"xyzzy\"");
127         let if_range = IfRange::etag(etag.clone());
128 
129         assert!(!if_range.is_modified(Some(&etag), None));
130 
131         let etag = ETag::from_static("W/\"xyzzy\"");
132         assert!(if_range.is_modified(Some(&etag), None));
133     }
134 }
135