tests: Split RFC 2616 date parsing code out and add tests
authorPhilip Withnall <pwithnall@endlessos.org>
Fri, 16 Oct 2020 16:05:54 +0000 (17:05 +0100)
committerPhilip Withnall <pwithnall@endlessos.org>
Thu, 22 Oct 2020 20:03:34 +0000 (21:03 +0100)
This makes it testable, and increases its test coverage too 100% of
lines, as measured by `make coverage`.

Signed-off-by: Philip Withnall <pwithnall@endlessos.org>
Makefile-libostree.am
Makefile-tests.am
src/libostree/ostree-date-utils-private.h [new file with mode: 0644]
src/libostree/ostree-date-utils.c [new file with mode: 0644]
src/libostree/ostree-fetcher-curl.c
tests/.gitignore
tests/test-rfc2616-dates.c [new file with mode: 0644]

index 96b9249b97f4a29112e46107b53900255363440d..eeb0b6c60f156342a02ecd79eed2e0be7c953c4a 100644 (file)
@@ -68,6 +68,8 @@ libostree_1_la_SOURCES = \
        src/libostree/ostree-cmdprivate.c \
        src/libostree/ostree-core-private.h \
        src/libostree/ostree-core.c \
+       src/libostree/ostree-date-utils.c \
+       src/libostree/ostree-date-utils-private.h \
        src/libostree/ostree-dummy-enumtypes.c \
        src/libostree/ostree-checksum-input-stream.c \
        src/libostree/ostree-checksum-input-stream.h \
index 570f83890d957a24a1380e68d2093cfac123163f..257b4a5d87d4ada7e3d00cdbf49eaef04a7d7df3 100644 (file)
@@ -273,7 +273,8 @@ endif
 _installed_or_uninstalled_test_programs = tests/test-varint tests/test-ot-unix-utils tests/test-bsdiff tests/test-mutable-tree \
        tests/test-keyfile-utils tests/test-ot-opt-utils tests/test-ot-tool-util \
        tests/test-checksum tests/test-lzma tests/test-rollsum \
-       tests/test-basic-c tests/test-sysroot-c tests/test-pull-c tests/test-repo tests/test-include-ostree-h tests/test-kargs
+       tests/test-basic-c tests/test-sysroot-c tests/test-pull-c tests/test-repo tests/test-include-ostree-h tests/test-kargs \
+       tests/test-rfc2616-dates
 
 if USE_GPGME
 _installed_or_uninstalled_test_programs += \
@@ -390,6 +391,12 @@ tests_test_lzma_SOURCES = src/libostree/ostree-lzma-common.c src/libostree/ostre
 tests_test_lzma_CFLAGS = $(TESTS_CFLAGS) $(OT_DEP_LZMA_CFLAGS)
 tests_test_lzma_LDADD = $(TESTS_LDADD) $(OT_DEP_LZMA_LIBS)
 
+tests_test_rfc2616_dates_SOURCES = \
+       src/libostree/ostree-date-utils.c \
+       tests/test-rfc2616-dates.c
+tests_test_rfc2616_dates_CFLAGS = $(TESTS_CFLAGS)
+tests_test_rfc2616_dates_LDADD = $(TESTS_LDADD)
+
 if USE_GPGME
 tests_test_gpg_verify_result_SOURCES = \
        src/libostree/ostree-gpg-verify-result-private.h \
