static_web_server/
conditional_headers.rs

1// SPDX-License-Identifier: MIT OR Apache-2.0
2// This file is part of Static Web Server.
3// See https://static-web-server.net/ for more information
4// Copyright (C) 2019-present Jose Quintana <joseluisq.net>
5
6//! Module that provides HTTP header conditionals.
7//!
8
9use headers::{
10    HeaderMap, HeaderMapExt, HeaderValue, IfModifiedSince, IfRange, IfUnmodifiedSince,
11    LastModified, Range,
12};
13use hyper::{Body, Response, StatusCode};
14
15#[derive(Debug)]
16pub(crate) struct ConditionalHeaders {
17    pub(crate) if_modified_since: Option<IfModifiedSince>,
18    pub(crate) if_unmodified_since: Option<IfUnmodifiedSince>,
19    pub(crate) if_range: Option<IfRange>,
20    pub(crate) range: Option<Range>,
21}
22
23impl ConditionalHeaders {
24    pub(crate) fn new(headers: &HeaderMap<HeaderValue>) -> Self {
25        let if_modified_since = headers.typed_get::<IfModifiedSince>();
26        let if_unmodified_since = headers.typed_get::<IfUnmodifiedSince>();
27        let if_range = headers.typed_get::<IfRange>();
28        let range = headers.typed_get::<Range>();
29
30        Self {
31            if_modified_since,
32            if_unmodified_since,
33            if_range,
34            range,
35        }
36    }
37}
38
39impl ConditionalHeaders {
40    pub(crate) fn check(self, last_modified: Option<LastModified>) -> ConditionalBody {
41        if let Some(since) = self.if_unmodified_since {
42            let precondition = last_modified
43                .map(|time| since.precondition_passes(time.into()))
44                .unwrap_or(false);
45
46            tracing::trace!(
47                "if-unmodified-since? {:?} vs {:?} = {}",
48                since,
49                last_modified,
50                precondition
51            );
52            if !precondition {
53                let mut res = Response::new(Body::empty());
54                *res.status_mut() = StatusCode::PRECONDITION_FAILED;
55                return ConditionalBody::NoBody(res);
56            }
57        }
58
59        if let Some(since) = self.if_modified_since {
60            tracing::trace!(
61                "if-modified-since? header = {:?}, file = {:?}",
62                since,
63                last_modified
64            );
65            let unmodified = last_modified
66                .map(|time| !since.is_modified(time.into()))
67                // no last_modified means its always modified
68                .unwrap_or(false);
69            if unmodified {
70                let mut res = Response::new(Body::empty());
71                *res.status_mut() = StatusCode::NOT_MODIFIED;
72                return ConditionalBody::NoBody(res);
73            }
74        }
75
76        if let Some(if_range) = self.if_range {
77            tracing::trace!("if-range? {:?} vs {:?}", if_range, last_modified);
78
79            let can_range = !if_range.is_modified(None, last_modified.as_ref());
80            if !can_range {
81                return ConditionalBody::WithBody(None);
82            }
83        }
84
85        ConditionalBody::WithBody(self.range)
86    }
87}
88
89pub(crate) enum ConditionalBody {
90    NoBody(Response<Body>),
91    WithBody(Option<Range>),
92}