1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
use std::time::SystemTime;

use super::{ETag, LastModified};
use util::{EntityTag, HttpDate};
use HeaderValue;

/// `If-Range` header, defined in [RFC7233](http://tools.ietf.org/html/rfc7233#section-3.2)
///
/// If a client has a partial copy of a representation and wishes to have
/// an up-to-date copy of the entire representation, it could use the
/// Range header field with a conditional GET (using either or both of
/// If-Unmodified-Since and If-Match.)  However, if the precondition
/// fails because the representation has been modified, the client would
/// then have to make a second request to obtain the entire current
/// representation.
///
/// The `If-Range` header field allows a client to \"short-circuit\" the
/// second request.  Informally, its meaning is as follows: if the
/// representation is unchanged, send me the part(s) that I am requesting
/// in Range; otherwise, send me the entire representation.
///
/// # ABNF
///
/// ```text
/// If-Range = entity-tag / HTTP-date
/// ```
///
/// # Example values
///
/// * `Sat, 29 Oct 1994 19:43:31 GMT`
/// * `\"xyzzy\"`
///
/// # Examples
///
/// ```
/// # extern crate headers;
/// use headers::IfRange;
/// use std::time::{SystemTime, Duration};
///
/// let fetched = SystemTime::now() - Duration::from_secs(60 * 60 * 24);
/// let if_range = IfRange::date(fetched);
/// ```
#[derive(Clone, Debug, PartialEq)]
pub struct IfRange(IfRange_);

derive_header! {
    IfRange(_),
    name: IF_RANGE
}

impl IfRange {
    /// Create an `IfRange` header with an entity tag.
    pub fn etag(tag: ETag) -> IfRange {
        IfRange(IfRange_::EntityTag(tag.0))
    }

    /// Create an `IfRange` header with a date value.
    pub fn date(time: SystemTime) -> IfRange {
        IfRange(IfRange_::Date(time.into()))
    }

    /// Checks if the resource has been modified, or if the range request
    /// can be served.
    pub fn is_modified(&self, etag: Option<&ETag>, last_modified: Option<&LastModified>) -> bool {
        match self.0 {
            IfRange_::Date(since) => last_modified.map(|time| since < time.0).unwrap_or(true),
            IfRange_::EntityTag(ref entity) => {
                etag.map(|etag| !etag.0.strong_eq(entity)).unwrap_or(true)
            }
        }
    }
}

#[derive(Clone, Debug, PartialEq)]
enum IfRange_ {
    /// The entity-tag the client has of the resource
    EntityTag(EntityTag),
    /// The date when the client retrieved the resource
    Date(HttpDate),
}

impl ::util::TryFromValues for IfRange_ {
    fn try_from_values<'i, I>(values: &mut I) -> Result<Self, ::Error>
    where
        I: Iterator<Item = &'i HeaderValue>,
    {
        values
            .next()
            .and_then(|val| {
                if let Some(tag) = EntityTag::from_val(val) {
                    return Some(IfRange_::EntityTag(tag));
                }

                let date = HttpDate::from_val(val)?;
                Some(IfRange_::Date(date))
            })
            .ok_or_else(::Error::invalid)
    }
}

impl<'a> From<&'a IfRange_> for HeaderValue {
    fn from(if_range: &'a IfRange_) -> HeaderValue {
        match *if_range {
            IfRange_::EntityTag(ref tag) => tag.into(),
            IfRange_::Date(ref date) => date.into(),
        }
    }
}

/*
#[cfg(test)]
mod tests {
    use std::str;
    use *;
    use super::IfRange as HeaderField;
    test_header!(test1, vec![b"Sat, 29 Oct 1994 19:43:31 GMT"]);
    test_header!(test2, vec![b"\"xyzzy\""]);
    test_header!(test3, vec![b"this-is-invalid"], None::<IfRange>);
}
*/

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_is_modified_etag() {
        let etag = ETag::from_static("\"xyzzy\"");
        let if_range = IfRange::etag(etag.clone());

        assert!(!if_range.is_modified(Some(&etag), None));

        let etag = ETag::from_static("W/\"xyzzy\"");
        assert!(if_range.is_modified(Some(&etag), None));
    }
}