Blob Blame History Raw
#ifndef __UCF_Handler_hpp__
#define __UCF_Handler_hpp__	1

// =================================================================================================
// ADOBE SYSTEMS INCORPORATED
// Copyright 2007 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.
// =================================================================================================

#include "public/include/XMP_Environment.h"	// ! XMP_Environment.h must be the first included header.

#include "public/include/XMP_Const.h"
#include "public/include/XMP_IO.hpp"

#include "XMPFiles/source/XMPFiles_Impl.hpp"
#include "source/XMPFiles_IO.hpp"

// =================================================================================================
/// \file UCF_Handler.hpp
//
//  underlying math:
//    __   0   ______    0    ______  __
//            |  A   |       |  A   |
//            |      |       |      |
//         al |      |  (a2l)|      |
//         x  |------|   b2  |------|
//         xl |  X   |       |  B   |_
//         b  |------|  (b2l)|      | |
//            |  B   |   x2  |------| | B2 could also be
//         bl |      |   x2l |  X2  | | _after_ X2
//         cd |------|   cd2 |------|<'
//            |//CD//|       |//CD2/|
//        cdx |------|  cdx2 |------|
//        cdxl|------|  cdx2l|------|
//         cdl|//////|   cd2l|//////|
//         z  |------|     z2|------|
//            |      |       |      |
//        [zl]|  Z   |   z2l |  Z2  |
//         h  |------|   h2  |------|
//    fl      |  H   |       |  H2  |  f2l
//    __   hl |______|  (h2l)|______|  __
//
//    fl  file length pre (2 = post)
//    numCf  number of content files prior to injection
//    numCf2            "            post
//
//
//    l	length variable, all else offset
//    [ ] variable is not needed
//    ( ) variable is identical to left
//    a   content files prior to xmp (possibly: all)
//    b   content files behind xmp (possibly: 0)
//    x   xmp packet (possibly: 0)
//    cd  central directory
//    h   end of central directory
//
//    z   zip64 record and locator (if existing)
//
//    general rules:
//    the bigger A, the less rewrite effort.
//    (also within the CD)
//    putting XMP at the end maximizes A.
//
//    bool previousXMP == x!=0
//
//    (x==0) == (cdx==0) == (xl==0) == (cdxl==0)
//
//    std::vector<XMP_Uns32> cdOffsetsPre;
//
//    -----------------
//    asserts:
//( 1)  a == a2 == 0,	making these variables obsolete
//( 2)  a2l == al,		this block is not touched
//( 3)  b2 <= b,		b is only moved closer to the beginning of file
//( 4)  b2l == bl,		b does not change in size
//( 5)  x2 >= x,		b is only moved further down in the file
//
//( 6) x != 0, x2l != 0, cd != 0, cdl != 0
//    				none of these blocks is at the beginning ('mimetype' by spec),
//    				nor is any of them zero byte long
//( 7) h!=0, hl >= 22	header is not at the beginning, minimum size 22
//
//    file size computation:
//( 8) al + bl + xl +cdl +hl = fl
//( 9) al + bl + x2l+cd2l+hl = fl2
//
//(10) ( x==0 ) <=> ( cdx == 0 )
//    				if there's a packet in the pre-file, or there isn't
//(11) (x==0)	=> xl=0
//(12) (cdx==0)=> cdx=0
//
//(13) x==0 ==> b,bl,b2,b2l==0
//    				if there is no pre-xmp, B does not exist
//(14) x!=0 ==> al:=x, b:=x+xl, bl:=cd-b
//
//    zip 64:
//(15)  zl and z2l are basically equal, except _one_ of them is 0 :
//
//(16)  b2l is indeed never different t
//
//    FIXED_SIZE means the fixed (minimal) portion of a struct
//    TOTAL_SIZE indicates, that this struct indeed has a fixed, known total length
//
// =================================================================================================

extern XMPFileHandler* UCF_MetaHandlerCTor ( XMPFiles * parent );

extern bool UCF_CheckFormat ( XMP_FileFormat format,
							  XMP_StringPtr  filePath,
                              XMP_IO*    fileRef,
                              XMPFiles *     parent );

