From: Alan T. DeKok Date: Thu, 28 Oct 2021 12:35:29 +0000 (-0400) Subject: move fr_unix_time_from_str() to time.c X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=79a6605685ebe710f886e351ac750468a4ee554f;p=thirdparty%2Ffreeradius-server.git move fr_unix_time_from_str() to time.c --- diff --git a/src/lib/util/misc.c b/src/lib/util/misc.c index e0ec8642a7d..b35fcd33442 100644 --- a/src/lib/util/misc.c +++ b/src/lib/util/misc.c @@ -39,10 +39,6 @@ RCSID("$Id$") a[0] = ((uint16_t) (val)) & 0xff;\ } while (0) -static char const *months[] = { - "jan", "feb", "mar", "apr", "may", "jun", - "jul", "aug", "sep", "oct", "nov", "dec" }; - /** Sets a signal handler using sigaction if available, else signal * * @param sig to set handler for. @@ -495,373 +491,6 @@ size_t fr_snprint_uint128(char *out, size_t outlen, uint128_t const num) return strlcpy(out, p, outlen); } - -/* - * Sort of strtok/strsep function. - */ -static char *mystrtok(char **ptr, char const *sep) -{ - char *res; - - if (**ptr == '\0') return NULL; - - while (**ptr && strchr(sep, **ptr)) (*ptr)++; - - if (**ptr == '\0') return NULL; - - res = *ptr; - while (**ptr && strchr(sep, **ptr) == NULL) (*ptr)++; - - if (**ptr != '\0') *(*ptr)++ = '\0'; - - return res; -} - -/* - * Helper function to get a 2-digit date. With a maximum value, - * and a terminating character. - */ -static int get_part(char **str, int *date, int min, int max, char term, char const *name) -{ - char *p = *str; - - if (!isdigit((int) *p) || !isdigit((int) p[1])) return -1; - *date = (p[0] - '0') * 10 + (p[1] - '0'); - - if (*date < min) { - fr_strerror_printf("Invalid %s (too small)", name); - return -1; - } - - if (*date > max) { - fr_strerror_printf("Invalid %s (too large)", name); - return -1; - } - - p += 2; - if (!term) { - *str = p; - return 0; - } - - if (*p != term) { - fr_strerror_printf("Expected '%c' after %s, got '%c'", - term, name, *p); - return -1; - } - p++; - - *str = p; - return 0; -} - -/** Convert string in various formats to a fr_unix_time_t - * - * @param date_str input date string. - * @param date time_t to write result to. - * @param[in] hint scale for the parsing. Default is "seconds" - * @return - * - 0 on success. - * - -1 on failure. - */ -int fr_unix_time_from_str(fr_unix_time_t *date, char const *date_str, fr_time_res_t hint) -{ - int i; - int64_t tmp; - struct tm *tm, s_tm; - char buf[64]; - char *p; - char *f[4]; - char *tail = NULL; - fr_time_delta_t gmtoff = fr_time_delta_wrap(0); - - /* - * Test for unix timestamp, which is just a number and - * nothing else. - */ - tmp = strtoul(date_str, &tail, 10); - if (*tail == '\0') { - *date = fr_unix_time_from_nsec(fr_time_scale(tmp, hint)); - return 0; - } - - tm = &s_tm; - memset(tm, 0, sizeof(*tm)); - tm->tm_isdst = -1; /* don't know, and don't care about DST */ - - /* - * Check for RFC 3339 dates. Note that we only support - * dates in a ~1000 year period. If the server is being - * used after 3000AD, someone can patch it then. - * - * %Y-%m-%dT%H:%M:%S - * [.%d] sub-seconds - * Z | (+/-)%H:%M time zone offset - * - */ - if ((tmp > 1900) && (tmp < 3000) && *tail == '-') { - unsigned long subseconds; - int tz, tz_hour, tz_min; - - p = tail + 1; - s_tm.tm_year = tmp - 1900; /* 'struct tm' starts years in 1900 */ - - if (get_part(&p, &s_tm.tm_mon, 1, 13, '-', "month") < 0) return -1; - s_tm.tm_mon--; /* ISO is 1..12, where 'struct tm' is 0..11 */ - - if (get_part(&p, &s_tm.tm_mday, 1, 31, 'T', "day") < 0) return -1; - if (get_part(&p, &s_tm.tm_hour, 0, 23, ':', "hour") < 0) return -1; - if (get_part(&p, &s_tm.tm_min, 0, 59, ':', "minute") < 0) return -1; - if (get_part(&p, &s_tm.tm_sec, 0, 60, '\0', "seconds") < 0) return -1; - - if (*p == '.') { - p++; - subseconds = strtoul(p, &tail, 10); - if (subseconds > NSEC) { - fr_strerror_const("Invalid nanosecond specifier"); - return -1; - } - - /* - * Scale subseconds to nanoseconds by how - * many digits were parsed/ - */ - if ((tail - p) < 9) { - for (i = 0; i < 9 - (tail -p); i++) { - subseconds *= 10; - } - } - - p = tail; - } else { - subseconds = 0; - } - - /* - * Time zone is GMT. Leave well enough - * alone. - */ - if (*p == 'Z') { - if (p[1] != '\0') { - fr_strerror_printf("Unexpected text '%c' after time zone", p[1]); - return -1; - } - tz = 0; - goto done; - } - - if ((*p != '+') && (*p != '-')) { - fr_strerror_printf("Invalid time zone specifier '%c'", *p); - return -1; - } - tail = p; /* remember sign for later */ - p++; - - if (get_part(&p, &tz_hour, 0, 23, ':', "hour in time zone") < 0) return -1; - if (get_part(&p, &tz_min, 0, 59, '\0', "minute in time zone") < 0) return -1; - - if (*p != '\0') { - fr_strerror_printf("Unexpected text '%c' after time zone", *p); - return -1; - } - - /* - * We set the time zone, but the timegm() - * function ignores it. Note also that mktime() - * ignores it too, and treats the time zone as - * local. - * - * We can't store this value in s_tm.gtmoff, - * because the timegm() function helpfully zeros - * it out. - * - * So insyead of using stupid C library - * functions, we just roll our own. - */ - tz = tz_hour * 3600 + tz_min; - if (*tail == '-') tz *= -1; - - done: - tm->tm_gmtoff = tz; - *date = fr_unix_time_add(fr_unix_time_from_tm(tm), fr_time_delta_wrap(subseconds)); - return 0; - } - - /* - * Try to parse dates via locale-specific names, - * using the same format string as strftime(). - * - * If that fails, then we fall back to our parsing - * routine, which is much more forgiving. - */ - -#ifdef __APPLE__ - /* - * OSX "man strptime" says it only accepts the local time zone, and GMT. - * - * However, when printing dates via strftime(), it prints - * "UTC" instead of "GMT". So... we have to fix it up - * for stupid nonsense. - */ - { - char const *tz = strstr(date_str, "UTC"); - if (tz) { - char *my_str; - - my_str = talloc_strdup(NULL, date_str); - if (my_str) { - p = my_str + (tz - date_str); - memcpy(p, "GMT", 3); - - p = strptime(my_str, "%b %e %Y %H:%M:%S %Z", tm); - if (p && (*p == '\0')) { - talloc_free(my_str); - *date = fr_unix_time_from_tm(tm); - return 0; - } - talloc_free(my_str); - } - } - } -#endif - - p = strptime(date_str, "%b %e %Y %H:%M:%S %Z", tm); - if (p && (*p == '\0')) { - *date = fr_unix_time_from_tm(tm); - return 0; - } - - strlcpy(buf, date_str, sizeof(buf)); - - p = buf; - f[0] = mystrtok(&p, " \t"); - f[1] = mystrtok(&p, " \t"); - f[2] = mystrtok(&p, " \t"); - f[3] = mystrtok(&p, " \t"); /* may, or may not, be present */ - if (!f[0] || !f[1] || !f[2]) { - fr_strerror_const("Too few fields"); - return -1; - } - - /* - * Try to parse the time zone. If it's GMT / UTC or a - * local time zone we're OK. - * - * Otherwise, ignore errors and assume GMT. - */ - if (*p != '\0') { - fr_skip_whitespace(p); - (void) fr_time_delta_from_time_zone(p, &gmtoff); - } - - /* - * The time has a colon, where nothing else does. - * So if we find it, bubble it to the back of the list. - */ - if (f[3]) { - for (i = 0; i < 3; i++) { - if (strchr(f[i], ':')) { - p = f[3]; - f[3] = f[i]; - f[i] = p; - break; - } - } - } - - /* - * The month is text, which allows us to find it easily. - */ - tm->tm_mon = 12; - for (i = 0; i < 3; i++) { - if (isalpha((int) *f[i])) { - int j; - - /* - * Bubble the month to the front of the list - */ - p = f[0]; - f[0] = f[i]; - f[i] = p; - - for (j = 0; j < 12; j++) { - if (strncasecmp(months[j], f[0], 3) == 0) { - tm->tm_mon = j; - break; - } - } - } - } - - /* month not found? */ - if (tm->tm_mon == 12) { - fr_strerror_const("No month found"); - return -1; - } - - /* - * The year may be in f[1], or in f[2] - */ - tm->tm_year = atoi(f[1]); - tm->tm_mday = atoi(f[2]); - - if (tm->tm_year >= 1900) { - tm->tm_year -= 1900; - - } else { - /* - * We can't use 2-digit years any more, they make it - * impossible to tell what's the day, and what's the year. - */ - if (tm->tm_mday < 1900) { - fr_strerror_const("Invalid year < 1900"); - return -1; - } - - /* - * Swap the year and the day. - */ - i = tm->tm_year; - tm->tm_year = tm->tm_mday - 1900; - tm->tm_mday = i; - } - - /* - * If the day is out of range, die. - */ - if ((tm->tm_mday < 1) || (tm->tm_mday > 31)) { - fr_strerror_const("Invalid day of month"); - return -1; - } - - /* - * There may be %H:%M:%S. Parse it in a hacky way. - */ - if (f[3]) { - f[0] = f[3]; /* HH */ - f[1] = strchr(f[0], ':'); /* find : separator */ - if (!f[1]) { - fr_strerror_const("No ':' after hour"); - return -1; - } - - *(f[1]++) = '\0'; /* nuke it, and point to MM:SS */ - - f[2] = strchr(f[1], ':'); /* find : separator */ - if (f[2]) { - *(f[2]++) = '\0'; /* nuke it, and point to SS */ - tm->tm_sec = atoi(f[2]); - } /* else leave it as zero */ - - tm->tm_hour = atoi(f[0]); - tm->tm_min = atoi(f[1]); - } - - *date = fr_unix_time_add(fr_unix_time_from_tm(tm), gmtoff); - - return 0; -} - int fr_size_from_str(size_t *out, char const *str) { char *q = NULL; diff --git a/src/lib/util/misc.h b/src/lib/util/misc.h index 40bae2b87f8..dead5f8aece 100644 --- a/src/lib/util/misc.h +++ b/src/lib/util/misc.h @@ -199,7 +199,6 @@ int fr_blocking(int fd); ssize_t fr_writev(int fd, struct iovec vector[], int iovcnt, fr_time_delta_t timeout); ssize_t fr_utf8_to_ucs2(uint8_t *out, size_t outlen, char const *in, size_t inlen); size_t fr_snprint_uint128(char *out, size_t outlen, uint128_t const num); -int fr_unix_time_from_str(fr_unix_time_t *date, char const *date_str, fr_time_res_t hint); bool fr_multiply(uint64_t *result, uint64_t lhs, uint64_t rhs); uint64_t fr_multiply_mod(uint64_t lhs, uint64_t rhs, uint64_t mod); diff --git a/src/lib/util/time.c b/src/lib/util/time.c index 9e745cd585e..4378d7406a7 100644 --- a/src/lib/util/time.c +++ b/src/lib/util/time.c @@ -690,3 +690,375 @@ int64_t fr_time_scale(int64_t t, fr_time_res_t hint) return t * scale; } + + +/* + * Sort of strtok/strsep function. + */ +static char *mystrtok(char **ptr, char const *sep) +{ + char *res; + + if (**ptr == '\0') return NULL; + + while (**ptr && strchr(sep, **ptr)) (*ptr)++; + + if (**ptr == '\0') return NULL; + + res = *ptr; + while (**ptr && strchr(sep, **ptr) == NULL) (*ptr)++; + + if (**ptr != '\0') *(*ptr)++ = '\0'; + + return res; +} + +/* + * Helper function to get a 2-digit date. With a maximum value, + * and a terminating character. + */ +static int get_part(char **str, int *date, int min, int max, char term, char const *name) +{ + char *p = *str; + + if (!isdigit((int) *p) || !isdigit((int) p[1])) return -1; + *date = (p[0] - '0') * 10 + (p[1] - '0'); + + if (*date < min) { + fr_strerror_printf("Invalid %s (too small)", name); + return -1; + } + + if (*date > max) { + fr_strerror_printf("Invalid %s (too large)", name); + return -1; + } + + p += 2; + if (!term) { + *str = p; + return 0; + } + + if (*p != term) { + fr_strerror_printf("Expected '%c' after %s, got '%c'", + term, name, *p); + return -1; + } + p++; + + *str = p; + return 0; +} + +static char const *months[] = { + "jan", "feb", "mar", "apr", "may", "jun", + "jul", "aug", "sep", "oct", "nov", "dec" }; + + +/** Convert string in various formats to a fr_unix_time_t + * + * @param date_str input date string. + * @param date time_t to write result to. + * @param[in] hint scale for the parsing. Default is "seconds" + * @return + * - 0 on success. + * - -1 on failure. + */ +int fr_unix_time_from_str(fr_unix_time_t *date, char const *date_str, fr_time_res_t hint) +{ + int i; + int64_t tmp; + struct tm *tm, s_tm; + char buf[64]; + char *p; + char *f[4]; + char *tail = NULL; + fr_time_delta_t gmt_delta = fr_time_delta_wrap(0); + + /* + * Test for unix timestamp, which is just a number and + * nothing else. + */ + tmp = strtoul(date_str, &tail, 10); + if (*tail == '\0') { + *date = fr_unix_time_from_nsec(fr_time_scale(tmp, hint)); + return 0; + } + + tm = &s_tm; + memset(tm, 0, sizeof(*tm)); + tm->tm_isdst = -1; /* don't know, and don't care about DST */ + + /* + * Check for RFC 3339 dates. Note that we only support + * dates in a ~1000 year period. If the server is being + * used after 3000AD, someone can patch it then. + * + * %Y-%m-%dT%H:%M:%S + * [.%d] sub-seconds + * Z | (+/-)%H:%M time zone offset + * + */ + if ((tmp > 1900) && (tmp < 3000) && *tail == '-') { + unsigned long subseconds; + int tz, tz_hour, tz_min; + + p = tail + 1; + s_tm.tm_year = tmp - 1900; /* 'struct tm' starts years in 1900 */ + + if (get_part(&p, &s_tm.tm_mon, 1, 13, '-', "month") < 0) return -1; + s_tm.tm_mon--; /* ISO is 1..12, where 'struct tm' is 0..11 */ + + if (get_part(&p, &s_tm.tm_mday, 1, 31, 'T', "day") < 0) return -1; + if (get_part(&p, &s_tm.tm_hour, 0, 23, ':', "hour") < 0) return -1; + if (get_part(&p, &s_tm.tm_min, 0, 59, ':', "minute") < 0) return -1; + if (get_part(&p, &s_tm.tm_sec, 0, 60, '\0', "seconds") < 0) return -1; + + if (*p == '.') { + p++; + subseconds = strtoul(p, &tail, 10); + if (subseconds > NSEC) { + fr_strerror_const("Invalid nanosecond specifier"); + return -1; + } + + /* + * Scale subseconds to nanoseconds by how + * many digits were parsed/ + */ + if ((tail - p) < 9) { + for (i = 0; i < 9 - (tail -p); i++) { + subseconds *= 10; + } + } + + p = tail; + } else { + subseconds = 0; + } + + /* + * Time zone is GMT. Leave well enough + * alone. + */ + if (*p == 'Z') { + if (p[1] != '\0') { + fr_strerror_printf("Unexpected text '%c' after time zone", p[1]); + return -1; + } + tz = 0; + goto done; + } + + if ((*p != '+') && (*p != '-')) { + fr_strerror_printf("Invalid time zone specifier '%c'", *p); + return -1; + } + tail = p; /* remember sign for later */ + p++; + + if (get_part(&p, &tz_hour, 0, 23, ':', "hour in time zone") < 0) return -1; + if (get_part(&p, &tz_min, 0, 59, '\0', "minute in time zone") < 0) return -1; + + if (*p != '\0') { + fr_strerror_printf("Unexpected text '%c' after time zone", *p); + return -1; + } + + /* + * We set the time zone, but the timegm() + * function ignores it. Note also that mktime() + * ignores it too, and treats the time zone as + * local. + * + * We can't store this value in s_tm.gtmoff, + * because the timegm() function helpfully zeros + * it out. + * + * So insyead of using stupid C library + * functions, we just roll our own. + */ + tz = tz_hour * 3600 + tz_min; + if (*tail == '-') tz *= -1; + + done: + tm->tm_gmtoff = tz; + *date = fr_unix_time_add(fr_unix_time_from_tm(tm), fr_time_delta_wrap(subseconds)); + return 0; + } + + /* + * Try to parse dates via locale-specific names, + * using the same format string as strftime(). + * + * If that fails, then we fall back to our parsing + * routine, which is much more forgiving. + */ + +#ifdef __APPLE__ + /* + * OSX "man strptime" says it only accepts the local time zone, and GMT. + * + * However, when printing dates via strftime(), it prints + * "UTC" instead of "GMT". So... we have to fix it up + * for stupid nonsense. + */ + { + char const *tz = strstr(date_str, "UTC"); + if (tz) { + char *my_str; + + my_str = talloc_strdup(NULL, date_str); + if (my_str) { + p = my_str + (tz - date_str); + memcpy(p, "GMT", 3); + + p = strptime(my_str, "%b %e %Y %H:%M:%S %Z", tm); + if (p && (*p == '\0')) { + talloc_free(my_str); + *date = fr_unix_time_from_tm(tm); + return 0; + } + talloc_free(my_str); + } + } + } +#endif + + p = strptime(date_str, "%b %e %Y %H:%M:%S %Z", tm); + if (p && (*p == '\0')) { + *date = fr_unix_time_from_tm(tm); + return 0; + } + + strlcpy(buf, date_str, sizeof(buf)); + + p = buf; + f[0] = mystrtok(&p, " \t"); + f[1] = mystrtok(&p, " \t"); + f[2] = mystrtok(&p, " \t"); + f[3] = mystrtok(&p, " \t"); /* may, or may not, be present */ + if (!f[0] || !f[1] || !f[2]) { + fr_strerror_const("Too few fields"); + return -1; + } + + /* + * Try to parse the time zone. If it's GMT / UTC or a + * local time zone we're OK. + * + * Otherwise, ignore errors and assume GMT. + */ + if (*p != '\0') { + fr_skip_whitespace(p); + (void) fr_time_delta_from_time_zone(p, &gmt_delta); + } + + /* + * The time has a colon, where nothing else does. + * So if we find it, bubble it to the back of the list. + */ + if (f[3]) { + for (i = 0; i < 3; i++) { + if (strchr(f[i], ':')) { + p = f[3]; + f[3] = f[i]; + f[i] = p; + break; + } + } + } + + /* + * The month is text, which allows us to find it easily. + */ + tm->tm_mon = 12; + for (i = 0; i < 3; i++) { + if (isalpha((int) *f[i])) { + int j; + + /* + * Bubble the month to the front of the list + */ + p = f[0]; + f[0] = f[i]; + f[i] = p; + + for (j = 0; j < 12; j++) { + if (strncasecmp(months[j], f[0], 3) == 0) { + tm->tm_mon = j; + break; + } + } + } + } + + /* month not found? */ + if (tm->tm_mon == 12) { + fr_strerror_const("No month found"); + return -1; + } + + /* + * The year may be in f[1], or in f[2] + */ + tm->tm_year = atoi(f[1]); + tm->tm_mday = atoi(f[2]); + + if (tm->tm_year >= 1900) { + tm->tm_year -= 1900; + + } else { + /* + * We can't use 2-digit years any more, they make it + * impossible to tell what's the day, and what's the year. + */ + if (tm->tm_mday < 1900) { + fr_strerror_const("Invalid year < 1900"); + return -1; + } + + /* + * Swap the year and the day. + */ + i = tm->tm_year; + tm->tm_year = tm->tm_mday - 1900; + tm->tm_mday = i; + } + + /* + * If the day is out of range, die. + */ + if ((tm->tm_mday < 1) || (tm->tm_mday > 31)) { + fr_strerror_const("Invalid day of month"); + return -1; + } + + /* + * There may be %H:%M:%S. Parse it in a hacky way. + */ + if (f[3]) { + f[0] = f[3]; /* HH */ + f[1] = strchr(f[0], ':'); /* find : separator */ + if (!f[1]) { + fr_strerror_const("No ':' after hour"); + return -1; + } + + *(f[1]++) = '\0'; /* nuke it, and point to MM:SS */ + + f[2] = strchr(f[1], ':'); /* find : separator */ + if (f[2]) { + *(f[2]++) = '\0'; /* nuke it, and point to SS */ + tm->tm_sec = atoi(f[2]); + } /* else leave it as zero */ + + tm->tm_hour = atoi(f[0]); + tm->tm_min = atoi(f[1]); + } + + *date = fr_unix_time_add(fr_unix_time_from_tm(tm), gmt_delta); + + return 0; +} diff --git a/src/lib/util/time.h b/src/lib/util/time.h index 78e4b6edd33..3be05a24784 100644 --- a/src/lib/util/time.h +++ b/src/lib/util/time.h @@ -663,7 +663,9 @@ int64_t fr_time_delta_scale(fr_time_delta_t delta, fr_time_res_t hint); void fr_time_elapsed_update(fr_time_elapsed_t *elapsed, fr_time_t start, fr_time_t end) CC_HINT(nonnull); void fr_time_elapsed_fprint(FILE *fp, fr_time_elapsed_t const *elapsed, char const *prefix, int tabs) CC_HINT(nonnull(1,2)); + fr_unix_time_t fr_unix_time_from_tm(struct tm *tm) CC_HINT(nonnull); +int fr_unix_time_from_str(fr_unix_time_t *date, char const *date_str, fr_time_res_t hint); #ifdef __cplusplus }