summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMichael Drake <tlsa@netsurf-browser.org>2016-08-13 13:36:17 +0100
committerMichael Drake <tlsa@netsurf-browser.org>2016-08-13 13:36:17 +0100
commit91f4f89d4c864c6ce0170d60a13a29aed56fea5d (patch)
tree2118f4f5b478ec66439c41fe14ccdfae4ad236e4
parent6a3f7a386ed7912d19971dec9235ad98692c22c1 (diff)
parentfe00eac8cb0740b74ec2f586d98e741c84299d90 (diff)
downloadnetsurf-91f4f89d4c864c6ce0170d60a13a29aed56fea5d.tar.gz
netsurf-91f4f89d4c864c6ce0170d60a13a29aed56fea5d.tar.bz2
Merge branch 'tlsa/date'
-rw-r--r--Makefile19
-rw-r--r--Makefile.defaults8
-rw-r--r--content/fetch.c2
-rw-r--r--content/fetchers/Makefile6
-rw-r--r--test/Makefile14
-rw-r--r--test/time.c423
-rw-r--r--utils/ascii.h358
-rw-r--r--utils/nsurl.c127
-rw-r--r--utils/time.c889
-rw-r--r--utils/url.c4
10 files changed, 1718 insertions, 132 deletions
diff --git a/Makefile b/Makefile
index 226bf4f3d..60810e153 100644
--- a/Makefile
+++ b/Makefile
@@ -511,14 +511,6 @@ $(eval $(call pkg_config_find_and_add,libcss,CSS))
$(eval $(call pkg_config_find_and_add,libdom,DOM))
$(eval $(call pkg_config_find_and_add,libnsutils,nsutils))
$(eval $(call pkg_config_find_and_add,libutf8proc,utf8proc))
-$(eval $(call pkg_config_find_and_add,openssl,OpenSSL))
-# freemint does not support pkg-config for libcurl
-ifeq ($(HOST),mint)
- CFLAGS += $(shell curl-config --cflags)
- LDFLAGS += $(shell curl-config --libs)
-else
- $(eval $(call pkg_config_find_and_add,libcurl,Curl))
-endif
# Common libraries without pkg-config support
LDFLAGS += -lz
@@ -529,9 +521,20 @@ LDFLAGS += -lz
NETSURF_FEATURE_PNG_CFLAGS := -DWITH_PNG
NETSURF_FEATURE_BMP_CFLAGS := -DWITH_BMP
NETSURF_FEATURE_GIF_CFLAGS := -DWITH_GIF
+NETSURF_FEATURE_CURL_CFLAGS := -DWITH_CURL
NETSURF_FEATURE_NSSVG_CFLAGS := -DWITH_NS_SVG
+NETSURF_FEATURE_OPENSSL_CFLAGS := -DWITH_OPENSSL
NETSURF_FEATURE_ROSPRITE_CFLAGS := -DWITH_NSSPRITE
+$(eval $(call pkg_config_find_and_add_enabled,OPENSSL,openssl,OpenSSL))
+# freemint does not support pkg-config for libcurl
+ifeq ($(HOST),mint)
+ CFLAGS += $(shell curl-config --cflags)
+ LDFLAGS += $(shell curl-config --libs)
+else
+ $(eval $(call pkg_config_find_and_add_enabled,CURL,libcurl,Curl))
+endif
+
$(eval $(call pkg_config_find_and_add_enabled,PNG,libpng,PNG))
$(eval $(call pkg_config_find_and_add_enabled,BMP,libnsbmp,BMP))
$(eval $(call pkg_config_find_and_add_enabled,GIF,libnsgif,GIF))
diff --git a/Makefile.defaults b/Makefile.defaults
index 196e19236..1651a2d21 100644
--- a/Makefile.defaults
+++ b/Makefile.defaults
@@ -31,6 +31,14 @@
# Options relating to all versions of NetSurf
# ----------------------------------------------------------------------------
+# Enable NetSurf's use of libcurl for fetching over http(s)
+# Valid options: YES, NO
+NETSURF_USE_CURL := YES
+
+# Enable NetSurf's use of openssl for fetching over https
+# Valid options: YES, NO
+NETSURF_USE_OPENSSL := YES
+
# Enable NetSurf's use of libnsbmp for displaying BMPs and ICOs
# Valid options: YES, NO
NETSURF_USE_BMP := YES
diff --git a/content/fetch.c b/content/fetch.c
index 11adf9cf4..93c7de2ad 100644
--- a/content/fetch.c
+++ b/content/fetch.c
@@ -290,10 +290,12 @@ nserror fetcher_init(void)
{
nserror ret;
+#ifdef WITH_CURL
ret = fetch_curl_register();
if (ret != NSERROR_OK) {
return ret;
}
+#endif
ret = fetch_data_register();
if (ret != NSERROR_OK) {
diff --git a/content/fetchers/Makefile b/content/fetchers/Makefile
index 855154232..9c8479320 100644
--- a/content/fetchers/Makefile
+++ b/content/fetchers/Makefile
@@ -1,8 +1,10 @@
# Content fetchers sources
-S_FETCHERS := curl.c data.c file.c about.c resource.c
+S_FETCHERS_YES := data.c file.c about.c resource.c
+S_FETCHERS_NO :=
+S_FETCHERS_$(NETSURF_USE_CURL) += curl.c
-S_FETCHERS := $(addprefix content/fetchers/,$(S_FETCHERS))
+S_FETCHERS := $(addprefix content/fetchers/,$(S_FETCHERS_YES))
# The following files depend on the testament
content/fetchers/about.c: testament $(OBJROOT)/testament.h
diff --git a/test/Makefile b/test/Makefile
index 0253f4dc0..d62e2fd8d 100644
--- a/test/Makefile
+++ b/test/Makefile
@@ -1,7 +1,16 @@
#
# NetSurf unit tests
-TESTS := nsurl urldbtest nsoption bloom hashtable urlescape utils messages #llcache
+TESTS := \
+ nsurl \
+ urldbtest \
+ nsoption \
+ bloom \
+ hashtable \
+ urlescape \
+ utils \
+ messages \
+ time #llcache
# nsurl sources
nsurl_SRCS := utils/corestrings.c utils/nsurl.c utils/idna.c \
@@ -44,6 +53,9 @@ urlescape_SRCS := utils/url.c test/log.c test/urlescape.c
utils_SRCS := utils/utils.c utils/messages.c utils/hashtable.c \
test/log.c test/utils.c
+# time test sources
+time_SRCS := utils/time.c test/log.c test/time.c
+
# Coverage builds need additional flags
ifeq ($(MAKECMDGOALS),coverage)
COV_CFLAGS ?= -fprofile-arcs -ftest-coverage -O0
diff --git a/test/time.c b/test/time.c
new file mode 100644
index 000000000..34cfac0f0
--- /dev/null
+++ b/test/time.c
@@ -0,0 +1,423 @@
+/*
+ * Copyright 2016 Michael Drake <tlsa@netsurf-browser.org>
+ *
+ * This file is part of NetSurf, http://www.netsurf-browser.org/
+ *
+ * NetSurf is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; version 2 of the License.
+ *
+ * NetSurf 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+/**
+ * \file
+ * Test time operations.
+ */
+
+#include <stdlib.h>
+#include <check.h>
+
+#include "utils/errors.h"
+#include "utils/time.h"
+
+#define NELEMS(x) (sizeof(x) / sizeof((x)[0]))
+
+struct test_string_pair {
+ const char* test;
+ const char* expected;
+};
+
+struct test_bad_string {
+ const char* test;
+ nserror res;
+};
+
+static const struct test_string_pair date_string_tests[] = {
+ {
+ .test = "Thu, 01 Jan 1970 00:00:00 GMT",
+ .expected = "Thu, 01 Jan 1970 00:00:00 GMT"
+ },
+ {
+ .test = "Thursday, 01 Jan 1970 00:00:00 GMT",
+ .expected = "Thu, 01 Jan 1970 00:00:00 GMT"
+ },
+ {
+ .test = "Tue, 16 Feb 1999 19:45:12 GMT",
+ .expected = "Tue, 16 Feb 1999 19:45:12 GMT"
+ },
+ {
+ .test = "Sunday, 16 Mar 1980 19:45:12 GMT",
+ .expected = "Sun, 16 Mar 1980 19:45:12 GMT"
+ },
+ {
+ .test = "Sun, 16 Mar 1980 19:45:12 GMT",
+ .expected = "Sun, 16 Mar 1980 19:45:12 GMT"
+ },
+ {
+ .test = "Tue, 16 Apr 2013 19:45:12 GMT",
+ .expected = "Tue, 16 Apr 2013 19:45:12 GMT"
+ },
+ {
+ .test = "Tue, 16 May 2000 19:45:12 GMT",
+ .expected = "Tue, 16 May 2000 19:45:12 GMT"
+ },
+ {
+ .test = "Tuesday, 12 Jun 2001 12:12:12 GMT",
+ .expected = "Tue, 12 Jun 2001 12:12:12 GMT"
+ },
+ {
+ .test = "Tue, 12 Jun 2001 12:12:12 GMT",
+ .expected = "Tue, 12 Jun 2001 12:12:12 GMT"
+ },
+ {
+ .test = "Thu, 16 Jul 2207 12:45:12 GMT",
+ .expected = "Thu, 16 Jul 2207 12:45:12 GMT"
+ },
+ {
+ .test = "Thu, 16 Aug 2007 19:45:12 GMT",
+ .expected = "Thu, 16 Aug 2007 19:45:12 GMT"
+ },
+ {
+ .test = "Tue, 16 Sep 3456 00:45:12 GMT",
+ .expected = "Tue, 16 Sep 3456 00:45:12 GMT"
+ },
+ {
+ .test = "Sun, 16 Oct 1988 19:45:59 GMT",
+ .expected = "Sun, 16 Oct 1988 19:45:59 GMT"
+ },
+ {
+ .test = "Tue, 16 Nov 1971 19:59:12 GMT",
+ .expected = "Tue, 16 Nov 1971 19:59:12 GMT"
+ },
+ {
+ .test = "Friday, 16 Dec 1977 23:45:12 GMT",
+ .expected = "Fri, 16 Dec 1977 23:45:12 GMT"
+ },
+ {
+ .test = "Fri, 16 Dec 1977 23:45:12 GMT",
+ .expected = "Fri, 16 Dec 1977 23:45:12 GMT"
+ },
+ {
+ .test = " 16 Dec 1977 23:45:12 GMT",
+ .expected = "Fri, 16 Dec 1977 23:45:12 GMT"
+ },
+ {
+ .test = " 16 Dec 1977 23:45 GMT",
+ .expected = "Fri, 16 Dec 1977 23:45:00 GMT"
+ },
+ {
+ .test = "23:59 16 Dec 1977 GMT",
+ .expected = "Fri, 16 Dec 1977 23:59:00 GMT"
+ },
+ {
+ .test = "23:59 16 Dec 1977 UTC",
+ .expected = "Fri, 16 Dec 1977 23:59:00 GMT"
+ },
+ {
+ .test = "1977 GMT 23:59 16 Dec",
+ .expected = "Fri, 16 Dec 1977 23:59:00 GMT"
+ },
+ {
+ .test = "1977 Dec GMT 16",
+ .expected = "Fri, 16 Dec 1977 00:00:00 GMT"
+ },
+ {
+ .test = "1977 Dec 12",
+ .expected = "Mon, 12 Dec 1977 00:00:00 GMT"
+ },
+ {
+ .test = "1977 12 Dec",
+ .expected = "Mon, 12 Dec 1977 00:00:00 GMT"
+ },
+ {
+ .test = "Dec 1977 12",
+ .expected = "Mon, 12 Dec 1977 00:00:00 GMT"
+ },
+ {
+ .test = "12 Dec 1977",
+ .expected = "Mon, 12 Dec 1977 00:00:00 GMT"
+ },
+ {
+ .test = "12 Dec 77",
+ .expected = "Mon, 12 Dec 1977 00:00:00 GMT"
+ },
+ {
+ .test = "12 77 Dec",
+ .expected = "Mon, 12 Dec 1977 00:00:00 GMT"
+ },
+ {
+ .test = "77 12 Dec",
+ .expected = "Mon, 12 Dec 1977 00:00:00 GMT"
+ },
+ {
+ .test = "12 12 Dec",
+ .expected = "Wed, 12 Dec 2012 00:00:00 GMT"
+ },
+ {
+ .test = "5 12 Dec",
+ .expected = "Wed, 05 Dec 2012 00:00:00 GMT"
+ },
+ {
+ .test = "12 5 Dec",
+ .expected = "Mon, 12 Dec 2005 00:00:00 GMT"
+ },
+ {
+ .test = "12/5/Dec",
+ .expected = "Mon, 12 Dec 2005 00:00:00 GMT"
+ },
+ {
+ .test = "Dec-12/2005/",
+ .expected = "Mon, 12 Dec 2005 00:00:00 GMT"
+ },
+ {
+ .test = "12-5-Dec",
+ .expected = "Mon, 12 Dec 2005 00:00:00 GMT"
+ },
+ {
+ .test = "2005-12-Dec",
+ .expected = "Mon, 12 Dec 2005 00:00:00 GMT"
+ },
+ {
+ .test = "2005-Dec-12",
+ .expected = "Mon, 12 Dec 2005 00:00:00 GMT"
+ },
+ {
+ .test = "2005-dec-12",
+ .expected = "Mon, 12 Dec 2005 00:00:00 GMT"
+ },
+ {
+ .test = "2005-dEC-12",
+ .expected = "Mon, 12 Dec 2005 00:00:00 GMT"
+ },
+ {
+ .test = "20051212",
+ .expected = "Mon, 12 Dec 2005 00:00:00 GMT"
+ },
+ {
+ .test = "20051212 GMT",
+ .expected = "Mon, 12 Dec 2005 00:00:00 GMT"
+ },
+ {
+ .test = "20051212 +0000",
+ .expected = "Mon, 12 Dec 2005 00:00:00 GMT"
+ },
+ {
+ .test = "20051212 UTC",
+ .expected = "Mon, 12 Dec 2005 00:00:00 GMT"
+ },
+ {
+ .test = "20051212 00:00 UTC",
+ .expected = "Mon, 12 Dec 2005 00:00:00 GMT"
+ },
+ {
+ .test = "00:00 20051212 UTC",
+ .expected = "Mon, 12 Dec 2005 00:00:00 GMT"
+ },
+ {
+ .test = "00:00:59 20051212 UTC",
+ .expected = "Mon, 12 Dec 2005 00:00:59 GMT"
+ },
+ {
+ .test = "00:00:60 20051212 UTC", /* leap second */
+ .expected = "Mon, 12 Dec 2005 00:01:00 GMT"
+ },
+ {
+ .test = "Thu, 11 Aug 2016 08:47:30 GMT",
+ .expected = "Thu, 11 Aug 2016 08:47:30 GMT"
+ },
+ {
+ .test = "Thu, 11 Aug 2016 08:47:30 UTC",
+ .expected = "Thu, 11 Aug 2016 08:47:30 GMT"
+ },
+ {
+ .test = "Thu, 11 Aug 2016 08:47:30 +0000",
+ .expected = "Thu, 11 Aug 2016 08:47:30 GMT"
+ },
+ {
+ .test = "Thu, 11 Aug 2016 08:47:30 -0000",
+ .expected = "Thu, 11 Aug 2016 08:47:30 GMT"
+ },
+ {
+ .test = "Thu, 11 Aug 2016 08:47:30 +0001",
+ .expected = "Thu, 11 Aug 2016 08:46:30 GMT"
+ },
+ {
+ .test = "Thu, 11 Aug 2016 08:47:30 -0001",
+ .expected = "Thu, 11 Aug 2016 08:48:30 GMT"
+ },
+ {
+ .test = "Thu, 11 Aug 2016 08:47:30 +0030",
+ .expected = "Thu, 11 Aug 2016 08:17:30 GMT"
+ },
+ {
+ .test = "Thu, 11 Aug 2016 08:47:30 -0030",
+ .expected = "Thu, 11 Aug 2016 09:17:30 GMT"
+ },
+ {
+ .test = "Thu, 11 Aug 2016 08:47:30 +0059",
+ .expected = "Thu, 11 Aug 2016 07:48:30 GMT"
+ },
+ {
+ .test = "Thu, 11 Aug 2016 08:47:30 -0059",
+ .expected = "Thu, 11 Aug 2016 09:46:30 GMT"
+ },
+ {
+ .test = "Thu, 11 Aug 2016 08:47:30 +0100",
+ .expected = "Thu, 11 Aug 2016 07:47:30 GMT"
+ },
+ {
+ .test = "Thu, 11 Aug 2016 08:47:30 -0100",
+ .expected = "Thu, 11 Aug 2016 09:47:30 GMT"
+ },
+ {
+ .test = "Thu, 11 Aug 2016 08:47:30 +1200",
+ .expected = "Wed, 10 Aug 2016 20:47:30 GMT"
+ },
+ {
+ .test = "Thu, 11 Aug 2016 08:47:30 -1200",
+ .expected = "Thu, 11 Aug 2016 20:47:30 GMT"
+ },
+ {
+ .test = "Thu, 11 Aug 2016 08:47:30 +0060",
+ .expected = "Thu, 11 Aug 2016 07:47:30 GMT"
+ },
+ {
+ .test = "Thu, 11 Aug 2016 08:47:30 -0060",
+ .expected = "Thu, 11 Aug 2016 09:47:30 GMT"
+ },
+ {
+ .test = "Thu, 11 Aug 2016 08:47:30 +0070",
+ .expected = "Thu, 11 Aug 2016 07:37:30 GMT"
+ },
+ {
+ .test = "Thu, 11 Aug 2016 08:47:30 -0070",
+ .expected = "Thu, 11 Aug 2016 09:57:30 GMT"
+ },
+ {
+ .test = "Thu, 11 Aug 2016 08:47:30 BST",
+ .expected = "Thu, 11 Aug 2016 07:47:30 GMT"
+ },
+ {
+ .test = "14-Aug-2015 11:58:16 GMT",
+ .expected = "Fri, 14 Aug 2015 11:58:16 GMT"
+ },
+};
+
+
+static const struct test_bad_string date_bad_string_tests[] = {
+ {
+ .test = NULL,
+ .res = NSERROR_BAD_PARAMETER
+ },
+ {
+ .test = "",
+ .res = NSERROR_INVALID
+ },
+ {
+ .test = "Th",
+ .res = NSERROR_INVALID
+ },
+ {
+ .test = "5",
+ .res = NSERROR_INVALID
+ },
+ {
+ .test = "5",
+ .res = NSERROR_INVALID
+ },
+ {
+ .test = "dsflihs9l84toswuhfsif74f",
+ .res = NSERROR_INVALID
+ },
+ {
+ .test = "Foosday, 16 Dec 1977 23:45:12 GMT",
+ .res = NSERROR_INVALID
+ },
+};
+
+/**
+ * Date string comparason test
+ */
+START_TEST(date_string_compare)
+{
+ nserror res;
+ time_t time_out;
+ const struct test_string_pair *t = &date_string_tests[_i];
+
+ res = nsc_strntimet(t->test, strlen(t->test), &time_out);
+ ck_assert(res == NSERROR_OK);
+ ck_assert_str_eq(rfc1123_date(time_out), t->expected);
+}
+END_TEST
+
+/**
+ * Date string conversion bad data test
+ */
+START_TEST(date_bad_string)
+{
+ nserror res;
+ time_t time_out;
+ const struct test_bad_string *t = &date_bad_string_tests[_i];
+
+ res = nsc_strntimet(t->test,
+ t->test != NULL ? strlen(t->test) : 0,
+ &time_out);
+ ck_assert(res != NSERROR_OK);
+ ck_assert(res == t->res);
+}
+END_TEST
+
+
+/* suite generation */
+static Suite *time_suite(void)
+{
+ Suite *s;
+ TCase *tc_date_string_compare;
+ TCase *tc_date_bad_string;
+
+ s = suite_create("time");
+
+ /* date parsing: string comparason */
+ tc_date_string_compare = tcase_create(
+ "date string to time_t");
+
+ /* date parsing: bad string handling */
+ tc_date_bad_string = tcase_create(
+ "date string to time_t (bad input)");
+
+ tcase_add_loop_test(tc_date_string_compare,
+ date_string_compare,
+ 0, NELEMS(date_string_tests));
+ suite_add_tcase(s, tc_date_string_compare);
+
+ tcase_add_loop_test(tc_date_bad_string,
+ date_bad_string,
+ 0, NELEMS(date_bad_string_tests));
+ suite_add_tcase(s, tc_date_bad_string);
+
+ return s;
+}
+
+int main(int argc, char **argv)
+{
+ int number_failed;
+ Suite *s;
+ SRunner *sr;
+
+ s = time_suite();
+
+ sr = srunner_create(s);
+ srunner_run_all(sr, CK_ENV);
+
+ number_failed = srunner_ntests_failed(sr);
+ srunner_free(sr);
+
+ return (number_failed == 0) ? EXIT_SUCCESS : EXIT_FAILURE;
+}
diff --git a/utils/ascii.h b/utils/ascii.h
new file mode 100644
index 000000000..f08e756a0
--- /dev/null
+++ b/utils/ascii.h
@@ -0,0 +1,358 @@
+/*
+ * Copyright 2016 Michael Drake <tlsa@netsurf-browser.org>
+ *
+ * This file is part of NetSurf, http://www.netsurf-browser.org/
+ *
+ * NetSurf is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; version 2 of the License.
+ *
+ * NetSurf 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+/**
+ * \file utils/ascii.h
+ * \brief Helpers for ASCII string handling.
+ *
+ * These helpers for string parsing will have the correct effect for parsing
+ * ASCII text (as used by most web specs), regardless of system locale.
+ */
+
+#ifndef _NETSURF_UTILS_ASCII_H_
+#define _NETSURF_UTILS_ASCII_H_
+
+#include <errno.h>
+#include <stdlib.h>
+#include <limits.h>
+
+/**
+ * Test whether a character is a whitespace character.
+ *
+ * \param[in] c Character to test.
+ * \return true iff `c` is whitespace, else false.
+ */
+static inline bool ascii_is_space(char c)
+{
+ return (c == ' ' || c == '\t' ||
+ c == '\n' || c == '\v' ||
+ c == '\f' || c == '\r');
+}
+
+/**
+ * Test whether a character is lower-case alphabetical.
+ *
+ * \param[in] c Character to test.
+ * \return true iff `c` is lower-case alphabetical, else false.
+ */
+static inline bool ascii_is_alpha_lower(char c)
+{
+ return (c >= 'a' && c <= 'z');
+}
+
+/**
+ * Test whether a character is upper-case alphabetical.
+ *
+ * \param[in] c Character to test.
+ * \return true iff `c` is upper-case alphabetical, else false.
+ */
+static inline bool ascii_is_alpha_upper(char c)
+{
+ return (c >= 'A' && c <= 'Z');
+}
+
+/**
+ * Test whether a character is alphabetical (upper or lower case).
+ *
+ * \param[in] c Character to test.
+ * \return true iff `c` is alphabetical, else false.
+ */
+static inline bool ascii_is_alpha(char c)
+{
+ return (ascii_is_alpha_lower(c) || ascii_is_alpha_upper(c));
+}
+
+/**
+ * Test whether a character is a decimal digit.
+ *
+ * \param[in] c Character to test.
+ * \return true iff `c` is a decimal digit, else false.
+ */
+static inline bool ascii_is_digit(char c)
+{
+ return (c >= '0' && c <= '9');
+}
+
+/**
+ * Test whether a character is a positive/negative numerical sign.
+ *
+ * \param[in] c Character to test.
+ * \return true iff `c` is a sign, else false.
+ */
+static inline bool ascii_is_sign(char c)
+{
+ return (c == '-' || c == '+');
+}
+
+/**
+ * Test whether a character is alphanumerical (upper or lower case).
+ *
+ * \param[in] c Character to test.
+ * \return true iff `c` is alphanumerical, else false.
+ */
+static inline bool ascii_is_alphanumerical(char c)
+{
+ return (ascii_is_alpha(c) || ascii_is_digit(c));
+}
+
+/**
+ * Test whether a character is hexadecimal (lower case).
+ *
+ * \param[in] c Character to test.
+ * \return true iff `c` is hexadecimal, else false.
+ */
+static inline bool ascii_is_hex_lower(char c)
+{
+ return (ascii_is_digit(c) ||
+ (c >= 'a' && c <= 'f'));
+}
+
+/**
+ * Test whether a character is hexadecimal (upper case).
+ *
+ * \param[in] c Character to test.
+ * \return true iff `c` is hexadecimal, else false.
+ */
+static inline bool ascii_is_hex_upper(char c)
+{
+ return (ascii_is_digit(c) ||
+ (c >= 'A' && c <= 'F'));
+}
+
+/**
+ * Test whether a character is hexadecimal (upper or lower case).
+ *
+ * \param[in] c Character to test.
+ * \return true iff `c` is hexadecimal, else false.
+ */
+static inline bool ascii_is_hex(char c)
+{
+ return (ascii_is_digit(c) ||
+ (c >= 'A' && c <= 'F') ||
+ (c >= 'a' && c <= 'f'));
+}
+
+/**
+ * Convert an upper case character to lower case.
+ *
+ * If the given character is not upper case alphabetical, it is returned
+ * unchanged.
+ *
+ * \param[in] c Character to convert.
+ * \return lower case conversion of `c` else `c`.
+ */
+static inline char ascii_to_lower(char c)
+{
+ return (ascii_is_alpha_upper(c)) ? (c + 'a' - 'A') : c;
+}
+
+/**
+ * Convert a lower case character to upper case.
+ *
+ * If the given character is not lower case alphabetical, it is returned
+ * unchanged.
+ *
+ * \param[in] c Character to convert.
+ * \return upper case conversion of `c` else `c`.
+ */
+static inline char ascii_to_upper(char c)
+{
+ return (ascii_is_alpha_lower(c)) ? (c + 'A' - 'a') : c;
+}
+
+/**
+ * Count consecutive lower case alphabetical characters in string.
+ *
+ * \param[in] str String to count characters in.
+ * \return number of consecutive lower case characters at start of `str`.
+ */
+static inline size_t ascii_count_alpha_lower(const char *str)
+{
+ size_t count = 0;
+ while (ascii_is_alpha_lower(*(str++))) {
+ count++;
+ }
+ return count;
+}
+
+/**
+ * Count consecutive upper case alphabetical characters in string.
+ *
+ * \param[in] str String to count characters in.
+ * \return number of consecutive upper case characters at start of `str`.
+ */
+static inline size_t ascii_count_alpha_upper(const char *str)
+{
+ size_t count = 0;
+ while (ascii_is_alpha_upper(*(str++))) {
+ count++;
+ }
+ return count;
+}
+
+/**
+ * Count consecutive alphabetical characters in string (upper or lower case).
+ *
+ * \param[in] str String to count characters in.
+ * \return number of consecutive alphabetical characters at start of `str`.
+ */
+static inline size_t ascii_count_alpha(const char *str)
+{
+ size_t count = 0;
+ while (ascii_is_alpha(*(str++))) {
+ count++;
+ }
+ return count;
+}
+
+/**
+ * Count consecutive decial digit characters in string.
+ *
+ * \param[in] str String to count characters in.
+ * \return number of consecutive decimal digit characters at start of `str`.
+ */
+static inline size_t ascii_count_digit(const char *str)
+{
+ size_t count = 0;
+ while (ascii_is_digit(*(str++))) {
+ count++;
+ }
+ return count;
+}
+
+/**
+ * Count consecutive characters either decimal digit or colon in string.
+ *
+ * \param[in] str String to count characters in.
+ * \return number of consecutive decimal or ':' characters at start of `str`.
+ */
+static inline size_t ascii_count_digit_or_colon(const char *str)
+{
+ size_t count = 0;
+ while (ascii_is_digit(*str) || *str == ':') {
+ count++;
+ str++;
+ }
+ return count;
+}
+
+/**
+ * Test for string equality (case insensitive).
+ *
+ * \param[in] s1 First string to compare.
+ * \param[in] s2 Second string to compare.
+ * \return true iff strings are equivalent, else false.
+ */
+static inline bool ascii_strings_equal_caseless(
+ const char *s1, const char *s2)
+{
+ while (*s1 != '\0') {
+ if (ascii_to_lower(*s1) != ascii_to_lower(*s2)) {
+ break;
+ }
+ s1++;
+ s2++;
+ }
+ return (ascii_to_lower(*s1) == ascii_to_lower(*s2));
+}
+
+/**
+ * Test for string equality (case sensitive).
+ *
+ * \param[in] s1 First string to compare.
+ * \param[in] s2 Second string to compare.
+ * \return true iff strings are equal, else false.
+ */
+static inline bool ascii_strings_equal(
+ const char *s1, const char *s2)
+{
+ while (*s1 != '\0') {
+ if (*s1 != *s2) {
+ break;
+ }
+ s1++;
+ s2++;
+ }
+ return (*s1 == *s2);
+}
+
+/**
+ * Count consecutive equal ascii characters (case insensitive).
+ *
+ * \param[in] s1 First string to compare.
+ * \param[in] s2 Second string to compare.
+ * \return number of equivalent characters.
+ */
+static inline size_t ascii_strings_count_equal_caseless(
+ const char *s1, const char *s2)
+{
+ const char *s = s1;
+ while (*s1 != '\0') {
+ if (ascii_to_lower(*s1) != ascii_to_lower(*s2)) {
+ break;
+ }
+ s1++;
+ s2++;
+ }
+ return s1 - s;
+}
+
+/**
+ * Count consecutive equal ascii characters (case sensitive).
+ *
+ * \param[in] s1 First string to compare.
+ * \param[in] s2 Second string to compare.
+ * \return number of equal characters.
+ */
+static inline size_t ascii_strings_count_equal(
+ const char *s1, const char *s2)
+{
+ const char *s = s1;
+ while (*s1 != '\0') {
+ if (*s1 != *s2) {
+ break;
+ }
+ s1++;
+ s2++;
+ }
+ return s1 - s;
+}
+
+/**
+ * Parse an int out of a string.
+ *
+ * \param[in] str String to parse integer out of.
+ * \param[out] res Returns parsed integer.
+ * \return The number of characters consumed in `str`.
+ * Returning 0 indicates failure to parse an integer out of the string.
+ */
+static inline size_t ascii_string_to_int(const char *str, int *res)
+{
+ char *end = NULL;
+ long long temp = strtoll(str, &end, 10);
+
+ if (end == str || errno == ERANGE ||
+ temp < INT_MIN || temp > INT_MAX) {
+ return 0;
+ }
+
+ *res = temp;
+ return end - str;
+}
+
+#endif
diff --git a/utils/nsurl.c b/utils/nsurl.c
index 6582264b8..c5c614c55 100644
--- a/utils/nsurl.c
+++ b/utils/nsurl.c
@@ -32,12 +32,12 @@
*/
#include <assert.h>
-#include <ctype.h>
#include <libwapcaplet/libwapcaplet.h>
#include <stdlib.h>
#include <string.h>
#include <strings.h>
+#include "utils/ascii.h"
#include "utils/corestrings.h"
#include "utils/errors.h"
#include "utils/idna.h"
@@ -48,28 +48,6 @@
/* Define to enable NSURL debugging */
#undef NSURL_DEBUG
-/** ascii character codes */
-enum ascii_codepoints {
- ASCII_NUL = 0,
- ASCII_SPC = 0x20,
- ASCII_FF = 0x0C,
- ASCII_NL = 0x0A,
- ASCII_CR = 0x0D,
- ASCII_HT = 0x09,
- ASCII_VT = 0x0B,
- ASCII_PLUS = 0x2b,
- ASCII_MINUS = 0x2d,
- ASCII_FULLSTOP = 0x2e,
- ASCII_SLASH = 0x2F,
- ASCII_0 = 0x30,
- ASCII_9 = 0x39,
- ASCII_COLON = 0x3A,
- ASCII_A = 0x41,
- ASCII_Z = 0x5A,
- ASCII_a = 0x61,
- ASCII_z = 0x7A
-};
-
/**
* nsurl scheme type
*/
@@ -208,79 +186,6 @@ inline static char digit2uppercase_hex(unsigned char digit) {
}
/**
- * determines if a character is a whitespace in the ascii character encoding
- *
- * whitespace characters are space, form feed, new line, carrige
- * return, horizontal tab and vertical tab.
- *
- * \param c character to classify
- * \return zero if the character is not whitespace else 1
- */
-inline static int is_ascii_space(int c)
-{
- if (c == ASCII_SPC ||
- c == ASCII_FF ||
- c == ASCII_NL ||
- c == ASCII_CR ||
- c == ASCII_HT ||
- c == ASCII_VT) {
- return 1;
- }
- return 0;
-}
-
-/**
- * determine if a character is alphabetical in the ascii character encoding
- *
- * characters in the range A-Z and a-z are considered alphabetical.
- *
- * \param c character to classify
- * \return zero if the character is not alphabetical else 1
- */
-inline static int is_ascii_alpha(int c)
-{
- if (((c >= ASCII_A) && (c <= ASCII_Z)) ||
- ((c >= ASCII_a) && (c <= ASCII_z))) {
- return 1;
- }
- return 0;
-}
-
-/**
- * determine if a character is a number in the ascii character encoding
- *
- * characters in the range 0-9 are considered numbers.
- *
- * \param c character to classify
- * \return 1 if the character is a number else 0
- */
-inline static int is_ascii_digit(int c)
-{
- if ((c >= ASCII_0) && (c <= ASCII_9)) {
- return 1;
- }
- return 0;
-}
-
-/**
- * determine if a character is alphanumerical in the ascii character encoding
- *
- * characters in the range A-Z, a-z and 0-9 are considered alphanumeric.
- *
- * \param c character to classify
- * \return zero if the character is not alphanumerical else 1
- */
-inline static int is_ascii_alnum(int c)
-{
- if (((c >= ASCII_0) && (c <= ASCII_9)) ||
- ((c >= ASCII_A) && (c <= ASCII_Z)) ||
- ((c >= ASCII_a) && (c <= ASCII_z))) {
- return 1;
- }
- return 0;
-}
-
-/**
* determine if a character is unreserved
*
* \param c character to classify.
@@ -397,7 +302,7 @@ static void nsurl__get_string_markers(const char * const url_s,
0, 0, 0, 0, NSURL_SCHEME_OTHER };
/* Skip any leading whitespace in url_s */
- while (is_ascii_space(*pos))
+ while (ascii_is_space(*pos))
pos++;
/* Record start point */
@@ -406,7 +311,7 @@ static void nsurl__get_string_markers(const char * const url_s,
marker.scheme_end = marker.authority = marker.colon_first = marker.at =
marker.colon_last = marker.path = marker.start;
- if (*pos == ASCII_NUL) {
+ if (*pos == '\0') {
/* Nothing but whitespace, early exit */
marker.query = marker.fragment = marker.end = marker.path;
*markers = marker;
@@ -414,14 +319,12 @@ static void nsurl__get_string_markers(const char * const url_s,
}
/* Get scheme */
- if (is_ascii_alpha(*pos)) {
+ if (ascii_is_alpha(*pos)) {
pos++;
- while (*pos != ASCII_COLON && *pos != ASCII_NUL) {
- if (!is_ascii_alnum(*pos) &&
- (*pos != ASCII_PLUS) &&
- (*pos != ASCII_MINUS) &&
- (*pos != ASCII_FULLSTOP)) {
+ while (*pos != ':' && *pos != '\0') {
+ if (!ascii_is_alphanumerical(*pos) && (*pos != '+') &&
+ (*pos != '-') && (*pos != '.')) {
/* This character is not valid in the
* scheme */
break;
@@ -429,7 +332,7 @@ static void nsurl__get_string_markers(const char * const url_s,
pos++;
}
- if (*pos == ASCII_COLON) {
+ if (*pos == ':') {
/* This delimits the end of the scheme */
size_t off;
@@ -607,9 +510,9 @@ static void nsurl__get_string_markers(const char * const url_s,
/* We got to the end of url_s.
* Need to skip back over trailing whitespace to find end of URL */
pos--;
- if (pos >= url_s && is_ascii_space(*pos)) {
+ if (pos >= url_s && ascii_is_space(*pos)) {
trailing_whitespace = true;
- while (pos >= url_s && is_ascii_space(*pos))
+ while (pos >= url_s && ascii_is_space(*pos))
pos--;
}
@@ -790,7 +693,7 @@ static inline int nsurl__get_ascii_offset(char c1, char c2)
int offset;
/* Use 1st char as most significant hex digit */
- if (is_ascii_digit(c1))
+ if (ascii_is_digit(c1))
offset = 16 * (c1 - '0');
else if (c1 >= 'a' && c1 <= 'f')
offset = 16 * (c1 - 'a' + 10);
@@ -801,7 +704,7 @@ static inline int nsurl__get_ascii_offset(char c1, char c2)
return -1;
/* Use 2nd char as least significant hex digit and sum */
- if (is_ascii_digit(c2))
+ if (ascii_is_digit(c2))
offset += c2 - '0';
else if (c2 >= 'a' && c2 <= 'f')
offset += c2 - 'a' + 10;
@@ -953,7 +856,7 @@ static nserror nsurl__create_from_section(const char * const url_s,
length += 2;
} else if ((section == URL_SCHEME || section == URL_HOST) &&
- isupper(*pos)) {
+ ascii_is_alpha_upper(*pos)) {
/* Lower case this letter */
if (copy_len > 0) {
@@ -963,7 +866,7 @@ static nserror nsurl__create_from_section(const char * const url_s,
copy_len = 0;
}
/* Copy lower cased letter into normalised URL */
- *(pos_norm++) = tolower(*pos);
+ *(pos_norm++) = ascii_to_lower(*pos);
pos_url_s = pos + 1;
} else {
@@ -1058,7 +961,7 @@ static nserror nsurl__create_from_section(const char * const url_s,
*/
sec_start += colon - pegs->at;
while (++sec_start < norm_start + length) {
- if (!is_ascii_digit(*sec_start)) {
+ if (!ascii_is_digit(*sec_start)) {
/* Character after port isn't a
* digit; not a port separator
*/
diff --git a/utils/time.c b/utils/time.c
index cf8acc0d6..14f4004b9 100644
--- a/utils/time.c
+++ b/utils/time.c
@@ -25,30 +25,98 @@
* \brief Implementation of time operations.
*/
-#include <errno.h>
#include <stdio.h>
-#include <stdlib.h>
+#include <stdint.h>
+#include <stdbool.h>
+#ifdef WITH_CURL
#include <curl/curl.h>
+#endif
+#include "utils/ascii.h"
#include "utils/errors.h"
#include "utils/time.h"
+/**
+ * Weekdays
+ *
+ * Must be calender order.
+ */
+enum nsc_time_weekdays {
+ NSC_TIME_WEEKDAY_SUN,
+ NSC_TIME_WEEKDAY_MON,
+ NSC_TIME_WEEKDAY_TUE,
+ NSC_TIME_WEEKDAY_WED,
+ NSC_TIME_WEEKDAY_THU,
+ NSC_TIME_WEEKDAY_FRI,
+ NSC_TIME_WEEKDAY_SAT,
+ NSC_TIME_WEEKDAY__COUNT
+};
+/**
+ * Months
+ *
+ * Must be calender order.
+ */
+enum nsc_time_months {
+ NSC_TIME_MONTH_JAN,
+ NSC_TIME_MONTH_FEB,
+ NSC_TIME_MONTH_MAR,
+ NSC_TIME_MONTH_APR,
+ NSC_TIME_MONTH_MAY,
+ NSC_TIME_MONTH_JUN,
+ NSC_TIME_MONTH_JUL,
+ NSC_TIME_MONTH_AUG,
+ NSC_TIME_MONTH_SEP,
+ NSC_TIME_MONTH_OCT,
+ NSC_TIME_MONTH_NOV,
+ NSC_TIME_MONTH_DEC,
+ NSC_TIME_MONTH__COUNT,
+};
+
+
+/**
+ * Array of short weekday names.
+ */
+static const char * const weekdays_short[NSC_TIME_WEEKDAY__COUNT] = {
+ [NSC_TIME_WEEKDAY_SUN] = "Sun",
+ [NSC_TIME_WEEKDAY_MON] = "Mon",
+ [NSC_TIME_WEEKDAY_TUE] = "Tue",
+ [NSC_TIME_WEEKDAY_WED] = "Wed",
+ [NSC_TIME_WEEKDAY_THU] = "Thu",
+ [NSC_TIME_WEEKDAY_FRI] = "Fri",
+ [NSC_TIME_WEEKDAY_SAT] = "Sat"
+};
+/**
+ * Array of month names.
+ */
+static const char * const months[NSC_TIME_MONTH__COUNT] = {
+ [NSC_TIME_MONTH_JAN] = "Jan",
+ [NSC_TIME_MONTH_FEB] = "Feb",
+ [NSC_TIME_MONTH_MAR] = "Mar",
+ [NSC_TIME_MONTH_APR] = "Apr",
+ [NSC_TIME_MONTH_MAY] = "May",
+ [NSC_TIME_MONTH_JUN] = "Jun",
+ [NSC_TIME_MONTH_JUL] = "Jul",
+ [NSC_TIME_MONTH_AUG] = "Aug",
+ [NSC_TIME_MONTH_SEP] = "Sep",
+ [NSC_TIME_MONTH_OCT] = "Oct",
+ [NSC_TIME_MONTH_NOV] = "Nov",
+ [NSC_TIME_MONTH_DEC] = "Dec"
+};
+
+
/* exported interface documented in utils/time.h */
const char *rfc1123_date(time_t t)
{
static char ret[30];
struct tm *tm = gmtime(&t);
- const char *days[] = { "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat" },
- *months[] = { "Jan", "Feb", "Mar", "Apr", "May", "Jun",
- "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" };
snprintf(ret, sizeof ret, "%s, %02d %s %d %02d:%02d:%02d GMT",
- days[tm->tm_wday], tm->tm_mday, months[tm->tm_mon],
- tm->tm_year + 1900, tm->tm_hour, tm->tm_min,
- tm->tm_sec);
+ weekdays_short[tm->tm_wday], tm->tm_mday,
+ months[tm->tm_mon], tm->tm_year + 1900,
+ tm->tm_hour, tm->tm_min, tm->tm_sec);
return ret;
}
@@ -115,11 +183,814 @@ nserror nsc_snptimet(const char *str, size_t size, time_t *timep)
}
+#ifndef WITH_CURL
+
+
+/**
+ * Array of long weekday names.
+ */
+static const char * const weekdays_long[NSC_TIME_WEEKDAY__COUNT] = {
+ [NSC_TIME_WEEKDAY_SUN] = "Sunday",
+ [NSC_TIME_WEEKDAY_MON] = "Monday",
+ [NSC_TIME_WEEKDAY_TUE] = "Tuesday",
+ [NSC_TIME_WEEKDAY_WED] = "Wednesday",
+ [NSC_TIME_WEEKDAY_THU] = "Thursday",
+ [NSC_TIME_WEEKDAY_FRI] = "Friday",
+ [NSC_TIME_WEEKDAY_SAT] = "Saturday"
+};
+
+/**
+ * Timezone offsets in mins
+ *
+ * Order doesn't matter.
+ */
+enum nsc_time_zone_offsets {
+ /* Timezones */
+ NSC_TIME_ZONE_OFFSET_IDLE = -12 * 60,
+ NSC_TIME_ZONE_OFFSET_NZST = -12 * 60,
+ NSC_TIME_ZONE_OFFSET_NZT = -12 * 60,
+ NSC_TIME_ZONE_OFFSET_EAST = -10 * 60,
+ NSC_TIME_ZONE_OFFSET_GST = -10 * 60,
+ NSC_TIME_ZONE_OFFSET_JST = - 9 * 60,
+ NSC_TIME_ZONE_OFFSET_CCT = - 8 * 60,
+ NSC_TIME_ZONE_OFFSET_WAST = - 7 * 60,
+ NSC_TIME_ZONE_OFFSET_EET = - 2 * 60,
+ NSC_TIME_ZONE_OFFSET_CET = - 1 * 60,
+ NSC_TIME_ZONE_OFFSET_FWT = - 1 * 60,
+ NSC_TIME_ZONE_OFFSET_MET = - 1 * 60,
+ NSC_TIME_ZONE_OFFSET_MEWT = - 1 * 60,
+ NSC_TIME_ZONE_OFFSET_GMT = 0,
+ NSC_TIME_ZONE_OFFSET_UTC = 0,
+ NSC_TIME_ZONE_OFFSET_WET = 0,
+ NSC_TIME_ZONE_OFFSET_WAT = 1 * 60,
+ NSC_TIME_ZONE_OFFSET_AST = 4 * 60,
+ NSC_TIME_ZONE_OFFSET_EST = 5 * 60,
+ NSC_TIME_ZONE_OFFSET_CST = 6 * 60,
+ NSC_TIME_ZONE_OFFSET_MST = 7 * 60,
+ NSC_TIME_ZONE_OFFSET_PST = 8 * 60,
+ NSC_TIME_ZONE_OFFSET_YST = 9 * 60,
+ NSC_TIME_ZONE_OFFSET_AHST = 10 * 60,
+ NSC_TIME_ZONE_OFFSET_CAT = 10 * 60,
+ NSC_TIME_ZONE_OFFSET_HST = 10 * 60,
+ NSC_TIME_ZONE_OFFSET_IDLW = 12 * 60,
+
+ /* Daylight saving modified timezones */
+ NSC_TIME_ZONE_OFFSET_NZDT = NSC_TIME_ZONE_OFFSET_NZT - 60,
+ NSC_TIME_ZONE_OFFSET_EADT = NSC_TIME_ZONE_OFFSET_EAST - 60,
+ NSC_TIME_ZONE_OFFSET_WADT = NSC_TIME_ZONE_OFFSET_WAST - 60,
+ NSC_TIME_ZONE_OFFSET_CEST = NSC_TIME_ZONE_OFFSET_CET - 60,
+ NSC_TIME_ZONE_OFFSET_FST = NSC_TIME_ZONE_OFFSET_FWT - 60,
+ NSC_TIME_ZONE_OFFSET_MEST = NSC_TIME_ZONE_OFFSET_MET - 60,
+ NSC_TIME_ZONE_OFFSET_MESZ = NSC_TIME_ZONE_OFFSET_MET - 60,
+ NSC_TIME_ZONE_OFFSET_BST = NSC_TIME_ZONE_OFFSET_GMT - 60,
+ NSC_TIME_ZONE_OFFSET_ADT = NSC_TIME_ZONE_OFFSET_AST - 60,
+ NSC_TIME_ZONE_OFFSET_EDT = NSC_TIME_ZONE_OFFSET_EST - 60,
+ NSC_TIME_ZONE_OFFSET_CDT = NSC_TIME_ZONE_OFFSET_CST - 60,
+ NSC_TIME_ZONE_OFFSET_MDT = NSC_TIME_ZONE_OFFSET_MST - 60,
+ NSC_TIME_ZONE_OFFSET_PDT = NSC_TIME_ZONE_OFFSET_PST - 60,
+ NSC_TIME_ZONE_OFFSET_YDT = NSC_TIME_ZONE_OFFSET_YST - 60,
+ NSC_TIME_ZONE_OFFSET_HDT = NSC_TIME_ZONE_OFFSET_HST - 60,
+
+ /* Military timezones */
+ NSC_TIME_ZONE_OFFSET_Y = -12 * 60,
+ NSC_TIME_ZONE_OFFSET_X = -11 * 60,
+ NSC_TIME_ZONE_OFFSET_W = -10 * 60,
+ NSC_TIME_ZONE_OFFSET_V = - 9 * 60,
+ NSC_TIME_ZONE_OFFSET_U = - 8 * 60,
+ NSC_TIME_ZONE_OFFSET_T = - 7 * 60,
+ NSC_TIME_ZONE_OFFSET_S = - 6 * 60,
+ NSC_TIME_ZONE_OFFSET_R = - 5 * 60,
+ NSC_TIME_ZONE_OFFSET_Q = - 4 * 60,
+ NSC_TIME_ZONE_OFFSET_P = - 3 * 60,
+ NSC_TIME_ZONE_OFFSET_O = - 2 * 60,
+ NSC_TIME_ZONE_OFFSET_N = - 1 * 60,
+ NSC_TIME_ZONE_OFFSET_Z = 0 * 60,
+ NSC_TIME_ZONE_OFFSET_A = 1 * 60,
+ NSC_TIME_ZONE_OFFSET_B = 2 * 60,
+ NSC_TIME_ZONE_OFFSET_C = 3 * 60,
+ NSC_TIME_ZONE_OFFSET_D = 4 * 60,
+ NSC_TIME_ZONE_OFFSET_E = 5 * 60,
+ NSC_TIME_ZONE_OFFSET_F = 6 * 60,
+ NSC_TIME_ZONE_OFFSET_G = 7 * 60,
+ NSC_TIME_ZONE_OFFSET_H = 8 * 60,
+ NSC_TIME_ZONE_OFFSET_I = 9 * 60,
+ NSC_TIME_ZONE_OFFSET_K = 10 * 60,
+ NSC_TIME_ZONE_OFFSET_L = 11 * 60,
+ NSC_TIME_ZONE_OFFSET_M = 12 * 60,
+};
+
+/**
+ * List of timezones.
+ *
+ * The order here is the order they appear in the `timezone_mins` array.
+ * So there is value in putting the most common timezones first.
+ */
+enum nsc_time_zones {
+ NSC_TIME_ZONE_IDLE,
+ NSC_TIME_ZONE_NZST,
+ NSC_TIME_ZONE_NZT,
+ NSC_TIME_ZONE_EAST,
+ NSC_TIME_ZONE_GST,
+ NSC_TIME_ZONE_JST,
+ NSC_TIME_ZONE_CCT,
+ NSC_TIME_ZONE_WAST,
+ NSC_TIME_ZONE_EET,
+ NSC_TIME_ZONE_CET,
+ NSC_TIME_ZONE_FWT,
+ NSC_TIME_ZONE_MET,
+ NSC_TIME_ZONE_MEWT,
+ NSC_TIME_ZONE_GMT,
+ NSC_TIME_ZONE_UTC,
+ NSC_TIME_ZONE_WET,
+ NSC_TIME_ZONE_WAT,
+ NSC_TIME_ZONE_AST,
+ NSC_TIME_ZONE_EST,
+ NSC_TIME_ZONE_CST,
+ NSC_TIME_ZONE_MST,
+ NSC_TIME_ZONE_PST,
+ NSC_TIME_ZONE_YST,
+ NSC_TIME_ZONE_AHST,
+ NSC_TIME_ZONE_CAT,
+ NSC_TIME_ZONE_HST,
+ NSC_TIME_ZONE_IDLW,
+ NSC_TIME_ZONE_NZDT,
+ NSC_TIME_ZONE_EADT,
+ NSC_TIME_ZONE_WADT,
+ NSC_TIME_ZONE_CEST,
+ NSC_TIME_ZONE_FST,
+ NSC_TIME_ZONE_MEST,
+ NSC_TIME_ZONE_MESZ,
+ NSC_TIME_ZONE_BST,
+ NSC_TIME_ZONE_ADT,
+ NSC_TIME_ZONE_EDT,
+ NSC_TIME_ZONE_CDT,
+ NSC_TIME_ZONE_MDT,
+ NSC_TIME_ZONE_PDT,
+ NSC_TIME_ZONE_YDT,
+ NSC_TIME_ZONE_HDT,
+ NSC_TIME_ZONE_Y,
+ NSC_TIME_ZONE_X,
+ NSC_TIME_ZONE_W,
+ NSC_TIME_ZONE_V,
+ NSC_TIME_ZONE_U,
+ NSC_TIME_ZONE_T,
+ NSC_TIME_ZONE_S,
+ NSC_TIME_ZONE_R,
+ NSC_TIME_ZONE_Q,
+ NSC_TIME_ZONE_P,
+ NSC_TIME_ZONE_O,
+ NSC_TIME_ZONE_N,
+ NSC_TIME_ZONE_Z,
+ NSC_TIME_ZONE_A,
+ NSC_TIME_ZONE_B,
+ NSC_TIME_ZONE_C,
+ NSC_TIME_ZONE_D,
+ NSC_TIME_ZONE_E,
+ NSC_TIME_ZONE_F,
+ NSC_TIME_ZONE_G,
+ NSC_TIME_ZONE_H,
+ NSC_TIME_ZONE_I,
+ NSC_TIME_ZONE_K,
+ NSC_TIME_ZONE_L,
+ NSC_TIME_ZONE_M,
+ NSC_TIME_ZONE__COUNT
+};
+
+/**
+ * Array of minute offsets for timezones.
+ */
+static const int16_t timezone_mins[NSC_TIME_ZONE__COUNT] = {
+ [NSC_TIME_ZONE_IDLE] = NSC_TIME_ZONE_OFFSET_IDLE,
+ [NSC_TIME_ZONE_NZST] = NSC_TIME_ZONE_OFFSET_NZST,
+ [NSC_TIME_ZONE_NZT] = NSC_TIME_ZONE_OFFSET_NZT,
+ [NSC_TIME_ZONE_EAST] = NSC_TIME_ZONE_OFFSET_EAST,
+ [NSC_TIME_ZONE_GST] = NSC_TIME_ZONE_OFFSET_GST,
+ [NSC_TIME_ZONE_JST] = NSC_TIME_ZONE_OFFSET_JST,
+ [NSC_TIME_ZONE_CCT] = NSC_TIME_ZONE_OFFSET_CCT,
+ [NSC_TIME_ZONE_WAST] = NSC_TIME_ZONE_OFFSET_WAST,
+ [NSC_TIME_ZONE_EET] = NSC_TIME_ZONE_OFFSET_EET,
+ [NSC_TIME_ZONE_CET] = NSC_TIME_ZONE_OFFSET_CET,
+ [NSC_TIME_ZONE_FWT] = NSC_TIME_ZONE_OFFSET_FWT,
+ [NSC_TIME_ZONE_MET] = NSC_TIME_ZONE_OFFSET_MET,
+ [NSC_TIME_ZONE_MEWT] = NSC_TIME_ZONE_OFFSET_MEWT,
+ [NSC_TIME_ZONE_GMT] = NSC_TIME_ZONE_OFFSET_GMT,
+ [NSC_TIME_ZONE_UTC] = NSC_TIME_ZONE_OFFSET_UTC,
+ [NSC_TIME_ZONE_WET] = NSC_TIME_ZONE_OFFSET_WET,
+ [NSC_TIME_ZONE_WAT] = NSC_TIME_ZONE_OFFSET_WAT,
+ [NSC_TIME_ZONE_AST] = NSC_TIME_ZONE_OFFSET_AST,
+ [NSC_TIME_ZONE_EST] = NSC_TIME_ZONE_OFFSET_EST,
+ [NSC_TIME_ZONE_CST] = NSC_TIME_ZONE_OFFSET_CST,
+ [NSC_TIME_ZONE_MST] = NSC_TIME_ZONE_OFFSET_MST,
+ [NSC_TIME_ZONE_PST] = NSC_TIME_ZONE_OFFSET_PST,
+ [NSC_TIME_ZONE_YST] = NSC_TIME_ZONE_OFFSET_YST,
+ [NSC_TIME_ZONE_AHST] = NSC_TIME_ZONE_OFFSET_AHST,
+ [NSC_TIME_ZONE_CAT] = NSC_TIME_ZONE_OFFSET_CAT,
+ [NSC_TIME_ZONE_HST] = NSC_TIME_ZONE_OFFSET_HST,
+ [NSC_TIME_ZONE_IDLW] = NSC_TIME_ZONE_OFFSET_IDLW,
+ [NSC_TIME_ZONE_NZDT] = NSC_TIME_ZONE_OFFSET_NZDT,
+ [NSC_TIME_ZONE_EADT] = NSC_TIME_ZONE_OFFSET_EADT,
+ [NSC_TIME_ZONE_WADT] = NSC_TIME_ZONE_OFFSET_WADT,
+ [NSC_TIME_ZONE_CEST] = NSC_TIME_ZONE_OFFSET_CEST,
+ [NSC_TIME_ZONE_FST] = NSC_TIME_ZONE_OFFSET_FST,
+ [NSC_TIME_ZONE_MEST] = NSC_TIME_ZONE_OFFSET_MEST,
+ [NSC_TIME_ZONE_MESZ] = NSC_TIME_ZONE_OFFSET_MESZ,
+ [NSC_TIME_ZONE_BST] = NSC_TIME_ZONE_OFFSET_BST,
+ [NSC_TIME_ZONE_ADT] = NSC_TIME_ZONE_OFFSET_ADT,
+ [NSC_TIME_ZONE_EDT] = NSC_TIME_ZONE_OFFSET_EDT,
+ [NSC_TIME_ZONE_CDT] = NSC_TIME_ZONE_OFFSET_CDT,
+ [NSC_TIME_ZONE_MDT] = NSC_TIME_ZONE_OFFSET_MDT,
+ [NSC_TIME_ZONE_PDT] = NSC_TIME_ZONE_OFFSET_PDT,
+ [NSC_TIME_ZONE_YDT] = NSC_TIME_ZONE_OFFSET_YDT,
+ [NSC_TIME_ZONE_HDT] = NSC_TIME_ZONE_OFFSET_HDT,
+ [NSC_TIME_ZONE_Y] = NSC_TIME_ZONE_OFFSET_Y,
+ [NSC_TIME_ZONE_X] = NSC_TIME_ZONE_OFFSET_X,
+ [NSC_TIME_ZONE_W] = NSC_TIME_ZONE_OFFSET_W,
+ [NSC_TIME_ZONE_V] = NSC_TIME_ZONE_OFFSET_V,
+ [NSC_TIME_ZONE_U] = NSC_TIME_ZONE_OFFSET_U,
+ [NSC_TIME_ZONE_T] = NSC_TIME_ZONE_OFFSET_T,
+ [NSC_TIME_ZONE_S] = NSC_TIME_ZONE_OFFSET_S,
+ [NSC_TIME_ZONE_R] = NSC_TIME_ZONE_OFFSET_R,
+ [NSC_TIME_ZONE_Q] = NSC_TIME_ZONE_OFFSET_Q,
+ [NSC_TIME_ZONE_P] = NSC_TIME_ZONE_OFFSET_P,
+ [NSC_TIME_ZONE_O] = NSC_TIME_ZONE_OFFSET_O,
+ [NSC_TIME_ZONE_N] = NSC_TIME_ZONE_OFFSET_N,
+ [NSC_TIME_ZONE_Z] = NSC_TIME_ZONE_OFFSET_Z,
+ [NSC_TIME_ZONE_A] = NSC_TIME_ZONE_OFFSET_A,
+ [NSC_TIME_ZONE_B] = NSC_TIME_ZONE_OFFSET_B,
+ [NSC_TIME_ZONE_C] = NSC_TIME_ZONE_OFFSET_C,
+ [NSC_TIME_ZONE_D] = NSC_TIME_ZONE_OFFSET_D,
+ [NSC_TIME_ZONE_E] = NSC_TIME_ZONE_OFFSET_E,
+ [NSC_TIME_ZONE_F] = NSC_TIME_ZONE_OFFSET_F,
+ [NSC_TIME_ZONE_G] = NSC_TIME_ZONE_OFFSET_G,
+ [NSC_TIME_ZONE_H] = NSC_TIME_ZONE_OFFSET_H,
+ [NSC_TIME_ZONE_I] = NSC_TIME_ZONE_OFFSET_I,
+ [NSC_TIME_ZONE_K] = NSC_TIME_ZONE_OFFSET_K,
+ [NSC_TIME_ZONE_L] = NSC_TIME_ZONE_OFFSET_L,
+ [NSC_TIME_ZONE_M] = NSC_TIME_ZONE_OFFSET_M
+};
+
+/**
+ * Array of timezone names. Order does not matter.
+ */
+static const char * const timezones[NSC_TIME_ZONE__COUNT] = {
+ [NSC_TIME_ZONE_IDLE] = "IDLE",
+ [NSC_TIME_ZONE_NZST] = "NZST",
+ [NSC_TIME_ZONE_NZT] = "NZT",
+ [NSC_TIME_ZONE_EAST] = "EAST",
+ [NSC_TIME_ZONE_GST] = "GST",
+ [NSC_TIME_ZONE_JST] = "JST",
+ [NSC_TIME_ZONE_CCT] = "CCT",
+ [NSC_TIME_ZONE_WAST] = "WAST",
+ [NSC_TIME_ZONE_EET] = "EET",
+ [NSC_TIME_ZONE_CET] = "CET",
+ [NSC_TIME_ZONE_FWT] = "FWT",
+ [NSC_TIME_ZONE_MET] = "MET",
+ [NSC_TIME_ZONE_MEWT] = "MEWT",
+ [NSC_TIME_ZONE_GMT] = "GMT",
+ [NSC_TIME_ZONE_UTC] = "UTC",
+ [NSC_TIME_ZONE_WET] = "WET",
+ [NSC_TIME_ZONE_WAT] = "WAT",
+ [NSC_TIME_ZONE_AST] = "AST",
+ [NSC_TIME_ZONE_EST] = "EST",
+ [NSC_TIME_ZONE_CST] = "CST",
+ [NSC_TIME_ZONE_MST] = "MST",
+ [NSC_TIME_ZONE_PST] = "PST",
+ [NSC_TIME_ZONE_YST] = "YST",
+ [NSC_TIME_ZONE_AHST] = "AHST",
+ [NSC_TIME_ZONE_CAT] = "CAT",
+ [NSC_TIME_ZONE_HST] = "HST",
+ [NSC_TIME_ZONE_IDLW] = "IDLW",
+ [NSC_TIME_ZONE_NZDT] = "NZDT",
+ [NSC_TIME_ZONE_EADT] = "EADT",
+ [NSC_TIME_ZONE_WADT] = "WADT",
+ [NSC_TIME_ZONE_CEST] = "CEST",
+ [NSC_TIME_ZONE_FST] = "FST",
+ [NSC_TIME_ZONE_MEST] = "MEST",
+ [NSC_TIME_ZONE_MESZ] = "MESZ",
+ [NSC_TIME_ZONE_BST] = "BST",
+ [NSC_TIME_ZONE_ADT] = "ADT",
+ [NSC_TIME_ZONE_EDT] = "EDT",
+ [NSC_TIME_ZONE_CDT] = "CDT",
+ [NSC_TIME_ZONE_MDT] = "MDT",
+ [NSC_TIME_ZONE_PDT] = "PDT",
+ [NSC_TIME_ZONE_YDT] = "YDT",
+ [NSC_TIME_ZONE_HDT] = "HDT",
+ [NSC_TIME_ZONE_Y] = "Y",
+ [NSC_TIME_ZONE_X] = "X",
+ [NSC_TIME_ZONE_W] = "W",
+ [NSC_TIME_ZONE_V] = "V",
+ [NSC_TIME_ZONE_U] = "U",
+ [NSC_TIME_ZONE_T] = "T",
+ [NSC_TIME_ZONE_S] = "S",
+ [NSC_TIME_ZONE_R] = "R",
+ [NSC_TIME_ZONE_Q] = "Q",
+ [NSC_TIME_ZONE_P] = "P",
+ [NSC_TIME_ZONE_O] = "O",
+ [NSC_TIME_ZONE_N] = "N",
+ [NSC_TIME_ZONE_Z] = "Z",
+ [NSC_TIME_ZONE_A] = "A",
+ [NSC_TIME_ZONE_B] = "B",
+ [NSC_TIME_ZONE_C] = "C",
+ [NSC_TIME_ZONE_D] = "D",
+ [NSC_TIME_ZONE_E] = "E",
+ [NSC_TIME_ZONE_F] = "F",
+ [NSC_TIME_ZONE_G] = "G",
+ [NSC_TIME_ZONE_H] = "H",
+ [NSC_TIME_ZONE_I] = "I",
+ [NSC_TIME_ZONE_K] = "K",
+ [NSC_TIME_ZONE_L] = "L",
+ [NSC_TIME_ZONE_M] = "M"
+};
+
+/**
+ * Flags for tracking the components of a date that have been parsed.
+ */
+enum nsc_date_component_flags {
+ NSC_COMPONENT_FLAGS_NONE = 0,
+ NSC_COMPONENT_FLAGS_HAVE_YEARS = (1 << 0),
+ NSC_COMPONENT_FLAGS_HAVE_MONTHS = (1 << 1),
+ NSC_COMPONENT_FLAGS_HAVE_DAYS = (1 << 2),
+ NSC_COMPONENT_FLAGS_HAVE_HOURS = (1 << 3),
+ NSC_COMPONENT_FLAGS_HAVE_MINS = (1 << 4),
+ NSC_COMPONENT_FLAGS_HAVE_SECS = (1 << 5),
+ NSC_COMPONENT_FLAGS_HAVE_TIMEZONE = (1 << 6),
+ NSC_COMPONENT_FLAGS_HAVE_WEEKDAY = (1 << 7),
+ NSC_COMPONENT_FLAGS__HAVE_YYYYMMDD =
+ NSC_COMPONENT_FLAGS_HAVE_YEARS |
+ NSC_COMPONENT_FLAGS_HAVE_MONTHS |
+ NSC_COMPONENT_FLAGS_HAVE_DAYS,
+ NSC_COMPONENT_FLAGS__HAVE_HHMMSS =
+ NSC_COMPONENT_FLAGS_HAVE_HOURS |
+ NSC_COMPONENT_FLAGS_HAVE_MINS |
+ NSC_COMPONENT_FLAGS_HAVE_SECS,
+ NSC_COMPONENT_FLAGS__HAVE_ALL =
+ NSC_COMPONENT_FLAGS_HAVE_YEARS |
+ NSC_COMPONENT_FLAGS_HAVE_MONTHS |
+ NSC_COMPONENT_FLAGS_HAVE_DAYS |
+ NSC_COMPONENT_FLAGS_HAVE_HOURS |
+ NSC_COMPONENT_FLAGS_HAVE_MINS |
+ NSC_COMPONENT_FLAGS_HAVE_SECS |
+ NSC_COMPONENT_FLAGS_HAVE_TIMEZONE
+};
+
+/**
+ * Context for date parsing.
+ */
+struct nsc_date_parse_ctx {
+ char prev; /**< Used for handling neumenrical timezone */
+ uint8_t secs;
+ uint8_t mins;
+ uint8_t hours;
+ uint8_t day;
+ uint8_t month;
+ uint16_t years;
+ int16_t timezone_offset_mins;
+};
+
+
+/**
+ * Helper for testing whether any of the flags in mask are set.
+ *
+ * \param[in] flags Flags to to check.
+ * \param[in] mask The set of flags to check for in `flags`.
+ * \return true iff any flags in `mask` are set in `flags`, else false.
+ */
+static bool flags_chk(
+ enum nsc_date_component_flags flags,
+ enum nsc_date_component_flags mask)
+{
+ return flags & mask;
+}
+
+/**
+ * Helper for testing whether all of the flags in mask are set.
+ *
+ * \param[in] flags Flags to to check.
+ * \param[in] mask The set of flags to check for in `flags`.
+ * \return true iff all flags in `mask` are set in `flags`, else false.
+ */
+static bool flags_chk_all(
+ enum nsc_date_component_flags flags,
+ enum nsc_date_component_flags mask)
+{
+ return (flags & mask) == mask;
+}
+
+
+/**
+ * Test for a weekday name in a string (case insensitive).
+ *
+ * \param[in] str String to parse a weekday name in.
+ * \param[in] len Number of consecutive alphabetical characters.
+ * \param[in,out] flags Flags indicating which date components have been
+ * found in `str` already. If a weekday component
+ * is found, the weekday flag is set.
+ * \return true iff weekday component is found, else false.
+ */
+static inline bool time__parse_weekday(
+ const char *str,
+ size_t len,
+ enum nsc_date_component_flags *flags)
+{
+ const char * const *names = (len == 3) ?
+ weekdays_short : weekdays_long;
+
+ if (flags_chk(*flags, NSC_COMPONENT_FLAGS_HAVE_WEEKDAY)) {
+ return false;
+ }
+
+ for (uint32_t i = 0; i < NSC_TIME_WEEKDAY__COUNT; i++) {
+ if (ascii_strings_count_equal_caseless(names[i], str) == len) {
+ *flags |= NSC_COMPONENT_FLAGS_HAVE_WEEKDAY;
+ return true;
+ }
+ }
+
+ return false;
+}
+
+
+/**
+ * Attempt to parse a month name in a string (case insensitive).
+ *
+ * \param[in] str String to parse a month name in.
+ * \param[in] len Number of consecutive alphabetical characters.
+ * \param[in,out] flags Flags indicating which date components have been
+ * found in `str` already. If a month component
+ * is found, the month flag is set.
+ * \param[in,out] ctx Current date parsing context. If month component
+ * is found, the context's month value is set.
+ * \return true iff month name component is found, else false.
+ */
+static inline bool time__parse_month(
+ const char *str,
+ size_t len,
+ enum nsc_date_component_flags *flags,
+ struct nsc_date_parse_ctx *ctx)
+{
+ if (flags_chk(*flags, NSC_COMPONENT_FLAGS_HAVE_MONTHS)) {
+ return false;
+ }
+
+ for (uint32_t i = 0; i < NSC_TIME_MONTH__COUNT; i++) {
+ if (ascii_strings_count_equal_caseless(months[i], str) == len) {
+ *flags |= NSC_COMPONENT_FLAGS_HAVE_MONTHS;
+ ctx->month = i;
+ return true;
+ }
+ }
+
+ return false;
+}
+
+
+/**
+ * Attempt to parse a timezone name in a string (case insensitive).
+ *
+ * \param[in] str String to parse a timezone name in.
+ * \param[in] len Number of consecutive alphabetical characters.
+ * \param[in,out] flags Flags indicating which date components have been
+ * found in `str` already. If a timezone component
+ * is found, the timezone flag is set.
+ * \param[in,out] ctx Current date parsing context. If timezone component
+ * is found, the context's timezone value is set.
+ * \return true iff timezone name component is found, else false.
+ */
+static inline bool time__parse_timezone(
+ const char *str,
+ size_t len,
+ enum nsc_date_component_flags *flags,
+ struct nsc_date_parse_ctx *ctx)
+{
+ if (flags_chk(*flags, NSC_COMPONENT_FLAGS_HAVE_TIMEZONE)) {
+ return false;
+ }
+
+ for (uint32_t i = 0; i < NSC_TIME_ZONE__COUNT; i++) {
+ if (ascii_strings_count_equal_caseless(
+ timezones[i], str) == len) {
+ *flags |= NSC_COMPONENT_FLAGS_HAVE_TIMEZONE;
+ ctx->timezone_offset_mins = timezone_mins[i];
+ return true;
+ }
+ }
+
+ return false;
+}
+
+
+/**
+ * Attempt to parse an "hh:mm:ss" time from a string.
+ *
+ * \param[in] str String to parse a time in.
+ * \param[in,out] len The number of characters until the first non-digit.
+ * Iff a time component is found, updated to the number
+ * of comsumend characters.
+ * \param[in,out] flags Flags indicating which date components have been
+ * found in `str` already. If a time component
+ * is found, the hours, mins and secs flags are set.
+ * \param[in,out] ctx Current date parsing context. If time component
+ * is found, the context's time values are set.
+ * \return true iff time component is found, else false.
+ */
+static inline bool time__parse_hh_mm_ss(
+ const char *str,
+ size_t *len,
+ enum nsc_date_component_flags *flags,
+ struct nsc_date_parse_ctx *ctx)
+{
+ size_t l;
+
+ if (*len != 2 || flags_chk(*flags, NSC_COMPONENT_FLAGS__HAVE_HHMMSS)) {
+ return false;
+ }
+
+ l = *len + ascii_count_digit_or_colon(str + *len);
+ if (l == 8) {
+ int h, m, s, count;
+ count = sscanf(str, "%02d:%02d:%02d", &h, &m, &s);
+ if (count == 3) {
+ ctx->hours = h;
+ ctx->mins = m;
+ ctx->secs = s;
+ *flags |= NSC_COMPONENT_FLAGS__HAVE_HHMMSS;
+ *len = l;
+ return true;
+ }
+ } else if (l == 5) {
+ int h, m, count;
+ count = sscanf(str, "%02d:%02d", &h, &m);
+ if (count == 2) {
+ ctx->hours = h;
+ ctx->mins = m;
+ ctx->secs = 0;
+ *flags |= NSC_COMPONENT_FLAGS__HAVE_HHMMSS;
+ *len = l;
+ return true;
+ }
+ }
+
+ return false;
+}
+
+
+/**
+ * Attempt to parse a number from a date string.
+ *
+ * How the number is treated depends on various things:
+ *
+ * - its character length,
+ * - its value,
+ * - which date components have already been parsed
+ *
+ * \param[in] str String to parse a time in.
+ * \param[in] len The number of characters until the first non-digit.
+ * \param[in,out] flags Flags indicating which date components have been
+ * found in `str` already. If any component is found,
+ * their flags are set.
+ * \param[in,out] ctx Current date parsing context. If any component
+ * is found, the appropriate context values are set.
+ * \return true iff a component is found, else false.
+ */
+static inline bool time__parse_number(
+ const char *str,
+ size_t len,
+ enum nsc_date_component_flags *flags,
+ struct nsc_date_parse_ctx *ctx)
+{
+ int value;
+
+ if (len != ascii_string_to_int(str, &value)) {
+ return false;
+ }
+
+ switch (len) {
+ case 8:
+ if (!flags_chk(*flags, NSC_COMPONENT_FLAGS__HAVE_YYYYMMDD)) {
+ ctx->years = value / 10000;
+ ctx->month = (value % 10000) / 100 - 1;
+ ctx->day = value % 100 - 1;
+ *flags |= NSC_COMPONENT_FLAGS__HAVE_YYYYMMDD;
+ return true;
+ }
+ break;
+
+ case 4:
+ if (!flags_chk(*flags, NSC_COMPONENT_FLAGS_HAVE_TIMEZONE)) {
+ if (ascii_is_sign(ctx->prev) && value <= 1400) {
+ ctx->timezone_offset_mins =
+ value / 100 * 60 +
+ value % 100;
+ if (ctx->prev == '+') {
+ ctx->timezone_offset_mins *= -1;
+ }
+ *flags |= NSC_COMPONENT_FLAGS_HAVE_TIMEZONE;
+ return true;
+ }
+ }
+ if (!flags_chk(*flags, NSC_COMPONENT_FLAGS_HAVE_YEARS)) {
+ ctx->years = value;
+ *flags |= NSC_COMPONENT_FLAGS_HAVE_YEARS;
+ return true;
+ }
+ break;
+
+ case 2:
+ case 1:
+ if (!flags_chk(*flags, NSC_COMPONENT_FLAGS_HAVE_DAYS) &&
+ value > 0 && value <= 31) {
+ ctx->day = value - 1;
+ *flags |= NSC_COMPONENT_FLAGS_HAVE_DAYS;
+ return true;
+ }
+ if (!flags_chk(*flags, NSC_COMPONENT_FLAGS_HAVE_YEARS)) {
+ ctx->years = (value > 70) ?
+ value + 1900 :
+ value + 2000;
+ *flags |= NSC_COMPONENT_FLAGS_HAVE_YEARS;
+ return true;
+ }
+ break;
+
+ default:
+ break;
+ }
+
+ return false;
+}
+
+/**
+ * Get number of leap days up until end of given year.
+ *
+ * \param[in] year Year to count leap years up to.
+ * \return Number of leap days up to end of `year`.
+ */
+static inline int time__get_leap_days(int year)
+{
+ return (year / 4) - (year / 100) + (year / 400);
+}
+
+
+/**
+ * Helper to convert a date string context to a time_t.
+ *
+ * \param[in] ctx Current date parsing context.
+ * \param[in] flags Flags indicating which date components have been set.
+ * \param[out] time Returns the number of seconds since 1 Jan 1970 00:00 UTC.
+ * \return NSERROR_OK on success, appropriate error otherwise.
+ */
+static nserror time__ctx_to_time_t(
+ const struct nsc_date_parse_ctx *ctx,
+ enum nsc_date_component_flags flags,
+ time_t *time)
+{
+ enum {
+ NSC_MONTH_DAYS_JAN = 31,
+ NSC_MONTH_DAYS_FEB = 28, /**< Leap years handled separatly */
+ NSC_MONTH_DAYS_MAR = 31,
+ NSC_MONTH_DAYS_APR = 30,
+ NSC_MONTH_DAYS_MAY = 31,
+ NSC_MONTH_DAYS_JUN = 30,
+ NSC_MONTH_DAYS_JUL = 31,
+ NSC_MONTH_DAYS_AUG = 31,
+ NSC_MONTH_DAYS_SEP = 30,
+ NSC_MONTH_DAYS_OCT = 31,
+ NSC_MONTH_DAYS_NOV = 30,
+ NSC_MONTH_DAYS_DEC = 31
+ };
+ enum {
+ NSC_MONTH_OFF_JAN = 0,
+ NSC_MONTH_OFF_FEB = NSC_MONTH_OFF_JAN + NSC_MONTH_DAYS_JAN,
+ NSC_MONTH_OFF_MAR = NSC_MONTH_OFF_FEB + NSC_MONTH_DAYS_FEB,
+ NSC_MONTH_OFF_APR = NSC_MONTH_OFF_MAR + NSC_MONTH_DAYS_MAR,
+ NSC_MONTH_OFF_MAY = NSC_MONTH_OFF_APR + NSC_MONTH_DAYS_APR,
+ NSC_MONTH_OFF_JUN = NSC_MONTH_OFF_MAY + NSC_MONTH_DAYS_MAY,
+ NSC_MONTH_OFF_JUL = NSC_MONTH_OFF_JUN + NSC_MONTH_DAYS_JUN,
+ NSC_MONTH_OFF_AUG = NSC_MONTH_OFF_JUL + NSC_MONTH_DAYS_JUL,
+ NSC_MONTH_OFF_SEP = NSC_MONTH_OFF_AUG + NSC_MONTH_DAYS_AUG,
+ NSC_MONTH_OFF_OCT = NSC_MONTH_OFF_SEP + NSC_MONTH_DAYS_SEP,
+ NSC_MONTH_OFF_NOV = NSC_MONTH_OFF_OCT + NSC_MONTH_DAYS_OCT,
+ NSC_MONTH_OFF_DEC = NSC_MONTH_OFF_NOV + NSC_MONTH_DAYS_NOV
+ };
+ static const uint16_t month_offsets[NSC_TIME_MONTH__COUNT] = {
+ [NSC_TIME_MONTH_JAN] = NSC_MONTH_OFF_JAN,
+ [NSC_TIME_MONTH_FEB] = NSC_MONTH_OFF_FEB,
+ [NSC_TIME_MONTH_MAR] = NSC_MONTH_OFF_MAR,
+ [NSC_TIME_MONTH_APR] = NSC_MONTH_OFF_APR,
+ [NSC_TIME_MONTH_MAY] = NSC_MONTH_OFF_MAY,
+ [NSC_TIME_MONTH_JUN] = NSC_MONTH_OFF_JUN,
+ [NSC_TIME_MONTH_JUL] = NSC_MONTH_OFF_JUL,
+ [NSC_TIME_MONTH_AUG] = NSC_MONTH_OFF_AUG,
+ [NSC_TIME_MONTH_SEP] = NSC_MONTH_OFF_SEP,
+ [NSC_TIME_MONTH_OCT] = NSC_MONTH_OFF_OCT,
+ [NSC_TIME_MONTH_NOV] = NSC_MONTH_OFF_NOV,
+ [NSC_TIME_MONTH_DEC] = NSC_MONTH_OFF_DEC
+ };
+ int year_days = (ctx->years - 1970) * 365;
+ int month_days = month_offsets[ctx->month];
+ int year = (ctx->month < NSC_TIME_MONTH_FEB) ?
+ ctx->years - 1 : ctx->years;
+ int leap_days = time__get_leap_days(year) - time__get_leap_days(1969);
+ int total_days = year_days + month_days + ctx->day + leap_days;
+
+ int mins = (int)ctx->mins + (int)ctx->timezone_offset_mins;
+
+ *time = (((((time_t)(total_days)) * 24) +
+ ctx->hours) * 60 +
+ mins) * 60 +
+ ctx->secs;
+ return NSERROR_OK;
+}
+
+
+/**
+ * Parse a date string to a `time_t`.
+ *
+ * \param[in] str String to parse.
+ * \param[out] time Returns the number of seconds since 1 Jan 1970 00:00 UTC.
+ * \return `NSERROR_OK` on success, else
+ * `NSERROR_INVALID` if the string parsing failed,
+ * appropriate error otherwise.
+ */
+static nserror time__get_date(const char *str, time_t *time)
+{
+ enum nsc_date_component_flags flags = NSC_COMPONENT_FLAGS_NONE;
+ struct nsc_date_parse_ctx ctx = {
+ .prev = '\0',
+ .secs = 0,
+ .mins = 0,
+ .hours = 0,
+ .day = 0,
+ .month = 0,
+ .years = 0,
+ .timezone_offset_mins = 0
+ };
+
+ if (str == NULL || time == NULL) {
+ return NSERROR_BAD_PARAMETER;
+ }
+
+ /* Parse */
+ while (*str != '\0' &&
+ !flags_chk_all(flags, NSC_COMPONENT_FLAGS__HAVE_ALL)) {
+ size_t len = 1;
+
+ if (ascii_is_alpha(*str)) {
+ len += ascii_count_alpha(str + 1);
+
+ if (!time__parse_weekday(str, len, &flags) &&
+ !time__parse_month(str, len, &flags, &ctx) &&
+ !time__parse_timezone(str, len, &flags, &ctx)) {
+ return NSERROR_INVALID;
+ }
+
+ } else if (ascii_is_digit(*str)) {
+ len += ascii_count_digit(str + 1);
+
+ if (!time__parse_hh_mm_ss(str, &len, &flags, &ctx) &&
+ !time__parse_number(str, len, &flags, &ctx)) {
+ return NSERROR_INVALID;
+ }
+ }
+ ctx.prev = *str;
+ str += len;
+ }
+
+ /* The initial values of 0 are used if hours, mins, secs, and timezone
+ * are not found */
+ flags |= NSC_COMPONENT_FLAGS__HAVE_HHMMSS;
+ flags |= NSC_COMPONENT_FLAGS_HAVE_TIMEZONE;
+
+ /* Validate */
+ if (!flags_chk_all(flags, NSC_COMPONENT_FLAGS__HAVE_ALL)) {
+ return NSERROR_INVALID;
+ }
+ if (ctx.secs > 60 || ctx.mins > 59 || ctx.hours > 23 ||
+ ctx.day > 31 || ctx.month > 11) {
+ return NSERROR_INVALID;
+ }
+
+ /* Convert */
+ return time__ctx_to_time_t(&ctx, flags, time);
+}
+
+/* exported function documented in utils/time.h */
+nserror nsc_strntimet(const char *str, size_t size, time_t *timep)
+{
+ return time__get_date(str, timep);
+}
+
+# else
+
/* exported function documented in utils/time.h */
nserror nsc_strntimet(const char *str, size_t size, time_t *timep)
{
time_t result;
+ if (str == NULL || timep == NULL) {
+ return NSERROR_BAD_PARAMETER;
+ }
+
result = curl_getdate(str, NULL);
if (result == -1) {
@@ -130,3 +1001,5 @@ nserror nsc_strntimet(const char *str, size_t size, time_t *timep)
return NSERROR_OK;
}
+
+#endif
diff --git a/utils/url.c b/utils/url.c
index 7a7b7a196..ee8485040 100644
--- a/utils/url.c
+++ b/utils/url.c
@@ -35,7 +35,9 @@
#include <assert.h>
#include <string.h>
#include <stdlib.h>
+#include <stdbool.h>
+#include "utils/ascii.h"
#include "utils/config.h"
#include "utils/log.h"
#include "utils/url.h"
@@ -92,7 +94,7 @@ nserror url_unescape(const char *str, size_t length,
char c1 = *(str + 1);
char c2 = *(str + 2);
- if (c == '%' && isxdigit(c1) && isxdigit(c2)) {
+ if (c == '%' && ascii_is_hex(c1) && ascii_is_hex(c2)) {
c = xdigit_to_hex(c1) << 4 | xdigit_to_hex(c2);
str += 2;
}