static const XMP_OptionBits kUCF_HandlerFlags = (
						kXMPFiles_CanInjectXMP     |
						kXMPFiles_CanExpand        |
						kXMPFiles_CanRewrite       |
						/* kXMPFiles_PrefersInPlace | removed, only reasonable for formats where difference is significant */
						kXMPFiles_AllowsOnlyXMP    |
						kXMPFiles_ReturnsRawPacket |
						// *** kXMPFiles_AllowsSafeUpdate |
						kXMPFiles_NeedsReadOnlyPacket //UCF/zip has checksums...
						);

enum {	// data descriptor
	// may or may not have a signature: 0x08074b50
	kUCF_DD_crc32				=   0,
	kUCF_DD_sizeCompressed		=   4,
	kUCF_DD_sizeUncompressed	=   8,
};

class UCF_MetaHandler : public XMPFileHandler
{
public:
	UCF_MetaHandler ( XMPFiles * _parent );
	~UCF_MetaHandler();

	void CacheFileData();
	void ProcessXMP();

	void UpdateFile ( bool doSafeUpdate );
    void WriteTempFile ( XMP_IO* tempRef );

protected:
	const static XMP_Uns16 xmpFilenameLen = 21;
	const static char* xmpFilename;

private:
	class Zip64EndOfDirectory {
	private:

	public:
		const static XMP_Uns16 o_sig				=	0;	// 0x06064b50
		const static XMP_Uns16 o_size				=	4;	// of this, excluding leading 12 bytes
				// == FIXED_SIZE -12, since we're never creating the extensible data sector...
		const static XMP_Uns16 o_VersionMade		=	12;
		const static XMP_Uns16 o_VersionNeededExtr	=	14;
		const static XMP_Uns16 o_numDisk			=	16;	// force 0
		const static XMP_Uns16 o_numCDDisk			=	20; // force 0
		const static XMP_Uns16 o_numCFsThisDisk		=	24;
		const static XMP_Uns16 o_numCFsTotal		=	32; // force equal
		const static XMP_Uns16 o_sizeOfCD			=	40; // (regular one, not Z64)
		const static XMP_Uns16 o_offsetCD			=	48; //           "

		const static XMP_Int32 FIXED_SIZE = 56;
		char  fields[FIXED_SIZE];

		const static XMP_Uns32 ID = 0x06064b50;

		Zip64EndOfDirectory( XMP_Int64 offsetCD, XMP_Int64 sizeOfCD, XMP_Uns64 numCFs )
		{
			memset(fields,'\0',FIXED_SIZE);

			PutUns32LE(ID				,&fields[o_sig] );
			PutUns64LE(FIXED_SIZE - 12,	&fields[o_size] );	//see above
			PutUns16LE(	45	,&fields[o_VersionMade] );
			PutUns16LE(	45	,&fields[o_VersionNeededExtr] );
			// fine at 0:	o_numDisk
			// fine at 0:	o_numCDDisk
			PutUns64LE( numCFs, &fields[o_numCFsThisDisk] );
			PutUns64LE( numCFs, &fields[o_numCFsTotal] );
			PutUns64LE( sizeOfCD,	&fields[o_sizeOfCD] );
			PutUns64LE( offsetCD,	&fields[o_offsetCD] );
		}

		void write(XMP_IO* file)
		{
			XMP_Validate( ID == GetUns32LE( &this->fields[o_sig] ), "invalid header on write", kXMPErr_BadFileFormat );
			file ->Write ( fields , FIXED_SIZE  );
		}

	};

	class Zip64Locator {
	public:
		const static XMP_Uns16 o_sig				=	0;	// 0x07064b50
		const static XMP_Uns16 o_numDiskZ64CD		=	4;	// force 0
		const static XMP_Uns16 o_offsZ64EOD			=   8;
		const static XMP_Uns16 o_numDisks			=   16; // set 1, tolerate 0

		const static XMP_Int32 TOTAL_SIZE			=	20;
		char  fields[TOTAL_SIZE];

