void GIF_MetaHandler::WriteTempFile ( XMP_IO* tempRef ) { XMP_Assert( this->needsUpdate ); XMP_IO* originalRef = this->parent->ioRef; originalRef->Rewind(); tempRef->Truncate ( 0 ); if ( XMPPacketOffset != 0 ) { // Copying blocks before XMP Application Block XIO::Copy( originalRef, tempRef, XMPPacketOffset ); // Writing XMP Packet tempRef->Write( this->xmpPacket.c_str(), (XMP_Uns32)this->xmpPacket.size() ); // Copying Rest of the file originalRef->Seek( XMPPacketLength, kXMP_SeekFromCurrent ); XIO::Copy( originalRef, tempRef, originalRef->Length() - originalRef->Offset() ); } else { if ( trailerOffset == 0 ) XMP_Throw( "Not able to write XMP packet in GIF file", kXMPErr_BadFileFormat ); // Copying blocks before XMP Application Block XIO::Copy( originalRef, tempRef, trailerOffset ); // Writing Extension Introducer XIO::WriteUns8( tempRef, kXMP_block_Extension ); // Writing Application Extension label XIO::WriteUns8( tempRef, 0xFF ); // Writing Application Extension label XIO::WriteUns8( tempRef, APP_ID_LEN ); // Writing Application Extension label tempRef->Write( XMP_APP_ID_DATA, APP_ID_LEN ); // Writing XMP Packet tempRef->Write( this->xmpPacket.c_str(), (XMP_Uns32)this->xmpPacket.size() ); // Writing Magic trailer XMP_Uns8 magicByte = 0x01; tempRef->Write( &magicByte, 1 ); for ( magicByte = 0xFF; magicByte != 0x00; --magicByte ) tempRef->Write( &magicByte, 1 ); tempRef->Write( &magicByte, 1 ); tempRef->Write( &magicByte, 1 ); // Copying Rest of the file XIO::Copy( originalRef, tempRef, originalRef->Length() - originalRef->Offset() ); } } // GIF_MetaHandler::WriteTempFile
void RIFF_MetaHandler::CacheFileData() { this->containsXMP = false; //assume for now XMP_IO* file = this->parent->ioRef; this->oldFileSize = file ->Length(); if ( (this->parent->format == kXMP_WAVFile) && (this->oldFileSize > 0xFFFFFFFF) ) XMP_Throw ( "RIFF_MetaHandler::CacheFileData: WAV Files larger 4GB not supported", kXMPErr_Unimplemented ); file ->Rewind(); this->level = 0; // parse top-level chunks (most likely just one, except large avi files) XMP_Int64 filePos = 0; while ( filePos < this->oldFileSize ) { this->riffChunks.push_back( (RIFF::ContainerChunk*) RIFF::getChunk( NULL, this ) ); // Tolerate limited forms of trailing garbage in a file. Some apps append private data. filePos = file->Offset(); XMP_Int64 fileTail = this->oldFileSize - filePos; if ( fileTail != 0 ) { if ( fileTail < 12 ) { this->oldFileSize = filePos; // Pretend the file is smaller. this->trailingGarbageSize = fileTail; } else if ( this->parent->format == kXMP_WAVFile ) { if ( fileTail < 1024*1024 ) { this->oldFileSize = filePos; // Pretend the file is smaller. this->trailingGarbageSize = fileTail; } else { XMP_Throw ( "Excessive garbage at end of file", kXMPErr_BadFileFormat ) } } else { XMP_Int32 chunkInfo [3]; file->ReadAll ( &chunkInfo, 12 ); file->Seek ( -12, kXMP_SeekFromCurrent ); if ( (GetUns32LE ( &chunkInfo[0] ) != RIFF::kChunk_RIFF) || (GetUns32LE ( &chunkInfo[2] ) != RIFF::kType_AVIX) ) { if ( fileTail < 1024*1024 ) { this->oldFileSize = filePos; // Pretend the file is smaller. this->trailingGarbageSize = fileTail; } else { XMP_Throw ( "Excessive garbage at end of file", kXMPErr_BadFileFormat ) } } } } }
void WEBP_MetaHandler::CacheFileData() { this->containsXMP = false; // assume for now XMP_IO* file = this->parent->ioRef; this->initialFileSize = file->Length(); file->Rewind(); XMP_Int64 filePos = 0; while (filePos < this->initialFileSize) { this->mainChunk = new WEBP::Container(this); filePos = file->Offset(); } // covered before => internal error if it occurs XMP_Validate(file->Offset() == this->initialFileSize, "WEBP_MetaHandler::CacheFileData: unknown data at end of file", kXMPErr_InternalFailure); }
// parsing creation Chunk::Chunk( ContainerChunk* parent_, RIFF_MetaHandler* handler, bool skip, ChunkType c ) { chunkType = c; // base class assumption this->parent = parent_; this->oldSize = 0; this->hasChange = false; // [2414649] valid assumption at creation time XMP_IO* file = handler->parent->ioRef; this->oldPos = file->Offset(); this->id = XIO::ReadUns32_LE( file ); this->oldSize = XIO::ReadUns32_LE( file ); this->oldSize += 8; // Make sure the size is within expected bounds. XMP_Int64 chunkEnd = this->oldPos + this->oldSize; XMP_Int64 chunkLimit = handler->oldFileSize; if ( parent_ != 0 ) chunkLimit = parent_->oldPos + parent_->oldSize; if ( chunkEnd > chunkLimit ) { bool isUpdate = XMP_OptionIsSet ( handler->parent->openFlags, kXMPFiles_OpenForUpdate ); bool repairFile = XMP_OptionIsSet ( handler->parent->openFlags, kXMPFiles_OpenRepairFile ); if ( (! isUpdate) || (repairFile && (parent_ == 0)) ) { this->oldSize = chunkLimit - this->oldPos; } else { XMP_Throw ( "Bad RIFF chunk size", kXMPErr_BadFileFormat ); } } this->newSize = this->oldSize; this->needSizeFix = false; if ( skip ) file->Seek ( (this->oldSize - 8), kXMP_SeekFromCurrent ); // "good parenting", essential for latter destruction. if ( this->parent != NULL ) { this->parent->children.push_back( this ); if( this->chunkType == chunk_VALUE ) this->parent->childmap.insert( std::make_pair( this->id, (ValueChunk*) this ) ); } }
// b) parsing ContainerChunk::ContainerChunk( ContainerChunk* parent, RIFF_MetaHandler* handler ) : Chunk( parent, handler, false, chunk_CONTAINER ) { bool repairMode = ( 0 != ( handler->parent->openFlags & kXMPFiles_OpenRepairFile )); try { XMP_IO* file = handler->parent->ioRef; XMP_Uns8 level = handler->level; // get type of container chunk this->containerType = XIO::ReadUns32_LE( file ); // ensure legality of top-level chunks if ( level == 0 && handler->riffChunks.size() > 0 ) { XMP_Validate( handler->parent->format == kXMP_AVIFile, "only AVI may have multiple top-level chunks", kXMPErr_BadFileFormat ); XMP_Validate( this->containerType == kType_AVIX, "all chunks beyond main chunk must be type AVIX", kXMPErr_BadFileFormat ); } // has *relevant* subChunks? (there might be e.g. non-INFO LIST chunks we don't care about) bool hasSubChunks = ( ( this->id == kChunk_RIFF ) || ( this->id == kChunk_LIST && this->containerType == kType_INFO ) || ( this->id == kChunk_LIST && this->containerType == kType_Tdat ) || ( this->id == kChunk_LIST && this->containerType == kType_hdrl ) ); XMP_Int64 endOfChunk = this->oldPos + this->oldSize; // this statement catches beyond-EoF-offsets on any level // exception: level 0, tolerate if in repairMode if ( (level == 0) && repairMode && (endOfChunk > handler->oldFileSize) ) { endOfChunk = handler->oldFileSize; // assign actual file size this->oldSize = endOfChunk - this->oldPos; //reversely calculate correct oldSize } XMP_Validate( endOfChunk <= handler->oldFileSize, "offset beyond EoF", kXMPErr_BadFileFormat ); Chunk* curChild = 0; if ( hasSubChunks ) { handler->level++; while ( file->Offset() < endOfChunk ) { curChild = RIFF::getChunk( this, handler ); // digest pad byte - no value validation (0), since some 3rd party files have non-0-padding. if ( file->Offset() % 2 == 1 ) { // [1521093] tolerate missing pad byte at very end of file: XMP_Uns8 pad; file->Read ( &pad, 1 ); // Read the pad, tolerate being at EOF. } // within relevant LISTs, relentlesly delete junk chunks (create a single one // at end as part of updateAndChanges() if ( (containerType== kType_INFO || containerType == kType_Tdat) && ( curChild->chunkType == chunk_JUNK ) ) { this->children.pop_back(); delete curChild; } // for other chunks: join neighouring Junk chunks into one else if ( (curChild->chunkType == chunk_JUNK) && ( this->children.size() >= 2 ) ) { // nb: if there are e.g 2 chunks, then last one is at(1), prev one at(0) ==> '-2' Chunk* prevChunk = this->children.at( this->children.size() - 2 ); if ( prevChunk->chunkType == chunk_JUNK ) { // stack up size to prior chunk prevChunk->oldSize += curChild->oldSize; prevChunk->newSize += curChild->newSize; XMP_Enforce( prevChunk->oldSize == prevChunk->newSize ); // destroy current chunk this->children.pop_back(); delete curChild; } } } handler->level--; XMP_Validate( file->Offset() == endOfChunk, "subchunks exceed outer chunk size", kXMPErr_BadFileFormat ); // pointers for later legacy processing if ( level==1 && this->id==kChunk_LIST && this->containerType == kType_INFO ) handler->listInfoChunk = this; if ( level==1 && this->id==kChunk_LIST && this->containerType == kType_Tdat ) handler->listTdatChunk = this; if ( level == 1 && this->id == kChunk_LIST && this->containerType == kType_hdrl ) handler->listHdlrChunk = this; } else // skip non-interest container chunk { file->Seek ( (this->oldSize - 8 - 4), kXMP_SeekFromCurrent ); } // if - else } // try catch (XMP_Error& e) { this->release(); // free resources if ( this->parent != 0) this->parent->children.pop_back(); // hereby taken care of, so removing myself... throw e; // re-throw } }
// ================================================================================================= // SVG_MetaHandler::WriteTempFile // ============================== // void SVG_MetaHandler::WriteTempFile( XMP_IO* tempRef ) { XMP_Assert( this->needsUpdate ); XMP_IO* sourceRef = this->parent->ioRef; if ( sourceRef == NULL || svgNode == NULL ) return; tempRef->Rewind(); sourceRef->Rewind(); XMP_Int64 currentOffset = svgAdapter->firstSVGElementOffset; XIO::Copy( sourceRef, tempRef, currentOffset ); OffsetStruct titleOffset = svgAdapter->GetElementOffsets( "title" ); OffsetStruct descOffset = svgAdapter->GetElementOffsets( "desc" ); OffsetStruct metadataOffset = svgAdapter->GetElementOffsets( "metadata" ); std::string title; std::string description; XML_NodePtr titleNode = svgNode->GetNamedElement( svgNode->ns.c_str(), "title" ); ( void ) this->xmpObj.GetLocalizedText( kXMP_NS_DC, "title", "", "x-default", 0, &title, 0 ); XML_NodePtr descNode = svgNode->GetNamedElement( svgNode->ns.c_str(), "desc" ); ( void ) this->xmpObj.GetLocalizedText( kXMP_NS_DC, "description", "", "x-default", 0, &description, 0 ); // Need to cover the case of both workflows // This would have been called after inplace is not possible // This would have called for safe update if ( !isTitleUpdateReq ) { if ( ( titleNode == NULL ) == ( title.empty() ) ) { if ( titleNode != NULL && titleNode->content.size() == 1 && titleNode->content[ 0 ]->kind == kCDataNode && !XMP_LitMatch( titleNode->content[ 0 ]->value.c_str(), title.c_str() ) ) isTitleUpdateReq = true; } else isTitleUpdateReq = true; } if ( !isDescUpdateReq ) { if ( ( descNode == NULL ) == ( description.empty() ) ) { if ( descNode != NULL && descNode->content.size() == 1 && descNode->content[ 0 ]->kind == kCDataNode && !XMP_LitMatch( descNode->content[ 0 ]->value.c_str(), description.c_str() ) ) isDescUpdateReq = true; } else isDescUpdateReq = true; } // Initial Insertion/Updation // Insert/Update Title if requires // Don't insert/update it if Metadata or desc child comes before title child bool isTitleWritten = !isTitleUpdateReq; if ( isTitleUpdateReq ) { // Insertion Case if ( titleNode == NULL ) { InsertNewTitle( tempRef, title ); isTitleWritten = true; } else if ( ( descOffset.startOffset == -1 || titleOffset.startOffset < descOffset.startOffset ) // Updation/Deletion Case && ( metadataOffset.startOffset == -1 || titleOffset.startOffset < metadataOffset.startOffset ) ) { ProcessTitle( sourceRef, tempRef, title, currentOffset, titleOffset ); isTitleWritten = true; } } // Insert/Update Description if requires // Don't insert/update it if Metadata child comes before desc child bool isDescWritten = !isDescUpdateReq; if ( isDescUpdateReq ) { if ( descNode == NULL ) { if ( titleOffset.nextOffset != -1 ) { XIO::Copy( sourceRef, tempRef, titleOffset.nextOffset - currentOffset ); currentOffset = titleOffset.nextOffset; } InsertNewDescription( tempRef, description ); isDescWritten = true; } else if ( metadataOffset.startOffset == -1 || descOffset.startOffset < metadataOffset.startOffset ) { ProcessDescription( sourceRef, tempRef, description, currentOffset, descOffset ); isDescWritten = true; } } // Insert/Update Metadata if requires // Don't insert/update it if case is DTM bool isMetadataWritten = false; if ( metadataOffset.startOffset == -1 ) { if ( descOffset.nextOffset != -1 ) { XIO::Copy( sourceRef, tempRef, descOffset.nextOffset - currentOffset ); currentOffset = descOffset.nextOffset; } else if ( titleOffset.nextOffset != -1 ) { XIO::Copy( sourceRef, tempRef, titleOffset.nextOffset - currentOffset ); currentOffset = titleOffset.nextOffset; } InsertNewMetadata( tempRef, this->xmpPacket ); isMetadataWritten = true; } else if ( !( !isTitleWritten && isDescWritten && titleOffset.startOffset < metadataOffset.startOffset ) ) // Not DTM { // No XMP packet was present in the file if ( this->packetInfo.offset == kXMPFiles_UnknownOffset ) { std::string metadataElement = "<metadata>"; XIO::Copy( sourceRef, tempRef, metadataOffset.startOffset - currentOffset + metadataElement.length() ); currentOffset = sourceRef->Offset(); tempRef->Write( this->xmpPacket.c_str(), static_cast< int >( this->xmpPacket.length() ) ); } else // Replace XMP Packet { XIO::Copy( sourceRef, tempRef, this->packetInfo.offset - currentOffset ); tempRef->Write( this->xmpPacket.c_str(), static_cast< int >( this->xmpPacket.length() ) ); sourceRef->Seek( this->packetInfo.offset + this->packetInfo.length, kXMP_SeekFromStart ); currentOffset = sourceRef->Offset(); } isMetadataWritten = true; } // If simple cases was followed then copy rest file if ( isTitleWritten && isDescWritten && isMetadataWritten ) { XIO::Copy( sourceRef, tempRef, ( sourceRef->Length() - currentOffset ) ); return; } // If the case is not Simple (TDM) then perform these operations if ( isDescWritten ) // TDM, DTM, DMT { if ( !isTitleWritten ) // DTM, DMT { if ( titleOffset.startOffset < metadataOffset.startOffset ) // DTM { ProcessTitle( sourceRef, tempRef, title, currentOffset, titleOffset ); isTitleWritten = true; if ( this->packetInfo.offset == kXMPFiles_UnknownOffset ) { std::string metadataElement = "<metadata>"; XIO::Copy( sourceRef, tempRef, metadataOffset.startOffset - currentOffset + metadataElement.length() ); currentOffset = sourceRef->Offset(); tempRef->Write( this->xmpPacket.c_str(), static_cast< int >( this->xmpPacket.length() ) ); } else { XIO::Copy( sourceRef, tempRef, this->packetInfo.offset - currentOffset ); tempRef->Write( this->xmpPacket.c_str(), static_cast< int >( this->xmpPacket.length() ) ); sourceRef->Seek( this->packetInfo.offset + this->packetInfo.length, kXMP_SeekFromStart ); currentOffset = sourceRef->Offset(); } isMetadataWritten = true; } else // DMT { ProcessTitle( sourceRef, tempRef, title, currentOffset, titleOffset ); isTitleWritten = true; } } // Else // Would have already covered this case: TDM } else // TMD, MDT, MTD { if ( isTitleWritten ) // TMD { ProcessDescription( sourceRef, tempRef, description, currentOffset, descOffset ); isDescWritten = true; } else // MDT or MTD { if ( titleOffset.startOffset < descOffset.startOffset ) // MTD { ProcessTitle( sourceRef, tempRef, title, currentOffset, titleOffset ); isTitleWritten = true; ProcessDescription( sourceRef, tempRef, description, currentOffset, descOffset ); isDescWritten = true; } else // MDT { ProcessDescription( sourceRef, tempRef, description, currentOffset, descOffset ); isDescWritten = true; ProcessTitle( sourceRef, tempRef, title, currentOffset, titleOffset ); isTitleWritten = true; } } } // Finally Everything would have been written XMP_Enforce( isTitleWritten && isDescWritten && isMetadataWritten ); XIO::Copy( sourceRef, tempRef, ( sourceRef->Length() - currentOffset ) ); this->needsUpdate = false; } // SVG_MetaHandler::WriteTempFile
void JPEG_MetaHandler::WriteTempFile ( XMP_IO* tempRef ) { XMP_IO* origRef = this->parent->ioRef; XMP_AbortProc abortProc = this->parent->abortProc; void * abortArg = this->parent->abortArg; const bool checkAbort = (abortProc != 0); XMP_Uns16 marker, contentLen; static const size_t kBufferSize = 64*1024; // Enough for a segment with maximum contents. XMP_Uns8 buffer [kBufferSize]; XMP_Int64 origLength = origRef->Length(); if ( origLength == 0 ) return; // Tolerate empty files. if ( origLength < 4 ) { XMP_Throw ( "JPEG must have at least SOI and EOI markers", kXMPErr_BadJPEG ); } if ( ! skipReconcile ) { // Update the IPTC-IIM and native TIFF/Exif metadata, and reserialize the now final XMP packet. ExportPhotoData ( kXMP_JPEGFile, &this->xmpObj, this->exifMgr, this->iptcMgr, this->psirMgr ); this->xmpObj.SerializeToBuffer ( &this->xmpPacket, kXMP_UseCompactFormat ); } origRef->Rewind(); tempRef->Truncate ( 0 ); marker = XIO::ReadUns16_BE ( origRef ); // Just read the SOI marker. if ( marker != 0xFFD8 ) XMP_Throw ( "Missing SOI marker", kXMPErr_BadJPEG ); XIO::WriteUns16_BE ( tempRef, marker ); // Copy any leading APP0 marker segments. while ( true ) { if ( checkAbort && abortProc(abortArg) ) { XMP_Throw ( "JPEG_MetaHandler::WriteFile - User abort", kXMPErr_UserAbort ); } if ( ! XIO::CheckFileSpace ( origRef, 2 ) ) break; // Tolerate a file that ends abruptly. marker = XIO::ReadUns16_BE ( origRef ); // Read the next marker. if ( marker == 0xFFFF ) { // Have a pad byte, skip it. These are almost unheard of, so efficiency isn't critical. origRef->Seek ( -1, kXMP_SeekFromCurrent ); // Skip the first 0xFF, read the second again. continue; } if ( marker != 0xFFE0 ) break; // Have a non-APP0 marker. XIO::WriteUns16_BE ( tempRef, marker ); // Write the APP0 marker. contentLen = XIO::ReadUns16_BE ( origRef ); // Copy the APP0 segment's length. XIO::WriteUns16_BE ( tempRef, contentLen ); if ( contentLen < 2 ) XMP_Throw ( "Invalid JPEG segment length", kXMPErr_BadJPEG ); contentLen -= 2; // Reduce to just the content length. origRef->ReadAll ( buffer, contentLen ); // Copy the APP0 segment's content. tempRef->Write ( buffer, contentLen ); } // Write the new Exif APP1 marker segment. XMP_Uns32 first4; if ( this->exifMgr != 0 ) { void* exifPtr; XMP_Uns32 exifLen = this->exifMgr->UpdateMemoryStream ( &exifPtr ); if ( exifLen > kExifMaxDataLength ) exifLen = this->exifMgr->UpdateMemoryStream ( &exifPtr, true /* compact */ ); while ( exifLen > 0 ) { XMP_Uns32 count = std::min ( exifLen, (XMP_Uns32) kExifMaxDataLength ); first4 = MakeUns32BE ( 0xFFE10000 + 2 + kExifSignatureLength + count ); tempRef->Write ( &first4, 4 ); tempRef->Write ( kExifSignatureString, kExifSignatureLength ); tempRef->Write ( exifPtr, count ); exifPtr = (XMP_Uns8 *) exifPtr + count; exifLen -= count; } } // Write the new XMP APP1 marker segment, with possible extension marker segments. std::string mainXMP, extXMP, extDigest; SXMPUtils::PackageForJPEG ( this->xmpObj, &mainXMP, &extXMP, &extDigest ); XMP_Assert ( (extXMP.size() == 0) || (extDigest.size() == 32) ); first4 = MakeUns32BE ( 0xFFE10000 + 2 + kMainXMPSignatureLength + (XMP_Uns32)mainXMP.size() ); tempRef->Write ( &first4, 4 ); tempRef->Write ( kMainXMPSignatureString, kMainXMPSignatureLength ); tempRef->Write ( mainXMP.c_str(), (XMP_Int32)mainXMP.size() ); size_t extPos = 0; size_t extLen = extXMP.size(); while ( extLen > 0 ) { size_t partLen = extLen; if ( partLen > 65000 ) partLen = 65000; first4 = MakeUns32BE ( 0xFFE10000 + 2 + kExtXMPPrefixLength + (XMP_Uns32)partLen ); tempRef->Write ( &first4, 4 ); tempRef->Write ( kExtXMPSignatureString, kExtXMPSignatureLength ); tempRef->Write ( extDigest.c_str(), (XMP_Int32)extDigest.size() ); first4 = MakeUns32BE ( (XMP_Int32)extXMP.size() ); tempRef->Write ( &first4, 4 ); first4 = MakeUns32BE ( (XMP_Int32)extPos ); tempRef->Write ( &first4, 4 ); tempRef->Write ( &extXMP[extPos], (XMP_Int32)partLen ); extPos += partLen; extLen -= partLen; } // Write the new PSIR APP13 marker segments. if ( this->psirMgr != 0 ) { void* psirPtr; XMP_Uns32 psirLen = this->psirMgr->UpdateMemoryResources ( &psirPtr ); while ( psirLen > 0 ) { XMP_Uns32 count = std::min ( psirLen, (XMP_Uns32) kPSIRMaxDataLength ); first4 = MakeUns32BE ( 0xFFED0000 + 2 + kPSIRSignatureLength + count ); tempRef->Write ( &first4, 4 ); tempRef->Write ( kPSIRSignatureString, kPSIRSignatureLength ); tempRef->Write ( psirPtr, count ); psirPtr = (XMP_Uns8 *) psirPtr + count; psirLen -= count; } } // Copy remaining marker segments, skipping old metadata, to the first SOS marker or to EOI. origRef->Seek ( -2, kXMP_SeekFromCurrent ); // Back up to the marker from the end of the APP0 copy loop. while ( true ) { if ( checkAbort && abortProc(abortArg) ) { XMP_Throw ( "JPEG_MetaHandler::WriteFile - User abort", kXMPErr_UserAbort ); } if ( ! XIO::CheckFileSpace ( origRef, 2 ) ) break; // Tolerate a file that ends abruptly. marker = XIO::ReadUns16_BE ( origRef ); // Read the next marker. if ( marker == 0xFFFF ) { // Have a pad byte, skip it. These are almost unheard of, so efficiency isn't critical. origRef->Seek ( -1, kXMP_SeekFromCurrent ); // Skip the first 0xFF, read the second again. continue; } if ( (marker == 0xFFDA) || (marker == 0xFFD9) ) { // Quit at the first SOS marker or at EOI. origRef->Seek ( -2, kXMP_SeekFromCurrent ); // The tail copy must include this marker. break; } if ( (marker == 0xFF01) || // Ill-formed file if we encounter a TEM or RSTn marker. ((0xFFD0 <= marker) && (marker <= 0xFFD7)) ) { XMP_Throw ( "Unexpected TEM or RSTn marker", kXMPErr_BadJPEG ); } contentLen = XIO::ReadUns16_BE ( origRef ); // Read this segment's length. if ( contentLen < 2 ) XMP_Throw ( "Invalid JPEG segment length", kXMPErr_BadJPEG ); contentLen -= 2; // Reduce to just the content length. XMP_Int64 contentOrigin = origRef->Offset(); bool copySegment = true; size_t signatureLen; if ( (marker == 0xFFED) && (contentLen >= kPSIRSignatureLength) ) { // This is an APP13 segment, skip if it is the old PSIR. signatureLen = origRef->Read ( buffer, kPSIRSignatureLength ); if ( (signatureLen == kPSIRSignatureLength) && CheckBytes ( &buffer[0], kPSIRSignatureString, kPSIRSignatureLength ) ) { copySegment = false; } } else if ( (marker == 0xFFE1) && (contentLen >= kExifSignatureLength) ) { // Check for the shortest signature. // This is an APP1 segment, skip if it is the old Exif or XMP. XMP_Assert ( (kExifSignatureLength < kMainXMPSignatureLength) && (kMainXMPSignatureLength < kExtXMPSignatureLength) ); signatureLen = origRef->Read ( buffer, kExtXMPSignatureLength ); // Read for the longest signature. if ( (signatureLen >= kExifSignatureLength) && (CheckBytes ( &buffer[0], kExifSignatureString, kExifSignatureLength ) || CheckBytes ( &buffer[0], kExifSignatureAltStr, kExifSignatureLength )) ) { copySegment = false; } if ( copySegment && (signatureLen >= kMainXMPSignatureLength) && CheckBytes ( &buffer[0], kMainXMPSignatureString, kMainXMPSignatureLength ) ) { copySegment = false; } if ( copySegment && (signatureLen == kExtXMPSignatureLength) && CheckBytes ( &buffer[0], kExtXMPSignatureString, kExtXMPPrefixLength ) ) { copySegment = false; } } if ( ! copySegment ) { origRef->Seek ( (contentOrigin + contentLen), kXMP_SeekFromStart ); } else { XIO::WriteUns16_BE ( tempRef, marker ); XIO::WriteUns16_BE ( tempRef, (contentLen + 2) ); origRef->Seek ( contentOrigin, kXMP_SeekFromStart ); origRef->ReadAll ( buffer, contentLen ); tempRef->Write ( buffer, contentLen ); } } // Copy the remainder of the source file. XIO::Copy ( origRef, tempRef, (origLength - origRef->Offset()) ); this->needsUpdate = false; } // JPEG_MetaHandler::WriteTempFile
void JPEG_MetaHandler::CacheFileData() { XMP_IO* fileRef = this->parent->ioRef; XMP_PacketInfo & packetInfo = this->packetInfo; static const size_t kBufferSize = 64*1024; // Enough for maximum segment contents. XMP_Uns8 buffer [kBufferSize]; psirContents.clear(); exifContents.clear(); XMP_AbortProc abortProc = this->parent->abortProc; void * abortArg = this->parent->abortArg; const bool checkAbort = (abortProc != 0); ExtendedXMPInfo extXMP; XMP_Assert ( ! this->containsXMP ); // Set containsXMP to true here only if the standard XMP packet is found. XMP_Assert ( kPSIRSignatureLength == (strlen(kPSIRSignatureString) + 1) ); XMP_Assert ( kMainXMPSignatureLength == (strlen(kMainXMPSignatureString) + 1) ); XMP_Assert ( kExtXMPSignatureLength == (strlen(kExtXMPSignatureString) + 1) ); // ------------------------------------------------------------------------------------------- // Look for any of the Exif, PSIR, main XMP, or extended XMP marker segments. Quit when we hit // an SOFn, EOI, or invalid/unexpected marker. fileRef->Seek ( 2, kXMP_SeekFromStart ); // Skip the SOI, CheckFormat made sure it is present. while ( true ) { if ( checkAbort && abortProc(abortArg) ) { XMP_Throw ( "JPEG_MetaHandler::CacheFileData - User abort", kXMPErr_UserAbort ); } if ( ! XIO::CheckFileSpace ( fileRef, 2 ) ) return; // Quit, don't throw, if the file ends unexpectedly. XMP_Uns16 marker = XIO::ReadUns16_BE ( fileRef ); // Read the next marker. if ( marker == 0xFFFF ) { // Have a pad byte, skip it. These are almost unheard of, so efficiency isn't critical. fileRef->Seek ( -1, kXMP_SeekFromCurrent ); // Skip the first 0xFF, read the second again. continue; } if ( (marker == 0xFFDA) || (marker == 0xFFD9) ) break; // Quit reading at the first SOS marker or at EOI. if ( (marker == 0xFF01) || // Ill-formed file if we encounter a TEM or RSTn marker. ((0xFFD0 <= marker) && (marker <= 0xFFD7)) ) return; XMP_Uns16 contentLen = XIO::ReadUns16_BE ( fileRef ); // Read this segment's length. if ( contentLen < 2 ) XMP_Throw ( "Invalid JPEG segment length", kXMPErr_BadJPEG ); contentLen -= 2; // Reduce to just the content length. XMP_Int64 contentOrigin = fileRef->Offset(); size_t signatureLen; if ( (marker == 0xFFED) && (contentLen >= kPSIRSignatureLength) ) { // This is an APP13 marker, is it the Photoshop image resources? signatureLen = fileRef->Read ( buffer, kPSIRSignatureLength ); if ( (signatureLen == kPSIRSignatureLength) && CheckBytes ( &buffer[0], kPSIRSignatureString, kPSIRSignatureLength ) ) { size_t psirLen = contentLen - kPSIRSignatureLength; fileRef->Seek ( (contentOrigin + kPSIRSignatureLength), kXMP_SeekFromStart ); fileRef->ReadAll ( buffer, psirLen ); this->psirContents.append( (char *) buffer, psirLen ); continue; // Move on to the next marker. } } else if ( (marker == 0xFFE1) && (contentLen >= kExifSignatureLength) ) { // Check for the shortest signature. // This is an APP1 marker, is it the Exif, main XMP, or extended XMP? // ! Check in that order, which is in increasing signature string length. XMP_Assert ( (kExifSignatureLength < kMainXMPSignatureLength) && (kMainXMPSignatureLength < kExtXMPSignatureLength) ); signatureLen = fileRef->Read ( buffer, kExtXMPSignatureLength ); // Read for the longest signature. if ( (signatureLen >= kExifSignatureLength) && (CheckBytes ( &buffer[0], kExifSignatureString, kExifSignatureLength ) || CheckBytes ( &buffer[0], kExifSignatureAltStr, kExifSignatureLength )) ) { size_t exifLen = contentLen - kExifSignatureLength; fileRef->Seek ( (contentOrigin + kExifSignatureLength), kXMP_SeekFromStart ); fileRef->ReadAll ( buffer, exifLen ); this->exifContents.append ( (char*)buffer, exifLen ); continue; // Move on to the next marker. } if ( (signatureLen >= kMainXMPSignatureLength) && CheckBytes ( &buffer[0], kMainXMPSignatureString, kMainXMPSignatureLength ) ) { this->containsXMP = true; // Found the standard XMP packet. size_t xmpLen = contentLen - kMainXMPSignatureLength; fileRef->Seek ( (contentOrigin + kMainXMPSignatureLength), kXMP_SeekFromStart ); fileRef->ReadAll ( buffer, xmpLen ); this->xmpPacket.assign ( (char*)buffer, xmpLen ); this->packetInfo.offset = contentOrigin + kMainXMPSignatureLength; this->packetInfo.length = (XMP_Int32)xmpLen; this->packetInfo.padSize = 0; // Assume the rest for now, set later in ProcessXMP. this->packetInfo.charForm = kXMP_CharUnknown; this->packetInfo.writeable = true; continue; // Move on to the next marker. } if ( (signatureLen >= kExtXMPSignatureLength) && CheckBytes ( &buffer[0], kExtXMPSignatureString, kExtXMPSignatureLength ) ) { fileRef->Seek ( contentOrigin, kXMP_SeekFromStart ); fileRef->ReadAll ( buffer, contentLen ); CacheExtendedXMP ( &extXMP, buffer, contentLen ); continue; // Move on to the next marker. } } // None of the above, seek to the next marker. fileRef->Seek ( (contentOrigin + contentLen) , kXMP_SeekFromStart ); } if ( ! extXMP.empty() ) { // We have extended XMP. Find out which ones are complete, collapse them into a single // string, and save them for ProcessXMP. ExtendedXMPInfo::iterator guidPos = extXMP.begin(); ExtendedXMPInfo::iterator guidEnd = extXMP.end(); for ( ; guidPos != guidEnd; ++guidPos ) { ExtXMPContent & thisContent = guidPos->second; ExtXMPPortions::iterator partZero = thisContent.portions.begin(); ExtXMPPortions::iterator partEnd = thisContent.portions.end(); ExtXMPPortions::iterator partPos = partZero; #if Trace_UnlimitedJPEG printf ( "Extended XMP portions for GUID %.32s, full length %d\n", guidPos->first.data, guidPos->second.length ); printf ( " Offset %d, length %d, next offset %d\n", partZero->first, partZero->second.size(), (partZero->first + partZero->second.size()) ); #endif for ( ++partPos; partPos != partEnd; ++partPos ) { #if Trace_UnlimitedJPEG printf ( " Offset %d, length %d, next offset %d\n", partPos->first, partPos->second.size(), (partPos->first + partPos->second.size()) ); #endif if ( partPos->first != partZero->second.size() ) break; // Quit if not contiguous. partZero->second.append ( partPos->second ); } if ( (partPos == partEnd) && (partZero->first == 0) && (partZero->second.size() == thisContent.length) ) { // This is a complete extended XMP stream. this->extendedXMP.insert ( ExtendedXMPMap::value_type ( guidPos->first, partZero->second ) ); #if Trace_UnlimitedJPEG printf ( "Full extended XMP for GUID %.32s, full length %d\n", guidPos->first.data, partZero->second.size() ); #endif } } } } // JPEG_MetaHandler::CacheFileData
void MP3_MetaHandler::CacheFileData() { //*** abort procedures this->containsXMP = false; //assume no XMP for now XMP_IO* file = this->parent->ioRef; XMP_PacketInfo &packetInfo = this->packetInfo; file->Rewind(); this->hasID3Tag = this->id3Header.read( file ); this->majorVersion = this->id3Header.fields[ID3Header::o_vMajor]; this->minorVersion = this->id3Header.fields[ID3Header::o_vMinor]; this->hasExtHeader = (0 != ( 0x40 & this->id3Header.fields[ID3Header::o_flags])); //'naturally' false if no ID3Tag this->hasFooter = ( 0 != ( 0x10 & this->id3Header.fields[ID3Header::o_flags])); //'naturally' false if no ID3Tag // stored size is w/o initial header (thus adding 10) // + but extended header (if existing) // + padding + frames after unsynchronisation (?) // (if no ID3 tag existing, constructed default correctly sets size to 10.) this->oldTagSize = ID3Header::kID3_TagHeaderSize + synchToInt32(GetUns32BE( &id3Header.fields[ID3Header::o_size] )); if ( ! hasExtHeader ) { this->extHeaderSize = 0; // := there is no such header. } else { this->extHeaderSize = synchToInt32( XIO::ReadInt32_BE( file)); XMP_Uns8 extHeaderNumFlagBytes = XIO::ReadUns8( file ); // v2.3 doesn't include the size, while v2.4 does if ( this->majorVersion < 4 ) this->extHeaderSize += 4; XMP_Validate( this->extHeaderSize >= 6, "extHeader size too small", kXMPErr_BadFileFormat ); file->Seek ( this->extHeaderSize - 6, kXMP_SeekFromCurrent ); } this->framesVector.clear(); //mac precaution ID3v2Frame* curFrame = 0; // reusable //////////////////////////////////////////////////// // read frames XMP_Uns32 xmpID = XMP_V23_ID; if ( this->majorVersion == 2 ) xmpID = XMP_V22_ID; while ( file->Offset() < this->oldTagSize ) { curFrame = new ID3v2Frame(); try { XMP_Int64 frameSize = curFrame->read ( file, this->majorVersion ); if ( frameSize == 0 ) { delete curFrame; // ..since not becoming part of vector for latter delete. break; // not a throw. There's nothing wrong with padding. } this->containsXMP = true; } catch ( ... ) { delete curFrame; throw; } // these are both pointer assignments, no (copy) construction // (MemLeak Note: for all things pushed, memory cleanup is taken care of in destructor.) this->framesVector.push_back ( curFrame ); //remember XMP-Frame, if it occurs: if ( (curFrame->id ==xmpID) && (curFrame->contentSize > 8) && CheckBytes ( &curFrame->content[0], "XMP\0", 4 ) ) { // be sure that this is the first packet (all else would be illegal format) XMP_Validate ( this->framesMap[xmpID] == 0, "two XMP packets in one file", kXMPErr_BadFileFormat ); //add this to map, needed on reconciliation this->framesMap[xmpID] = curFrame; this->packetInfo.length = curFrame->contentSize - 4; // content minus "XMP\0" this->packetInfo.offset = ( file->Offset() - this->packetInfo.length ); this->xmpPacket.erase(); //safety this->xmpPacket.assign( &curFrame->content[4], curFrame->contentSize - 4 ); this->containsXMP = true; // do this last, after all possible failure } // No space for another frame? => assume into ID3v2.4 padding. XMP_Int64 newPos = file->Offset(); XMP_Int64 spaceLeft = this->oldTagSize - newPos; // Depends on first check below! if ( (newPos > this->oldTagSize) || (spaceLeft < (XMP_Int64)ID3Header::kID3_TagHeaderSize) ) break; } //////////////////////////////////////////////////// // padding this->oldPadding = this->oldTagSize - file->Offset(); this->oldFramesSize = this->oldTagSize - ID3Header::kID3_TagHeaderSize - this->oldPadding; XMP_Validate ( (this->oldPadding >= 0), "illegal oldTagSize or padding value", kXMPErr_BadFileFormat ); for ( XMP_Int64 i = this->oldPadding; i > 0; ) { if ( i >= 8 ) { if ( XIO::ReadInt64_BE(file) != 0 ) XMP_Throw ( "padding not nulled out", kXMPErr_BadFileFormat ); i -= 8; continue; } if ( XIO::ReadUns8(file) != 0) XMP_Throw ( "padding(2) not nulled out", kXMPErr_BadFileFormat ); i--; } //// read ID3v1 tag if ( ! this->containsXMP ) this->containsXMP = id3v1Tag.read ( file, &this->xmpObj ); } // MP3_MetaHandler::CacheFileData