1 // =================================================================================================
2 // Copyright 2002-2007 Adobe Systems Incorporated
3 // All Rights Reserved.
4 //
5 // NOTICE:	Adobe permits you to use, modify, and distribute this file in accordance with the terms
6 // of the Adobe license agreement accompanying it.
7 // =================================================================================================
8 
9 #include "XMP_Environment.h"	// ! This must be the first include!
10 #include "XMPCore_Impl.hpp"
11 
12 #include "XMPUtils.hpp"
13 
14 #include "MD5.h"
15 
16 #include <map>
17 
18 #include <time.h>
19 #include <string.h>
20 #include <stdlib.h>
21 #include <locale.h>
22 #include <errno.h>
23 
24 #include <stdio.h>	// For snprintf.
25 
26 #if XMP_WinBuild
27 #ifdef _MSC_VER
28 	#pragma warning ( disable : 4800 )	// forcing value to bool 'true' or 'false' (performance warning)
29 	#pragma warning ( disable : 4996 )	// '...' was declared deprecated
30 #endif
31 #endif
32 
33 // =================================================================================================
34 // Local Types and Constants
35 // =========================
36 
37 static const char * sBase64Chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
38 
39 // =================================================================================================
40 // Static Variables
41 // ================
42 
43 XMP_VarString * sComposedPath = 0;		// *** Only really need 1 string. Shrink periodically?
44 XMP_VarString * sConvertedValue = 0;
45 XMP_VarString * sBase64Str = 0;
46 XMP_VarString * sCatenatedItems = 0;
47 XMP_VarString * sStandardXMP = 0;
48 XMP_VarString * sExtendedXMP = 0;
49 XMP_VarString * sExtendedDigest = 0;
50 
51 // =================================================================================================
52 // Local Utilities
53 // ===============
54 
55 
56 // -------------------------------------------------------------------------------------------------
57 // ANSI Time Functions
58 // -------------------
59 //
60 // A bit of hackery to use the best available time functions. Mac and UNIX have thread safe versions
61 // of gmtime and localtime. On Mac the CodeWarrior functions are buggy, use Apple's.
62 
63 #if XMP_UNIXBuild
64 
65 	typedef time_t			ansi_tt;
66 	typedef struct tm		ansi_tm;
67 
68 	#define ansi_time		time
69 	#define ansi_mktime		mktime
70 	#define ansi_difftime	difftime
71 
72 	#define ansi_gmtime		gmtime_r
73 	#define ansi_localtime	localtime_r
74 
75 #elif XMP_WinBuild
76 
77 	// ! VS.Net 2003 (VC7) does not provide thread safe versions of gmtime and localtime.
78 	// ! VS.Net 2005 (VC8) inverts the parameters for the safe versions of gmtime and localtime.
79 
80 	typedef time_t			ansi_tt;
81 	typedef struct tm		ansi_tm;
82 
83 	#define ansi_time		time
84 	#define ansi_mktime		mktime
85 	#define ansi_difftime	difftime
86 
87 	#if defined(_MSC_VER) && (_MSC_VER >= 1400)
88 		#define ansi_gmtime(tt,tm)		gmtime_s ( tm, tt )
89 		#define ansi_localtime(tt,tm)	localtime_s ( tm, tt )
90 	#else
ansi_gmtime(const ansi_tt * ttTime,ansi_tm * tmTime)91 		static inline void ansi_gmtime ( const ansi_tt * ttTime, ansi_tm * tmTime )
92 		{
93 			ansi_tm * tmx = gmtime ( ttTime );	// ! Hope that there is no race!
94 			if ( tmx == 0 ) XMP_Throw ( "Failure from ANSI C gmtime function", kXMPErr_ExternalFailure );
95 			*tmTime = *tmx;
96 		}
ansi_localtime(const ansi_tt * ttTime,ansi_tm * tmTime)97 		static inline void ansi_localtime ( const ansi_tt * ttTime, ansi_tm * tmTime )
98 		{
99 			ansi_tm * tmx = localtime ( ttTime );	// ! Hope that there is no race!
100 			if ( tmx == 0 ) XMP_Throw ( "Failure from ANSI C localtime function", kXMPErr_ExternalFailure );
101 			*tmTime = *tmx;
102 		}
103 	#endif
104 
105 #elif XMP_MacBuild
106 
107 	#if ! __MWERKS__
108 
109 		typedef time_t			ansi_tt;
110 		typedef struct tm		ansi_tm;
111 
112 		#define ansi_time		time
113 		#define ansi_mktime		mktime
114 		#define ansi_difftime	difftime
115 
116 		#define ansi_gmtime		gmtime_r
117 		#define ansi_localtime	localtime_r
118 
119 	#else
120 
121 		// ! The CW versions are buggy. Use Apple's code, time_t, and "struct tm".
122 
123 		#include <mach-o/dyld.h>
124 
125 		typedef _BSD_TIME_T_	ansi_tt;
126 
127 		typedef struct apple_tm {
128 			int tm_sec;		/* seconds after the minute [0-60] */
129 			int tm_min;		/* minutes after the hour [0-59] */
130 			int tm_hour;	/* hours since midnight [0-23] */
131 			int tm_mday;	/* day of the month [1-31] */
132 			int tm_mon;		/* months since January [0-11] */
133 			int tm_year;	/* years since 1900 */
134 			int tm_wday;	/* days since Sunday [0-6] */
135 			int tm_yday;	/* days since January 1 [0-365] */
136 			int tm_isdst;	/* Daylight Savings Time flag */
137 			long	tm_gmtoff;	/* offset from CUT in seconds */
138 			char	*tm_zone;	/* timezone abbreviation */
139 		} ansi_tm;
140 
141 
142 		typedef ansi_tt (* GetTimeProc)	 ( ansi_tt * ttTime );
143 		typedef ansi_tt (* MakeTimeProc) ( ansi_tm * tmTime );
144 		typedef double	(* DiffTimeProc) ( ansi_tt t1, ansi_tt t0 );
145 
146 		typedef void (* ConvertTimeProc) ( const ansi_tt * ttTime, ansi_tm * tmTime );
147 
148 		static GetTimeProc ansi_time = 0;
149 		static MakeTimeProc ansi_mktime = 0;
150 		static DiffTimeProc ansi_difftime = 0;
151 
152 		static ConvertTimeProc ansi_gmtime = 0;
153 		static ConvertTimeProc ansi_localtime = 0;
154 
LookupTimeProcs()155 		static void LookupTimeProcs()
156 		{
157 			_dyld_lookup_and_bind_with_hint ( "_time", "libSystem", (XMP_Uns32*)&ansi_time, 0 );
158 			_dyld_lookup_and_bind_with_hint ( "_mktime", "libSystem", (XMP_Uns32*)&ansi_mktime, 0 );
159 			_dyld_lookup_and_bind_with_hint ( "_difftime", "libSystem", (XMP_Uns32*)&ansi_difftime, 0 );
160 			_dyld_lookup_and_bind_with_hint ( "_gmtime_r", "libSystem", (XMP_Uns32*)&ansi_gmtime, 0 );
161 			_dyld_lookup_and_bind_with_hint ( "_localtime_r", "libSystem", (XMP_Uns32*)&ansi_localtime, 0 );
162 		}
163 
164 	#endif
165 
166 #endif
167 
168 
169 // -------------------------------------------------------------------------------------------------
170 // IsLeapYear
171 // ----------
172 
173 static bool
IsLeapYear(long year)174 IsLeapYear ( long year )
175 {
176 
177 	if ( year < 0 ) year = -year + 1;		// Fold the negative years, assuming there is a year 0.
178 
179 	if ( (year % 4) != 0 ) return false;	// Not a multiple of 4.
180 	if ( (year % 100) != 0 ) return true;	// A multiple of 4 but not a multiple of 100.
181 	if ( (year % 400) == 0 ) return true;	// A multiple of 400.
182 
183 	return false;							// A multiple of 100 but not a multiple of 400.
184 
185 }	// IsLeapYear
186 
187 
188 // -------------------------------------------------------------------------------------------------
189 // DaysInMonth
190 // -----------
191 
192 static int
DaysInMonth(XMP_Int32 year,XMP_Int32 month)193 DaysInMonth ( XMP_Int32 year, XMP_Int32 month )
194 {
195 
196 	static short	daysInMonth[13] = { 0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 };
197 									   // Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec
198 
199 	int days = daysInMonth [ month ];
200 	if ( (month == 2) && IsLeapYear ( year ) ) days += 1;
201 
202 	return days;
203 
204 }	// DaysInMonth
205 
206 
207 // -------------------------------------------------------------------------------------------------
208 // AdjustTimeOverflow
209 // ------------------
210 
211 static void
AdjustTimeOverflow(XMP_DateTime * time)212 AdjustTimeOverflow ( XMP_DateTime * time )
213 {
214 	enum { kBillion = 1000*1000*1000L };
215 
216 	// ----------------------------------------------------------------------------------------------
217 	// To be safe against pathalogical overflow we first adjust from month to second, then from
218 	// nanosecond back up to month. This leaves each value closer to zero before propagating into it.
219 	// For example if the hour and minute are both near max, adjusting minutes first can cause the
220 	// hour to overflow.
221 
222 	// ! Photoshop 8 creates "time only" values with zeros for year, month, and day.
223 
224 	if ( (time->year != 0) || (time->month != 0) || (time->day != 0) ) {
225 
226 		while ( time->month < 1 ) {
227 			time->year -= 1;
228 			time->month += 12;
229 		}
230 
231 		while ( time->month > 12 ) {
232 			time->year += 1;
233 			time->month -= 12;
234 		}
235 
236 		while ( time->day < 1 ) {
237 			time->month -= 1;
238 			if ( time->month < 1 ) {	// ! Keep the months in range for indexing daysInMonth!
239 				time->year -= 1;
240 				time->month += 12;
241 			}
242 			time->day += DaysInMonth ( time->year, time->month );	// ! Decrement month before so index here is right!
243 		}
244 
245 		while ( time->day > DaysInMonth ( time->year, time->month ) ) {
246 			time->day -= DaysInMonth ( time->year, time->month );	// ! Increment month after so index here is right!
247 			time->month += 1;
248 			if ( time->month > 12 ) {
249 				time->year += 1;
250 				time->month -= 12;
251 			}
252 		}
253 
254 	}
255 
256 	while ( time->hour < 0 ) {
257 		time->day -= 1;
258 		time->hour += 24;
259 	}
260 
261 	while ( time->hour >= 24 ) {
262 		time->day += 1;
263 		time->hour -= 24;
264 	}
265 
266 	while ( time->minute < 0 ) {
267 		time->hour -= 1;
268 		time->minute += 60;
269 	}
270 
271 	while ( time->minute >= 60 ) {
272 		time->hour += 1;
273 		time->minute -= 60;
274 	}
275 
276 	while ( time->second < 0 ) {
277 		time->minute -= 1;
278 		time->second += 60;
279 	}
280 
281 	while ( time->second >= 60 ) {
282 		time->minute += 1;
283 		time->second -= 60;
284 	}
285 
286 	while ( time->nanoSecond < 0 ) {
287 		time->second -= 1;
288 		time->nanoSecond += kBillion;
289 	}
290 
291 	while ( time->nanoSecond >= kBillion ) {
292 		time->second += 1;
293 		time->nanoSecond -= kBillion;
294 	}
295 
296 	while ( time->second < 0 ) {
297 		time->minute -= 1;
298 		time->second += 60;
299 	}
300 
301 	while ( time->second >= 60 ) {
302 		time->minute += 1;
303 		time->second -= 60;
304 	}
305 
306 	while ( time->minute < 0 ) {
307 		time->hour -= 1;
308 		time->minute += 60;
309 	}
310 
311 	while ( time->minute >= 60 ) {
312 		time->hour += 1;
313 		time->minute -= 60;
314 	}
315 
316 	while ( time->hour < 0 ) {
317 		time->day -= 1;
318 		time->hour += 24;
319 	}
320 
321 	while ( time->hour >= 24 ) {
322 		time->day += 1;
323 		time->hour -= 24;
324 	}
325 
326 	if ( (time->year != 0) || (time->month != 0) || (time->day != 0) ) {
327 
328 		while ( time->month < 1 ) { // Make sure the months are OK first, for DaysInMonth.
329 			time->year -= 1;
330 			time->month += 12;
331 		}
332 
333 		while ( time->month > 12 ) {
334 			time->year += 1;
335 			time->month -= 12;
336 		}
337 
338 		while ( time->day < 1 ) {
339 			time->month -= 1;
340 			if ( time->month < 1 ) {
341 				time->year -= 1;
342 				time->month += 12;
343 			}
344 			time->day += DaysInMonth ( time->year, time->month );
345 		}
346 
347 		while ( time->day > DaysInMonth ( time->year, time->month ) ) {
348 			time->day -= DaysInMonth ( time->year, time->month );
349 			time->month += 1;
350 			if ( time->month > 12 ) {
351 				time->year += 1;
352 				time->month -= 12;
353 			}
354 		}
355 
356 	}
357 
358 }	// AdjustTimeOverflow
359 
360 
361 // -------------------------------------------------------------------------------------------------
362 // GatherInt
363 // ---------
364 
365 static XMP_Int32
GatherInt(XMP_StringPtr strValue,size_t * _pos,const char * errMsg)366 GatherInt ( XMP_StringPtr strValue, size_t * _pos, const char * errMsg )
367 {
368 	size_t	 pos   = *_pos;
369 	XMP_Int32 value = 0;
370 
371 	for ( char ch = strValue[pos]; ('0' <= ch) && (ch <= '9'); ++pos, ch = strValue[pos] ) {
372 		value = (value * 10) + (ch - '0');
373 	}
374 
375 	if ( pos == *_pos ) XMP_Throw ( errMsg, kXMPErr_BadParam );
376 	*_pos = pos;
377 	return value;
378 
379 }	// GatherInt
380 
381 
382 // -------------------------------------------------------------------------------------------------
383 
FormatFullDateTime(XMP_DateTime & tempDate,char * buffer,size_t bufferLen)384 static void FormatFullDateTime ( XMP_DateTime & tempDate, char * buffer, size_t bufferLen )
385 {
386 
387 	AdjustTimeOverflow ( &tempDate );	// Make sure all time parts are in range.
388 
389 	if ( (tempDate.second == 0) && (tempDate.nanoSecond == 0) ) {
390 
391 		// Output YYYY-MM-DDThh:mmTZD.
392 		snprintf ( buffer, bufferLen, "%.4d-%02d-%02dT%02d:%02d",	// AUDIT: Callers pass sizeof(buffer).
393                            static_cast<int>(tempDate.year), static_cast<int>(tempDate.month), static_cast<int>(tempDate.day), static_cast<int>(tempDate.hour), static_cast<int>(tempDate.minute) );
394 
395 	} else if ( tempDate.nanoSecond == 0  ) {
396 
397 		// Output YYYY-MM-DDThh:mm:ssTZD.
398 		snprintf ( buffer, bufferLen, "%.4d-%02d-%02dT%02d:%02d:%02d",	// AUDIT: Callers pass sizeof(buffer).
399                            static_cast<int>(tempDate.year), static_cast<int>(tempDate.month), static_cast<int>(tempDate.day),
400                            static_cast<int>(tempDate.hour), static_cast<int>(tempDate.minute), static_cast<int>(tempDate.second) );
401 
402 	} else {
403 
404 		// Output YYYY-MM-DDThh:mm:ss.sTZD.
405 		snprintf ( buffer, bufferLen, "%.4d-%02d-%02dT%02d:%02d:%02d.%09d", // AUDIT: Callers pass sizeof(buffer).
406                            static_cast<int>(tempDate.year), static_cast<int>(tempDate.month), static_cast<int>(tempDate.day),
407                            static_cast<int>(tempDate.hour), static_cast<int>(tempDate.minute), static_cast<int>(tempDate.second), static_cast<int>(tempDate.nanoSecond) );
408 		for ( size_t i = strlen(buffer)-1; buffer[i] == '0'; --i ) buffer[i] = 0;	// Trim excess digits.
409 
410 	}
411 
412 }	// FormatFullDateTime
413 
414 
415 // -------------------------------------------------------------------------------------------------
416 // DecodeBase64Char
417 // ----------------
418 
419 // The decode mapping:
420 //
421 //	encoded		encoded			raw
422 //	char		value			value
423 //	-------		-------			-----
424 //	A .. Z		0x41 .. 0x5A	 0 .. 25
425 //	a .. z		0x61 .. 0x7A	26 .. 51
426 //	0 .. 9		0x30 .. 0x39	52 .. 61
427 //	+			0x2B			62
428 //	/			0x2F			63
429 
430 static unsigned char
DecodeBase64Char(XMP_Uns8 ch)431 DecodeBase64Char ( XMP_Uns8 ch )
432 {
433 
434 	if ( ('A' <= ch) && (ch <= 'Z') ) {
435 		ch = ch - 'A';
436 	} else if ( ('a' <= ch) && (ch <= 'z') ) {
437 		ch = ch - 'a' + 26;
438 	} else if ( ('0' <= ch) && (ch <= '9') ) {
439 		ch = ch - '0' + 52;
440 	} else if ( ch == '+' ) {
441 		ch = 62;
442 	} else if ( ch == '/' ) {
443 		ch = 63;
444 	} else if ( (ch == ' ') || (ch == kTab) || (ch == kLF) || (ch == kCR) ) {
445 		ch = 0xFF;	// Will be ignored by the caller.
446 	} else {
447 		XMP_Throw ( "Invalid base-64 encoded character", kXMPErr_BadParam );
448 	}
449 
450 	return ch;
451 
452 }	// DecodeBase64Char ();
453 
454 
455 // -------------------------------------------------------------------------------------------------
456 // EstimateSizeForJPEG
457 // -------------------
458 //
459 // Estimate the serialized size for the subtree of an XMP_Node. Support for PackageForJPEG.
460 
461 static size_t
EstimateSizeForJPEG(const XMP_Node * xmpNode)462 EstimateSizeForJPEG ( const XMP_Node * xmpNode )
463 {
464 
465 	size_t estSize = 0;
466 	size_t nameSize = xmpNode->name.size();
467 	bool   includeName = (! XMP_PropIsArray ( xmpNode->parent->options ));
468 
469 	if ( XMP_PropIsSimple ( xmpNode->options ) ) {
470 
471 		if ( includeName ) estSize += (nameSize + 3);	// Assume attribute form.
472 		estSize += xmpNode->value.size();
473 
474 	} else if ( XMP_PropIsArray ( xmpNode->options ) ) {
475 
476 		// The form of the value portion is: <rdf:Xyz><rdf:li>...</rdf:li>...</rdf:Xyx>
477 		if ( includeName ) estSize += (2*nameSize + 5);
478 		size_t arraySize = xmpNode->children.size();
479 		estSize += 9 + 10;	// The rdf:Xyz tags.
480 		estSize += arraySize * (8 + 9);	// The rdf:li tags.
481 		for ( size_t i = 0; i < arraySize; ++i ) {
482 			estSize += EstimateSizeForJPEG ( xmpNode->children[i] );
483 		}
484 
485 	} else {
486 
487 		// The form is: <headTag rdf:parseType="Resource">...fields...</tailTag>
488 		if ( includeName ) estSize += (2*nameSize + 5);
489 		estSize += 25;	// The rdf:parseType="Resource" attribute.
490 		size_t fieldCount = xmpNode->children.size();
491 		for ( size_t i = 0; i < fieldCount; ++i ) {
492 			estSize += EstimateSizeForJPEG ( xmpNode->children[i] );
493 		}
494 
495 	}
496 
497 	return estSize;
498 
499 }	// EstimateSizeForJPEG
500 
501 
502 // -------------------------------------------------------------------------------------------------
503 // MoveOneProperty
504 // ---------------
505 
MoveOneProperty(XMPMeta & stdXMP,XMPMeta * extXMP,XMP_StringPtr schemaURI,XMP_StringPtr propName)506 static bool MoveOneProperty ( XMPMeta & stdXMP, XMPMeta * extXMP,
507 							  XMP_StringPtr schemaURI, XMP_StringPtr propName )
508 {
509 
510 	XMP_Node * propNode = 0;
511 	XMP_NodePtrPos stdPropPos;
512 
513 	XMP_Node * stdSchema = FindSchemaNode ( &stdXMP.tree, schemaURI, kXMP_ExistingOnly, 0 );
514 	if ( stdSchema != 0 ) {
515 		propNode = FindChildNode ( stdSchema, propName, kXMP_ExistingOnly, &stdPropPos );
516 	}
517 	if ( propNode == 0 ) return false;
518 
519 	XMP_Node * extSchema = FindSchemaNode ( &extXMP->tree, schemaURI, kXMP_CreateNodes );
520 
521 	propNode->parent = extSchema;
522 
523 	extSchema->options &= ~kXMP_NewImplicitNode;
524 	extSchema->children.push_back ( propNode );
525 
526 	stdSchema->children.erase ( stdPropPos );
527 	DeleteEmptySchema ( stdSchema );
528 
529 	return true;
530 
531 }	// MoveOneProperty
532 
533 
534 // -------------------------------------------------------------------------------------------------
535 // CreateEstimatedSizeMap
536 // ----------------------
537 
538 #ifndef Trace_PackageForJPEG
539 	#define Trace_PackageForJPEG 0
540 #endif
541 
542 typedef std::pair < XMP_VarString*, XMP_VarString* > StringPtrPair;
543 typedef std::multimap < size_t, StringPtrPair > PropSizeMap;
544 
CreateEstimatedSizeMap(XMPMeta & stdXMP,PropSizeMap * propSizes)545 static void CreateEstimatedSizeMap ( XMPMeta & stdXMP, PropSizeMap * propSizes )
546 {
547 	#if Trace_PackageForJPEG
548 		printf ( "  Creating top level property map:\n" );
549 	#endif
550 
551 	for ( size_t s = stdXMP.tree.children.size(); s > 0; --s ) {
552 
553 		XMP_Node * stdSchema = stdXMP.tree.children[s-1];
554 
555 		for ( size_t p = stdSchema->children.size(); p > 0; --p ) {
556 
557 			XMP_Node * stdProp = stdSchema->children[p-1];
558 			if ( (stdSchema->name == kXMP_NS_XMP_Note) &&
559 				 (stdProp->name == "xmpNote:HasExtendedXMP") ) continue;	// ! Don't move xmpNote:HasExtendedXMP.
560 
561 			size_t propSize = EstimateSizeForJPEG ( stdProp );
562 			StringPtrPair namePair ( &stdSchema->name, &stdProp->name );
563 			PropSizeMap::value_type mapValue ( propSize, namePair );
564 
565 			(void) propSizes->insert ( propSizes->upper_bound ( propSize ), mapValue );
566 			#if Trace_PackageForJPEG
567 				printf ( "    %d bytes, %s in %s\n", propSize, stdProp->name.c_str(), stdSchema->name.c_str() );
568 			#endif
569 
570 		}
571 
572 	}
573 
574 }	// CreateEstimatedSizeMap
575 
576 
577 // -------------------------------------------------------------------------------------------------
578 // MoveLargestProperty
579 // -------------------
580 
MoveLargestProperty(XMPMeta & stdXMP,XMPMeta * extXMP,PropSizeMap & propSizes)581 static size_t MoveLargestProperty ( XMPMeta & stdXMP, XMPMeta * extXMP, PropSizeMap & propSizes )
582 {
583 	XMP_Assert ( ! propSizes.empty() );
584 
585 	#if 0
586 		// *** Xcode 2.3 on Mac OS X 10.4.7 seems to have a bug where this does not pick the last
587 		// *** item in the map. We'll just avoid it on all platforms until thoroughly tested.
588 		PropSizeMap::iterator lastPos = propSizes.end();
589 		--lastPos;	// Move to the actual last item.
590 	#else
591 		PropSizeMap::iterator lastPos = propSizes.begin();
592 		PropSizeMap::iterator nextPos = lastPos;
593 		for ( ++nextPos; nextPos != propSizes.end(); ++nextPos ) lastPos = nextPos;
594 	#endif
595 
596 	size_t propSize = lastPos->first;
597 	const char * schemaURI = lastPos->second.first->c_str();
598 	const char * propName  = lastPos->second.second->c_str();
599 
600 	#if Trace_PackageForJPEG
601 		printf ( "  Move %s, %d bytes\n", propName, propSize );
602 	#endif
603 
604     bool moved = MoveOneProperty ( stdXMP, extXMP, schemaURI, propName );
605 	XMP_Assert ( moved );
606 	UNUSED(moved);
607 
608 	propSizes.erase ( lastPos );
609 	return propSize;
610 
611 }	// MoveLargestProperty
612 
613 
614 // =================================================================================================
615 // Class Static Functions
616 // ======================
617 
618 
619 // -------------------------------------------------------------------------------------------------
620 // Initialize
621 // ----------
622 
623 /* class static */ bool
Initialize()624 XMPUtils::Initialize()
625 {
626 	sComposedPath	= new XMP_VarString();
627 	sConvertedValue = new XMP_VarString();
628 	sBase64Str		= new XMP_VarString();
629 	sCatenatedItems = new XMP_VarString();
630 	sStandardXMP    = new XMP_VarString();
631 	sExtendedXMP    = new XMP_VarString();
632 	sExtendedDigest = new XMP_VarString();
633 
634 	#if XMP_MacBuild && __MWERKS__
635 		LookupTimeProcs();
636 	#endif
637 
638 	return true;
639 
640 }	// Initialize
641 
642 
643 // -------------------------------------------------------------------------------------------------
644 // Terminate
645 // ---------
646 
647 #define EliminateGlobal(g) delete ( g ); g = 0
648 
649 /* class static */ void
Terminate()650 XMPUtils::Terminate() RELEASE_NO_THROW
651 {
652 	EliminateGlobal ( sComposedPath );
653 	EliminateGlobal ( sConvertedValue );
654 	EliminateGlobal ( sBase64Str );
655 	EliminateGlobal ( sCatenatedItems );
656 	EliminateGlobal ( sStandardXMP );
657 	EliminateGlobal ( sExtendedXMP );
658 	EliminateGlobal ( sExtendedDigest );
659 
660 	return;
661 
662 }	// Terminate
663 
664 
665 // -------------------------------------------------------------------------------------------------
666 // Unlock
667 // ------
668 
669 /* class static */ void
Unlock(XMP_OptionBits options)670 XMPUtils::Unlock ( XMP_OptionBits options )
671 {
672 	UNUSED(options);
673 
674 	XMPMeta::Unlock ( 0 );
675 
676 }	// Unlock
677 
678 // -------------------------------------------------------------------------------------------------
679 // ComposeArrayItemPath
680 // --------------------
681 //
682 // Return "arrayName[index]".
683 
684 /* class static */ void
ComposeArrayItemPath(XMP_StringPtr schemaNS,XMP_StringPtr arrayName,XMP_Index itemIndex,XMP_StringPtr * fullPath,XMP_StringLen * pathSize)685 XMPUtils::ComposeArrayItemPath ( XMP_StringPtr	 schemaNS,
686 								 XMP_StringPtr	 arrayName,
687 								 XMP_Index		 itemIndex,
688 								 XMP_StringPtr * fullPath,
689 								 XMP_StringLen * pathSize )
690 {
691 	XMP_Assert ( schemaNS != 0 );	// Enforced by wrapper.
692 	XMP_Assert ( *arrayName != 0 ); // Enforced by wrapper.
693 	XMP_Assert ( (fullPath != 0) && (pathSize != 0) );	// Enforced by wrapper.
694 
695 	XMP_ExpandedXPath expPath;	// Just for side effects to check namespace and basic path.
696 	ExpandXPath ( schemaNS, arrayName, &expPath );
697 
698 	if ( (itemIndex < 0) && (itemIndex != kXMP_ArrayLastItem) ) XMP_Throw ( "Array index out of bounds", kXMPErr_BadParam );
699 
700 	XMP_StringLen reserveLen = strlen(arrayName) + 2 + 32;	// Room plus padding.
701 
702 	sComposedPath->erase();
703 	sComposedPath->reserve ( reserveLen );
704 	sComposedPath->append ( reserveLen, ' ' );
705 
706 	if ( itemIndex != kXMP_ArrayLastItem ) {
707 		// AUDIT: Using string->size() for the snprintf length is safe.
708                 snprintf ( const_cast<char*>(sComposedPath->c_str()), sComposedPath->size(), "%s[%d]", arrayName, static_cast<int>(itemIndex) );
709 	} else {
710 		*sComposedPath = arrayName;
711 		*sComposedPath += "[last()] ";
712 		(*sComposedPath)[sComposedPath->size()-1] = 0;	// ! Final null is for the strlen at exit.
713 	}
714 
715 	*fullPath = sComposedPath->c_str();
716 	*pathSize = strlen ( *fullPath );	// ! Don't use sComposedPath->size()!
717 
718 	XMP_Enforce ( *pathSize < sComposedPath->size() );	// Rather late, but complain about buffer overflow.
719 
720 }	// ComposeArrayItemPath
721 
722 
723 // -------------------------------------------------------------------------------------------------
724 // ComposeStructFieldPath
725 // ----------------------
726 //
727 // Return "structName/ns:fieldName".
728 
729 /* class static */ void
ComposeStructFieldPath(XMP_StringPtr schemaNS,XMP_StringPtr structName,XMP_StringPtr fieldNS,XMP_StringPtr fieldName,XMP_StringPtr * fullPath,XMP_StringLen * pathSize)730 XMPUtils::ComposeStructFieldPath ( XMP_StringPtr   schemaNS,
731 								   XMP_StringPtr   structName,
732 								   XMP_StringPtr   fieldNS,
733 								   XMP_StringPtr   fieldName,
734 								   XMP_StringPtr * fullPath,
735 								   XMP_StringLen * pathSize )
736 {
737 	XMP_Assert ( (schemaNS != 0) && (fieldNS != 0) );		// Enforced by wrapper.
738 	XMP_Assert ( (*structName != 0) && (*fieldName != 0) ); // Enforced by wrapper.
739 	XMP_Assert ( (fullPath != 0) && (pathSize != 0) );		// Enforced by wrapper.
740 
741 	XMP_ExpandedXPath expPath;	// Just for side effects to check namespace and basic path.
742 	ExpandXPath ( schemaNS, structName, &expPath );
743 
744 	XMP_ExpandedXPath fieldPath;
745 	ExpandXPath ( fieldNS, fieldName, &fieldPath );
746 	if ( fieldPath.size() != 2 ) XMP_Throw ( "The fieldName must be simple", kXMPErr_BadXPath );
747 
748 	XMP_StringLen reserveLen = strlen(structName) + fieldPath[kRootPropStep].step.size() + 1;
749 
750 	sComposedPath->erase();
751 	sComposedPath->reserve ( reserveLen );
752 	*sComposedPath = structName;
753 	*sComposedPath += '/';
754 	*sComposedPath += fieldPath[kRootPropStep].step;
755 
756 	*fullPath = sComposedPath->c_str();
757 	*pathSize = sComposedPath->size();
758 
759 }	// ComposeStructFieldPath
760 
761 
762 // -------------------------------------------------------------------------------------------------
763 // ComposeQualifierPath
764 // --------------------
765 //
766 // Return "propName/?ns:qualName".
767 
768 /* class static */ void
ComposeQualifierPath(XMP_StringPtr schemaNS,XMP_StringPtr propName,XMP_StringPtr qualNS,XMP_StringPtr qualName,XMP_StringPtr * fullPath,XMP_StringLen * pathSize)769 XMPUtils::ComposeQualifierPath ( XMP_StringPtr	 schemaNS,
770 								 XMP_StringPtr	 propName,
771 								 XMP_StringPtr	 qualNS,
772 								 XMP_StringPtr	 qualName,
773 								 XMP_StringPtr * fullPath,
774 								 XMP_StringLen * pathSize )
775 {
776 	XMP_Assert ( (schemaNS != 0) && (qualNS != 0) );		// Enforced by wrapper.
777 	XMP_Assert ( (*propName != 0) && (*qualName != 0) );	// Enforced by wrapper.
778 	XMP_Assert ( (fullPath != 0) && (pathSize != 0) );		// Enforced by wrapper.
779 
780 	XMP_ExpandedXPath expPath;	// Just for side effects to check namespace and basic path.
781 	ExpandXPath ( schemaNS, propName, &expPath );
782 
783 	XMP_ExpandedXPath qualPath;
784 	ExpandXPath ( qualNS, qualName, &qualPath );
785 	if ( qualPath.size() != 2 ) XMP_Throw ( "The qualifier name must be simple", kXMPErr_BadXPath );
786 
787 	XMP_StringLen reserveLen = strlen(propName) + qualPath[kRootPropStep].step.size() + 2;
788 
789 	sComposedPath->erase();
790 	sComposedPath->reserve ( reserveLen );
791 	*sComposedPath = propName;
792 	*sComposedPath += "/?";
793 	*sComposedPath += qualPath[kRootPropStep].step;
794 
795 	*fullPath = sComposedPath->c_str();
796 	*pathSize = sComposedPath->size();
797 
798 }	// ComposeQualifierPath
799 
800 
801 // -------------------------------------------------------------------------------------------------
802 // ComposeLangSelector
803 // -------------------
804 //
805 // Return "arrayName[?xml:lang="lang"]".
806 
807 // *** #error "handle quotes in the lang - or verify format"
808 
809 /* class static */ void
ComposeLangSelector(XMP_StringPtr schemaNS,XMP_StringPtr arrayName,XMP_StringPtr _langName,XMP_StringPtr * fullPath,XMP_StringLen * pathSize)810 XMPUtils::ComposeLangSelector ( XMP_StringPtr	schemaNS,
811 								XMP_StringPtr	arrayName,
812 								XMP_StringPtr	_langName,
813 								XMP_StringPtr * fullPath,
814 								XMP_StringLen * pathSize )
815 {
816 	XMP_Assert ( schemaNS != 0 );	// Enforced by wrapper.
817 	XMP_Assert ( (*arrayName != 0) && (*_langName != 0) );	// Enforced by wrapper.
818 	XMP_Assert ( (fullPath != 0) && (pathSize != 0) );		// Enforced by wrapper.
819 
820 	XMP_ExpandedXPath expPath;	// Just for side effects to check namespace and basic path.
821 	ExpandXPath ( schemaNS, arrayName, &expPath );
822 
823 	XMP_VarString langName ( _langName );
824 	NormalizeLangValue ( &langName );
825 
826 	XMP_StringLen reserveLen = strlen(arrayName) + langName.size() + 14;
827 
828 	sComposedPath->erase();
829 	sComposedPath->reserve ( reserveLen );
830 	*sComposedPath = arrayName;
831 	*sComposedPath += "[?xml:lang=\"";
832 	*sComposedPath += langName;
833 	*sComposedPath += "\"]";
834 
835 	*fullPath = sComposedPath->c_str();
836 	*pathSize = sComposedPath->size();
837 
838 }	// ComposeLangSelector
839 
840 
841 // -------------------------------------------------------------------------------------------------
842 // ComposeFieldSelector
843 // --------------------
844 //
845 // Return "arrayName[ns:fieldName="fieldValue"]".
846 
847 // *** #error "handle quotes in the value"
848 
849 /* class static */ void
ComposeFieldSelector(XMP_StringPtr schemaNS,XMP_StringPtr arrayName,XMP_StringPtr fieldNS,XMP_StringPtr fieldName,XMP_StringPtr fieldValue,XMP_StringPtr * fullPath,XMP_StringLen * pathSize)850 XMPUtils::ComposeFieldSelector ( XMP_StringPtr	 schemaNS,
851 								 XMP_StringPtr	 arrayName,
852 								 XMP_StringPtr	 fieldNS,
853 								 XMP_StringPtr	 fieldName,
854 								 XMP_StringPtr	 fieldValue,
855 								 XMP_StringPtr * fullPath,
856 								 XMP_StringLen * pathSize )
857 {
858 	XMP_Assert ( (schemaNS != 0) && (fieldNS != 0) && (fieldValue != 0) );	// Enforced by wrapper.
859 	XMP_Assert ( (*arrayName != 0) && (*fieldName != 0) );	// Enforced by wrapper.
860 	XMP_Assert ( (fullPath != 0) && (pathSize != 0) );		// Enforced by wrapper.
861 
862 	XMP_ExpandedXPath expPath;	// Just for side effects to check namespace and basic path.
863 	ExpandXPath ( schemaNS, arrayName, &expPath );
864 
865 	XMP_ExpandedXPath fieldPath;
866 	ExpandXPath ( fieldNS, fieldName, &fieldPath );
867 	if ( fieldPath.size() != 2 ) XMP_Throw ( "The fieldName must be simple", kXMPErr_BadXPath );
868 
869 	XMP_StringLen reserveLen = strlen(arrayName) + fieldPath[kRootPropStep].step.size() + strlen(fieldValue) + 5;
870 
871 	sComposedPath->erase();
872 	sComposedPath->reserve ( reserveLen );
873 	*sComposedPath = arrayName;
874 	*sComposedPath += '[';
875 	*sComposedPath += fieldPath[kRootPropStep].step;
876 	*sComposedPath += "=\"";
877 	*sComposedPath += fieldValue;
878 	*sComposedPath += "\"]";
879 
880 	*fullPath = sComposedPath->c_str();
881 	*pathSize = sComposedPath->size();
882 
883 }	// ComposeFieldSelector
884 
885 
886 // -------------------------------------------------------------------------------------------------
887 // ConvertFromBool
888 // ---------------
889 
890 /* class static */ void
ConvertFromBool(bool binValue,XMP_StringPtr * strValue,XMP_StringLen * strSize)891 XMPUtils::ConvertFromBool ( bool			binValue,
892 							XMP_StringPtr * strValue,
893 							XMP_StringLen * strSize )
894 {
895 	XMP_Assert ( (strValue != 0) && (strSize != 0) );	// Enforced by wrapper.
896 
897 	if ( binValue ) {
898 		*strValue = kXMP_TrueStr;
899 		*strSize  = strlen ( kXMP_TrueStr );
900 	} else {
901 		*strValue = kXMP_FalseStr;
902 		*strSize  = strlen ( kXMP_FalseStr );
903 	}
904 
905 }	// ConvertFromBool
906 
907 
908 // -------------------------------------------------------------------------------------------------
909 // ConvertFromInt
910 // --------------
911 
912 /* class static */ void
ConvertFromInt(XMP_Int32 binValue,XMP_StringPtr format,XMP_StringPtr * strValue,XMP_StringLen * strSize)913 XMPUtils::ConvertFromInt ( XMP_Int32	   binValue,
914 						   XMP_StringPtr   format,
915 						   XMP_StringPtr * strValue,
916 						   XMP_StringLen * strSize )
917 {
918 	XMP_Assert ( (format != 0) && (strValue != 0) && (strSize != 0) );	// Enforced by wrapper.
919 
920 	if ( *format == 0 ) format = "%d";
921 
922 	sConvertedValue->erase();
923 	sConvertedValue->reserve ( 100 );		// More than enough for any reasonable format and value.
924 	sConvertedValue->append ( 100, ' ' );
925 
926 	// AUDIT: Using string->size() for the snprintf length is safe.
927 	snprintf ( const_cast<char*>(sConvertedValue->c_str()), sConvertedValue->size(), format, binValue );
928 
929 	*strValue = sConvertedValue->c_str();
930 	*strSize  = strlen ( *strValue );	// ! Don't use sConvertedValue->size()!
931 
932 	XMP_Enforce ( *strSize < sConvertedValue->size() ); // Rather late, but complain about buffer overflow.
933 
934 }	// ConvertFromInt
935 
936 
937 // -------------------------------------------------------------------------------------------------
938 // ConvertFromInt64
939 // ----------------
940 
941 /* class static */ void
ConvertFromInt64(XMP_Int64 binValue,XMP_StringPtr format,XMP_StringPtr * strValue,XMP_StringLen * strSize)942 XMPUtils::ConvertFromInt64 ( XMP_Int64	     binValue,
943 						     XMP_StringPtr   format,
944 						     XMP_StringPtr * strValue,
945 						     XMP_StringLen * strSize )
946 {
947 	XMP_Assert ( (format != 0) && (strValue != 0) && (strSize != 0) );	// Enforced by wrapper.
948 
949 	if ( *format == 0 ) format = "%lld";
950 
951 	sConvertedValue->erase();
952 	sConvertedValue->reserve ( 100 );		// More than enough for any reasonable format and value.
953 	sConvertedValue->append ( 100, ' ' );
954 
955 	// AUDIT: Using string->size() for the snprintf length is safe.
956 	snprintf ( const_cast<char*>(sConvertedValue->c_str()), sConvertedValue->size(), format, binValue );
957 
958 	*strValue = sConvertedValue->c_str();
959 	*strSize  = strlen ( *strValue );	// ! Don't use sConvertedValue->size()!
960 
961 	XMP_Enforce ( *strSize < sConvertedValue->size() ); // Rather late, but complain about buffer overflow.
962 
963 }	// ConvertFromInt64
964 
965 
966 // -------------------------------------------------------------------------------------------------
967 // ConvertFromFloat
968 // ----------------
969 
970 /* class static */ void
ConvertFromFloat(double binValue,XMP_StringPtr format,XMP_StringPtr * strValue,XMP_StringLen * strSize)971 XMPUtils::ConvertFromFloat ( double			 binValue,
972 							 XMP_StringPtr	 format,
973 							 XMP_StringPtr * strValue,
974 							 XMP_StringLen * strSize )
975 {
976 	XMP_Assert ( (format != 0) && (strValue != 0) && (strSize != 0) );	// Enforced by wrapper.
977 
978 	if ( *format == 0 ) format = "%f";
979 
980 	sConvertedValue->erase();
981 	sConvertedValue->reserve ( 1000 );		// More than enough for any reasonable format and value.
982 	sConvertedValue->append ( 1000, ' ' );
983 
984 	// AUDIT: Using string->size() for the snprintf length is safe.
985 	snprintf ( const_cast<char*>(sConvertedValue->c_str()), sConvertedValue->size(), format, binValue );
986 
987 	*strValue = sConvertedValue->c_str();
988 	*strSize  = strlen ( *strValue );	// ! Don't use sConvertedValue->size()!
989 
990 	XMP_Enforce ( *strSize < sConvertedValue->size() ); // Rather late, but complain about buffer overflow.
991 
992 }	// ConvertFromFloat
993 
994 
995 // -------------------------------------------------------------------------------------------------
996 // ConvertFromDate
997 // ---------------
998 //
999 // Format a date according to ISO 8601 and http://www.w3.org/TR/NOTE-datetime:
1000 //	YYYY
1001 //	YYYY-MM
1002 //	YYYY-MM-DD
1003 //	YYYY-MM-DDThh:mmTZD
1004 //	YYYY-MM-DDThh:mm:ssTZD
1005 //	YYYY-MM-DDThh:mm:ss.sTZD
1006 //
1007 //	YYYY = four-digit year
1008 //	MM	 = two-digit month (01=January, etc.)
1009 //	DD	 = two-digit day of month (01 through 31)
1010 //	hh	 = two digits of hour (00 through 23)
1011 //	mm	 = two digits of minute (00 through 59)
1012 //	ss	 = two digits of second (00 through 59)
1013 //	s	 = one or more digits representing a decimal fraction of a second
1014 //	TZD	 = time zone designator (Z or +hh:mm or -hh:mm)
1015 //
1016 // Note that ISO 8601 does not seem to allow years less than 1000 or greater than 9999. We allow
1017 // any year, even negative ones. The year is formatted as "%.4d".
1018 
1019 // *** Need to check backward compatibility for partial forms!
1020 
1021 /* class static */ void
ConvertFromDate(const XMP_DateTime & binValue,XMP_StringPtr * strValue,XMP_StringLen * strSize)1022 XMPUtils::ConvertFromDate ( const XMP_DateTime & binValue,
1023 							XMP_StringPtr *		 strValue,
1024 							XMP_StringLen *		 strSize )
1025 {
1026 	XMP_Assert ( (strValue != 0) && (strSize != 0) );	// Enforced by wrapper.
1027 
1028 	bool addTimeZone = false;
1029 	char buffer [100];	// Plenty long enough.
1030 
1031 	// Pick the format, use snprintf to format into a local buffer, assign to static output string.
1032 	// Don't use AdjustTimeOverflow at the start, that will wipe out zero month or day values.
1033 
1034 	// ! Photoshop 8 creates "time only" values with zeros for year, month, and day.
1035 
1036 	XMP_DateTime tempDate = binValue;
1037 
1038 	// Temporary fix for bug 1269463, silently fix out of range month or day.
1039 
1040 	bool haveDay  = (tempDate.day != 0);
1041 	bool haveTime = ( (tempDate.hour != 0)   || (tempDate.minute != 0) ||
1042 			          (tempDate.second != 0) || (tempDate.nanoSecond != 0) ||
1043 			          (tempDate.tzSign != 0) || (tempDate.tzHour != 0) || (tempDate.tzMinute != 0) );
1044 
1045 	if ( tempDate.month == 0 ) {
1046 		if ( haveDay || haveTime ) tempDate.month = 1;
1047 	} else {
1048 		if ( tempDate.month < 1 ) tempDate.month = 1;
1049 		if ( tempDate.month > 12 ) tempDate.month = 12;
1050 	}
1051 
1052 	if ( tempDate.day == 0 ) {
1053 		if ( haveTime ) tempDate.day = 1;
1054 	} else {
1055 		if ( tempDate.day < 1 ) tempDate.day = 1;
1056 		if ( tempDate.day > 31 ) tempDate.day = 31;
1057 	}
1058 
1059 	// Now carry on with the original logic.
1060 
1061 	if ( tempDate.month == 0 ) {
1062 
1063 		// Output YYYY if all else is zero, otherwise output a full string for the quasi-bogus
1064 		// "time only" values from Photoshop CS.
1065 		if ( (tempDate.day == 0) && (tempDate.hour == 0) && (tempDate.minute == 0) &&
1066 			 (tempDate.second == 0) && (tempDate.nanoSecond == 0) &&
1067 			 (tempDate.tzSign == 0) && (tempDate.tzHour == 0) && (tempDate.tzMinute == 0) ) {
1068                     snprintf ( buffer, sizeof(buffer), "%.4d", static_cast<int>(tempDate.year) ); // AUDIT: Using sizeof for snprintf length is safe.
1069 		} else if ( (tempDate.year == 0) && (tempDate.day == 0) ) {
1070 			FormatFullDateTime ( tempDate, buffer, sizeof(buffer) );
1071 			addTimeZone = true;
1072 		} else {
1073 			XMP_Throw ( "Invalid partial date", kXMPErr_BadParam);
1074 		}
1075 
1076 	} else if ( tempDate.day == 0 ) {
1077 
1078 		// Output YYYY-MM.
1079 		if ( (tempDate.month < 1) || (tempDate.month > 12) ) XMP_Throw ( "Month is out of range", kXMPErr_BadParam);
1080 		if ( (tempDate.hour != 0) || (tempDate.minute != 0) ||
1081 			 (tempDate.second != 0) || (tempDate.nanoSecond != 0) ||
1082 			 (tempDate.tzSign != 0) || (tempDate.tzHour != 0) || (tempDate.tzMinute != 0) ) {
1083 			XMP_Throw ( "Invalid partial date, non-zeros after zero month and day", kXMPErr_BadParam);
1084 		}
1085 		snprintf ( buffer, sizeof(buffer), "%.4d-%02d", static_cast<int>(tempDate.year), static_cast<int>(tempDate.month) );	// AUDIT: Using sizeof for snprintf length is safe.
1086 
1087 	} else if ( (tempDate.hour == 0) && (tempDate.minute == 0) &&
1088 				(tempDate.second == 0) && (tempDate.nanoSecond == 0) &&
1089 				(tempDate.tzSign == 0) && (tempDate.tzHour == 0) && (tempDate.tzMinute == 0) ) {
1090 
1091 		// Output YYYY-MM-DD.
1092 		if ( (tempDate.month < 1) || (tempDate.month > 12) ) XMP_Throw ( "Month is out of range", kXMPErr_BadParam);
1093 		if ( (tempDate.day < 1) || (tempDate.day > 31) ) XMP_Throw ( "Day is out of range", kXMPErr_BadParam);
1094 		snprintf ( buffer, sizeof(buffer), "%.4d-%02d-%02d", static_cast<int>(tempDate.year), static_cast<int>(tempDate.month), static_cast<int>(tempDate.day) ); // AUDIT: Using sizeof for snprintf length is safe.
1095 
1096 	} else {
1097 
1098 		FormatFullDateTime ( tempDate, buffer, sizeof(buffer) );
1099 		addTimeZone = true;
1100 
1101 	}
1102 
1103 	sConvertedValue->assign ( buffer );
1104 
1105 	if ( addTimeZone ) {
1106 
1107 		if ( (tempDate.tzHour < 0) || (tempDate.tzHour > 23) ||
1108 			 (tempDate.tzMinute < 0 ) || (tempDate.tzMinute > 59) ||
1109 			 (tempDate.tzSign < -1) || (tempDate.tzSign > +1) ||
1110 			 ((tempDate.tzSign != 0) && (tempDate.tzHour == 0) && (tempDate.tzMinute == 0)) ||
1111 			 ((tempDate.tzSign == 0) && ((tempDate.tzHour != 0) || (tempDate.tzMinute != 0))) ) {
1112 			XMP_Throw ( "Invalid time zone values", kXMPErr_BadParam );
1113 		}
1114 
1115 		if ( tempDate.tzSign == 0 ) {
1116 			*sConvertedValue += 'Z';
1117 		} else {
1118                     snprintf ( buffer, sizeof(buffer), "+%02d:%02d", static_cast<int>(tempDate.tzHour), static_cast<int>(tempDate.tzMinute) );	// AUDIT: Using sizeof for snprintf length is safe.
1119 			if ( tempDate.tzSign < 0 ) buffer[0] = '-';
1120 			*sConvertedValue += buffer;
1121 		}
1122 
1123 	}
1124 
1125 	*strValue = sConvertedValue->c_str();
1126 	*strSize  = sConvertedValue->size();
1127 
1128 }	// ConvertFromDate
1129 
1130 
1131 // -------------------------------------------------------------------------------------------------
1132 // ConvertToBool
1133 // -------------
1134 //
1135 // Formally the string value should be "True" or "False", but we should be more flexible here. Map
1136 // the string to lower case. Allow any of "true", "false", "t", "f", "1", or "0".
1137 
1138 /* class static */ bool
ConvertToBool(XMP_StringPtr strValue)1139 XMPUtils::ConvertToBool ( XMP_StringPtr strValue )
1140 {
1141 	if ( (strValue == 0) || (*strValue == 0) ) XMP_Throw ( "Empty convert-from string", kXMPErr_BadValue );
1142 
1143 	bool result = false;
1144 	XMP_VarString strObj ( strValue );
1145 
1146 	for ( XMP_VarStringPos ch = strObj.begin(); ch != strObj.end(); ++ch ) {
1147 		if ( ('A' <= *ch) && (*ch <= 'Z') ) *ch += 0x20;
1148 	}
1149 
1150 	if ( (strObj == "true") || (strObj == "t") || (strObj == "1") ) {
1151 		result = true;
1152 	} else if ( (strObj == "false") || (strObj == "f") || (strObj == "0") ) {
1153 		result = false;
1154 	} else {
1155 		XMP_Throw ( "Invalid Boolean string", kXMPErr_BadParam );
1156 	}
1157 
1158 	return result;
1159 
1160 }	// ConvertToBool
1161 
1162 
1163 // -------------------------------------------------------------------------------------------------
1164 // ConvertToInt
1165 // ------------
1166 
1167 /* class static */ XMP_Int32
ConvertToInt(XMP_StringPtr strValue)1168 XMPUtils::ConvertToInt ( XMP_StringPtr strValue )
1169 {
1170 	if ( (strValue == 0) || (*strValue == 0) ) XMP_Throw ( "Empty convert-from string", kXMPErr_BadValue );
1171 
1172 	int count;
1173 	char nextCh;
1174 	XMP_Int32 result;
1175 
1176 	if ( ! XMP_LitNMatch ( strValue, "0x", 2 ) ) {
1177             count = sscanf ( strValue, "%d%c", (int*)&result, &nextCh );
1178 	} else {
1179             count = sscanf ( strValue, "%x%c", (unsigned int*)&result, &nextCh );
1180 	}
1181 
1182 	if ( count != 1 ) XMP_Throw ( "Invalid integer string", kXMPErr_BadParam );
1183 
1184 	return result;
1185 
1186 }	// ConvertToInt
1187 
1188 
1189 // -------------------------------------------------------------------------------------------------
1190 // ConvertToInt64
1191 // --------------
1192 
1193 /* class static */ XMP_Int64
ConvertToInt64(XMP_StringPtr strValue)1194 XMPUtils::ConvertToInt64 ( XMP_StringPtr strValue )
1195 {
1196 #if defined(__MINGW32__)// || defined(__MINGW64__)
1197     return ConvertToInt(strValue);
1198 #else
1199 	if ( (strValue == 0) || (*strValue == 0) ) XMP_Throw ( "Empty convert-from string", kXMPErr_BadValue );
1200 
1201 	int count;
1202 	char nextCh;
1203 	XMP_Int64 result;
1204 
1205 	if ( ! XMP_LitNMatch ( strValue, "0x", 2 ) ) {
1206 		count = sscanf ( strValue, "%lld%c", &result, &nextCh );
1207 	} else {
1208 		count = sscanf ( strValue, "%llx%c", &result, &nextCh );
1209 	}
1210 
1211 	if ( count != 1 ) XMP_Throw ( "Invalid integer string", kXMPErr_BadParam );
1212 
1213 	return result;
1214 #endif
1215 }	// ConvertToInt64
1216 
1217 
1218 // -------------------------------------------------------------------------------------------------
1219 // ConvertToFloat
1220 // --------------
1221 
1222 /* class static */ double
ConvertToFloat(XMP_StringPtr strValue)1223 XMPUtils::ConvertToFloat ( XMP_StringPtr strValue )
1224 {
1225 	if ( (strValue == 0) || (*strValue == 0) ) XMP_Throw ( "Empty convert-from string", kXMPErr_BadValue );
1226 
1227 	XMP_VarString oldLocale;	// Try to make sure number conversion uses '.' as the decimal point.
1228 	XMP_StringPtr oldLocalePtr = setlocale ( LC_ALL, 0 );
1229 	if ( oldLocalePtr != 0 ) {
1230 		oldLocale.assign ( oldLocalePtr );
1231 		setlocale ( LC_ALL, "C" );
1232 	}
1233 
1234 	errno = 0;
1235 	char * numEnd;
1236 	double result = strtod ( strValue, &numEnd );
1237 
1238 	if ( oldLocalePtr != 0 ) setlocale ( LC_ALL, oldLocalePtr );	// ! Reset locale before possible throw!
1239 	if ( (errno != 0) || (*numEnd != 0) ) XMP_Throw ( "Invalid float string", kXMPErr_BadParam );
1240 
1241 	return result;
1242 
1243 }	// ConvertToFloat
1244 
1245 
1246 // -------------------------------------------------------------------------------------------------
1247 // ConvertToDate
1248 // -------------
1249 //
1250 // Parse a date according to ISO 8601 and http://www.w3.org/TR/NOTE-datetime:
1251 //	YYYY
1252 //	YYYY-MM
1253 //	YYYY-MM-DD
1254 //	YYYY-MM-DDThh:mmTZD
1255 //	YYYY-MM-DDThh:mm:ssTZD
1256 //	YYYY-MM-DDThh:mm:ss.sTZD
1257 //
1258 //	YYYY = four-digit year
1259 //	MM	 = two-digit month (01=January, etc.)
1260 //	DD	 = two-digit day of month (01 through 31)
1261 //	hh	 = two digits of hour (00 through 23)
1262 //	mm	 = two digits of minute (00 through 59)
1263 //	ss	 = two digits of second (00 through 59)
1264 //	s	 = one or more digits representing a decimal fraction of a second
1265 //	TZD	 = time zone designator (Z or +hh:mm or -hh:mm)
1266 //
1267 // Note that ISO 8601 does not seem to allow years less than 1000 or greater than 9999. We allow
1268 // any year, even negative ones. The year is formatted as "%.4d".
1269 
1270 // ! Tolerate missing TZD, assume the time is in local time
1271 // ! Tolerate missing date portion, in case someone foolishly writes a time-only value that way.
1272 
1273 // *** Put the ISO format comments in the header documentation.
1274 
1275 /* class static */ void
ConvertToDate(XMP_StringPtr strValue,XMP_DateTime * binValue)1276 XMPUtils::ConvertToDate ( XMP_StringPtr	 strValue,
1277 						  XMP_DateTime * binValue )
1278 {
1279 	if ( (strValue == 0) || (*strValue == 0) ) XMP_Throw ( "Empty convert-from string", kXMPErr_BadValue);
1280 
1281 	size_t	 pos = 0;
1282 	XMP_Int32 temp;
1283 
1284 	XMP_Assert ( sizeof(*binValue) == sizeof(XMP_DateTime) );
1285 	(void) memset ( binValue, 0, sizeof(*binValue) );	// AUDIT: Safe, using sizeof destination.
1286 
1287 	bool timeOnly = ( (strValue[0] == 'T') ||
1288 					  ((strlen(strValue) >= 2) && (strValue[1] == ':')) ||
1289 					  ((strlen(strValue) >= 3) && (strValue[2] == ':')) );
1290 
1291 	if ( ! timeOnly ) {
1292 
1293 		if ( strValue[0] == '-' ) pos = 1;
1294 
1295 		temp = GatherInt ( strValue, &pos, "Invalid year in date string" ); // Extract the year.
1296 		if ( (strValue[pos] != 0) && (strValue[pos] != '-') ) XMP_Throw ( "Invalid date string, after year", kXMPErr_BadParam );
1297 		if ( strValue[0] == '-' ) temp = -temp;
1298 		binValue->year = temp;
1299 		if ( strValue[pos] == 0 ) return;
1300 
1301 		++pos;
1302 		temp = GatherInt ( strValue, &pos, "Invalid month in date string" );	// Extract the month.
1303 		if ( (strValue[pos] != 0) && (strValue[pos] != '-') ) XMP_Throw ( "Invalid date string, after month", kXMPErr_BadParam );
1304 		binValue->month = temp;
1305 		if ( strValue[pos] == 0 ) return;
1306 
1307 		++pos;
1308 		temp = GatherInt ( strValue, &pos, "Invalid day in date string" );	// Extract the day.
1309 		if ( (strValue[pos] != 0) && (strValue[pos] != 'T') ) XMP_Throw ( "Invalid date string, after day", kXMPErr_BadParam );
1310 		binValue->day = temp;
1311 		if ( strValue[pos] == 0 ) return;
1312 
1313 		// Allow year, month, and day to all be zero; implies the date portion is missing.
1314 		if ( (binValue->year != 0) || (binValue->month != 0) || (binValue->day != 0) ) {
1315 			// Temporary fix for bug 1269463, silently fix out of range month or day.
1316 			// if ( (binValue->month < 1) || (binValue->month > 12) ) XMP_Throw ( "Month is out of range", kXMPErr_BadParam );
1317 			// if ( (binValue->day < 1) || (binValue->day > 31) ) XMP_Throw ( "Day is out of range", kXMPErr_BadParam );
1318 			if ( binValue->month < 1 ) binValue->month = 1;
1319 			if ( binValue->month > 12 ) binValue->month = 12;
1320 			if ( binValue->day < 1 ) binValue->day = 1;
1321 			if ( binValue->day > 31 ) binValue->day = 31;
1322 		}
1323 
1324 	}
1325 
1326 	if ( strValue[pos] == 'T' ) {
1327 		++pos;
1328 	} else if ( ! timeOnly ) {
1329 		XMP_Throw ( "Invalid date string, missing 'T' after date", kXMPErr_BadParam );
1330 	}
1331 
1332 	temp = GatherInt ( strValue, &pos, "Invalid hour in date string" ); // Extract the hour.
1333 	if ( strValue[pos] != ':' ) XMP_Throw ( "Invalid date string, after hour", kXMPErr_BadParam );
1334 	if ( temp > 23 ) temp = 23;	// *** 1269463: XMP_Throw ( "Hour is out of range", kXMPErr_BadParam );
1335 	binValue->hour = temp;
1336 	// Don't check for done, we have to work up to the time zone.
1337 
1338 	++pos;
1339 	temp = GatherInt ( strValue, &pos, "Invalid minute in date string" );	// And the minute.
1340 	if ( (strValue[pos] != ':') && (strValue[pos] != 'Z') &&
1341 		 (strValue[pos] != '+') && (strValue[pos] != '-') && (strValue[pos] != 0) ) XMP_Throw ( "Invalid date string, after minute", kXMPErr_BadParam );
1342 	if ( temp > 59 ) temp = 59;	// *** 1269463: XMP_Throw ( "Minute is out of range", kXMPErr_BadParam );
1343 	binValue->minute = temp;
1344 	// Don't check for done, we have to work up to the time zone.
1345 
1346 	if ( strValue[pos] == ':' ) {
1347 
1348 		++pos;
1349 		temp = GatherInt ( strValue, &pos, "Invalid whole seconds in date string" );	// Extract the whole seconds.
1350 		if ( (strValue[pos] != '.') && (strValue[pos] != 'Z') &&
1351 			 (strValue[pos] != '+') && (strValue[pos] != '-') && (strValue[pos] != 0) ) {
1352 			XMP_Throw ( "Invalid date string, after whole seconds", kXMPErr_BadParam );
1353 		}
1354 		if ( temp > 59 ) temp = 59;	// *** 1269463: XMP_Throw ( "Whole second is out of range", kXMPErr_BadParam );
1355 		binValue->second = temp;
1356 		// Don't check for done, we have to work up to the time zone.
1357 
1358 		if ( strValue[pos] == '.' ) {
1359 
1360 			++pos;
1361 			size_t digits = pos;	// Will be the number of digits later.
1362 
1363 			temp = GatherInt ( strValue, &pos, "Invalid fractional seconds in date string" );	// Extract the fractional seconds.
1364 			if ( (strValue[pos] != 'Z') && (strValue[pos] != '+') && (strValue[pos] != '-') && (strValue[pos] != 0) ) {
1365 				XMP_Throw ( "Invalid date string, after fractional second", kXMPErr_BadParam );
1366 			}
1367 
1368 			digits = pos - digits;
1369 			for ( ; digits > 9; --digits ) temp = temp / 10;
1370 			for ( ; digits < 9; ++digits ) temp = temp * 10;
1371 
1372 			if ( temp >= 1000*1000*1000 ) XMP_Throw ( "Fractional second is out of range", kXMPErr_BadParam );
1373 			binValue->nanoSecond = temp;
1374 			// Don't check for done, we have to work up to the time zone.
1375 
1376 		}
1377 
1378 	}
1379 
1380 	if ( strValue[pos] == 'Z' ) {
1381 
1382 		++pos;
1383 
1384 	} else if ( strValue[pos] != 0 ) {
1385 
1386 		if ( strValue[pos] == '+' ) {
1387 			binValue->tzSign = kXMP_TimeEastOfUTC;
1388 		} else if ( strValue[pos] == '-' ) {
1389 			binValue->tzSign = kXMP_TimeWestOfUTC;
1390 		} else {
1391 			XMP_Throw ( "Time zone must begin with 'Z', '+', or '-'", kXMPErr_BadParam );
1392 		}
1393 
1394 		++pos;
1395 		temp = GatherInt ( strValue, &pos, "Invalid time zone hour in date string" );	// Extract the time zone hour.
1396 		if ( strValue[pos] != ':' ) XMP_Throw ( "Invalid date string, after time zone hour", kXMPErr_BadParam );
1397 		if ( temp > 23 ) XMP_Throw ( "Time zone hour is out of range", kXMPErr_BadParam );
1398 		binValue->tzHour = temp;
1399 
1400 		++pos;
1401 		temp = GatherInt ( strValue, &pos, "Invalid time zone minute in date string" ); // Extract the time zone minute.
1402 		if ( temp > 59 ) XMP_Throw ( "Time zone minute is out of range", kXMPErr_BadParam );
1403 		binValue->tzMinute = temp;
1404 
1405 	} else {
1406 
1407 		XMPUtils::SetTimeZone( binValue );
1408 
1409 	}
1410 
1411 	if ( strValue[pos] != 0 ) XMP_Throw ( "Invalid date string, extra chars at end", kXMPErr_BadParam );
1412 
1413 }	// ConvertToDate
1414 
1415 
1416 // -------------------------------------------------------------------------------------------------
1417 // EncodeToBase64
1418 // --------------
1419 //
1420 // Encode a string of raw data bytes in base 64 according to RFC 2045. For the encoding definition
1421 // see section 6.8 in <http://www.ietf.org/rfc/rfc2045.txt>. Although it isn't needed for RDF, we
1422 // do insert a linefeed character as a newline for every 76 characters of encoded output.
1423 
1424 /* class static */ void
EncodeToBase64(XMP_StringPtr rawStr,XMP_StringLen rawLen,XMP_StringPtr * encodedStr,XMP_StringLen * encodedLen)1425 XMPUtils::EncodeToBase64 ( XMP_StringPtr   rawStr,
1426 						   XMP_StringLen   rawLen,
1427 						   XMP_StringPtr * encodedStr,
1428 						   XMP_StringLen * encodedLen )
1429 {
1430 	if ( (rawStr == 0) && (rawLen != 0) ) XMP_Throw ( "Null raw data buffer", kXMPErr_BadParam );
1431 	if ( rawLen == 0 ) {
1432 		*encodedStr = 0;
1433 		*encodedLen = 0;
1434 		return;
1435 	}
1436 
1437 	char	encChunk[4];
1438 
1439 	unsigned long	in, out;
1440 	unsigned char	c1, c2, c3;
1441 	unsigned long	merge;
1442 
1443 	const size_t	outputSize	= (rawLen / 3) * 4; // Approximate, might be  small.
1444 
1445 	sBase64Str->erase();
1446 	sBase64Str->reserve ( outputSize );
1447 
1448 	// ----------------------------------------------------------------------------------------
1449 	// Each 6 bits of input produces 8 bits of output, so 3 input bytes become 4 output bytes.
1450 	// Process the whole chunks of 3 bytes first, then deal with any remainder. Be careful with
1451 	// the loop comparison, size-2 could be negative!
1452 
1453 	for ( in = 0, out = 0; (in+2) < rawLen; in += 3, out += 4 ) {
1454 
1455 		c1	= rawStr[in];
1456 		c2	= rawStr[in+1];
1457 		c3	= rawStr[in+2];
1458 
1459 		merge	= (c1 << 16) + (c2 << 8) + c3;
1460 
1461 		encChunk[0] = sBase64Chars [ merge >> 18 ];
1462 		encChunk[1] = sBase64Chars [ (merge >> 12) & 0x3F ];
1463 		encChunk[2] = sBase64Chars [ (merge >> 6) & 0x3F ];
1464 		encChunk[3] = sBase64Chars [ merge & 0x3F ];
1465 
1466 		if ( out >= 76 ) {
1467 			sBase64Str->append ( 1, kLF );
1468 			out = 0;
1469 		}
1470 		sBase64Str->append ( encChunk, 4 );
1471 
1472 	}
1473 
1474 	// ------------------------------------------------------------------------------------------
1475 	// The output must always be a multiple of 4 bytes. If there is a 1 or 2 byte input remainder
1476 	// we need to create another chunk. Zero pad with bits to a 6 bit multiple, then add one or
1477 	// two '=' characters to pad out to 4 bytes.
1478 
1479 	switch ( rawLen - in ) {
1480 
1481 		case 0:		// Done, no remainder.
1482 			break;
1483 
1484 		case 1:		// One input byte remains.
1485 
1486 			c1	= rawStr[in];
1487 			merge	= c1 << 16;
1488 
1489 			encChunk[0] = sBase64Chars [ merge >> 18 ];
1490 			encChunk[1] = sBase64Chars [ (merge >> 12) & 0x3F ];
1491 			encChunk[2] = encChunk[3] = '=';
1492 
1493 			if ( out >= 76 ) sBase64Str->append ( 1, kLF );
1494 			sBase64Str->append ( encChunk, 4 );
1495 			break;
1496 
1497 		case 2:		// Two input bytes remain.
1498 
1499 			c1	= rawStr[in];
1500 			c2	= rawStr[in+1];
1501 			merge	= (c1 << 16) + (c2 << 8);
1502 
1503 			encChunk[0] = sBase64Chars [ merge >> 18 ];
1504 			encChunk[1] = sBase64Chars [ (merge >> 12) & 0x3F ];
1505 			encChunk[2] = sBase64Chars [ (merge >> 6) & 0x3F ];
1506 			encChunk[3] = '=';
1507 
1508 			if ( out >= 76 ) sBase64Str->append ( 1, kLF );
1509 			sBase64Str->append ( encChunk, 4 );
1510 			break;
1511 
1512 	}
1513 
1514 	// -------------------------
1515 	// Assign the output values.
1516 
1517 	*encodedStr = sBase64Str->c_str();
1518 	*encodedLen = sBase64Str->size();
1519 
1520 }	// EncodeToBase64
1521 
1522 
1523 // -------------------------------------------------------------------------------------------------
1524 // DecodeFromBase64
1525 // ----------------
1526 //
1527 // Decode a string of raw data bytes from base 64 according to RFC 2045. For the encoding definition
1528 // see section 6.8 in <http://www.ietf.org/rfc/rfc2045.txt>. RFC 2045 talks about ignoring all "bad"
1529 // input but warning about non-whitespace. For XMP use we ignore space, tab, LF, and CR. Any other
1530 // bad input is rejected.
1531 
1532 /* class static */ void
DecodeFromBase64(XMP_StringPtr encodedStr,XMP_StringLen encodedLen,XMP_StringPtr * rawStr,XMP_StringLen * rawLen)1533 XMPUtils::DecodeFromBase64 ( XMP_StringPtr	 encodedStr,
1534 							 XMP_StringLen	 encodedLen,
1535 							 XMP_StringPtr * rawStr,
1536 							 XMP_StringLen * rawLen )
1537 {
1538 	if ( (encodedStr == 0) && (encodedLen != 0) ) XMP_Throw ( "Null encoded data buffer", kXMPErr_BadParam );
1539 	if ( encodedLen == 0 ) {
1540 		*rawStr = 0;
1541 		*rawLen = 0;
1542 		return;
1543 	}
1544 
1545 	unsigned char	ch, rawChunk[3];
1546 	unsigned long	inStr, inChunk, inLimit, merge, padding;
1547 
1548 	XMP_StringLen	outputSize	= (encodedLen / 4) * 3; // Only a close approximation.
1549 
1550 	sBase64Str->erase();
1551 	sBase64Str->reserve ( outputSize );
1552 
1553 
1554 	// ----------------------------------------------------------------------------------------
1555 	// Each 8 bits of input produces 6 bits of output, so 4 input bytes become 3 output bytes.
1556 	// Process all but the last 4 data bytes first, then deal with the final chunk. Whitespace
1557 	// in the input must be ignored. The first loop finds where the last 4 data bytes start and
1558 	// counts the number of padding equal signs.
1559 
1560 	padding = 0;
1561 	for ( inStr = 0, inLimit = encodedLen; (inStr < 4) && (inLimit > 0); ) {
1562 		inLimit -= 1;	// ! Don't do in the loop control, the decr/test order is wrong.
1563 		ch = encodedStr[inLimit];
1564 		if ( ch == '=' ) {
1565 			padding += 1;	// The equal sign padding is a data byte.
1566 		} else if ( DecodeBase64Char(ch) == 0xFF ) {
1567 			continue;	// Ignore whitespace, don't increment inStr.
1568 		} else {
1569 			inStr += 1;
1570 		}
1571 	}
1572 
1573 	// ! Be careful to count whitespace that is immediately before the final data. Otherwise
1574 	// ! middle portion will absorb the final data and mess up the final chunk processing.
1575 
1576 	while ( (inLimit > 0) && (DecodeBase64Char(encodedStr[inLimit-1]) == 0xFF) ) --inLimit;
1577 
1578 	if ( inStr == 0 ) return;	// Nothing but whitespace.
1579 	if ( padding > 2 ) XMP_Throw ( "Invalid encoded string", kXMPErr_BadParam );
1580 
1581 	// -------------------------------------------------------------------------------------------
1582 	// Now process all but the last chunk. The limit ensures that we have at least 4 data bytes
1583 	// left when entering the output loop, so the inner loop will succeed without overrunning the
1584 	// end of the data. At the end of the outer loop we might be past inLimit though.
1585 
1586 	inStr = 0;
1587 	while ( inStr < inLimit ) {
1588 
1589 		merge = 0;
1590 		for ( inChunk = 0; inChunk < 4; ++inStr ) { // ! Yes, increment inStr on each pass.
1591 			ch = DecodeBase64Char ( encodedStr [inStr] );
1592 			if ( ch == 0xFF ) continue; // Ignore whitespace.
1593 			merge = (merge << 6) + ch;
1594 			inChunk += 1;
1595 		}
1596 
1597 		rawChunk[0] = (unsigned char) (merge >> 16);
1598 		rawChunk[1] = (unsigned char) ((merge >> 8) & 0xFF);
1599 		rawChunk[2] = (unsigned char) (merge & 0xFF);
1600 
1601 		sBase64Str->append ( (char*)rawChunk, 3 );
1602 
1603 	}
1604 
1605 	// -------------------------------------------------------------------------------------------
1606 	// Process the final, possibly partial, chunk of data. The input is always a multiple 4 bytes,
1607 	// but the raw data can be any length. The number of padding '=' characters determines if the
1608 	// final chunk has 1, 2, or 3 raw data bytes.
1609 
1610 	XMP_Assert ( inStr < encodedLen );
1611 
1612 	merge = 0;
1613 	for ( inChunk = 0; inChunk < 4-padding; ++inStr ) { // ! Yes, increment inStr on each pass.
1614 		ch = DecodeBase64Char ( encodedStr[inStr] );
1615 		if ( ch == 0xFF ) continue; // Ignore whitespace.
1616 		merge = (merge << 6) + ch;
1617 		inChunk += 1;
1618 	}
1619 
1620 	if ( padding == 2 ) {
1621 
1622 		rawChunk[0] = (unsigned char) (merge >> 4);
1623 		sBase64Str->append ( (char*)rawChunk, 1 );
1624 
1625 	} else if ( padding == 1 ) {
1626 
1627 		rawChunk[0] = (unsigned char) (merge >> 10);
1628 		rawChunk[1] = (unsigned char) ((merge >> 2) & 0xFF);
1629 		sBase64Str->append ( (char*)rawChunk, 2 );
1630 
1631 	} else {
1632 
1633 		rawChunk[0] = (unsigned char) (merge >> 16);
1634 		rawChunk[1] = (unsigned char) ((merge >> 8) & 0xFF);
1635 		rawChunk[2] = (unsigned char) (merge & 0xFF);
1636 		sBase64Str->append ( (char*)rawChunk, 3 );
1637 
1638 	}
1639 
1640 	// -------------------------
1641 	// Assign the output values.
1642 
1643 	*rawStr = sBase64Str->c_str();
1644 	*rawLen = sBase64Str->size();
1645 
1646 }	// DecodeFromBase64
1647 
1648 
1649 // -------------------------------------------------------------------------------------------------
1650 // PackageForJPEG
1651 // --------------
1652 
1653 /* class static */ void
PackageForJPEG(const XMPMeta & origXMP,XMP_StringPtr * stdStr,XMP_StringLen * stdLen,XMP_StringPtr * extStr,XMP_StringLen * extLen,XMP_StringPtr * digestStr,XMP_StringLen * digestLen)1654 XMPUtils::PackageForJPEG ( const XMPMeta & origXMP,
1655 						   XMP_StringPtr * stdStr,
1656 						   XMP_StringLen * stdLen,
1657 						   XMP_StringPtr * extStr,
1658 						   XMP_StringLen * extLen,
1659 						   XMP_StringPtr * digestStr,
1660 						   XMP_StringLen * digestLen )
1661 {
1662 	enum { kStdXMPLimit = 65000 };
1663 	static const char * kPacketTrailer = "<?xpacket end=\"w\"?>";
1664 	static size_t kTrailerLen = strlen ( kPacketTrailer );
1665 
1666 	XMP_StringPtr tempStr;
1667 	XMP_StringLen tempLen;
1668 
1669 	XMPMeta stdXMP, extXMP;
1670 
1671 	sStandardXMP->clear();	// Clear the static strings that get returned to the client.
1672 	sExtendedXMP->clear();
1673 	sExtendedDigest->clear();
1674 
1675 	XMP_OptionBits keepItSmall = kXMP_UseCompactFormat | kXMP_OmitAllFormatting;
1676 
1677 	// Try to serialize everything. Note that we're making internal calls to SerializeToBuffer, so
1678 	// we'll be getting back the pointer and length for its internal string.
1679 
1680 	origXMP.SerializeToBuffer ( &tempStr, &tempLen, keepItSmall, 1, "", "", 0 );
1681 	#if Trace_PackageForJPEG
1682 		printf ( "\nXMPUtils::PackageForJPEG - Full serialize %d bytes\n", tempLen );
1683 	#endif
1684 
1685 	if ( tempLen > kStdXMPLimit ) {
1686 
1687 		// Couldn't fit everything, make a copy of the input XMP and make sure there is no xmp:Thumbnails property.
1688 
1689 		stdXMP.tree.options = origXMP.tree.options;
1690 		stdXMP.tree.name    = origXMP.tree.name;
1691 		stdXMP.tree.value   = origXMP.tree.value;
1692 		CloneOffspring ( &origXMP.tree, &stdXMP.tree );
1693 
1694 		if ( stdXMP.DoesPropertyExist ( kXMP_NS_XMP, "Thumbnails" ) ) {
1695 			stdXMP.DeleteProperty ( kXMP_NS_XMP, "Thumbnails" );
1696 			stdXMP.SerializeToBuffer ( &tempStr, &tempLen, keepItSmall, 1, "", "", 0 );
1697 			#if Trace_PackageForJPEG
1698 				printf ( "  Delete xmp:Thumbnails, %d bytes left\n", tempLen );
1699 			#endif
1700 		}
1701 
1702 	}
1703 
1704 	if ( tempLen > kStdXMPLimit ) {
1705 
1706 		// Still doesn't fit, move all of the Camera Raw namespace. Add a dummy value for xmpNote:HasExtendedXMP.
1707 
1708 		stdXMP.SetProperty ( kXMP_NS_XMP_Note, "HasExtendedXMP", "123456789-123456789-123456789-12", 0 );
1709 
1710 		XMP_NodePtrPos crSchemaPos;
1711 		XMP_Node * crSchema = FindSchemaNode ( &stdXMP.tree, kXMP_NS_CameraRaw, kXMP_ExistingOnly, &crSchemaPos );
1712 
1713 		if ( crSchema != 0 ) {
1714 			crSchema->parent = &extXMP.tree;
1715 			extXMP.tree.children.push_back ( crSchema );
1716 			stdXMP.tree.children.erase ( crSchemaPos );
1717 			stdXMP.SerializeToBuffer ( &tempStr, &tempLen, keepItSmall, 1, "", "", 0 );
1718 			#if Trace_PackageForJPEG
1719 				printf ( "  Move Camera Raw schema, %d bytes left\n", tempLen );
1720 			#endif
1721 		}
1722 
1723 	}
1724 
1725 	if ( tempLen > kStdXMPLimit ) {
1726 
1727 		// Still doesn't fit, move photoshop:History.
1728 
1729 		bool moved = MoveOneProperty ( stdXMP, &extXMP, kXMP_NS_Photoshop, "photoshop:History" );
1730 
1731 		if ( moved ) {
1732 			stdXMP.SerializeToBuffer ( &tempStr, &tempLen, keepItSmall, 1, "", "", 0 );
1733 			#if Trace_PackageForJPEG
1734 				printf ( "  Move photoshop:History, %d bytes left\n", tempLen );
1735 			#endif
1736 		}
1737 
1738 	}
1739 
1740 	if ( tempLen > kStdXMPLimit ) {
1741 
1742 		// Still doesn't fit, move top level properties in order of estimated size. This is done by
1743 		// creating a multi-map that maps the serialized size to the string pair for the schema URI
1744 		// and top level property name. Since maps are inherently ordered, a reverse iteration of
1745 		// the map can be done to move the largest things first. We use a double loop to keep going
1746 		// until the serialization actually fits, in case the estimates are off.
1747 
1748 		PropSizeMap propSizes;
1749 		CreateEstimatedSizeMap ( stdXMP, &propSizes );
1750 
1751 		#if Trace_PackageForJPEG
1752 		if ( ! propSizes.empty() ) {
1753 			printf ( "  Top level property map, smallest to largest:\n" );
1754 			PropSizeMap::iterator mapPos = propSizes.begin();
1755 			PropSizeMap::iterator mapEnd = propSizes.end();
1756 			for ( ; mapPos != mapEnd; ++mapPos ) {
1757 				size_t propSize = mapPos->first;
1758 				const char * schemaName = mapPos->second.first->c_str();
1759 				const char * propName   = mapPos->second.second->c_str();
1760 				printf ( "    %d bytes, %s in %s\n", propSize, propName, schemaName );
1761 			}
1762 		}
1763 		#endif
1764 
1765 		#if 0	// Trace_PackageForJPEG		*** Xcode 2.3 on 10.4.7 has bugs in backwards iteration
1766 		if ( ! propSizes.empty() ) {
1767 			printf ( "  Top level property map, largest to smallest:\n" );
1768 			PropSizeMap::iterator mapPos   = propSizes.end();
1769 			PropSizeMap::iterator mapBegin = propSizes.begin();
1770 			for ( --mapPos; true; --mapPos ) {
1771 				size_t propSize = mapPos->first;
1772 				const char * schemaName = mapPos->second.first->c_str();
1773 				const char * propName   = mapPos->second.second->c_str();
1774 				printf ( "    %d bytes, %s in %s\n", propSize, propName, schemaName );
1775 				if ( mapPos == mapBegin ) break;
1776 			}
1777 		}
1778 		#endif
1779 
1780 		// Outer loop to make sure enough is actually moved.
1781 
1782 		while ( (tempLen > kStdXMPLimit) && (! propSizes.empty()) ) {
1783 
1784 			// Inner loop, move what seems to be enough according to the estimates.
1785 
1786 			while ( (tempLen > kStdXMPLimit) && (! propSizes.empty()) ) {
1787 
1788 				size_t propSize = MoveLargestProperty ( stdXMP, &extXMP, propSizes );
1789 				XMP_Assert ( propSize > 0 );
1790 
1791 				if ( propSize > tempLen ) propSize = tempLen;	// ! Don't go negative.
1792 				tempLen -= propSize;
1793 
1794 			}
1795 
1796 			// Reserialize the remaining standard XMP.
1797 
1798 			stdXMP.SerializeToBuffer ( &tempStr, &tempLen, keepItSmall, 1, "", "", 0 );
1799 
1800 		}
1801 
1802 	}
1803 
1804 	if ( tempLen > kStdXMPLimit ) {
1805 		// Still doesn't fit, throw an exception and let the client decide what to do.
1806 		// ! This should never happen with the policy of moving any and all top level properties.
1807 		XMP_Throw ( "Can't reduce XMP enough for JPEG file", kXMPErr_TooLargeForJPEG );
1808 	}
1809 
1810 	// Set the static output strings.
1811 
1812 	if ( extXMP.tree.children.empty() ) {
1813 
1814 		// Just have the standard XMP.
1815 		sStandardXMP->assign ( tempStr, tempLen );
1816 
1817 	} else {
1818 
1819 		// Have extended XMP. Serialize it, compute the digest, reset xmpNote:HasExtendedXMP, and
1820 		// reserialize the standard XMP.
1821 
1822 		extXMP.SerializeToBuffer ( &tempStr, &tempLen, (keepItSmall | kXMP_OmitPacketWrapper), 0, "", "", 0 );
1823 		sExtendedXMP->assign ( tempStr, tempLen );
1824 
1825 		MD5_CTX  context;
1826 		XMP_Uns8 digest [16];
1827 		MD5Init ( &context );
1828 		MD5Update ( &context, (XMP_Uns8*)tempStr, tempLen );
1829 		MD5Final ( digest, &context );
1830 
1831 		sExtendedDigest->reserve ( 32 );
1832 		for ( size_t i = 0; i < 16; ++i ) {
1833 			XMP_Uns8 byte = digest[i];
1834 			sExtendedDigest->push_back ( kHexDigits [ byte>>4 ] );
1835 			sExtendedDigest->push_back ( kHexDigits [ byte&0xF ] );
1836 		}
1837 
1838 		stdXMP.SetProperty ( kXMP_NS_XMP_Note, "HasExtendedXMP", sExtendedDigest->c_str(), 0 );
1839 		stdXMP.SerializeToBuffer ( &tempStr, &tempLen, keepItSmall, 1, "", "", 0 );
1840 		sStandardXMP->assign ( tempStr, tempLen );
1841 
1842 	}
1843 
1844 	// Adjust the standard XMP padding to be up to 2KB.
1845 
1846 	XMP_Assert ( (sStandardXMP->size() > kTrailerLen) && (sStandardXMP->size() <= kStdXMPLimit) );
1847 	const char * packetEnd = 0;
1848     packetEnd = sStandardXMP->c_str() + sStandardXMP->size() - kTrailerLen;
1849 	XMP_Assert ( XMP_LitMatch ( packetEnd, kPacketTrailer ) );
1850 	UNUSED(packetEnd);
1851 
1852 	size_t extraPadding = kStdXMPLimit - sStandardXMP->size();	// ! Do this before erasing the trailer.
1853 	if ( extraPadding > 2047 ) extraPadding = 2047;
1854 	sStandardXMP->erase ( sStandardXMP->size() - kTrailerLen );
1855 	sStandardXMP->append ( extraPadding, ' ' );
1856 	sStandardXMP->append ( kPacketTrailer );
1857 
1858 	// Assign the output pointer and sizes.
1859 
1860 	*stdStr = sStandardXMP->c_str();
1861 	*stdLen = sStandardXMP->size();
1862 	*extStr = sExtendedXMP->c_str();
1863 	*extLen = sExtendedXMP->size();
1864 	*digestStr = sExtendedDigest->c_str();
1865 	*digestLen = sExtendedDigest->size();
1866 
1867 }	// PackageForJPEG
1868 
1869 
1870 // -------------------------------------------------------------------------------------------------
1871 // MergeFromJPEG
1872 // -------------
1873 //
1874 // Copy all of the top level properties from extendedXMP to fullXMP, replacing any duplicates.
1875 // Delete the xmpNote:HasExtendedXMP property from fullXMP.
1876 
1877 /* class static */ void
MergeFromJPEG(XMPMeta * fullXMP,const XMPMeta & extendedXMP)1878 XMPUtils::MergeFromJPEG ( XMPMeta *       fullXMP,
1879                           const XMPMeta & extendedXMP )
1880 {
1881 
1882 	XMPUtils::AppendProperties ( extendedXMP, fullXMP, kXMPUtil_DoAllProperties );
1883 	fullXMP->DeleteProperty ( kXMP_NS_XMP_Note, "HasExtendedXMP" );
1884 
1885 }	// MergeFromJPEG
1886 
1887 
1888 // -------------------------------------------------------------------------------------------------
1889 // CurrentDateTime
1890 // ---------------
1891 
1892 /* class static */ void
CurrentDateTime(XMP_DateTime * xmpTime)1893 XMPUtils::CurrentDateTime ( XMP_DateTime * xmpTime )
1894 {
1895 	XMP_Assert ( xmpTime != 0 );	// ! Enforced by wrapper.
1896 
1897 	ansi_tt binTime = ansi_time(0);
1898 	if ( binTime == -1 ) XMP_Throw ( "Failure from ANSI C time function", kXMPErr_ExternalFailure );
1899 	ansi_tm currTime;
1900 	ansi_localtime ( &binTime, &currTime );
1901 
1902 	xmpTime->year = currTime.tm_year + 1900;
1903 	xmpTime->month = currTime.tm_mon + 1;
1904 	xmpTime->day = currTime.tm_mday;
1905 	xmpTime->hour = currTime.tm_hour;
1906 	xmpTime->minute = currTime.tm_min;
1907 	xmpTime->second = currTime.tm_sec;
1908 
1909 	xmpTime->nanoSecond = 0;
1910 	xmpTime->tzSign = 0;
1911 	xmpTime->tzHour = 0;
1912 	xmpTime->tzMinute = 0;
1913 
1914 	XMPUtils::SetTimeZone ( xmpTime );
1915 
1916 }	// CurrentDateTime
1917 
1918 
1919 // -------------------------------------------------------------------------------------------------
1920 // SetTimeZone
1921 // -----------
1922 //
1923 // Sets just the time zone part of the time.  Useful for determining the local time zone or for
1924 // converting a "zone-less" time to a proper local time. The ANSI C time functions are smart enough
1925 // to do all the right stuff, as long as we call them properly!
1926 
1927 /* class static */ void
SetTimeZone(XMP_DateTime * xmpTime)1928 XMPUtils::SetTimeZone ( XMP_DateTime * xmpTime )
1929 {
1930 	XMP_Assert ( xmpTime != 0 );	// ! Enforced by wrapper.
1931 
1932 	if ( (xmpTime->tzSign != 0) || (xmpTime->tzHour != 0) || (xmpTime->tzMinute != 0) ) {
1933 		XMP_Throw ( "SetTimeZone can only be used on \"zoneless\" times", kXMPErr_BadParam );
1934 	}
1935 
1936 	// Create ansi_tt form of the input time. Need the ansi_tm form to make the ansi_tt form.
1937 
1938 	ansi_tt ttTime;
1939 	ansi_tm tmLocal, tmUTC;
1940 
1941 	if ( (xmpTime->year == 0) && (xmpTime->month == 0) && (xmpTime->day == 0) ) {
1942 		ansi_tt now = ansi_time(0);
1943 		if ( now == -1 ) XMP_Throw ( "Failure from ANSI C time function", kXMPErr_ExternalFailure );
1944 		ansi_localtime ( &now, &tmLocal );
1945 	} else {
1946 		tmLocal.tm_year = xmpTime->year - 1900;
1947 		while ( tmLocal.tm_year < 70 ) tmLocal.tm_year += 4;	// ! Some versions of mktime barf on years before 1970.
1948 		tmLocal.tm_mon	 = xmpTime->month - 1;
1949 		tmLocal.tm_mday	 = xmpTime->day;
1950 	}
1951 
1952 	tmLocal.tm_hour = xmpTime->hour;
1953 	tmLocal.tm_min = xmpTime->minute;
1954 	tmLocal.tm_sec = xmpTime->second;
1955 	tmLocal.tm_isdst = -1;	// Don't know if daylight time is in effect.
1956 
1957 	ttTime = ansi_mktime ( &tmLocal );
1958 	if ( ttTime == -1 ) XMP_Throw ( "Failure from ANSI C mktime function", kXMPErr_ExternalFailure );
1959 
1960 	// Convert back to a localized ansi_tm time and get the corresponding UTC ansi_tm time.
1961 
1962 	ansi_localtime ( &ttTime, &tmLocal );
1963 	ansi_gmtime ( &ttTime, &tmUTC );
1964 
1965 	// Get the offset direction and amount.
1966 
1967 	ansi_tm tmx = tmLocal;	// ! Note that mktime updates the ansi_tm parameter, messing up difftime!
1968 	ansi_tm tmy = tmUTC;
1969 	tmx.tm_isdst = tmy.tm_isdst = 0;
1970 	ansi_tt ttx = ansi_mktime ( &tmx );
1971 	ansi_tt tty = ansi_mktime ( &tmy );
1972 	double diffSecs;
1973 
1974 	if ( (ttx != -1) && (tty != -1) ) {
1975 		diffSecs = ansi_difftime ( ttx, tty );
1976 	} else {
1977 		#if XMP_MacBuild
1978 			// Looks like Apple's mktime is buggy - see W1140533. But the offset is visible.
1979 			diffSecs = tmLocal.tm_gmtoff;
1980 		#else
1981 			// Win and UNIX don't have a visible offset. Make sure we know about the failure,
1982 			// then try using the current date/time as a close fallback.
1983 			ttTime = ansi_time(0);
1984 			if ( ttTime == -1 ) XMP_Throw ( "Failure from ANSI C time function", kXMPErr_ExternalFailure );
1985 			ansi_localtime ( &ttTime, &tmx );
1986 			ansi_gmtime ( &ttTime, &tmy );
1987 			tmx.tm_isdst = tmy.tm_isdst = 0;
1988 			ttx = ansi_mktime ( &tmx );
1989 			tty = ansi_mktime ( &tmy );
1990 			if ( (ttx == -1) || (tty == -1) ) XMP_Throw ( "Failure from ANSI C mktime function", kXMPErr_ExternalFailure );
1991 			diffSecs = ansi_difftime ( ttx, tty );
1992 		#endif
1993 	}
1994 
1995 	if ( diffSecs > 0.0 ) {
1996 		xmpTime->tzSign = kXMP_TimeEastOfUTC;
1997 	} else if ( diffSecs == 0.0 ) {
1998 		xmpTime->tzSign = kXMP_TimeIsUTC;
1999 	} else {
2000 		xmpTime->tzSign = kXMP_TimeWestOfUTC;
2001 		diffSecs = -diffSecs;
2002 	}
2003 	xmpTime->tzHour = XMP_Int32 ( diffSecs / 3600.0 );
2004 	xmpTime->tzMinute = XMP_Int32 ( (diffSecs / 60.0) - (xmpTime->tzHour * 60.0) );
2005 
2006 	// *** Save the tm_isdst flag in a qualifier?
2007 
2008 	XMP_Assert ( (0 <= xmpTime->tzHour) && (xmpTime->tzHour <= 23) );
2009 	XMP_Assert ( (0 <= xmpTime->tzMinute) && (xmpTime->tzMinute <= 59) );
2010 	XMP_Assert ( (-1 <= xmpTime->tzSign) && (xmpTime->tzSign <= +1) );
2011 	XMP_Assert ( (xmpTime->tzSign == 0) ? ((xmpTime->tzHour == 0) && (xmpTime->tzMinute == 0)) :
2012 										  ((xmpTime->tzHour != 0) || (xmpTime->tzMinute != 0)) );
2013 
2014 }	// SetTimeZone
2015 
2016 
2017 // -------------------------------------------------------------------------------------------------
2018 // ConvertToUTCTime
2019 // ----------------
2020 
2021 /* class static */ void
ConvertToUTCTime(XMP_DateTime * time)2022 XMPUtils::ConvertToUTCTime ( XMP_DateTime * time )
2023 {
2024 	XMP_Assert ( time != 0 );	// ! Enforced by wrapper.
2025 
2026 	XMP_Assert ( (0 <= time->tzHour) && (time->tzHour <= 23) );
2027 	XMP_Assert ( (0 <= time->tzMinute) && (time->tzMinute <= 59) );
2028 	XMP_Assert ( (-1 <= time->tzSign) && (time->tzSign <= +1) );
2029 	XMP_Assert ( (time->tzSign == 0) ? ((time->tzHour == 0) && (time->tzMinute == 0)) :
2030 									   ((time->tzHour != 0) || (time->tzMinute != 0)) );
2031 
2032 	if ( time->tzSign == kXMP_TimeEastOfUTC ) {
2033 		// We are before (east of) GMT, subtract the offset from the time.
2034 		time->hour -= time->tzHour;
2035 		time->minute -= time->tzMinute;
2036 	} else if ( time->tzSign == kXMP_TimeWestOfUTC ) {
2037 		// We are behind (west of) GMT, add the offset to the time.
2038 		time->hour += time->tzHour;
2039 		time->minute += time->tzMinute;
2040 	}
2041 
2042 	AdjustTimeOverflow ( time );
2043 	time->tzSign = time->tzHour = time->tzMinute = 0;
2044 
2045 }	// ConvertToUTCTime
2046 
2047 
2048 // -------------------------------------------------------------------------------------------------
2049 // ConvertToLocalTime
2050 // ------------------
2051 
2052 /* class static */ void
ConvertToLocalTime(XMP_DateTime * time)2053 XMPUtils::ConvertToLocalTime ( XMP_DateTime * time )
2054 {
2055 	XMP_Assert ( time != 0 );	// ! Enforced by wrapper.
2056 
2057 	XMP_Assert ( (0 <= time->tzHour) && (time->tzHour <= 23) );
2058 	XMP_Assert ( (0 <= time->tzMinute) && (time->tzMinute <= 59) );
2059 	XMP_Assert ( (-1 <= time->tzSign) && (time->tzSign <= +1) );
2060 	XMP_Assert ( (time->tzSign == 0) ? ((time->tzHour == 0) && (time->tzMinute == 0)) :
2061 									   ((time->tzHour != 0) || (time->tzMinute != 0)) );
2062 
2063 	ConvertToUTCTime ( time );	// The existing time zone might not be the local one.
2064 	SetTimeZone ( time );		// Fill in the local timezone offset, then adjust the time.
2065 
2066 	if ( time->tzSign > 0 ) {
2067 		// We are before (east of) GMT, add the offset to the time.
2068 		time->hour += time->tzHour;
2069 		time->minute += time->tzMinute;
2070 	} else if ( time->tzSign < 0 ) {
2071 		// We are behind (west of) GMT, subtract the offset from the time.
2072 		time->hour -= time->tzHour;
2073 		time->minute -= time->tzMinute;
2074 	}
2075 
2076 	AdjustTimeOverflow ( time );
2077 
2078 }	// ConvertToLocalTime
2079 
2080 
2081 // -------------------------------------------------------------------------------------------------
2082 // CompareDateTime
2083 // ---------------
2084 
2085 /* class static */ int
CompareDateTime(const XMP_DateTime & _in_left,const XMP_DateTime & _in_right)2086 XMPUtils::CompareDateTime ( const XMP_DateTime & _in_left,
2087 							const XMP_DateTime & _in_right )
2088 {
2089 	int result;
2090 
2091 	XMP_DateTime left  = _in_left;
2092 	XMP_DateTime right = _in_right;
2093 
2094 	ConvertToUTCTime ( &left );
2095 	ConvertToUTCTime ( &right );
2096 
2097 	// *** We could use memcmp if the XMP_DateTime stuct has no holes.
2098 
2099 	if ( left.year < right.year ) {
2100 		result = -1;
2101 	} else if ( left.year > right.year ) {
2102 		result = +1;
2103 	} else if ( left.month < right.month ) {
2104 		result = -1;
2105 	} else if ( left.month > right.month ) {
2106 		result = +1;
2107 	} else if ( left.day < right.day ) {
2108 		result = -1;
2109 	} else if ( left.day > right.day ) {
2110 		result = +1;
2111 	} else if ( left.hour < right.hour ) {
2112 		result = -1;
2113 	} else if ( left.hour > right.hour ) {
2114 		result = +1;
2115 	} else if ( left.minute < right.minute ) {
2116 		result = -1;
2117 	} else if ( left.minute > right.minute ) {
2118 		result = +1;
2119 	} else if ( left.second < right.second ) {
2120 		result = -1;
2121 	} else if ( left.second > right.second ) {
2122 		result = +1;
2123 	} else if ( left.nanoSecond < right.nanoSecond ) {
2124 		result = -1;
2125 	} else if ( left.nanoSecond > right.nanoSecond ) {
2126 		result = +1;
2127 	} else {
2128 		result = 0;
2129 	}
2130 
2131 	return result;
2132 
2133 }	// CompareDateTime
2134 
2135 // =================================================================================================
2136