		const static XMP_Uns32 ID = 0x07064b50;

		Zip64Locator( XMP_Int64 offsetZ64EOD )
		{
			memset(fields,'\0',TOTAL_SIZE);
			PutUns32LE(ID,				&fields[Zip64Locator::o_sig] );
			PutUns32LE(0,				&fields[Zip64Locator::o_numDiskZ64CD] );
			PutUns64LE(offsetZ64EOD,	&fields[Zip64Locator::o_offsZ64EOD] );
			PutUns32LE(1,				&fields[Zip64Locator::o_numDisks] );
		}

		// writes structure to file (starting at current position)
		void write(XMP_IO* file)
		{
			XMP_Validate( ID == GetUns32LE( &this->fields[o_sig] ), "invalid header on write", kXMPErr_BadFileFormat );
			file ->Write ( fields , TOTAL_SIZE  );
		}
	};

	struct EndOfDirectory {
	public:
		const static XMP_Int32 FIXED_SIZE = 22;	//32 bit type is important to not overrun on maxcomment
		const static XMP_Uns32 ID = 0x06054b50;
		const static XMP_Int32 COMMENT_MAX = 0xFFFF;
		//offsets
		const static XMP_Int32 o_CentralDirectorySize = 12;
		const static XMP_Int32 o_CentralDirectoryOffset = 16;
	};

	class FileHeader {
		private:
			//TODO intergrate in clear()
			void release() // avoid terminus free() since subject to a #define (mem-leak-check)
			{
				if (filename)	delete [] filename;
				if (extraField)	delete [] extraField;
				filename=0;
				extraField=0;
			}

		public:
			const static XMP_Uns32 SIG = 0x04034b50;
			const static XMP_Uns16 kdataDescriptorFlag = 0x8;

			const static XMP_Uns16 o_sig = 0;
			const static XMP_Uns16 o_extractVersion		=	4;
			const static XMP_Uns16 o_flags				=   6;
			const static XMP_Uns16 o_compression		=   8;
			const static XMP_Uns16 o_lastmodTime		=   10;
			const static XMP_Uns16 o_lastmodDate		=   12;
			const static XMP_Uns16 o_crc32				=   14;
			const static XMP_Uns16 o_sizeCompressed		=   18;
			const static XMP_Uns16 o_sizeUncompressed	=   22;
			const static XMP_Uns16 o_fileNameLength		=   26;
			const static XMP_Uns16 o_extraFieldLength	=   28;
			// total									30

			const static int FIXED_SIZE = 30;
			char  fields[FIXED_SIZE];

			char* filename;
			char* extraField;
			XMP_Uns16 filenameLen;
			XMP_Uns16 extraFieldLen;

			void clear()
			{
				this->release();
				memset(fields,'\0',FIXED_SIZE);
				//arm with minimal default values:
				PutUns32LE(0x04034b50,	&fields[FileHeader::o_sig] );
				PutUns16LE(0x14,		&fields[FileHeader::o_extractVersion] );
			}

			FileHeader() : filename(0),extraField(0),filenameLen(0),extraFieldLen(0)
			{
				clear();
			}

			// reads entire *FileHeader* structure from file (starting at current position)
			void read(XMP_IO* file)
			{
				this->release();

				file ->ReadAll ( fields , FIXED_SIZE  );

				XMP_Uns32 tmp32 = GetUns32LE( &this->fields[FileHeader::o_sig] );
				XMP_Validate( SIG == tmp32, "invalid header", kXMPErr_BadFileFormat );
				filenameLen   = GetUns16LE( &this->fields[FileHeader::o_fileNameLength] );
				extraFieldLen = GetUns16LE( &this->fields[FileHeader::o_extraFieldLength] );

				// nb unlike the CDFileHeader the FileHeader will in practice never have
				// extra fields. Reasoning: File headers never carry (their own) offsets,
				// (un)compressed size of XMP will hardly ever reach 4 GB

				if (filenameLen) {
					filename = new char[filenameLen];
					file->ReadAll ( filename, filenameLen );
				}
				if (extraFieldLen) {
					extraField = new char[extraFieldLen];
					file->ReadAll ( extraField, extraFieldLen );
					// *** NB: this WOULD need parsing for content files that are
					//   compressed or uncompressed >4GB (VERY unlikely for XMP)
				}
			}

