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