Skip to content
Permalink
VERSION_0_12_1
Switch branches/tags
Go to file
 
 
Cannot retrieve contributors at this time
579 lines (479 sloc) 13.5 KB
/* $Id$ */
/*
* Copyright (c) 2016, 2017, 2020 Kristaps Dzonsons <kristaps@bsd.lv>
*
* Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
#include "config.h"
#include <assert.h> /* debug */
#include <inttypes.h>
#include <limits.h>
#include <stdarg.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include "kcgi.h"
#include "extern.h"
/*
* We'll never have khttp_epoch2str or khttp_epoch2ustr larger than
* this, even with 64-bit time.
*/
#define MAX_TIME_STRING 64
struct tm64 {
int64_t tm_sec; /* seconds after the minute [0-60] */
int64_t tm_min; /* minutes after the hour [0-59] */
int64_t tm_hour; /* hours since midnight [0-23] */
int64_t tm_mday; /* day of the month [1-31] */
int64_t tm_mon; /* months since January [0-11] */
int64_t tm_year; /* years since 1900 */
int64_t tm_wday; /* days since Sunday [0-6] */
int64_t tm_yday; /* days since January 1 [0-365] */
};
/*
* The following code is modified from newlib, the relevant parts being
* licensed as follows:
*
* Copyright (c) 1994-2009 Red Hat, Inc. All rights reserved.
*
* This copyrighted material is made available to anyone wishing to use,
* modify, copy, or redistribute it subject to the terms and conditions
* of the BSD License. This program is distributed in the hope that
* it will be useful, but WITHOUT ANY WARRANTY expressed or implied,
* including the implied warranties of MERCHANTABILITY or FITNESS FOR
* A PARTICULAR PURPOSE. A copy of this license is available at
* http://www.opensource.org/licenses. Any Red Hat trademarks that are
* incorporated in the source code or documentation are not subject to
* the BSD License and may only be used or replicated with the express
* permission of Red Hat, Inc.
*/
#define _SEC_IN_MINUTE 60L
#define _SEC_IN_HOUR 3600L
#define _SEC_IN_DAY 86400L
static const int DAYS_IN_MONTH[12] =
{31, 28, 31, 30, 31, 30,
31, 31, 30, 31, 30, 31};
#define _DAYS_IN_MONTH(x) \
((x == 1) ? days_in_feb : DAYS_IN_MONTH[x])
static const int _DAYS_BEFORE_MONTH[12] =
{0, 31, 59, 90, 120, 151,
181, 212, 243, 273, 304, 334};
#define _ISLEAP(y) \
(((y) % 4) == 0 && \
(((y) % 100) != 0 || (((y)+1900) % 400) == 0))
#define _DAYS_IN_YEAR(year) \
(_ISLEAP(year) ? 366 : 365)
/*
* There are 97 leap years in 400-year periods. ((400 - 97) * 365 + 97 *
* 366).
*/
#define DAYS_PER_ERA 146097L
/*
* Make sure that all values are sane.
* Return zero on failure, non-zero on success.
*/
static int
khttp_validate_time(const struct tm64 *tim_p)
{
int64_t days_in_feb = 28;
if (tim_p->tm_sec < 0 || tim_p->tm_sec > 59)
return 0;
if (tim_p->tm_min < 0 || tim_p->tm_min > 59)
return 0;
if (tim_p->tm_hour < 0 || tim_p->tm_hour > 23)
return 0;
if (tim_p->tm_mon < 0 || tim_p->tm_mon > 11)
return 0;
/*
* This magic number is (more or less) the maximum number of
* years that we can consider in a 64-bit year.
* Outside of this we'll get overflow or underflow.
*/
if (tim_p->tm_year > 292277026596 ||
tim_p->tm_year < -292277024557)
return 0;
if (_DAYS_IN_YEAR(tim_p->tm_year) == 366)
days_in_feb = 29;
if (tim_p->tm_mday <= 0 ||
tim_p->tm_mday > _DAYS_IN_MONTH(tim_p->tm_mon))
return 0;
return 1;
}
/*
* Convert broken-down time to the UNIX epoch.
* Returns zero if the broken-dwon values are not sane, non-zero
* otherwise.
* See khttp_validate_time().
*/
static int
khttp_mktime(int64_t *res, struct tm64 *tim_p)
{
int64_t tim = 0, days = 0, year, maxyear, era;
/* Validate structure. */
if (!khttp_validate_time(tim_p))
return 0;
/* Compute hours, minutes, seconds. */
tim += tim_p->tm_sec +
(tim_p->tm_min * _SEC_IN_MINUTE) +
(tim_p->tm_hour * _SEC_IN_HOUR);
/* Compute days in year. */
days += tim_p->tm_mday - 1;
days += _DAYS_BEFORE_MONTH[tim_p->tm_mon];
if (tim_p->tm_mon > 1 && _DAYS_IN_YEAR(tim_p->tm_year) == 366)
days++;
/* Compute day of the year. */
tim_p->tm_yday = days;
/* Compute days in other years. */
/* WARNING: THIS IS VERY SLOW. */
if ((year = tim_p->tm_year) > 70) {
maxyear = tim_p->tm_year > 400 ? 400 : tim_p->tm_year;
for (year = 70; year < maxyear; year++)
days += _DAYS_IN_YEAR(year);
era = (tim_p->tm_year - year) / 400;
days += era * DAYS_PER_ERA;
year += era * 400;
for ( ; year < tim_p->tm_year; year++)
days += _DAYS_IN_YEAR(year);
} else if (year < 70) {
maxyear = tim_p->tm_year < -400 ? -400 : tim_p->tm_year;
for (year = 69; year > maxyear; year--)
days -= _DAYS_IN_YEAR(year);
era = (tim_p->tm_year - year) / 400;
assert(era <= 0);
days += era * DAYS_PER_ERA;
year += era * 400;
for ( ; year > tim_p->tm_year; year--)
days -= _DAYS_IN_YEAR(year);
days -= _DAYS_IN_YEAR(year);
}
/* Compute total seconds. */
tim += days * _SEC_IN_DAY;
/* Compute day of the week. */
if ((tim_p->tm_wday = (days + 4) % 7) < 0)
tim_p->tm_wday += 7;
*res = tim;
return 1;
}
/*
* Move epoch from 01.01.1970 to 01.03.0000 (yes, Year 0) - this is the
* first day of a 400-year long "era", right after additional day of
* leap year. This adjustment is required only for date calculation, so
* instead of modifying time_t value (which would require 64-bit
* operations to work correctly) it's enough to adjust the calculated
* number of days since epoch.
*/
#define EPOCH_ADJUSTMENT_DAYS 719468L
/* Year to which the adjustment was made. */
#define ADJUSTED_EPOCH_YEAR 0
/* 1st March of year 0 is Wednesday. */
#define ADJUSTED_EPOCH_WDAY 3
/*
* There are 24 leap years in 100-year periods. ((100 - 24) * 365 + 24 *
* 366).
*/
#define DAYS_PER_CENTURY 36524L
/* There is one leap year every 4 years. */
#define DAYS_PER_4_YEARS (3 * 365 + 366)
/* Number of days in a non-leap year. */
#define DAYS_PER_YEAR 365
/* Number of days in January. */
#define DAYS_IN_JANUARY 31
/* Number of days in non-leap February. */
#define DAYS_IN_FEBRUARY 28
/* Number of years per era. */
#define YEARS_PER_ERA 400
/* Various constants. */
#define SECSPERMIN 60L
#define MINSPERHOUR 60L
#define HOURSPERDAY 24L
#define SECSPERHOUR (SECSPERMIN * MINSPERHOUR)
#define SECSPERDAY (SECSPERHOUR * HOURSPERDAY)
#define YEAR_BASE 1900
#define DAYSPERWEEK 7
#define MONSPERYEAR 12
/*
* This form of _ISLEAP doesn't adjust the year.
*/
#define _ISLEAP2(y) \
(((y) % 4) == 0 && \
(((y) % 100) != 0 || ((y) % 400) == 0))
/*
* Convert UNIX epoch to values in "res".
*/
static void
khttp_gmtime_r(int64_t lcltime, struct tm64 *res)
{
int64_t days, rem, era, weekday, year;
uint64_t erayear, yearday, month, day, eraday;
memset(res, 0, sizeof(struct tm64));
days = lcltime / SECSPERDAY + EPOCH_ADJUSTMENT_DAYS;
rem = lcltime % SECSPERDAY;
if (rem < 0) {
rem += SECSPERDAY;
--days;
}
/* Compute hour, min, and sec. */
res->tm_hour = (int64_t)(rem / SECSPERHOUR);
rem %= SECSPERHOUR;
res->tm_min = (int64_t)(rem / SECSPERMIN);
res->tm_sec = (int64_t)(rem % SECSPERMIN);
/* Compute day of week. */
if ((weekday = ((ADJUSTED_EPOCH_WDAY + days) % DAYSPERWEEK)) < 0)
weekday += DAYSPERWEEK;
res->tm_wday = weekday;
/*
* Compute year, month, day & day of year.
* For description of this algorithm see
* http://howardhinnant.github.io/date_algorithms.html#civil_from_days
*/
era = (days >= 0 ? days : days - (DAYS_PER_ERA - 1)) / DAYS_PER_ERA;
/* [0, 146096] */
eraday = days - era * DAYS_PER_ERA;
/* [0, 399] */
erayear = (eraday - eraday / (DAYS_PER_4_YEARS - 1) +
eraday / DAYS_PER_CENTURY - eraday /
(DAYS_PER_ERA - 1)) / 365;
/* [0, 365] */
yearday = eraday - (DAYS_PER_YEAR * erayear +
erayear / 4 - erayear / 100);
/* [0, 11] */
month = (5 * yearday + 2) / 153;
/* [1, 31] */
day = yearday - (153 * month + 2) / 5 + 1;
month += month < 10 ? 2 : -10;
year = ADJUSTED_EPOCH_YEAR + erayear +
era * YEARS_PER_ERA + (month <= 1);
res->tm_yday =
yearday >= DAYS_PER_YEAR -
DAYS_IN_JANUARY - DAYS_IN_FEBRUARY ?
yearday - (DAYS_PER_YEAR -
DAYS_IN_JANUARY - DAYS_IN_FEBRUARY) :
yearday + DAYS_IN_JANUARY +
DAYS_IN_FEBRUARY + _ISLEAP2(erayear);
res->tm_year = year - YEAR_BASE;
res->tm_mon = month;
res->tm_mday = day;
}
char *
khttp_epoch2str(int64_t tt, char *buf, size_t sz)
{
struct tm64 tm;
char rbuf[MAX_TIME_STRING];
const char *days[7] = {
"Sun",
"Mon",
"Tue",
"Wed",
"Thu",
"Fri",
"Sat"
};
const char *months[12] = {
"Jan",
"Feb",
"Mar",
"Apr",
"May",
"Jun",
"Jul",
"Aug",
"Sep",
"Oct",
"Nov",
"Dec"
};
if (buf == NULL || sz == 0)
return NULL;
khttp_gmtime_r(tt, &tm);
if (snprintf(rbuf, sizeof(rbuf),
"%s, %.2" PRId64 " %s %.4" PRId64 " "
"%.2" PRId64 ":%.2" PRId64 ":%.2" PRId64 " 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) == -1) {
XWARNX("snprintf");
return NULL;
}
strlcpy(buf, rbuf, sz);
return buf;
}
/*
* Deprecated interface simply zeroes the time structure if it's
* negative. The original implementation did not do this (it set the
* "struct tm" to zero), but the documentation still said otherwise.
* This uses the original documented behaviour.
*/
char *
kutil_epoch2str(int64_t tt, char *buf, size_t sz)
{
return khttp_epoch2str(tt < 0 ? 0 : tt, buf, sz);
}
char *
khttp_epoch2ustr(int64_t tt, char *buf, size_t sz)
{
char rbuf[MAX_TIME_STRING];
struct tm64 tm;
if (buf == NULL || sz == 0)
return NULL;
khttp_gmtime_r(tt, &tm);
if (snprintf(rbuf, sizeof(rbuf),
"%.4" PRId64 "-%.2" PRId64 "-%.2" PRId64
"T%.2" PRId64 ":%.2" PRId64 ":%.2" PRId64 "Z",
tm.tm_year + 1900, tm.tm_mon + 1, tm.tm_mday,
tm.tm_hour, tm.tm_min, tm.tm_sec) == -1) {
XWARNX("snprintf");
return NULL;
}
strlcpy(buf, rbuf, sz);
return buf;
}
/*
* Deprecated.
* See kutil_epoch2str() for behaviour notes.
*/
char *
kutil_epoch2utcstr(int64_t tt, char *buf, size_t sz)
{
return khttp_epoch2ustr(tt < 0 ? 0 : tt, buf, sz);
}
void
khttp_epoch2datetime(int64_t tt, int64_t *tm_sec, int64_t *tm_min,
int64_t *tm_hour, int64_t *tm_mday, int64_t *tm_mon,
int64_t *tm_year, int64_t *tm_wday, int64_t *tm_yday)
{
struct tm64 tm;
khttp_gmtime_r(tt, &tm);
if (tm_sec != NULL)
*tm_sec = tm.tm_sec;
if (tm_min != NULL)
*tm_min = tm.tm_min;
if (tm_hour != NULL)
*tm_hour = tm.tm_hour;
if (tm_mday != NULL)
*tm_mday = tm.tm_mday;
if (tm_mon != NULL)
*tm_mon = tm.tm_mon + 1;
if (tm_year != NULL)
*tm_year = tm.tm_year + 1900;
if (tm_wday != NULL)
*tm_wday = tm.tm_wday;
if (tm_yday != NULL)
*tm_yday = tm.tm_yday;
}
int
khttp_epoch2tms(int64_t tt, int *tm_sec, int *tm_min,
int *tm_hour, int *tm_mday, int *tm_mon,
int *tm_year, int *tm_wday, int *tm_yday)
{
struct tm64 tm;
khttp_gmtime_r(tt, &tm);
if (tm.tm_year > INT_MAX || tm.tm_year < -INT_MAX)
return 0;
if (tm_sec != NULL)
*tm_sec = tm.tm_sec;
if (tm_min != NULL)
*tm_min = tm.tm_min;
if (tm_hour != NULL)
*tm_hour = tm.tm_hour;
if (tm_mday != NULL)
*tm_mday = tm.tm_mday;
if (tm_mon != NULL)
*tm_mon = tm.tm_mon;
if (tm_year != NULL)
*tm_year = tm.tm_year;
if (tm_wday != NULL)
*tm_wday = tm.tm_wday;
if (tm_yday != NULL)
*tm_yday = tm.tm_yday;
return 1;
}
/*
* Deprecated.
* This has a bad corner case where gmtime() returns NULL and we just
* assume the epoch---this wasn't present in the earlier version of this
* because it was using a hand-rolled gmtime() that didn't fail. This
* is suitably unlikely that it's ok, as it's deprecated.
*/
void
kutil_epoch2tmvals(int64_t tt, int *tm_sec, int *tm_min,
int *tm_hour, int *tm_mday, int *tm_mon,
int *tm_year, int *tm_wday, int *tm_yday)
{
khttp_epoch2tms(tt < 0 ? 0 : tt, tm_sec, tm_min,
tm_hour, tm_mday, tm_mon, tm_year, tm_wday, tm_yday);
}
int
khttp_datetime2epoch(int64_t *res, int64_t day, int64_t mon,
int64_t year, int64_t hour, int64_t min, int64_t sec)
{
struct tm64 tm;
int64_t val;
if (res == NULL)
res = &val;
memset(&tm, 0, sizeof(struct tm64));
tm.tm_sec = sec;
tm.tm_min = min;
tm.tm_hour = hour;
tm.tm_mday = day;
tm.tm_mon = mon - 1;
tm.tm_year = year - 1900;
return khttp_mktime(res, &tm);
}
int
khttp_date2epoch(int64_t *res, int64_t day, int64_t mon, int64_t year)
{
return khttp_datetime2epoch(res, day, mon, year, 0, 0, 0);
}
/*
* Deprecated interface.
*/
int64_t
kutil_date2epoch(int64_t day, int64_t mon, int64_t year)
{
int64_t res;
if (!khttp_date2epoch(&res, day, mon, year))
return -1;
return res;
}
/*
* Deprecated interface.
*/
int
kutil_datetime_check(int64_t day, int64_t mon, int64_t year,
int64_t hour, int64_t min, int64_t sec)
{
return khttp_datetime2epoch
(NULL, day, mon, year, hour, min, sec);
}
/*
* Deprecated interface.
*/
int
kutil_date_check(int64_t day, int64_t mon, int64_t year)
{
return khttp_date2epoch(NULL, day, mon, year);
}
/*
* Deprecated interface.
*/
int64_t
kutil_datetime2epoch(int64_t day, int64_t mon, int64_t year,
int64_t hour, int64_t min, int64_t sec)
{
int64_t res;
if (!khttp_datetime2epoch
(&res, day, mon, year, hour, min, sec))
return -1;
return res;
}