summaryrefslogtreecommitdiffstats
path: root/common/gettime.c
diff options
context:
space:
mode:
Diffstat (limited to 'common/gettime.c')
-rw-r--r--common/gettime.c264
1 files changed, 193 insertions, 71 deletions
diff --git a/common/gettime.c b/common/gettime.c
index 3fe30ce..d24a4b1 100644
--- a/common/gettime.c
+++ b/common/gettime.c
@@ -37,6 +37,11 @@
#ifdef HAVE_LANGINFO_H
#include <langinfo.h>
#endif
+#ifdef HAVE_W32_SYSTEM
+# define WIN32_LEAN_AND_MEAN
+# include <windows.h>
+#endif /*!HAVE_W32_SYSTEM*/
+#include <stdint.h> /* We use uint64_t. */
#include "util.h"
#include "i18n.h"
@@ -57,6 +62,111 @@ static enum { NORMAL = 0, FROZEN, FUTURE, PAST } timemode;
#define JD_DIFF 1721060L
+
+/*
+ timegm() is a GNU function that might not be available everywhere.
+ It's basically the inverse of gmtime() - you give it a struct tm,
+ and get back a time_t. It differs from mktime() in that it handles
+ the case where the struct tm is UTC and the local environment isn't.
+
+ Note, that this replacement implementation might not be thread-safe!
+
+ Some BSDs don't handle the putenv("foo") case properly, so we use
+ unsetenv if the platform has it to remove environment variables.
+*/
+#ifndef HAVE_TIMEGM
+time_t
+timegm (struct tm *tm)
+{
+#ifdef HAVE_W32_SYSTEM
+ uint64_t val = timegm_u64 (tm);
+ if (val == (uint64_t)(-1))
+ return (time_t)(-1);
+ return (time_t)val;
+#else /* (Non thread safe implementation!) */
+
+ time_t answer;
+ char *zone;
+
+ zone=getenv("TZ");
+ putenv("TZ=UTC");
+ tzset();
+ answer=mktime(tm);
+ if(zone)
+ {
+ static char *old_zone;
+
+ if (!old_zone)
+ {
+ old_zone = malloc(3+strlen(zone)+1);
+ if (old_zone)
+ {
+ strcpy(old_zone,"TZ=");
+ strcat(old_zone,zone);
+ }
+ }
+ if (old_zone)
+ putenv (old_zone);
+ }
+ else
+ gnupg_unsetenv("TZ");
+
+ tzset();
+ return answer;
+#endif
+}
+#endif /*!HAVE_TIMEGM*/
+
+
+/* Version of the GNU timegm which returns an unsigned 64 bit integer
+ * instead of the usually signed time_t. On error (uint64_t)(-1) is
+ * returned. This function is mostly here becuase on 32 bit Windows
+ * we have an internal API to get the system time even after
+ * 2023-01-19. For 32 bit Unix we need to suffer from the too short
+ * time_t and no system function to construct the time from a tm. */
+uint64_t
+timegm_u64 (struct tm *tm)
+{
+#ifdef HAVE_W32_SYSTEM
+ /* This one is thread safe. */
+ SYSTEMTIME st;
+ FILETIME ft;
+ unsigned long long cnsecs;
+
+ st.wYear = tm->tm_year + 1900;
+ st.wMonth = tm->tm_mon + 1;
+ st.wDay = tm->tm_mday;
+ st.wHour = tm->tm_hour;
+ st.wMinute = tm->tm_min;
+ st.wSecond = tm->tm_sec;
+ st.wMilliseconds = 0; /* Not available. */
+ st.wDayOfWeek = 0; /* Ignored. */
+
+ /* System time is UTC thus the conversion is pretty easy. */
+ if (!SystemTimeToFileTime (&st, &ft))
+ {
+ gpg_err_set_errno (EINVAL);
+ return (uint64_t)(-1);
+ }
+
+ cnsecs = (((unsigned long long)ft.dwHighDateTime << 32)
+ | ft.dwLowDateTime);
+ cnsecs -= 116444736000000000ULL; /* The filetime epoch is 1601-01-01. */
+ return (uint64_t)(cnsecs / 10000000ULL);
+
+#else /*Unix*/
+
+ time_t t = timegm (tm);
+ if (t == (time_t)(-1))
+ return (uint64_t)(-1);
+ if ((int64_t)t < 0)
+ return (uint64_t)(-1);
+ return (uint64_t)t;
+
+#endif /*Unix*/
+}
+
+
/* Wrapper for the time(3). We use this here so we can fake the time
for tests */
time_t
@@ -168,6 +278,28 @@ make_timestamp (void)
}
+/* Specialized version of atoi which returns an u32 instead of an int
+ * and caps the result at 2^32-2. Leading white space is skipped,
+ * scanning stops at at the first non-convertable byte. Note that we
+ * do not cap at 2^32-1 because that value is often used as error
+ * return. */
+u32
+scan_secondsstr (const char *string)
+{
+ uint64_t value = 0;
+
+ while (*string == ' ' || *string == '\t')
+ string++;
+ for (; *string >= '0' && *string <= '9'; string++)
+ {
+ value *= 10;
+ value += atoi_1 (string);
+ if (value >= (u32)(-1))
+ return (u32)(-1) - 1;
+ }
+ return (u32)value;
+}
+
/****************
* Scan a date string and return a timestamp.
@@ -204,7 +336,21 @@ scan_isodatestr( const char *string )
tmbuf.tm_isdst = -1;
stamp = mktime( &tmbuf );
if( stamp == (time_t)-1 )
- return 0;
+ {
+ /* mktime did not work. Construct an ISO timestring for noon
+ * of the given day instead. We keep the use of mktime for 64
+ * bit system to limit the risk of regressions. */
+ gnupg_isotime_t isobuf;
+ uint64_t tmp64;
+
+ snprintf (isobuf, 16, "%04d%02d%02dT120000", year, month, day);
+ tmp64 = isotime2epoch_u64 (isobuf);
+ if (tmp64 == (uint64_t)(-1))
+ return 0; /* Error. */
+ if (tmp64 >= (u32)(-1))
+ return 0; /* Error. */
+ return (u32)tmp64;
+ }
return stamp;
}
@@ -359,18 +505,14 @@ string2isotime (gnupg_isotime_t atime, const char *string)
}
-/* Scan an ISO timestamp and return an Epoch based timestamp. The
- only supported format is "yyyymmddThhmmss[Z]" delimited by white
- space, nul, a colon or a comma. Returns (time_t)(-1) for an
- invalid string. */
-time_t
-isotime2epoch (const char *string)
+/* Helper for isotime2epoch. Returns 0 on success. */
+static int
+isotime_make_tm (const char *string, struct tm *tmbuf)
{
int year, month, day, hour, minu, sec;
- struct tm tmbuf;
if (!isotime_p (string))
- return (time_t)(-1);
+ return -1;
year = atoi_4 (string);
month = atoi_2 (string + 4);
@@ -382,20 +524,48 @@ isotime2epoch (const char *string)
/* Basic checks. */
if (year < 1970 || month < 1 || month > 12 || day < 1 || day > 31
|| hour > 23 || minu > 59 || sec > 61 )
+ return -1;
+
+ memset (tmbuf, 0, sizeof *tmbuf);
+ tmbuf->tm_sec = sec;
+ tmbuf->tm_min = minu;
+ tmbuf->tm_hour = hour;
+ tmbuf->tm_mday = day;
+ tmbuf->tm_mon = month-1;
+ tmbuf->tm_year = year - 1900;
+ tmbuf->tm_isdst = -1;
+ return 0;
+}
+
+
+/* Scan an ISO timestamp and return an Epoch based timestamp. The
+ only supported format is "yyyymmddThhmmss[Z]" delimited by white
+ space, nul, a colon or a comma. Returns (time_t)(-1) for an
+ invalid string. */
+time_t
+isotime2epoch (const char *string)
+{
+ struct tm tmbuf;
+
+ if (isotime_make_tm (string, &tmbuf))
return (time_t)(-1);
- memset (&tmbuf, 0, sizeof tmbuf);
- tmbuf.tm_sec = sec;
- tmbuf.tm_min = minu;
- tmbuf.tm_hour = hour;
- tmbuf.tm_mday = day;
- tmbuf.tm_mon = month-1;
- tmbuf.tm_year = year - 1900;
- tmbuf.tm_isdst = -1;
return timegm (&tmbuf);
}
+uint64_t
+isotime2epoch_u64 (const char *string)
+{
+ struct tm tmbuf;
+
+ if (isotime_make_tm (string, &tmbuf))
+ return (uint64_t)(-1);
+
+ return timegm_u64 (&tmbuf);
+}
+
+
/* Convert an Epoch time to an iso time stamp. */
void
epoch2isotime (gnupg_isotime_t timebuf, time_t atime)
@@ -449,41 +619,6 @@ isodate_human_to_tm (const char *string, struct tm *t)
}
-/* This function is a copy of gpgme/src/conversion.c:_gpgme_timegm.
- If you change it, then update the other one too. */
-#ifdef HAVE_W32_SYSTEM
-static time_t
-_win32_timegm (struct tm *tm)
-{
- /* This one is thread safe. */
- SYSTEMTIME st;
- FILETIME ft;
- unsigned long long cnsecs;
-
- st.wYear = tm->tm_year + 1900;
- st.wMonth = tm->tm_mon + 1;
- st.wDay = tm->tm_mday;
- st.wHour = tm->tm_hour;
- st.wMinute = tm->tm_min;
- st.wSecond = tm->tm_sec;
- st.wMilliseconds = 0; /* Not available. */
- st.wDayOfWeek = 0; /* Ignored. */
-
- /* System time is UTC thus the conversion is pretty easy. */
- if (!SystemTimeToFileTime (&st, &ft))
- {
- gpg_err_set_errno (EINVAL);
- return (time_t)(-1);
- }
-
- cnsecs = (((unsigned long long)ft.dwHighDateTime << 32)
- | ft.dwLowDateTime);
- cnsecs -= 116444736000000000ULL; /* The filetime epoch is 1601-01-01. */
- return (time_t)(cnsecs / 10000000ULL);
-}
-#endif
-
-
/* Parse the string TIMESTAMP into a time_t. The string may either be
seconds since Epoch or in the ISO 8601 format like
"20390815T143012". Returns 0 for an empty string or seconds since
@@ -492,7 +627,11 @@ _win32_timegm (struct tm *tm)
This function is a copy of
gpgme/src/conversion.c:_gpgme_parse_timestamp. If you change it,
- then update the other one too. */
+ then update the other one too.
+
+ FIXME: Replace users of this function by one of the more modern
+ functions or change the return type to u64.
+*/
time_t
parse_timestamp (const char *timestamp, char **endp)
{
@@ -528,24 +667,7 @@ parse_timestamp (const char *timestamp, char **endp)
buf.tm_min = atoi_2 (timestamp+11);
buf.tm_sec = atoi_2 (timestamp+13);
-#ifdef HAVE_W32_SYSTEM
- return _win32_timegm (&buf);
-#else
-#ifdef HAVE_TIMEGM
return timegm (&buf);
-#else
- {
- time_t tim;
-
- putenv ("TZ=UTC");
- tim = mktime (&buf);
-#ifdef __GNUC__
-#warning fixme: we must somehow reset TZ here. It is not threadsafe anyway.
-#endif
- return tim;
- }
-#endif /* !HAVE_TIMEGM */
-#endif /* !HAVE_W32_SYSTEM */
}
else
return (time_t)strtoul (timestamp, endp, 10);
@@ -768,7 +890,7 @@ asctimestamp (u32 stamp)
* 2018 has a lot of additional support but that will for sure
* break other things. We should move to ISO strings to get
* rid of such problems. */
- setlocale (LC_TIME, "");
+ setlocale (LC_TIME, ".UTF8");
done = 1;
/* log_debug ("LC_ALL now '%s'\n", setlocale (LC_ALL, NULL)); */
/* log_debug ("LC_TIME now '%s'\n", setlocale (LC_TIME, NULL)); */