text: /*	$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;
  }
  