			// writes structure to file (starting at current position)
			void write(XMP_IO* file)
			{
				XMP_Validate( SIG == GetUns32LE( &this->fields[FileHeader::o_sig] ), "invalid header on write", kXMPErr_BadFileFormat );

				filenameLen   = GetUns16LE( &this->fields[FileHeader::o_fileNameLength] );
				extraFieldLen = GetUns16LE( &this->fields[FileHeader::o_extraFieldLength] );

				file ->Write ( fields , FIXED_SIZE  );
				if (filenameLen)	file->Write ( filename, filenameLen    );
				if (extraFieldLen)	file->Write ( extraField, extraFieldLen  );
			}

			void transfer(const FileHeader &orig)
			{
				memcpy(fields,orig.fields,FIXED_SIZE);
				if (orig.extraField)
				{
					extraFieldLen=orig.extraFieldLen;
					extraField = new char[extraFieldLen];
					memcpy(extraField,orig.extraField,extraFieldLen);
				}
				if (orig.filename)
				{
					filenameLen=orig.filenameLen;
					filename = new char[filenameLen];
					memcpy(filename,orig.filename,filenameLen);
				}
			};

			void setXMPFilename()
			{
				// only needed for fresh structs, thus enforcing rather than catering to memory issues
				XMP_Enforce( (filenameLen==0) && (extraFieldLen == 0) );
				filenameLen = xmpFilenameLen;
				PutUns16LE(filenameLen,	&fields[FileHeader::o_fileNameLength] );
				filename = new char[xmpFilenameLen];
				memcpy(filename,"META-INF/metadata.xml",xmpFilenameLen);
			}

			XMP_Uns32 sizeHeader()
			{
				return this->FIXED_SIZE + this->filenameLen + this->extraFieldLen;
			}

			XMP_Uns32 sizeTotalCF()
			{
				//*** not zip64 bit safe yet, use only for non-large xmp packet
				return this->sizeHeader() + GetUns32LE( &fields[FileHeader::o_sizeCompressed] );
			}

			~FileHeader()
			{
				this->release();
			};

	}; //class FileHeader

	////// yes, this needs an own class
	////// offsets must be extracted, added, modified,
	////// come&go depending on being >0xffffff
	////class extraField {
	////	private:


	class CDFileHeader {
		private:
			void release()  //*** needed or can go?
			{
				if (filename)	delete [] filename;
				if (extraField)	delete [] extraField;
				if (comment)	delete [] comment;
				filename=0; filenameLen=0;
				extraField=0; extraFieldLen=0;
				comment=0; commentLen=0;
			}

			const static XMP_Uns32 SIG = 0x02014b50;

		public:
			const static XMP_Uns16	o_sig	=   0;	//0x02014b50
			const static XMP_Uns16	o_versionMadeBy		=   4;
			const static XMP_Uns16	o_extractVersion	=	6;
			const static XMP_Uns16	o_flags				=   8;
			const static XMP_Uns16	o_compression		=   10;
			const static XMP_Uns16	o_lastmodTime		=   12;
			const static XMP_Uns16	o_lastmodDate		=   14;
			const static XMP_Uns16	o_crc32				=   16;
			const static XMP_Uns16	o_sizeCompressed	=   20; // 16bit stub
			const static XMP_Uns16	o_sizeUncompressed	=   24; // 16bit stub
			const static XMP_Uns16	o_fileNameLength	=   28;
			const static XMP_Uns16	o_extraFieldLength	=   30;
			const static XMP_Uns16	o_commentLength		=   32;
			const static XMP_Uns16	o_diskNo			=   34;
			const static XMP_Uns16	o_internalAttribs	=   36;
			const static XMP_Uns16	o_externalAttribs	=   38;
			const static XMP_Uns16	o_offsetLocalHeader	=   42;	// 16bit stub
			//					total size is 4+12+12+10+8=46

