ostree/trivial-httpd: Add Last-Modified/ETag support
authorPhilip Withnall <pwithnall@endlessos.org>
Fri, 9 Oct 2020 17:46:06 +0000 (18:46 +0100)
committerPhilip Withnall <pwithnall@endlessos.org>
Thu, 22 Oct 2020 20:03:34 +0000 (21:03 +0100)
This is basic support for the
Last-Modified/ETag/If-Modified-Since/If-None-Match headers. It’s not
high performance, and doesn’t support all of the related caching
features (like the If-Match header, etc.).

Signed-off-by: Philip Withnall <pwithnall@endlessos.org>
src/ostree/ostree-trivial-httpd.c

index a38abbea6110498e0c98d0e51c7c74d2e1b55000..d8bc0556811e94e727e195c6b29d00e1000b196b 100644 (file)
@@ -206,6 +206,15 @@ close_socket (SoupMessage *msg, gpointer user_data)
 #endif
 }
 
+/* Returns the ETag including the surrounding quotes */
+static gchar *
+calculate_etag (GMappedFile *mapping)
+{
+  g_autoptr(GBytes) bytes = g_mapped_file_get_bytes (mapping);
+  g_autofree gchar *checksum = g_compute_checksum_for_bytes (G_CHECKSUM_SHA256, bytes);
+  return g_strconcat ("\"", checksum, "\"", NULL);
+}
+
 static void
 do_get (OtTrivialHttpd    *self,
         SoupServer        *server,
@@ -367,31 +376,41 @@ do_get (OtTrivialHttpd    *self,
           soup_message_set_status (msg, SOUP_STATUS_FORBIDDEN);
           goto out;
         }
+
+      glnx_autofd int fd = openat (self->root_dfd, path, O_RDONLY | O_CLOEXEC);
+      if (fd < 0)
+        {
+          soup_message_set_status (msg, SOUP_STATUS_INTERNAL_SERVER_ERROR);
+          goto out;
+        }
+
+      g_autoptr(GMappedFile) mapping = g_mapped_file_new_from_fd (fd, FALSE, NULL);
+      if (!mapping)
+        {
+          soup_message_set_status (msg, SOUP_STATUS_INTERNAL_SERVER_ERROR);
+          goto out;
+        }
+      (void) close (fd); fd = -1;
+
+      /* Send caching headers */
+      g_autoptr(GDateTime) last_modified = g_date_time_new_from_unix_utc (stbuf.st_mtim.tv_sec);
+      if (last_modified != NULL)
+        {
+          g_autofree gchar *formatted = g_date_time_format (last_modified, "%a, %d %b %Y %H:%M:%S GMT");
+          soup_message_headers_append (msg->response_headers, "Last-Modified", formatted);
+        }
+
+      g_autofree gchar *etag = calculate_etag (mapping);
+      if (etag != NULL)
+        soup_message_headers_append (msg->response_headers, "ETag", etag);
       
       if (msg->method == SOUP_METHOD_GET)
         {
-          glnx_autofd int fd = -1;
-          g_autoptr(GMappedFile) mapping = NULL;
           gsize buffer_length, file_size;
           SoupRange *ranges;
           int ranges_length;
           gboolean have_ranges;
 
-          fd = openat (self->root_dfd, path, O_RDONLY | O_CLOEXEC);
-          if (fd < 0)
-            {
-              soup_message_set_status (msg, SOUP_STATUS_INTERNAL_SERVER_ERROR);
-              goto out;
-            }
-
-          mapping = g_mapped_file_new_from_fd (fd, FALSE, NULL);
-          if (!mapping)
-            {
-              soup_message_set_status (msg, SOUP_STATUS_INTERNAL_SERVER_ERROR);
-              goto out;
-            }
-          (void) close (fd); fd = -1;
-
           file_size = g_mapped_file_get_length (mapping);
           have_ranges = soup_message_headers_get_ranges(msg->request_headers, file_size, &ranges, &ranges_length);
           if (opt_force_ranges && !have_ranges && g_strrstr (path, "/objects") != NULL)
@@ -447,7 +466,50 @@ do_get (OtTrivialHttpd    *self,
           soup_message_headers_append (msg->response_headers,
                                        "Content-Length", length);
         }
-      soup_message_set_status (msg, SOUP_STATUS_OK);
+
+      /* Check client’s caching headers. */
+      const gchar *if_modified_since = soup_message_headers_get_one (msg->request_headers,
+                                                                     "If-Modified-Since");
+      const gchar *if_none_match = soup_message_headers_get_one (msg->request_headers,
+                                                                 "If-None-Match");
+
+      if (if_none_match != NULL && etag != NULL)
+        {
+          if (g_strcmp0 (etag, if_none_match) == 0)
+            {
+              soup_message_set_status (msg, SOUP_STATUS_NOT_MODIFIED);
+              soup_message_body_truncate (msg->response_body);
+            }
+          else
+            {
+              soup_message_set_status (msg, SOUP_STATUS_OK);
+            }
+        }
+      else if (if_modified_since != NULL && last_modified != NULL)
+        {
+          SoupDate *if_modified_since_sd = soup_date_new_from_string (if_modified_since);
+          g_autoptr(GDateTime) if_modified_since_dt = NULL;
+
+          if (if_modified_since_sd != NULL)
+            if_modified_since_dt = g_date_time_new_from_unix_utc (soup_date_to_time_t (if_modified_since_sd));
+
+          if (if_modified_since_dt != NULL &&
+              g_date_time_compare (last_modified, if_modified_since_dt) <= 0)
+            {
+              soup_message_set_status (msg, SOUP_STATUS_NOT_MODIFIED);
+              soup_message_body_truncate (msg->response_body);
+            }
+          else
+            {
+              soup_message_set_status (msg, SOUP_STATUS_OK);
+            }
+
+          g_clear_pointer (&if_modified_since_sd, soup_date_free);
+        }
+      else
+        {
+          soup_message_set_status (msg, SOUP_STATUS_OK);
+        }
     }
  out:
   {