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 <inttypes.h>
19 #include <time.h>
20 #include <string.h>
21 #include <stdlib.h>
22 #include <locale.h>
23 #include <errno.h>
24 
25 #include <stdio.h>	// For snprintf.
26 
27 #if XMP_WinBuild
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 
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 				   tempDate.year, tempDate.month, tempDate.day, tempDate.hour, 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 				   tempDate.year, tempDate.month, tempDate.day,
400 				   tempDate.hour, tempDate.minute, 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 				   tempDate.year, tempDate.month, tempDate.day,
407 				   tempDate.hour, tempDate.minute, tempDate.second, 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 		// *** Xocde 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 
607 	propSizes.erase ( lastPos );
608 	return propSize;
609 
610 }	// MoveLargestProperty
611 
612 
613 // =================================================================================================
614 // Class Static Functions
615 // ======================
616 
617 
618 // -------------------------------------------------------------------------------------------------
619 // Initialize
620 // ----------
621 
622 /* class static */ bool
Initialize()623 XMPUtils::Initialize()
624 {
625 	sComposedPath	= new XMP_VarString();
626 	sConvertedValue = new XMP_VarString();
627 	sBase64Str		= new XMP_VarString();
628 	sCatenatedItems = new XMP_VarString();
629 	sStandardXMP    = new XMP_VarString();
630 	sExtendedXMP    = new XMP_VarString();
631 	sExtendedDigest = new XMP_VarString();
632 
633 	#if XMP_MacBuild && __MWERKS__
634 		LookupTimeProcs();
635 	#endif
636 
637 	return true;
638 
639 }	// Initialize
640 
641 
642 // -------------------------------------------------------------------------------------------------
643 // Terminate
644 // ---------
645 
646 #define EliminateGlobal(g) delete ( g ); g = 0
647 
648 /* class static */ void
Terminate()649 XMPUtils::Terminate() RELEASE_NO_THROW
650 {
651 	EliminateGlobal ( sComposedPath );
652 	EliminateGlobal ( sConvertedValue );
653 	EliminateGlobal ( sBase64Str );
654 	EliminateGlobal ( sCatenatedItems );
655 	EliminateGlobal ( sStandardXMP );
656 	EliminateGlobal ( sExtendedXMP );
657 	EliminateGlobal ( sExtendedDigest );
658 
659 	return;
660 
661 }	// Terminate
662 
663 
664 // -------------------------------------------------------------------------------------------------
665 // Unlock
666 // ------
667 
668 /* class static */ void
Unlock(XMP_OptionBits options)669 XMPUtils::Unlock ( XMP_OptionBits options )
670 {
671 	options = options;	// Avoid unused parameter warning.
672 
673 	XMPMeta::Unlock ( 0 );
674 
675 }	// Unlock
676 
677 // -------------------------------------------------------------------------------------------------
678 // ComposeArrayItemPath
679 // --------------------
680 //
681 // Return "arrayName[index]".
682 
683 /* class static */ void
ComposeArrayItemPath(XMP_StringPtr schemaNS,XMP_StringPtr arrayName,XMP_Index itemIndex,XMP_StringPtr * fullPath,XMP_StringLen * pathSize)684 XMPUtils::ComposeArrayItemPath ( XMP_StringPtr	 schemaNS,
685 								 XMP_StringPtr	 arrayName,
686 								 XMP_Index		 itemIndex,
687 								 XMP_StringPtr * fullPath,
688 								 XMP_StringLen * pathSize )
689 {
690 	XMP_Assert ( schemaNS != 0 );	// Enforced by wrapper.
691 	XMP_Assert ( *arrayName != 0 ); // Enforced by wrapper.
692 	XMP_Assert ( (fullPath != 0) && (pathSize != 0) );	// Enforced by wrapper.
693 
694 	XMP_ExpandedXPath expPath;	// Just for side effects to check namespace and basic path.
695 	ExpandXPath ( schemaNS, arrayName, &expPath );
696 
697 	if ( (itemIndex < 0) && (itemIndex != kXMP_ArrayLastItem) ) XMP_Throw ( "Array index out of bounds", kXMPErr_BadParam );
698 
699 	XMP_StringLen reserveLen = strlen(arrayName) + 2 + 32;	// Room plus padding.
700 
701 	sComposedPath->erase();
702 	sComposedPath->reserve ( reserveLen );
703 	sComposedPath->append ( reserveLen, ' ' );
704 
705 	if ( itemIndex != kXMP_ArrayLastItem ) {
706 		// AUDIT: Using string->size() for the snprintf length is safe.
707 		snprintf ( const_cast<char*>(sComposedPath->c_str()), sComposedPath->size(), "%s[%d]", arrayName, itemIndex );
708 	} else {
709 		*sComposedPath = arrayName;
710 		*sComposedPath += "[last()] ";
711 		(*sComposedPath)[sComposedPath->size()-1] = 0;	// ! Final null is for the strlen at exit.
712 	}
713 
714 	*fullPath = sComposedPath->c_str();
715 	*pathSize = strlen ( *fullPath );	// ! Don't use sComposedPath->size()!
716 
717 	XMP_Enforce ( *pathSize < sComposedPath->size() );	// Rather late, but complain about buffer overflow.
718 
719 }	// ComposeArrayItemPath
720 
721 
722 // -------------------------------------------------------------------------------------------------
723 // ComposeStructFieldPath
724 // ----------------------
725 //
726 // Return "structName/ns:fieldName".
727 
728 /* class static */ void
ComposeStructFieldPath(XMP_StringPtr schemaNS,XMP_StringPtr structName,XMP_StringPtr fieldNS,XMP_StringPtr fieldName,XMP_StringPtr * fullPath,XMP_StringLen * pathSize)729 XMPUtils::ComposeStructFieldPath ( XMP_StringPtr   schemaNS,
730 								   XMP_StringPtr   structName,
731 								   XMP_StringPtr   fieldNS,
732 								   XMP_StringPtr   fieldName,
733 								   XMP_StringPtr * fullPath,
734 								   XMP_StringLen * pathSize )
735 {
736 	XMP_Assert ( (schemaNS != 0) && (fieldNS != 0) );		// Enforced by wrapper.
737 	XMP_Assert ( (*structName != 0) && (*fieldName != 0) ); // Enforced by wrapper.
738 	XMP_Assert ( (fullPath != 0) && (pathSize != 0) );		// Enforced by wrapper.
739 
740 	XMP_ExpandedXPath expPath;	// Just for side effects to check namespace and basic path.
741 	ExpandXPath ( schemaNS, structName, &expPath );
742 
743 	XMP_ExpandedXPath fieldPath;
744 	ExpandXPath ( fieldNS, fieldName, &fieldPath );
745 	if ( fieldPath.size() != 2 ) XMP_Throw ( "The fieldName must be simple", kXMPErr_BadXPath );
746 
747 	XMP_StringLen reserveLen = strlen(structName) + fieldPath[kRootPropStep].step.size() + 1;
748 
749 	sComposedPath->erase();
750 	sComposedPath->reserve ( reserveLen );
751 	*sComposedPath = structName;
752 	*sComposedPath += '/';
753 	*sComposedPath += fieldPath[kRootPropStep].step;
754 
755 	*fullPath = sComposedPath->c_str();
756 	*pathSize = sComposedPath->size();
757 
758 }	// ComposeStructFieldPath
759 
760 
761 // -------------------------------------------------------------------------------------------------
762 // ComposeQualifierPath
763 // --------------------
764 //
765 // Return "propName/?ns:qualName".
766 
767 /* class static */ void
ComposeQualifierPath(XMP_StringPtr schemaNS,XMP_StringPtr propName,XMP_StringPtr qualNS,XMP_StringPtr qualName,XMP_StringPtr * fullPath,XMP_StringLen * pathSize)768 XMPUtils::ComposeQualifierPath ( XMP_StringPtr	 schemaNS,
769 								 XMP_StringPtr	 propName,
770 								 XMP_StringPtr	 qualNS,
771 								 XMP_StringPtr	 qualName,
772 								 XMP_StringPtr * fullPath,
773 								 XMP_StringLen * pathSize )
774 {
775 	XMP_Assert ( (schemaNS != 0) && (qualNS != 0) );		// Enforced by wrapper.
776 	XMP_Assert ( (*propName != 0) && (*qualName != 0) );	// Enforced by wrapper.
777 	XMP_Assert ( (fullPath != 0) && (pathSize != 0) );		// Enforced by wrapper.
778 
779 	XMP_ExpandedXPath expPath;	// Just for side effects to check namespace and basic path.
780 	ExpandXPath ( schemaNS, propName, &expPath );
781 
782 	XMP_ExpandedXPath qualPath;
783 	ExpandXPath ( qualNS, qualName, &qualPath );
784 	if ( qualPath.size() != 2 ) XMP_Throw ( "The qualifier name must be simple", kXMPErr_BadXPath );
785 
786 	XMP_StringLen reserveLen = strlen(propName) + qualPath[kRootPropStep].step.size() + 2;
787 
788 	sComposedPath->erase();
789 	sComposedPath->reserve ( reserveLen );
790 	*sComposedPath = propName;
791 	*sComposedPath += "/?";
792 	*sComposedPath += qualPath[kRootPropStep].step;
793 
794 	*fullPath = sComposedPath->c_str();
795 	*pathSize = sComposedPath->size();
796 
797 }	// ComposeQualifierPath
798 
799 
800 // -------------------------------------------------------------------------------------------------
801 // ComposeLangSelector
802 // -------------------
803 //
804 // Return "arrayName[?xml:lang="lang"]".
805 
806 // *** #error "handle quotes in the lang - or verify format"
807 
808 /* class static */ void
ComposeLangSelector(XMP_StringPtr schemaNS,XMP_StringPtr arrayName,XMP_StringPtr _langName,XMP_StringPtr * fullPath,XMP_StringLen * pathSize)809 XMPUtils::ComposeLangSelector ( XMP_StringPtr	schemaNS,
810 								XMP_StringPtr	arrayName,
811 								XMP_StringPtr	_langName,
812 								XMP_StringPtr * fullPath,
813 								XMP_StringLen * pathSize )
814 {
815 	XMP_Assert ( schemaNS != 0 );	// Enforced by wrapper.
816 	XMP_Assert ( (*arrayName != 0) && (*_langName != 0) );	// Enforced by wrapper.
817 	XMP_Assert ( (fullPath != 0) && (pathSize != 0) );		// Enforced by wrapper.
818 
819 	XMP_ExpandedXPath expPath;	// Just for side effects to check namespace and basic path.
820 	ExpandXPath ( schemaNS, arrayName, &expPath );
821 
822 	XMP_VarString langName ( _langName );
823 	NormalizeLangValue ( &langName );
824 
825 	XMP_StringLen reserveLen = strlen(arrayName) + langName.size() + 14;
826 
827 	sComposedPath->erase();
828 	sComposedPath->reserve ( reserveLen );
829 	*sComposedPath = arrayName;
830 	*sComposedPath += "[?xml:lang=\"";
831 	*sComposedPath += langName;
832 	*sComposedPath += "\"]";
833 
834 	*fullPath = sComposedPath->c_str();
835 	*pathSize = sComposedPath->size();
836 
837 }	// ComposeLangSelector
838 
839 
840 // -------------------------------------------------------------------------------------------------
841 // ComposeFieldSelector
842 // --------------------
843 //
844 // Return "arrayName[ns:fieldName="fieldValue"]".
845 
846 // *** #error "handle quotes in the value"
847 
848 /* 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)849 XMPUtils::ComposeFieldSelector ( XMP_StringPtr	 schemaNS,
850 								 XMP_StringPtr	 arrayName,
851 								 XMP_StringPtr	 fieldNS,
852 								 XMP_StringPtr	 fieldName,
853 								 XMP_StringPtr	 fieldValue,
854 								 XMP_StringPtr * fullPath,
855 								 XMP_StringLen * pathSize )
856 {
857 	XMP_Assert ( (schemaNS != 0) && (fieldNS != 0) && (fieldValue != 0) );	// Enforced by wrapper.
858 	XMP_Assert ( (*arrayName != 0) && (*fieldName != 0) );	// Enforced by wrapper.
859 	XMP_Assert ( (fullPath != 0) && (pathSize != 0) );		// Enforced by wrapper.
860 
861 	XMP_ExpandedXPath expPath;	// Just for side effects to check namespace and basic path.
862 	ExpandXPath ( schemaNS, arrayName, &expPath );
863 
864 	XMP_ExpandedXPath fieldPath;
865 	ExpandXPath ( fieldNS, fieldName, &fieldPath );
866 	if ( fieldPath.size() != 2 ) XMP_Throw ( "The fieldName must be simple", kXMPErr_BadXPath );
867 
868 	XMP_StringLen reserveLen = strlen(arrayName) + fieldPath[kRootPropStep].step.size() + strlen(fieldValue) + 5;
869 
870 	sComposedPath->erase();
871 	sComposedPath->reserve ( reserveLen );
872 	*sComposedPath = arrayName;
873 	*sComposedPath += '[';
874 	*sComposedPath += fieldPath[kRootPropStep].step;
875 	*sComposedPath += "=\"";
876 	*sComposedPath += fieldValue;
877 	*sComposedPath += "\"]";
878 
879 	*fullPath = sComposedPath->c_str();
880 	*pathSize = sComposedPath->size();
881 
882 }	// ComposeFieldSelector
883 
884 
885 // -------------------------------------------------------------------------------------------------
886 // ConvertFromBool
887 // ---------------
888 
889 /* class static */ void
ConvertFromBool(bool binValue,XMP_StringPtr * strValue,XMP_StringLen * strSize)890 XMPUtils::ConvertFromBool ( bool			binValue,
891 							XMP_StringPtr * strValue,
892 							XMP_StringLen * strSize )
893 {
894 	XMP_Assert ( (strValue != 0) && (strSize != 0) );	// Enforced by wrapper.
895 
896 	if ( binValue ) {
897 		*strValue = kXMP_TrueStr;
898 		*strSize  = strlen ( kXMP_TrueStr );
899 	} else {
900 		*strValue = kXMP_FalseStr;
901 		*strSize  = strlen ( kXMP_FalseStr );
902 	}
903 
904 }	// ConvertFromBool
905 
906 
907 // -------------------------------------------------------------------------------------------------
908 // ConvertFromInt
909 // --------------
910 
911 /* class static */ void
ConvertFromInt(XMP_Int32 binValue,XMP_StringPtr format,XMP_StringPtr * strValue,XMP_StringLen * strSize)912 XMPUtils::ConvertFromInt ( XMP_Int32	   binValue,
913 						   XMP_StringPtr   format,
914 						   XMP_StringPtr * strValue,
915 						   XMP_StringLen * strSize )
916 {
917 	XMP_Assert ( (format != 0) && (strValue != 0) && (strSize != 0) );	// Enforced by wrapper.
918 
919 	if ( *format == 0 ) format = "%d";
920 
921 	sConvertedValue->erase();
922 	sConvertedValue->reserve ( 100 );		// More than enough for any reasonable format and value.
923 	sConvertedValue->append ( 100, ' ' );
924 
925 	// AUDIT: Using string->size() for the snprintf length is safe.
926 	snprintf ( const_cast<char*>(sConvertedValue->c_str()), sConvertedValue->size(), format, binValue );
927 
928 	*strValue = sConvertedValue->c_str();
929 	*strSize  = strlen ( *strValue );	// ! Don't use sConvertedValue->size()!
930 
931 	XMP_Enforce ( *strSize < sConvertedValue->size() ); // Rather late, but complain about buffer overflow.
932 
933 }	// ConvertFromInt
934 
935 
936 // -------------------------------------------------------------------------------------------------
937 // ConvertFromInt64
938 // ----------------
939 
940 /* class static */ void
ConvertFromInt64(XMP_Int64 binValue,XMP_StringPtr format,XMP_StringPtr * strValue,XMP_StringLen * strSize)941 XMPUtils::ConvertFromInt64 ( XMP_Int64	     binValue,
942 						     XMP_StringPtr   format,
943 						     XMP_StringPtr * strValue,
944 						     XMP_StringLen * strSize )
945 {
946 	XMP_Assert ( (format != 0) && (strValue != 0) && (strSize != 0) );	// Enforced by wrapper.
947 
948 	if ( *format == 0 ) format = "%lld";
949 
950 	sConvertedValue->erase();
951 	sConvertedValue->reserve ( 100 );		// More than enough for any reasonable format and value.
952 	sConvertedValue->append ( 100, ' ' );
953 
954 	// AUDIT: Using string->size() for the snprintf length is safe.
955 	snprintf ( const_cast<char*>(sConvertedValue->c_str()), sConvertedValue->size(), format, binValue );
956 
957 	*strValue = sConvertedValue->c_str();
958 	*strSize  = strlen ( *strValue );	// ! Don't use sConvertedValue->size()!
959 
960 	XMP_Enforce ( *strSize < sConvertedValue->size() ); // Rather late, but complain about buffer overflow.
961 
962 }	// ConvertFromInt64
963 
964 
965 // -------------------------------------------------------------------------------------------------
966 // ConvertFromFloat
967 // ----------------
968 
969 /* class static */ void
ConvertFromFloat(double binValue,XMP_StringPtr format,XMP_StringPtr * strValue,XMP_StringLen * strSize)970 XMPUtils::ConvertFromFloat ( double			 binValue,
971 							 XMP_StringPtr	 format,
972 							 XMP_StringPtr * strValue,
973 							 XMP_StringLen * strSize )
974 {
975 	XMP_Assert ( (format != 0) && (strValue != 0) && (strSize != 0) );	// Enforced by wrapper.
976 
977 	if ( *format == 0 ) format = "%f";
978 
979 	sConvertedValue->erase();
980 	sConvertedValue->reserve ( 1000 );		// More than enough for any reasonable format and value.
981 	sConvertedValue->append ( 1000, ' ' );
982 
983 	// AUDIT: Using string->size() for the snprintf length is safe.
984 	snprintf ( const_cast<char*>(sConvertedValue->c_str()), sConvertedValue->size(), format, binValue );
985 
986 	*strValue = sConvertedValue->c_str();
987 	*strSize  = strlen ( *strValue );	// ! Don't use sConvertedValue->size()!
988 
989 	XMP_Enforce ( *strSize < sConvertedValue->size() ); // Rather late, but complain about buffer overflow.
990 
991 }	// ConvertFromFloat
992 
993 
994 // -------------------------------------------------------------------------------------------------
995 // ConvertFromDate
996 // ---------------
997 //
998 // Format a date according to ISO 8601 and http://www.w3.org/TR/NOTE-datetime:
999 //	YYYY
1000 //	YYYY-MM
1001 //	YYYY-MM-DD
1002 //	YYYY-MM-DDThh:mmTZD
1003 //	YYYY-MM-DDThh:mm:ssTZD
1004 //	YYYY-MM-DDThh:mm:ss.sTZD
1005 //
1006 //	YYYY = four-digit year
1007 //	MM	 = two-digit month (01=January, etc.)
1008 //	DD	 = two-digit day of month (01 through 31)
1009 //	hh	 = two digits of hour (00 through 23)
1010 //	mm	 = two digits of minute (00 through 59)
1011 //	ss	 = two digits of second (00 through 59)
1012 //	s	 = one or more digits representing a decimal fraction of a second
1013 //	TZD	 = time zone designator (Z or +hh:mm or -hh:mm)
1014 //
1015 // Note that ISO 8601 does not seem to allow years less than 1000 or greater than 9999. We allow
1016 // any year, even negative ones. The year is formatted as "%.4d".
1017 
1018 // *** Need to check backward compatibility for partial forms!
1019 
1020 /* class static */ void
ConvertFromDate(const XMP_DateTime & binValue,XMP_StringPtr * strValue,XMP_StringLen * strSize)1021 XMPUtils::ConvertFromDate ( const XMP_DateTime & binValue,
1022 							XMP_StringPtr *		 strValue,
1023 							XMP_StringLen *		 strSize )
1024 {
1025 	XMP_Assert ( (strValue != 0) && (strSize != 0) );	// Enforced by wrapper.
1026 
1027 	bool addTimeZone = false;
1028 	char buffer [100];	// Plenty long enough.
1029 
1030 	// Pick the format, use snprintf to format into a local buffer, assign to static output string.
1031 	// Don't use AdjustTimeOverflow at the start, that will wipe out zero month or day values.
1032 
1033 	// ! Photoshop 8 creates "time only" values with zeros for year, month, and day.
1034 
1035 	XMP_DateTime tempDate = binValue;
1036 
1037 	// Temporary fix for bug 1269463, silently fix out of range month or day.
1038 
1039 	bool haveDay  = (tempDate.day != 0);
1040 	bool haveTime = ( (tempDate.hour != 0)   || (tempDate.minute != 0) ||
1041 			          (tempDate.second != 0) || (tempDate.nanoSecond != 0) ||
1042 			          (tempDate.tzSign != 0) || (tempDate.tzHour != 0) || (tempDate.tzMinute != 0) );
1043 
1044 	if ( tempDate.month == 0 ) {
1045 		if ( haveDay || haveTime ) tempDate.month = 1;
1046 	} else {
1047 		if ( tempDate.month < 1 ) tempDate.month = 1;
1048 		if ( tempDate.month > 12 ) tempDate.month = 12;
1049 	}
1050 
1051 	if ( tempDate.day == 0 ) {
1052 		if ( haveTime ) tempDate.day = 1;
1053 	} else {
1054 		if ( tempDate.day < 1 ) tempDate.day = 1;
1055 		if ( tempDate.day > 31 ) tempDate.day = 31;
1056 	}
1057 
1058 	// Now carry on with the original logic.
1059 
1060 	if ( tempDate.month == 0 ) {
1061 
1062 		// Output YYYY if all else is zero, otherwise output a full string for the quasi-bogus
1063 		// "time only" values from Photoshop CS.
1064 		if ( (tempDate.day == 0) && (tempDate.hour == 0) && (tempDate.minute == 0) &&
1065 			 (tempDate.second == 0) && (tempDate.nanoSecond == 0) &&
1066 			 (tempDate.tzSign == 0) && (tempDate.tzHour == 0) && (tempDate.tzMinute == 0) ) {
1067 			snprintf ( buffer, sizeof(buffer), "%.4d", tempDate.year ); // AUDIT: Using sizeof for snprintf length is safe.
1068 		} else if ( (tempDate.year == 0) && (tempDate.day == 0) ) {
1069 			FormatFullDateTime ( tempDate, buffer, sizeof(buffer) );
1070 			addTimeZone = true;
1071 		} else {
1072 			XMP_Throw ( "Invalid partial date", kXMPErr_BadParam);
1073 		}
1074 
1075 	} else if ( tempDate.day == 0 ) {
1076 
1077 		// Output YYYY-MM.
1078 		if ( (tempDate.month < 1) || (tempDate.month > 12) ) XMP_Throw ( "Month is out of range", kXMPErr_BadParam);
1079 		if ( (tempDate.hour != 0) || (tempDate.minute != 0) ||
1080 			 (tempDate.second != 0) || (tempDate.nanoSecond != 0) ||
1081 			 (tempDate.tzSign != 0) || (tempDate.tzHour != 0) || (tempDate.tzMinute != 0) ) {
1082 			XMP_Throw ( "Invalid partial date, non-zeros after zero month and day", kXMPErr_BadParam);
1083 		}
1084 		snprintf ( buffer, sizeof(buffer), "%.4d-%02d", tempDate.year, tempDate.month );	// AUDIT: Using sizeof for snprintf length is safe.
1085 
1086 	} else if ( (tempDate.hour == 0) && (tempDate.minute == 0) &&
1087 				(tempDate.second == 0) && (tempDate.nanoSecond == 0) &&
1088 				(tempDate.tzSign == 0) && (tempDate.tzHour == 0) && (tempDate.tzMinute == 0) ) {
1089 
1090 		// Output YYYY-MM-DD.
1091 		if ( (tempDate.month < 1) || (tempDate.month > 12) ) XMP_Throw ( "Month is out of range", kXMPErr_BadParam);
1092 		if ( (tempDate.day < 1) || (tempDate.day > 31) ) XMP_Throw ( "Day is out of range", kXMPErr_BadParam);
1093 		snprintf ( buffer, sizeof(buffer), "%.4d-%02d-%02d", tempDate.year, tempDate.month, tempDate.day ); // AUDIT: Using sizeof for snprintf length is safe.
1094 
1095 	} else {
1096 
1097 		FormatFullDateTime ( tempDate, buffer, sizeof(buffer) );
1098 		addTimeZone = true;
1099 
1100 	}
1101 
1102 	sConvertedValue->assign ( buffer );
1103 
1104 	if ( addTimeZone ) {
1105 
1106 		if ( (tempDate.tzHour < 0) || (tempDate.tzHour > 23) ||
1107 			 (tempDate.tzMinute < 0 ) || (tempDate.tzMinute > 59) ||
1108 			 (tempDate.tzSign < -1) || (tempDate.tzSign > +1) ||
1109 			 ((tempDate.tzSign != 0) && (tempDate.tzHour == 0) && (tempDate.tzMinute == 0)) ||
1110 			 ((tempDate.tzSign == 0) && ((tempDate.tzHour != 0) || (tempDate.tzMinute != 0))) ) {
1111 			XMP_Throw ( "Invalid time zone values", kXMPErr_BadParam );
1112 		}
1113 
1114 		if ( tempDate.tzSign == 0 ) {
1115 			*sConvertedValue += 'Z';
1116 		} else {
1117 			snprintf ( buffer, sizeof(buffer), "+%02d:%02d", tempDate.tzHour, tempDate.tzMinute );	// AUDIT: Using sizeof for snprintf length is safe.
1118 			if ( tempDate.tzSign < 0 ) buffer[0] = '-';
1119 			*sConvertedValue += buffer;
1120 		}
1121 
1122 	}
1123 
1124 	*strValue = sConvertedValue->c_str();
1125 	*strSize  = sConvertedValue->size();
1126 
1127 }	// ConvertFromDate
1128 
1129 
1130 // -------------------------------------------------------------------------------------------------
1131 // ConvertToBool
1132 // -------------
1133 //
1134 // Formally the string value should be "True" or "False", but we should be more flexible here. Map
1135 // the string to lower case. Allow any of "true", "false", "t", "f", "1", or "0".
1136 
1137 /* class static */ bool
ConvertToBool(XMP_StringPtr strValue)1138 XMPUtils::ConvertToBool ( XMP_StringPtr strValue )
1139 {
1140 	if ( (strValue == 0) || (*strValue == 0) ) XMP_Throw ( "Empty convert-from string", kXMPErr_BadValue );
1141 
1142 	bool result = false;
1143 	XMP_VarString strObj ( strValue );
1144 
1145 	for ( XMP_VarStringPos ch = strObj.begin(); ch != strObj.end(); ++ch ) {
1146 		if ( ('A' <= *ch) && (*ch <= 'Z') ) *ch += 0x20;
1147 	}
1148 
1149 	if ( (strObj == "true") || (strObj == "t") || (strObj == "1") ) {
1150 		result = true;
1151 	} else if ( (strObj == "false") || (strObj == "f") || (strObj == "0") ) {
1152 		result = false;
1153 	} else {
1154 		XMP_Throw ( "Invalid Boolean string", kXMPErr_BadParam );
1155 	}
1156 
1157 	return result;
1158 
1159 }	// ConvertToBool
1160 
1161 
1162 // -------------------------------------------------------------------------------------------------
1163 // ConvertToInt
1164 // ------------
1165 
1166 /* class static */ XMP_Int32
ConvertToInt(XMP_StringPtr strValue)1167 XMPUtils::ConvertToInt ( XMP_StringPtr strValue )
1168 {
1169 	if ( (strValue == 0) || (*strValue == 0) ) XMP_Throw ( "Empty convert-from string", kXMPErr_BadValue );
1170 
1171 	int count;
1172 	char nextCh;
1173 	XMP_Int32 result;
1174 
1175 	if ( ! XMP_LitNMatch ( strValue, "0x", 2 ) ) {
1176 		count = sscanf ( strValue, "%d%c", &result, &nextCh );
1177 	} else {
1178 		count = sscanf ( strValue, "%x%c", &result, &nextCh );
1179 	}
1180 
1181 	if ( count != 1 ) XMP_Throw ( "Invalid integer string", kXMPErr_BadParam );
1182 
1183 	return result;
1184 
1185 }	// ConvertToInt
1186 
1187 
1188 // -------------------------------------------------------------------------------------------------
1189 // ConvertToInt64
1190 // --------------
1191 
1192 /* class static */ XMP_Int64
ConvertToInt64(XMP_StringPtr strValue)1193 XMPUtils::ConvertToInt64 ( XMP_StringPtr strValue )
1194 {
1195 	if ( (strValue == 0) || (*strValue == 0) ) XMP_Throw ( "Empty convert-from string", kXMPErr_BadValue );
1196 
1197 	int count;
1198 	char nextCh;
1199 	XMP_Int64 result;
1200 
1201 	if ( ! XMP_LitNMatch ( strValue, "0x", 2 ) ) {
1202 		count = sscanf ( strValue, "%"PRId64"%c", &result, &nextCh );
1203 	} else {
1204 		count = sscanf ( strValue, "%"PRIx64"%c", &result, &nextCh );
1205 	}
1206 
1207 	if ( count != 1 ) XMP_Throw ( "Invalid integer string", kXMPErr_BadParam );
1208 
1209 	return result;
1210 
1211 }	// ConvertToInt64
1212 
1213 
1214 // -------------------------------------------------------------------------------------------------
1215 // ConvertToFloat
1216 // --------------
1217 
1218 /* class static */ double
ConvertToFloat(XMP_StringPtr strValue)1219 XMPUtils::ConvertToFloat ( XMP_StringPtr strValue )
1220 {
1221 	if ( (strValue == 0) || (*strValue == 0) ) XMP_Throw ( "Empty convert-from string", kXMPErr_BadValue );
1222 
1223 	XMP_VarString oldLocale;	// Try to make sure number conversion uses '.' as the decimal point.
1224 	XMP_StringPtr oldLocalePtr = setlocale ( LC_ALL, 0 );
1225 	if ( oldLocalePtr != 0 ) {
1226 		oldLocale.assign ( oldLocalePtr );
1227 		setlocale ( LC_ALL, "C" );
1228 	}
1229 
1230 	errno = 0;
1231 	char * numEnd;
1232 	double result = strtod ( strValue, &numEnd );
1233 
1234 	if ( oldLocalePtr != 0 ) setlocale ( LC_ALL, oldLocalePtr );	// ! Reset locale before possible throw!
1235 	if ( (errno != 0) || (*numEnd != 0) ) XMP_Throw ( "Invalid float string", kXMPErr_BadParam );
1236 
1237 	return result;
1238 
1239 }	// ConvertToFloat
1240 
1241 
1242 // -------------------------------------------------------------------------------------------------
1243 // ConvertToDate
1244 // -------------
1245 //
1246 // Parse a date according to ISO 8601 and http://www.w3.org/TR/NOTE-datetime:
1247 //	YYYY
1248 //	YYYY-MM
1249 //	YYYY-MM-DD
1250 //	YYYY-MM-DDThh:mmTZD
1251 //	YYYY-MM-DDThh:mm:ssTZD
1252 //	YYYY-MM-DDThh:mm:ss.sTZD
1253 //
1254 //	YYYY = four-digit year
1255 //	MM	 = two-digit month (01=January, etc.)
1256 //	DD	 = two-digit day of month (01 through 31)
1257 //	hh	 = two digits of hour (00 through 23)
1258 //	mm	 = two digits of minute (00 through 59)
1259 //	ss	 = two digits of second (00 through 59)
1260 //	s	 = one or more digits representing a decimal fraction of a second
1261 //	TZD	 = time zone designator (Z or +hh:mm or -hh:mm)
1262 //
1263 // Note that ISO 8601 does not seem to allow years less than 1000 or greater than 9999. We allow
1264 // any year, even negative ones. The year is formatted as "%.4d".
1265 
1266 // ! Tolerate missing TZD, assume is UTC. Photoshop 8 writes dates like this for exif:GPSTimeStamp.
1267 // ! Tolerate missing date portion, in case someone foolishly writes a time-only value that way.
1268 
1269 // *** Put the ISO format comments in the header documentation.
1270 
1271 /* class static */ void
ConvertToDate(XMP_StringPtr strValue,XMP_DateTime * binValue)1272 XMPUtils::ConvertToDate ( XMP_StringPtr	 strValue,
1273 						  XMP_DateTime * binValue )
1274 {
1275 	if ( (strValue == 0) || (*strValue == 0) ) XMP_Throw ( "Empty convert-from string", kXMPErr_BadValue);
1276 
1277 	size_t	 pos = 0;
1278 	XMP_Int32 temp;
1279 
1280 	XMP_Assert ( sizeof(*binValue) == sizeof(XMP_DateTime) );
1281 	(void) memset ( binValue, 0, sizeof(*binValue) );	// AUDIT: Safe, using sizeof destination.
1282 
1283 	bool timeOnly = ( (strValue[0] == 'T') ||
1284 					  ((strlen(strValue) >= 2) && (strValue[1] == ':')) ||
1285 					  ((strlen(strValue) >= 3) && (strValue[2] == ':')) );
1286 
1287 	if ( ! timeOnly ) {
1288 
1289 		if ( strValue[0] == '-' ) pos = 1;
1290 
1291 		temp = GatherInt ( strValue, &pos, "Invalid year in date string" ); // Extract the year.
1292 		if ( (strValue[pos] != 0) && (strValue[pos] != '-') ) XMP_Throw ( "Invalid date string, after year", kXMPErr_BadParam );
1293 		if ( strValue[0] == '-' ) temp = -temp;
1294 		binValue->year = temp;
1295 		if ( strValue[pos] == 0 ) return;
1296 
1297 		++pos;
1298 		temp = GatherInt ( strValue, &pos, "Invalid month in date string" );	// Extract the month.
1299 		if ( (strValue[pos] != 0) && (strValue[pos] != '-') ) XMP_Throw ( "Invalid date string, after month", kXMPErr_BadParam );
1300 		binValue->month = temp;
1301 		if ( strValue[pos] == 0 ) return;
1302 
1303 		++pos;
1304 		temp = GatherInt ( strValue, &pos, "Invalid day in date string" );	// Extract the day.
1305 		if ( (strValue[pos] != 0) && (strValue[pos] != 'T') ) XMP_Throw ( "Invalid date string, after day", kXMPErr_BadParam );
1306 		binValue->day = temp;
1307 		if ( strValue[pos] == 0 ) return;
1308 
1309 		// Allow year, month, and day to all be zero; implies the date portion is missing.
1310 		if ( (binValue->year != 0) || (binValue->month != 0) || (binValue->day != 0) ) {
1311 			// Temporary fix for bug 1269463, silently fix out of range month or day.
1312 			// if ( (binValue->month < 1) || (binValue->month > 12) ) XMP_Throw ( "Month is out of range", kXMPErr_BadParam );
1313 			// if ( (binValue->day < 1) || (binValue->day > 31) ) XMP_Throw ( "Day is out of range", kXMPErr_BadParam );
1314 			if ( binValue->month < 1 ) binValue->month = 1;
1315 			if ( binValue->month > 12 ) binValue->month = 12;
1316 			if ( binValue->day < 1 ) binValue->day = 1;
1317 			if ( binValue->day > 31 ) binValue->day = 31;
1318 		}
1319 
1320 	}
1321 
1322 	if ( strValue[pos] == 'T' ) {
1323 		++pos;
1324 	} else if ( ! timeOnly ) {
1325 		XMP_Throw ( "Invalid date string, missing 'T' after date", kXMPErr_BadParam );
1326 	}
1327 
1328 	temp = GatherInt ( strValue, &pos, "Invalid hour in date string" ); // Extract the hour.
1329 	if ( strValue[pos] != ':' ) XMP_Throw ( "Invalid date string, after hour", kXMPErr_BadParam );
1330 	if ( temp > 23 ) temp = 23;	// *** 1269463: XMP_Throw ( "Hour is out of range", kXMPErr_BadParam );
1331 	binValue->hour = temp;
1332 	// Don't check for done, we have to work up to the time zone.
1333 
1334 	++pos;
1335 	temp = GatherInt ( strValue, &pos, "Invalid minute in date string" );	// And the minute.
1336 	if ( (strValue[pos] != ':') && (strValue[pos] != 'Z') &&
1337 		 (strValue[pos] != '+') && (strValue[pos] != '-') && (strValue[pos] != 0) ) XMP_Throw ( "Invalid date string, after minute", kXMPErr_BadParam );
1338 	if ( temp > 59 ) temp = 59;	// *** 1269463: XMP_Throw ( "Minute is out of range", kXMPErr_BadParam );
1339 	binValue->minute = temp;
1340 	// Don't check for done, we have to work up to the time zone.
1341 
1342 	if ( strValue[pos] == ':' ) {
1343 
1344 		++pos;
1345 		temp = GatherInt ( strValue, &pos, "Invalid whole seconds in date string" );	// Extract the whole seconds.
1346 		if ( (strValue[pos] != '.') && (strValue[pos] != 'Z') &&
1347 			 (strValue[pos] != '+') && (strValue[pos] != '-') && (strValue[pos] != 0) ) {
1348 			XMP_Throw ( "Invalid date string, after whole seconds", kXMPErr_BadParam );
1349 		}
1350 		if ( temp > 59 ) temp = 59;	// *** 1269463: XMP_Throw ( "Whole second is out of range", kXMPErr_BadParam );
1351 		binValue->second = temp;
1352 		// Don't check for done, we have to work up to the time zone.
1353 
1354 		if ( strValue[pos] == '.' ) {
1355 
1356 			++pos;
1357 			size_t digits = pos;	// Will be the number of digits later.
1358 
1359 			temp = GatherInt ( strValue, &pos, "Invalid fractional seconds in date string" );	// Extract the fractional seconds.
1360 			if ( (strValue[pos] != 'Z') && (strValue[pos] != '+') && (strValue[pos] != '-') && (strValue[pos] != 0) ) {
1361 				XMP_Throw ( "Invalid date string, after fractional second", kXMPErr_BadParam );
1362 			}
1363 
1364 			digits = pos - digits;
1365 			for ( ; digits > 9; --digits ) temp = temp / 10;
1366 			for ( ; digits < 9; ++digits ) temp = temp * 10;
1367 
1368 			if ( temp >= 1000*1000*1000 ) XMP_Throw ( "Fractional second is out of range", kXMPErr_BadParam );
1369 			binValue->nanoSecond = temp;
1370 			// Don't check for done, we have to work up to the time zone.
1371 
1372 		}
1373 
1374 	}
1375 
1376 	if ( strValue[pos] == 'Z' ) {
1377 
1378 		++pos;
1379 
1380 	} else if ( strValue[pos] != 0 ) {
1381 
1382 		if ( strValue[pos] == '+' ) {
1383 			binValue->tzSign = kXMP_TimeEastOfUTC;
1384 		} else if ( strValue[pos] == '-' ) {
1385 			binValue->tzSign = kXMP_TimeWestOfUTC;
1386 		} else {
1387 			XMP_Throw ( "Time zone must begin with 'Z', '+', or '-'", kXMPErr_BadParam );
1388 		}
1389 
1390 		++pos;
1391 		temp = GatherInt ( strValue, &pos, "Invalid time zone hour in date string" );	// Extract the time zone hour.
1392 		if ( strValue[pos] != ':' ) XMP_Throw ( "Invalid date string, after time zone hour", kXMPErr_BadParam );
1393 		if ( temp > 23 ) XMP_Throw ( "Time zone hour is out of range", kXMPErr_BadParam );
1394 		binValue->tzHour = temp;
1395 
1396 		++pos;
1397 		temp = GatherInt ( strValue, &pos, "Invalid time zone minute in date string" ); // Extract the time zone minute.
1398 		if ( temp > 59 ) XMP_Throw ( "Time zone minute is out of range", kXMPErr_BadParam );
1399 		binValue->tzMinute = temp;
1400 
1401 	}
1402 
1403 	if ( strValue[pos] != 0 ) XMP_Throw ( "Invalid date string, extra chars at end", kXMPErr_BadParam );
1404 
1405 }	// ConvertToDate
1406 
1407 
1408 // -------------------------------------------------------------------------------------------------
1409 // EncodeToBase64
1410 // --------------
1411 //
1412 // Encode a string of raw data bytes in base 64 according to RFC 2045. For the encoding definition
1413 // see section 6.8 in <http://www.ietf.org/rfc/rfc2045.txt>. Although it isn't needed for RDF, we
1414 // do insert a linefeed character as a newline for every 76 characters of encoded output.
1415 
1416 /* class static */ void
EncodeToBase64(XMP_StringPtr rawStr,XMP_StringLen rawLen,XMP_StringPtr * encodedStr,XMP_StringLen * encodedLen)1417 XMPUtils::EncodeToBase64 ( XMP_StringPtr   rawStr,
1418 						   XMP_StringLen   rawLen,
1419 						   XMP_StringPtr * encodedStr,
1420 						   XMP_StringLen * encodedLen )
1421 {
1422 	if ( (rawStr == 0) && (rawLen != 0) ) XMP_Throw ( "Null raw data buffer", kXMPErr_BadParam );
1423 	if ( rawLen == 0 ) {
1424 		*encodedStr = 0;
1425 		*encodedLen = 0;
1426 		return;
1427 	}
1428 
1429 	char	encChunk[4];
1430 
1431 	unsigned long	in, out;
1432 	unsigned char	c1, c2, c3;
1433 	unsigned long	merge;
1434 
1435 	const size_t	outputSize	= (rawLen / 3) * 4; // Approximate, might be  small.
1436 
1437 	sBase64Str->erase();
1438 	sBase64Str->reserve ( outputSize );
1439 
1440 	// ----------------------------------------------------------------------------------------
1441 	// Each 6 bits of input produces 8 bits of output, so 3 input bytes become 4 output bytes.
1442 	// Process the whole chunks of 3 bytes first, then deal with any remainder. Be careful with
1443 	// the loop comparison, size-2 could be negative!
1444 
1445 	for ( in = 0, out = 0; (in+2) < rawLen; in += 3, out += 4 ) {
1446 
1447 		c1	= rawStr[in];
1448 		c2	= rawStr[in+1];
1449 		c3	= rawStr[in+2];
1450 
1451 		merge	= (c1 << 16) + (c2 << 8) + c3;
1452 
1453 		encChunk[0] = sBase64Chars [ merge >> 18 ];
1454 		encChunk[1] = sBase64Chars [ (merge >> 12) & 0x3F ];
1455 		encChunk[2] = sBase64Chars [ (merge >> 6) & 0x3F ];
1456 		encChunk[3] = sBase64Chars [ merge & 0x3F ];
1457 
1458 		if ( out >= 76 ) {
1459 			sBase64Str->append ( 1, kLF );
1460 			out = 0;
1461 		}
1462 		sBase64Str->append ( encChunk, 4 );
1463 
1464 	}
1465 
1466 	// ------------------------------------------------------------------------------------------
1467 	// The output must always be a multiple of 4 bytes. If there is a 1 or 2 byte input remainder
1468 	// we need to create another chunk. Zero pad with bits to a 6 bit multiple, then add one or
1469 	// two '=' characters to pad out to 4 bytes.
1470 
1471 	switch ( rawLen - in ) {
1472 
1473 		case 0:		// Done, no remainder.
1474 			break;
1475 
1476 		case 1:		// One input byte remains.
1477 
1478 			c1	= rawStr[in];
1479 			merge	= c1 << 16;
1480 
1481 			encChunk[0] = sBase64Chars [ merge >> 18 ];
1482 			encChunk[1] = sBase64Chars [ (merge >> 12) & 0x3F ];
1483 			encChunk[2] = encChunk[3] = '=';
1484 
1485 			if ( out >= 76 ) sBase64Str->append ( 1, kLF );
1486 			sBase64Str->append ( encChunk, 4 );
1487 			break;
1488 
1489 		case 2:		// Two input bytes remain.
1490 
1491 			c1	= rawStr[in];
1492 			c2	= rawStr[in+1];
1493 			merge	= (c1 << 16) + (c2 << 8);
1494 
1495 			encChunk[0] = sBase64Chars [ merge >> 18 ];
1496 			encChunk[1] = sBase64Chars [ (merge >> 12) & 0x3F ];
1497 			encChunk[2] = sBase64Chars [ (merge >> 6) & 0x3F ];
1498 			encChunk[3] = '=';
1499 
1500 			if ( out >= 76 ) sBase64Str->append ( 1, kLF );
1501 			sBase64Str->append ( encChunk, 4 );
1502 			break;
1503 
1504 	}
1505 
1506 	// -------------------------
1507 	// Assign the output values.
1508 
1509 	*encodedStr = sBase64Str->c_str();
1510 	*encodedLen = sBase64Str->size();
1511 
1512 }	// EncodeToBase64
1513 
1514 
1515 // -------------------------------------------------------------------------------------------------
1516 // DecodeFromBase64
1517 // ----------------
1518 //
1519 // Decode a string of raw data bytes from base 64 according to RFC 2045. For the encoding definition
1520 // see section 6.8 in <http://www.ietf.org/rfc/rfc2045.txt>. RFC 2045 talks about ignoring all "bad"
1521 // input but warning about non-whitespace. For XMP use we ignore space, tab, LF, and CR. Any other
1522 // bad input is rejected.
1523 
1524 /* class static */ void
DecodeFromBase64(XMP_StringPtr encodedStr,XMP_StringLen encodedLen,XMP_StringPtr * rawStr,XMP_StringLen * rawLen)1525 XMPUtils::DecodeFromBase64 ( XMP_StringPtr	 encodedStr,
1526 							 XMP_StringLen	 encodedLen,
1527 							 XMP_StringPtr * rawStr,
1528 							 XMP_StringLen * rawLen )
1529 {
1530 	if ( (encodedStr == 0) && (encodedLen != 0) ) XMP_Throw ( "Null encoded data buffer", kXMPErr_BadParam );
1531 	if ( encodedLen == 0 ) {
1532 		*rawStr = 0;
1533 		*rawLen = 0;
1534 		return;
1535 	}
1536 
1537 	unsigned char	ch, rawChunk[3];
1538 	unsigned long	inStr, inChunk, inLimit, merge, padding;
1539 
1540 	XMP_StringLen	outputSize	= (encodedLen / 4) * 3; // Only a close approximation.
1541 
1542 	sBase64Str->erase();
1543 	sBase64Str->reserve ( outputSize );
1544 
1545 
1546 	// ----------------------------------------------------------------------------------------
1547 	// Each 8 bits of input produces 6 bits of output, so 4 input bytes become 3 output bytes.
1548 	// Process all but the last 4 data bytes first, then deal with the final chunk. Whitespace
1549 	// in the input must be ignored. The first loop finds where the last 4 data bytes start and
1550 	// counts the number of padding equal signs.
1551 
1552 	padding = 0;
1553 	for ( inStr = 0, inLimit = encodedLen; (inStr < 4) && (inLimit > 0); ) {
1554 		inLimit -= 1;	// ! Don't do in the loop control, the decr/test order is wrong.
1555 		ch = encodedStr[inLimit];
1556 		if ( ch == '=' ) {
1557 			padding += 1;	// The equal sign padding is a data byte.
1558 		} else if ( DecodeBase64Char(ch) == 0xFF ) {
1559 			continue;	// Ignore whitespace, don't increment inStr.
1560 		} else {
1561 			inStr += 1;
1562 		}
1563 	}
1564 
1565 	// ! Be careful to count whitespace that is immediately before the final data. Otherwise
1566 	// ! middle portion will absorb the final data and mess up the final chunk processing.
1567 
1568 	while ( (inLimit > 0) && (DecodeBase64Char(encodedStr[inLimit-1]) == 0xFF) ) --inLimit;
1569 
1570 	if ( inStr == 0 ) return;	// Nothing but whitespace.
1571 	if ( padding > 2 ) XMP_Throw ( "Invalid encoded string", kXMPErr_BadParam );
1572 
1573 	// -------------------------------------------------------------------------------------------
1574 	// Now process all but the last chunk. The limit ensures that we have at least 4 data bytes
1575 	// left when entering the output loop, so the inner loop will succeed without overrunning the
1576 	// end of the data. At the end of the outer loop we might be past inLimit though.
1577 
1578 	inStr = 0;
1579 	while ( inStr < inLimit ) {
1580 
1581 		merge = 0;
1582 		for ( inChunk = 0; inChunk < 4; ++inStr ) { // ! Yes, increment inStr on each pass.
1583 			ch = DecodeBase64Char ( encodedStr [inStr] );
1584 			if ( ch == 0xFF ) continue; // Ignore whitespace.
1585 			merge = (merge << 6) + ch;
1586 			inChunk += 1;
1587 		}
1588 
1589 		rawChunk[0] = (unsigned char) (merge >> 16);
1590 		rawChunk[1] = (unsigned char) ((merge >> 8) & 0xFF);
1591 		rawChunk[2] = (unsigned char) (merge & 0xFF);
1592 
1593 		sBase64Str->append ( (char*)rawChunk, 3 );
1594 
1595 	}
1596 
1597 	// -------------------------------------------------------------------------------------------
1598 	// Process the final, possibly partial, chunk of data. The input is always a multiple 4 bytes,
1599 	// but the raw data can be any length. The number of padding '=' characters determines if the
1600 	// final chunk has 1, 2, or 3 raw data bytes.
1601 
1602 	XMP_Assert ( inStr < encodedLen );
1603 
1604 	merge = 0;
1605 	for ( inChunk = 0; inChunk < 4-padding; ++inStr ) { // ! Yes, increment inStr on each pass.
1606 		ch = DecodeBase64Char ( encodedStr[inStr] );
1607 		if ( ch == 0xFF ) continue; // Ignore whitespace.
1608 		merge = (merge << 6) + ch;
1609 		inChunk += 1;
1610 	}
1611 
1612 	if ( padding == 2 ) {
1613 
1614 		rawChunk[0] = (unsigned char) (merge >> 4);
1615 		sBase64Str->append ( (char*)rawChunk, 1 );
1616 
1617 	} else if ( padding == 1 ) {
1618 
1619 		rawChunk[0] = (unsigned char) (merge >> 10);
1620 		rawChunk[1] = (unsigned char) ((merge >> 2) & 0xFF);
1621 		sBase64Str->append ( (char*)rawChunk, 2 );
1622 
1623 	} else {
1624 
1625 		rawChunk[0] = (unsigned char) (merge >> 16);
1626 		rawChunk[1] = (unsigned char) ((merge >> 8) & 0xFF);
1627 		rawChunk[2] = (unsigned char) (merge & 0xFF);
1628 		sBase64Str->append ( (char*)rawChunk, 3 );
1629 
1630 	}
1631 
1632 	// -------------------------
1633 	// Assign the output values.
1634 
1635 	*rawStr = sBase64Str->c_str();
1636 	*rawLen = sBase64Str->size();
1637 
1638 }	// DecodeFromBase64
1639 
1640 
1641 // -------------------------------------------------------------------------------------------------
1642 // PackageForJPEG
1643 // --------------
1644 
1645 /* 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)1646 XMPUtils::PackageForJPEG ( const XMPMeta & origXMP,
1647 						   XMP_StringPtr * stdStr,
1648 						   XMP_StringLen * stdLen,
1649 						   XMP_StringPtr * extStr,
1650 						   XMP_StringLen * extLen,
1651 						   XMP_StringPtr * digestStr,
1652 						   XMP_StringLen * digestLen )
1653 {
1654 	enum { kStdXMPLimit = 65000 };
1655 	static const char * kPacketTrailer = "<?xpacket end=\"w\"?>";
1656 	static size_t kTrailerLen = strlen ( kPacketTrailer );
1657 
1658 	XMP_StringPtr tempStr;
1659 	XMP_StringLen tempLen;
1660 
1661 	XMPMeta stdXMP, extXMP;
1662 
1663 	sStandardXMP->clear();	// Clear the static strings that get returned to the client.
1664 	sExtendedXMP->clear();
1665 	sExtendedDigest->clear();
1666 
1667 	XMP_OptionBits keepItSmall = kXMP_UseCompactFormat | kXMP_OmitAllFormatting;
1668 
1669 	// Try to serialize everything. Note that we're making internal calls to SerializeToBuffer, so
1670 	// we'll be getting back the pointer and length for its internal string.
1671 
1672 	origXMP.SerializeToBuffer ( &tempStr, &tempLen, keepItSmall, 1, "", "", 0 );
1673 	#if Trace_PackageForJPEG
1674 		printf ( "\nXMPUtils::PackageForJPEG - Full serialize %d bytes\n", tempLen );
1675 	#endif
1676 
1677 	if ( tempLen > kStdXMPLimit ) {
1678 
1679 		// Couldn't fit everything, make a copy of the input XMP and make sure there is no xmp:Thumbnails property.
1680 
1681 		stdXMP.tree.options = origXMP.tree.options;
1682 		stdXMP.tree.name    = origXMP.tree.name;
1683 		stdXMP.tree.value   = origXMP.tree.value;
1684 		CloneOffspring ( &origXMP.tree, &stdXMP.tree );
1685 
1686 		if ( stdXMP.DoesPropertyExist ( kXMP_NS_XMP, "Thumbnails" ) ) {
1687 			stdXMP.DeleteProperty ( kXMP_NS_XMP, "Thumbnails" );
1688 			stdXMP.SerializeToBuffer ( &tempStr, &tempLen, keepItSmall, 1, "", "", 0 );
1689 			#if Trace_PackageForJPEG
1690 				printf ( "  Delete xmp:Thumbnails, %d bytes left\n", tempLen );
1691 			#endif
1692 		}
1693 
1694 	}
1695 
1696 	if ( tempLen > kStdXMPLimit ) {
1697 
1698 		// Still doesn't fit, move all of the Camera Raw namespace. Add a dummy value for xmpNote:HasExtendedXMP.
1699 
1700 		stdXMP.SetProperty ( kXMP_NS_XMP_Note, "HasExtendedXMP", "123456789-123456789-123456789-12", 0 );
1701 
1702 		XMP_NodePtrPos crSchemaPos;
1703 		XMP_Node * crSchema = FindSchemaNode ( &stdXMP.tree, kXMP_NS_CameraRaw, kXMP_ExistingOnly, &crSchemaPos );
1704 
1705 		if ( crSchema != 0 ) {
1706 			crSchema->parent = &extXMP.tree;
1707 			extXMP.tree.children.push_back ( crSchema );
1708 			stdXMP.tree.children.erase ( crSchemaPos );
1709 			stdXMP.SerializeToBuffer ( &tempStr, &tempLen, keepItSmall, 1, "", "", 0 );
1710 			#if Trace_PackageForJPEG
1711 				printf ( "  Move Camera Raw schema, %d bytes left\n", tempLen );
1712 			#endif
1713 		}
1714 
1715 	}
1716 
1717 	if ( tempLen > kStdXMPLimit ) {
1718 
1719 		// Still doesn't fit, move photoshop:History.
1720 
1721 		bool moved = MoveOneProperty ( stdXMP, &extXMP, kXMP_NS_Photoshop, "photoshop:History" );
1722 
1723 		if ( moved ) {
1724 			stdXMP.SerializeToBuffer ( &tempStr, &tempLen, keepItSmall, 1, "", "", 0 );
1725 			#if Trace_PackageForJPEG
1726 				printf ( "  Move photoshop:History, %d bytes left\n", tempLen );
1727 			#endif
1728 		}
1729 
1730 	}
1731 
1732 	if ( tempLen > kStdXMPLimit ) {
1733 
1734 		// Still doesn't fit, move top level properties in order of estimated size. This is done by
1735 		// creating a multi-map that maps the serialized size to the string pair for the schema URI
1736 		// and top level property name. Since maps are inherently ordered, a reverse iteration of
1737 		// the map can be done to move the largest things first. We use a double loop to keep going
1738 		// until the serialization actually fits, in case the estimates are off.
1739 
1740 		PropSizeMap propSizes;
1741 		CreateEstimatedSizeMap ( stdXMP, &propSizes );
1742 
1743 		#if Trace_PackageForJPEG
1744 		if ( ! propSizes.empty() ) {
1745 			printf ( "  Top level property map, smallest to largest:\n" );
1746 			PropSizeMap::iterator mapPos = propSizes.begin();
1747 			PropSizeMap::iterator mapEnd = propSizes.end();
1748 			for ( ; mapPos != mapEnd; ++mapPos ) {
1749 				size_t propSize = mapPos->first;
1750 				const char * schemaName = mapPos->second.first->c_str();
1751 				const char * propName   = mapPos->second.second->c_str();
1752 				printf ( "    %d bytes, %s in %s\n", propSize, propName, schemaName );
1753 			}
1754 		}
1755 		#endif
1756 
1757 		#if 0	// Trace_PackageForJPEG		*** Xcode 2.3 on 10.4.7 has bugs in backwards iteration
1758 		if ( ! propSizes.empty() ) {
1759 			printf ( "  Top level property map, largest to smallest:\n" );
1760 			PropSizeMap::iterator mapPos   = propSizes.end();
1761 			PropSizeMap::iterator mapBegin = propSizes.begin();
1762 			for ( --mapPos; true; --mapPos ) {
1763 				size_t propSize = mapPos->first;
1764 				const char * schemaName = mapPos->second.first->c_str();
1765 				const char * propName   = mapPos->second.second->c_str();
1766 				printf ( "    %d bytes, %s in %s\n", propSize, propName, schemaName );
1767 				if ( mapPos == mapBegin ) break;
1768 			}
1769 		}
1770 		#endif
1771 
1772 		// Outer loop to make sure enough is actually moved.
1773 
1774 		while ( (tempLen > kStdXMPLimit) && (! propSizes.empty()) ) {
1775 
1776 			// Inner loop, move what seems to be enough according to the estimates.
1777 
1778 			while ( (tempLen > kStdXMPLimit) && (! propSizes.empty()) ) {
1779 
1780 				size_t propSize = MoveLargestProperty ( stdXMP, &extXMP, propSizes );
1781 				XMP_Assert ( propSize > 0 );
1782 
1783 				if ( propSize > tempLen ) propSize = tempLen;	// ! Don't go negative.
1784 				tempLen -= propSize;
1785 
1786 			}
1787 
1788 			// Reserialize the remaining standard XMP.
1789 
1790 			stdXMP.SerializeToBuffer ( &tempStr, &tempLen, keepItSmall, 1, "", "", 0 );
1791 
1792 		}
1793 
1794 	}
1795 
1796 	if ( tempLen > kStdXMPLimit ) {
1797 		// Still doesn't fit, throw an exception and let the client decide what to do.
1798 		// ! This should never happen with the policy of moving any and all top level properties.
1799 		XMP_Throw ( "Can't reduce XMP enough for JPEG file", kXMPErr_TooLargeForJPEG );
1800 	}
1801 
1802 	// Set the static output strings.
1803 
1804 	if ( extXMP.tree.children.empty() ) {
1805 
1806 		// Just have the standard XMP.
1807 		sStandardXMP->assign ( tempStr, tempLen );
1808 
1809 	} else {
1810 
1811 		// Have extended XMP. Serialize it, compute the digest, reset xmpNote:HasExtendedXMP, and
1812 		// reserialize the standard XMP.
1813 
1814 		extXMP.SerializeToBuffer ( &tempStr, &tempLen, (keepItSmall | kXMP_OmitPacketWrapper), 0, "", "", 0 );
1815 		sExtendedXMP->assign ( tempStr, tempLen );
1816 
1817 		MD5_CTX  context;
1818 		XMP_Uns8 digest [16];
1819 		MD5Init ( &context );
1820 		MD5Update ( &context, (XMP_Uns8*)tempStr, tempLen );
1821 		MD5Final ( digest, &context );
1822 
1823 		sExtendedDigest->reserve ( 32 );
1824 		for ( size_t i = 0; i < 16; ++i ) {
1825 			XMP_Uns8 byte = digest[i];
1826 			sExtendedDigest->push_back ( kHexDigits [ byte>>4 ] );
1827 			sExtendedDigest->push_back ( kHexDigits [ byte&0xF ] );
1828 		}
1829 
1830 		stdXMP.SetProperty ( kXMP_NS_XMP_Note, "HasExtendedXMP", sExtendedDigest->c_str(), 0 );
1831 		stdXMP.SerializeToBuffer ( &tempStr, &tempLen, keepItSmall, 1, "", "", 0 );
1832 		sStandardXMP->assign ( tempStr, tempLen );
1833 
1834 	}
1835 
1836 	// Adjust the standard XMP padding to be up to 2KB.
1837 
1838 	XMP_Assert ( (sStandardXMP->size() > kTrailerLen) && (sStandardXMP->size() <= kStdXMPLimit) );
1839 	const char * packetEnd = sStandardXMP->c_str() + sStandardXMP->size() - kTrailerLen;
1840 	XMP_Assert ( XMP_LitMatch ( packetEnd, kPacketTrailer ) );
1841 
1842 	size_t extraPadding = kStdXMPLimit - sStandardXMP->size();	// ! Do this before erasing the trailer.
1843 	if ( extraPadding > 2047 ) extraPadding = 2047;
1844 	sStandardXMP->erase ( sStandardXMP->size() - kTrailerLen );
1845 	sStandardXMP->append ( extraPadding, ' ' );
1846 	sStandardXMP->append ( kPacketTrailer );
1847 
1848 	// Assign the output pointer and sizes.
1849 
1850 	*stdStr = sStandardXMP->c_str();
1851 	*stdLen = sStandardXMP->size();
1852 	*extStr = sExtendedXMP->c_str();
1853 	*extLen = sExtendedXMP->size();
1854 	*digestStr = sExtendedDigest->c_str();
1855 	*digestLen = sExtendedDigest->size();
1856 
1857 }	// PackageForJPEG
1858 
1859 
1860 // -------------------------------------------------------------------------------------------------
1861 // MergeFromJPEG
1862 // -------------
1863 //
1864 // Copy all of the top level properties from extendedXMP to fullXMP, replacing any duplicates.
1865 // Delete the xmpNote:HasExtendedXMP property from fullXMP.
1866 
1867 /* class static */ void
MergeFromJPEG(XMPMeta * fullXMP,const XMPMeta & extendedXMP)1868 XMPUtils::MergeFromJPEG ( XMPMeta *       fullXMP,
1869                           const XMPMeta & extendedXMP )
1870 {
1871 
1872 	XMPUtils::AppendProperties ( extendedXMP, fullXMP, kXMPUtil_DoAllProperties );
1873 	fullXMP->DeleteProperty ( kXMP_NS_XMP_Note, "HasExtendedXMP" );
1874 
1875 }	// MergeFromJPEG
1876 
1877 
1878 // -------------------------------------------------------------------------------------------------
1879 // CurrentDateTime
1880 // ---------------
1881 
1882 /* class static */ void
CurrentDateTime(XMP_DateTime * xmpTime)1883 XMPUtils::CurrentDateTime ( XMP_DateTime * xmpTime )
1884 {
1885 	XMP_Assert ( xmpTime != 0 );	// ! Enforced by wrapper.
1886 
1887 	ansi_tt binTime = ansi_time(0);
1888 	if ( binTime == -1 ) XMP_Throw ( "Failure from ANSI C time function", kXMPErr_ExternalFailure );
1889 	ansi_tm currTime;
1890 	ansi_localtime ( &binTime, &currTime );
1891 
1892 	xmpTime->year = currTime.tm_year + 1900;
1893 	xmpTime->month = currTime.tm_mon + 1;
1894 	xmpTime->day = currTime.tm_mday;
1895 	xmpTime->hour = currTime.tm_hour;
1896 	xmpTime->minute = currTime.tm_min;
1897 	xmpTime->second = currTime.tm_sec;
1898 
1899 	xmpTime->nanoSecond = 0;
1900 	xmpTime->tzSign = 0;
1901 	xmpTime->tzHour = 0;
1902 	xmpTime->tzMinute = 0;
1903 
1904 	XMPUtils::SetTimeZone ( xmpTime );
1905 
1906 }	// CurrentDateTime
1907 
1908 
1909 // -------------------------------------------------------------------------------------------------
1910 // SetTimeZone
1911 // -----------
1912 //
1913 // Sets just the time zone part of the time.  Useful for determining the local time zone or for
1914 // converting a "zone-less" time to a proper local time. The ANSI C time functions are smart enough
1915 // to do all the right stuff, as long as we call them properly!
1916 
1917 /* class static */ void
SetTimeZone(XMP_DateTime * xmpTime)1918 XMPUtils::SetTimeZone ( XMP_DateTime * xmpTime )
1919 {
1920 	XMP_Assert ( xmpTime != 0 );	// ! Enforced by wrapper.
1921 
1922 	if ( (xmpTime->tzSign != 0) || (xmpTime->tzHour != 0) || (xmpTime->tzMinute != 0) ) {
1923 		XMP_Throw ( "SetTimeZone can only be used on \"zoneless\" times", kXMPErr_BadParam );
1924 	}
1925 
1926 	// Create ansi_tt form of the input time. Need the ansi_tm form to make the ansi_tt form.
1927 
1928 	ansi_tt ttTime;
1929 	ansi_tm tmLocal, tmUTC;
1930 
1931 	if ( (xmpTime->year == 0) && (xmpTime->month == 0) && (xmpTime->day == 0) ) {
1932 		ansi_tt now = ansi_time(0);
1933 		if ( now == -1 ) XMP_Throw ( "Failure from ANSI C time function", kXMPErr_ExternalFailure );
1934 		ansi_localtime ( &now, &tmLocal );
1935 	} else {
1936 		tmLocal.tm_year = xmpTime->year - 1900;
1937 		while ( tmLocal.tm_year < 70 ) tmLocal.tm_year += 4;	// ! Some versions of mktime barf on years before 1970.
1938 		tmLocal.tm_mon	 = xmpTime->month - 1;
1939 		tmLocal.tm_mday	 = xmpTime->day;
1940 	}
1941 
1942 	tmLocal.tm_hour = xmpTime->hour;
1943 	tmLocal.tm_min = xmpTime->minute;
1944 	tmLocal.tm_sec = xmpTime->second;
1945 	tmLocal.tm_isdst = -1;	// Don't know if daylight time is in effect.
1946 
1947 	ttTime = ansi_mktime ( &tmLocal );
1948 	if ( ttTime == -1 ) XMP_Throw ( "Failure from ANSI C mktime function", kXMPErr_ExternalFailure );
1949 
1950 	// Convert back to a localized ansi_tm time and get the corresponding UTC ansi_tm time.
1951 
1952 	ansi_localtime ( &ttTime, &tmLocal );
1953 	ansi_gmtime ( &ttTime, &tmUTC );
1954 
1955 	// Get the offset direction and amount.
1956 
1957 	ansi_tm tmx = tmLocal;	// ! Note that mktime updates the ansi_tm parameter, messing up difftime!
1958 	ansi_tm tmy = tmUTC;
1959 	tmx.tm_isdst = tmy.tm_isdst = 0;
1960 	ansi_tt ttx = ansi_mktime ( &tmx );
1961 	ansi_tt tty = ansi_mktime ( &tmy );
1962 	double diffSecs;
1963 
1964 	if ( (ttx != -1) && (tty != -1) ) {
1965 		diffSecs = ansi_difftime ( ttx, tty );
1966 	} else {
1967 		#if XMP_MacBuild
1968 			// Looks like Apple's mktime is buggy - see W1140533. But the offset is visible.
1969 			diffSecs = tmLocal.tm_gmtoff;
1970 		#else
1971 			// Win and UNIX don't have a visible offset. Make sure we know about the failure,
1972 			// then try using the current date/time as a close fallback.
1973 			ttTime = ansi_time(0);
1974 			if ( ttTime == -1 ) XMP_Throw ( "Failure from ANSI C time function", kXMPErr_ExternalFailure );
1975 			ansi_localtime ( &ttTime, &tmx );
1976 			ansi_gmtime ( &ttTime, &tmy );
1977 			tmx.tm_isdst = tmy.tm_isdst = 0;
1978 			ttx = ansi_mktime ( &tmx );
1979 			tty = ansi_mktime ( &tmy );
1980 			if ( (ttx == -1) || (tty == -1) ) XMP_Throw ( "Failure from ANSI C mktime function", kXMPErr_ExternalFailure );
1981 			diffSecs = ansi_difftime ( ttx, tty );
1982 		#endif
1983 	}
1984 
1985 	if ( diffSecs > 0.0 ) {
1986 		xmpTime->tzSign = kXMP_TimeEastOfUTC;
1987 	} else if ( diffSecs == 0.0 ) {
1988 		xmpTime->tzSign = kXMP_TimeIsUTC;
1989 	} else {
1990 		xmpTime->tzSign = kXMP_TimeWestOfUTC;
1991 		diffSecs = -diffSecs;
1992 	}
1993 	xmpTime->tzHour = XMP_Int32 ( diffSecs / 3600.0 );
1994 	xmpTime->tzMinute = XMP_Int32 ( (diffSecs / 60.0) - (xmpTime->tzHour * 60.0) );
1995 
1996 	// *** Save the tm_isdst flag in a qualifier?
1997 
1998 	XMP_Assert ( (0 <= xmpTime->tzHour) && (xmpTime->tzHour <= 23) );
1999 	XMP_Assert ( (0 <= xmpTime->tzMinute) && (xmpTime->tzMinute <= 59) );
2000 	XMP_Assert ( (-1 <= xmpTime->tzSign) && (xmpTime->tzSign <= +1) );
2001 	XMP_Assert ( (xmpTime->tzSign == 0) ? ((xmpTime->tzHour == 0) && (xmpTime->tzMinute == 0)) :
2002 										  ((xmpTime->tzHour != 0) || (xmpTime->tzMinute != 0)) );
2003 
2004 }	// SetTimeZone
2005 
2006 
2007 // -------------------------------------------------------------------------------------------------
2008 // ConvertToUTCTime
2009 // ----------------
2010 
2011 /* class static */ void
ConvertToUTCTime(XMP_DateTime * time)2012 XMPUtils::ConvertToUTCTime ( XMP_DateTime * time )
2013 {
2014 	XMP_Assert ( time != 0 );	// ! Enforced by wrapper.
2015 
2016 	XMP_Assert ( (0 <= time->tzHour) && (time->tzHour <= 23) );
2017 	XMP_Assert ( (0 <= time->tzMinute) && (time->tzMinute <= 59) );
2018 	XMP_Assert ( (-1 <= time->tzSign) && (time->tzSign <= +1) );
2019 	XMP_Assert ( (time->tzSign == 0) ? ((time->tzHour == 0) && (time->tzMinute == 0)) :
2020 									   ((time->tzHour != 0) || (time->tzMinute != 0)) );
2021 
2022 	if ( time->tzSign == kXMP_TimeEastOfUTC ) {
2023 		// We are before (east of) GMT, subtract the offset from the time.
2024 		time->hour -= time->tzHour;
2025 		time->minute -= time->tzMinute;
2026 	} else if ( time->tzSign == kXMP_TimeWestOfUTC ) {
2027 		// We are behind (west of) GMT, add the offset to the time.
2028 		time->hour += time->tzHour;
2029 		time->minute += time->tzMinute;
2030 	}
2031 
2032 	AdjustTimeOverflow ( time );
2033 	time->tzSign = time->tzHour = time->tzMinute = 0;
2034 
2035 }	// ConvertToUTCTime
2036 
2037 
2038 // -------------------------------------------------------------------------------------------------
2039 // ConvertToLocalTime
2040 // ------------------
2041 
2042 /* class static */ void
ConvertToLocalTime(XMP_DateTime * time)2043 XMPUtils::ConvertToLocalTime ( XMP_DateTime * time )
2044 {
2045 	XMP_Assert ( time != 0 );	// ! Enforced by wrapper.
2046 
2047 	XMP_Assert ( (0 <= time->tzHour) && (time->tzHour <= 23) );
2048 	XMP_Assert ( (0 <= time->tzMinute) && (time->tzMinute <= 59) );
2049 	XMP_Assert ( (-1 <= time->tzSign) && (time->tzSign <= +1) );
2050 	XMP_Assert ( (time->tzSign == 0) ? ((time->tzHour == 0) && (time->tzMinute == 0)) :
2051 									   ((time->tzHour != 0) || (time->tzMinute != 0)) );
2052 
2053 	ConvertToUTCTime ( time );	// The existing time zone might not be the local one.
2054 	SetTimeZone ( time );		// Fill in the local timezone offset, then adjust the time.
2055 
2056 	if ( time->tzSign > 0 ) {
2057 		// We are before (east of) GMT, add the offset to the time.
2058 		time->hour += time->tzHour;
2059 		time->minute += time->tzMinute;
2060 	} else if ( time->tzSign < 0 ) {
2061 		// We are behind (west of) GMT, subtract the offset from the time.
2062 		time->hour -= time->tzHour;
2063 		time->minute -= time->tzMinute;
2064 	}
2065 
2066 	AdjustTimeOverflow ( time );
2067 
2068 }	// ConvertToLocalTime
2069 
2070 
2071 // -------------------------------------------------------------------------------------------------
2072 // CompareDateTime
2073 // ---------------
2074 
2075 /* class static */ int
CompareDateTime(const XMP_DateTime & _in_left,const XMP_DateTime & _in_right)2076 XMPUtils::CompareDateTime ( const XMP_DateTime & _in_left,
2077 							const XMP_DateTime & _in_right )
2078 {
2079 	int result;
2080 
2081 	XMP_DateTime left  = _in_left;
2082 	XMP_DateTime right = _in_right;
2083 
2084 	ConvertToUTCTime ( &left );
2085 	ConvertToUTCTime ( &right );
2086 
2087 	// *** We could use memcmp if the XMP_DateTime stuct has no holes.
2088 
2089 	if ( left.year < right.year ) {
2090 		result = -1;
2091 	} else if ( left.year > right.year ) {
2092 		result = +1;
2093 	} else if ( left.month < right.month ) {
2094 		result = -1;
2095 	} else if ( left.month > right.month ) {
2096 		result = +1;
2097 	} else if ( left.day < right.day ) {
2098 		result = -1;
2099 	} else if ( left.day > right.day ) {
2100 		result = +1;
2101 	} else if ( left.hour < right.hour ) {
2102 		result = -1;
2103 	} else if ( left.hour > right.hour ) {
2104 		result = +1;
2105 	} else if ( left.minute < right.minute ) {
2106 		result = -1;
2107 	} else if ( left.minute > right.minute ) {
2108 		result = +1;
2109 	} else if ( left.second < right.second ) {
2110 		result = -1;
2111 	} else if ( left.second > right.second ) {
2112 		result = +1;
2113 	} else if ( left.nanoSecond < right.nanoSecond ) {
2114 		result = -1;
2115 	} else if ( left.nanoSecond > right.nanoSecond ) {
2116 		result = +1;
2117 	} else {
2118 		result = 0;
2119 	}
2120 
2121 	return result;
2122 
2123 }	// CompareDateTime
2124 
2125 // =================================================================================================
2126