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