			const static int FIXED_SIZE = 46;
			char fields[FIXED_SIZE];

			// do not bet on any zero-freeness,
			// certainly no zero termination (pascal strings),
			// treat as data blocks
			char* filename;
			char* extraField;
			char* comment;
			XMP_Uns16 filenameLen;
			XMP_Uns16 extraFieldLen;
			XMP_Uns16 commentLen;

			// full, real, parsed 64 bit values
			XMP_Int64 sizeUncompressed;
			XMP_Int64 sizeCompressed;
			XMP_Int64 offsetLocalHeader;

			CDFileHeader() : filename(0),extraField(0),comment(0),filenameLen(0),
				extraFieldLen(0),commentLen(0),sizeUncompressed(0),sizeCompressed(0),offsetLocalHeader(0)
			{
				memset(fields,'\0',FIXED_SIZE);
				//already arm with appropriate values where applicable:
				PutUns32LE(0x02014b50,	&fields[CDFileHeader::o_sig] );
				PutUns16LE(0x14,		&fields[CDFileHeader::o_extractVersion] );
			};

			// copy constructor
			CDFileHeader(const CDFileHeader& orig) : filename(0),extraField(0),comment(0),filenameLen(0),
				extraFieldLen(0),commentLen(0),sizeUncompressed(0),sizeCompressed(0),offsetLocalHeader(0)
			{
				memcpy(fields,orig.fields,FIXED_SIZE);
				if (orig.extraField)
				{
					extraFieldLen=orig.extraFieldLen;
					extraField = new char[extraFieldLen];
					memcpy(extraField , orig.extraField , extraFieldLen);
				}
				if (orig.filename)
				{
					filenameLen=orig.filenameLen;
					filename = new char[filenameLen];
					memcpy(filename , orig.filename , filenameLen);
				}
				if (orig.comment)
				{
					commentLen=orig.commentLen;
					comment = new char[commentLen];
					memcpy(comment , orig.comment , commentLen);
				}

				filenameLen   = orig.filenameLen;
				extraFieldLen = orig.extraFieldLen;
				commentLen    = orig.commentLen;

				sizeUncompressed = orig.sizeUncompressed;
				sizeCompressed = orig.sizeCompressed;
				offsetLocalHeader = orig.offsetLocalHeader;
			}

			// Assignment operator
			CDFileHeader& operator=(const CDFileHeader& obj)
			{
				XMP_Throw("not supported",kXMPErr_Unimplemented);
			}

			// reads entire structure from file (starting at current position)
			void read(XMP_IO* file)
			{
				this->release();

				file->ReadAll ( fields, FIXED_SIZE );
				XMP_Validate( SIG == GetUns32LE( &this->fields[CDFileHeader::o_sig] ), "invalid header", kXMPErr_BadFileFormat );

				filenameLen   = GetUns16LE( &this->fields[CDFileHeader::o_fileNameLength] );
				extraFieldLen = GetUns16LE( &this->fields[CDFileHeader::o_extraFieldLength] );
				commentLen    = GetUns16LE( &this->fields[CDFileHeader::o_commentLength] );

				if (filenameLen) {
					filename = new char[filenameLen];
					file->ReadAll ( filename, filenameLen );
				}
				if (extraFieldLen) {
					extraField = new char[extraFieldLen];
					file->ReadAll ( extraField, extraFieldLen );
				}
				if (commentLen) {
					comment = new char[commentLen];
					file->ReadAll ( comment, commentLen );
				}

				////// GET ACTUAL 64 BIT VALUES //////////////////////////////////////////////
				// get 32bit goodies first, correct later
				sizeUncompressed		= GetUns32LE( &fields[o_sizeUncompressed] );
				sizeCompressed			= GetUns32LE( &fields[o_sizeCompressed] );
				offsetLocalHeader		= GetUns32LE( &fields[o_offsetLocalHeader] );

				XMP_Int32 offset = 0;
				while ( offset < extraFieldLen )
				{
					XMP_Validate( (extraFieldLen - offset) >= 4, "need 4 bytes for next header ID+len", kXMPErr_BadFileFormat);
					XMP_Uns16 headerID = GetUns16LE( &extraField[offset] );
					XMP_Uns16 dataSize = GetUns16LE( &extraField[offset+2] );
					offset += 4;

					XMP_Validate( (extraFieldLen - offset) <= dataSize,
									"actual field lenght not given", kXMPErr_BadFileFormat);
					if ( headerID == 0x1 ) //we only care about "Zip64 extended information extra field"
					{
						XMP_Validate( offset < extraFieldLen, "extra field too short", kXMPErr_BadFileFormat);
						if (sizeUncompressed == 0xffffffff)
						{
							sizeUncompressed = GetUns64LE( &extraField[offset] );
							offset += 8;
						}
						if (sizeCompressed == 0xffffffff)
						{
							sizeCompressed = GetUns64LE( &extraField[offset] );
							offset += 8;
						}
						if (offsetLocalHeader == 0xffffffff)
						{
							offsetLocalHeader = GetUns64LE( &extraField[offset] );
							offset += 8;
						}
					}
					else
					{
						offset += dataSize;
					} // if
				} // while
			} // read()

