headers/common/
if_range.rs

1use std::time::SystemTime;
2
3use super::{ETag, LastModified};
4use util::{EntityTag, HttpDate};
5use 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)]
44pub struct IfRange(IfRange_);
45
46derive_header! {
47    IfRange(_),
48    name: IF_RANGE
49}
50
51impl IfRange {
52    /// Create an `IfRange` header with an entity tag.
53    pub fn etag(tag: ETag) -> IfRange {
54        IfRange(IfRange_::EntityTag(tag.0))
55    }
56
57    /// Create an `IfRange` header with a date value.
58    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.
64    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) => {
68                etag.map(|etag| !etag.0.strong_eq(entity)).unwrap_or(true)
69            }
70        }
71    }
72}
73
74#[derive(Clone, Debug, PartialEq)]
75enum IfRange_ {
76    /// The entity-tag the client has of the resource
77    EntityTag(EntityTag),
78    /// The date when the client retrieved the resource
79    Date(HttpDate),
80}
81
82impl ::util::TryFromValues for IfRange_ {
83    fn try_from_values<'i, I>(values: &mut I) -> Result<Self, ::Error>
84    where
85        I: Iterator<Item = &'i HeaderValue>,
86    {
87        values
88            .next()
89            .and_then(|val| {
90                if let Some(tag) = EntityTag::from_val(val) {
91                    return Some(IfRange_::EntityTag(tag));
92                }
93
94                let date = HttpDate::from_val(val)?;
95                Some(IfRange_::Date(date))
96            })
97            .ok_or_else(::Error::invalid)
98    }
99}
100
101impl<'a> From<&'a IfRange_> for HeaderValue {
102    fn from(if_range: &'a IfRange_) -> HeaderValue {
103        match *if_range {
104            IfRange_::EntityTag(ref tag) => tag.into(),
105            IfRange_::Date(ref date) => date.into(),
106        }
107    }
108}
109
110/*
111#[cfg(test)]
112mod tests {
113    use std::str;
114    use *;
115    use super::IfRange as HeaderField;
116    test_header!(test1, vec![b"Sat, 29 Oct 1994 19:43:31 GMT"]);
117    test_header!(test2, vec![b"\"xyzzy\""]);
118    test_header!(test3, vec![b"this-is-invalid"], None::<IfRange>);
119}
120*/
121
122#[cfg(test)]
123mod tests {
124    use super::*;
125
126    #[test]
127    fn test_is_modified_etag() {
128        let etag = ETag::from_static("\"xyzzy\"");
129        let if_range = IfRange::etag(etag.clone());
130
131        assert!(!if_range.is_modified(Some(&etag), None));
132
133        let etag = ETag::from_static("W/\"xyzzy\"");
134        assert!(if_range.is_modified(Some(&etag), None));
135    }
136}