From ceedef8c97f5872436705c00a34bacbff056c4cf Mon Sep 17 00:00:00 2001
From: Stephen <webmaster@scd31.com>
Date: Tue, 14 Feb 2023 18:16:11 -0400
Subject: [PATCH] move to chrono. also add asset expiry

---
 Cargo.lock    | 163 ++++++++++++++++++++++++++++++++++++++++++++++++--
 Cargo.toml    |   2 +-
 src/date.rs   |   6 +-
 src/image.rs  |   8 ++-
 src/main.rs   |  17 ++++--
 src/misc.rs   |   7 ++-
 src/parser.rs |  17 +++---
 src/post.rs   |  11 ++--
 src/resp.rs   |  24 ++++++++
 9 files changed, 226 insertions(+), 29 deletions(-)

diff --git a/Cargo.lock b/Cargo.lock
index 77c607e..37a7a47 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -8,6 +8,15 @@ version = "1.0.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe"
 
+[[package]]
+name = "android_system_properties"
+version = "0.1.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311"
+dependencies = [
+ "libc",
+]
+
 [[package]]
 name = "anyhow"
 version = "1.0.69"
@@ -108,12 +117,43 @@ version = "1.0.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
 
+[[package]]
+name = "chrono"
+version = "0.4.23"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "16b0a3d9ed01224b22057780a37bb8c5dbfe1be8ba48678e7bf57ec4b385411f"
+dependencies = [
+ "iana-time-zone",
+ "js-sys",
+ "num-integer",
+ "num-traits",
+ "time 0.1.45",
+ "wasm-bindgen",
+ "winapi",
+]
+
+[[package]]
+name = "codespan-reporting"
+version = "0.11.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3538270d33cc669650c4b093848450d380def10c331d38c768e34cac80576e6e"
+dependencies = [
+ "termcolor",
+ "unicode-width",
+]
+
 [[package]]
 name = "color_quant"
 version = "1.1.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "3d7b894f5411737b7867f4827955924d7c254fc9f4d91a6aad6b097804b1018b"
 
+[[package]]
+name = "core-foundation-sys"
+version = "0.8.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5827cebf4670468b8772dd191856768aedcb1b0278a04f989f7766351917b9dc"
+
 [[package]]
 name = "cpufeatures"
 version = "0.2.5"
@@ -191,6 +231,50 @@ dependencies = [
  "typenum",
 ]
 
+[[package]]
+name = "cxx"
+version = "1.0.90"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "90d59d9acd2a682b4e40605a242f6670eaa58c5957471cbf85e8aa6a0b97a5e8"
+dependencies = [
+ "cc",
+ "cxxbridge-flags",
+ "cxxbridge-macro",
+ "link-cplusplus",
+]
+
+[[package]]
+name = "cxx-build"
+version = "1.0.90"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ebfa40bda659dd5c864e65f4c9a2b0aff19bea56b017b9b77c73d3766a453a38"
+dependencies = [
+ "cc",
+ "codespan-reporting",
+ "once_cell",
+ "proc-macro2",
+ "quote",
+ "scratch",
+ "syn",
+]
+
+[[package]]
+name = "cxxbridge-flags"
+version = "1.0.90"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "457ce6757c5c70dc6ecdbda6925b958aae7f959bda7d8fb9bde889e34a09dc03"
+
+[[package]]
+name = "cxxbridge-macro"
+version = "1.0.90"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ebf883b7aacd7b2aeb2a7b338648ee19f57c140d4ee8e52c68979c6b2f7f2263"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
 [[package]]
 name = "digest"
 version = "0.10.6"
@@ -331,7 +415,7 @@ dependencies = [
  "cfg-if",
  "js-sys",
  "libc",
- "wasi",
+ "wasi 0.11.0+wasi-snapshot-preview1",
  "wasm-bindgen",
 ]
 
@@ -471,6 +555,30 @@ dependencies = [
  "want",
 ]
 
