summaryrefslogtreecommitdiff
path: root/source/XMPFiles/FileHandlers/UCF_Handler.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'source/XMPFiles/FileHandlers/UCF_Handler.cpp')
-rw-r--r--source/XMPFiles/FileHandlers/UCF_Handler.cpp846
1 files changed, 846 insertions, 0 deletions
diff --git a/source/XMPFiles/FileHandlers/UCF_Handler.cpp b/source/XMPFiles/FileHandlers/UCF_Handler.cpp
new file mode 100644
index 0000000..e9c2219
--- /dev/null
+++ b/source/XMPFiles/FileHandlers/UCF_Handler.cpp
@@ -0,0 +1,846 @@
+// =================================================================================================
+// ADOBE SYSTEMS INCORPORATED
+// Copyright 2002-2008 Adobe Systems Incorporated
+// All Rights Reserved
+//
+// NOTICE: Adobe permits you to use, modify, and distribute this file in accordance with the terms
+// of the Adobe license agreement accompanying it.
+// ===============================================================================================
+
+#if XMP_WinBuild
+ #pragma warning ( disable : 4996 ) // '...' was declared deprecated
+#endif
+
+#include "UCF_Handler.hpp"
+
+#include "zlib.h"
+
+#include <time.h>
+
+#ifdef DYNAMIC_CRC_TABLE
+ #error "unexpectedly DYNAMIC_CRC_TABLE defined."
+ //Must implement get_crc_table prior to any multi-threading (see notes there)
+#endif
+
+using namespace std;
+
+// =================================================================================================
+/// \file UCF_Handler.cpp
+/// \brief UCF handler class
+// =================================================================================================
+const XMP_Uns16 xmpFilenameLen = 21;
+const char* xmpFilename = "META-INF/metadata.xml";
+
+// =================================================================================================
+// UCF_MetaHandlerCTor
+// ====================
+XMPFileHandler* UCF_MetaHandlerCTor ( XMPFiles * parent )
+{
+ return new UCF_MetaHandler ( parent );
+} // UCF_MetaHandlerCTor
+
+// =================================================================================================
+// UCF_CheckFormat
+// ================
+// * lenght must at least be 114 bytes
+// * first bytes must be \x50\x4B\x03\x04 for *any* zip file
+// * at offset 30 it must spell "mimetype"
+
+#define MIN_UCF_LENGTH 114
+// zip minimum considerations:
+// the shortest legal zip is 100 byte:
+// 30+1* bytes file header
+//+ 0 byte content file (uncompressed)
+//+ 46+1* bytes central directory file header
+//+ 22 byte end of central directory record
+//-------
+//100 bytes
+//
+//1 byte is the shortest legal filename. anything below is no valid zip.
+//
+//==> the mandatory+first "mimetype" content file has a filename length of 8 bytes,
+// thus even if empty (arguably incorrect but tolerable),
+// the shortest legal UCF is 114 bytes (30 + 8 + 0 + 46 + 8 + 22 )
+// anything below is with certainty not a valid ucf.
+
+bool UCF_CheckFormat ( XMP_FileFormat format,
+ XMP_StringPtr filePath,
+ LFA_FileRef fileRef,
+ XMPFiles * parent )
+{
+ // *not* using buffer functionality here, all we need
+ // to detect UCF securely is in the first 38 bytes...
+ IgnoreParam(filePath); IgnoreParam(parent); //suppress warnings
+ XMP_Assert ( format == kXMP_UCFFile ); //standard assert
+
+ XMP_Uns8 buffer[MIN_UCF_LENGTH];
+
+ LFA_Seek ( fileRef, 0, SEEK_SET );
+ if ( MIN_UCF_LENGTH != LFA_Read ( fileRef, buffer, MIN_UCF_LENGTH) ) //NO requireall (->no throw), just return false
+ return false;
+ if ( !CheckBytes ( &buffer[0], "\x50\x4B\x03\x04", 4 ) ) // "PK 03 04"
+ return false;
+ // UCF spec says: there must be a content file mimetype, and be first and be uncompressed...
+ if ( !CheckBytes ( &buffer[30], "mimetype", 8 ) )
+ return false;
+
+ //////////////////////////////////////////////////////////////////////////////
+ //figure out mimetype, decide on writeability
+ // grab mimetype
+ LFA_Seek( fileRef, 18, SEEK_SET );
+ XMP_Uns32 mimeLength = LFA_ReadUns32_LE ( fileRef );
+ XMP_Uns32 mimeCompressedLength = LFA_ReadUns32_LE ( fileRef ); // must be same since uncompressed
+
+ XMP_Validate( mimeLength == mimeCompressedLength,
+ "mimetype compressed and uncompressed length differ",
+ kXMPErr_BadFileFormat );
+
+ XMP_Validate( mimeLength != 0, "0-byte mimetype", kXMPErr_BadFileFormat );
+
+ // determine writability based on mimetype
+ LFA_Seek( fileRef, 30 + 8, SEEK_SET );
+ char* mimetype = new char[ mimeLength + 1 ];
+ LFA_Read( fileRef, mimetype, mimeLength, true );
+ mimetype[mimeLength] = '\0';
+
+ bool okMimetype;
+
+ // be lenient on extraneous CR (0xA) [non-XMP bug #16980028]
+ if ( mimeLength > 0 ) //avoid potential crash (will properly fail below anyhow)
+ if ( mimetype[mimeLength-1] == 0xA )
+ mimetype[mimeLength-1] = '\0';
+
+ if (
+ XMP_LitMatch( mimetype, "application/vnd.adobe.xfl" ) || //Flash Diesel team
+ XMP_LitMatch( mimetype, "application/vnd.adobe.xfl+zip") || //Flash Diesel team
+ XMP_LitMatch( mimetype, "application/vnd.adobe.x-mars" ) || //Mars plugin(labs only), Acrobat8
+ XMP_LitMatch( mimetype, "application/vnd.adobe.pdfxml" ) || //Mars plugin(labs only), Acrobat 9
+ XMP_LitMatch( mimetype, "vnd.adobe.x-asnd" ) || //Adobe Sound Document (Soundbooth Team)
+ XMP_LitMatch( mimetype, "application/vnd.adobe.indesign-idml-package" ) ) //inCopy (inDesign) IDML Document
+
+ // *** ==> unknown are also treated as not acceptable
+ okMimetype = true;
+ else
+ okMimetype = false;
+
+ // not accepted (neither read nor write
+ //.air - Adobe Air Files
+ //application/vnd.adobe.air-application-installer-package+zip
+ //.airi - temporary Adobe Air Files
+ //application/vnd.adobe.air-application-intermediate-package+zip
+
+ delete mimetype;
+ return okMimetype;
+
+} // UCF_CheckFormat
+
+// =================================================================================================
+// UCF_MetaHandler::UCF_MetaHandler
+// ==================================
+
+UCF_MetaHandler::UCF_MetaHandler ( XMPFiles * _parent )
+{
+ this->parent = _parent;
+ this->handlerFlags = kUCF_HandlerFlags;
+ this->stdCharForm = kXMP_Char8Bit;
+} // UCF_MetaHandler::UCF_MetaHandler
+
+// =================================================================================================
+// UCF_MetaHandler::~UCF_MetaHandler
+// =====================================
+
+UCF_MetaHandler::~UCF_MetaHandler()
+{
+ // nothing
+}
+
+// =================================================================================================
+// UCF_MetaHandler::CacheFileData
+// ===============================
+//
+void UCF_MetaHandler::CacheFileData()
+{
+ //*** abort procedures
+ this->containsXMP = false; //assume no XMP for now (beware of exceptions...)
+ LFA_FileRef file = this->parent->fileRef;
+ XMP_PacketInfo &packetInfo = this->packetInfo;
+
+ // clear file positioning info ---------------------------------------------------
+ b=0;b2=0;x=0;x2=0;cd=0;cd2=0;cdx=0;cdx2=0;h=0;h2=0,fl=0;f2l=0;
+ al=0;bl=0;xl=0;x2l=0;cdl=0;cd2l=0;cdxl=0;cdx2l=0;hl=0,z=0,z2=0,z2l=0;
+ numCF=0;numCF2=0;
+ wasCompressed = false;
+
+ // -------------------------------------------------------------------------------
+ fl=LFA_Measure( file );
+ if ( fl < MIN_UCF_LENGTH ) XMP_Throw("file too short, can't be correct UCF",kXMPErr_Unimplemented);
+
+ //////////////////////////////////////////////////////////////////////////////
+ // find central directory before optional comment
+ // things have to go bottom-up, since description headers are allowed in UCF
+ // "scan backwards" until feasible field found (plus sig sanity check)
+ // OS buffering should be smart enough, so not doing anything on top
+ // plus almost all comments will be zero or rather short
+
+ //no need to check anything but the 21 chars of "METADATA-INF/metadata.xml"
+ char filenameToTest[22];
+ filenameToTest[21]='\0';
+
+ XMP_Int32 zipCommentLen = 0;
+ for ( ; zipCommentLen <= EndOfDirectory::COMMENT_MAX; zipCommentLen++ )
+ {
+ LFA_Seek( file, -zipCommentLen -2, SEEK_END );
+ if ( LFA_ReadUns16_LE( file ) == zipCommentLen ) //found it?
+ {
+ //double check, might just look like comment length (actually be 'evil' comment)
+ LFA_Seek( file , - EndOfDirectory::FIXED_SIZE, SEEK_CUR );
+ if ( LFA_ReadUns32_LE( file ) == EndOfDirectory::ID ) break; //heureka, directory ID
+ // 'else': pretend nothing happended, just go on
+ }
+ }
+ //was it a break or just not found ?
+ if ( zipCommentLen > EndOfDirectory::COMMENT_MAX ) XMP_Throw( "zip broken near end or invalid comment" , kXMPErr_BadFileFormat );
+
+ ////////////////////////////////////////////////////////////////////////////
+ //read central directory
+ hl = zipCommentLen + EndOfDirectory::FIXED_SIZE;
+ h = fl - hl;
+ LFA_Seek( file , h , SEEK_SET );
+
+ if ( LFA_ReadUns32_LE( file ) != EndOfDirectory::ID )
+ XMP_Throw("directory header id not found. or broken comment",kXMPErr_BadFileFormat);
+ if ( LFA_ReadUns16_LE( file ) != 0 )
+ XMP_Throw("UCF must be 'first' zip volume",kXMPErr_BadFileFormat);
+ if ( LFA_ReadUns16_LE( file ) != 0 )
+ XMP_Throw("UCF must be single-volume zip",kXMPErr_BadFileFormat);
+
+ numCF = LFA_ReadUns16_LE( file ); //number of content files
+ if ( numCF != LFA_ReadUns16_LE( file ) )
+ XMP_Throw( "per volume and total number of dirs differ" , kXMPErr_BadFileFormat );
+ cdl = LFA_ReadUns32_LE( file );
+ cd = LFA_ReadUns32_LE( file );
+ LFA_Seek( file, 2,SEEK_CUR ); //skip comment len, needed since next LFA is SEEK_CUR !
+
+ //////////////////////////////////////////////////////////////////////////////
+ // check for zip64-end-of-CD-locator/ zip64-end-of-CD
+ // to to central directory
+ if ( cd == 0xffffffff )
+ { // deal with zip 64, otherwise continue
+ XMP_Int64 tmp = LFA_Seek( file,
+ - EndOfDirectory::FIXED_SIZE - Zip64Locator::TOTAL_SIZE,
+ SEEK_CUR ); //go to begining of zip64 locator
+ //relative movement , absolute would imho only require another -zipCommentLen
+
+ if ( Zip64Locator::ID == LFA_ReadUns32_LE(file) ) // prevent 'coincidental length' ffffffff
+ {
+ XMP_Validate( 0 == LFA_ReadUns32_LE(file),
+ "zip64 CD disk must be 0", kXMPErr_BadFileFormat );
+
+ z = LFA_ReadUns64_LE(file);
+ XMP_Validate( z < 0xffffffffffffLL, "file in terrabyte range?", kXMPErr_BadFileFormat ); // 3* ffff, sanity test
+
+ XMP_Uns32 totalNumOfDisks = LFA_ReadUns32_LE(file);
+ /* tolerated while pkglib bug #1742179 */
+ XMP_Validate( totalNumOfDisks == 0 || totalNumOfDisks == 1,
+ "zip64 total num of disks must be 0", kXMPErr_BadFileFormat );
+
+ ///////////////////////////////////////////////
+ /// on to end-of-CD itself
+ LFA_Seek( file, z, SEEK_SET );
+ XMP_Validate( Zip64EndOfDirectory::ID == LFA_ReadUns32_LE(file),
+ "invalid zip64 end of CD sig", kXMPErr_BadFileFormat );
+
+ XMP_Int64 sizeOfZip64EOD = LFA_ReadUns64_LE(file);
+ LFA_Seek(file, 12, SEEK_CUR );
+ //yes twice "total" and "per disk"
+ XMP_Int64 tmp64 = LFA_ReadUns64_LE(file);
+ XMP_Validate( tmp64 == numCF, "num of content files differs to zip64 (1)", kXMPErr_BadFileFormat );
+ tmp64 = LFA_ReadUns64_LE(file);
+ XMP_Validate( tmp64 == numCF, "num of content files differs to zip64 (2)", kXMPErr_BadFileFormat );
+ // cd length verification
+ tmp64 = LFA_ReadUns64_LE(file);
+ XMP_Validate( tmp64 == cdl, "CD length differs in zip64", kXMPErr_BadFileFormat );
+
+ cd = LFA_ReadUns64_LE(file); // wipe out invalid 0xffffffff with the real thing
+ //ignoring "extensible data sector (would need fullLength - fixed length) for now
+ }
+ } // of zip64 fork
+ /////////////////////////////////////////////////////////////////////////////
+ // parse central directory
+ // 'foundXMP' <=> cdx != 0
+
+ LFA_Seek( file, cd, SEEK_SET );
+ XMP_Int64 cdx_suspect=0;
+ XMP_Int64 cdxl_suspect=0;
+ CDFileHeader curCDHeader;
+
+ for ( XMP_Uns16 entryNum=1 ; entryNum <= numCF ; entryNum++ )
+ {
+ cdx_suspect = LFA_Tell( file ); //just suspect for now
+ curCDHeader.read( file );
+
+ if ( GetUns32LE( &curCDHeader.fields[CDFileHeader::o_sig] ) != 0x02014b50 )
+ XMP_Throw("&invalid file header",kXMPErr_BadFileFormat);
+
+ cdxl_suspect = curCDHeader.FIXED_SIZE +
+ GetUns16LE(&curCDHeader.fields[CDFileHeader::o_fileNameLength]) +
+ GetUns16LE(&curCDHeader.fields[CDFileHeader::o_extraFieldLength]) +
+ GetUns16LE(&curCDHeader.fields[CDFileHeader::o_commentLength]);
+
+ // we only look 21 characters, that's META-INF/metadata.xml, no \0 attached
+ if ( curCDHeader.filenameLen == xmpFilenameLen /*21*/ )
+ if( XMP_LitNMatch( curCDHeader.filename , "META-INF/metadata.xml", 21 ) )
+ {
+ cdx = cdx_suspect;
+ cdxl = cdxl_suspect;
+ break;
+ }
+ //hop to next
+ LFA_Seek( file, cdx_suspect + cdxl_suspect , SEEK_SET );
+ } //for-loop, iterating *all* central directory headers (also beyond found)
+
+ if ( !cdx ) // not found xmp
+ {
+ // b and bl remain 0, x and xl remain 0
+ // ==> a is everything before directory
+ al = cd;
+ return;
+ }
+
+ // from here is if-found-only
+ //////////////////////////////////////////////////////////////////////////////
+ //CD values needed, most serve counter-validation purposes (below) only
+ // read whole object (incl. all 3 fields) again properly
+ // to get extra Fields, etc
+ LFA_Seek( file, cdx, SEEK_SET );
+ xmpCDHeader.read( file );
+
+ XMP_Validate( xmpFilenameLen == GetUns16LE( &xmpCDHeader.fields[CDFileHeader::o_fileNameLength]),
+ "content file length not ok", kXMPErr_BadFileFormat );
+
+ XMP_Uns16 CD_compression = GetUns16LE( &xmpCDHeader.fields[CDFileHeader::o_compression] );
+ XMP_Validate(( CD_compression == 0 || CD_compression == 0x08),
+ "illegal compression, must be flate or none", kXMPErr_BadFileFormat );
+ XMP_Uns16 CD_flags = GetUns16LE( &xmpCDHeader.fields[CDFileHeader::o_flags] );
+ XMP_Uns32 CD_crc = GetUns32LE( &xmpCDHeader.fields[CDFileHeader::o_crc32] );
+
+ // parse (actual, non-CD!) file header ////////////////////////////////////////////////
+ x = xmpCDHeader.offsetLocalHeader;
+ LFA_Seek( file , x ,SEEK_SET);
+ xmpFileHeader.read( file );
+ xl = xmpFileHeader.sizeHeader() + xmpCDHeader.sizeCompressed;
+
+ //values needed
+ XMP_Uns16 fileNameLength = GetUns16LE( &xmpFileHeader.fields[FileHeader::o_fileNameLength] );
+ XMP_Uns16 extraFieldLength = GetUns16LE( &xmpFileHeader.fields[FileHeader::o_extraFieldLength] );
+ XMP_Uns16 compression = GetUns16LE( &xmpFileHeader.fields[FileHeader::o_compression] );
+ XMP_Uns32 sig = GetUns32LE( &xmpFileHeader.fields[FileHeader::o_sig] );
+ XMP_Uns16 flags = GetUns16LE( &xmpFileHeader.fields[FileHeader::o_flags] );
+ XMP_Uns32 sizeCompressed = GetUns32LE( &xmpFileHeader.fields[FileHeader::o_sizeCompressed] );
+ XMP_Uns32 sizeUncompressed = GetUns32LE( &xmpFileHeader.fields[FileHeader::o_sizeUncompressed] );
+ XMP_Uns32 crc = GetUns32LE( &xmpFileHeader.fields[FileHeader::o_crc32] );
+
+ // check filename
+ XMP_Validate( fileNameLength == 21, "filename size contradiction" , kXMPErr_BadFileFormat );
+ XMP_Enforce ( xmpFileHeader.filename != 0 );
+ XMP_Validate( !memcmp( "META-INF/metadata.xml", xmpFileHeader.filename , xmpFilenameLen ) , "filename is cf header is not META-INF/metadata.xml" , kXMPErr_BadFileFormat );
+
+ // deal with data descriptor if needed
+ if ( flags & FileHeader::kdataDescriptorFlag )
+ {
+ if ( sizeCompressed!=0 || sizeUncompressed!=0 || crc!=0 ) XMP_Throw("data descriptor must mean 3x zero",kXMPErr_BadFileFormat);
+ LFA_Seek( file, xmpCDHeader.sizeCompressed + fileNameLength + xmpCDHeader.extraFieldLen, SEEK_CUR); //skip actual data to get to descriptor
+ crc = LFA_ReadUns32_LE( file );
+ if ( crc == 0x08074b50 ) //data descriptor may or may not have signature (see spec)
+ {
+ crc = LFA_ReadUns32_LE( file ); //if it does, re-read
+ }
+ sizeCompressed = LFA_ReadUns32_LE( file );
+ sizeUncompressed = LFA_ReadUns32_LE( file );
+ // *** cater for zip64 plus 'streamed' data-descriptor stuff
+ }
+
+ // more integrity checks (post data descriptor handling)
+ if ( sig != 0x04034b50 ) XMP_Throw("invalid content file header",kXMPErr_BadFileFormat);
+ if ( compression != CD_compression ) XMP_Throw("compression contradiction",kXMPErr_BadFileFormat);
+ if ( sizeUncompressed != xmpCDHeader.sizeUncompressed ) XMP_Throw("contradicting uncompressed lengths",kXMPErr_BadFileFormat);
+ if ( sizeCompressed != xmpCDHeader.sizeCompressed ) XMP_Throw("contradicting compressed lengths",kXMPErr_BadFileFormat);
+ if ( sizeUncompressed == 0 ) XMP_Throw("0-byte uncompressed size", kXMPErr_BadFileFormat );
+
+ ////////////////////////////////////////////////////////////////////
+ // packet Info
+ this->packetInfo.charForm = stdCharForm;
+ this->packetInfo.writeable = false;
+ this->packetInfo.offset = kXMPFiles_UnknownOffset; // checksum!, hide position to not give funny ideas
+ this->packetInfo.length = kXMPFiles_UnknownLength;
+
+ ////////////////////////////////////////////////////////////////////
+ // prepare packet (compressed or not)
+ this->xmpPacket.erase();
+ this->xmpPacket.reserve( sizeUncompressed );
+ this->xmpPacket.append( sizeUncompressed, ' ' );
+ XMP_StringPtr packetStr = XMP_StringPtr ( xmpPacket.c_str() ); // only set after reserving the space!
+
+ // go to packet offset
+ LFA_Seek ( file, x + xmpFileHeader.FIXED_SIZE + fileNameLength + extraFieldLength , SEEK_SET);
+
+ // compression fork --------------------------------------------------
+ switch (compression)
+ {
+ case 0x8: // FLATE
+ {
+ wasCompressed = true;
+ XMP_Uns32 bytesRead = 0;
+ XMP_Uns32 bytesWritten = 0; // for writing into packetString
+ const unsigned int CHUNK = 16384;
+
+ int ret;
+ unsigned int have; //added type
+ z_stream strm;
+ unsigned char in[CHUNK];
+ unsigned char out[CHUNK];
+ // does need this intermediate stage, no direct compressio to packetStr possible,
+ // since also partially filled buffers must be picked up. That's how it works.
+ // in addition: internal zlib variables might have 16 bit limits...
+
+ /* allocate inflate state */
+ strm.zalloc = Z_NULL;
+ strm.zfree = Z_NULL;
+ strm.opaque = Z_NULL;
+ strm.avail_in = 0;
+ strm.next_in = Z_NULL;
+
+ /* must use windowBits = -15, for raw inflate, no zlib header */
+ ret = inflateInit2(&strm,-MAX_WBITS);
+
+ if (ret != Z_OK)
+ XMP_Throw("zlib error ",kXMPErr_ExternalFailure);
+
+ /* decompress until deflate stream ends or end of file */
+ do {
+ // must take care here not to read too much, thus whichever is smaller:
+ XMP_Int32 bytesRemaining = sizeCompressed - bytesRead;
+ if ( (XMP_Int32)CHUNK < bytesRemaining ) bytesRemaining = (XMP_Int32)CHUNK;
+ strm.avail_in=LFA_Read( file , in , bytesRemaining , true );
+ bytesRead += strm.avail_in; // NB: avail_in is "unsigned_int", so might be 16 bit (not harmfull)
+
+ if (strm.avail_in == 0) break;
+ strm.next_in = in;
+
+ do {
+ strm.avail_out = CHUNK;
+ strm.next_out = out;
+ ret = inflate(&strm, Z_NO_FLUSH);
+ XMP_Assert( ret != Z_STREAM_ERROR ); /* state not clobbered */
+ switch (ret)
+ {
+ case Z_NEED_DICT:
+ (void)inflateEnd(&strm);
+ XMP_Throw("zlib error: Z_NEED_DICT",kXMPErr_ExternalFailure);
+ case Z_DATA_ERROR:
+ (void)inflateEnd(&strm);
+ XMP_Throw("zlib error: Z_DATA_ERROR",kXMPErr_ExternalFailure);
+ case Z_MEM_ERROR:
+ (void)inflateEnd(&strm);
+ XMP_Throw("zlib error: Z_MEM_ERROR",kXMPErr_ExternalFailure);
+ }
+
+ have = CHUNK - strm.avail_out;
+ memcpy( (unsigned char*) packetStr + bytesWritten , out , have );
+ bytesWritten += have;
+
+ } while (strm.avail_out == 0);
+
+ /* it's done when inflate() says it's done */
+ } while (ret != Z_STREAM_END);
+
+ /* clean up and return */
+ (void)inflateEnd(&strm);
+ if (ret != Z_STREAM_END)
+ XMP_Throw("zlib error ",kXMPErr_ExternalFailure);
+ break;
+ }
+ case 0x0: // no compression - read directly into the right place
+ {
+ wasCompressed = false;
+ XMP_Enforce( LFA_Read ( file, (char*)packetStr, sizeUncompressed, kLFA_RequireAll ) );
+ break;
+ }
+ default:
+ {
+ XMP_Throw("illegal zip compression method (not none, not flate)",kXMPErr_BadFileFormat);
+ }
+ }
+ this->containsXMP = true; // do this last, after all possible failure/execptions
+}
+
+
+// =================================================================================================
+// UCF_MetaHandler::ProcessXMP
+// ============================
+
+void UCF_MetaHandler::ProcessXMP()
+{
+ // we have no legacy, CacheFileData did all that was needed
+ // ==> default implementation is fine
+ XMPFileHandler::ProcessXMP();
+}
+
+// =================================================================================================
+// UCF_MetaHandler::UpdateFile
+// =============================
+
+// TODO: xmp packet with data descriptor
+
+void UCF_MetaHandler::UpdateFile ( bool doSafeUpdate )
+{
+ //sanity
+ XMP_Enforce( (x!=0) == (cdx!=0) );
+ if (!cdx)
+ xmpCDHeader.setXMPFilename(); //if new, set filename (impacts length, thus before computation)
+ if ( ! this->needsUpdate )
+ return;
+
+ // ***
+ if ( doSafeUpdate ) XMP_Throw ( "UCF_MetaHandler::UpdateFile: Safe update not supported", kXMPErr_Unavailable );
+
+ LFA_FileRef file = this->parent->fileRef;
+
+ // final may mean compressed or not, whatever is to-be-embedded
+ uncomprPacketStr = xmpPacket.c_str();
+ uncomprPacketLen = (XMP_StringLen) xmpPacket.size();
+ finalPacketStr = uncomprPacketStr; // will be overriden if compressedXMP==true
+ finalPacketLen = uncomprPacketLen;
+ std::string compressedPacket; // moot if non-compressed, still here for scope reasons (having to keep a .c_str() alive)
+
+ if ( !x ) // if new XMP...
+ {
+ xmpFileHeader.clear();
+ xmpFileHeader.setXMPFilename();
+ // ZIP64 TODO: extra Fields, impact on cdxl2 and x2l
+ }
+
+ ////////////////////////////////////////////////////////////////////////////////////////////////
+ // COMPRESSION DECISION
+
+ // for large files compression is bad:
+ // a) size of XMP becomes irrelevant on large files ==> why worry over compression ?
+ // b) more importantly: no such thing as padding possible, compression == ever changing sizes
+ // => never in-place rewrites, *ugly* performance impact on large files
+ inPlacePossible = false; //assume for now
+
+ if ( !x ) // no prior XMP? -> decide on filesize
+ compressXMP = ( fl > 1024*50 /* 100 kB */ ) ? false : true;
+ else
+ compressXMP = wasCompressed; // don't change a thing
+
+ if ( !wasCompressed && !compressXMP &&
+ ( GetUns32LE( &xmpFileHeader.fields[FileHeader::o_sizeUncompressed] ) == uncomprPacketLen ))
+ {
+ inPlacePossible = true;
+ }
+ ////////////////////////////////////////////////////////////////////////////////////////////////
+ // COMPRESS XMP
+ if ( compressXMP )
+ {
+ const unsigned int CHUNK = 16384;
+ int ret, flush;
+ unsigned int have;
+ z_stream strm;
+ unsigned char out[CHUNK];
+
+ /* allocate deflate state */
+ strm.zalloc = Z_NULL; strm.zfree = Z_NULL; strm.opaque = Z_NULL;
+ if ( deflateInit2(&strm, Z_DEFAULT_COMPRESSION, Z_DEFLATED, -MAX_WBITS, 8 /*memlevel*/, Z_DEFAULT_STRATEGY) )
+ XMP_Throw("zlib error ",kXMPErr_ExternalFailure);
+
+ //write at once, since we got it in mem anyway:
+ strm.avail_in = uncomprPacketLen;
+ flush = Z_FINISH; // that's all, folks
+ strm.next_in = (unsigned char*) uncomprPacketStr;
+
+ do {
+ strm.avail_out = CHUNK;
+ strm.next_out = out;
+
+ ret = deflate(&strm, flush); /* no bad return value (!=0 acceptable) */
+ XMP_Enforce(ret != Z_STREAM_ERROR); /* state not clobbered */
+ //fwrite(buffer,size,count,file)
+ have = CHUNK - strm.avail_out;
+ compressedPacket.append( (const char*) out, have);
+ } while (strm.avail_out == 0);
+
+ if (ret != Z_STREAM_END)
+ XMP_Throw("zlib stream incomplete ",kXMPErr_ExternalFailure);
+ XMP_Enforce(strm.avail_in == 0); // all input will be used
+ (void)deflateEnd(&strm); //clean up (do prior to checks)
+
+ finalPacketStr = compressedPacket.c_str();
+ finalPacketLen = (XMP_StringLen)compressedPacket.size();
+ }
+
+ PutUns32LE ( uncomprPacketLen, &xmpFileHeader.fields[FileHeader::o_sizeUncompressed] );
+ PutUns32LE ( finalPacketLen, &xmpFileHeader.fields[FileHeader::o_sizeCompressed] );
+ PutUns16LE ( compressXMP ? 8:0, &xmpFileHeader.fields[FileHeader::o_compression] );
+
+ ////////////////////////////////////////////////////////////////////////////////////////////////
+ // CRC (always of uncompressed data)
+ XMP_Uns32 crc = crc32( 0 , (Bytef*)uncomprPacketStr, uncomprPacketLen );
+ PutUns32LE( crc, &xmpFileHeader.fields[FileHeader::o_crc32] );
+
+ ////////////////////////////////////////////////////////////////////////////////////////////////
+ // TIME calculation for timestamp
+ // will be applied both to xmp content file and CD header
+ XMP_Uns16 lastModTime, lastModDate;
+ XMP_DateTime time;
+ SXMPUtils::CurrentDateTime( &time );
+
+ if ( (time.year - 1900) < 80)
+ {
+ lastModTime = 0; // 1.1.1980 00:00h
+ lastModDate = 21;
+ }
+
+ // typedef unsigned short ush; //2 bytes
+ lastModDate = (XMP_Uns16) (((time.year) - 1980 ) << 9 | ((time.month) << 5) | time.day);
+ lastModTime = ((XMP_Uns16)time.hour << 11) | ((XMP_Uns16)time.minute << 5) | ((XMP_Uns16)time.second >> 1);
+
+ PutUns16LE ( lastModDate, &xmpFileHeader.fields[FileHeader::o_lastmodDate] );
+ PutUns16LE ( lastModTime, &xmpFileHeader.fields[FileHeader::o_lastmodTime] );
+
+ ////////////////////////////////////////////////////////////////////////////////////////////////
+ // adjustments depending on 4GB Border,
+ // decisions on in-place update
+ // so far only z, zl have been determined
+
+ // Zip64 related assurances, see (15)
+ XMP_Enforce(!z2);
+ XMP_Enforce(h+hl == fl );
+
+ ////////////////////////////////////////////////////////////////////////////////////////////////
+ // COMPUTE MISSING VARIABLES
+ // A - based on xmp existence
+ //
+ // already known: x, xl, cd
+ // most left side vars,
+ //
+ // finalPacketStr, finalPacketLen
+
+ if ( x ) // previous xmp?
+ {
+ al = x;
+ b = x + xl;
+ bl = cd - b;
+ }
+ else
+ {
+ al = cd;
+ //b,bl left at zero
+ }
+
+ if ( inPlacePossible )
+ { // leave xmp right after A
+ x2 = al;
+ x2l = xmpFileHeader.sizeTotalCF(); //COULDDO: assert (x2l == xl)
+ if (b) b2 = x2 + x2l; // b follows x as last content part
+ cd2 = b2 + bl; // CD follows B2
+ }
+ else
+ { // move xmp to end
+ if (b) b2 = al; // b follows
+ // x follows as last content part (B existing or not)
+ x2 = al + bl;
+ x2l = xmpFileHeader.sizeTotalCF();
+ cd2 = x2 + x2l; // CD follows X
+ }
+
+ /// create new XMP header ///////////////////////////////////////////////////
+ // written into actual fields + generation of extraField at .write()-time...
+ // however has impact on .size() computation -- thus enter before cdx2l computation
+ xmpCDHeader.sizeUncompressed = uncomprPacketLen;
+ xmpCDHeader.sizeCompressed = finalPacketLen;
+ xmpCDHeader.offsetLocalHeader = x2;
+ PutUns32LE ( crc, &xmpCDHeader.fields[CDFileHeader::o_crc32] );
+ PutUns16LE ( compressXMP ? 8:0, &xmpCDHeader.fields[CDFileHeader::o_compression] );
+ PutUns16LE ( lastModDate, &xmpCDHeader.fields[CDFileHeader::o_lastmodDate] );
+ PutUns16LE ( lastModTime, &xmpCDHeader.fields[CDFileHeader::o_lastmodTime] );
+
+ // for
+ if ( inPlacePossible )
+ {
+ cdx2 = cdx; //same, same
+ writeOut( file, file, false, true );
+ return;
+ }
+
+ ////////////////////////////////////////////////////////////////////////
+ // temporarily store (those few, small) trailing things that might not survive the move around:
+ LFA_Seek(file, cd, SEEK_SET); // seek to central directory
+ cdEntries.clear(); //mac precaution
+
+ //////////////////////////////////////////////////////////////////////////////
+ // parse headers
+ // * stick together output header list
+ cd2l = 0; //sum up below
+
+ CDFileHeader tempHeader;
+ for( XMP_Uns16 pos=1 ; pos <= numCF ; pos++ )
+ {
+ if ( (cdx) && (LFA_Tell( file ) == cdx) )
+ {
+ tempHeader.read( file ); //read, even if not use, to advance file pointer
+ }
+ else
+ {
+ tempHeader.read( file );
+ // adjust b2 offset for files that were behind the xmp:
+ // may (if xmp moved to back)
+ // or may not (inPlace Update) make a difference
+ if ( (x) && ( tempHeader.offsetLocalHeader > x) ) // if xmp existed before and this was a file behind it
+ tempHeader.offsetLocalHeader += b2 - b;
+ cd2l += tempHeader.size(); // prior offset change might have impact
+ cdEntries.push_back( tempHeader );
+ }
+ }
+
+ //push in XMP packet as last one (new or not)
+ cdEntries.push_back( xmpCDHeader );
+ cdx2l = xmpCDHeader.size();
+ cd2l += cdx2l; // true, no matter which order
+
+ //OLD cd2l = : cdl - cdxl + cdx2l; // (NB: cdxl might be 0)
+ numCF2 = numCF + ( (cdx)?0:1 ); //xmp packet for the first time? -> add one more CF
+
+ XMP_Validate( numCF2 > 0, "no content files", kXMPErr_BadFileFormat );
+ XMP_Validate( numCF2 <= 0xFFFE, "max number of 0xFFFE entries reached", kXMPErr_BadFileFormat );
+
+ cdx2 = cd2 + cd2l - cdx2l; // xmp content entry comes last (since beyond inPlace Update)
+
+ // zip64 decision
+ if ( ( cd2 + cd2l + hl ) > 0xffffffff ) // predict non-zip size ==> do we need a zip-64?
+ {
+ z2 = cd2 + cd2l;
+ z2l = Zip64EndOfDirectory::FIXED_SIZE + Zip64Locator::TOTAL_SIZE;
+ }
+
+ // header and output length,
+ h2 = cd2 + cd2l + z2l; // (z2l might be 0)
+ f2l = h2 + hl;
+
+ ////////////////////////////////////////////////////////////////////////////////////////////////
+ // read H (endOfCD), correct offset
+ LFA_Seek(file, h, SEEK_SET);
+
+ endOfCD.read( file );
+ if ( cd2 <= 0xffffffff )
+ PutUns32LE( (XMP_Int32) cd2 , &endOfCD.fields[ endOfCD.o_CdOffset ] );
+ else
+ PutUns32LE( 0xffffffff , &endOfCD.fields[ endOfCD.o_CdOffset ] );
+ PutUns16LE( numCF2, &endOfCD.fields[ endOfCD.o_CdNumEntriesDisk ] );
+ PutUns16LE( numCF2, &endOfCD.fields[ endOfCD.o_CdNumEntriesTotal ] );
+
+ XMP_Enforce( cd2l <= 0xffffffff ); // _size_ of directory itself certainly under 4GB
+ PutUns32LE( (XMP_Uns32)cd2l, &endOfCD.fields[ endOfCD.o_CdSize ] );
+
+ ////////////////////////////////////////////////////////////////////////////////////////////////
+ // MOVING
+ writeOut( file, file, false, false );
+
+ this->needsUpdate = false; //do last for safety reasons
+} // UCF_MetaHandler::UpdateFile
+
+// =================================================================================================
+// UCF_MetaHandler::WriteFile
+// ============================
+void UCF_MetaHandler::WriteFile ( LFA_FileRef sourceRef, const std::string & sourcePath )
+{
+ IgnoreParam ( sourcePath );
+ XMP_Throw ( "UCF_MetaHandler::WriteFile: TO BE IMPLEMENTED", kXMPErr_Unimplemented );
+}
+
+// =================================================================================================
+// own approach to unify Update and WriteFile:
+// ============================
+
+void UCF_MetaHandler::writeOut( LFA_FileRef sourceFile, LFA_FileRef targetFile, bool isRewrite, bool isInPlace)
+{
+ // isInPlace is only possible when it's not a complete rewrite
+ XMP_Enforce( (!isInPlace) || (!isRewrite) );
+
+ /////////////////////////////////////////////////////////
+ // A
+ if (isRewrite) //move over A block
+ LFA_Move( sourceFile , 0 , targetFile, 0 , al );
+
+ /////////////////////////////////////////////////////////
+ // B / X (not necessarily in this order)
+ if ( !isInPlace ) // B does not change a thing (important optimization)
+ {
+ LFA_Seek( targetFile , b2 , SEEK_SET );
+ LFA_Move( sourceFile , b , targetFile, b2 , bl );
+ }
+
+ LFA_Seek( targetFile , x2 , SEEK_SET );
+ xmpFileHeader.write( targetFile );
+ LFA_Write( targetFile, finalPacketStr, finalPacketLen );
+ //TODO: cover reverse case / inplace ...
+
+ /////////////////////////////////////////////////////////
+ // CD
+ // No Seek here on purpose.
+ // This assert must still be valid
+
+ // if inPlace, the only thing that needs still correction is the CRC in CDX:
+ if ( isInPlace )
+ {
+ XMP_Uns32 crc; //TEMP, not actually needed
+ crc = GetUns32LE( &xmpFileHeader.fields[FileHeader::o_crc32] );
+
+ // go there,
+ // do the job (take value directly from (non-CD-)fileheader),
+ // end of story.
+ LFA_Seek( targetFile , cdx2 + CDFileHeader::o_crc32 , SEEK_SET );
+ LFA_Write( targetFile, &xmpFileHeader.fields[FileHeader::o_crc32], 4);
+
+ return;
+ }
+
+ LFA_Seek( targetFile , cd2 , SEEK_SET );
+
+ std::vector<CDFileHeader>::iterator iter;
+ int tmptmp=1;
+ for( iter = cdEntries.begin(); iter != cdEntries.end(); iter++ ) {
+ CDFileHeader* p=&(*iter);
+ XMP_Int64 before = LFA_Tell(targetFile);
+ p->write( targetFile );
+ XMP_Int64 total = LFA_Tell(targetFile) - before;
+ XMP_Int64 tmpSize = p->size();
+ tmptmp++;
+ }
+
+ /////////////////////////////////////////////////////////
+ // Z
+ if ( z2 ) // yes, that simple
+ {
+ XMP_Assert( z2 == LFA_Tell(targetFile));
+ LFA_Seek( targetFile , z2 , SEEK_SET );
+
+ //no use in copying, always construct from scratch
+ Zip64EndOfDirectory zip64EndOfDirectory( cd2, cd2l, numCF2) ;
+ Zip64Locator zip64Locator( z2 );
+
+ zip64EndOfDirectory.write( targetFile );
+ zip64Locator.write( targetFile );
+ }
+
+ /////////////////////////////////////////////////////////
+ // H
+ XMP_Assert( h2 == LFA_Tell(targetFile));
+ endOfCD.write( targetFile );
+
+ XMP_Assert( f2l == LFA_Tell(targetFile));
+ if ( f2l< fl)
+ LFA_Truncate(targetFile,f2l); //file may have shrunk
+}
+
+