1/*
2 * Copyright (c) 2000-2004 Apple Computer, Inc. All Rights Reserved.
3 *
4 * @APPLE_LICENSE_HEADER_START@
5 *
6 * This file contains Original Code and/or Modifications of Original Code
7 * as defined in and that are subject to the Apple Public Source License
8 * Version 2.0 (the 'License'). You may not use this file except in
9 * compliance with the License. Please obtain a copy of the License at
10 * http://www.opensource.apple.com/apsl/ and read it before using this
11 * file.
12 *
13 * The Original Code and all software distributed under the License are
14 * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
15 * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
16 * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
17 * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT.
18 * Please see the License for the specific language governing rights and
19 * limitations under the License.
20 *
21 * @APPLE_LICENSE_HEADER_END@
22 *
23 * cssmdatetime.cpp -- CSSM date and time utilities for the Mac
24 */
25
26#ifdef __MWERKS__
27#define _CPP_CSSM_DATE_TIME_UTILS
28#endif
29
30#include "cssmdatetime.h"
31
32#include <string.h>
33#include <stdio.h>
34#include <security_utilities/errors.h>
35#include <CoreFoundation/CFDate.h>
36#include <CoreFoundation/CFTimeZone.h>
37#include <ctype.h>
38#include <stdlib.h>
39#include <SecBase.h>
40namespace Security
41{
42
43namespace CSSMDateTimeUtils
44{
45
46#define UTC_TIME_NOSEC_LEN			11
47#define UTC_TIME_STRLEN				13
48#define GENERALIZED_TIME_STRLEN		15
49#define LOCALIZED_UTC_TIME_STRLEN	17
50#define LOCALIZED_TIME_STRLEN		19
51#define MAX_TIME_STR_LEN			30
52
53
54void
55GetCurrentMacLongDateTime(sint64 &outMacDate)
56{
57	CFTimeZoneRef timeZone = CFTimeZoneCopyDefault();
58	CFAbsoluteTime absTime = CFAbsoluteTimeGetCurrent();
59	absTime += CFTimeZoneGetSecondsFromGMT(timeZone, absTime);
60	CFRelease(timeZone);
61	outMacDate = sint64(double(absTime + kCFAbsoluteTimeIntervalSince1904));
62}
63
64void
65TimeStringToMacSeconds (const CSSM_DATA &inUTCTime, uint32 &ioMacDate)
66{
67    sint64 ldt;
68    TimeStringToMacLongDateTime(inUTCTime, ldt);
69    ioMacDate = uint32(ldt);
70}
71
72/*
73 * Given a CSSM_DATA containing either a UTC-style or "generalized time"
74 * time string, convert to 32-bit Mac time in seconds.
75 * Returns nonzero on error.
76 */
77void
78TimeStringToMacLongDateTime (const CSSM_DATA &inUTCTime, sint64 &outMacDate)
79{
80	char 		szTemp[5];
81	size_t          len;
82	int 		isUtc;
83	sint32 		x;
84	sint32 		i;
85	char 		*cp;
86
87	CFGregorianDate date;
88	::memset( &date, 0, sizeof(date) );
89
90	if ((inUTCTime.Data == NULL) || (inUTCTime.Length == 0))
91    {
92    	MacOSError::throwMe(errSecParam);
93  	}
94
95  	/* tolerate NULL terminated or not */
96  	len = inUTCTime.Length;
97  	if (inUTCTime.Data[len - 1] == '\0')
98  		len--;
99
100  	switch(len)
101    {
102  		case UTC_TIME_STRLEN:			// 2-digit year, not Y2K compliant
103  			isUtc = 1;
104  			break;
105  		case GENERALIZED_TIME_STRLEN:	// 4-digit year
106  			isUtc = 0;
107  			break;
108  		default:						// unknown format
109            MacOSError::throwMe(errSecParam);
110  	}
111
112  	cp = (char *)inUTCTime.Data;
113
114	/* check that all characters except last are digits */
115    for(i=0; i<(sint32)(len - 1); i++) {
116		if ( !(isdigit(cp[i])) ) {
117            MacOSError::throwMe(errSecParam);
118		}
119	}
120
121  	/* check last character is a 'Z' */
122  	if(cp[len - 1] != 'Z' )	{
123        MacOSError::throwMe(errSecParam);
124  	}
125
126  	/* YEAR */
127	szTemp[0] = *cp++;
128	szTemp[1] = *cp++;
129	if(!isUtc) {
130		/* two more digits */
131		szTemp[2] = *cp++;
132		szTemp[3] = *cp++;
133		szTemp[4] = '\0';
134	}
135	else {
136		szTemp[2] = '\0';
137	}
138	x = atoi( szTemp );
139	if(isUtc) {
140		/*
141		 * 2-digit year.
142		 *   0  <= year <= 50 : assume century 21
143		 *   50 <  year <  70 : illegal per PKIX
144		 *   70 <  year <= 99 : assume century 20
145		 */
146		if(x <= 50) {
147			x += 100;
148		}
149		else if(x < 70) {
150            MacOSError::throwMe(errSecParam);
151		}
152		/* else century 20, OK */
153
154		/* bug fix... we need to end up with a 4-digit year! */
155		x += 1900;
156	}
157  	/* by definition - tm_year is year - 1900 */
158  	//tmp->tm_year = x - 1900;
159  	date.year = x;
160
161  	/* MONTH */
162	szTemp[0] = *cp++;
163	szTemp[1] = *cp++;
164	szTemp[2] = '\0';
165	x = atoi( szTemp );
166	/* in the string, months are from 1 to 12 */
167	if((x > 12) || (x <= 0)) {
168        MacOSError::throwMe(errSecParam);
169	}
170	/* in a tm, 0 to 11 */
171  	//tmp->tm_mon = x - 1;
172  	date.month = x;
173
174 	/* DAY */
175	szTemp[0] = *cp++;
176	szTemp[1] = *cp++;
177	szTemp[2] = '\0';
178	x = atoi( szTemp );
179	/* 1..31 in both formats */
180	if((x > 31) || (x <= 0)) {
181        MacOSError::throwMe(errSecParam);
182	}
183  	//tmp->tm_mday = x;
184  	date.day = x;
185
186	/* HOUR */
187	szTemp[0] = *cp++;
188	szTemp[1] = *cp++;
189	szTemp[2] = '\0';
190	x = atoi( szTemp );
191	if((x > 23) || (x < 0)) {
192        MacOSError::throwMe(errSecParam);
193	}
194	//tmp->tm_hour = x;
195	date.hour = x;
196
197  	/* MINUTE */
198	szTemp[0] = *cp++;
199	szTemp[1] = *cp++;
200	szTemp[2] = '\0';
201	x = atoi( szTemp );
202	if((x > 59) || (x < 0)) {
203        MacOSError::throwMe(errSecParam);
204	}
205  	//tmp->tm_min = x;
206  	date.minute = x;
207
208  	/* SECOND */
209	szTemp[0] = *cp++;
210	szTemp[1] = *cp++;
211	szTemp[2] = '\0';
212  	x = atoi( szTemp );
213	if((x > 59) || (x < 0)) {
214        MacOSError::throwMe(errSecParam);
215	}
216  	//tmp->tm_sec = x;
217  	date.second = x;
218
219	CFTimeZoneRef timeZone = CFTimeZoneCreateWithTimeIntervalFromGMT(NULL, 0);
220	CFAbsoluteTime absTime = CFGregorianDateGetAbsoluteTime(date, timeZone);
221	CFRelease(timeZone);
222
223	// Adjust abstime to local timezone
224	timeZone = CFTimeZoneCopyDefault();
225	absTime += CFTimeZoneGetSecondsFromGMT(timeZone, absTime);
226	CFRelease(timeZone);
227
228	outMacDate = sint64(double(absTime + kCFAbsoluteTimeIntervalSince1904));
229}
230
231void MacSecondsToTimeString(uint32 inMacDate, uint32 inLength, void *outData)
232{
233    sint64 ldt = sint64(uint64(inMacDate));
234    MacLongDateTimeToTimeString(ldt, inLength, outData);
235}
236
237void MacLongDateTimeToTimeString(const sint64 &inMacDate,
238                                        uint32 inLength, void *outData)
239{
240	// @@@ this code is close, but on the fringe case of a daylight savings time it will be off for a little while
241	CFAbsoluteTime absTime = inMacDate - kCFAbsoluteTimeIntervalSince1904;
242
243	// Remove local timezone component from absTime
244	CFTimeZoneRef timeZone = CFTimeZoneCopyDefault();
245	absTime -= CFTimeZoneGetSecondsFromGMT(timeZone, absTime);
246	CFRelease(timeZone);
247
248	timeZone = CFTimeZoneCreateWithTimeIntervalFromGMT(NULL, 0);
249	CFGregorianDate date = CFAbsoluteTimeGetGregorianDate(absTime, timeZone);
250	CFRelease(timeZone);
251
252    if (inLength == 16)
253    {
254        sprintf((char *)(outData), "%04d%02d%02d%02d%02d%02dZ",
255                int(date.year % 10000), date.month, date.day,
256                date.hour, date.minute, int(date.second));
257    }
258    else if (inLength == 14)
259    {
260 		/* UTC - 2 year digits - code which parses this assumes that
261		 * (2-digit) years between 0 and 49 are in century 21 */
262        sprintf((char *)(outData), "%02d%02d%02d%02d%02d%02dZ",
263                int(date.year % 100), date.month, date.day,
264                date.hour, date.minute, int(date.second));
265    }
266    else
267        MacOSError::throwMe(errSecParam);
268}
269
270void
271CFDateToCssmDate(CFDateRef date, char *outCssmDate)
272{
273	CFTimeZoneRef timeZone = CFTimeZoneCreateWithTimeIntervalFromGMT(NULL, 0);
274	CFGregorianDate gd = CFAbsoluteTimeGetGregorianDate(CFDateGetAbsoluteTime(date), timeZone);
275	sprintf(outCssmDate, "%04d%02d%02d%02d%02d%02dZ", (int)gd.year, gd.month, gd.day, gd.hour, gd.minute, (unsigned int)gd.second);
276	CFRelease(timeZone);
277}
278
279void
280CssmDateToCFDate(const char *cssmDate, CFDateRef *outCFDate)
281{
282	CFTimeZoneRef timeZone = CFTimeZoneCreateWithTimeIntervalFromGMT(NULL, 0);
283	CFGregorianDate gd;
284	unsigned int year, month, day, hour, minute, second;
285	sscanf(cssmDate, "%4d%2d%2d%2d%2d%2d", &year, &month, &day, &hour, &minute, &second);
286	gd.year = year;
287	gd.month = month;
288	gd.day = day;
289	gd.hour = hour;
290	gd.minute = minute;
291	gd.second = second;
292	*outCFDate = CFDateCreate(NULL, CFGregorianDateGetAbsoluteTime(gd, timeZone));
293	CFRelease(timeZone);
294}
295
296int
297CssmDateStringToCFDate(const char *cssmDate, unsigned int len, CFDateRef *outCFDate)
298{
299	CFTimeZoneRef timeZone;
300	CFGregorianDate gd;
301	CFTimeInterval ti=0;
302	char szTemp[5];
303	unsigned isUtc=0, isLocal=0, i;
304        int x;
305	unsigned noSeconds=0;
306	char *cp;
307
308	if((cssmDate == NULL) || (len == 0) || (outCFDate == NULL))
309    	return 1;
310
311  	/* tolerate NULL terminated or not */
312  	if(cssmDate[len - 1] == '\0')
313  		len--;
314
315  	switch(len) {
316		case UTC_TIME_NOSEC_LEN:		// 2-digit year, no seconds, not y2K compliant
317   			isUtc = 1;
318			noSeconds = 1;
319  			break;
320 		case UTC_TIME_STRLEN:			// 2-digit year, not Y2K compliant
321  			isUtc = 1;
322  			break;
323  		case GENERALIZED_TIME_STRLEN:	// 4-digit year
324			//isUtc = 0;
325  			break;
326		case LOCALIZED_UTC_TIME_STRLEN:	// "YYMMDDhhmmssThhmm" (where T=[+,-])
327			isUtc = 1;
328			// deliberate fallthrough
329		case LOCALIZED_TIME_STRLEN:		// "YYYYMMDDhhmmssThhmm" (where T=[+,-])
330			isLocal = 1;
331			break;
332  		default:						// unknown format
333  			return 1;
334  	}
335
336  	cp = (char *)cssmDate;
337
338	/* check that all characters except last (or timezone indicator, if localized) are digits */
339	for(i=0; i<(len - 1); i++) {
340		if ( !(isdigit(cp[i])) )
341			if ( !isLocal || !(cp[i]=='+' || cp[i]=='-') )
342				return 1;
343	}
344	/* check last character is a 'Z', unless localized */
345	if(!isLocal && cp[len - 1] != 'Z' )	{
346		return 1;
347	}
348
349  	/* YEAR */
350	szTemp[0] = *cp++;
351	szTemp[1] = *cp++;
352	if(!isUtc) {
353		/* two more digits */
354		szTemp[2] = *cp++;
355		szTemp[3] = *cp++;
356		szTemp[4] = '\0';
357	}
358	else {
359		szTemp[2] = '\0';
360	}
361	x = atoi( szTemp );
362	if(isUtc) {
363		/*
364		 * 2-digit year.
365		 *   0  <= year <  50 : assume century 21
366		 *   50 <= year <  70 : illegal per PKIX
367		 *   70 <  year <= 99 : assume century 20
368		 */
369		if(x < 50) {
370			x += 2000;
371		}
372		else if(x < 70) {
373			return 1;
374		}
375		else {
376			/* century 20 */
377			x += 1900;
378		}
379	}
380	gd.year = x;
381
382  	/* MONTH */
383	szTemp[0] = *cp++;
384	szTemp[1] = *cp++;
385	szTemp[2] = '\0';
386	x = atoi( szTemp );
387	/* in the string, months are from 1 to 12 */
388	if((x > 12) || (x <= 0)) {
389    	return 1;
390	}
391	gd.month = x;
392
393 	/* DAY */
394	szTemp[0] = *cp++;
395	szTemp[1] = *cp++;
396	szTemp[2] = '\0';
397	x = atoi( szTemp );
398	/* 1..31 in both formats */
399	if((x > 31) || (x <= 0)) {
400		return 1;
401	}
402	gd.day = x;
403
404	/* HOUR */
405	szTemp[0] = *cp++;
406	szTemp[1] = *cp++;
407	szTemp[2] = '\0';
408	x = atoi( szTemp );
409	if((x > 23) || (x < 0)) {
410		return 1;
411	}
412	gd.hour = x;
413
414  	/* MINUTE */
415	szTemp[0] = *cp++;
416	szTemp[1] = *cp++;
417	szTemp[2] = '\0';
418	x = atoi( szTemp );
419	if((x > 59) || (x < 0)) {
420		return 1;
421	}
422	gd.minute = x;
423
424  	/* SECOND */
425	if(noSeconds) {
426		gd.second = 0;
427	}
428	else {
429		szTemp[0] = *cp++;
430		szTemp[1] = *cp++;
431		szTemp[2] = '\0';
432		x = atoi( szTemp );
433		if((x > 59) || (x < 0)) {
434			return 1;
435		}
436		gd.second = x;
437	}
438
439	if (isLocal) {
440		/* ZONE INDICATOR */
441		ti = (*cp++ == '+') ? 1 : -1;
442	  	/* ZONE HH OFFSET */
443		szTemp[0] = *cp++;
444		szTemp[1] = *cp++;
445		szTemp[2] = '\0';
446		x = atoi( szTemp ) * 60 * 60;
447		ti *= x;
448	  	/* ZONE MM OFFSET */
449		szTemp[0] = *cp++;
450		szTemp[1] = *cp++;
451		szTemp[2] = '\0';
452		x = atoi( szTemp ) * 60;
453		ti += ((ti < 0) ? (x*-1) : x);
454	}
455	timeZone = CFTimeZoneCreateWithTimeIntervalFromGMT(NULL, ti);
456	if (!timeZone) return 1;
457	*outCFDate = CFDateCreate(NULL, CFGregorianDateGetAbsoluteTime(gd, timeZone));
458	CFRelease(timeZone);
459
460	return 0;
461}
462
463}; // end namespace CSSMDateTimeUtils
464
465} // end namespace Security
466