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