]> OzVa Git service - ozva-cloud/commitdiff
feat: implements remaining http cache conditionalss (#407)
authorMatthias Möller <m_moeller@live.de>
Sun, 23 Jun 2024 12:25:07 +0000 (14:25 +0200)
committerGitHub <noreply@github.com>
Sun, 23 Jun 2024 12:25:07 +0000 (20:25 +0800)
* implements remaining http conditionals

* computed etag is not optional

src/server.rs
tests/cache.rs [new file with mode: 0644]

index 942da7d039477f78c5aeee638e8768562668e51a..a28c4f309229b89f74b62663b3019e3f9e42432b 100644 (file)
@@ -15,8 +15,8 @@ use chrono::{LocalResult, TimeZone, Utc};
 use futures_util::{pin_mut, TryStreamExt};
 use headers::{
     AcceptRanges, AccessControlAllowCredentials, AccessControlAllowOrigin, CacheControl,
-    ContentLength, ContentType, ETag, HeaderMap, HeaderMapExt, IfModifiedSince, IfNoneMatch,
-    IfRange, LastModified, Range,
+    ContentLength, ContentType, ETag, HeaderMap, HeaderMapExt, IfMatch, IfModifiedSince,
+    IfNoneMatch, IfRange, IfUnmodifiedSince, LastModified, Range,
 };
 use http_body_util::{combinators::BoxBody, BodyExt, StreamBody};
 use hyper::body::Frame;
@@ -796,18 +796,29 @@ impl Server {
         let size = meta.len();
         let mut use_range = true;
         if let Some((etag, last_modified)) = extract_cache_headers(&meta) {
-            let cached = {
-                if let Some(if_none_match) = headers.typed_get::<IfNoneMatch>() {
-                    !if_none_match.precondition_passes(&etag)
-                } else if let Some(if_modified_since) = headers.typed_get::<IfModifiedSince>() {
-                    !if_modified_since.is_modified(last_modified.into())
-                } else {
-                    false
+            if let Some(if_unmodified_since) = headers.typed_get::<IfUnmodifiedSince>() {
+                if !if_unmodified_since.precondition_passes(last_modified.into()) {
+                    *res.status_mut() = StatusCode::PRECONDITION_FAILED;
+                    return Ok(());
+                }
+            }
+            if let Some(if_match) = headers.typed_get::<IfMatch>() {
+                if !if_match.precondition_passes(&etag) {
+                    *res.status_mut() = StatusCode::PRECONDITION_FAILED;
+                    return Ok(());
+                }
+            }
+            if let Some(if_modified_since) = headers.typed_get::<IfModifiedSince>() {
+                if !if_modified_since.is_modified(last_modified.into()) {
+                    *res.status_mut() = StatusCode::NOT_MODIFIED;
+                    return Ok(());
+                }
+            }
+            if let Some(if_none_match) = headers.typed_get::<IfNoneMatch>() {
+                if !if_none_match.precondition_passes(&etag) {
+                    *res.status_mut() = StatusCode::NOT_MODIFIED;
+                    return Ok(());
                 }
-            };
-            if cached {
-                *res.status_mut() = StatusCode::NOT_MODIFIED;
-                return Ok(());
             }
 
             res.headers_mut().typed_insert(last_modified);
diff --git a/tests/cache.rs b/tests/cache.rs
new file mode 100644 (file)
index 0000000..588f021
--- /dev/null
@@ -0,0 +1,80 @@
+mod fixtures;
+mod utils;
+
+use chrono::{DateTime, Duration};
+use fixtures::{server, Error, TestServer};
+use reqwest::header::{
+    HeaderName, ETAG, IF_MATCH, IF_MODIFIED_SINCE, IF_NONE_MATCH, IF_UNMODIFIED_SINCE,
+    LAST_MODIFIED,
+};
+use reqwest::StatusCode;
+use rstest::rstest;
+
+#[rstest]
+#[case(IF_UNMODIFIED_SINCE, Duration::days(1), StatusCode::OK)]
+#[case(IF_UNMODIFIED_SINCE, Duration::days(0), StatusCode::OK)]
+#[case(IF_UNMODIFIED_SINCE, Duration::days(-1), StatusCode::PRECONDITION_FAILED)]
+#[case(IF_MODIFIED_SINCE, Duration::days(1), StatusCode::NOT_MODIFIED)]
+#[case(IF_MODIFIED_SINCE, Duration::days(0), StatusCode::NOT_MODIFIED)]
+#[case(IF_MODIFIED_SINCE, Duration::days(-1), StatusCode::OK)]
+fn get_file_with_if_modified_since_condition(
+    #[case] header_condition: HeaderName,
+    #[case] duration_after_file_modified: Duration,
+    #[case] expected_code: StatusCode,
+    server: TestServer,
+) -> Result<(), Error> {
+    let resp = fetch!(b"HEAD", format!("{}index.html", server.url())).send()?;
+
+    let last_modified = resp
+        .headers()
+        .get(LAST_MODIFIED)
+        .and_then(|h| h.to_str().ok())
+        .and_then(|s| DateTime::parse_from_rfc2822(s).ok())
+        .expect("Recieved no valid last modified header");
+
+    let req_modified_time = (last_modified + duration_after_file_modified)
+        .format("%a, %e %b %Y %T GMT")
+        .to_string();
+
+    let resp = fetch!(b"GET", format!("{}index.html", server.url()))
+        .header(header_condition, req_modified_time)
+        .send()?;
+
+    assert_eq!(resp.status(), expected_code);
+    Ok(())
+}
+
+fn same_etag(etag: &str) -> String {
+    etag.to_owned()
+}
+
+fn different_etag(etag: &str) -> String {
+    format!("{}1234", etag)
+}
+
+#[rstest]
+#[case(IF_MATCH, same_etag, StatusCode::OK)]
+#[case(IF_MATCH, different_etag, StatusCode::PRECONDITION_FAILED)]
+#[case(IF_NONE_MATCH, same_etag, StatusCode::NOT_MODIFIED)]
+#[case(IF_NONE_MATCH, different_etag, StatusCode::OK)]
+fn get_file_with_etag_match(
+    #[case] header_condition: HeaderName,
+    #[case] etag_modifier: fn(&str) -> String,
+    #[case] expected_code: StatusCode,
+    server: TestServer,
+) -> Result<(), Error> {
+    let resp = fetch!(b"HEAD", format!("{}index.html", server.url())).send()?;
+
+    let etag = resp
+        .headers()
+        .get(ETAG)
+        .and_then(|h| h.to_str().ok())
+        .expect("Recieved no valid etag header");
+
+    let resp = fetch!(b"GET", format!("{}index.html", server.url()))
+        .header(header_condition, etag_modifier(etag))
+        .send()?;
+
+    assert_eq!(resp.status(), expected_code);
+    Ok(())
+}