1 // =================================================================================================
2 // ADOBE SYSTEMS INCORPORATED
3 // Copyright 2008 Adobe Systems Incorporated
4 // All Rights Reserved
5 //
6 // NOTICE: Adobe permits you to use, modify, and distribute this file in accordance with the terms
7 // of the Adobe license agreement accompanying it.
8 // =================================================================================================
9 
10 #include "public/include/XMP_Environment.h"	// ! XMP_Environment.h must be the first included header.
11 
12 #include "public/include/XMP_Const.h"
13 #include "public/include/XMP_IO.hpp"
14 
15 #include "XMPFiles/source/XMPFiles_Impl.hpp"
16 #include "source/XMPFiles_IO.hpp"
17 #include "source/XIO.hpp"
18 
19 // must have access to handler class fields...
20 #include "XMPFiles/source/FormatSupport/RIFF.hpp"
21 #include "XMPFiles/source/FileHandlers/RIFF_Handler.hpp"
22 #include "XMPFiles/source/FormatSupport/RIFF_Support.hpp"
23 #include "XMPFiles/source/FormatSupport/Reconcile_Impl.hpp"
24 
25 #include <sstream>
26 
27 #define MIN(a, b)       ((a) < (b) ? (a) : (b))
28 
29 using namespace RIFF;
30 namespace RIFF {
31 
32 // The minimum BEXT chunk size should be 610 (incl. 8 byte header/size field)
33 XMP_Int32 MIN_BEXT_SIZE = 610; // = > 8 + ( 256+32+32+10+8+4+4+2+64+190+0 )
34 
35 // An assumed secure max value of 100 MB.
36 XMP_Int32 MAX_BEXT_SIZE = 100 * 1024 * 1024;
37 
38 // CR8R, PrmL have fixed sizes
39 XMP_Int32 CR8R_SIZE = 0x5C;
40 XMP_Int32 PRML_SIZE = 0x122;
41 
42 // IDIT chunk size
43 XMP_Int32 IDIT_SIZE = 0x1A;
44 
45 static const char* sHexChars =    "0123456789ABCDEF";
46 
47 // Encode a string of raw data bytes into a HexString (w/o spaces, i.e. "DEADBEEF").
48 // No insertation/acceptance of whitespace/linefeeds. No output/tolerance of lowercase.
49 // returns true, if *all* characters returned are zero (or if 0 bytes are returned).
EncodeToHexString(XMP_StringPtr rawStr,XMP_StringLen rawLen,std::string * encodedStr)50 static bool EncodeToHexString ( XMP_StringPtr   rawStr,
51 						      XMP_StringLen   rawLen,
52 						      std::string* encodedStr )
53 {
54 	bool allZero = true; // assume for now
55 
56 	if ( (rawStr == 0) && (rawLen != 0) )
57 		XMP_Throw ( "EncodeToHexString: null rawStr", kXMPErr_BadParam );
58 	if ( encodedStr == 0 )
59 		XMP_Throw ( "EncodeToHexString: null encodedStr", kXMPErr_BadParam );
60 
61 	encodedStr->erase();
62 	if ( rawLen == 0 ) return allZero;
63 	encodedStr->reserve ( rawLen * 2 );
64 
65 	for( XMP_Uns32 i = 0; i < rawLen; i++ )
66 	{
67 		// first, second nibble
68 		XMP_Uns8 first = rawStr[i] >> 4;
69 		XMP_Uns8 second = rawStr[i] & 0xF;
70 
71 		if ( allZero && (( first != 0 ) || (second != 0)))
72 			allZero = false;
73 
74 		encodedStr->append( 1, sHexChars[first] );
75 		encodedStr->append( 1, sHexChars[second] );
76 	}
77 
78 	return allZero;
79 }	// EncodeToHexString
80 
81 // -------------------------------------------------------------------------------------------------
82 // DecodeFromHexString
83 // ----------------
84 //
85 // Decode a hex string to raw data bytes.
86 // * Input must be all uppercase and w/o any whitespace, strictly (0-9A-Z)* (i.e. "DEADBEEF0099AABC")
87 // * No insertation/acceptance of whitespace/linefeeds.
88 // * bNo use/tolerance of lowercase.
89 // * Number of bytes in the encoded String must be even.
90 // * returns true if everything went well, false if illegal (non 0-9A-F) character encountered
91 
DecodeFromHexString(XMP_StringPtr encodedStr,XMP_StringLen encodedLen,std::string * rawStr)92 static bool DecodeFromHexString ( XMP_StringPtr  encodedStr,
93 							    XMP_StringLen  encodedLen,
94 							    std::string*   rawStr )
95 {
96 	if ( (encodedLen % 2) != 0 )
97 		return false;
98 	rawStr->erase();
99 	if ( encodedLen == 0 ) return true;
100 	rawStr->reserve ( encodedLen / 2 );
101 
102 	for( XMP_Uns32 i = 0; i < encodedLen; )
103 	{
104 		XMP_Uns8 upperNibble = encodedStr[i];
105 		if ( (upperNibble < 48) || ( (upperNibble > 57 ) && ( upperNibble < 65 ) ) || (upperNibble > 70) )
106 			return false;
107 		if ( upperNibble >= 65 )
108 			upperNibble -= 7; // shift A-F area adjacent to 0-9
109 		upperNibble -= 48; // 'shift' to a value [0..15]
110 		upperNibble = ( upperNibble << 4 );
111 		i++;
112 
113 		XMP_Uns8 lowerNibble = encodedStr[i];
114 		if ( (lowerNibble < 48) || ( (lowerNibble > 57 ) && ( lowerNibble < 65 ) ) || (lowerNibble > 70) )
115 			return false;
116 		if ( lowerNibble >= 65 )
117 			lowerNibble -= 7; // shift A-F area adjacent to 0-9
118 		lowerNibble -= 48; // 'shift' to a value [0..15]
119 		i++;
120 
121 		rawStr->append ( 1, (upperNibble + lowerNibble) );
122 	}
123 	return true;
124 }	// DecodeFromHexString
125 
126 // Converts input string to an ascii output string
127 // - terminates at first 0
128 // - replaces all non ascii with 0x3F ('?')
129 // - produces up to maxOut characters (note that several UTF-8 character bytes can 'melt' to one byte '?' in ascii.)
convertToASCII(XMP_StringPtr input,XMP_StringLen inputLen,std::string * output,XMP_StringLen maxOutputLen)130 static XMP_StringLen convertToASCII( XMP_StringPtr input, XMP_StringLen inputLen, std::string* output, XMP_StringLen maxOutputLen )
131 {
132 	if ( (input == 0) && (inputLen != 0) )
133 		XMP_Throw ( "convertToASCII: null input string", kXMPErr_BadParam );
134 	if ( output == 0)
135 		XMP_Throw ( "convertToASCII: null output string", kXMPErr_BadParam );
136 	if ( maxOutputLen == 0)
137 		XMP_Throw ( "convertToASCII: zero maxOutputLen chars", kXMPErr_BadParam );
138 
139 	output->reserve(inputLen);
140 	output->erase();
141 
142 	bool isUTF8 = ReconcileUtils::IsUTF8( input, inputLen );
143 	XMP_StringLen outputLen = 0;
144 
145 	for ( XMP_Uns32 i=0; i < inputLen; i++ )
146 	{
147 		XMP_Uns8 c = (XMP_Uns8) input[i];
148 		if ( c == 0 )  // early 0 termination, leave.
149 			break;
150 		if ( c > 127 ) // uft-8 multi-byte sequence.
151 		{
152 			if ( isUTF8 ) // skip all high bytes
153 			{
154 				// how many bytes in this ?
155 				if ( c >= 0xC2 && c <= 0xDF )
156 					i+=1; // 2-byte sequence
157 				else if ( c >= 0xE0 && c <= 0xEF )
158 					i+=2; // 3-byte sequence
159 				else if ( c >= 0xF0 && c <= 0xF4 )
160 					i+=3; // 4-byte sequence
161 				else
162 					continue; //invalid sequence, look for next 'low' byte ..
163 			} // thereafter and 'else': just append a question mark:
164 			output->append( 1, '?' );
165 		}
166 		else // regular valid ascii. 1 byte.
167 		{
168 			output->append( 1, input[i] );
169 		}
170 		outputLen++;
171 		if ( outputLen >= maxOutputLen )
172 			break; // (may be even or even greater due to UFT-8 multi-byte jumps)
173 	}
174 
175 	return outputLen;
176 }
177 
178 /**
179  * ensures that native property gets returned as UTF-8 (may or mayn not already be UTF-8)
180  *  - also takes care of "moot padding" (pre-mature zero termination)
181  *  - propertyExists: it is important to know if there as an existing, non zero property
182  *    even (in the event of serverMode) it is not actually returned, but an empty string instead.
183  */
nativePropertyToUTF8(XMP_StringPtr cstring,XMP_StringLen maxSize,bool * propertyExists)184 static std::string nativePropertyToUTF8 ( XMP_StringPtr cstring, XMP_StringLen maxSize, bool* propertyExists )
185 {
186 	// the value might be properly 0-terminated, prematurely or not
187 	// at all, hence scan through to find actual size
188 	XMP_StringLen size   = 0;
189 	for ( size = 0; size < maxSize; size++ )
190 	{
191 		if ( cstring[size] == 0 )
192 			break;
193 	}
194 
195 	(*propertyExists) = ( size > 0 );
196 
197 	std::string utf8("");
198 	if ( ReconcileUtils::IsUTF8( cstring, size ) )
199 		utf8 = std::string( cstring, size ); //use utf8 directly
200 	else
201 	{
202 		if ( ! ignoreLocalText )
203 		{
204 			#if ! UNIX_ENV // n/a anyway, since always ignoreLocalText on Unix
205 				ReconcileUtils::LocalToUTF8( cstring, size, &utf8 );
206 			#endif
207 		}
208 	}
209 	return utf8;
210 }
211 
212 // reads maxSize bytes from file (not "up to", exactly fullSize)
213 // puts it into a string, sets respective tree property
getBextField(const char * data,XMP_Uns32 offset,XMP_Uns32 maxSize)214 static std::string getBextField ( const char* data, XMP_Uns32 offset, XMP_Uns32 maxSize )
215 {
216 	if (data == 0)
217 		XMP_Throw ( "getBextField: null data pointer", kXMPErr_BadParam );
218 	if ( maxSize == 0)
219 		XMP_Throw ( "getBextField: maxSize must be greater than 0", kXMPErr_BadParam );
220 
221 	std::string r;
222 	convertToASCII( data+offset, maxSize, &r, maxSize );
223 	return r;
224 }
225 
importBextChunkToXMP(RIFF_MetaHandler * handler,ValueChunk * bextChunk)226 static void importBextChunkToXMP( RIFF_MetaHandler* handler, ValueChunk* bextChunk )
227 {
228 	// if there's a bext chunk, there is data...
229 	handler->containsXMP = true; // very important for treatment on caller level
230 
231 	XMP_Enforce( bextChunk->oldSize >= MIN_BEXT_SIZE );
232 	XMP_Enforce( bextChunk->oldSize < MAX_BEXT_SIZE );
233 
234 	const char* data = bextChunk->oldValue.data();
235 	std::string value;
236 
237 	// register bext namespace:
238 	SXMPMeta::RegisterNamespace( kXMP_NS_BWF, "bext:", 0 );
239 
240 	// bextDescription ------------------------------------------------
241 	value = getBextField( data, 0, 256 );
242 	if ( value.size() > 0 )
243 		handler->xmpObj.SetProperty( bextDescription.ns, bextDescription.prop, value.c_str() );
244 
245 	// bextOriginator -------------------------------------------------
246 	value = getBextField( data, 256, 32 );
247 	if ( value.size() > 0 )
248 		handler->xmpObj.SetProperty( bextOriginator.ns , bextOriginator.prop, value.c_str() );
249 
250 	// bextOriginatorRef ----------------------------------------------
251 	value = getBextField( data, 256+32, 32 );
252 	if ( value.size() > 0 )
253 		handler->xmpObj.SetProperty( bextOriginatorRef.ns , bextOriginatorRef.prop, value.c_str() );
254 
255 	// bextOriginationDate --------------------------------------------
256 	value = getBextField( data, 256+32+32, 10 );
257 	if ( value.size() > 0 )
258 		handler->xmpObj.SetProperty( bextOriginationDate.ns , bextOriginationDate.prop, value.c_str() );
259 
260 	// bextOriginationTime --------------------------------------------
261 	value = getBextField( data, 256+32+32+10, 8 );
262 	if ( value.size() > 0 )
263 		handler->xmpObj.SetProperty( bextOriginationTime.ns , bextOriginationTime.prop, value.c_str() );
264 
265 	// bextTimeReference ----------------------------------------------
266 	// thanx to nice byte order, all 8 bytes can be read as one:
267 	XMP_Uns64 timeReferenceFull  = GetUns64LE( &(data[256+32+32+10+8 ] ) );
268 	value.erase();
269 	SXMPUtils::ConvertFromInt64( timeReferenceFull, "%llu", &value );
270 	handler->xmpObj.SetProperty( bextTimeReference.ns, bextTimeReference.prop, value );
271 
272 	// bextVersion ----------------------------------------------------
273 	XMP_Uns16 bwfVersion  = GetUns16LE( &(data[256+32+32+10+8+8] ) );
274 	value.erase();
275 	SXMPUtils::ConvertFromInt( bwfVersion, "", &value );
276 	handler->xmpObj.SetProperty( bextVersion.ns, bextVersion.prop, value );
277 
278 	// bextUMID -------------------------------------------------------
279 	// binary string is already in memory, must convert to hex string
280 	std::string umidString;
281 	bool allZero = EncodeToHexString( &(data[256+32+32+10+8+8+2]), 64, &umidString );
282 	if (! allZero )
283 		handler->xmpObj.SetProperty( bextUMID.ns, bextUMID.prop, umidString );
284 
285 	// bextCodingHistory ----------------------------------------------
286 	bool hasCodingHistory = bextChunk->oldSize > MIN_BEXT_SIZE;
287 
288 	if ( hasCodingHistory )
289 	{
290 		XMP_StringLen codingHistorySize = (XMP_StringLen) (bextChunk->oldSize - MIN_BEXT_SIZE);
291 		std::string codingHistory;
292 		convertToASCII( &data[MIN_BEXT_SIZE-8], codingHistorySize, &codingHistory, codingHistorySize );
293 		if (! codingHistory.empty() )
294 			handler->xmpObj.SetProperty( bextCodingHistory.ns, bextCodingHistory.prop, codingHistory );
295 	}
296 } // importBextChunkToXMP
297 
GetMonth(const char * valuePtr)298 static XMP_Int32 GetMonth( const char * valuePtr )
299 {
300 	// This will check 3 characters (4,5,6) of the input and return corresponding months as 1,2,...,12
301 	// else it will return 0
302 	// Input should as follows : wda mon dd hh:mm:ss yyyy\n\0
303 	char firstChar = tolower( valuePtr[ 4 ] );
304 	char secondChar = tolower( valuePtr[ 5 ] );
305 	char thirdChar = tolower( valuePtr[ 6 ] );
306 	if ( firstChar == 'j' &&  secondChar == 'a' && thirdChar == 'n' )
307 		return 1;
308 	if ( firstChar == 'f' &&  secondChar == 'e' && thirdChar == 'b' )
309 		return 2;
310 	if ( firstChar == 'm' && secondChar == 'a' )
311 	{
312 		if ( thirdChar == 'r' )
313 			return 3;
314 		if ( thirdChar == 'y' )
315 			return 5;
316 	}
317 	if ( firstChar == 'a' &&  secondChar == 'p' && thirdChar == 'r' )
318 		return 4;
319 	if ( firstChar == 'j' && secondChar == 'u' )
320 	{
321 		if ( thirdChar == 'n' )
322 			return 6;
323 		if ( thirdChar == 'l' )
324 			return 7;
325 	}
326 	if ( firstChar == 'a' &&  secondChar == 'u' && thirdChar == 'g' )
327 		return 8;
328 	if ( firstChar == 's' &&  secondChar == 'e' && thirdChar == 'p' )
329 		return 9;
330 	if ( firstChar == 'o' &&  secondChar == 'c' && thirdChar == 't' )
331 		return 10;
332 	if ( firstChar == 'n' &&  secondChar == 'o' && thirdChar == 'v' )
333 		return 11;
334 	if ( firstChar == 'd' &&  secondChar == 'e' && thirdChar == 'c' )
335 		return 12;
336 	return 0;
337 }
338 
GatherUnsignedInt(const char * strPtr,size_t count)339 static XMP_Uns32 GatherUnsignedInt ( const char * strPtr, size_t count )
340 {
341 	XMP_Uns32 value = 0;
342 	const char * strEnd = strPtr + count;
343 
344 	while ( strPtr < strEnd ) {
345 		if ( *strPtr == ' ' )	++strPtr;
346 		else break;
347 	}
348 
349 	while ( strPtr < strEnd ) {
350 		char ch = *strPtr;
351 		if ( (ch < '0') || (ch > '9') ) break;
352 		value = value*10 + (ch - '0');
353 		++strPtr;
354 	}
355 
356 	return value;
357 
358 }	// GatherUnsignedInt
359 
importIditChunkToXMP(RIFF_MetaHandler * handler,ValueChunk * iditChunk)360 static void importIditChunkToXMP( RIFF_MetaHandler* handler, ValueChunk* iditChunk )
361 {
362 	// if there iss a IDIT chunk, there is data...
363 	handler->containsXMP = true; // very important for treatment on caller level
364 
365 	// Size has been already checked in calling function
366 	XMP_Enforce( iditChunk->oldSize == IDIT_SIZE + 8 );
367 	const char * valuePtr = iditChunk->oldValue.c_str();
368 	XMP_Enforce( valuePtr[ IDIT_SIZE - 2 ] == 0x0A );
369 	XMP_Enforce( valuePtr[ 13 ] == ':' && valuePtr[ 16 ] == ':' );
370 	XMP_DateTime dateTime;
371 	dateTime.month = GetMonth( valuePtr );
372 	dateTime.day = GatherUnsignedInt( valuePtr + 8, 2 );
373 	dateTime.hour = GatherUnsignedInt( valuePtr + 11, 2 );
374 	dateTime.minute = GatherUnsignedInt( valuePtr + 14, 2 );
375 	dateTime.second = GatherUnsignedInt( valuePtr + 17, 2 );
376 	dateTime.year = GatherUnsignedInt( valuePtr + 20, 4 );
377 	handler->xmpObj.SetProperty_Date( kXMP_NS_EXIF, "DateTimeOriginal", dateTime );
378 
379 } // importIditChunkToXMP
380 
importPrmLToXMP(RIFF_MetaHandler * handler,ValueChunk * prmlChunk)381 static void importPrmLToXMP( RIFF_MetaHandler* handler, ValueChunk* prmlChunk )
382 {
383 	bool haveXMP = false;
384 
385 	XMP_Enforce( prmlChunk->oldSize == PRML_SIZE );
386 	PrmLBoxContent rawPrmL;
387 	XMP_Assert ( sizeof ( rawPrmL ) == PRML_SIZE - 8 ); // double check tight packing.
388 	XMP_Assert ( sizeof ( rawPrmL.filePath ) == 260 );
389 	memcpy ( &rawPrmL, prmlChunk->oldValue.data(), sizeof (rawPrmL) );
390 
391 	if ( rawPrmL.magic != 0xBEEFCAFE ) {
392 		Flip4 ( &rawPrmL.exportType );	// The only numeric field that we care about.
393 	}
394 
395 	rawPrmL.filePath[259] = 0;	// Ensure a terminating nul.
396 	if ( rawPrmL.filePath[0] != 0 ) {
397 		if ( rawPrmL.filePath[0] == '/' ) {
398 			haveXMP = true;
399 			handler->xmpObj.SetStructField ( kXMP_NS_CreatorAtom, "macAtom",
400 								  kXMP_NS_CreatorAtom, "posixProjectPath", rawPrmL.filePath );
401 		} else if ( XMP_LitNMatch ( rawPrmL.filePath, "\\\\?\\", 4 ) ) {
402 			haveXMP = true;
403 			handler->xmpObj.SetStructField ( kXMP_NS_CreatorAtom, "windowsAtom",
404 								  kXMP_NS_CreatorAtom, "uncProjectPath", rawPrmL.filePath );
405 		}
406 	}
407 
408 	const char * exportStr = 0;
409 	switch ( rawPrmL.exportType ) {
410 		case kExportTypeMovie  : exportStr = "movie";  break;
411 		case kExportTypeStill  : exportStr = "still";  break;
412 		case kExportTypeAudio  : exportStr = "audio";  break;
413 		case kExportTypeCustom : exportStr = "custom"; break;
414 	}
415 	if ( exportStr != 0 ) {
416 		haveXMP = true;
417 		handler->xmpObj.SetStructField ( kXMP_NS_DM, "projectRef", kXMP_NS_DM, "type", exportStr );
418 	}
419 
420 	handler->containsXMP |= haveXMP; // mind the '|='
421 } // importCr8rToXMP
422 
importCr8rToXMP(RIFF_MetaHandler * handler,ValueChunk * cr8rChunk)423 static void importCr8rToXMP( RIFF_MetaHandler* handler, ValueChunk* cr8rChunk )
424 {
425 	bool haveXMP = false;
426 
427 	XMP_Enforce( cr8rChunk->oldSize == CR8R_SIZE );
428 	Cr8rBoxContent rawCr8r;
429 	XMP_Assert ( sizeof ( rawCr8r ) == CR8R_SIZE - 8 ); // double check tight packing.
430 	memcpy ( &rawCr8r, cr8rChunk->oldValue.data(), sizeof (rawCr8r) );
431 
432 	if ( rawCr8r.magic != 0xBEEFCAFE ) {
433 		Flip4 ( &rawCr8r.creatorCode );	// The only numeric fields that we care about.
434 		Flip4 ( &rawCr8r.appleEvent );
435 	}
436 
437 	std::string fieldPath;
438 
439 	SXMPUtils::ComposeStructFieldPath ( kXMP_NS_CreatorAtom, "macAtom", kXMP_NS_CreatorAtom, "applicationCode", &fieldPath );
440 	if ( rawCr8r.creatorCode != 0 ) {
441 		haveXMP = true;
442 		handler->xmpObj.SetProperty_Int64 ( kXMP_NS_CreatorAtom, fieldPath.c_str(), (XMP_Int64)rawCr8r.creatorCode );	// ! Unsigned trickery.
443 	}
444 
445 	SXMPUtils::ComposeStructFieldPath ( kXMP_NS_CreatorAtom, "macAtom", kXMP_NS_CreatorAtom, "invocationAppleEvent", &fieldPath );
446 	if ( rawCr8r.appleEvent != 0 ) {
447 		haveXMP = true;
448 		handler->xmpObj.SetProperty_Int64 ( kXMP_NS_CreatorAtom, fieldPath.c_str(), (XMP_Int64)rawCr8r.appleEvent );	// ! Unsigned trickery.
449 	}
450 
451 	rawCr8r.fileExt[15] = 0;	// Ensure a terminating nul.
452 	if ( rawCr8r.fileExt[0] != 0 ) {
453 		haveXMP = true;
454 		handler->xmpObj.SetStructField ( kXMP_NS_CreatorAtom, "windowsAtom", kXMP_NS_CreatorAtom, "extension", rawCr8r.fileExt );
455 	}
456 
457 	rawCr8r.appOptions[15] = 0;	// Ensure a terminating nul.
458 	if ( rawCr8r.appOptions[0] != 0 ) {
459 		haveXMP = true;
460 		handler->xmpObj.SetStructField ( kXMP_NS_CreatorAtom, "windowsAtom", kXMP_NS_CreatorAtom, "invocationFlags", rawCr8r.appOptions );
461 	}
462 
463 	rawCr8r.appName[31] = 0;	// Ensure a terminating nul.
464 	if ( rawCr8r.appName[0] != 0 ) {
465 		haveXMP = true;
466 		handler->xmpObj.SetProperty ( kXMP_NS_XMP, "CreatorTool", rawCr8r.appName );
467 	}
468 
469 	handler->containsXMP |= haveXMP; // mind the '|='
470 } // importCr8rToXMP
471 
472 
importListChunkToXMP(RIFF_MetaHandler * handler,ContainerChunk * listChunk,Mapping mapping[],bool xmpHasPriority)473 static void importListChunkToXMP( RIFF_MetaHandler* handler, ContainerChunk* listChunk, Mapping mapping[], bool xmpHasPriority )
474 {
475 	valueMap* cm = &listChunk->childmap;
476 	for (int p=0; mapping[p].chunkID != 0; p++) // go through legacy chunks
477 	{
478 		valueMapIter result = cm->find(mapping[p].chunkID);
479 		if( result != cm->end() ) // if value found
480 		{
481 			ValueChunk* propChunk = result->second;
482 
483 			bool propertyExists = false;
484 			std::string utf8 = nativePropertyToUTF8(
485 				propChunk->oldValue.c_str(),
486 				(XMP_StringLen)propChunk->oldValue.size(), &propertyExists );
487 
488 			if ( utf8.size() > 0 ) // if property is not-empty, set Property
489 			{
490 				switch ( mapping[p].propType )
491 				{
492 					case prop_TIMEVALUE:
493 						if ( xmpHasPriority &&
494 							 handler->xmpObj.DoesStructFieldExist( mapping[p].ns, mapping[p].prop, kXMP_NS_DM, "timeValue" ))
495 							 break; // skip if XMP has precedence and exists
496 						handler->xmpObj.SetStructField( mapping[p].ns, mapping[p].prop,
497 										kXMP_NS_DM, "timeValue", utf8.c_str() );
498 						break;
499 					case prop_LOCALIZED_TEXT:
500 						if ( xmpHasPriority && handler->xmpObj.GetLocalizedText( mapping[p].ns ,
501 							mapping[p].prop, "" , "x-default", 0, 0, 0 ))
502 							break; // skip if XMP has precedence and exists
503 						handler->xmpObj.SetLocalizedText( mapping[p].ns , mapping[p].prop,
504 											"" , "x-default" , utf8.c_str() );
505 						if ( mapping[p].chunkID == kPropChunkINAM )
506 							handler->hasListInfoINAM = true; // needs to be known for special 3-way merge around dc:title
507 						break;
508 					case prop_ARRAYITEM:
509 						if ( xmpHasPriority &&
510 							 handler->xmpObj.DoesArrayItemExist( mapping[p].ns, mapping[p].prop, 1 ))
511 							break; // skip if XMP has precedence and exists
512 						handler->xmpObj.DeleteProperty( mapping[p].ns, mapping[p].prop );
513 						handler->xmpObj.AppendArrayItem( mapping[p].ns, mapping[p].prop, kXMP_PropValueIsArray, utf8.c_str(), kXMP_NoOptions );
514 						break;
515 					case prop_SIMPLE:
516 						if ( xmpHasPriority &&
517 							handler->xmpObj.DoesPropertyExist( mapping[p].ns, mapping[p].prop ))
518 							break; // skip if XMP has precedence and exists
519 						handler->xmpObj.SetProperty( mapping[p].ns, mapping[p].prop, utf8.c_str() );
520 						break;
521 					default:
522 						XMP_Throw( "internal error" , kXMPErr_InternalFailure );
523 				}
524 
525 				handler->containsXMP = true; // very important for treatment on caller level
526 			}
527 			else if ( ! propertyExists) // otherwise remove it.
528 			{ // [2389942] don't, if legacy value is existing but non-retrievable (due to server mode)
529 				switch ( mapping[p].propType )
530 				{
531 					case prop_TIMEVALUE:
532 						if ( (!xmpHasPriority) && // forward deletion only if XMP has no priority
533 							 handler->xmpObj.DoesPropertyExist( mapping[p].ns, mapping[p].prop ))
534 							handler->xmpObj.DeleteProperty( mapping[p].ns, mapping[p].prop );
535 						break;
536 					case prop_LOCALIZED_TEXT:
537 						if ( (!xmpHasPriority) && // forward deletion only if XMP has no priority
538 							 handler->xmpObj.DoesPropertyExist( mapping[p].ns, mapping[p].prop ))
539 							handler->xmpObj.DeleteLocalizedText( mapping[p].ns, mapping[p].prop, "", "x-default" );
540 						break;
541 					case prop_ARRAYITEM:
542 					case prop_SIMPLE:
543 						if ( (!xmpHasPriority) && // forward deletion only if XMP has no priority
544 							 handler->xmpObj.DoesPropertyExist( mapping[p].ns, mapping[p].prop ))
545 							handler->xmpObj.DeleteProperty( mapping[p].ns, mapping[p].prop );
546 						break;
547 					default:
548 						XMP_Throw( "internal error" , kXMPErr_InternalFailure );
549 				}
550 			}
551 		}
552 	} // for
553 }
importProperties(RIFF_MetaHandler * handler)554 void importProperties( RIFF_MetaHandler* handler )
555 {
556 	bool hasDigest = handler->xmpObj.GetProperty( kXMP_NS_WAV, "NativeDigest", NULL , NULL );
557 	if ( hasDigest )
558 	{
559 		// remove! since it now becomse a 'new' handler file
560 		handler->xmpObj.DeleteProperty( kXMP_NS_WAV, "NativeDigest" );
561 	}
562 
563 	// BWF Bext extension chunk -----------------------------------------------
564 	if (   handler->parent->format == kXMP_WAVFile && // applies only to WAV
565 		   handler->bextChunk != 0 ) //skip if no BEXT chunk found.
566 	{
567 		importBextChunkToXMP( handler, handler->bextChunk );
568 	}
569 
570 	// PrmL chunk --------------------------------------------------------------
571 	if ( handler->prmlChunk != 0 && handler->prmlChunk->oldSize == PRML_SIZE )
572 	{
573 		importPrmLToXMP( handler, handler->prmlChunk );
574 	}
575 
576 	// Cr8r chunk --------------------------------------------------------------
577 	if ( handler->cr8rChunk != 0 && handler->cr8rChunk->oldSize == CR8R_SIZE )
578 	{
579 		importCr8rToXMP( handler, handler->cr8rChunk );
580 	}
581 
582 	// LIST:INFO --------------------------------------------------------------
583 	if ( handler->listInfoChunk != 0) //skip if no LIST:INFO chunk found.
584 		importListChunkToXMP( handler, handler->listInfoChunk, listInfoProps, hasDigest );
585 
586 	// LIST:Tdat --------------------------------------------------------------
587 	if ( handler->listTdatChunk != 0)
588 		importListChunkToXMP( handler, handler->listTdatChunk, listTdatProps, hasDigest );
589 
590 	// DISP (do last, higher priority than INAM ) -----------------------------
591 	bool takeXMP = false; // assume for now
592 	if ( hasDigest )
593 	{
594 		std::string actualLang, value;
595 		bool r = handler->xmpObj.GetLocalizedText( kXMP_NS_DC, "title", "" , "x-default" , &actualLang, &value, NULL );
596 		if ( r && (actualLang == "x-default") ) takeXMP = true;
597 	}
598 
599 	if ( (!takeXMP) && handler->dispChunk != 0) //skip if no LIST:INFO chunk found.
600 	{
601 		std::string* value = &handler->dispChunk->oldValue;
602 		if ( value->size() >= 4 ) // ignore contents if file too small
603 		{
604 			XMP_StringPtr cstring = value->c_str();
605 			XMP_StringLen size = (XMP_StringLen) value->size();
606 
607 			size -= 4; // skip first four bytes known to contain constant
608 			cstring += 4;
609 
610 			bool propertyExists = false;
611 			std::string utf8 = nativePropertyToUTF8( cstring, size, &propertyExists );
612 
613 			if ( utf8.size() > 0 )
614 			{
615 				handler->xmpObj.SetLocalizedText( kXMP_NS_DC, "title", "" , "x-default" , utf8.c_str() );
616 				handler->containsXMP = true; // very important for treatment on caller level
617 			}
618 			else
619 			{
620 				// found as part of [2389942]
621 				// forward deletion may only happen if no LIST:INFO/INAM is present:
622 				if ( ! handler->hasListInfoINAM &&
623 					 ! propertyExists )  // ..[2389942]part2: and if truly no legacy property
624 				{	                    //    (not just an unreadable one due to ServerMode).
625 					handler->xmpObj.DeleteProperty( kXMP_NS_DC, "title" );
626 				}
627 			}
628 		} // if size sufficient
629 	} // handler->dispChunk
630 
631 	// IDIT chunk --------------------------------------------------------------
632 	if ( handler->parent->format == kXMP_AVIFile &&	// Only for AVI file
633 		 handler->iditChunk != 0 && handler->iditChunk->oldSize == IDIT_SIZE + 8 )	// Including header size i.e, ID + size
634 	{
635 		importIditChunkToXMP( handler, handler->iditChunk );
636 	}
637 
638 } // importProperties
639 
640 ////////////////////////////////////////////////////////////////////////////////
641 // EXPORT
642 ////////////////////////////////////////////////////////////////////////////////
643 
relocateWronglyPlacedXMPChunk(RIFF_MetaHandler * handler)644 void relocateWronglyPlacedXMPChunk( RIFF_MetaHandler* handler )
645 {
646 	/*XMP_IO* file = handler->parent->ioRef;*/
647 	RIFF::containerVect *rc = &handler->riffChunks;
648 	RIFF::ContainerChunk* lastChunk = rc->at( rc->size()-1 );
649 
650 	// 1) XMPPacket
651 	// needChunk exists but is not in lastChunk ?
652 	if (
653 		 handler->xmpChunk != 0 &&		// XMP Chunk existing?
654 		 (XMP_Uns32)rc->size() > 1 &&	// more than 1 top-level chunk (otherwise pointless)
655          lastChunk->getChild( handler->xmpChunk ) == lastChunk->children.end() // not already in last chunk?
656 	    )
657 	{
658 		RIFF::ContainerChunk* cur;
659 		chunkVectIter child;
660 		XMP_Int32 chunkNo;
661 
662 		// find and relocate to last chunk:
663 		for ( chunkNo = (XMP_Int32)rc->size()-2 ; chunkNo >= 0; chunkNo-- ) // ==> start with second-last chunk
664 		{
665 			cur = rc->at(chunkNo);
666 			child = cur->getChild( handler->xmpChunk );
667 			if ( child != cur->children.end() ) // found?
668 				break;
669 		} // for
670 
671 		if ( chunkNo < 0 ) // already in place? nothing left to do.
672 			return;
673 
674 		lastChunk->children.push_back( *child ); // nb: order matters!
675 		cur->replaceChildWithJunk( *child, false );
676 		cur->hasChange = true; // [2414649] initialize early-on i.e: here
677 	} // if
678 } // relocateWronglyPlacedXMPChunk
679 
680 // writes to buffer up to max size,
681 // 0 termination only if shorter than maxSize
682 // converts down to ascii
setBextField(std::string * value,XMP_Uns8 * data,XMP_Uns32 offset,XMP_Uns32 maxSize)683 static void setBextField ( std::string* value, XMP_Uns8* data, XMP_Uns32 offset, XMP_Uns32 maxSize )
684 {
685 	XMP_Validate( value != 0, "setBextField: null value string pointer", kXMPErr_BadParam );
686 	XMP_Validate( data != 0, "setBextField: null data value", kXMPErr_BadParam );
687 	XMP_Validate( maxSize > 0, "setBextField: maxSize must be greater than 0", kXMPErr_BadParam );
688 
689 	std::string ascii;
690 	XMP_StringLen actualSize = convertToASCII( value->data(), (XMP_StringLen) value->size() , &ascii , maxSize );
691 	strncpy( (char*)(data + offset), ascii.data(), actualSize );
692 }
693 
694 // add bwf-bext related data to bext chunk, create if not existing yet.
695 // * in fact, since bext is fully fixed and known, there can be no unknown subchunks worth keeping:
696 //    * prepare bext chunk in buffer
697 //    * value changed/created if needed only, otherways remove chunk
698 // * remove bext-mapped properties from xmp (non-redundant storage)
699 // note: ValueChunk**: adress of pointer to allow changing the pointer itself (i.e. chunk creation)
exportXMPtoBextChunk(RIFF_MetaHandler * handler,ValueChunk ** bextChunk)700 static void exportXMPtoBextChunk( RIFF_MetaHandler* handler, ValueChunk** bextChunk )
701 {
702 	// register bext namespace ( if there was no import, this is news, otherwise harmless moot)
703 	SXMPMeta::RegisterNamespace( kXMP_NS_BWF, "bext:", 0 );
704 
705 	bool chunkUsed = false; // assume for now
706 	SXMPMeta* xmp = &handler->xmpObj;
707 
708 	// prepare buffer, need to know CodingHistory size as the only variable
709 	XMP_Int32 bextBufferSize = MIN_BEXT_SIZE - 8; // -8 because of header
710 	std::string value;
711 	if ( xmp->GetProperty( bextCodingHistory.ns, bextCodingHistory.prop, &value, (XMP_OptionBits *) kXMP_NoOptions ))
712 	{
713 		bextBufferSize += ((XMP_StringLen)value.size()) + 1 ; // add to size (and a trailing zero)
714 	}
715 
716 	// create and clear buffer
717 	XMP_Uns8* buffer = new XMP_Uns8[bextBufferSize];
718 	for (XMP_Int32 i = 0; i < bextBufferSize; i++ )
719 		buffer[i] = 0;
720 
721 	// grab props, write into buffer, remove from XMP ///////////////////////////
722 	// bextDescription ------------------------------------------------
723 	if ( xmp->GetProperty( bextDescription.ns, bextDescription.prop, &value, (XMP_OptionBits *) kXMP_NoOptions ) )
724 	{
725 		setBextField( &value, (XMP_Uns8*) buffer, 0, 256 );
726 		xmp->DeleteProperty( bextDescription.ns, bextDescription.prop)					;
727 		chunkUsed = true;
728 	}
729 	// bextOriginator -------------------------------------------------
730 	if ( xmp->GetProperty( bextOriginator.ns , bextOriginator.prop, &value, (XMP_OptionBits *) kXMP_NoOptions ) )
731 	{
732 		setBextField( &value, (XMP_Uns8*) buffer, 256, 32 );
733 		xmp->DeleteProperty( bextOriginator.ns , bextOriginator.prop );
734 		chunkUsed = true;
735 	}
736 	// bextOriginatorRef ----------------------------------------------
737 	if ( xmp->GetProperty( bextOriginatorRef.ns , bextOriginatorRef.prop, &value, (XMP_OptionBits *) kXMP_NoOptions ) )
738 	{
739 		setBextField( &value, (XMP_Uns8*) buffer, 256+32, 32 );
740 		xmp->DeleteProperty( bextOriginatorRef.ns , bextOriginatorRef.prop );
741 		chunkUsed = true;
742 	}
743 	// bextOriginationDate --------------------------------------------
744 	if ( xmp->GetProperty( bextOriginationDate.ns , bextOriginationDate.prop, &value, (XMP_OptionBits *) kXMP_NoOptions ) )
745 	{
746 		setBextField( &value, (XMP_Uns8*) buffer, 256+32+32, 10 );
747 		xmp->DeleteProperty( bextOriginationDate.ns , bextOriginationDate.prop );
748 		chunkUsed = true;
749 	}
750 	// bextOriginationTime --------------------------------------------
751 	if ( xmp->GetProperty( bextOriginationTime.ns , bextOriginationTime.prop, &value, (XMP_OptionBits *) kXMP_NoOptions ) )
752 	{
753 		setBextField( &value, (XMP_Uns8*) buffer, 256+32+32+10, 8 );
754 		xmp->DeleteProperty( bextOriginationTime.ns , bextOriginationTime.prop );
755 		chunkUsed = true;
756 	}
757 	// bextTimeReference ----------------------------------------------
758 	// thanx to friendly byte order, all 8 bytes can be written in one go:
759 	if ( xmp->GetProperty( bextTimeReference.ns, bextTimeReference.prop, &value, (XMP_OptionBits *) kXMP_NoOptions ) )
760 	{
761 		try
762 		{
763 			XMP_Int64 v = SXMPUtils::ConvertToInt64( value.c_str() );
764 			PutUns64LE( v, &(buffer[256+32+32+10+8] ));
765 			chunkUsed = true;
766 		}
767 		catch (XMP_Error& e)
768 		{
769 			if ( e.GetID() != kXMPErr_BadParam )
770 				throw e;         // re-throw on any other error
771 		}  // 'else' tolerate ( time reference remains 0x00000000 )
772 		// valid or not, do not store redundantly:
773 		xmp->DeleteProperty( bextTimeReference.ns, bextTimeReference.prop );
774 	}
775 
776 	// bextVersion ----------------------------------------------------
777 	// set version=1, no matter what.
778 	PutUns16LE( 1, &(buffer[256+32+32+10+8+8]) );
779 	xmp->DeleteProperty( bextVersion.ns, bextVersion.prop );
780 
781 	// bextUMID -------------------------------------------------------
782 	if ( xmp->GetProperty( bextUMID.ns, bextUMID.prop, &value, (XMP_OptionBits *) kXMP_NoOptions ) )
783 	{
784 		std::string rawStr;
785 
786 		if ( !DecodeFromHexString( value.data(), (XMP_StringLen) value.size(), &rawStr ) )
787 		{
788 			delete [] buffer; // important.
789 			XMP_Throw ( "EncodeFromHexString: illegal umid string. Must contain an even number of 0-9 and uppercase A-F chars.", kXMPErr_BadParam );
790 		}
791 
792 		// if UMID is smaller/longer than 64 byte for any reason,
793 		// truncate/do a partial write (just like for any other bext property)
794 
795 		memcpy( (char*) &(buffer[256+32+32+10+8+8+2]), rawStr.data(), MIN( 64, rawStr.size() ) );
796 		xmp->DeleteProperty( bextUMID.ns, bextUMID.prop );
797 		chunkUsed = true;
798 	}
799 
800 	// bextCodingHistory ----------------------------------------------
801 	if ( xmp->GetProperty( bextCodingHistory.ns, bextCodingHistory.prop, &value, (XMP_OptionBits *) kXMP_NoOptions ) )
802 	{
803 		std::string ascii;
804 		convertToASCII( value.data(), (XMP_StringLen) value.size() , &ascii, (XMP_StringLen) value.size() );
805 		strncpy( (char*) &(buffer[MIN_BEXT_SIZE-8]), ascii.data(), ascii.size() );
806 		xmp->DeleteProperty( bextCodingHistory.ns, bextCodingHistory.prop );
807 		chunkUsed = true;
808 	}
809 
810 	// always delete old, recreate if needed
811 	if ( *bextChunk != 0 )
812 	{
813 		(*bextChunk)->parent->replaceChildWithJunk( *bextChunk );
814 		(*bextChunk) = 0; // clear direct Chunk pointer
815 	}
816 
817 	if ( chunkUsed)
818 		*bextChunk = new ValueChunk( handler->riffChunks.at(0), std::string( (char*)buffer, bextBufferSize ), kChunk_bext );
819 
820 	delete [] buffer; // important.
821 }
822 
SetBufferedString(char * dest,const std::string source,size_t limit)823 static inline void SetBufferedString ( char * dest, const std::string source, size_t limit )
824 {
825 	memset ( dest, 0, limit );
826 	size_t count = source.size();
827 	if ( count >= limit ) count = limit - 1;	// Ensure a terminating nul.
828 	memcpy ( dest, source.c_str(), count );
829 }
830 
exportXMPtoCr8rChunk(RIFF_MetaHandler * handler,ValueChunk ** cr8rChunk)831 static void exportXMPtoCr8rChunk ( RIFF_MetaHandler* handler, ValueChunk** cr8rChunk )
832 {
833 	const SXMPMeta & xmp = handler->xmpObj;
834 
835 	// Make sure an existing Cr8r chunk has the proper fixed length.
836 	bool haveOldCr8r = (*cr8rChunk != 0);
837 	if ( haveOldCr8r && ((*cr8rChunk)->oldSize != sizeof(Cr8rBoxContent)+8) ) {
838 		(*cr8rChunk)->parent->replaceChildWithJunk ( *cr8rChunk );	// Wrong length, the existing chunk must be bad.
839 		(*cr8rChunk) = 0;
840 		haveOldCr8r = false;
841 	}
842 
843 	bool haveNewCr8r = false;
844 	std::string creatorCode, appleEvent, fileExt, appOptions, appName;
845 
846 	haveNewCr8r |= xmp.GetStructField ( kXMP_NS_CreatorAtom, "macAtom", kXMP_NS_CreatorAtom, "applicationCode", &creatorCode, 0 );
847 	haveNewCr8r |= xmp.GetStructField ( kXMP_NS_CreatorAtom, "macAtom", kXMP_NS_CreatorAtom, "invocationAppleEvent", &appleEvent, 0 );
848 	haveNewCr8r |= xmp.GetStructField ( kXMP_NS_CreatorAtom, "windowsAtom", kXMP_NS_CreatorAtom, "extension", &fileExt, 0 );
849 	haveNewCr8r |= xmp.GetStructField ( kXMP_NS_CreatorAtom, "windowsAtom", kXMP_NS_CreatorAtom, "invocationFlags", &appOptions, 0 );
850 	haveNewCr8r |= xmp.GetProperty ( kXMP_NS_XMP, "CreatorTool", &appName, 0 );
851 
852 	if ( ! haveNewCr8r ) {	// Get rid of an existing Cr8r chunk if there is no new XMP.
853 		if ( haveOldCr8r ) {
854 			(*cr8rChunk)->parent->replaceChildWithJunk ( *cr8rChunk );
855 			*cr8rChunk = 0;
856 		}
857 		return;
858 	}
859 
860 	if ( ! haveOldCr8r ) {
861 		*cr8rChunk = new ValueChunk ( handler->lastChunk, std::string(), kChunk_Cr8r );
862 	}
863 
864 	std::string strValue;
865 	strValue.assign ( (sizeof(Cr8rBoxContent) - 1), '\0' );	// ! Use size-1 because SetValue appends a trailing 0 byte.
866 	(*cr8rChunk)->SetValue ( strValue );	// ! Just get the space available.
867 	XMP_Assert ( (*cr8rChunk)->newValue.size() == sizeof(Cr8rBoxContent) );
868 	(*cr8rChunk)->hasChange = true;
869 
870 	Cr8rBoxContent * newCr8r = (Cr8rBoxContent*) (*cr8rChunk)->newValue.data();
871 
872 	if ( ! haveOldCr8r ) {
873 
874 		newCr8r->magic = MakeUns32LE ( 0xBEEFCAFE );
875 		newCr8r->size = MakeUns32LE ( sizeof(Cr8rBoxContent) );
876 		newCr8r->majorVer = MakeUns16LE ( 1 );
877 
878 	} else {
879 
880 		const Cr8rBoxContent * oldCr8r = (Cr8rBoxContent*) (*cr8rChunk)->oldValue.data();
881 		memcpy ( newCr8r, oldCr8r, sizeof(Cr8rBoxContent) );
882 		if ( GetUns32LE ( &newCr8r->magic ) != 0xBEEFCAFE ) {	// Make sure we write LE numbers.
883 			Flip4 ( &newCr8r->magic );
884 			Flip4 ( &newCr8r->size );
885 			Flip2 ( &newCr8r->majorVer );
886 			Flip2 ( &newCr8r->minorVer );
887 			Flip4 ( &newCr8r->creatorCode );
888 			Flip4 ( &newCr8r->appleEvent );
889 		}
890 
891 	}
892 
893 	if ( ! creatorCode.empty() ) {
894 		newCr8r->creatorCode = MakeUns32LE ( (XMP_Uns32) strtoul ( creatorCode.c_str(), 0, 0 ) );
895 	}
896 
897 	if ( ! appleEvent.empty() ) {
898 		newCr8r->appleEvent = MakeUns32LE ( (XMP_Uns32) strtoul ( appleEvent.c_str(), 0, 0 ) );
899 	}
900 
901 	if ( ! fileExt.empty() ) SetBufferedString ( newCr8r->fileExt, fileExt, sizeof ( newCr8r->fileExt ) );
902 	if ( ! appOptions.empty() ) SetBufferedString ( newCr8r->appOptions, appOptions, sizeof ( newCr8r->appOptions ) );
903 	if ( ! appName.empty() ) SetBufferedString ( newCr8r->appName, appName, sizeof ( newCr8r->appName ) );
904 
905 }
906 
907 // Returns numbers of leap years between 1800 and provided year value. Both end values will be inclusive for getting this field.
908 // For leap year this will be handled later in GetIDITString() function
GetLeapYearsNumber(const XMP_Uns32 & year)909 static XMP_Uns32 GetLeapYearsNumber( const XMP_Uns32 & year )
910 {
911 
912 	XMP_Uns32 numLeapYears = ( year / 4 );
913 	numLeapYears -= ( year / 100 );
914 	numLeapYears += ( ( year + 200 ) / 400 );	// 200 is added becuase our base is 1800 which give modulas 200 by divinding with 400
915 	return numLeapYears;
916 
917 }	//GetLeapYearsNumber
918 
GetDays(const XMP_Int32 & month,const bool & isLeapYear)919 static XMP_Uns32 GetDays( const XMP_Int32 & month, const bool &isLeapYear )
920 {
921 	// Adding number of days as per last month of the provided year
922 	// Leap year case is handled later
923 	XMP_Uns32 numDays = 0;
924 	switch ( month )
925 	{
926 	case 2:
927 		numDays = 31;
928 		break;
929 	case 3:
930 		numDays = 31 + 28;
931 		break;
932 	case 4:
933 		numDays = 31 + 28 + 31;
934 		break;
935 	case 5:
936 		numDays = 31 + 28 + 31 + 30;
937 		break;
938 	case 6:
939 		numDays = 31 + 28 + 31 + 30 + 31;
940 		break;
941 	case 7:
942 		numDays = 31 + 28 + 31 + 30 + 31 + 30;
943 		break;
944 	case 8:
945 		numDays = 31 + 28 + 31 + 30 + 31 + 30 + 31;
946 		break;
947 	case 9:
948 		numDays = 31 + 28 + 31 + 30 + 31 + 30 + 31 + 31;
949 		break;
950 	case 10:
951 		numDays = 31 + 28 + 31 + 30 + 31 + 30 + 31 + 31 + 30;
952 		break;
953 	case 11:
954 		numDays = 31 + 28 + 31 + 30 + 31 + 30 + 31 + 31 + 30 + 31;
955 		break;
956 	case 12:
957 		numDays = 31 + 28 + 31 + 30 + 31 + 30 + 31 + 31 + 30 + 31 + 30;
958 		break;
959 	default:
960 		break;
961 	}
962 
963 	// Adding one day for leap year and month above than feb
964 	if ( isLeapYear == true && month > 2 )
965 		numDays += 1;
966 
967 	return numDays;
968 
969 } // GetDays
970 
GetIDITString(const XMP_DateTime & targetDate)971 static const std::string GetIDITString( const XMP_DateTime & targetDate )
972 {
973 	// Date 1 Jan 1800 is treating as base date for handling this issue
974 	// 1800 is chosen becuase of consistency in calender after 1752
975 
976 	XMP_Uns64 numOfDays = 0;	// Specifies number of days after 1 jan 1800
977 	XMP_Uns32 year = targetDate.year - 1800;
978 
979 	// 2000 was the first exception when year dividing by 100 was still a leap year
980 	bool isLeapYear = ( year % 4 != 0 ) ? false : ( year % 100 != 0 ) ? true : ( ( year % 400 != 200 ) ? false : true );
981 
982 	// Adding days according to normal year and adjusting days for leap years
983 	numOfDays = 365 * year;
984 	numOfDays += GetLeapYearsNumber( year );
985 
986 	// Adding days according to the month
987 	numOfDays += GetDays( targetDate.month, isLeapYear );
988 
989 	// GetLeapYearsNumber() function is also considering provided year for calculating number of leap numbers between provided year
990 	// and 1800. This consideration is done by inclusive both end values. So both GetLeapYearsNumber() and GetDays() would have added
991 	// extra day for higher end year for leap year.
992 	// So, we need to decrease one day from number of days field
993 	if ( isLeapYear )
994 		--numOfDays;
995 
996 	// Adding days according to provided month
997 	numOfDays += targetDate.day;
998 
999 	// Weekday starting from Wednesday i.e., Wed will be the first day of the week.
1000 	// This day was choosen because 1 Jan 1800 was Wednesday
1001 	XMP_Uns8 weekDayNum = numOfDays % 7;
1002 	std::string weekDay;
1003 	switch ( weekDayNum )
1004 	{
1005 	case 0:
1006 		weekDay = "Tue";
1007 		break;
1008 	case 1:
1009 		weekDay = "Wed";
1010 		break;
1011 	case 2:
1012 		weekDay = "Thu";
1013 		break;
1014 	case 3:
1015 		weekDay = "Fri";
1016 		break;
1017 	case 4:
1018 		weekDay = "Sat";
1019 		break;
1020 	case 5:
1021 		weekDay = "Sun";
1022 		break;
1023 	case 6:
1024 		weekDay = "Mon";
1025 		break;
1026 	default:
1027 		break;
1028 	}
1029 
1030 	// Stream to convert into IDIT format
1031 	std::stringstream iditStream;
1032 	iditStream << weekDay;
1033 	iditStream.put( ' ' );
1034 
1035 	// IDIT needs 3 character codes for month
1036 	const char * monthArray[] = { "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" };
1037 	iditStream << monthArray[ targetDate.month - 1 ];
1038 	iditStream.put( ' ' );
1039 
1040 	if ( targetDate.day < 10 )
1041 		iditStream.put( '0' );
1042 	iditStream << targetDate.day;
1043 	iditStream.put( ' ' );
1044 
1045 	if ( targetDate.hour < 10 )
1046 		iditStream.put( '0' );
1047 	iditStream << targetDate.hour;
1048 	iditStream.put( ':' );
1049 
1050 	if ( targetDate.minute < 10 )
1051 		iditStream.put( '0' );
1052 	iditStream << targetDate.minute;
1053 	iditStream.put( ':' );
1054 
1055 	if ( targetDate.second < 10 )
1056 		iditStream.put( '0' );
1057 	iditStream << targetDate.second;
1058 	iditStream.put( ' ' );
1059 
1060 	// No need to handle casese for year 1 to 999
1061 	/*
1062 	if ( targetDate.year < 10 )
1063 		iditStream << "   ";
1064 	else if ( targetDate.year < 100 )
1065 		iditStream << "  ";
1066 	else if ( targetDate.year < 1000 )
1067 		iditStream << " ";
1068 	*/
1069 	// Year will be in the range of 1800-3999
1070 	iditStream << targetDate.year;
1071 
1072 	// Adding new line charcter for IDIT
1073 	iditStream.put( '\n' );
1074 
1075 	return iditStream.str();
1076 
1077 }	// GetIDITString
1078 
exportXMPtoIDITChunk(RIFF_MetaHandler * handler)1079 static void exportXMPtoIDITChunk( RIFF_MetaHandler* handler )
1080 {
1081 	// exif:DateTimeOriginal -> IDIT chunk
1082 	ContainerChunk * hdlrChunk = handler->listHdlrChunk;
1083 	if ( hdlrChunk == 0 )
1084 		XMP_Throw( "Header of AVI file (hdlr chunk) must exists", kXMPErr_BadFileFormat );
1085 
1086 	XMP_DateTime dateTime;
1087 	bool propExists = handler->xmpObj.GetProperty_Date( kXMP_NS_EXIF, "DateTimeOriginal", &dateTime, 0 );
1088 	if ( !propExists )
1089 	{
1090 		if ( handler->iditChunk != 0 )
1091 		{
1092 			// Exception would have thrown if we don't find hdlr chunk for AVI file
1093 			XMP_Assert( hdlrChunk != 0 );
1094 			bool isSuccess = hdlrChunk->removeValue( kChunk_IDIT );
1095 			if ( !isSuccess )
1096 				XMP_Throw( "Removal of IDIT block fails", kXMPErr_InternalFailure );
1097 			handler->iditChunk = 0;
1098 			hdlrChunk->hasChange = true;
1099 		}
1100 		// Else no need to do anything
1101 	}
1102 	else
1103 	{
1104 		if ( dateTime.year < 1800 || dateTime.year > 3999 )
1105 			XMP_Throw( "For IDIT block, XMP currently supports years in between 1800 and 3999 (Both inclusive).", kXMPErr_InternalFailure );
1106 
1107 		/*
1108 		Conversion need to be done from XMP date time to IDIT structure.
1109 		XMP_DateTime accepts any value but IDIT needs to have weekday, month-day, month, year and time.
1110 		*/
1111 
1112 		// Silently modifying dateTime for invalid dates.
1113 		if ( dateTime.month < 1 )
1114 			dateTime.month = 1;
1115 		if ( dateTime.month > 12 )
1116 			dateTime.month = 12;
1117 		if ( dateTime.day < 1 )
1118 			dateTime.day = 1;
1119 		if ( dateTime.day > 31 )
1120 			dateTime.day = 31;
1121 
1122 		const std::string iditString = GetIDITString( dateTime );
1123 
1124 		// If no IDIT exits then create one
1125 		if ( handler->iditChunk == 0 )
1126 			handler->iditChunk = new ValueChunk( hdlrChunk, std::string(), kChunk_IDIT );
1127 		else if ( strncmp( iditString.c_str(), handler->iditChunk->oldValue.c_str(), IDIT_SIZE ) == 0 )		// Equal
1128 			return;
1129 
1130 		// Setting the IDIT value
1131 		handler->iditChunk->hasChange = true;
1132 		handler->iditChunk->SetValue( iditString, true );
1133 
1134 	}
1135 
1136 }	// exportXMPtoIDITChunk
1137 
exportXMPtoListChunk(XMP_Uns32 id,XMP_Uns32 containerType,RIFF_MetaHandler * handler,ContainerChunk ** listChunk,Mapping mapping[])1138 static void exportXMPtoListChunk( XMP_Uns32 id, XMP_Uns32 containerType,
1139 						   RIFF_MetaHandler* handler, ContainerChunk** listChunk, Mapping mapping[])
1140 {
1141 	// note: ContainerChunk**: adress of pointer to allow changing the pointer itself (i.e. chunk creation)
1142 	SXMPMeta* xmp = &handler->xmpObj;
1143 	bool listChunkIsNeeded = false; // assume for now
1144 
1145 	// ! The NUL is optional in WAV to avoid a parsing bug in Audition 3 - can't handle implicit pad byte.
1146 	bool optionalNUL = (handler->parent->format == kXMP_WAVFile);
1147 
1148 	for ( int p=0; mapping[p].chunkID != 0; ++p ) {	// go through all potential property mappings
1149 
1150 		bool propExists = false;
1151 		std::string value, actualLang;
1152 
1153 		switch ( mapping[p].propType ) {
1154 
1155 			// get property. if existing, remove from XMP (to avoid redundant storage)
1156 			case prop_TIMEVALUE:
1157 				propExists = xmp->GetStructField ( mapping[p].ns, mapping[p].prop, kXMP_NS_DM, "timeValue", &value, 0 );
1158 				break;
1159 
1160 			case prop_LOCALIZED_TEXT:
1161 				propExists = xmp->GetLocalizedText ( mapping[p].ns, mapping[p].prop, "", "x-default", &actualLang, &value, 0);
1162 				if ( actualLang != "x-default" ) propExists = false; // no "x-default" => nothing to reconcile !
1163 				break;
1164 
1165 			case prop_ARRAYITEM:
1166 				propExists = xmp->GetArrayItem ( mapping[p].ns, mapping[p].prop, 1, &value, 0 );
1167 				break;
1168 
1169 			case prop_SIMPLE:
1170 				propExists = xmp->GetProperty ( mapping[p].ns, mapping[p].prop, &value, 0 );
1171 				break;
1172 
1173 			default:
1174 				XMP_Throw ( "internal error", kXMPErr_InternalFailure );
1175 
1176 		}
1177 
1178 		if ( ! propExists ) {
1179 
1180 			if ( *listChunk != 0 ) (*listChunk)->removeValue ( mapping[p].chunkID );
1181 
1182 		} else {
1183 
1184 			listChunkIsNeeded = true;
1185 			if ( *listChunk == 0 ) *listChunk = new ContainerChunk ( handler->riffChunks[0], id, containerType );
1186 
1187 			valueMap* cm = &(*listChunk)->childmap;
1188 			valueMapIter result = cm->find( mapping[p].chunkID );
1189 			ValueChunk* propChunk = 0;
1190 
1191 			if ( result != cm->end() ) {
1192 				propChunk = result->second;
1193 			} else {
1194 				propChunk = new ValueChunk ( *listChunk, std::string(), mapping[p].chunkID );
1195 			}
1196 
1197 			propChunk->SetValue ( value.c_str(), optionalNUL );
1198 
1199 		}
1200 
1201 	} // for each property
1202 
1203 	if ( (! listChunkIsNeeded) && (*listChunk != 0) && ((*listChunk)->children.size() == 0) ) {
1204 		(*listChunk)->parent->replaceChildWithJunk ( *listChunk );
1205 		(*listChunk) = 0; // reset direct Chunk pointer
1206 	}
1207 
1208 }
1209 
exportAndRemoveProperties(RIFF_MetaHandler * handler)1210 void exportAndRemoveProperties ( RIFF_MetaHandler* handler )
1211 {
1212 	SXMPMeta xmpObj = handler->xmpObj;
1213 
1214 	exportXMPtoCr8rChunk ( handler, &handler->cr8rChunk );
1215 
1216 	// 1/5 BWF Bext extension chunk -----------------------------------------------
1217 	if ( handler->parent->format == kXMP_WAVFile ) {	// applies only to WAV
1218 		exportXMPtoBextChunk ( handler, &handler->bextChunk );
1219 	}
1220 
1221 	// 2/5 DISP chunk
1222 	if ( handler->parent->format == kXMP_WAVFile ) {	// create for WAVE only
1223 
1224 		std::string actualLang, xmpValue;
1225 		bool r = xmpObj.GetLocalizedText ( kXMP_NS_DC, "title", "" , "x-default" , &actualLang, &xmpValue, 0 );
1226 
1227 		if ( r && ( actualLang == "x-default" ) ) {	// prop exists?
1228 
1229 			// the 'right' DISP is lead by a 32 bit low endian 0x0001
1230 			std::string dispValue = std::string( "\x1\0\0\0", 4 );
1231 			dispValue.append ( xmpValue );
1232 
1233 			if ( handler->dispChunk == 0 ) {
1234 				handler->dispChunk = new RIFF::ValueChunk ( handler->riffChunks.at(0), std::string(), kChunk_DISP );
1235 			}
1236 
1237 			// ! The NUL is optional in WAV to avoid a parsing bug in Audition 3 - can't handle implicit pad byte.
1238 			handler->dispChunk->SetValue ( dispValue, ValueChunk::kNULisOptional );
1239 
1240 		} else {	// remove Disp Chunk..
1241 
1242 			if ( handler->dispChunk != 0 ) {	// ..if existing
1243 				ContainerChunk* mainChunk = handler->riffChunks.at(0);
1244 				Chunk* needle = handler->dispChunk;
1245 				chunkVectIter iter = mainChunk->getChild ( needle );
1246 				if ( iter != mainChunk->children.end() ) {
1247 					mainChunk->replaceChildWithJunk ( *iter );
1248 					handler->dispChunk = 0;
1249 					mainChunk->hasChange = true;
1250 				}
1251 			}
1252 
1253 		}
1254 
1255 	}
1256 
1257 	// 3/5 LIST:INFO
1258 	exportXMPtoListChunk ( kChunk_LIST, kType_INFO, handler, &handler->listInfoChunk, listInfoProps );
1259 
1260 	// 4/5 LIST:Tdat
1261 	exportXMPtoListChunk ( kChunk_LIST, kType_Tdat, handler, &handler->listTdatChunk, listTdatProps );
1262 
1263 	// 5/5 LIST:HDRL:IDIT
1264 	if ( handler->parent->format == kXMP_AVIFile )
1265 		exportXMPtoIDITChunk ( handler );
1266 
1267 }
1268 
1269 } // namespace RIFF
1270