diff --git a/Makefile-libostree.am b/Makefile-libostree.am index 96b9249b97..eeb0b6c60f 100644 --- a/Makefile-libostree.am +++ b/Makefile-libostree.am @@ -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 \ diff --git a/Makefile-tests.am b/Makefile-tests.am index 570f83890d..257b4a5d87 100644 --- a/Makefile-tests.am +++ b/Makefile-tests.am @@ -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 index 0000000000..f9b8b3e086 --- /dev/null +++ b/src/libostree/ostree-date-utils-private.h @@ -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 + */ + +#pragma once + +#ifndef __GI_SCANNER__ + +#include + +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 index 0000000000..8076e084cb --- /dev/null +++ b/src/libostree/ostree-date-utils.c @@ -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 + */ + +#include "config.h" + +#include +#include +#include + +#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: + * , :: 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); +} diff --git a/src/libostree/ostree-fetcher-curl.c b/src/libostree/ostree-fetcher-curl.c index 975508ebff..129e6988e6 100644 --- a/src/libostree/ostree-fetcher-curl.c +++ b/src/libostree/ostree-fetcher-curl.c @@ -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: - * , :: 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; } diff --git a/tests/.gitignore b/tests/.gitignore index f5e95e49f6..938c169f4b 100644 --- a/tests/.gitignore +++ b/tests/.gitignore @@ -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 index 0000000000..d3f2073ec6 --- /dev/null +++ b/tests/test-rfc2616-dates.c @@ -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 + */ + +#include "config.h" + +#include + +#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 (); +}