1 // =================================================================================================
2 // ADOBE SYSTEMS INCORPORATED
3 // Copyright 2010 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 #include "public/include/XMP_Const.h"
12 #include "source/XIO.hpp"
13
14 #include "XMPFiles/source/FormatSupport/IFF/ChunkController.h"
15 #include "XMPFiles/source/FormatSupport/IFF/Chunk.h"
16
17 #include <cstdio>
18
19 using namespace IFF_RIFF;
20
21 //-----------------------------------------------------------------------------
22 //
23 // ChunkController::ChunkController(...)
24 //
25 // Purpose: ctor/dtor
26 //
27 //-----------------------------------------------------------------------------
28
ChunkController(IChunkBehavior * chunkBehavior,XMP_Bool bigEndian)29 ChunkController::ChunkController( IChunkBehavior* chunkBehavior, XMP_Bool bigEndian )
30 : mEndian (NULL),
31 mChunkBehavior (chunkBehavior),
32 mFileSize (0),
33 mRoot (NULL),
34 mTrailingGarbageOffset (0),
35 mTrailingGarbageSize (0)
36 {
37 if (bigEndian)
38 {
39 mEndian = &BigEndian::getInstance();
40 } else {
41 mEndian = &LittleEndian::getInstance();
42 }
43
44 // create virtual root chunk
45 mRoot = Chunk::createChunk(*mEndian);
46
47 // share chunk paths with behavior
48 mChunkBehavior->setMovablePaths( &mChunkPaths );
49 }
50
~ChunkController()51 ChunkController::~ChunkController() noexcept(false)
52 {
53 XMP_Validate( mRoot != NULL, "ERROR inserting Chunk. mRoot is NULL.", kXMPErr_InternalFailure );
54 XMP_Assert(dynamic_cast<Chunk*>(mRoot) == static_cast<Chunk*>(mRoot));
55 delete dynamic_cast<Chunk*>(mRoot);
56 }
57
58 //-----------------------------------------------------------------------------
59 //
60 // ChunkController::addChunkPath(...)
61 //
62 // Purpose: Adds the given path to the array of "Chunk's of interest"
63 //
64 //-----------------------------------------------------------------------------
65
addChunkPath(const ChunkPath & path)66 void ChunkController::addChunkPath( const ChunkPath& path )
67 {
68 mChunkPaths.push_back(path);
69 }
70
71 //-----------------------------------------------------------------------------
72 //
73 // ChunkController::compareChunkPaths(...)
74 //
75 // Purpose: The function parses all the sibling chunks. For every chunk it
76 // either caches the chunk, skips it, or calls the function recusivly
77 // for the children chunks
78 //
79 //-----------------------------------------------------------------------------
80
compareChunkPaths(const ChunkPath & currentPath)81 ChunkPath::MatchResult ChunkController::compareChunkPaths(const ChunkPath& currentPath)
82 {
83 ChunkPath::MatchResult result = ChunkPath::kNoMatch;
84
85 for( PathIterator iter = mChunkPaths.begin(); ( result == ChunkPath::kNoMatch ) && ( iter != mChunkPaths.end() ); iter++ )
86 {
87 result = iter->match(currentPath);
88 }
89
90 return result;
91 }
92
93 //-----------------------------------------------------------------------------
94 //
95 // ChunkController::parseChunks(...)
96 //
97 // Purpose: The function Parses all the sibling chunks. For every chunk it
98 // either caches the chunk, skips it, or calls the function recusivly
99 // for the children chunks
100 //
101 //-----------------------------------------------------------------------------
102
parseChunks(XMP_IO * stream,ChunkPath & currentPath,XMP_OptionBits * options,Chunk * parent)103 void ChunkController::parseChunks( XMP_IO* stream, ChunkPath& currentPath, XMP_OptionBits* options /* = NULL */, Chunk* parent /* = NULL */)
104 {
105 XMP_Uns64 filePos = stream->Offset();
106 XMP_Bool isRoot = (parent == mRoot);
107 XMP_Uns64 parseLimit = mFileSize;
108 XMP_Uns32 chunkCnt = 0;
109
110 XMP_Validate( mRoot != NULL, "ERROR inserting Chunk. mRoot is NULL.", kXMPErr_InternalFailure );
111 XMP_Assert(dynamic_cast<Chunk*>(mRoot) == static_cast<Chunk*>(mRoot));
112 parent = ( parent == NULL ? dynamic_cast<Chunk*>(mRoot) : parent );
113
114 //
115 // calculate the parse limit
116 //
117 if ( !isRoot )
118 {
119 parseLimit = parent->getOriginalOffset() + parent->getSize( true );
120
121 if( parseLimit > mFileSize )
122 {
123 parseLimit = mFileSize;
124 }
125 }
126
127 while ( filePos < parseLimit )
128 {
129 XMP_Uns64 fileTail = mFileSize - filePos;
130
131 //
132 // check if there is enough space (at least for id and size)
133 //
134 if ( fileTail < Chunk::HEADER_SIZE )
135 {
136 //preserve rest of bytes (fileTail)
137 mTrailingGarbageOffset = filePos;
138 mTrailingGarbageSize = fileTail;
139 break; // stop parsing
140 }
141 else
142 {
143 bool chunkJump = false;
144
145 //
146 // create a new Chunk
147 //
148 Chunk* chunk = Chunk::createChunk(* mEndian );
149
150 bool readFailure = false;
151 //
152 // read the Chunk (id, size, [type]) without caching the data
153 //
154 try
155 {
156 chunk->readChunk( stream );
157 }
158 catch( ... )
159 {
160 // remember exception during reading the chunk
161 readFailure = true;
162 }
163
164 //
165 // validate chunk ID for top-level chunks
166 //
167 if( isRoot && ! mChunkBehavior->isValidTopLevelChunk( chunk->getIdentifier(), chunkCnt ) )
168 {
169 // notValid: preserve rest of bytes (fileTail)
170 mTrailingGarbageOffset = filePos;
171 mTrailingGarbageSize = fileTail;
172 //delete unused chunk (because these are undefined trailing bytes)
173 delete chunk;
174 break; // stop parsing
175 }
176 else if ( readFailure )
177 {
178 delete chunk;
179 XMP_Throw ( "Bad RIFF chunk", kXMPErr_BadFileFormat );
180 }
181
182 //
183 // parenting
184 // (as early as possible in order to be able to clean up
185 // the tree correctly in the case of an exception)
186 //
187 parent->appendChild(chunk, false);
188
189 // count top-level chunks
190 if( isRoot )
191 {
192 chunkCnt++;
193 }
194
195 //
196 // check size if value exceeds 4GB border
197 //
198 if( chunk->getSize() >= 0x00000000FFFFFFFFLL )
199 {
200 // remember file position
201 XMP_Int64 currentFilePos = stream->Offset();
202
203 // ask for the "real" size value
204 XMP_Uns64 realSize = mChunkBehavior->getRealSize( chunk->getSize(),
205 chunk->getIdentifier(),
206 *mRoot,
207 stream );
208
209 // set new size at chunk
210 chunk->setSize( realSize, true );
211
212 // set flag if the file position changed
213 chunkJump = currentFilePos < stream->Offset();
214 }
215
216 //
217 // Repair if needed
218 //
219 if ( filePos + chunk->getSize(true) > mFileSize )
220 {
221 bool isUpdate = ( options != NULL ? XMP_OptionIsSet ( *options, kXMPFiles_OpenForUpdate ) : false );
222 bool repairFile = ( options != NULL ? XMP_OptionIsSet ( *options, kXMPFiles_OpenRepairFile ) : false );
223
224 if ( ( ! isUpdate ) || ( repairFile && isRoot ) )
225 {
226 chunk->setSize( mFileSize-filePos-Chunk::HEADER_SIZE, true );
227 }
228 else
229 {
230 XMP_Throw ( "Bad RIFF chunk size", kXMPErr_BadFileFormat );
231 }
232 }
233
234 // extend search path
235 currentPath.append( chunk->getIdentifier() );
236
237 // first 4 bytes might be already read by the chunk->readChunk function
238 XMP_Uns64 offsetOfChunkRead = stream->Offset() - filePos - Chunk::HEADER_SIZE;
239
240 switch ( compareChunkPaths(currentPath) )
241 {
242 case ChunkPath::kFullMatch :
243 {
244 chunk->cacheChunkData( stream );
245 }
246 break;
247
248 case ChunkPath::kPartMatch :
249 {
250 parseChunks( stream, currentPath, options, chunk);
251 // recalculate the size based on the sizes of its children
252 chunk->calculateSize( true );
253 }
254 break;
255
256 case ChunkPath::kNoMatch :
257 {
258 // Not a chunk we are interested in, so mark it as not changed
259 // It will then be ignored by any further logic
260 chunk->resetChanges();
261
262 if ( !chunkJump && chunk->getSize() > 0) // if chunk not empty
263 {
264 XMP_Validate( stream->Offset() + chunk->getSize() - offsetOfChunkRead <= mFileSize , "ERROR: want's to skip beyond EOF", kXMPErr_InternalFailure);
265 stream->Seek ( chunk->getSize() - offsetOfChunkRead , kXMP_SeekFromCurrent );
266 }
267 }
268 break;
269 }
270
271 // remove last identifier from current path
272 currentPath.remove();
273
274 // update current file position
275 filePos = stream->Offset();
276
277 // skip pad byte if there is one (if size odd)
278 if( filePos < mFileSize &&
279 ( ( chunkJump && ( stream->Offset() & 1 ) > 0 ) ||
280 ( !chunkJump && ( chunk->getSize() & 1 ) > 0 ) ) )
281 {
282 stream->Seek ( 1 , kXMP_SeekFromCurrent );
283 filePos++;
284 }
285 }
286 }
287 }
288
289
290 //-----------------------------------------------------------------------------
291 //
292 // ChunkController::parseFile(...)
293 //
294 // Purpose: construct the tree, parse children for list of interesting Chunks
295 // All requested leaf chunks are cached, the parent chunks are created
296 // but not cached and the rest is skipped
297 //
298 //-----------------------------------------------------------------------------
299
parseFile(XMP_IO * stream,XMP_OptionBits * options)300 void ChunkController::parseFile( XMP_IO* stream, XMP_OptionBits* options /* = NULL */ )
301 {
302 // store file information in root node
303 mFileSize = stream ->Length();
304 ChunkPath currentPath;
305
306 // Make sure the tree is clean before parsing
307 cleanupTree();
308
309 try
310 {
311 parseChunks( stream, currentPath, options, dynamic_cast<Chunk*>(mRoot) );
312 }
313 catch( ... )
314 {
315 this->cleanupTree();
316 throw;
317 }
318 }
319
320
321 //-----------------------------------------------------------------------------
322 //
323 // ChunkController::writeFile(...)
324 //
325 // Purpose: Called by the handler to write back the changes to the file.
326 //
327 //-----------------------------------------------------------------------------
writeFile(XMP_IO * stream,XMP_ProgressTracker * progressTracker)328 void ChunkController::writeFile( XMP_IO* stream ,XMP_ProgressTracker * progressTracker )
329
330 {
331 //
332 // if any of the top-level chunks exceeds their maximum size then skip writing and throw an exception
333 //
334 for( XMP_Uns32 i=0; i<mRoot->numChildren(); i++ )
335 {
336 Chunk* toplevel = mRoot->getChildAt(i);
337 XMP_Validate( toplevel->getSize() < mChunkBehavior->getMaxChunkSize(), "Exceeded maximum chunk size.", kXMPErr_AssertFailure );
338 }
339
340 //
341 // if exception is thrown write chunk is skipped
342 //
343 mChunkBehavior->fixHierarchy(*mRoot);
344
345 if (mRoot->numChildren() > 0)
346 {
347 // The new file size (without trailing garbage) is the offset of the last top-level chunk + its size.
348 // NOTE: the padding bytes can be ignored, as the top-level chunk is always a node, not a leaf.
349 Chunk* lastChild = mRoot->getChildAt(mRoot->numChildren() - 1);
350 XMP_Uns64 newFileSize = lastChild->getOffset() + lastChild->getSize(true);
351 if ( progressTracker != 0 )
352 {
353 float fileWriteSize=0.0f;
354 for( XMP_Uns32 i = 0; i < mRoot->numChildren(); i++ )
355 {
356 Chunk* child = mRoot->getChildAt(i);
357 fileWriteSize+=child->calculateWriteSize( );
358 }
359 XMP_Assert ( progressTracker->WorkInProgress() );
360 progressTracker->AddTotalWork ( fileWriteSize );
361 }
362
363 // Move garbage tail after last top-level chunk,
364 // BEFORE the chunks are written -- in case the file shrinks
365 if (mTrailingGarbageSize > 0 && newFileSize != mTrailingGarbageOffset)
366 {
367 if ( progressTracker != 0 )
368 {
369 XMP_Assert ( progressTracker->WorkInProgress() );
370 progressTracker->AddTotalWork ( (float)mTrailingGarbageSize );
371 }
372 XIO::Move( stream, mTrailingGarbageOffset, stream, newFileSize, mTrailingGarbageSize );
373 newFileSize += mTrailingGarbageSize;
374 }
375
376 // Write changed and new chunks to the file
377 for( XMP_Uns32 i = 0; i < mRoot->numChildren(); i++ )
378 {
379 Chunk* child = mRoot->getChildAt(i);
380 child->writeChunk( stream );
381 }
382
383 // file has been completely written,
384 // truncate the file it has been bigger before
385 if (newFileSize < mFileSize)
386 {
387 stream->Truncate ( newFileSize );
388 }
389 }
390 }
391
392 //-----------------------------------------------------------------------------
393 //
394 // ChunkController::getChunk(...)
395 //
396 // Purpose: returns a certain Chunk
397 //
398 //-----------------------------------------------------------------------------
399
getChunk(const ChunkPath & path,XMP_Bool last) const400 IChunkData* ChunkController::getChunk( const ChunkPath& path, XMP_Bool last ) const
401 {
402 IChunkData* ret = NULL;
403
404 if( path.length() > 0 )
405 {
406 ChunkPath current;
407 ret = this->findChunk( path, current, *(dynamic_cast<Chunk*>(mRoot)), last );
408 }
409
410 return ret;
411 }
412
413
414 //-----------------------------------------------------------------------------
415 //
416 // ChunkController::findChunk(...)
417 //
418 // Purpose: Find a chunk described by path in the hierarchy of chunks starting
419 // at the passed chunk.
420 // The position of chunk in the hierarchy is described by the parameter
421 // currentPath.
422 // This method is supposed to be recursively.
423 //
424 //-----------------------------------------------------------------------------
425
findChunk(const ChunkPath & path,ChunkPath & currentPath,const Chunk & chunk,XMP_Bool last) const426 Chunk* ChunkController::findChunk( const ChunkPath& path, ChunkPath& currentPath, const Chunk& chunk, XMP_Bool last ) const
427 {
428 Chunk* ret = NULL;
429 XMP_Uns32 cnt = 0;
430
431 if( path.length() > currentPath.length() )
432 {
433 for( XMP_Uns32 i=0; i<chunk.numChildren() && ret == NULL; i++ )
434 {
435 //if last is true go backwards
436 last ? cnt=chunk.numChildren()-1-i : cnt=i;
437
438 Chunk* child = NULL;
439
440 try
441 {
442 child = chunk.getChildAt(cnt);
443 }
444 catch(...)
445 {
446 child = NULL;
447 }
448
449 if( child != NULL )
450 {
451 currentPath.append( child->getIdentifier() );
452
453 switch( path.match( currentPath ) )
454 {
455 case ChunkPath::kFullMatch:
456 {
457 ret = child;
458 }
459 break;
460
461 case ChunkPath::kPartMatch:
462 {
463 ret = this->findChunk( path, currentPath, *child, last );
464 }
465 break;
466
467 case ChunkPath::kNoMatch:
468 {
469 // Nothing to do
470 }
471 break;
472 }
473
474 currentPath.remove();
475 }
476 }
477 }
478
479 return ret;
480 }
481
482
483 //-----------------------------------------------------------------------------
484 //
485 // ChunkController::getChunks(...)
486 //
487 // Purpose: Returns all chunks that match completely to the passed path.
488 //
489 //-----------------------------------------------------------------------------
490
getChunks(const ChunkPath & path)491 const std::vector<IChunkData*>& ChunkController::getChunks( const ChunkPath& path )
492 {
493 mSearchResults.clear();
494
495 if( path.length() > 0 )
496 {
497 ChunkPath current;
498 this->findChunks( path, current, *(dynamic_cast<Chunk*>(mRoot)) );
499 }
500
501 return mSearchResults;
502 }//getChunks
503
504
505 //-----------------------------------------------------------------------------
506 //
507 // ChunkController::getTopLevelTypes(...)
508 //
509 // Purpose: Return an array containing the types of the top level nodes
510 // Top level nodes are the ones beneath ROOT
511 //
512 //-----------------------------------------------------------------------------
513
getTopLevelTypes()514 const std::vector<XMP_Uns32> ChunkController::getTopLevelTypes()
515 {
516 std::vector<XMP_Uns32> typeList;
517
518 for( XMP_Uns32 i = 0; i < mRoot->numChildren(); i++ )
519 {
520 typeList.push_back( mRoot->getChildAt( i )->getType() );
521 }
522
523 return typeList;
524 }// getTopLevelTypes
525
526
527 //-----------------------------------------------------------------------------
528 //
529 // ChunkController::findChunks(...)
530 //
531 // Purpose: Find all chunks described by path in the hierarchy of chunks starting
532 // at the passed chunk.
533 // The position of chunks in the hierarchy is described by the parameter
534 // currentPath. Found chunks that match to the path are stored in the
535 // member mSearchResults.
536 // This method is supposed to be recursively.
537 //
538 //-----------------------------------------------------------------------------
539
findChunks(const ChunkPath & path,ChunkPath & currentPath,const Chunk & chunk)540 void ChunkController::findChunks( const ChunkPath& path, ChunkPath& currentPath, const Chunk& chunk )
541 {
542 if( path.length() > currentPath.length() )
543 {
544 for( XMP_Uns32 i=0; i<chunk.numChildren(); i++ )
545 {
546 Chunk* child = NULL;
547
548 try
549 {
550 child = chunk.getChildAt(i);
551 }
552 catch(...)
553 {
554 child = NULL;
555 }
556
557 if( child != NULL )
558 {
559 currentPath.append( child->getIdentifier() );
560
561 switch( path.match( currentPath ) )
562 {
563 case ChunkPath::kFullMatch:
564 {
565 mSearchResults.push_back( child );
566 }
567 break;
568
569 case ChunkPath::kPartMatch:
570 {
571 this->findChunks( path, currentPath, *child );
572 }
573 break;
574
575 case ChunkPath::kNoMatch:
576 {
577 // Nothing to do
578 }
579 break;
580 }
581
582 currentPath.remove();
583 }
584 }
585 }
586 }//findChunks
587
588
589 //-----------------------------------------------------------------------------
590 //
591 // ChunkController::cleanupTree(...)
592 //
593 // Purpose: Cleanup function called from destructor and in case of an exception
594 //
595 //-----------------------------------------------------------------------------
596
cleanupTree()597 void ChunkController::cleanupTree()
598 {
599 XMP_Validate( mRoot != NULL, "ERROR inserting Chunk. mRoot is NULL.", kXMPErr_InternalFailure );
600 XMP_Assert(dynamic_cast<Chunk*>(mRoot) == static_cast<Chunk*>(mRoot));
601 delete dynamic_cast<Chunk*>(mRoot);
602 mRoot = Chunk::createChunk(*mEndian);
603 }
604
605
606 //-----------------------------------------------------------------------------
607 //
608 // ChunkController::dumpTree(...)
609 //
610 // Purpose: dumps the tree structure
611 //
612 //-----------------------------------------------------------------------------
613
dumpTree()614 std::string ChunkController::dumpTree( )
615 {
616 std::string ret;
617 char buffer[256];
618
619 if ( mRoot != NULL )
620 {
621 ret = mRoot->toString();
622 }
623
624 if ( mTrailingGarbageSize != 0 )
625 {
626 snprintf( buffer, 255, "\n Trailing Bytes: %llu", (long long unsigned)mTrailingGarbageSize );
627
628 std::string str(buffer);
629 ret.append(str);
630 }
631 return ret;
632 }
633
634 //-----------------------------------------------------------------------------
635 //
636 // ChunkController::createChunk(...)
637 //
638 // Purpose: Create a new empty chunk
639 //
640 //-----------------------------------------------------------------------------
641
createChunk(XMP_Uns32 id,XMP_Uns32 type)642 IChunkData* ChunkController::createChunk( XMP_Uns32 id, XMP_Uns32 type /*= kType_NONE*/ )
643 {
644 Chunk* chunk = Chunk::createChunk(* mEndian );
645
646 chunk->setID( id );
647 if( type != kType_NONE )
648 {
649 chunk->setType( type );
650 }
651
652 return chunk;
653 }
654
655 //-----------------------------------------------------------------------------
656 //
657 // ChunkController::insertChunk(...)
658 //
659 // Purpose: Insert a new chunk. The position of this new chunk within the
660 // hierarchy is determined internally by the behavior.
661 // Throws an exception if a chunk cannot be inserted into the tree
662 //
663 //-----------------------------------------------------------------------------
664
insertChunk(IChunkData * chunk)665 void ChunkController::insertChunk( IChunkData* chunk )
666 {
667 XMP_Validate( chunk != NULL, "ERROR inserting Chunk. Chunk is NULL.", kXMPErr_InternalFailure );
668 XMP_Assert(dynamic_cast<Chunk*>(chunk) == static_cast<Chunk*>(chunk));
669
670 Chunk* ch = dynamic_cast<Chunk*>(chunk);
671 mChunkBehavior->insertChunk( *mRoot, *ch );
672 // sets OriginalSize = Size / OriginalOffset = Offset
673 ch->setAsNew();
674 // force set dirty flag
675 ch->setChanged();
676 }
677
678 //-----------------------------------------------------------------------------
679 //
680 // ChunkController::removeChunk(...)
681 //
682 // Purpose: Delete a chunk or remove/delete it from the tree.
683 // If the chunk exists within the chunk hierarchy the chunk gets removed
684 // from the tree and deleted.
685 // If it is not in the tree, then it is only destroyed.
686 //
687 //-----------------------------------------------------------------------------
688
removeChunk(IChunkData * chunk)689 void ChunkController::removeChunk( IChunkData* chunk )
690 {
691 if( chunk != NULL )
692 {
693 Chunk* chk = dynamic_cast<Chunk*>(chunk);
694
695 if( this->isInTree( chk ) )
696 {
697 if( mChunkBehavior->removeChunk( *mRoot, *chk ) )
698 {
699 delete chk;
700 }
701 }
702 else
703 {
704 delete chk;
705 }
706 }
707 }
708
709 //-----------------------------------------------------------------------------
710 //
711 // ChunkController::isInTree(...)
712 //
713 // Purpose: return true if the passed in Chunk is part of the Chunk tree
714 //
715 //-----------------------------------------------------------------------------
716
isInTree(Chunk * chunk)717 bool ChunkController::isInTree( Chunk* chunk )
718 {
719 bool ret = ( mRoot == chunk );
720
721 if( !ret && chunk != NULL )
722 {
723 Chunk* parent = chunk->getParent();
724
725 while( !ret && parent != NULL )
726 {
727 ret = ( mRoot == parent );
728 parent = parent->getParent();
729 }
730 }
731
732 return ret;
733 }
734