+[[package]]
+name = "iana-time-zone"
+version = "0.1.53"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "64c122667b287044802d6ce17ee2ddf13207ed924c712de9a66a5814d5b64765"
+dependencies = [
+ "android_system_properties",
+ "core-foundation-sys",
+ "iana-time-zone-haiku",
+ "js-sys",
+ "wasm-bindgen",
+ "winapi",
+]
+
+[[package]]
+name = "iana-time-zone-haiku"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0703ae284fc167426161c2e3f1da3ea71d94b21bedbcc9494e92b28e334e3dca"
+dependencies = [
+ "cxx",
+ "cxx-build",
+]
+
 [[package]]
 name = "idna"
 version = "0.3.0"
@@ -583,6 +691,15 @@ dependencies = [
  "safemem",
 ]
 
+[[package]]
+name = "link-cplusplus"
+version = "1.0.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ecd207c9c713c34f95a097a5b029ac2ce6010530c7b49d7fea24d977dede04f5"
+dependencies = [
+ "cc",
+]
+
 [[package]]
 name = "linked-hash-map"
 version = "0.5.6"
@@ -662,7 +779,7 @@ checksum = "e5d732bc30207a6423068df043e3d02e0735b155ad7ce1a6f76fe2baa5b158de"
 dependencies = [
  "libc",
  "log",
- "wasi",
+ "wasi 0.11.0+wasi-snapshot-preview1",
  "windows-sys 0.42.0",
 ]
 
