volga 0.9.1

Easy & Fast Web Framework for Rust
Documentation
use httpdate::parse_http_date;
use std::time::SystemTime;

use crate::headers::{ETag, ETagRef, HttpHeaders, IF_MODIFIED_SINCE, IF_NONE_MATCH};

#[inline]
#[allow(dead_code)]
pub(crate) fn validate_etag(etag: &ETag, headers: &HttpHeaders) -> bool {
    let Some(hv) = headers.get_raw(&IF_NONE_MATCH) else {
        return false;
    };

    let Ok(s) = hv.to_str() else {
        return false;
    };

    let target_tag = etag.tag();

    s.split(',')
        .map(|v| v.trim())
        .filter(|v| !v.is_empty())
        .filter_map(|raw| ETagRef::parse(raw).ok())
        .any(|candidate| candidate.weak_eq_tag(target_tag))
}

#[inline]
#[allow(dead_code)]
pub(crate) fn validate_last_modified(last_modified: SystemTime, headers: &HttpHeaders) -> bool {
    headers
        .get_raw(&IF_MODIFIED_SINCE)
        .and_then(|if_modified_since| if_modified_since.to_str().ok())
        .and_then(|if_modified_since| parse_http_date(if_modified_since).ok())
        .is_some_and(|value| last_modified <= value)
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::headers::{
        ETag, HeaderMap, HeaderValue, HttpHeaders, IF_MODIFIED_SINCE, IF_NONE_MATCH,
    };
    use std::time::Duration;

    #[test]
    fn it_validates_etag_list() {
        let mut headers = HeaderMap::new();
        headers.insert(
            IF_NONE_MATCH,
            HeaderValue::from_static("\"123\",\"321\",\"111\""),
        );

        let headers = HttpHeaders::from(headers);

        assert!(validate_etag(&ETag::strong("123"), &headers));
    }

    #[test]
    fn it_validates_etag_not_present_in_list() {
        let mut headers = HeaderMap::new();
        headers.insert(
            IF_NONE_MATCH,
            HeaderValue::from_static("\"123\",\"321\",\"111\""),
        );

        let headers = HttpHeaders::from(headers);

        assert!(!validate_etag(&ETag::strong("555"), &headers));
    }

    #[test]
    fn it_validates_etag_single() {
        let mut headers = HeaderMap::new();
        headers.insert(IF_NONE_MATCH, HeaderValue::from_static("\"123\""));

        let headers = HttpHeaders::from(headers);

        assert!(validate_etag(&ETag::strong("123"), &headers));
    }

    #[test]
    fn it_validates_etag_single_different_value() {
        let mut headers = HeaderMap::new();
        headers.insert(IF_NONE_MATCH, HeaderValue::from_static("\"123\""));

        let headers = HttpHeaders::from(headers);

        assert!(!validate_etag(&ETag::strong("555"), &headers));
    }

    #[test]
    fn it_validates_etag_when_if_none_match_missing() {
        let headers = HttpHeaders::from(HeaderMap::new());

        assert!(!validate_etag(&ETag::strong("123"), &headers));
    }

    #[test]
    fn it_validates_last_modified() {
        let now = SystemTime::now();
        let mut headers = HeaderMap::new();
        headers.insert(
            IF_MODIFIED_SINCE,
            HeaderValue::from_str(&httpdate::fmt_http_date(now)).unwrap(),
        );

        let headers = HttpHeaders::from(headers);

        assert!(validate_last_modified(
            now - Duration::from_secs(10),
            &headers
        ));
    }

    #[test]
    fn it_validates_last_modified_resource_has_been_updated() {
        let now = SystemTime::now();
        let mut headers = HeaderMap::new();
        headers.insert(
            IF_MODIFIED_SINCE,
            HeaderValue::from_str(&httpdate::fmt_http_date(now)).unwrap(),
        );

        let headers = HttpHeaders::from(headers);

        assert!(!validate_last_modified(
            now + Duration::from_secs(10),
            &headers
        ));
    }

    #[test]
    fn it_validates_last_modified_when_if_modified_since_missing() {
        let now = SystemTime::now();
        let headers = HttpHeaders::from(HeaderMap::new());

        assert!(!validate_last_modified(now, &headers));
    }
}