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