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