/*
 * Copyright (c) 2000-2001 Apple Computer, Inc. All Rights Reserved.
 * 
 * The contents of this file constitute Original Code as defined in and are
 * subject to the Apple Public Source License Version 1.2 (the 'License').
 * You may not use this file except in compliance with the License. Please obtain
 * a copy of the License at http://www.apple.com/publicsource and read it before
 * using this file.
 * 
 * This Original Code and all software distributed under the License are
 * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESS
 * OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, INCLUDING WITHOUT
 * LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
 * PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. Please see the License for the
 * specific language governing rights and limitations under the License.
 */


/*
 * tpTime.c - cert related time functions
 *
 * Written 10/10/2000 by Doug Mitchell.
 */
 
#include "tpTime.h"
#include <string.h>
#include <stdlib.h>
#include <stdio.h>
#include <ctype.h>
#include <stdbool.h>

/*
 * Given a string containing either a UTC-style or "generalized time"
 * time string, convert to a CFDateRef. Returns nonzero on
 * error. 
 */
int timeStringToCfDate(
	const char			*str,
	unsigned			len,
	CFDateRef			*cfDate)
{
	char 		szTemp[5];
	bool 		isUtc = false;			// 2-digit year
	bool		isLocal = false;		// trailing timezone offset
	bool		isCssmTime = false;		// no trailing 'Z'
	bool		noSeconds = false;
	unsigned 	x;
	unsigned 	i;
	char 		*cp;
	CFGregorianDate		gd;
	CFTimeZoneRef		timeZone;
	CFTimeInterval		gmtOff = 0;
	
	if((str == NULL) || (len == 0) || (cfDate == NULL)) {
    	return 1;
  	}
  	
  	/* tolerate NULL terminated or not */
  	if(str[len - 1] == '\0') {
  		len--;
  	}
  	switch(len) {
		case UTC_TIME_NOSEC_LEN:		// 2-digit year, no seconds, not y2K compliant
			isUtc = true;
			noSeconds = true;
			break;
  		case UTC_TIME_STRLEN:			// 2-digit year, not Y2K compliant
  			isUtc = true;
  			break;
		case CSSM_TIME_STRLEN:
			isCssmTime = true;
			break;
  		case GENERALIZED_TIME_STRLEN:	// 4-digit year
  			break;
 		case LOCALIZED_UTC_TIME_STRLEN:	// "YYMMDDhhmmssThhmm" (where T=[+,-])
			isUtc = 1;
			// deliberate fallthrough
		case LOCALIZED_TIME_STRLEN:		// "YYYYMMDDhhmmssThhmm" (where T=[+,-])
			isLocal = 1;
			break;
 		default:						// unknown format 
  			return 1;
  	}
  	
  	cp = (char *)str;
  	
	/* check that all characters except last (or timezone indicator, if localized) are digits */
	for(i=0; i<(len - 1); i++) {
		if ( !(isdigit(cp[i])) )
			if ( !isLocal || !(cp[i]=='+' || cp[i]=='-') )
				return 1;
	}

  	/* check last character is a 'Z' or digit as appropriate */
	if(isCssmTime || isLocal) {
		if(!isdigit(cp[len - 1])) {
			return 1;
		}
	}
	else {
		if(cp[len - 1] != 'Z' )	{
			return 1;
		}
	}
	
  	/* YEAR */
	szTemp[0] = *cp++;
	szTemp[1] = *cp++;
	if(!isUtc) {
		/* two more digits */
		szTemp[2] = *cp++;
		szTemp[3] = *cp++;
		szTemp[4] = '\0';
	}
	else { 
		szTemp[2] = '\0';
	}
	x = atoi( szTemp );
	if(isUtc) {
		/* 
		 * 2-digit year. 
		 *   0  <= year <  50 : assume century 21
		 *   50 <= year <  70 : illegal per PKIX
		 *   ...though we allow this as of 10/10/02...dmitch
		 *   70 <  year <= 99 : assume century 20
		 */
		if(x < 50) {
			x += 2000;
		}
		/*
		else if(x < 70) {
			return 1;
		}
		*/
		else {
			/* century 20 */
			x += 1900;			
		}
	}
	gd.year = x;

  	/* MONTH */
	szTemp[0] = *cp++;
	szTemp[1] = *cp++;
	szTemp[2] = '\0';
	x = atoi( szTemp );
	/* in the string, months are from 1 to 12 */
	if((x > 12) || (x <= 0)) {
    	return 1;
	}
	gd.month = x;

 	/* DAY */
	szTemp[0] = *cp++;
	szTemp[1] = *cp++;
	szTemp[2] = '\0';
	x = atoi( szTemp );
	/* 1..31 in both formats */
	if((x > 31) || (x <= 0)) {
		return 1;
	}
	gd.day = x;

	/* HOUR */
	szTemp[0] = *cp++;
	szTemp[1] = *cp++;
	szTemp[2] = '\0';
	x = atoi( szTemp );
	if((x > 23) || (x < 0)) {
		return 1;
	}
	gd.hour = x;

  	/* MINUTE */
	szTemp[0] = *cp++;
	szTemp[1] = *cp++;
	szTemp[2] = '\0';
	x = atoi( szTemp );
	if((x > 59) || (x < 0)) {
		return 1;
	}
	gd.minute = x;

  	/* SECOND */
	if(noSeconds) {
		gd.second = 0;
	}
	else {
		szTemp[0] = *cp++;
		szTemp[1] = *cp++;
		szTemp[2] = '\0';
		x = atoi( szTemp );
		if((x > 59) || (x < 0)) {
			return 1;
		}
		gd.second = x;
	}
	
	if (isLocal) {
		/* ZONE INDICATOR */
		switch(*cp++) {
			case '+':
				gmtOff = 1;
				break;
			case '-':
				gmtOff = -1;
				break;
			default:
				return 1;
		}
	  	/* ZONE HH OFFSET */
		szTemp[0] = *cp++;
		szTemp[1] = *cp++;
		szTemp[2] = '\0';
		x = atoi( szTemp ) * 60 * 60;
		gmtOff *= x;
	  	/* ZONE MM OFFSET */
		szTemp[0] = *cp++;
		szTemp[1] = *cp++;
		szTemp[2] = '\0';
		x = atoi( szTemp ) * 60;
		if(gmtOff < 0) {
			gmtOff -= x;
		}
		else {
			gmtOff += x;
		}
	}
	timeZone = CFTimeZoneCreateWithTimeIntervalFromGMT(NULL, gmtOff);
	if (!timeZone) {
		return 1;
	}
	*cfDate = CFDateCreate(NULL, CFGregorianDateGetAbsoluteTime(gd, timeZone));
	CFRelease(timeZone);
	return 0;
}

