mirror of
https://github.com/bdnugget/kcgi.git
synced 2026-11-07 05:47:27 +00:00
580 lines
14 KiB
C
580 lines
14 KiB
C
/* $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) {
|
|
kutil_warn(NULL, NULL, "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) {
|
|
kutil_warn(NULL, NULL, "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;
|
|
}
|