Coverage Report

Created: 2026-04-07 06:51

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