/*
 * Compare two times. Assumes they're both in GMT. Returns:
 * -1 if t1 <  t2
 *  0 if t1 == t2
 *  1 if t1 >  t2
 */
int compareTimes(
	CFDateRef 	t1,
	CFDateRef 	t2)
{
	switch(CFDateCompare(t1, t2, NULL)) {
		case kCFCompareLessThan:
			return -1;
		case kCFCompareEqualTo:
			return 0;
		case kCFCompareGreaterThan:
			return 1;
	}
	/* NOT REACHED */
	assert(0);
	return 0;
}

/*
 * Create a time string, in either UTC (2-digit) or or Generalized (4-digit)
 * year format. Caller mallocs the output string whose length is at least
 * (UTC_TIME_STRLEN+1), (GENERALIZED_TIME_STRLEN+1), or (CSSM_TIME_STRLEN+1)
 * respectively. Caller must hold tpTimeLock.
 */
void timeAtNowPlus(unsigned secFromNow, 
	TpTimeSpec timeSpec,
	char *outStr)
{
	struct tm utc;
	time_t baseTime;
	
	baseTime = time(NULL);
	baseTime += (time_t)secFromNow;
	utc = *gmtime(&baseTime);
	
	switch(timeSpec) {
		case TIME_UTC:
			/* UTC - 2 year digits - code which parses this assumes that
			 * (2-digit) years between 0 and 49 are in century 21 */
			if(utc.tm_year >= 100) {
				utc.tm_year -= 100;
			}
			sprintf(outStr, "%02d%02d%02d%02d%02d%02dZ",
				utc.tm_year /* + 1900 */, utc.tm_mon + 1,
				utc.tm_mday, utc.tm_hour, utc.tm_min, utc.tm_sec);
			break;
		case TIME_GEN:
			sprintf(outStr, "%04d%02d%02d%02d%02d%02dZ",
				/* note year is relative to 1900, hopefully it'll have 
				* four valid digits! */
				utc.tm_year + 1900, utc.tm_mon + 1,
				utc.tm_mday, utc.tm_hour, utc.tm_min, utc.tm_sec);
			break;
		case TIME_CSSM:
			sprintf(outStr, "%04d%02d%02d%02d%02d%02d",
				/* note year is relative to 1900, hopefully it'll have 
				* four valid digits! */
				utc.tm_year + 1900, utc.tm_mon + 1,
				utc.tm_mday, utc.tm_hour, utc.tm_min, utc.tm_sec);
			break;
	}
}

/*
 * Convert a time string, which can be in any of three forms (UTC,
 * generalized, or CSSM_TIMESTRING) into a CSSM_TIMESTRING. Caller
 * mallocs the result, which must be at least (CSSM_TIME_STRLEN+1) bytes.
 * Returns nonzero if incoming time string is badly formed. 
 */
int tpTimeToCssmTimestring(
	const char 	*inStr,			// not necessarily NULL terminated
	unsigned	inStrLen,		// not including possible NULL
	char 		*outTime)
{
	if((inStrLen == 0) || (inStr == NULL)) {
		return 1;
	}
	outTime[0] = '\0';
	switch(inStrLen) {
		case UTC_TIME_STRLEN:
		{
			/* infer century and prepend to output */
			char tmp[3];
			int year;
			tmp[0] = inStr[0];
			tmp[1] = inStr[1];
			tmp[2] = '\0';
			year = atoi(tmp);
			
			/* 
			 *   0  <= year <  50 : assume century 21
			 *   50 <= year <  70 : illegal per PKIX
			 *   70 <  year <= 99 : assume century 20
			 */
			if(year < 50) {
				/* century 21 */
				strcpy(outTime, "20");
			}
			else if(year < 70) {
				return 1;
			}
			else {
				/* century 20 */
				strcpy(outTime, "19");
			}
			memmove(outTime + 2, inStr, inStrLen - 1);		// don't copy the Z
			break;
		}
		case CSSM_TIME_STRLEN:
			memmove(outTime, inStr, inStrLen);				// trivial case
			break;
		case GENERALIZED_TIME_STRLEN:
			memmove(outTime, inStr, inStrLen - 1);			// don't copy the Z
			break;
		
		default:
			return 1;
	}
	outTime[CSSM_TIME_STRLEN] = '\0';
	return 0;
}