diff --git a/src/libostree/ostree-date-utils-private.h b/src/libostree/ostree-date-utils-private.h
new file mode 100644 (file)
index 0000000..f9b8b3e
--- /dev/null
@@ -0,0 +1,38 @@
+/*
+ * Copyright © 2020 Endless OS Foundation LLC
+ *
+ * SPDX-License-Identifier: LGPL-2.0+
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ *
+ * Authors:
+ *  - Philip Withnall <pwithnall@endlessos.org>
+ */
+
+#pragma once
+
+#ifndef __GI_SCANNER__
+
+#include <glib.h>
+
+G_BEGIN_DECLS
+
+GDateTime *_ostree_parse_rfc2616_date_time (const char *buf,
+                                            size_t      len);
+
+G_END_DECLS
+
+#endif
diff --git a/src/libostree/ostree-date-utils.c b/src/libostree/ostree-date-utils.c
new file mode 100644 (file)
index 0000000..8076e08
--- /dev/null
@@ -0,0 +1,166 @@
+/*
+ * Copyright © 2020 Endless OS Foundation LLC
+ *
+ * SPDX-License-Identifier: LGPL-2.0+
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ *
+ * Authors:
+ *  - Philip Withnall <pwithnall@endlessos.org>
+ */
+
+#include "config.h"
+
+#include <errno.h>
+#include <glib.h>
+#include <string.h>
+
+#include "ostree-date-utils-private.h"
+
+/* @buf must already be known to be long enough */
+static gboolean
+parse_uint (const char *buf,
+            guint       n_digits,
+            guint       min,
+            guint       max,
+            guint      *out)
+{
+  guint64 number;
+  const char *end_ptr = NULL;
+  gint saved_errno = 0;
+
+  g_return_val_if_fail (n_digits == 2 || n_digits == 4, FALSE);
+  g_return_val_if_fail (out != NULL, FALSE);
+
+  errno = 0;
+  number = g_ascii_strtoull (buf, (gchar **)&end_ptr, 10);
+  saved_errno = errno;
+
+  if (!g_ascii_isdigit (buf[0]) ||
+      saved_errno != 0 ||
+      end_ptr == NULL ||
+      end_ptr != buf + n_digits ||
+      number < min ||
+      number > max)
+    return FALSE;
+
+  *out = number;
+  return TRUE;
+}
+
+/* Locale-independent parsing for RFC 2616 date/times.
+ *
+ * Reference: https://tools.ietf.org/html/rfc2616#section-3.3.1
+ *
+ * Syntax:
+ *    <day-name>, <day> <month> <year> <hour>:<minute>:<second> GMT
+ *
+ * Note that this only accepts the full-year and GMT formats specified by
+ * RFC 1123. It doesn’t accept RFC 850 or asctime formats.
+ *
+ * Example:
+ *    Wed, 21 Oct 2015 07:28:00 GMT
+ */
+GDateTime *
+_ostree_parse_rfc2616_date_time (const char *buf,
+                                 size_t      len)
+{
+  guint day_int, year_int, hour_int, minute_int, second_int;
+  const char *day_names[] =
+    {
+      "Mon",
+      "Tue",
+      "Wed",
+      "Thu",
+      "Fri",
+      "Sat",
+      "Sun",
+    };
+  size_t day_name_index;
+  const char *month_names[] =
+    {
+      "Jan",
+      "Feb",
+      "Mar",
+      "Apr",
+      "May",
+      "Jun",
+      "Jul",
+      "Aug",
+      "Sep",
+      "Oct",
+      "Nov",
+      "Dec",
+    };
+  size_t month_name_index;
+
+  if (len != 29)
+    return NULL;
+
+  const char *day_name = buf;
+  const char *day = buf + 5;
+  const char *month_name = day + 3;
+  const char *year = month_name + 4;
+  const char *hour = year + 5;
+  const char *minute = hour + 3;
+  const char *second = minute + 3;
+  const char *tz = second + 3;
+
+  for (day_name_index = 0; day_name_index < G_N_ELEMENTS (day_names); day_name_index++)
+    {
+      if (strncmp (day_names[day_name_index], day_name, 3) == 0)
+        break;
+    }
+  if (day_name_index >= G_N_ELEMENTS (day_names))
+    return NULL;
+  /* don’t validate whether the day_name matches the rest of the date */
+  if (*(day_name + 3) != ',' || *(day_name + 4) != ' ')
+    return NULL;
+  if (!parse_uint (day, 2, 1, 31, &day_int))
+    return NULL;
+  if (*(day + 2) != ' ')
+    return NULL;
+  for (month_name_index = 0; month_name_index < G_N_ELEMENTS (month_names); month_name_index++)
+    {
+      if (strncmp (month_names[month_name_index], month_name, 3) == 0)
+        break;
+    }
+  if (month_name_index >= G_N_ELEMENTS (month_names))
+    return NULL;
+  if (*(month_name + 3) != ' ')
+    return NULL;
+  if (!parse_uint (year, 4, 0, 9999, &year_int))
+    return NULL;
+  if (*(year + 4) != ' ')
+    return NULL;
+  if (!parse_uint (hour, 2, 0, 23, &hour_int))
+    return NULL;
+  if (*(hour + 2) != ':')
+    return NULL;
+  if (!parse_uint (minute, 2, 0, 59, &minute_int))
+    return NULL;
+  if (*(minute + 2) != ':')
+    return NULL;
+  if (!parse_uint (second, 2, 0, 60, &second_int))  /* allow leap seconds */
+    return NULL;
+  if (*(second + 2) != ' ')
+    return NULL;
+  if (strncmp (tz, "GMT", 3) != 0)
+    return NULL;
+
+  return g_date_time_new_utc (year_int, month_name_index + 1, day_int,
+                              hour_int, minute_int, second_int);
+}
index 975508ebff4b2c21de2857032ad9c321cba15388..129e6988e6a8263f52b087b5a4fe8d5948f54161 100644 (file)
@@ -45,6 +45,7 @@
 #define CURLPIPE_MULTIPLEX 0
 #endif
 
