932 lines
28 KiB
Diff
932 lines
28 KiB
Diff
From 52825c2eb7043698d7a0668cfe6bb5a23da87cf5 Mon Sep 17 00:00:00 2001
|
|
From: Milan Crha <mcrha@redhat.com>
|
|
Date: Thu, 15 May 2025 07:59:14 +0200
|
|
Subject: [PATCH 1/2] soup-date-utils: Add value checks for date/time parsing
|
|
|
|
Reject date/time when it does not represent a valid value.
|
|
|
|
Closes https://gitlab.gnome.org/GNOME/libsoup/-/issues/448
|
|
---
|
|
libsoup/soup-date.c | 23 ++-
|
|
tests/cookies-test.c | 10 +
|
|
tests/date-test.c | 434 +++++++++++++++++++++++++++++++++++++++++++
|
|
3 files changed, 459 insertions(+), 8 deletions(-)
|
|
create mode 100644 tests/date-test.c
|
|
|
|
diff --git a/libsoup/soup-date.c b/libsoup/soup-date.c
|
|
index dabae9d4..8b2beea4 100644
|
|
--- a/libsoup/soup-date.c
|
|
+++ b/libsoup/soup-date.c
|
|
@@ -284,7 +284,7 @@ parse_day (SoupDate *date, const char **date_string)
|
|
while (*end == ' ' || *end == '-')
|
|
end++;
|
|
*date_string = end;
|
|
- return TRUE;
|
|
+ return date->day >= 1 && date->day <= 31;
|
|
}
|
|
|
|
static inline gboolean
|
|
@@ -324,7 +324,7 @@ parse_year (SoupDate *date, const char **date_string)
|
|
while (*end == ' ' || *end == '-')
|
|
end++;
|
|
*date_string = end;
|
|
- return TRUE;
|
|
+ return date->year > 0 && date->year < 9999;
|
|
}
|
|
|
|
static inline gboolean
|
|
@@ -348,7 +348,7 @@ parse_time (SoupDate *date, const char **date_string)
|
|
while (*p == ' ')
|
|
p++;
|
|
*date_string = p;
|
|
- return TRUE;
|
|
+ return date->hour >= 0 && date->hour < 24 && date->minute >= 0 && date->minute < 60 && date->second >= 0 && date->second < 60;
|
|
}
|
|
|
|
static inline gboolean
|
|
@@ -361,9 +361,14 @@ parse_timezone (SoupDate *date, const char **date_string)
|
|
gulong val;
|
|
int sign = (**date_string == '+') ? -1 : 1;
|
|
val = strtoul (*date_string + 1, (char **)date_string, 10);
|
|
- if (**date_string == ':')
|
|
- val = 60 * val + strtoul (*date_string + 1, (char **)date_string, 10);
|
|
- else
|
|
+ if (val > 9999)
|
|
+ return FALSE;
|
|
+ if (**date_string == ':') {
|
|
+ gulong val2 = strtoul (*date_string + 1, (char **)date_string, 10);
|
|
+ if (val > 99 || val2 > 99)
|
|
+ return FALSE;
|
|
+ val = 60 * val + val2;
|
|
+ } else
|
|
val = 60 * (val / 100) + (val % 100);
|
|
date->offset = sign * val;
|
|
date->utc = (sign == -1) && !val;
|
|
@@ -407,7 +412,8 @@ parse_textual_date (SoupDate *date, const char *date_string)
|
|
if (!parse_month (date, &date_string) ||
|
|
!parse_day (date, &date_string) ||
|
|
!parse_time (date, &date_string) ||
|
|
- !parse_year (date, &date_string))
|
|
+ !parse_year (date, &date_string) ||
|
|
+ !g_date_valid_dmy (date->day, date->month, date->year))
|
|
return FALSE;
|
|
|
|
/* There shouldn't be a timezone, but check anyway */
|
|
@@ -419,7 +425,8 @@ parse_textual_date (SoupDate *date, const char *date_string)
|
|
if (!parse_day (date, &date_string) ||
|
|
!parse_month (date, &date_string) ||
|
|
!parse_year (date, &date_string) ||
|
|
- !parse_time (date, &date_string))
|
|
+ !parse_time (date, &date_string) ||
|
|
+ !g_date_valid_dmy (date->day, date->month, date->year))
|
|
return FALSE;
|
|
|
|
/* This time there *should* be a timezone, but we
|
|
diff --git a/tests/cookies-test.c b/tests/cookies-test.c
|
|
index 17b77f29..61adfac5 100644
|
|
--- a/tests/cookies-test.c
|
|
+++ b/tests/cookies-test.c
|
|
@@ -288,6 +288,15 @@ do_cookies_parsing_nopath_nullorigin (void)
|
|
soup_cookie_free (cookie);
|
|
}
|
|
|
|
+static void
|
|
+do_cookies_parsing_int32_overflow (void)
|
|
+{
|
|
+ SoupCookie *cookie = soup_cookie_parse ("Age=1;expires=3Mar9 999:9:9+ 999999999-age=main=gne=", NULL);
|
|
+ g_assert_nonnull (cookie);
|
|
+ g_assert_null (soup_cookie_get_expires (cookie));
|
|
+ soup_cookie_free (cookie);
|
|
+}
|
|
+
|
|
int
|
|
main (int argc, char **argv)
|
|
{
|
|
@@ -309,6 +318,7 @@ main (int argc, char **argv)
|
|
g_test_add_func ("/cookies/accept-policy-subdomains", do_cookies_subdomain_policy_test);
|
|
g_test_add_func ("/cookies/parsing", do_cookies_parsing_test);
|
|
g_test_add_func ("/cookies/parsing/no-path-null-origin", do_cookies_parsing_nopath_nullorigin);
|
|
+ g_test_add_func ("/cookies/parsing/int32-overflow", do_cookies_parsing_int32_overflow);
|
|
|
|
ret = g_test_run ();
|
|
|
|
diff --git a/tests/date-test.c b/tests/date-test.c
|
|
new file mode 100644
|
|
index 00000000..38f27335
|
|
--- /dev/null
|
|
+++ b/tests/date-test.c
|
|
@@ -0,0 +1,434 @@
|
|
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
|
|
+/*
|
|
+ * Copyright (C) 2005 Novell, Inc.
|
|
+ */
|
|
+
|
|
+#include "test-utils.h"
|
|
+
|
|
+static void check_ok (gconstpointer data);
|
|
+
|
|
+static SoupDate *
|
|
+make_date (const char *strdate)
|
|
+{
|
|
+ char *dup;
|
|
+ SoupDate *date;
|
|
+
|
|
+ /* We do it this way so that if soup_date_new_from_string()
|
|
+ * reads off the end of the string, it will trigger an error
|
|
+ * when valgrinding, rather than just reading the start of the
|
|
+ * next const string.
|
|
+ */
|
|
+ dup = g_strdup (strdate);
|
|
+ date = soup_date_new_from_string (dup);
|
|
+ g_free (dup);
|
|
+ return date;
|
|
+}
|
|
+
|
|
+static SoupDate *
|
|
+check_correct_date (const char *strdate)
|
|
+{
|
|
+ SoupDate *date;
|
|
+
|
|
+ date = make_date (strdate);
|
|
+ if (!date) {
|
|
+ g_assert_nonnull (date);
|
|
+ return NULL;
|
|
+ }
|
|
+
|
|
+ g_assert_cmpint (date->year, ==, 2004);
|
|
+ g_assert_cmpint (date->month, ==, 11);
|
|
+ g_assert_cmpint (date->day, ==, 6);
|
|
+ g_assert_cmpint (date->hour, ==, 8);
|
|
+ g_assert_cmpint (date->minute, ==, 9);
|
|
+ g_assert_cmpint (date->second, ==, 7);
|
|
+
|
|
+ return date;
|
|
+}
|
|
+
|
|
+typedef struct {
|
|
+ SoupDateFormat format;
|
|
+ const char *date;
|
|
+ const char *bugref;
|
|
+} GoodDate;
|
|
+
|
|
+static const GoodDate good_dates[] = {
|
|
+ { SOUP_DATE_HTTP, "Sat, 06 Nov 2004 08:09:07 GMT", NULL },
|
|
+ { SOUP_DATE_COOKIE, "Sat, 06-Nov-2004 08:09:07 GMT", NULL },
|
|
+ { SOUP_DATE_RFC2822, "Sat, 6 Nov 2004 08:09:07 -0430", "579055" },
|
|
+ { SOUP_DATE_ISO8601_COMPACT, "20041106T080907", NULL },
|
|
+ { SOUP_DATE_ISO8601_FULL, "2004-11-06T08:09:07", NULL },
|
|
+ { SOUP_DATE_ISO8601_XMLRPC, "20041106T08:09:07", NULL }
|
|
+};
|
|
+
|
|
+static void
|
|
+check_good (gconstpointer data)
|
|
+{
|
|
+ GoodDate *good = (GoodDate *)data;
|
|
+ SoupDate *date;
|
|
+ char *strdate2;
|
|
+
|
|
+ if (good->bugref)
|
|
+ g_test_bug (good->bugref);
|
|
+
|
|
+ date = check_correct_date (good->date);
|
|
+ if (!date)
|
|
+ return;
|
|
+
|
|
+ strdate2 = soup_date_to_string (date, good->format);
|
|
+ soup_date_free (date);
|
|
+
|
|
+ soup_test_assert (strcmp (good->date, strdate2) == 0,
|
|
+ "restringification failed: '%s' -> '%s'\n",
|
|
+ good->date, strdate2);
|
|
+ g_free (strdate2);
|
|
+}
|
|
+
|
|
+typedef struct {
|
|
+ const char *date;
|
|
+ const char *bugref;
|
|
+} OkDate;
|
|
+
|
|
+static const OkDate ok_dates[] = {
|
|
+ /* rfc1123-date, and broken variants */
|
|
+ { "Sat, 06 Nov 2004 08:09:07 GMT", NULL },
|
|
+ { "Sat, 6 Nov 2004 08:09:07 GMT", NULL },
|
|
+ { "Sat, 6 Nov 2004 08:09:07 GMT", NULL },
|
|
+ { "Sat, 06 Nov 2004 08:09:07", NULL },
|
|
+ { "06 Nov 2004 08:09:07 GMT", NULL },
|
|
+ { "SAT, 06 NOV 2004 08:09:07 +1000", "644048" },
|
|
+ { "Sat, 06-Nov-2004 08:09:07 -10000", "https://gitlab.gnome.org/GNOME/libsoup/-/issues/448" },
|
|
+ { "Sat, 06-Nov-2004 08:09:07 +01:30", "https://gitlab.gnome.org/GNOME/libsoup/-/issues/448" },
|
|
+ { "Sat, 06-Nov-2004 08:09:07 +0:180", "https://gitlab.gnome.org/GNOME/libsoup/-/issues/448" },
|
|
+ { "Sat, 06-Nov-2004 08:09:07 +100:100", "https://gitlab.gnome.org/GNOME/libsoup/-/issues/448" },
|
|
+ { "Sat, 06-Nov-2004 08:09:07 Z", "https://gitlab.gnome.org/GNOME/libsoup/-/issues/448" },
|
|
+
|
|
+ /* rfc850-date, and broken variants */
|
|
+ { "Saturday, 06-Nov-04 08:09:07 GMT", NULL },
|
|
+ { "Saturday, 6-Nov-04 08:09:07 GMT", NULL },
|
|
+ { "Saturday, 6-Nov-04 08:09:07 GMT", NULL },
|
|
+ { "Saturday, 06-Nov-104 08:09:07 GMT", NULL },
|
|
+ { "Saturday, 06-Nov-2004 08:09:07 GMT", NULL },
|
|
+ { "Saturday, 6-Nov-2004 08:09:07 GMT", NULL },
|
|
+ { "Saturday, 6-Nov-2004 08:09:07 GMT", NULL },
|
|
+ { "Saturday, 06-Nov-04 08:09:07", NULL },
|
|
+ { "06-Nov-04 08:09:07 GMT", NULL },
|
|
+
|
|
+ /* asctime-date, and broken variants */
|
|
+ { "Sat Nov 6 08:09:07 2004", NULL },
|
|
+ { "Sat Nov 06 08:09:07 2004", NULL },
|
|
+ { "Sat Nov 6 08:09:07 2004", NULL },
|
|
+ { "Sat Nov 6 08:09:07 2004 GMT", NULL },
|
|
+ { "Sat Nov 6 08:09:07 2004 NoZone", "https://gitlab.gnome.org/GNOME/libsoup/-/issues/448" },
|
|
+ { "Sat Nov 6 08:09:07 2004 UTC", "https://gitlab.gnome.org/GNOME/libsoup/-/issues/448" },
|
|
+
|
|
+ /* ISO 8601 */
|
|
+ { "2004-11-06T08:09:07Z", NULL },
|
|
+ { "20041106T08:09:07Z", NULL },
|
|
+ { "20041106T08:09:07+00:00", NULL },
|
|
+ { "20041106T080907+00:00", NULL },
|
|
+
|
|
+ /* Netscape cookie spec date, and broken variants */
|
|
+ { "Sat, 06-Nov-2004 08:09:07 GMT", NULL },
|
|
+ { "Sat, 6-Nov-2004 08:09:07 GMT", NULL },
|
|
+ { "Sat, 6-Nov-2004 08:09:07 GMT", NULL },
|
|
+ { "Sat, 06-Nov-2004 08:09:07", NULL },
|
|
+
|
|
+ /* Original version of Netscape cookie spec, and broken variants */
|
|
+ { "Sat, 06-Nov-04 08:09:07 GMT", NULL },
|
|
+ { "Sat, 6-Nov-04 08:09:07 GMT", NULL },
|
|
+ { "Sat, 6-Nov-04 08:09:07 GMT", NULL },
|
|
+ { "Sat, 06-Nov-104 08:09:07 GMT", NULL },
|
|
+ { "Sat, 06-Nov-04 08:09:07", NULL },
|
|
+
|
|
+ /* Miscellaneous broken formats seen on the web */
|
|
+ { "Sat 06-Nov-2004 08:9:07", NULL },
|
|
+ { "Saturday, 06-Nov-04 8:9:07 GMT", NULL },
|
|
+ { "Sat, 06 Nov 2004 08:09:7 GMT", NULL }
|
|
+};
|
|
+
|
|
+static void
|
|
+check_ok (gconstpointer data)
|
|
+{
|
|
+ OkDate *ok = (OkDate *)data;
|
|
+ SoupDate *date;
|
|
+
|
|
+ if (ok->bugref)
|
|
+ g_test_bug (ok->bugref);
|
|
+
|
|
+ date = check_correct_date (ok->date);
|
|
+ if (!date)
|
|
+ return;
|
|
+ soup_date_free (date);
|
|
+}
|
|
+
|
|
+#define TIME_T 1099728547L
|
|
+#define TIME_T_STRING "1099728547"
|
|
+
|
|
+static void
|
|
+check_ok_time_t (void)
|
|
+{
|
|
+ SoupDate *date;
|
|
+
|
|
+ date = soup_date_new_from_time_t (TIME_T);
|
|
+
|
|
+ g_assert_cmpint (date->year, ==, 2004);
|
|
+ g_assert_cmpint (date->month, ==, 11);
|
|
+ g_assert_cmpint (date->day, ==, 6);
|
|
+ g_assert_cmpint (date->hour, ==, 8);
|
|
+ g_assert_cmpint (date->minute, ==, 9);
|
|
+ g_assert_cmpint (date->second, ==, 7);
|
|
+
|
|
+ g_assert_cmpuint (TIME_T, ==, soup_date_to_time_t (date));
|
|
+
|
|
+ soup_date_free (date);
|
|
+}
|
|
+
|
|
+typedef struct {
|
|
+ const char *date;
|
|
+ const char *bugref;
|
|
+} BadDate;
|
|
+
|
|
+static const BadDate bad_dates[] = {
|
|
+ /* broken rfc1123-date */
|
|
+ { ", 06 Nov 2004 08:09:07 GMT", NULL },
|
|
+ { "Sat, Nov 2004 08:09:07 GMT", NULL },
|
|
+ { "Sat, 06 2004 08:09:07 GMT", NULL },
|
|
+ { "Sat, 06 Nov 08:09:07 GMT", NULL },
|
|
+ { "Sat, 06 Nov 2004 :09:07 GMT", NULL },
|
|
+ { "Sat, 06 Nov 2004 09:07 GMT", NULL },
|
|
+ { "Sat, 06 Nov 2004 08::07 GMT", NULL },
|
|
+ { "Sat, 06 Nov 2004 08:09: GMT", NULL },
|
|
+
|
|
+ /* broken rfc850-date */
|
|
+ { ", 06-Nov-04 08:09:07 GMT", NULL },
|
|
+ { "Saturday, -Nov-04 08:09:07 GMT", NULL },
|
|
+ { "Saturday, Nov-04 08:09:07 GMT", NULL },
|
|
+ { "Saturday, 06-04 08:09:07 GMT", NULL },
|
|
+ { "Saturday, 06--04 08:09:07 GMT", NULL },
|
|
+ { "Saturday, 06-Nov- 08:09:07 GMT", NULL },
|
|
+ { "Saturday, 06-Nov 08:09:07 GMT", NULL },
|
|
+ { "Saturday, 06-Nov-04 :09:07 GMT", NULL },
|
|
+ { "Saturday, 06-Nov-04 09:07 GMT", NULL },
|
|
+ { "Saturday, 06-Nov-04 08::07 GMT", NULL },
|
|
+ { "Saturday, 06-Nov-04 08:09: GMT", NULL },
|
|
+
|
|
+ /* broken asctime-date */
|
|
+ { "Nov 6 08:09:07 2004", NULL },
|
|
+ { "Sat 6 08:09:07 2004", NULL },
|
|
+ { "Sat Nov 08:09:07 2004", NULL },
|
|
+ { "Sat Nov 6 :09:07 2004", NULL },
|
|
+ { "Sat Nov 6 09:07 2004", NULL },
|
|
+ { "Sat Nov 6 08::07 2004", NULL },
|
|
+ { "Sat Nov 6 08:09: 2004", NULL },
|
|
+ { "Sat Nov 6 08:09:07", NULL },
|
|
+ { "Sat Nov 6 08:09:07 GMT 2004", NULL },
|
|
+
|
|
+ /* range constraints added "https://gitlab.gnome.org/GNOME/libsoup/-/issues/448" */
|
|
+ { "Sat, 00-Nov-2004 08:09:07 GMT", NULL },
|
|
+ { "Sat, 32-Nov-2004 08:09:07 GMT", NULL },
|
|
+ { "Sat, 06-Nov-0 08:09:07 GMT", NULL },
|
|
+ { "Sat, 06-Nov-9999 08:09:07 GMT", NULL },
|
|
+ { "Sat, 06-Nov-2004 0-1:09:07 GMT", NULL },
|
|
+ { "(Sat), Nov 6 -1:09:07 2004", NULL },
|
|
+ { "Sat, 06-Nov-2004 24:09:07 GMT", NULL },
|
|
+ { "Sat, 06-Nov-2004 08:-1:07 GMT", NULL },
|
|
+ { "Sat, 06-Nov-2004 08:60:07 GMT", NULL },
|
|
+ { "Sat, 06-Nov-2004 08:09:-10 GMT", NULL },
|
|
+ { "Sat, 06-Nov-2004 08:09:60 GMT", NULL },
|
|
+ { "Sat, 06-Nov-71 08:09:99 UTC", NULL },
|
|
+ { "Sat, 31-Feb-2004 08:09:07 UTC", NULL },
|
|
+};
|
|
+
|
|
+static void
|
|
+check_bad (gconstpointer data)
|
|
+{
|
|
+ BadDate *bad = (BadDate *)data;
|
|
+ SoupDate *date;
|
|
+
|
|
+ if (bad->bugref)
|
|
+ g_test_bug (bad->bugref);
|
|
+
|
|
+ date = make_date (bad->date);
|
|
+ soup_test_assert (date == NULL,
|
|
+ "date parsing succeeded for '%s': %d %d %d - %d %d %d",
|
|
+ bad->date,
|
|
+ date->year, date->month, date->day,
|
|
+ date->hour, date->minute, date->second);
|
|
+ g_clear_pointer (&date, soup_date_free);
|
|
+}
|
|
+
|
|
+typedef struct {
|
|
+ const char *source;
|
|
+ const char *http, *cookie, *rfc2822, *compact, *full, *xmlrpc;
|
|
+} DateConversion;
|
|
+
|
|
+static const DateConversion conversions[] = {
|
|
+ /* SOUP_DATE_HTTP */
|
|
+ { "Sat, 06 Nov 2004 08:09:07 GMT",
|
|
+
|
|
+ "Sat, 06 Nov 2004 08:09:07 GMT",
|
|
+ "Sat, 06-Nov-2004 08:09:07 GMT",
|
|
+ "Sat, 6 Nov 2004 08:09:07 +0000",
|
|
+ "20041106T080907Z",
|
|
+ "2004-11-06T08:09:07Z",
|
|
+ "20041106T08:09:07" },
|
|
+
|
|
+ /* RFC2822 GMT */
|
|
+ { "Sat, 6 Nov 2004 08:09:07 +0000",
|
|
+
|
|
+ "Sat, 06 Nov 2004 08:09:07 GMT",
|
|
+ "Sat, 06-Nov-2004 08:09:07 GMT",
|
|
+ "Sat, 6 Nov 2004 08:09:07 +0000",
|
|
+ "20041106T080907Z",
|
|
+ "2004-11-06T08:09:07Z",
|
|
+ "20041106T08:09:07" },
|
|
+
|
|
+ /* RFC2822 with positive offset */
|
|
+ { "Sat, 6 Nov 2004 08:09:07 +0430",
|
|
+
|
|
+ "Sat, 06 Nov 2004 04:39:07 GMT",
|
|
+ "Sat, 06-Nov-2004 04:39:07 GMT",
|
|
+ "Sat, 6 Nov 2004 08:09:07 +0430",
|
|
+ "20041106T080907+0430",
|
|
+ "2004-11-06T08:09:07+04:30",
|
|
+ "20041106T08:09:07" },
|
|
+
|
|
+ /* RFC2822 with negative offset */
|
|
+ { "Sat, 6 Nov 2004 08:09:07 -0430",
|
|
+
|
|
+ "Sat, 06 Nov 2004 12:39:07 GMT",
|
|
+ "Sat, 06-Nov-2004 12:39:07 GMT",
|
|
+ "Sat, 6 Nov 2004 08:09:07 -0430",
|
|
+ "20041106T080907-0430",
|
|
+ "2004-11-06T08:09:07-04:30",
|
|
+ "20041106T08:09:07" },
|
|
+
|
|
+ /* RFC2822 floating */
|
|
+ { "Sat, 6 Nov 2004 08:09:07 -0000",
|
|
+
|
|
+ "Sat, 06 Nov 2004 08:09:07 GMT",
|
|
+ "Sat, 06-Nov-2004 08:09:07 GMT",
|
|
+ "Sat, 6 Nov 2004 08:09:07 -0000",
|
|
+ "20041106T080907",
|
|
+ "2004-11-06T08:09:07",
|
|
+ "20041106T08:09:07" },
|
|
+
|
|
+ /* ISO GMT */
|
|
+ { "2004-11-06T08:09:07Z",
|
|
+
|
|
+ "Sat, 06 Nov 2004 08:09:07 GMT",
|
|
+ "Sat, 06-Nov-2004 08:09:07 GMT",
|
|
+ "Sat, 6 Nov 2004 08:09:07 +0000",
|
|
+ "20041106T080907Z",
|
|
+ "2004-11-06T08:09:07Z",
|
|
+ "20041106T08:09:07" },
|
|
+
|
|
+ /* ISO with positive offset */
|
|
+ { "2004-11-06T08:09:07+04:30",
|
|
+
|
|
+ "Sat, 06 Nov 2004 04:39:07 GMT",
|
|
+ "Sat, 06-Nov-2004 04:39:07 GMT",
|
|
+ "Sat, 6 Nov 2004 08:09:07 +0430",
|
|
+ "20041106T080907+0430",
|
|
+ "2004-11-06T08:09:07+04:30",
|
|
+ "20041106T08:09:07" },
|
|
+
|
|
+ /* ISO with negative offset */
|
|
+ { "2004-11-06T08:09:07-04:30",
|
|
+
|
|
+ "Sat, 06 Nov 2004 12:39:07 GMT",
|
|
+ "Sat, 06-Nov-2004 12:39:07 GMT",
|
|
+ "Sat, 6 Nov 2004 08:09:07 -0430",
|
|
+ "20041106T080907-0430",
|
|
+ "2004-11-06T08:09:07-04:30",
|
|
+ "20041106T08:09:07" },
|
|
+
|
|
+ /* ISO floating */
|
|
+ { "2004-11-06T08:09:07",
|
|
+
|
|
+ "Sat, 06 Nov 2004 08:09:07 GMT",
|
|
+ "Sat, 06-Nov-2004 08:09:07 GMT",
|
|
+ "Sat, 6 Nov 2004 08:09:07 -0000",
|
|
+ "20041106T080907",
|
|
+ "2004-11-06T08:09:07",
|
|
+ "20041106T08:09:07" }
|
|
+};
|
|
+
|
|
+static void
|
|
+check_conversion (gconstpointer data)
|
|
+{
|
|
+ const DateConversion *conv = data;
|
|
+ SoupDate *date;
|
|
+ char *str;
|
|
+
|
|
+ date = make_date (conv->source);
|
|
+ if (!date) {
|
|
+ soup_test_assert (FALSE, "date parsing failed for '%s'.", conv->source);
|
|
+ return;
|
|
+ }
|
|
+
|
|
+ str = soup_date_to_string (date, SOUP_DATE_HTTP);
|
|
+ g_assert_cmpstr (str, ==, conv->http);
|
|
+ g_free (str);
|
|
+
|
|
+ str = soup_date_to_string (date, SOUP_DATE_COOKIE);
|
|
+ g_assert_cmpstr (str, ==, conv->cookie);
|
|
+ g_free (str);
|
|
+
|
|
+ str = soup_date_to_string (date, SOUP_DATE_RFC2822);
|
|
+ g_assert_cmpstr (str, ==, conv->rfc2822);
|
|
+ g_free (str);
|
|
+
|
|
+ str = soup_date_to_string (date, SOUP_DATE_ISO8601_COMPACT);
|
|
+ g_assert_cmpstr (str, ==, conv->compact);
|
|
+ g_free (str);
|
|
+
|
|
+ str = soup_date_to_string (date, SOUP_DATE_ISO8601_FULL);
|
|
+ g_assert_cmpstr (str, ==, conv->full);
|
|
+ g_free (str);
|
|
+
|
|
+ str = soup_date_to_string (date, SOUP_DATE_ISO8601_XMLRPC);
|
|
+ g_assert_cmpstr (str, ==, conv->xmlrpc);
|
|
+ g_free (str);
|
|
+
|
|
+ soup_date_free (date);
|
|
+}
|
|
+
|
|
+int
|
|
+main (int argc, char **argv)
|
|
+{
|
|
+ int i, ret;
|
|
+ char *path;
|
|
+
|
|
+ test_init (argc, argv, NULL);
|
|
+
|
|
+ for (i = 0; i < G_N_ELEMENTS (good_dates); i++) {
|
|
+ path = g_strdup_printf ("/date/good/%s", good_dates[i].date);
|
|
+ g_test_add_data_func (path, &good_dates[i], check_good);
|
|
+ g_free (path);
|
|
+ }
|
|
+
|
|
+ for (i = 0; i < G_N_ELEMENTS (ok_dates); i++) {
|
|
+ path = g_strdup_printf ("/date/ok/%s", ok_dates[i].date);
|
|
+ g_test_add_data_func (path, &ok_dates[i], check_ok);
|
|
+ g_free (path);
|
|
+ }
|
|
+ g_test_add_func ("/date/ok/" TIME_T_STRING, check_ok_time_t);
|
|
+
|
|
+ for (i = 0; i < G_N_ELEMENTS (bad_dates); i++) {
|
|
+ path = g_strdup_printf ("/date/bad/%s", bad_dates[i].date);
|
|
+ g_test_add_data_func (path, &bad_dates[i], check_bad);
|
|
+ g_free (path);
|
|
+ }
|
|
+
|
|
+ for (i = 0; i < G_N_ELEMENTS (conversions); i++) {
|
|
+ path = g_strdup_printf ("/date/conversions/%s", conversions[i].source);
|
|
+ g_test_add_data_func (path, &conversions[i], check_conversion);
|
|
+ g_free (path);
|
|
+ }
|
|
+
|
|
+ ret = g_test_run ();
|
|
+
|
|
+ test_cleanup ();
|
|
+ return ret;
|
|
+}
|
|
--
|
|
2.51.0
|
|
|
|
|
|
From 724efae52bcf2c0b8a247f736103285ad68d1b15 Mon Sep 17 00:00:00 2001
|
|
From: Brian Yurko <155515-byurko@users.noreply.gitlab.gnome.org>
|
|
Date: Wed, 11 Jun 2025 11:00:56 -0400
|
|
Subject: [PATCH 2/2] tests: Add tests for date-time including timezone
|
|
validation work
|
|
|
|
These tests are built on top of earlier work in a related pull request.
|
|
|
|
Closes #448
|
|
---
|
|
libsoup/soup-date-utils.c | 339 ++++++++++++++++++++++++++++++++++++++
|
|
tests/cookies-test.c | 1 +
|
|
2 files changed, 340 insertions(+)
|
|
create mode 100644 libsoup/soup-date-utils.c
|
|
|
|
diff --git a/libsoup/soup-date-utils.c b/libsoup/soup-date-utils.c
|
|
new file mode 100644
|
|
index 00000000..73f80ab6
|
|
--- /dev/null
|
|
+++ b/libsoup/soup-date-utils.c
|
|
@@ -0,0 +1,339 @@
|
|
+/*
|
|
+ * Copyright (C) 2020 Igalia, S.L.
|
|
+ *
|
|
+ * This library is free software; you can redistribute it and/or
|
|
+ * modify it under the terms of the GNU Library 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
|
|
+ * Library General Public License for more details.
|
|
+ *
|
|
+ * You should have received a copy of the GNU Library General Public License
|
|
+ * along with this library; see the file COPYING.LIB. If not, write to
|
|
+ * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
|
|
+ * Boston, MA 02110-1301, USA.
|
|
+ */
|
|
+
|
|
+#ifdef HAVE_CONFIG_H
|
|
+#include <config.h>
|
|
+#endif
|
|
+
|
|
+#include <stdlib.h>
|
|
+
|
|
+#include "soup-date-utils.h"
|
|
+#include "soup-date-utils-private.h"
|
|
+
|
|
+/**
|
|
+ * soup_date_time_is_past:
|
|
+ * @date: a #GDateTime
|
|
+ *
|
|
+ * Determines if @date is in the past.
|
|
+ *
|
|
+ * Returns: %TRUE if @date is in the past
|
|
+ */
|
|
+gboolean
|
|
+soup_date_time_is_past (GDateTime *date)
|
|
+{
|
|
+ g_return_val_if_fail (date != NULL, TRUE);
|
|
+
|
|
+ /* optimization */
|
|
+ if (g_date_time_get_year (date) < 2025)
|
|
+ return TRUE;
|
|
+
|
|
+ return g_date_time_to_unix (date) < time (NULL);
|
|
+}
|
|
+
|
|
+/**
|
|
+ * SoupDateFormat:
|
|
+ * @SOUP_DATE_HTTP: RFC 1123 format, used by the HTTP "Date" header. Eg
|
|
+ * "Sun, 06 Nov 1994 08:49:37 GMT".
|
|
+ * @SOUP_DATE_COOKIE: The format for the "Expires" timestamp in the
|
|
+ * Netscape cookie specification. Eg, "Sun, 06-Nov-1994 08:49:37 GMT".
|
|
+ *
|
|
+ * Date formats that [func@date_time_to_string] can use.
|
|
+ *
|
|
+ * @SOUP_DATE_HTTP and @SOUP_DATE_COOKIE always coerce the time to
|
|
+ * UTC.
|
|
+ *
|
|
+ * This enum may be extended with more values in future releases.
|
|
+ **/
|
|
+
|
|
+/* Do not internationalize */
|
|
+static const char *const months[] = {
|
|
+ "Jan", "Feb", "Mar", "Apr", "May", "Jun",
|
|
+ "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"
|
|
+};
|
|
+
|
|
+/* Do not internationalize */
|
|
+static const char *const days[] = {
|
|
+ "Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"
|
|
+};
|
|
+
|
|
+/**
|
|
+ * soup_date_time_to_string:
|
|
+ * @date: a #GDateTime
|
|
+ * @format: the format to generate the date in
|
|
+ *
|
|
+ * Converts @date to a string in the format described by @format.
|
|
+ *
|
|
+ * Returns: (transfer full): @date as a string or %NULL
|
|
+ **/
|
|
+char *
|
|
+soup_date_time_to_string (GDateTime *date,
|
|
+ SoupDateFormat format)
|
|
+{
|
|
+ g_return_val_if_fail (date != NULL, NULL);
|
|
+
|
|
+ if (format == SOUP_DATE_HTTP || format == SOUP_DATE_COOKIE) {
|
|
+ /* HTTP and COOKIE formats require UTC timestamp, so coerce
|
|
+ * @date if it's non-UTC.
|
|
+ */
|
|
+ GDateTime *utcdate = g_date_time_to_utc (date);
|
|
+ char *date_format;
|
|
+ char *formatted_date;
|
|
+
|
|
+ // We insert days/months ourselves to avoid locale specific formatting
|
|
+ if (format == SOUP_DATE_HTTP) {
|
|
+ /* "Sun, 06 Nov 1994 08:49:37 GMT" */
|
|
+ date_format = g_strdup_printf ("%s, %%d %s %%Y %%T GMT",
|
|
+ days[g_date_time_get_day_of_week (utcdate) - 1],
|
|
+ months[g_date_time_get_month (utcdate) - 1]);
|
|
+ } else {
|
|
+ /* "Sun, 06-Nov-1994 08:49:37 GMT" */
|
|
+ date_format = g_strdup_printf ("%s, %%d-%s-%%Y %%T GMT",
|
|
+ days[g_date_time_get_day_of_week (utcdate) - 1],
|
|
+ months[g_date_time_get_month (utcdate) - 1]);
|
|
+ }
|
|
+
|
|
+ formatted_date = g_date_time_format (utcdate, (const char*)date_format);
|
|
+ g_date_time_unref (utcdate);
|
|
+ g_free (date_format);
|
|
+ return formatted_date;
|
|
+ }
|
|
+
|
|
+ g_return_val_if_reached (NULL);
|
|
+}
|
|
+
|
|
+static inline gboolean
|
|
+parse_day (int *day, const char **date_string)
|
|
+{
|
|
+ char *end;
|
|
+
|
|
+ *day = strtoul (*date_string, &end, 10);
|
|
+ if (end == (char *)*date_string)
|
|
+ return FALSE;
|
|
+
|
|
+ while (*end == ' ' || *end == '-')
|
|
+ end++;
|
|
+ *date_string = end;
|
|
+ return *day >= 1 && *day <= 31;
|
|
+}
|
|
+
|
|
+static inline gboolean
|
|
+parse_month (int *month, const char **date_string)
|
|
+{
|
|
+ int i;
|
|
+
|
|
+ for (i = 0; i < G_N_ELEMENTS (months); i++) {
|
|
+ if (!g_ascii_strncasecmp (*date_string, months[i], 3)) {
|
|
+ *month = i + 1;
|
|
+ *date_string += 3;
|
|
+ while (**date_string == ' ' || **date_string == '-')
|
|
+ (*date_string)++;
|
|
+ return TRUE;
|
|
+ }
|
|
+ }
|
|
+ return FALSE;
|
|
+}
|
|
+
|
|
+static inline gboolean
|
|
+parse_year (int *year, const char **date_string)
|
|
+{
|
|
+ char *end;
|
|
+
|
|
+ *year = strtoul (*date_string, &end, 10);
|
|
+ if (end == (char *)*date_string)
|
|
+ return FALSE;
|
|
+
|
|
+ if (end == (char *)*date_string + 2) {
|
|
+ if (*year < 70)
|
|
+ *year += 2000;
|
|
+ else
|
|
+ *year += 1900;
|
|
+ } else if (end == (char *)*date_string + 3)
|
|
+ *year += 1900;
|
|
+
|
|
+ while (*end == ' ' || *end == '-')
|
|
+ end++;
|
|
+ *date_string = end;
|
|
+ return *year > 0 && *year < 9999;
|
|
+}
|
|
+
|
|
+static inline gboolean
|
|
+parse_time (int *hour, int *minute, int *second, const char **date_string)
|
|
+{
|
|
+ char *p, *end;
|
|
+
|
|
+ *hour = strtoul (*date_string, &end, 10);
|
|
+ if (end == (char *)*date_string || *end++ != ':')
|
|
+ return FALSE;
|
|
+ p = end;
|
|
+ *minute = strtoul (p, &end, 10);
|
|
+ if (end == p || *end++ != ':')
|
|
+ return FALSE;
|
|
+ p = end;
|
|
+ *second = strtoul (p, &end, 10);
|
|
+ if (end == p)
|
|
+ return FALSE;
|
|
+ p = end;
|
|
+
|
|
+ while (*p == ' ')
|
|
+ p++;
|
|
+ *date_string = p;
|
|
+ return *hour >= 0 && *hour < 24 && *minute >= 0 && *minute < 60 && *second >= 0 && *second < 60;
|
|
+}
|
|
+
|
|
+static inline gboolean
|
|
+parse_timezone (GTimeZone **timezone, const char **date_string)
|
|
+{
|
|
+ gint32 offset_minutes;
|
|
+ gboolean utc;
|
|
+
|
|
+ if (!**date_string) {
|
|
+ utc = FALSE;
|
|
+ offset_minutes = 0;
|
|
+ } else if (**date_string == '+' || **date_string == '-') {
|
|
+ gulong val;
|
|
+ int sign = (**date_string == '+') ? 1 : -1;
|
|
+ val = strtoul (*date_string + 1, (char **)date_string, 10);
|
|
+ if (val > 9999)
|
|
+ return FALSE;
|
|
+ if (**date_string == ':') {
|
|
+ gulong val2 = strtoul (*date_string + 1, (char **)date_string, 10);
|
|
+ if (val > 99 || val2 > 99)
|
|
+ return FALSE;
|
|
+ val = 60 * val + val2;
|
|
+ } else
|
|
+ val = 60 * (val / 100) + (val % 100);
|
|
+ offset_minutes = sign * val;
|
|
+ utc = (sign == -1) && !val;
|
|
+ } else if (**date_string == 'Z') {
|
|
+ offset_minutes = 0;
|
|
+ utc = TRUE;
|
|
+ (*date_string)++;
|
|
+ } else if (!strcmp (*date_string, "GMT") ||
|
|
+ !strcmp (*date_string, "UTC")) {
|
|
+ offset_minutes = 0;
|
|
+ utc = TRUE;
|
|
+ (*date_string) += 3;
|
|
+ } else if (strchr ("ECMP", **date_string) &&
|
|
+ ((*date_string)[1] == 'D' || (*date_string)[1] == 'S') &&
|
|
+ (*date_string)[2] == 'T') {
|
|
+ offset_minutes = -60 * (5 * strcspn ("ECMP", *date_string));
|
|
+ if ((*date_string)[1] == 'D')
|
|
+ offset_minutes += 60;
|
|
+ utc = FALSE;
|
|
+ } else
|
|
+ return FALSE;
|
|
+
|
|
+ if (utc)
|
|
+ *timezone = g_time_zone_new_utc ();
|
|
+ else
|
|
+ *timezone = g_time_zone_new_offset (offset_minutes * 60);
|
|
+ return TRUE;
|
|
+}
|
|
+
|
|
+static GDateTime *
|
|
+parse_textual_date (const char *date_string)
|
|
+{
|
|
+ int month, day, year, hour, minute, second;
|
|
+ GTimeZone *tz = NULL;
|
|
+ GDateTime *date;
|
|
+
|
|
+ /* If it starts with a word, it must be a weekday, which we skip */
|
|
+ if (g_ascii_isalpha (*date_string)) {
|
|
+ while (g_ascii_isalpha (*date_string))
|
|
+ date_string++;
|
|
+ if (*date_string == ',')
|
|
+ date_string++;
|
|
+ while (g_ascii_isspace (*date_string))
|
|
+ date_string++;
|
|
+ }
|
|
+
|
|
+ /* If there's now another word, this must be an asctime-date */
|
|
+ if (g_ascii_isalpha (*date_string)) {
|
|
+ /* (Sun) Nov 6 08:49:37 1994 */
|
|
+ if (!parse_month (&month, &date_string) ||
|
|
+ !parse_day (&day, &date_string) ||
|
|
+ !parse_time (&hour, &minute, &second, &date_string) ||
|
|
+ !parse_year (&year, &date_string) ||
|
|
+ !g_date_valid_dmy (day, month, year))
|
|
+ return NULL;
|
|
+
|
|
+ /* There shouldn't be a timezone, but check anyway */
|
|
+ parse_timezone (&tz, &date_string);
|
|
+ } else {
|
|
+ /* Non-asctime date, so some variation of
|
|
+ * (Sun,) 06 Nov 1994 08:49:37 GMT
|
|
+ */
|
|
+ if (!parse_day (&day, &date_string) ||
|
|
+ !parse_month (&month, &date_string) ||
|
|
+ !parse_year (&year, &date_string) ||
|
|
+ !parse_time (&hour, &minute, &second, &date_string) ||
|
|
+ !g_date_valid_dmy (day, month, year))
|
|
+ return NULL;
|
|
+
|
|
+ /* This time there *should* be a timezone, but we
|
|
+ * survive if there isn't.
|
|
+ */
|
|
+ parse_timezone (&tz, &date_string);
|
|
+ }
|
|
+
|
|
+ if (!tz)
|
|
+ tz = g_time_zone_new_utc ();
|
|
+
|
|
+ date = g_date_time_new (tz, year, month, day, hour, minute, second);
|
|
+ g_time_zone_unref (tz);
|
|
+
|
|
+ return date;
|
|
+}
|
|
+
|
|
+/**
|
|
+ * soup_date_time_new_from_http_string:
|
|
+ * @date_string: The date as a string
|
|
+ *
|
|
+ * Parses @date_string and tries to extract a date from it.
|
|
+ *
|
|
+ * This recognizes all of the "HTTP-date" formats from RFC 2616, RFC 2822 dates,
|
|
+ * and reasonable approximations thereof. (Eg, it is lenient about whitespace,
|
|
+ * leading "0"s, etc.)
|
|
+ *
|
|
+ * Returns: (nullable): a new #GDateTime, or %NULL if @date_string
|
|
+ * could not be parsed.
|
|
+ **/
|
|
+GDateTime *
|
|
+soup_date_time_new_from_http_string (const char *date_string)
|
|
+{
|
|
+ g_return_val_if_fail (date_string != NULL, NULL);
|
|
+
|
|
+ while (g_ascii_isspace (*date_string))
|
|
+ date_string++;
|
|
+
|
|
+ /* If it starts with a digit, it's either an ISO 8601 date, or
|
|
+ * an RFC2822 date without the optional weekday; in the later
|
|
+ * case, there will be a month name later on, so look for one
|
|
+ * of the month-start letters.
|
|
+ * Previous versions of this library supported parsing iso8601 strings
|
|
+ * however g_date_time_new_from_iso8601() should be used now. Just
|
|
+ * catch those in case for testing.
|
|
+ */
|
|
+ if (G_UNLIKELY (g_ascii_isdigit (*date_string) && !strpbrk (date_string, "JFMASOND"))) {
|
|
+ g_debug ("Unsupported format passed to soup_date_time_new_from_http_string(): %s", date_string);
|
|
+ return NULL;
|
|
+ }
|
|
+
|
|
+ return parse_textual_date (date_string);
|
|
+}
|
|
diff --git a/tests/cookies-test.c b/tests/cookies-test.c
|
|
index 61adfac5..aa719cdc 100644
|
|
--- a/tests/cookies-test.c
|
|
+++ b/tests/cookies-test.c
|
|
@@ -292,6 +292,7 @@ static void
|
|
do_cookies_parsing_int32_overflow (void)
|
|
{
|
|
SoupCookie *cookie = soup_cookie_parse ("Age=1;expires=3Mar9 999:9:9+ 999999999-age=main=gne=", NULL);
|
|
+ g_test_bug ("https://gitlab.gnome.org/GNOME/libsoup/-/issues/448");
|
|
g_assert_nonnull (cookie);
|
|
g_assert_null (soup_cookie_get_expires (cookie));
|
|
soup_cookie_free (cookie);
|
|
--
|
|
2.51.0
|
|
|