headers/common/
if_range.rs

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