+#include "ostree-date-utils-private.h"
 #include "ostree-fetcher.h"
 #include "ostree-fetcher-util.h"
 #include "ostree-enumtypes.h"
@@ -591,141 +592,6 @@ write_cb (void *ptr, size_t size, size_t nmemb, void *data)
   return realsize;
 }
 
-/* @buf must already be known to be long enough */
-static gboolean
-parse_uint (const char *buf,
-            guint       n_digits,
-            guint       min,
-            guint       max,
-            guint      *out)
-{
-  guint64 number;
-  const char *end_ptr = NULL;
-  gint saved_errno = 0;
-
-  g_return_val_if_fail (n_digits == 2 || n_digits == 4, FALSE);
-  g_return_val_if_fail (out != NULL, FALSE);
-
-  errno = 0;
-  number = g_ascii_strtoull (buf, (gchar **)&end_ptr, 10);
-  saved_errno = errno;
-
-  if (!g_ascii_isdigit (buf[0]) ||
-      saved_errno != 0 ||
-      end_ptr == NULL ||
-      end_ptr != buf + n_digits ||
-      number < min ||
-      number > max)
-    return FALSE;
-
-  *out = number;
-  return TRUE;
-}
-
-/* Locale-independent parsing for RFC 2616 date/times.
- *
- * Reference: https://tools.ietf.org/html/rfc2616#section-3.3.1
- *
- * Syntax:
- *    <day-name>, <day> <month> <year> <hour>:<minute>:<second> GMT
- *
- * Note that this only accepts the full-year and GMT formats specified by
- * RFC 1123. It doesn’t accept RFC 850 or asctime formats.
- *
- * Example:
- *    Wed, 21 Oct 2015 07:28:00 GMT
- */
-static GDateTime *
-parse_rfc2616_date_time (const char *buf,
-                         size_t      len)
-{
-  guint day_int, year_int, hour_int, minute_int, second_int;
-  const char *day_names[] =
-    {
-      "Mon",
-      "Tue",
-      "Wed",
-      "Thu",
-      "Fri",
-      "Sat",
-      "Sun",
-    };
-  size_t day_name_index;
-  const char *month_names[] =
-    {
-      "Jan",
-      "Feb",
-      "Mar",
-      "Apr",
-      "May",
-      "Jun",
-      "Jul",
-      "Aug",
-      "Sep",
-      "Oct",
-      "Nov",
-      "Dec",
-    };
-  size_t month_name_index;
-
-  if (len != 29)
-    return NULL;
-
-  const char *day_name = buf;
-  const char *day = buf + 5;
-  const char *month_name = day + 3;
-  const char *year = month_name + 4;
-  const char *hour = year + 5;
-  const char *minute = hour + 3;
-  const char *second = minute + 3;
-  const char *tz = second + 3;
-
-  for (day_name_index = 0; day_name_index < G_N_ELEMENTS (day_names); day_name_index++)
-    {
-      if (strncmp (day_names[day_name_index], day_name, 3) == 0)
-        break;
-    }
-  if (day_name_index >= G_N_ELEMENTS (day_names))
-    return NULL;
-  /* don’t validate whether the day_name matches the rest of the date */
-  if (*(day_name + 3) != ',' || *(day_name + 4) != ' ')
-    return NULL;
-  if (!parse_uint (day, 2, 1, 31, &day_int))
-    return NULL;
-  if (*(day + 2) != ' ')
-    return NULL;
-  for (month_name_index = 0; month_name_index < G_N_ELEMENTS (month_names); month_name_index++)
-    {
-      if (strncmp (month_names[month_name_index], month_name, 3) == 0)
-        break;
-    }
-  if (month_name_index >= G_N_ELEMENTS (month_names))
-    return NULL;
-  if (*(month_name + 3) != ' ')
-    return NULL;
-  if (!parse_uint (year, 4, 0, 9999, &year_int))
-    return NULL;
-  if (*(year + 4) != ' ')
-    return NULL;
-  if (!parse_uint (hour, 2, 0, 23, &hour_int))
-    return NULL;
-  if (*(hour + 2) != ':')
-    return NULL;
-  if (!parse_uint (minute, 2, 0, 59, &minute_int))
-    return NULL;
-  if (*(minute + 2) != ':')
-    return NULL;
-  if (!parse_uint (second, 2, 0, 60, &second_int))  /* allow leap seconds */
-    return NULL;
-  if (*(second + 2) != ' ')
-    return NULL;
-  if (strncmp (tz, "GMT", 3) != 0)
-    return NULL;
-
-  return g_date_time_new_utc (year_int, month_name_index + 1, day_int,
-                              hour_int, minute_int, second_int);
-}
-
 /* CURLOPT_HEADERFUNCTION */
 static size_t
 response_header_cb (const char *buffer, size_t size, size_t n_items, void *user_data)
