1 // =================================================================================================
2 // ADOBE SYSTEMS INCORPORATED
3 // Copyright 2009 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/FormatSupport/RIFF_Support.hpp"
22 #include "XMPFiles/source/FileHandlers/RIFF_Handler.hpp"
23 
24 #include <utility>
25 
26 using namespace RIFF;
27 
28 namespace RIFF {
29 
30 // GENERAL STATIC FUNCTIONS ////////////////////////////////////////
31 
getChunk(ContainerChunk * parent,RIFF_MetaHandler * handler)32 Chunk* getChunk ( ContainerChunk* parent, RIFF_MetaHandler* handler )
33 {
34 	XMP_IO* file = handler->parent->ioRef;
35 	XMP_Uns8 level = handler->level;
36 	XMP_Uns32 peek = XIO::PeekUns32_LE ( file );
37 
38 	if ( level == 0 )
39 	{
40 		XMP_Validate( peek == kChunk_RIFF, "expected RIFF chunk not found", kXMPErr_BadFileFormat );
41 		XMP_Enforce( parent == NULL );
42 	}
43 	else
44 	{
45 		XMP_Validate( peek != kChunk_RIFF, "unexpected RIFF chunk below top-level", kXMPErr_BadFileFormat );
46 		XMP_Enforce( parent != NULL );
47 	}
48 
49 	switch( peek )
50 	{
51 	case kChunk_RIFF:
52 		return new ContainerChunk( parent, handler );
53 	case kChunk_LIST:
54 		{
55 			if ( level != 1 ) break; // only care on this level
56 
57 			// look further (beyond 4+4 = beyond id+size) to check on relevance
58 			file->Seek ( 8, kXMP_SeekFromCurrent  );
59 			XMP_Uns32 containerType = XIO::PeekUns32_LE ( file );
60 			file->Seek ( -8, kXMP_SeekFromCurrent  );
61 
62 			bool isRelevantList = ( containerType== kType_INFO || containerType == kType_Tdat || containerType == kType_hdrl );
63 			if ( !isRelevantList ) break;
64 			return new ContainerChunk( parent, handler );
65 		}
66 	case kChunk_XMP:
67 			if ( level != 1 ) break; // ignore on inappropriate levels (might be compound metadata?)
68 			return new XMPChunk( parent, handler );
69 	case kChunk_DISP:
70 		{
71 			if ( level != 1 ) break; // only care on this level
72 			// peek even further to see if type is 0x001 and size is reasonable
73 			file ->Seek ( 4, kXMP_SeekFromCurrent  ); // jump DISP
74 			XMP_Uns32 dispSize = XIO::ReadUns32_LE( file );
75 			XMP_Uns32 dispType = XIO::ReadUns32_LE( file );
76 			file ->Seek ( -12, kXMP_SeekFromCurrent ); // rewind, be in front of chunkID again
77 
78 			// only take as a relevant disp if both criteria met,
79 			// otherwise treat as generic chunk!
80 			if ( (dispType == 0x0001) && ( dispSize < 256 * 1024 ) )
81 			{
82 				ValueChunk* r = new ValueChunk( parent, handler );
83 				handler->dispChunk = r;
84 				return r;
85 			}
86 			break; // treat as irrelevant (non-0x1) DISP chunks as generic chunk
87 		}
88 	case kChunk_bext:
89 		{
90 			if ( level != 1 ) break; // only care on this level
91 			// store for now in a value chunk
92 			ValueChunk* r = new ValueChunk( parent, handler );
93 			handler->bextChunk = r;
94 			return r;
95 		}
96 	case kChunk_PrmL:
97 		{
98 			if ( level != 1 ) break; // only care on this level
99 			ValueChunk* r = new ValueChunk( parent, handler );
100 			handler->prmlChunk = r;
101 			return r;
102 		}
103 	case kChunk_Cr8r:
104 		{
105 			if ( level != 1 ) break; // only care on this level
106 			ValueChunk* r = new ValueChunk( parent, handler );
107 			handler->cr8rChunk = r;
108 			return r;
109 		}
110 	case kChunk_JUNQ:
111 	case kChunk_JUNK:
112 		{
113 			JunkChunk* r = new JunkChunk( parent, handler );
114 			return r;
115 		}
116 	case kChunk_IDIT:
117 		{
118 			if ( level != 2 ) break; // only care on this level
119 			ValueChunk* r = new ValueChunk( parent, handler );
120 			handler->iditChunk = r;
121 			return r;
122 		}
123 	}
124 	// this "default:" section must be ouside switch bracket, to be
125 	// reachable by all those break statements above:
126 
127 
128 	// digest 'valuable' container chunks: LIST:INFO, LIST:Tdat
129 	bool insideRelevantList = ( level==2 && parent->id == kChunk_LIST
130 		&& ( parent->containerType== kType_INFO || parent->containerType == kType_Tdat ));
131 
132 	if ( insideRelevantList )
133 	{
134 		ValueChunk* r = new ValueChunk( parent, handler );
135 		return r;
136 	}
137 
138 	// general chunk of no interest, treat as unknown blob
139 	return new Chunk( parent, handler, true, chunk_GENERAL );
140 }
141 
142 // BASE CLASS CHUNK ///////////////////////////////////////////////
143 // ad hoc creation
Chunk(ContainerChunk * parent_,ChunkType c,XMP_Uns32 id_)144 Chunk::Chunk( ContainerChunk* parent_, ChunkType c, XMP_Uns32 id_ )
145 {
146 	this->hasChange = false;
147 	this->chunkType = c; // base class assumption
148 	this->parent = parent_;
149 	this->id = id_;
150 	this->oldSize = 0;
151 	this->newSize = 8;
152 	this->oldPos = 0; // inevitable for ad-hoc
153 	this->needSizeFix = false;
154 
155 	// good parenting for latter destruction
156 	if ( this->parent != NULL )
157 	{
158 		this->parent->children.push_back( this );
159 		if( this->chunkType == chunk_VALUE )
160 			this->parent->childmap.insert( std::make_pair( this->id, (ValueChunk*) this ) );
161 	}
162 }
163 
164 // parsing creation
Chunk(ContainerChunk * parent_,RIFF_MetaHandler * handler,bool skip,ChunkType c)165 Chunk::Chunk( ContainerChunk* parent_, RIFF_MetaHandler* handler, bool skip, ChunkType c )
166 {
167 	chunkType = c; // base class assumption
168 	this->parent = parent_;
169 	this->oldSize = 0;
170 	this->hasChange = false; // [2414649] valid assumption at creation time
171 
172 	XMP_IO* file = handler->parent->ioRef;
173 
174 	this->oldPos = file->Offset();
175 	this->id = XIO::ReadUns32_LE( file );
176 	this->oldSize = XIO::ReadUns32_LE( file );
177 	this->oldSize += 8;
178 
179 	// Make sure the size is within expected bounds.
180 	XMP_Int64 chunkEnd = this->oldPos + this->oldSize;
181 	XMP_Int64 chunkLimit = handler->oldFileSize;
182 	if ( parent_ != 0 ) chunkLimit = parent_->oldPos + parent_->oldSize;
183 	if ( chunkEnd > chunkLimit ) {
184 		bool isUpdate = XMP_OptionIsSet ( handler->parent->openFlags, kXMPFiles_OpenForUpdate );
185 		bool repairFile = XMP_OptionIsSet ( handler->parent->openFlags, kXMPFiles_OpenRepairFile );
186 		if ( (! isUpdate) || (repairFile && (parent_ == 0)) ) {
187 			this->oldSize = chunkLimit - this->oldPos;
188 		} else {
189 			XMP_Throw ( "Bad RIFF chunk size", kXMPErr_BadFileFormat );
190 		}
191 	}
192 
193 	this->newSize = this->oldSize;
194 	this->needSizeFix = false;
195 
196 	if ( skip ) file->Seek ( (this->oldSize - 8), kXMP_SeekFromCurrent );
197 
198 	// "good parenting", essential for latter destruction.
199 	if ( this->parent != NULL )
200 	{
201 		this->parent->children.push_back( this );
202 		if( this->chunkType == chunk_VALUE )
203 			this->parent->childmap.insert( std::make_pair( this->id, (ValueChunk*) this ) );
204 	}
205 }
206 
changesAndSize(RIFF_MetaHandler *)207 void Chunk::changesAndSize( RIFF_MetaHandler* /*handler*/ )
208 {
209 	// only unknown chunks should reach this method,
210 	// all others must reach overloads, hence little to do here:
211 	hasChange = false; // unknown chunk ==> no change, naturally
212 	this->newSize = this->oldSize;
213 }
214 
toString(XMP_Uns8)215 std::string Chunk::toString(XMP_Uns8 /*level*/)
216 {
217 	char buffer[256];
218 	snprintf( buffer, 255, "%.4s -- "
219 							"oldSize: 0x%.8llX,  "
220 							"newSize: 0x%.8llX,  "
221 							"oldPos: 0x%.8llX\n",
222                   (char*)(&this->id), (long long unsigned)this->oldSize,
223                   (long long unsigned)this->newSize, (long long unsigned)this->oldPos );
224 	return std::string(buffer);
225 }
226 
write(RIFF_MetaHandler *,XMP_IO *,bool)227 void Chunk::write( RIFF_MetaHandler* /*handler*/, XMP_IO* /*file*/, bool /*isMainChunk*/ )
228 {
229 	throw new XMP_Error(kXMPErr_InternalFailure, "Chunk::write never to be called for unknown chunks.");
230 }
231 
~Chunk()232 Chunk::~Chunk()
233 {
234 	//nothing
235 }
236 
237 // CONTAINER CHUNK /////////////////////////////////////////////////
238 // a) creation
239 // [2376832] expectedSize - minimum padding "parking size" to use, if not available append to end
ContainerChunk(ContainerChunk * parent_,XMP_Uns32 id_,XMP_Uns32 containerType)240 ContainerChunk::ContainerChunk( ContainerChunk* parent_, XMP_Uns32 id_, XMP_Uns32 containerType ) : Chunk( NULL /* !! */, chunk_CONTAINER, id_ )
241 {
242 	// accept no unparented ConatinerChunks
243 	XMP_Enforce( parent_ != NULL );
244 
245 	this->containerType = containerType;
246 	this->newSize = 12;
247 	this->parent = parent_;
248 
249 	chunkVect* siblings = &parent_->children;
250 
251 	// add at end. ( oldSize==0 will flag optimization later in the process)
252 	siblings->push_back( this );
253 }
254 
255 // b) parsing
ContainerChunk(ContainerChunk * parent,RIFF_MetaHandler * handler)256 ContainerChunk::ContainerChunk( ContainerChunk* parent, RIFF_MetaHandler* handler ) : Chunk( parent, handler, false, chunk_CONTAINER )
257 {
258 	bool repairMode = ( 0 != ( handler->parent->openFlags & kXMPFiles_OpenRepairFile ));
259 
260 	try
261 	{
262 		XMP_IO* file = handler->parent->ioRef;
263 		XMP_Uns8 level = handler->level;
264 
265 		// get type of container chunk
266 		this->containerType = XIO::ReadUns32_LE( file );
267 
268 		// ensure legality of top-level chunks
269 		if ( level == 0 && handler->riffChunks.size() > 0 )
270 		{
271 			XMP_Validate( handler->parent->format == kXMP_AVIFile, "only AVI may have multiple top-level chunks", kXMPErr_BadFileFormat );
272 			XMP_Validate( this->containerType == kType_AVIX, "all chunks beyond main chunk must be type AVIX", kXMPErr_BadFileFormat );
273 		}
274 
275 		// has *relevant* subChunks? (there might be e.g. non-INFO LIST chunks we don't care about)
276 		bool hasSubChunks = ( ( this->id == kChunk_RIFF ) ||
277 							  ( this->id == kChunk_LIST && this->containerType == kType_INFO ) ||
278 							  ( this->id == kChunk_LIST && this->containerType == kType_Tdat ) ||
279 							  ( this->id == kChunk_LIST && this->containerType == kType_hdrl )
280 						  );
281 		XMP_Int64 endOfChunk = this->oldPos + this->oldSize;
282 
283 		// this statement catches beyond-EoF-offsets on any level
284 		// exception: level 0, tolerate if in repairMode
285 		if ( (level == 0) && repairMode && (endOfChunk > handler->oldFileSize) )
286 		{
287 			endOfChunk = handler->oldFileSize; // assign actual file size
288 			this->oldSize = endOfChunk - this->oldPos; //reversely calculate correct oldSize
289 		}
290 
291 		XMP_Validate( endOfChunk <= handler->oldFileSize, "offset beyond EoF", kXMPErr_BadFileFormat );
292 
293 		Chunk* curChild = 0;
294 		if ( hasSubChunks )
295 		{
296 			handler->level++;
297 			while ( file->Offset() < endOfChunk )
298 			{
299 				curChild = RIFF::getChunk( this, handler );
300 
301 				// digest pad byte - no value validation (0), since some 3rd party files have non-0-padding.
302 				if ( file->Offset() % 2 == 1 )
303 				{
304 					// [1521093] tolerate missing pad byte at very end of file:
305 					XMP_Uns8 pad;
306 					file->Read ( &pad, 1 );  // Read the pad, tolerate being at EOF.
307 
308 				}
309 
310 				// within relevant LISTs, relentlesly delete junk chunks (create a single one
311 				// at end as part of updateAndChanges()
312 				if ( (containerType== kType_INFO || containerType == kType_Tdat)
313 						&& ( curChild->chunkType == chunk_JUNK ) )
314 				{
315 						this->children.pop_back();
316 						delete curChild;
317 				} // for other chunks: join neighouring Junk chunks into one
318 				else if ( (curChild->chunkType == chunk_JUNK) && ( this->children.size() >= 2 ) )
319 				{
320 					// nb: if there are e.g 2 chunks, then last one is at(1), prev one at(0) ==> '-2'
321 					Chunk* prevChunk = this->children.at( this->children.size() - 2 );
322 					if ( prevChunk->chunkType == chunk_JUNK )
323 					{
324 						// stack up size to prior chunk
325 						prevChunk->oldSize += curChild->oldSize;
326 						prevChunk->newSize += curChild->newSize;
327 						XMP_Enforce( prevChunk->oldSize == prevChunk->newSize );
328 						// destroy current chunk
329 						this->children.pop_back();
330 						delete curChild;
331 					}
332 				}
333 			}
334 			handler->level--;
335 			XMP_Validate( file->Offset() == endOfChunk, "subchunks exceed outer chunk size", kXMPErr_BadFileFormat );
336 
337 			// pointers for later legacy processing
338 			if ( level==1 && this->id==kChunk_LIST && this->containerType == kType_INFO )
339 				handler->listInfoChunk = this;
340 			if ( level==1 && this->id==kChunk_LIST && this->containerType == kType_Tdat )
341 				handler->listTdatChunk = this;
342 			if ( level == 1 && this->id == kChunk_LIST && this->containerType == kType_hdrl )
343 				handler->listHdlrChunk = this;
344 		}
345 		else // skip non-interest container chunk
346 		{
347 			file->Seek ( (this->oldSize - 8 - 4), kXMP_SeekFromCurrent );
348 		} // if - else
349 
350 	} // try
351 	catch (XMP_Error& e) {
352 		this->release(); // free resources
353 		if ( this->parent != 0)
354 			this->parent->children.pop_back(); // hereby taken care of, so removing myself...
355 
356 		throw e;         // re-throw
357 	}
358 }
359 
changesAndSize(RIFF_MetaHandler * handler)360 void ContainerChunk::changesAndSize( RIFF_MetaHandler* handler )
361 {
362 
363 	// Walk the container subtree adjusting the children that have size changes. The only containers
364 	// are RIFF and LIST chunks, they are treated differently.
365 	//
366 	// LISTs get recomposed as a whole. Existing JUNK children of a LIST are removed, existing real
367 	// children are left in order with their new size, new children have already been appended. The
368 	// LIST as a whole gets a new size that is the sum of the final children.
369 	//
370 	// Special rules apply to various children of a RIFF container. FIrst, adjacent JUNK children
371 	// are combined, this simplifies maximal reuse. The children are recursively adjusted in order
372 	// to get their final size.
373 	//
374 	// Try to determine the final placement of each RIFF child using general rules:
375 	//	- if the size is unchanged: leave at current location
376 	//	- if the chunk is at the end of the last RIFF chunk and grows: leave at current location
377 	//	- if there is enough following JUNK: add part of the JUNK, adjust remaining JUNK size
378 	//	- if it shrinks by 9 bytes or more: carve off trailing JUNK
379 	//	- try to find adequate JUNK in the current parent
380 	//
381 	// Use child-specific rules as a last resort:
382 	//	- if it is LIST:INFO: delete it, must be in first RIFF chunk
383 	//	- for others: move to end of last RIFF chunk, make old space JUNK
384 
385 	// ! Don't create any junk chunks of exactly 8 bytes, just a header and no content. That has a
386 	// ! size field of zero, which hits a crashing bug in some versions of Windows Media Player.
387 
388 	bool isRIFFContainer = (this->id == kChunk_RIFF);
389 	bool isLISTContainer = (this->id == kChunk_LIST);
390 	XMP_Enforce ( isRIFFContainer | isLISTContainer );
391 
392 	XMP_Index childIndex;	// Could be local to the loops, this simplifies debuging. Need a signed type!
393 	Chunk * currChild;
394 
395 	if ( this->children.empty() ) {
396 		if ( isRIFFContainer) {
397 			this->newSize = 12;	// Keep a minimal size container.
398 		} else {
399 			this->newSize = 0;	// Will get removed from parent in outer call.
400 		}
401 		this->hasChange = true;
402 		return;	// Nothing more to do without children.
403 	}
404 
405 	// Collapse adjacent RIFF junk children, remove all LIST junk children. Work back to front to
406 	// simplify the effect of .erase() on the loop. Purposely ignore the first chunk.
407 
408 	for ( childIndex = (XMP_Index)this->children.size() - 1; childIndex > 0; --childIndex ) {
409 
410 		currChild = this->children[childIndex];
411 		if ( currChild->chunkType != chunk_JUNK ) continue;
412 
413 		if ( isRIFFContainer ) {
414 			Chunk * prevChild = this->children[childIndex-1];
415 			if ( prevChild->chunkType != chunk_JUNK ) continue;
416 			prevChild->oldSize += currChild->oldSize;
417 			prevChild->newSize += currChild->newSize;
418 			prevChild->hasChange = true;
419 		}
420 
421 		this->children.erase ( this->children.begin() + childIndex );
422 		delete currChild;
423 		this->hasChange = true;
424 
425 	}
426 
427 	// Process the children of RIFF and LIST containers to get their final size. Remove empty
428 	// children. Work back to front to simplify the effect of .erase() on the loop. Do not ignore
429 	// the first chunk.
430 
431 	for ( childIndex = (XMP_Index)this->children.size() - 1; childIndex >= 0; --childIndex ) {
432 
433 		currChild = this->children[childIndex];
434 
435 		++handler->level;
436 		currChild->changesAndSize ( handler );
437 		--handler->level;
438 
439 		if ( (currChild->newSize == 8) || (currChild->newSize == 0) ) {	// ! The newSIze is supposed to include the header.
440 			this->children.erase ( this->children.begin() + childIndex );
441 			delete currChild;
442 			this->hasChange = true;
443 		} else {
444 			this->hasChange |= currChild->hasChange;
445 			currChild->needSizeFix = (currChild->newSize != currChild->oldSize);
446 			if ( currChild->needSizeFix && (currChild->newSize > currChild->oldSize) &&
447 				 (this == handler->lastChunk) && (childIndex+1 == (XMP_Index)this->children.size()) ) {
448 				// Let an existing last-in-file chunk grow in-place. Shrinking is conceptually OK,
449 				// but complicates later sanity check that the main AVI chunk is not OK to append
450 				// other chunks later. Ignore new chunks, they might reuse junk space.
451 				if ( currChild->oldSize != 0 ) currChild->needSizeFix = false;
452 			}
453 		}
454 
455 	}
456 
457 	// Go through the children of a RIFF container, adjusting the placement as necessary. In brief,
458 	// things can only grow at the end of the last RIFF chunk, and non-junk chunks can't be shifted.
459 
460 	if ( isRIFFContainer ) {
461 
462 		for ( childIndex = 0; childIndex < (XMP_Index)this->children.size(); ++childIndex ) {
463 
464 			currChild = this->children[childIndex];
465 			if ( ! currChild->needSizeFix ) continue;
466 			currChild->needSizeFix = false;
467 
468 			XMP_Int64 sizeDiff = currChild->newSize - currChild->oldSize;	// Positive for growth.
469 			XMP_Uns8  padSize = (currChild->newSize & 1);	// Need a pad for odd size.
470 
471 			// See if the following chunk is junk that can be utilized.
472 
473 			Chunk * nextChild = 0;
474 			if ( childIndex+1 < (XMP_Index)this->children.size() ) nextChild = this->children[childIndex+1];
475 
476 			if ( (nextChild != 0) && (nextChild->chunkType == chunk_JUNK) ) {
477 				if ( nextChild->newSize >= (9 + sizeDiff + padSize) ) {
478 
479 					// Incorporate part of the trailing junk, or make the trailing junk grow.
480 					nextChild->newSize -= sizeDiff;
481 					nextChild->newSize -= padSize;
482 					nextChild->hasChange = true;
483 					continue;
484 
485 				} else if (  nextChild->newSize == (sizeDiff + padSize)  ) {
486 
487 					// Incorporate all of the trailing junk.
488 					this->children.erase ( this->children.begin() + childIndex + 1 );
489 					delete nextChild;
490 					continue;
491 
492 				}
493 			}
494 
495 			// See if the chunk shrinks enough to turn the leftover space into junk.
496 
497 			if ( (sizeDiff + padSize) <= -9 ) {
498 				this->children.insert ( (this->children.begin() + childIndex + 1), new JunkChunk ( NULL, ((-sizeDiff) - padSize) ) );
499 				continue;
500 			}
501 
502 			// Look through the parent for a usable span of junk.
503 
504 			XMP_Index junkIndex;
505 			Chunk * junkChunk = 0;
506 			for ( junkIndex = 0; junkIndex < (XMP_Index)this->children.size(); ++junkIndex ) {
507 				junkChunk = this->children[junkIndex];
508 				if ( junkChunk->chunkType != chunk_JUNK ) continue;
509 				if ( (junkChunk->newSize >= (9 + currChild->newSize + padSize)) ||
510 					 (junkChunk->newSize == (currChild->newSize + padSize)) ) break;
511 			}
512 
513 			if ( junkIndex < (XMP_Index)this->children.size() ) {
514 
515 				// Use part or all of the junk for the relocated chunk, replace the old space with junk.
516 
517 				if ( junkChunk->newSize == (currChild->newSize + padSize) ) {
518 
519 					// The found junk is an exact fit.
520 					this->children[junkIndex] = currChild;
521 					delete junkChunk;
522 
523 				} else {
524 
525 					// The found junk has excess space. Insert the moving chunk and shrink the junk.
526 					XMP_Assert ( junkChunk->newSize >= (9 + currChild->newSize + padSize) );
527 					junkChunk->newSize -= (currChild->newSize + padSize);
528 					junkChunk->hasChange = true;
529 					this->children.insert ( (this->children.begin() + junkIndex), currChild );
530 					if ( junkIndex < childIndex ) ++childIndex;	// The insertion moved the current child.
531 
532 				}
533 
534 				if ( currChild->oldSize != 0 ) {
535 					this->children[childIndex] = new JunkChunk ( 0, currChild->oldSize );	// Replace the old space with junk.
536 				} else {
537 					this->children.erase ( this->children.begin() + childIndex );	// Remove the newly created chunk's old location.
538 					--childIndex;	// Make the next loop iteration not skip a chunk.
539 				}
540 
541 				continue;
542 
543 			}
544 
545 			// If this is a LIST:INFO chunk not in the last of multiple RIFF chunks, then give up
546 			// and replace it with oldSize junk. Preserve the first RIFF chunk's original size.
547 
548 			bool isListInfo = (currChild->id == kChunk_LIST) && (currChild->chunkType == chunk_CONTAINER) &&
549 							  (((ContainerChunk*)currChild)->containerType == kType_INFO);
550 
551 			if ( isListInfo && (handler->riffChunks.size() > 1) &&
552 				 (this->id == kChunk_RIFF) && (this != handler->lastChunk) ) {
553 
554 				if ( currChild->oldSize != 0 ) {
555 					this->children[childIndex] = new JunkChunk ( 0, currChild->oldSize );
556 				} else {
557 					this->children.erase ( this->children.begin() + childIndex );
558 					--childIndex;	// Make the next loop iteration not skip a chunk.
559 				}
560 
561 				delete currChild;
562 				continue;
563 
564 			}
565 
566 			// Move the chunk to the end of the last RIFF chunk and make the old space junk.
567 
568 			if ( (this == handler->lastChunk) && (childIndex+1 == (XMP_Index)this->children.size()) ) continue;	// Already last.
569 
570 			handler->lastChunk->children.push_back( currChild );
571 			if ( currChild->oldSize != 0 ) {
572 				this->children[childIndex] = new JunkChunk ( 0, currChild->oldSize );	// Replace the old space with junk.
573 			} else {
574 				this->children.erase ( this->children.begin() + childIndex );	// Remove the newly created chunk's old location.
575 				--childIndex;	// Make the next loop iteration not skip a chunk.
576 			}
577 
578 		}
579 
580 	}
581 
582 	// Compute the finished container's new size (for both RIFF and LIST).
583 
584 	this->newSize = 12;	// Start with standard container header.
585 	for ( childIndex = 0; childIndex < (XMP_Index)this->children.size(); ++childIndex ) {
586 		currChild = this->children[childIndex];
587 		this->newSize += currChild->newSize;
588 		this->newSize += (this->newSize & 1);	// Round up if odd.
589 	}
590 
591 	XMP_Validate ( (this->newSize <= 0xFFFFFFFFLL), "No single chunk may be above 4 GB", kXMPErr_Unimplemented );
592 
593 }
594 
toString(XMP_Uns8 level)595 std::string ContainerChunk::toString(XMP_Uns8 level )
596 {
597 	XMP_Int64 offset= 12; // compute offsets, just for informational purposes
598 	// (actually only correct for first chunk)
599 
600 	char buffer0[256];
601 	snprintf( buffer0, 255, "%.4s:%.4s, "
602 			"oldSize: 0x%8llX, "
603 			"newSize: 0x%.8llX, "
604 			"oldPos: 0x%.8llX\n",
605 			(char*)(&this->id), (char*)(&this->containerType),
606                   (long long unsigned)this->oldSize,
607                   (long long unsigned)this->newSize,
608                   (long long unsigned)this->oldPos );
609 
610 	std::string r(buffer0);
611 	chunkVectIter iter;
612 	for( iter = this->children.begin(); iter != this->children.end(); iter++ )
613 	{
614 		char buffer[256];
615 		snprintf( buffer, 250, "offset 0x%.8llX", offset );
616 		r += std::string ( level*4, ' ' ) + std::string( buffer ) + ":" + (*iter)->toString( level + 1 );
617 		offset += (*iter)->newSize;
618 		if ( offset % 2 == 1 )
619 			offset++;
620 	}
621 	return std::string(r);
622 }
623 
write(RIFF_MetaHandler * handler,XMP_IO * file,bool isMainChunk)624 void ContainerChunk::write( RIFF_MetaHandler* handler, XMP_IO* file, bool isMainChunk )
625 {
626 	if ( isMainChunk )
627 		file ->Rewind();
628 
629 	// enforce even position
630 	XMP_Int64 chunkStart = file->Offset();
631 	XMP_Int64 chunkEnd = chunkStart + this->newSize;
632 	XMP_Enforce( chunkStart % 2 == 0 );
633 	chunkVect *rc = &this->children;
634 
635 	// [2473303] have to write back-to-front to avoid stomp-on-feet
636 	XMP_Int64 childStart = chunkEnd;
637 	for ( XMP_Int32 chunkNo = (XMP_Int32)(rc->size() -1); chunkNo >= 0; chunkNo-- )
638 	{
639 		Chunk* cur = rc->at(chunkNo);
640 
641 		// pad byte first
642 		if ( cur->newSize % 2 == 1 )
643 		{
644 			childStart--;
645 			file->Seek ( childStart, kXMP_SeekFromStart  );
646 			XIO::WriteUns8( file, 0 );
647 		}
648 
649 		// then contents
650 		childStart-= cur->newSize;
651 		file->Seek ( childStart, kXMP_SeekFromStart  );
652 		switch ( cur->chunkType )
653 		{
654 			case chunk_GENERAL: //COULDDO enfore no change, since not write-out-able
655 				if ( cur->oldPos != childStart )
656 					XIO::Move( file, cur->oldPos, file, childStart, cur->oldSize );
657 				break;
658 			default:
659 				cur->write( handler, file, false );
660 				break;
661 		} // switch
662 
663 	} // for
664 	XMP_Enforce ( chunkStart + 12 == childStart);
665 	file->Seek ( chunkStart, kXMP_SeekFromStart );
666 
667 	XIO::WriteUns32_LE( file, this->id );
668 	XIO::WriteUns32_LE( file, (XMP_Uns32) this->newSize - 8 ); // validated in changesAndSize() above
669 	XIO::WriteUns32_LE( file, this->containerType );
670 
671 }
672 
release()673 void ContainerChunk::release()
674 {
675 	// free subchunks
676 	Chunk* curChunk;
677 	while( ! this->children.empty() )
678 	{
679 		curChunk = this->children.back();
680 		delete curChunk;
681 		this->children.pop_back();
682 	}
683 }
684 
~ContainerChunk()685 ContainerChunk::~ContainerChunk()
686 {
687 	this->release(); // free resources
688 }
689 
690 // XMP CHUNK ///////////////////////////////////////////////
691 // a) create
692 
693 // a) creation
XMPChunk(ContainerChunk * parent_)694 XMPChunk::XMPChunk( ContainerChunk* parent_ ) : Chunk( parent_, chunk_XMP , kChunk_XMP )
695 {
696 	// nothing
697 }
698 
699 // b) parse
XMPChunk(ContainerChunk * parent_,RIFF_MetaHandler * handler)700 XMPChunk::XMPChunk( ContainerChunk* parent_, RIFF_MetaHandler* handler ) : Chunk( parent_, handler, false, chunk_XMP )
701 {
702 	chunkType = chunk_XMP;
703 	XMP_IO* file = handler->parent->ioRef;
704 	/*XMP_Uns8 level = handler->level*/;
705 
706 	handler->packetInfo.offset = this->oldPos + 8;
707 	handler->packetInfo.length = (XMP_Int32) this->oldSize - 8;
708 
709 	handler->xmpPacket.reserve ( handler->packetInfo.length );
710 	handler->xmpPacket.assign ( handler->packetInfo.length, ' ' );
711 	file->ReadAll ( (void*)handler->xmpPacket.data(), handler->packetInfo.length );
712 
713 	handler->containsXMP = true; // last, after all possible failure
714 
715 	// pointer for later processing
716 	handler->xmpChunk = this;
717 }
718 
changesAndSize(RIFF_MetaHandler * handler)719 void XMPChunk::changesAndSize( RIFF_MetaHandler* handler )
720 {
721 	XMP_Enforce( &handler->xmpPacket != 0 );
722 	XMP_Enforce( handler->xmpPacket.size() > 0 );
723 	this->newSize = 8 + handler->xmpPacket.size();
724 
725 	XMP_Validate( this->newSize <= 0xFFFFFFFFLL, "no single chunk may be above 4 GB", kXMPErr_InternalFailure );
726 
727 	// a complete no-change would have been caught in XMPFiles common code anyway
728 	this->hasChange = true;
729 }
730 
write(RIFF_MetaHandler * handler,XMP_IO * file,bool)731 void XMPChunk::write( RIFF_MetaHandler* handler, XMP_IO* file, bool /*isMainChunk*/ )
732 {
733 	XIO::WriteUns32_LE( file, kChunk_XMP );
734 	XIO::WriteUns32_LE( file, (XMP_Uns32) this->newSize - 8 ); // validated in changesAndSize() above
735 	file->Write ( handler->xmpPacket.data(), (XMP_Int32)handler->xmpPacket.size()  );
736 }
737 
738 // Value CHUNK ///////////////////////////////////////////////
739 // a) creation
ValueChunk(ContainerChunk * parent_,std::string value,XMP_Uns32 id_)740 ValueChunk::ValueChunk( ContainerChunk* parent_, std::string value, XMP_Uns32 id_ ) : Chunk( parent_, chunk_VALUE, id_ )
741 {
742 	this->oldValue = std::string();
743 	this->SetValue( value );
744 }
745 
746 // b) parsing
ValueChunk(ContainerChunk * parent_,RIFF_MetaHandler * handler)747 ValueChunk::ValueChunk( ContainerChunk* parent_, RIFF_MetaHandler* handler ) : Chunk( parent_, handler, false, chunk_VALUE )
748 {
749 	// set value: -----------------
750 	XMP_IO* file = handler->parent->ioRef;
751 	/*XMP_Uns8 level = handler->level;*/
752 
753 	// unless changed through reconciliation, assume for now.
754 	// IMPORTANT to stay true to the original (no \0 cleanup or similar)
755 	// since unknown value chunks might not be fully understood,
756 	// hence must be precisely preserved !!!
757 
758 	XMP_Int32 length = (XMP_Int32) this->oldSize - 8;
759 	this->oldValue.reserve( length );
760 	this->oldValue.assign( length + 1, '\0' );
761 	file->ReadAll ( (void*)this->oldValue.data(), length );
762 
763 	this->newValue = this->oldValue;
764 	this->newSize = this->oldSize;
765 }
766 
SetValue(std::string value,bool optionalNUL)767 void ValueChunk::SetValue( std::string value, bool optionalNUL /* = false */ )
768 {
769 	this->newValue.assign( value );
770 	if ( (! optionalNUL) || ((value.size() & 1) == 1) ) {
771 		// ! The NUL should be optional in WAV to avoid a parsing bug in Audition 3 - can't handle implicit pad byte.
772 		this->newValue.append( 1, '\0' ); // append zero termination as explicit part of string
773 	}
774 	this->newSize = this->newValue.size() + 8;
775 }
776 
changesAndSize(RIFF_MetaHandler *)777 void ValueChunk::changesAndSize( RIFF_MetaHandler* /*handler*/ )
778 {
779 	// Don't simply assign to this->hasChange, it might already be true.
780 	if ( this->newValue.size() != this->oldValue.size() ) {
781 		this->hasChange = true;
782 	} else if ( strncmp ( this->oldValue.c_str(), this->newValue.c_str(), this->newValue.size() ) != 0 ) {
783 		this->hasChange = true;
784 	}
785 }
786 
write(RIFF_MetaHandler *,XMP_IO * file,bool)787 void ValueChunk::write( RIFF_MetaHandler* /*handler*/, XMP_IO* file, bool /*isMainChunk*/ )
788 {
789 	XIO::WriteUns32_LE( file, this->id );
790 	XIO::WriteUns32_LE( file, (XMP_Uns32)this->newSize - 8 );
791 	file->Write ( this->newValue.data() , (XMP_Int32)this->newSize - 8  );
792 }
793 
794 /* remove value chunk if existing.
795    return true if it was existing. */
removeValue(XMP_Uns32 id_)796 bool ContainerChunk::removeValue( XMP_Uns32	id_ )
797 {
798 	valueMap* cm = &this->childmap;
799 	valueMapIter iter = cm->find( id_ );
800 
801 	if( iter == cm->end() )
802 		return false;  //not found
803 
804 	ValueChunk* propChunk = iter->second;
805 
806 	// remove from vector (difficult)
807 	chunkVect* cv = &this->children;
808 	chunkVectIter cvIter;
809 	for (cvIter = cv->begin(); cvIter != cv->end(); ++cvIter )
810 	{
811 		if ( (*cvIter)->id == id_ )
812 			break; // found!
813 	}
814 	XMP_Validate( cvIter != cv->end(), "property not found in children vector", kXMPErr_InternalFailure );
815 	cv->erase( cvIter );
816 
817 	// remove from map (easy)
818 	cm->erase( iter );
819 
820 	delete propChunk;
821 	return true; // found and removed
822 }
823 
824 /* returns iterator to (first) occurence of this chunk.
825    iterator to the end of the map if chunk pointer is not found  */
getChild(Chunk * needle)826 chunkVectIter ContainerChunk::getChild( Chunk* needle )
827 {
828 	chunkVectIter iter;
829 	for( iter = this->children.begin(); iter != this->children.end(); iter++ )
830 	{
831 		if ( (*iter) == needle ) return iter;
832 	}
833 	return this->children.end();
834 }
835 
836 /* replaces a chunk by a JUNK chunk.
837    Also frees memory of prior chunk. */
replaceChildWithJunk(Chunk * child,bool deleteChild)838 void ContainerChunk::replaceChildWithJunk( Chunk* child, bool deleteChild )
839 {
840 	chunkVectIter iter = getChild( child );
841 	if ( iter == this->children.end() ) {
842 		throw new XMP_Error(kXMPErr_InternalFailure, "replaceChildWithJunk: childChunk not found.");
843 	}
844 
845 	*iter = new JunkChunk ( NULL, child->oldSize );
846 	if ( deleteChild ) delete child;
847 
848 	this->hasChange = true;
849 }
850 
851 // JunkChunk ///////////////////////////////////////////////////
852 // a) creation
JunkChunk(ContainerChunk * parent_,XMP_Int64 size)853 JunkChunk::JunkChunk( ContainerChunk* parent_, XMP_Int64 size ) : Chunk( parent_, chunk_JUNK, kChunk_JUNK )
854 {
855 	XMP_Assert( size >= 8 );
856 	this->oldSize = size;
857 	this->newSize = size;
858 	this->hasChange = true;
859 }
860 
861 // b) parsing
JunkChunk(ContainerChunk * parent_,RIFF_MetaHandler * handler)862 JunkChunk::JunkChunk( ContainerChunk* parent_, RIFF_MetaHandler* handler ) : Chunk( parent_, handler, true, chunk_JUNK )
863 {
864 	chunkType = chunk_JUNK;
865 }
866 
changesAndSize(RIFF_MetaHandler *)867 void JunkChunk::changesAndSize( RIFF_MetaHandler* /*handler*/ )
868 {
869 	this->newSize = this->oldSize; // optimization at a later stage
870 	XMP_Validate( this->newSize <= 0xFFFFFFFFLL, "no single chunk may be above 4 GB", kXMPErr_InternalFailure );
871 	if ( this->id == kChunk_JUNQ ) this->hasChange = true;	// Force ID change to JUNK.
872 }
873 
874 // zeroBuffer, etc to write out empty native padding
875 const static XMP_Uns32 kZeroBufferSize64K = 64 * 1024;
876 static XMP_Uns8 kZeroes64K [ kZeroBufferSize64K ]; // C semantics guarantee zero initialization.
877 
write(RIFF_MetaHandler *,XMP_IO * file,bool)878 void JunkChunk::write( RIFF_MetaHandler* /*handler*/, XMP_IO* file, bool /*isMainChunk*/ )
879 {
880 	XIO::WriteUns32_LE( file, kChunk_JUNK );		// write JUNK, never JUNQ
881 	XMP_Enforce( this->newSize < 0xFFFFFFFF );
882 	XMP_Enforce( this->newSize >= 8 );			// minimum size of any chunk
883 	XMP_Uns32 innerSize = (XMP_Uns32)this->newSize - 8;
884 	XIO::WriteUns32_LE( file, innerSize );
885 
886 	// write out in 64K chunks
887 	while ( innerSize > kZeroBufferSize64K )
888 	{
889 		file->Write ( kZeroes64K , kZeroBufferSize64K  );
890 		innerSize -= kZeroBufferSize64K;
891 	}
892 	file->Write ( kZeroes64K , innerSize  );
893 }
894 
895 } // namespace RIFF
896