			// writes structure to file (starting at current position)
			void write(XMP_IO* file)
			{
				//// WRITE BACK REAL 64 BIT VALUES, CREATE EXTRA FIELD ///////////////
				//may only wipe extra field after obtaining all Info from it
				if (extraField)	delete [] extraField;
				extraFieldLen=0;

				if ( ( sizeUncompressed  > 0xffffffff ) ||
					 ( sizeCompressed    > 0xffffffff ) ||
					 ( offsetLocalHeader > 0xffffffff )  )
				{
					extraField = new char[64]; // actual maxlen is 32
					extraFieldLen = 4; //first fields are for ID, size
					if ( sizeUncompressed > 0xffffffff )
					{
						PutUns64LE( sizeUncompressed, &extraField[extraFieldLen] );
						extraFieldLen += 8;
						sizeUncompressed = 0xffffffff;
					}
					if ( sizeCompressed > 0xffffffff )
					{
						PutUns64LE( sizeCompressed, &extraField[extraFieldLen] );
						extraFieldLen += 8;
						sizeCompressed = 0xffffffff;
					}
					if ( offsetLocalHeader > 0xffffffff )
					{
						PutUns64LE( offsetLocalHeader, &extraField[extraFieldLen] );
						extraFieldLen += 8;
						offsetLocalHeader = 0xffffffff;
					}

					//write ID, dataSize
					PutUns16LE( 0x0001, &extraField[0] );
					PutUns16LE( extraFieldLen-4, &extraField[2] );
					//extraFieldSize
					PutUns16LE( extraFieldLen, &this->fields[CDFileHeader::o_extraFieldLength] );
				}

				// write out 32-bit ('ff-stubs' or not)
				PutUns32LE( (XMP_Uns32)sizeUncompressed, &fields[o_sizeUncompressed] );
				PutUns32LE( (XMP_Uns32)sizeCompressed, &fields[o_sizeCompressed] );
				PutUns32LE( (XMP_Uns32)offsetLocalHeader, &fields[o_offsetLocalHeader] );

				/// WRITE /////////////////////////////////////////////////////////////////
				XMP_Enforce( SIG == GetUns32LE( &this->fields[CDFileHeader::o_sig] ) );

				file ->Write ( fields , FIXED_SIZE  );
				if (filenameLen)	file->Write ( filename   , filenameLen    );
				if (extraFieldLen)	file->Write ( extraField , extraFieldLen  );
				if (commentLen)		file->Write ( comment , commentLen  );
			}

			void setXMPFilename()
			{
				if (filename) delete [] filename;
				filenameLen = xmpFilenameLen;
				filename = new char[xmpFilenameLen];
				PutUns16LE(filenameLen,	&fields[CDFileHeader::o_fileNameLength] );
				memcpy(filename,"META-INF/metadata.xml",xmpFilenameLen);
			}