@@ -785,12 +902,12 @@ name = "org_flux"
 version = "0.1.0"
 dependencies = [
  "anyhow",
+ "chrono",
  "fnv",
  "image",
  "orgize",
  "serde",
  "syntect",
- "time",
  "tokio",
  "toml",
  "warp",
@@ -889,7 +1006,7 @@ dependencies = [
  "line-wrap",
  "quick-xml",
  "serde",
- "time",
+ "time 0.3.17",
 ]
 
 [[package]]
@@ -1067,6 +1184,12 @@ version = "1.1.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd"
 
+[[package]]
+name = "scratch"
+version = "1.0.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ddccb15bcce173023b3fedd9436f882a0739b8dfb45e4f6b6002bee5929f61b2"
+
 [[package]]
 name = "serde"
 version = "1.0.152"
@@ -1248,6 +1371,15 @@ dependencies = [
  "winapi",
 ]
 
+[[package]]
+name = "termcolor"
+version = "1.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "be55cf8942feac5c765c2c993422806843c9a9a45d4d5c407ad6dd2ea95eb9b6"
+dependencies = [
+ "winapi-util",
+]
+
 [[package]]
 name = "thiserror"
 version = "1.0.38"
@@ -1288,6 +1420,17 @@ dependencies = [
  "weezl",
 ]
 
+[[package]]
+name = "time"
+version = "0.1.45"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1b797afad3f312d1c66a56d11d0316f916356d11bd158fbc6ca6389ff6bf805a"
+dependencies = [
+ "libc",
+ "wasi 0.10.0+wasi-snapshot-preview1",
+ "winapi",
+]
+
 [[package]]
 name = "time"
 version = "0.3.17"
@@ -1529,6 +1672,12 @@ dependencies = [
  "tinyvec",
 ]
 
+[[package]]
+name = "unicode-width"
+version = "0.1.10"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c0edd1e5b14653f783770bce4a4dabb4a5108a5370a5f5d8cfe8710c361f6c8b"
+
 [[package]]
 name = "url"
 version = "2.3.1"
@@ -1604,6 +1753,12 @@ dependencies = [
  "tracing",
 ]
 
+[[package]]
+name = "wasi"
+version = "0.10.0+wasi-snapshot-preview1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1a143597ca7c7793eff794def352d41792a93c481eb1042423ff7ff72ba2c31f"
+
 [[package]]
 name = "wasi"
 version = "0.11.0+wasi-snapshot-preview1"
diff --git a/Cargo.toml b/Cargo.toml
index 65a47df..7dfe630 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -12,7 +12,7 @@ syntect = "5.0"
 anyhow = "1.0"
 tokio = { version = "1.0", features = ["full"] }
 warp = "0.3"
-time = { version = "0.3", features = ["macros", "formatting"] }
+chrono = { version = "0.4", features = ["clock"] }
 image = "0.24.5"
 fnv = "1.0"
 serde = { version = "1.0", features = ["derive"] }
diff --git a/src/date.rs b/src/date.rs
index 0de7ad8..1e29dba 100644
--- a/src/date.rs
+++ b/src/date.rs
@@ -1,11 +1,11 @@
 use anyhow::Context;
-use time::{macros::format_description, Date};
+use chrono::NaiveDate;
 
 // very basic org mode date parser
-pub fn parse_org_date(s: &str) -> anyhow::Result<Date> {
+pub fn parse_org_date(s: &str) -> anyhow::Result<NaiveDate> {
     let s = s.strip_prefix('<').context("Invalid date")?;
     let s = s.strip_suffix('>').context("Invalid date")?;
     let s = s.rsplit_once(' ').context("Invalid date")?.0;
 
-    Ok(Date::parse(s, format_description!("[year]-[month]-[day]"))?)
+    Ok(NaiveDate::parse_from_str(s, "%Y-%m-%d")?)
 }
diff --git a/src/image.rs b/src/image.rs
index c512d79..312688f 100644
--- a/src/image.rs
+++ b/src/image.rs
@@ -3,7 +3,11 @@ use std::{io::Cursor, path::Path};
 use anyhow::{bail, Context};
 use image::{io::Reader as ImageReader, DynamicImage, ImageOutputFormat};
 
-use crate::{load::Loadable, resp::Response, util::slugify};
+use crate::{
+    load::Loadable,
+    resp::{Expiry, Response},
+    util::slugify,
+};
 
 pub struct MyImage {
     original: DynamicImage,
@@ -30,11 +34,13 @@ impl MyImage {
             self.original_url,
             Response {
                 content_type,
+                expiry: Expiry::Year,
                 data: original,
             },
             self.thumbnail_url,
             Response {
                 content_type,
+                expiry: Expiry::Year,
                 data: thumbnail,
             },
         ))
diff --git a/src/main.rs b/src/main.rs
index 679afd7..2d8fb83 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -5,7 +5,10 @@ use tokio::sync::RwLock;
 use blog::{Blog, RenderedBlog};
 use warp::{
     http::HeaderValue,
-    hyper::{header::CONTENT_TYPE, Body},
+    hyper::{
+        header::{CONTENT_TYPE, EXPIRES},
+        Body,
+    },
     path::FullPath,
     reply, Filter,
 };
@@ -32,9 +35,15 @@ async fn handle_request(
     let (status, content) = blog.get(req.as_str());
 
     let mut reply = reply::Response::new(Body::from(content.data));
-    reply
-        .headers_mut()
-        .insert(CONTENT_TYPE, HeaderValue::from_static(content.content_type));
+    let headers = reply.headers_mut();
+    headers.insert(CONTENT_TYPE, HeaderValue::from_static(content.content_type));
+
+    if let Some(expires) = content.expiry.header_value() {
+        // TODO log a metric (statsd) if this fails)
+        if let Ok(hv) = HeaderValue::from_str(&expires) {
+            headers.insert(EXPIRES, hv);
+        }
+    }
     *reply.status_mut() = status;
 
     Ok(reply)
diff --git a/src/misc.rs b/src/misc.rs
index 4f725bc..7e327e3 100644
--- a/src/misc.rs
+++ b/src/misc.rs
@@ -2,7 +2,11 @@ use std::{fs::File, io::Read, path::Path};
 
 use anyhow::{bail, Context};
 
-use crate::{load::Loadable, resp::Response, util::slugify};
+use crate::{
+    load::Loadable,
+    resp::{Expiry, Response},
+    util::slugify,
+};
 
 pub struct Misc {
     pub url: String,
@@ -42,6 +46,7 @@ impl Misc {
     pub fn response(self) -> Response {
         Response {
             content_type: self.content_type,
+            expiry: Expiry::Year,
             data: self.data,
         }
     }
diff --git a/src/parser.rs b/src/parser.rs
index e94f358..c050e6c 100644
--- a/src/parser.rs
+++ b/src/parser.rs
@@ -1,6 +1,7 @@
 use anyhow::bail;
 use anyhow::Context;
 use anyhow::Error;
+use chrono::NaiveDate;
 use orgize::elements::Keyword;
 use orgize::elements::Link;
 use orgize::elements::Timestamp;
@@ -15,8 +16,6 @@ use syntect::{
     html::highlighted_html_for_string,
     parsing::SyntaxSet,
 };
-use time::macros::format_description;
-use time::Date;
 
 pub trait Parser<T> {
     fn start(&mut self, _: &Element) -> anyhow::Result<()>;
@@ -80,16 +79,14 @@ impl HtmlHandler<Error> for CustomHtmlHandler {
                 _ => bail!("Unrecognized special block name {}", block.name),
             },
             Element::Timestamp(Timestamp::Active { start, .. }) => {
-                let date = Date::from_calendar_date(
+                let date = NaiveDate::from_ymd_opt(
                     start.year.into(),
-                    start.month.try_into()?,
-                    start.day,
-                )?;
+                    start.month.into(),
+                    start.day.into(),
+                )
+                .context("Invalid date")?;
 
-                date.format_into(
-                    &mut w,
-                    format_description!("[day] [month repr:short] [year]"),
-                )?;
+                write!(w, "{}", date.format("%d %b %Y"))?;
             }
             Element::Link(Link { path, desc }) if desc.is_none() => {
                 write!(w, r#"<a href="/img/{path}"><img src="/thumb/{path}">"#)?;
diff --git a/src/post.rs b/src/post.rs
index 79eca9e..7800121 100644
--- a/src/post.rs
+++ b/src/post.rs
@@ -1,8 +1,8 @@
 use std::{cmp::Ordering, fs, path::Path};
 
 use anyhow::{bail, Context};
+use chrono::NaiveDate;
 use orgize::{elements::Keyword, export::HtmlHandler, Element};
-use time::{macros::format_description, Date};
 
 use crate::{
     date::parse_org_date,
@@ -14,7 +14,7 @@ use crate::{
 #[derive(Eq)]
 pub struct Post {
     pub title: String,
-    pub date: Option<Date>,
+    pub date: Option<NaiveDate>,
     pub index: u32,
     pub content: String,
     pub url: String,
@@ -64,9 +64,10 @@ impl Post {
 
     fn date_f(&self) -> anyhow::Result<Option<String>> {
         let date_f = match self.date {
-            Some(date) => date.format(format_description!("[day] [month repr:short] [year]"))?,
+            Some(date) => date.format("%d %b %Y"),
             None => return Ok(None),
-        };
+        }
+        .to_string();
 
         Ok(Some(date_f))
     }
@@ -115,7 +116,7 @@ impl PartialEq for Post {
 
 struct PostParser {
     title: Option<String>,
-    date: Option<Date>,
+    date: Option<NaiveDate>,
     index: Option<u32>,
     url: String,
     content: Vec<u8>,
diff --git a/src/resp.rs b/src/resp.rs
index 0095741..b3f45a3 100644
--- a/src/resp.rs
+++ b/src/resp.rs
@@ -1,6 +1,26 @@
+use chrono::{Days, Utc};
+
+#[derive(Copy, Clone)]
+pub enum Expiry {
+    Instant,
+    Year,
+}
+
+impl Expiry {
+    pub fn header_value(self) -> Option<String> {
+        let date = match self {
+            Expiry::Instant => return None,
+            Expiry::Year => Utc::now() + Days::new(365),
+        };
+
+        Some(date.format("%a, %d %b %Y %T GMT").to_string())
+    }
+}
+
 #[derive(Clone)]
 pub struct Response {
     pub content_type: &'static str,
+    pub expiry: Expiry,
     pub data: Vec<u8>,
 }
 
@@ -8,6 +28,7 @@ impl Response {
     pub fn html(html: String) -> Self {
         Self {
             content_type: "text/html; charset=utf-8",
+            expiry: Expiry::Instant,
             data: html.into_bytes(),
         }
     }
@@ -15,6 +36,7 @@ impl Response {
     pub fn css(css: String) -> Self {
         Self {
             content_type: "text/css; charset=utf-8",
+            expiry: Expiry::Year,
             data: css.into_bytes(),
         }
     }
@@ -22,6 +44,7 @@ impl Response {
     pub fn png(data: Vec<u8>) -> Self {
         Self {
             content_type: "image/png",
+            expiry: Expiry::Year,
             data,
         }
     }
@@ -29,6 +52,7 @@ impl Response {
     pub fn ico(data: Vec<u8>) -> Self {
         Self {
             content_type: "image/x-icon",
+            expiry: Expiry::Year,
             data,
         }
     }
-- 
GitLab