@@ -753,7 +619,7 @@ response_header_cb (const char *buffer, size_t size, size_t n_items, void *user_
            strncasecmp (buffer, last_modified_header, strlen (last_modified_header)) == 0)
     {
       g_autofree char *lm_buf = g_strstrip (g_strdup (buffer + strlen (last_modified_header)));
-      g_autoptr(GDateTime) dt = parse_rfc2616_date_time (lm_buf, strlen (lm_buf));
+      g_autoptr(GDateTime) dt = _ostree_parse_rfc2616_date_time (lm_buf, strlen (lm_buf));
       req->out_last_modified = (dt != NULL) ? g_date_time_to_unix (dt) : 0;
     }
 
index f5e95e49f60a7b9a9ff403be711c9bbb29c84a7e..938c169f4b48fb5b069af6de4872687d1d19f342 100644 (file)
@@ -21,5 +21,6 @@ test-repo
 test-repo-finder-avahi
 test-repo-finder-config
 test-repo-finder-mount
+test-rfc2616-dates
 test-rollsum-cli
 test-kargs
diff --git a/tests/test-rfc2616-dates.c b/tests/test-rfc2616-dates.c
new file mode 100644 (file)
index 0000000..d3f2073
--- /dev/null
@@ -0,0 +1,123 @@
+/*
+ * Copyright © 2020 Endless OS Foundation LLC
+ *
+ * SPDX-License-Identifier: LGPL-2.0+
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ *
+ * Authors:
+ *  - Philip Withnall <pwithnall@endlessos.org>
+ */
+
+#include "config.h"
+
+#include <glib.h>
+
+#include "ostree-date-utils-private.h"
+
+static void
+test_ostree_parse_rfc2616_date_time (void)
+{
+#if GLIB_CHECK_VERSION(2, 62, 0)
+G_GNUC_BEGIN_IGNORE_DEPRECATIONS
+  const struct
+    {
+      const char *rfc2616;
+      const char *expected_iso8601;  /* (nullable) if parsing is expected to fail */
+    }
+  tests[] =
+    {
+      { "Wed, 21 Oct 2015 07:28:00 GMT", "2015-10-21T07:28:00Z" },
+      { "Wed, 21 Oct 2015 07:28:00", NULL },  /* too short */
+      { "Wed, 21 Oct 2015 07:28:00 CEST", NULL },  /* too long; not GMT */
+      { "Cat, 21 Oct 2015 07:28:00 GMT", NULL },  /* invalid day */
+      { "Wed  21 Oct 2015 07:28:00 GMT", NULL },  /* no comma */
+      { "Wed,21 Oct 2015 07:28:00 GMT ", NULL },  /* missing space */
+      { "Wed, xx Oct 2015 07:28:00 GMT", NULL },  /* no day-of-month */
+      { "Wed, 011Oct 2015 07:28:00 GMT", NULL },  /* overlong day-of-month */
+      { "Wed, 00 Oct 2015 07:28:00 GMT", NULL },  /* day-of-month underflow */
+      { "Wed, 32 Oct 2015 07:28:00 GMT", NULL },  /* day-of-month overflow */
+      { "Wed, 21,Oct 2015 07:28:00 GMT", NULL },  /* missing space */
+      { "Wed, 21 Cat 2015 07:28:00 GMT", NULL },  /* invalid month */
+      { "Wed, 21 Oct,2015 07:28:00 GMT", NULL },  /* missing space */
+      { "Wed, 21 Oct xxxx 07:28:00 GMT", NULL },  /* no year */
+      { "Wed, 21 Oct 0201507:28:00 GMT", NULL },  /* overlong year */
+      { "Wed, 21 Oct 0000 07:28:00 GMT", NULL },  /* year underflow */
+      { "Wed, 21 Oct 10000 07:28:00 GM", NULL },  /* year overflow */
+      { "Wed, 21 Oct 2015,07:28:00 GMT", NULL },  /* missing space */
+      { "Wed, 21 Oct 2015 07 28:00 GMT", NULL },  /* missing colon */
+      { "Wed, 21 Oct 2015 007:28:00 GM", NULL },  /* overlong hour */
+      { "Wed, 21 Oct 2015 xx:28:00 GMT", NULL },  /* missing hour */
+      { "Wed, 21 Oct 2015 -1:28:00 GMT", NULL },  /* hour underflow */
+      { "Wed, 21 Oct 2015 24:28:00 GMT", NULL },  /* hour overflow */
+      { "Wed, 21 Oct 2015 07:28 00 GMT", NULL },  /* missing colon */
+      { "Wed, 21 Oct 2015 07:028:00 GM", NULL },  /* overlong minute */
+      { "Wed, 21 Oct 2015 07:xx:00 GMT", NULL },  /* missing minute */
+      { "Wed, 21 Oct 2015 07:-1:00 GMT", NULL },  /* minute underflow */
+      { "Wed, 21 Oct 2015 07:60:00 GMT", NULL },  /* minute overflow */
+      { "Wed, 21 Oct 2015 07:28:00CEST", NULL },  /* missing space */
+      { "Wed, 21 Oct 2015 07:28:000 GM", NULL },  /* overlong second */
+      { "Wed, 21 Oct 2015 07:28:xx GMT", NULL },  /* missing second */
+      { "Wed, 21 Oct 2015 07:28:-1 GMT", NULL },  /* seconds underflow */
+      { "Wed, 21 Oct 2015 07:28:61 GMT", NULL },  /* seconds overflow */
+      { "Wed, 21 Oct 2015 07:28:00 UTC", NULL },  /* invalid timezone (only GMT is allowed) */
+      { "Thu, 01 Jan 1970 00:00:00 GMT", "1970-01-01T00:00:00Z" },  /* extreme but valid date */
+      { "Mon, 31 Dec 9999 23:59:59 GMT", "9999-12-31T23:59:59Z" },  /* extreme but valid date */
+    };
+
+  for (gsize i = 0; i < G_N_ELEMENTS (tests); i++)
+    {
+      g_test_message ("Test %" G_GSIZE_FORMAT ": %s", i, tests[i].rfc2616);
+
+      /* Parse once with a trailing nul */
+      g_autoptr(GDateTime) dt1 = _ostree_parse_rfc2616_date_time (tests[i].rfc2616, strlen (tests[i].rfc2616));
+      if (tests[i].expected_iso8601 == NULL)
+        g_assert_null (dt1);
+      else
+        {
+          g_assert_nonnull (dt1);
+          g_autofree char *iso8601 = g_date_time_format_iso8601 (dt1);
+          g_assert_cmpstr (iso8601, ==, tests[i].expected_iso8601);
+        }
+
+      /* And parse again with no trailing nul */
+      g_autofree char *rfc2616_no_nul = g_malloc (strlen (tests[i].rfc2616));
+      memcpy (rfc2616_no_nul, tests[i].rfc2616, strlen (tests[i].rfc2616));
+      g_autoptr(GDateTime) dt2 = _ostree_parse_rfc2616_date_time (rfc2616_no_nul, strlen (tests[i].rfc2616));
+      if (tests[i].expected_iso8601 == NULL)
+        g_assert_null (dt2);
+      else
+        {
+          g_assert_nonnull (dt2);
+          g_autofree char *iso8601 = g_date_time_format_iso8601 (dt2);
+          g_assert_cmpstr (iso8601, ==, tests[i].expected_iso8601);
+        }
+    }
+G_GNUC_END_IGNORE_DEPRECATIONS
+#else
+  /* GLib 2.62 is needed for g_date_time_format_iso8601(). */
+  g_test_skip ("RFC 2616 date parsing test needs GLib ≥ 2.62.0");
+#endif
+}
+
+int
+main (int    argc,
+      char **argv)
+{
+  g_test_init (&argc, &argv, NULL);
+  g_test_add_func ("/ostree_parse_rfc2616_date_time", test_ostree_parse_rfc2616_date_time);
+  return g_test_run ();
+}