			XMP_Int64 size()
			{
				XMP_Int64 r = this->FIXED_SIZE + this->filenameLen + this->commentLen;
				// predict serialization size
				if (  (sizeUncompressed > 0xffffffff)||(sizeCompressed > 0xffffffff)||(offsetLocalHeader>0xffffffff) )
				{
					r += 4;		//extra fields necessary
					if (sizeUncompressed > 0xffffffff) r += 8;
					if (sizeCompressed > 0xffffffff) r += 8;
					if (offsetLocalHeader > 0xffffffff) r += 8;
				}
				return r;
			}

			~CDFileHeader()
			{
				this->release();
			};
	}; // class CDFileHeader

	class EndOfCD {
	private:
		const static XMP_Uns32 SIG = 0x06054b50;
		void UCFECD_Free()
		{
			if(commentLen) delete [] comment;
			commentLen = 0;
			comment = 0;
		}
	public:
		const static XMP_Int32 o_Sig = 0;
		const static XMP_Int32 o_CdNumEntriesDisk = 8;  // same-same for UCF, since single-volume
		const static XMP_Int32 o_CdNumEntriesTotal = 10;// must update both
		const static XMP_Int32 o_CdSize = 12;
		const static XMP_Int32 o_CdOffset = 16;
		const static XMP_Int32 o_CommentLen = 20;

		const static int FIXED_SIZE = 22;
		char fields[FIXED_SIZE];

		char* comment;
		XMP_Uns16 commentLen;

		EndOfCD() : comment(0), commentLen(0)
		{
			//nothing
		};

		void read (XMP_IO* file)
		{
			UCFECD_Free();

			file->ReadAll ( fields, FIXED_SIZE );
			XMP_Validate( this->SIG == GetUns32LE( &this->fields[o_Sig] ), "invalid header", kXMPErr_BadFileFormat );

			commentLen = GetUns16LE( &this->fields[o_CommentLen] );
			if(commentLen)
			{
				comment = new char[commentLen];
				file->ReadAll ( comment, commentLen );
			}
		};

		void write(XMP_IO* file)
		{
			XMP_Enforce( this->SIG == GetUns32LE( &this->fields[o_Sig] ) );
			commentLen   = GetUns16LE( &this->fields[o_CommentLen] );
			file ->Write ( fields , FIXED_SIZE  );
			if (commentLen)
				file->Write ( comment, commentLen );
		}

		~EndOfCD()
		{
			if (comment)	delete [] comment;
		};
	}; //class EndOfCD

	////////////////////////////////////////////////////////////////////////////////////
	// EMBEDDING MATH
	//
	// a = content files before xmp (always 0 thus ommited)
	// b/b2 = content files behind xmp (before/after injection)
	// x/x2 = offset xmp content header + content file (before/after injection)
	// cd/cd = central directory
	// h/h2 = end of central directory record
	XMP_Int64	b,b2,x,x2,cd,cd2,cdx,cdx2,z,z2,h,h2,
	// length thereof ('2' only where possibly different)
	// using XMP_Int64 here also for length (not XMP_Int32),
	// to be prepared for zip64, our LFA functions might need things in multiple chunks...
				al,bl,xl,x2l,cdl,cd2l,cdxl,cdx2l,z2l,hl,fl,f2l;
	XMP_Uns16	numCF,numCF2;

	bool wasCompressed;	// ..before, false if no prior xmp
	bool compressXMP;   // compress this time?
	bool inPlacePossible;
	/* bool isZip64; <=> z2 != 0 */

	FileHeader		xmpFileHeader;
	CDFileHeader	xmpCDHeader;

	XMP_StringPtr uncomprPacketStr;
	XMP_StringLen uncomprPacketLen;
	XMP_StringPtr finalPacketStr;
	XMP_StringLen finalPacketLen;
	std::vector<CDFileHeader> cdEntries;
	EndOfCD endOfCD;
	void writeOut( XMP_IO* sourceFile, XMP_IO* targetFile, bool isRewrite, bool isInPlace);

};	// UCF_MetaHandler

// =================================================================================================

#endif /* __UCF_Handler_hpp__ */