1 // =================================================================================================
2 // ADOBE SYSTEMS INCORPORATED
3 // Copyright 2002-2008 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 #if XMP_WinBuild
11 	#pragma warning ( disable : 4996 )	// '...' was declared deprecated
12 #endif
13 
14 #include "UCF_Handler.hpp"
15 
16 #include "zlib.h"
17 
18 #include <time.h>
19 
20 #ifdef DYNAMIC_CRC_TABLE
21 	#error "unexpectedly DYNAMIC_CRC_TABLE defined."
22 	//Must implement get_crc_table prior to any multi-threading (see notes there)
23 #endif
24 
25 using namespace std;
26 
27 // =================================================================================================
28 /// \file UCF_Handler.cpp
29 /// \brief UCF handler class
30 // =================================================================================================
31 const XMP_Uns16 xmpFilenameLen = 21;
32 const char* xmpFilename = "META-INF/metadata.xml";
33 
34 // =================================================================================================
35 // UCF_MetaHandlerCTor
36 // ====================
UCF_MetaHandlerCTor(XMPFiles * parent)37 XMPFileHandler* UCF_MetaHandlerCTor ( XMPFiles * parent )
38 {
39 	return new UCF_MetaHandler ( parent );
40 }	// UCF_MetaHandlerCTor
41 
42 // =================================================================================================
43 // UCF_CheckFormat
44 // ================
45 // * lenght must at least be 114 bytes
46 // * first bytes must be \x50\x4B\x03\x04 for *any* zip file
47 // * at offset 30 it must spell "mimetype"
48 
49 #define MIN_UCF_LENGTH 114
50 // zip minimum considerations:
51 // the shortest legal zip is 100 byte:
52 //  30+1* bytes file header
53 //+ 0 byte content file (uncompressed)
54 //+ 46+1* bytes central directory file header
55 //+ 22 byte end of central directory record
56 //-------
57 //100 bytes
58 //
59 //1 byte is the shortest legal filename. anything below is no valid zip.
60 //
61 //==> the mandatory+first "mimetype" content file has a filename length of 8 bytes,
62 //    thus even if empty (arguably incorrect but tolerable),
63 //    the shortest legal UCF is 114 bytes (30 + 8 + 0 + 46 + 8 + 22 )
64 //    anything below is with certainty not a valid ucf.
65 
UCF_CheckFormat(XMP_FileFormat format,XMP_StringPtr filePath,LFA_FileRef fileRef,XMPFiles * parent)66 bool UCF_CheckFormat (  XMP_FileFormat format,
67 						XMP_StringPtr  filePath,
68 						LFA_FileRef    fileRef,
69 						XMPFiles *     parent )
70 {
71 	// *not* using buffer functionality here, all we need
72 	// to detect UCF securely is in the first 38 bytes...
73 	IgnoreParam(filePath); IgnoreParam(parent);	//suppress warnings
74 	XMP_Assert ( format == kXMP_UCFFile );								//standard assert
75 
76 	XMP_Uns8 buffer[MIN_UCF_LENGTH];
77 
78 	LFA_Seek ( fileRef, 0, SEEK_SET );
79 	if ( MIN_UCF_LENGTH != LFA_Read ( fileRef, buffer, MIN_UCF_LENGTH) ) //NO requireall (->no throw), just return false
80 		return false;
81 	if ( !CheckBytes ( &buffer[0], "\x50\x4B\x03\x04", 4 ) ) // "PK 03 04"
82 		return false;
83 	// UCF spec says: there must be a content file mimetype, and be first and be uncompressed...
84 	if ( !CheckBytes ( &buffer[30], "mimetype", 8 ) )
85 		return false;
86 
87 	//////////////////////////////////////////////////////////////////////////////
88 	//figure out mimetype, decide on writeability
89 	// grab mimetype
90 	LFA_Seek( fileRef, 18, SEEK_SET );
91 	XMP_Uns32 mimeLength    = LFA_ReadUns32_LE ( fileRef );
92 	XMP_Uns32 mimeCompressedLength = LFA_ReadUns32_LE ( fileRef );	 // must be same since uncompressed
93 
94 	XMP_Validate( mimeLength == mimeCompressedLength,
95 				  "mimetype compressed and uncompressed length differ",
96 				  kXMPErr_BadFileFormat );
97 
98 	XMP_Validate( mimeLength != 0, "0-byte mimetype", kXMPErr_BadFileFormat );
99 
100 	// determine writability based on mimetype
101 	LFA_Seek( fileRef, 30 + 8, SEEK_SET );
102 	char* mimetype = new char[ mimeLength + 1 ];
103 	LFA_Read( fileRef, mimetype, mimeLength, true );
104 	mimetype[mimeLength] = '\0';
105 
106 	bool okMimetype;
107 
108 	// be lenient on extraneous CR (0xA) [non-XMP bug #16980028]
109 	if ( mimeLength > 0 ) //avoid potential crash (will properly fail below anyhow)
110 		if ( mimetype[mimeLength-1] == 0xA )
111 			mimetype[mimeLength-1] = '\0';
112 
113 	if (
114 		XMP_LitMatch( mimetype, "application/vnd.adobe.xfl"    ) ||  //Flash Diesel team
115 		XMP_LitMatch( mimetype, "application/vnd.adobe.xfl+zip") ||  //Flash Diesel team
116 		XMP_LitMatch( mimetype, "application/vnd.adobe.x-mars" ) ||  //Mars plugin(labs only), Acrobat8
117 		XMP_LitMatch( mimetype, "application/vnd.adobe.pdfxml" ) ||  //Mars plugin(labs only), Acrobat 9
118 		XMP_LitMatch( mimetype, "vnd.adobe.x-asnd"             ) ||  //Adobe Sound Document (Soundbooth Team)
119 		XMP_LitMatch( mimetype, "application/vnd.adobe.indesign-idml-package" ) ) //inCopy (inDesign) IDML Document
120 
121 		// *** ==> unknown are also treated as not acceptable
122 		okMimetype = true;
123 	else
124 		okMimetype = false;
125 
126 	// not accepted (neither read nor write
127 	//.air  - Adobe Air Files
128 	//application/vnd.adobe.air-application-installer-package+zip
129 	//.airi - temporary Adobe Air Files
130 	//application/vnd.adobe.air-application-intermediate-package+zip
131 
132 	delete[] mimetype;
133 	return okMimetype;
134 
135 }	// UCF_CheckFormat
136 
137 // =================================================================================================
138 // UCF_MetaHandler::UCF_MetaHandler
139 // ==================================
140 
UCF_MetaHandler(XMPFiles * _parent)141 UCF_MetaHandler::UCF_MetaHandler ( XMPFiles * _parent )
142 {
143 	this->parent = _parent;
144 	this->handlerFlags = kUCF_HandlerFlags;
145 	this->stdCharForm  = kXMP_Char8Bit;
146 }	// UCF_MetaHandler::UCF_MetaHandler
147 
148 // =================================================================================================
149 // UCF_MetaHandler::~UCF_MetaHandler
150 // =====================================
151 
~UCF_MetaHandler()152 UCF_MetaHandler::~UCF_MetaHandler()
153 {
154 	// nothing
155 }
156 
157 // =================================================================================================
158 // UCF_MetaHandler::CacheFileData
159 // ===============================
160 //
CacheFileData()161 void UCF_MetaHandler::CacheFileData()
162 {
163 	//*** abort procedures
164 	this->containsXMP = false;		//assume no XMP for now (beware of exceptions...)
165 	LFA_FileRef file = this->parent->fileRef;
166 	XMP_PacketInfo &packetInfo = this->packetInfo;
167 
168 	// clear file positioning info ---------------------------------------------------
169 	b=0;b2=0;x=0;x2=0;cd=0;cd2=0;cdx=0;cdx2=0;h=0;h2=0,fl=0;f2l=0;
170 	al=0;bl=0;xl=0;x2l=0;cdl=0;cd2l=0;cdxl=0;cdx2l=0;hl=0,z=0,z2=0,z2l=0;
171 	numCF=0;numCF2=0;
172 	wasCompressed = false;
173 
174 	// -------------------------------------------------------------------------------
175 	fl=LFA_Measure( file );
176 	if ( fl < MIN_UCF_LENGTH )	XMP_Throw("file too short, can't be correct UCF",kXMPErr_Unimplemented);
177 
178 	//////////////////////////////////////////////////////////////////////////////
179 	// find central directory before optional comment
180 	// things have to go bottom-up, since description headers are allowed in UCF
181 	// "scan backwards" until feasible field found (plus sig sanity check)
182 	// OS buffering should be smart enough, so not doing anything on top
183 	// plus almost all comments will be zero or rather short
184 
185 	//no need to check anything but the 21 chars of "METADATA-INF/metadata.xml"
186 	char filenameToTest[22];
187 	filenameToTest[21]='\0';
188 
189 	XMP_Int32 zipCommentLen = 0;
190 	for ( ; zipCommentLen <= EndOfDirectory::COMMENT_MAX; zipCommentLen++ )
191 	{
192 		LFA_Seek( file, -zipCommentLen -2, SEEK_END );
193 		if ( LFA_ReadUns16_LE( file ) == zipCommentLen )	//found it?
194 		{
195 			//double check, might just look like comment length (actually be 'evil' comment)
196 			LFA_Seek( file , - EndOfDirectory::FIXED_SIZE, SEEK_CUR );
197 			if ( LFA_ReadUns32_LE( file ) == EndOfDirectory::ID ) break; //heureka, directory ID
198 			// 'else': pretend nothing happended, just go on
199 		}
200 	}
201 	//was it a break or just not found ?
202 	if ( zipCommentLen > EndOfDirectory::COMMENT_MAX ) XMP_Throw( "zip broken near end or invalid comment" , kXMPErr_BadFileFormat );
203 
204 	////////////////////////////////////////////////////////////////////////////
205 	//read central directory
206 	hl = zipCommentLen + EndOfDirectory::FIXED_SIZE;
207 	h = fl - hl;
208 	LFA_Seek( file , h , SEEK_SET );
209 
210 	if ( LFA_ReadUns32_LE( file ) != EndOfDirectory::ID )
211 		XMP_Throw("directory header id not found. or broken comment",kXMPErr_BadFileFormat);
212 	if ( LFA_ReadUns16_LE( file ) != 0 )
213 		XMP_Throw("UCF must be 'first' zip volume",kXMPErr_BadFileFormat);
214 	if ( LFA_ReadUns16_LE( file ) != 0 )
215 		XMP_Throw("UCF must be single-volume zip",kXMPErr_BadFileFormat);
216 
217 	numCF = LFA_ReadUns16_LE( file ); //number of content files
218 	if ( numCF != LFA_ReadUns16_LE( file ) )
219 		XMP_Throw( "per volume and total number of dirs differ" , kXMPErr_BadFileFormat );
220 	cdl = LFA_ReadUns32_LE( file );
221 	cd = LFA_ReadUns32_LE( file );
222 	LFA_Seek( file, 2,SEEK_CUR ); //skip comment len, needed since next LFA is SEEK_CUR !
223 
224 	//////////////////////////////////////////////////////////////////////////////
225 	// check for zip64-end-of-CD-locator/ zip64-end-of-CD
226 	// to to central directory
227 	if ( cd == 0xffffffff )
228 	{	// deal with zip 64, otherwise continue
229 		XMP_Int64 tmp = LFA_Seek( file,
230 				- EndOfDirectory::FIXED_SIZE  - Zip64Locator::TOTAL_SIZE,
231 				  SEEK_CUR ); //go to begining of zip64 locator
232 		//relative movement , absolute would imho only require another -zipCommentLen
233 
234 		if (  Zip64Locator::ID == LFA_ReadUns32_LE(file) ) // prevent 'coincidental length' ffffffff
235 		{
236 			XMP_Validate( 0 == LFA_ReadUns32_LE(file),
237 					"zip64 CD disk must be 0", kXMPErr_BadFileFormat );
238 
239 			z = LFA_ReadUns64_LE(file);
240 			XMP_Validate( z < 0xffffffffffffLL, "file in terrabyte range?", kXMPErr_BadFileFormat ); // 3* ffff, sanity test
241 
242 			XMP_Uns32 totalNumOfDisks = LFA_ReadUns32_LE(file);
243 			/* tolerated while pkglib bug #1742179 */
244 			XMP_Validate( totalNumOfDisks == 0 || totalNumOfDisks == 1,
245 					"zip64 total num of disks must be 0", kXMPErr_BadFileFormat );
246 
247 			///////////////////////////////////////////////
248 			/// on to end-of-CD itself
249 			LFA_Seek( file, z, SEEK_SET );
250 			XMP_Validate( Zip64EndOfDirectory::ID == LFA_ReadUns32_LE(file),
251 				   "invalid zip64 end of CD sig", kXMPErr_BadFileFormat );
252 
253 			XMP_Int64 sizeOfZip64EOD = LFA_ReadUns64_LE(file);
254 			LFA_Seek(file, 12, SEEK_CUR );
255 			//yes twice "total" and "per disk"
256 			XMP_Int64 tmp64 = LFA_ReadUns64_LE(file);
257 			XMP_Validate( tmp64 == numCF, "num of content files differs to zip64 (1)", kXMPErr_BadFileFormat );
258 			tmp64 = LFA_ReadUns64_LE(file);
259 			XMP_Validate( tmp64 == numCF, "num of content files differs to zip64 (2)", kXMPErr_BadFileFormat );
260 			// cd length verification
261 			tmp64 = LFA_ReadUns64_LE(file);
262 			XMP_Validate( tmp64 == cdl, "CD length differs in zip64", kXMPErr_BadFileFormat );
263 
264 			cd = LFA_ReadUns64_LE(file); // wipe out invalid 0xffffffff with the real thing
265 			//ignoring "extensible data sector (would need fullLength - fixed length) for now
266 		}
267 	} // of zip64 fork
268 	/////////////////////////////////////////////////////////////////////////////
269 	// parse central directory
270 	// 'foundXMP' <=> cdx != 0
271 
272 	LFA_Seek( file, cd, SEEK_SET );
273 	XMP_Int64 cdx_suspect=0;
274 	XMP_Int64 cdxl_suspect=0;
275 	CDFileHeader curCDHeader;
276 
277 	for ( XMP_Uns16 entryNum=1 ; entryNum <= numCF  ; entryNum++ )
278 	{
279 		cdx_suspect =  LFA_Tell( file ); //just suspect for now
280 		curCDHeader.read( file );
281 
282 		if ( GetUns32LE( &curCDHeader.fields[CDFileHeader::o_sig] ) != 0x02014b50 )
283 			XMP_Throw("&invalid file header",kXMPErr_BadFileFormat);
284 
285 		cdxl_suspect = curCDHeader.FIXED_SIZE +
286 			GetUns16LE(&curCDHeader.fields[CDFileHeader::o_fileNameLength]) +
287 			GetUns16LE(&curCDHeader.fields[CDFileHeader::o_extraFieldLength]) +
288 			GetUns16LE(&curCDHeader.fields[CDFileHeader::o_commentLength]);
289 
290 		// we only look 21 characters, that's META-INF/metadata.xml, no \0 attached
291 		if ( curCDHeader.filenameLen == xmpFilenameLen /*21*/ )
292 			if( XMP_LitNMatch( curCDHeader.filename , "META-INF/metadata.xml", 21 ) )
293 				{
294 					cdx  = cdx_suspect;
295 					cdxl = cdxl_suspect;
296 					break;
297 				}
298 		//hop to next
299 		LFA_Seek( file, cdx_suspect + cdxl_suspect , SEEK_SET );
300 	} //for-loop, iterating *all* central directory headers (also beyond found)
301 
302 	if ( !cdx ) // not found xmp
303 	{
304 		// b and bl remain 0, x and xl remain 0
305 		// ==> a is everything before directory
306 		al = cd;
307 		return;
308 	}
309 
310 	// from here is if-found-only
311 	//////////////////////////////////////////////////////////////////////////////
312 	//CD values needed, most serve counter-validation purposes (below) only
313 	// read whole object (incl. all 3 fields) again properly
314 	//  to get extra Fields, etc
315 	LFA_Seek( file, cdx, SEEK_SET );
316 	xmpCDHeader.read( file );
317 
318 	XMP_Validate( xmpFilenameLen == GetUns16LE( &xmpCDHeader.fields[CDFileHeader::o_fileNameLength]),
319 		"content file length not ok", kXMPErr_BadFileFormat );
320 
321 	XMP_Uns16 CD_compression		= GetUns16LE( &xmpCDHeader.fields[CDFileHeader::o_compression] );
322 	XMP_Validate(( CD_compression == 0 || CD_compression == 0x08),
323 		"illegal compression, must be flate or none", kXMPErr_BadFileFormat );
324 	XMP_Uns16 CD_flags				= GetUns16LE( &xmpCDHeader.fields[CDFileHeader::o_flags] );
325 	XMP_Uns32 CD_crc				= GetUns32LE( &xmpCDHeader.fields[CDFileHeader::o_crc32] );
326 
327 	// parse (actual, non-CD!) file header ////////////////////////////////////////////////
328 	x  = xmpCDHeader.offsetLocalHeader;
329 	LFA_Seek( file , x ,SEEK_SET);
330 	xmpFileHeader.read( file );
331 	xl = xmpFileHeader.sizeHeader() + xmpCDHeader.sizeCompressed;
332 
333 	//values needed
334 	XMP_Uns16 fileNameLength	= GetUns16LE( &xmpFileHeader.fields[FileHeader::o_fileNameLength] );
335 	XMP_Uns16 extraFieldLength	= GetUns16LE( &xmpFileHeader.fields[FileHeader::o_extraFieldLength] );
336 	XMP_Uns16 compression       = GetUns16LE( &xmpFileHeader.fields[FileHeader::o_compression] );
337 	XMP_Uns32 sig				= GetUns32LE( &xmpFileHeader.fields[FileHeader::o_sig] );
338 	XMP_Uns16 flags				= GetUns16LE( &xmpFileHeader.fields[FileHeader::o_flags] );
339 	XMP_Uns32 sizeCompressed    = GetUns32LE( &xmpFileHeader.fields[FileHeader::o_sizeCompressed] );
340 	XMP_Uns32 sizeUncompressed  = GetUns32LE( &xmpFileHeader.fields[FileHeader::o_sizeUncompressed] );
341 	XMP_Uns32 crc				= GetUns32LE( &xmpFileHeader.fields[FileHeader::o_crc32] );
342 
343 	// check filename
344 	XMP_Validate( fileNameLength == 21, "filename size contradiction" , kXMPErr_BadFileFormat );
345 	XMP_Enforce ( xmpFileHeader.filename != 0 );
346 	XMP_Validate( !memcmp( "META-INF/metadata.xml", xmpFileHeader.filename , xmpFilenameLen ) , "filename is cf header is not META-INF/metadata.xml" , kXMPErr_BadFileFormat );
347 
348 	// deal with data descriptor if needed
349 	if ( flags & FileHeader::kdataDescriptorFlag )
350 	{
351 		if ( sizeCompressed!=0 || sizeUncompressed!=0 || crc!=0 )	XMP_Throw("data descriptor must mean 3x zero",kXMPErr_BadFileFormat);
352 		LFA_Seek( file, xmpCDHeader.sizeCompressed + fileNameLength + xmpCDHeader.extraFieldLen, SEEK_CUR); //skip actual data to get to descriptor
353 		crc = LFA_ReadUns32_LE( file );
354 		if ( crc == 0x08074b50 ) //data descriptor may or may not have signature (see spec)
355 		{
356 			crc = LFA_ReadUns32_LE( file ); //if it does, re-read
357 		}
358 		sizeCompressed = LFA_ReadUns32_LE( file );
359 		sizeUncompressed = LFA_ReadUns32_LE( file );
360 		// *** cater for zip64 plus 'streamed' data-descriptor stuff
361 	}
362 
363 	// more integrity checks (post data descriptor handling)
364 	if ( sig != 0x04034b50 )						XMP_Throw("invalid content file header",kXMPErr_BadFileFormat);
365 	if ( compression != CD_compression )			XMP_Throw("compression contradiction",kXMPErr_BadFileFormat);
366 	if ( sizeUncompressed != xmpCDHeader.sizeUncompressed )	XMP_Throw("contradicting uncompressed lengths",kXMPErr_BadFileFormat);
367 	if ( sizeCompressed != xmpCDHeader.sizeCompressed ) XMP_Throw("contradicting compressed lengths",kXMPErr_BadFileFormat);
368 	if ( sizeUncompressed == 0 )					XMP_Throw("0-byte uncompressed size", kXMPErr_BadFileFormat );
369 
370 	////////////////////////////////////////////////////////////////////
371 	// packet Info
372 	this->packetInfo.charForm = stdCharForm;
373 	this->packetInfo.writeable = false;
374 	this->packetInfo.offset = kXMPFiles_UnknownOffset; // checksum!, hide position to not give funny ideas
375 	this->packetInfo.length = kXMPFiles_UnknownLength;
376 
377 	////////////////////////////////////////////////////////////////////
378 	// prepare packet (compressed or not)
379 	this->xmpPacket.erase();
380 	this->xmpPacket.reserve( sizeUncompressed );
381 	this->xmpPacket.append( sizeUncompressed, ' ' );
382 	XMP_StringPtr packetStr = XMP_StringPtr ( xmpPacket.c_str() );	// only set after reserving the space!
383 
384 	// go to packet offset
385 	LFA_Seek ( file, x + xmpFileHeader.FIXED_SIZE + fileNameLength + extraFieldLength , SEEK_SET);
386 
387 	// compression fork --------------------------------------------------
388 	switch (compression)
389 	{
390 	case 0x8: // FLATE
391 		{
392 			wasCompressed = true;
393 			XMP_Uns32 bytesRead = 0;
394 			XMP_Uns32 bytesWritten = 0; // for writing into packetString
395 			const unsigned int CHUNK = 16384;
396 
397 			int ret;
398 			unsigned int have; //added type
399 			z_stream strm;
400 			unsigned char in[CHUNK];
401 			unsigned char out[CHUNK];
402 			// does need this intermediate stage, no direct compressio to packetStr possible,
403 			// since also partially filled buffers must be picked up. That's how it works.
404 			// in addition: internal zlib variables might have 16 bit limits...
405 
406 			/* allocate inflate state */
407 			strm.zalloc = Z_NULL;
408 			strm.zfree = Z_NULL;
409 			strm.opaque = Z_NULL;
410 			strm.avail_in = 0;
411 			strm.next_in = Z_NULL;
412 
413 			/* must use windowBits = -15, for raw inflate, no zlib header */
414 			ret = inflateInit2(&strm,-MAX_WBITS);
415 
416 			if (ret != Z_OK)
417 				XMP_Throw("zlib error ",kXMPErr_ExternalFailure);
418 
419 			/* decompress until deflate stream ends or end of file */
420 			do {
421 				// must take care here not to read too much, thus whichever is smaller:
422 				XMP_Int32 bytesRemaining = sizeCompressed - bytesRead;
423 				if ( (XMP_Int32)CHUNK < bytesRemaining ) bytesRemaining = (XMP_Int32)CHUNK;
424 				strm.avail_in=LFA_Read( file , in , bytesRemaining , true );
425 				bytesRead += strm.avail_in; // NB: avail_in is "unsigned_int", so might be 16 bit (not harmfull)
426 
427 				if (strm.avail_in == 0) break;
428 				strm.next_in = in;
429 
430 				do {
431 					strm.avail_out = CHUNK;
432 					strm.next_out = out;
433 					ret = inflate(&strm, Z_NO_FLUSH);
434 					XMP_Assert( ret != Z_STREAM_ERROR );	/* state not clobbered */
435 					switch (ret)
436 					{
437 					case Z_NEED_DICT:
438 						(void)inflateEnd(&strm);
439 						XMP_Throw("zlib error: Z_NEED_DICT",kXMPErr_ExternalFailure);
440 					case Z_DATA_ERROR:
441 						(void)inflateEnd(&strm);
442 						XMP_Throw("zlib error: Z_DATA_ERROR",kXMPErr_ExternalFailure);
443 					case Z_MEM_ERROR:
444 						(void)inflateEnd(&strm);
445 						XMP_Throw("zlib error: Z_MEM_ERROR",kXMPErr_ExternalFailure);
446 					}
447 
448 					have = CHUNK - strm.avail_out;
449 					memcpy( (unsigned char*) packetStr + bytesWritten , out , have );
450 					bytesWritten += have;
451 
452 				} while (strm.avail_out == 0);
453 
454 				/* it's done when inflate() says it's done */
455 			} while (ret != Z_STREAM_END);
456 
457 			/* clean up and return */
458 			(void)inflateEnd(&strm);
459 			if (ret != Z_STREAM_END)
460 				XMP_Throw("zlib error ",kXMPErr_ExternalFailure);
461 			break;
462 		}
463 	case 0x0:	// no compression - read directly into the right place
464 		{
465 			wasCompressed = false;
466 			XMP_Enforce( LFA_Read ( file, (char*)packetStr, sizeUncompressed, kLFA_RequireAll ) );
467 			break;
468 		}
469 	default:
470 		{
471 			XMP_Throw("illegal zip compression method (not none, not flate)",kXMPErr_BadFileFormat);
472 		}
473 	}
474 	this->containsXMP = true; // do this last, after all possible failure/execptions
475 }
476 
477 
478 // =================================================================================================
479 // UCF_MetaHandler::ProcessXMP
480 // ============================
481 
ProcessXMP()482 void UCF_MetaHandler::ProcessXMP()
483 {
484 	// we have no legacy, CacheFileData did all that was needed
485 	// ==> default implementation is fine
486 	XMPFileHandler::ProcessXMP();
487 }
488 
489 // =================================================================================================
490 // UCF_MetaHandler::UpdateFile
491 // =============================
492 
493 // TODO: xmp packet with data descriptor
494 
UpdateFile(bool doSafeUpdate)495 void UCF_MetaHandler::UpdateFile ( bool doSafeUpdate )
496 {
497 	//sanity
498 	XMP_Enforce( (x!=0) == (cdx!=0) );
499 	if (!cdx)
500 		xmpCDHeader.setXMPFilename();	//if new, set filename (impacts length, thus before computation)
501 	if ( ! this->needsUpdate )
502 		return;
503 
504 	// ***
505 	if ( doSafeUpdate ) XMP_Throw ( "UCF_MetaHandler::UpdateFile: Safe update not supported", kXMPErr_Unavailable );
506 
507 	LFA_FileRef file = this->parent->fileRef;
508 
509 	// final may mean compressed or not, whatever is to-be-embedded
510 	uncomprPacketStr = xmpPacket.c_str();
511 	uncomprPacketLen = (XMP_StringLen) xmpPacket.size();
512 	finalPacketStr = uncomprPacketStr;		// will be overriden if compressedXMP==true
513 	finalPacketLen = uncomprPacketLen;
514 	std::string compressedPacket;	// moot if non-compressed, still here for scope reasons (having to keep a .c_str() alive)
515 
516 	if ( !x ) // if new XMP...
517 	{
518 		xmpFileHeader.clear();
519 		xmpFileHeader.setXMPFilename();
520 		// ZIP64 TODO: extra Fields, impact on cdxl2 and x2l
521 	}
522 
523 	////////////////////////////////////////////////////////////////////////////////////////////////
524 	// COMPRESSION DECISION
525 
526 	// for large files compression is bad:
527 	// a) size of XMP becomes irrelevant on large files ==> why worry over compression ?
528 	// b) more importantly: no such thing as padding possible, compression ==  ever changing sizes
529 	//    => never in-place rewrites, *ugly* performance impact on large files
530 	inPlacePossible = false; //assume for now
531 
532 	if ( !x )	// no prior XMP? -> decide on filesize
533 		compressXMP = ( fl > 1024*50 /* 100 kB */ ) ? false : true;
534 	else
535 		compressXMP = wasCompressed;	// don't change a thing
536 
537 	if ( !wasCompressed && !compressXMP &&
538 		( GetUns32LE( &xmpFileHeader.fields[FileHeader::o_sizeUncompressed] ) == uncomprPacketLen ))
539 	{
540 			inPlacePossible = true;
541 	}
542 	////////////////////////////////////////////////////////////////////////////////////////////////
543 	// COMPRESS XMP
544 	if ( compressXMP )
545 	{
546 		const unsigned int CHUNK = 16384;
547 		int ret, flush;
548 		unsigned int have;
549 		z_stream strm;
550 		unsigned char out[CHUNK];
551 
552 		/* allocate deflate state */
553 		strm.zalloc = Z_NULL; strm.zfree = Z_NULL; strm.opaque = Z_NULL;
554 		if ( deflateInit2(&strm, Z_DEFAULT_COMPRESSION, Z_DEFLATED, -MAX_WBITS, 8 /*memlevel*/, Z_DEFAULT_STRATEGY) )
555 			XMP_Throw("zlib error ",kXMPErr_ExternalFailure);
556 
557 		//write at once, since we got it in mem anyway:
558 		strm.avail_in = uncomprPacketLen;
559 		flush = Z_FINISH; // that's all, folks
560 		strm.next_in = (unsigned char*) uncomprPacketStr;
561 
562 		do {
563 			strm.avail_out = CHUNK;
564 			strm.next_out = out;
565 
566 			ret = deflate(&strm, flush);    /* no bad return value (!=0 acceptable) */
567 			XMP_Enforce(ret != Z_STREAM_ERROR); /* state not clobbered */
568 			//fwrite(buffer,size,count,file)
569 			have = CHUNK - strm.avail_out;
570 			compressedPacket.append( (const char*) out,  have);
571 		} while (strm.avail_out == 0);
572 
573 		if (ret != Z_STREAM_END)
574 			XMP_Throw("zlib stream incomplete ",kXMPErr_ExternalFailure);
575 		XMP_Enforce(strm.avail_in == 0);		// all input will be used
576 		(void)deflateEnd(&strm);				//clean up (do prior to checks)
577 
578 		finalPacketStr = compressedPacket.c_str();
579 		finalPacketLen = (XMP_StringLen)compressedPacket.size();
580 	}
581 
582 	PutUns32LE ( uncomprPacketLen,	&xmpFileHeader.fields[FileHeader::o_sizeUncompressed] );
583 	PutUns32LE ( finalPacketLen,	&xmpFileHeader.fields[FileHeader::o_sizeCompressed]   );
584 	PutUns16LE ( compressXMP ? 8:0,	&xmpFileHeader.fields[FileHeader::o_compression]      );
585 
586 	////////////////////////////////////////////////////////////////////////////////////////////////
587 	// CRC (always of uncompressed data)
588 	XMP_Uns32 crc = crc32( 0 , (Bytef*)uncomprPacketStr, uncomprPacketLen );
589 	PutUns32LE( crc, &xmpFileHeader.fields[FileHeader::o_crc32] );
590 
591 	////////////////////////////////////////////////////////////////////////////////////////////////
592 	// TIME calculation for timestamp
593 	// will be applied both to xmp content file and CD header
594 	XMP_Uns16 lastModTime, lastModDate;
595 	XMP_DateTime time;
596 	SXMPUtils::CurrentDateTime( &time );
597 
598 	if ( (time.year - 1900) < 80)
599 	{
600 		lastModTime = 0; // 1.1.1980 00:00h
601 		lastModDate = 21;
602 	}
603 
604 	// typedef unsigned short  ush; //2 bytes
605 	lastModDate = (XMP_Uns16) (((time.year) - 1980 ) << 9  | ((time.month) << 5) | time.day);
606 	lastModTime = ((XMP_Uns16)time.hour << 11) | ((XMP_Uns16)time.minute << 5) | ((XMP_Uns16)time.second >> 1);
607 
608 	PutUns16LE ( lastModDate,	&xmpFileHeader.fields[FileHeader::o_lastmodDate]      );
609 	PutUns16LE ( lastModTime,	&xmpFileHeader.fields[FileHeader::o_lastmodTime]      );
610 
611 	////////////////////////////////////////////////////////////////////////////////////////////////
612 	// adjustments depending on 4GB Border,
613 	//   decisions on in-place update
614 	//   so far only z, zl have been determined
615 
616 	// Zip64 related assurances, see (15)
617 	XMP_Enforce(!z2);
618 	XMP_Enforce(h+hl == fl );
619 
620 	////////////////////////////////////////////////////////////////////////////////////////////////
621 	// COMPUTE MISSING VARIABLES
622 	// A - based on xmp existence
623 	//
624 	// already known: x, xl, cd
625 	// most left side vars,
626 	//
627 	// finalPacketStr, finalPacketLen
628 
629 	if ( x ) // previous xmp?
630 	{
631 		al = x;
632 		b  = x + xl;
633 		bl = cd - b;
634 	}
635 	else
636 	{
637 		al = cd;
638 		//b,bl left at zero
639 	}
640 
641 	if ( inPlacePossible )
642 	{ // leave xmp right after A
643 		x2 = al;
644 		x2l = xmpFileHeader.sizeTotalCF(); //COULDDO: assert (x2l == xl)
645 		if (b) b2 = x2 + x2l; // b follows x as last content part
646 		cd2 = b2 + bl;	// CD follows B2
647 	}
648 	else
649 	{ // move xmp to end
650 		if (b) b2 = al; // b follows
651 		// x follows as last content part (B existing or not)
652 		x2 = al + bl;
653 		x2l = xmpFileHeader.sizeTotalCF();
654 		cd2 = x2 + x2l;	// CD follows X
655 	}
656 
657 	/// create new XMP header ///////////////////////////////////////////////////
658 	// written into actual fields + generation of extraField at .write()-time...
659 	// however has impact on .size() computation  --  thus enter before cdx2l computation
660 	xmpCDHeader.sizeUncompressed = uncomprPacketLen;
661 	xmpCDHeader.sizeCompressed = finalPacketLen;
662 	xmpCDHeader.offsetLocalHeader = x2;
663 	PutUns32LE ( crc,				&xmpCDHeader.fields[CDFileHeader::o_crc32] );
664 	PutUns16LE ( compressXMP ? 8:0,	&xmpCDHeader.fields[CDFileHeader::o_compression] );
665 	PutUns16LE ( lastModDate,		&xmpCDHeader.fields[CDFileHeader::o_lastmodDate] );
666 	PutUns16LE ( lastModTime,		&xmpCDHeader.fields[CDFileHeader::o_lastmodTime] );
667 
668 	// for
669 	if ( inPlacePossible )
670 	{
671 		cdx2 = cdx;	//same, same
672 		writeOut( file, file, false, true );
673 		return;
674 	}
675 
676 	////////////////////////////////////////////////////////////////////////
677 	// temporarily store (those few, small) trailing things that might not survive the move around:
678 	LFA_Seek(file, cd, SEEK_SET);	// seek to central directory
679 	cdEntries.clear(); //mac precaution
680 
681 	//////////////////////////////////////////////////////////////////////////////
682 	// parse headers
683 	//   * stick together output header list
684 	cd2l = 0; //sum up below
685 
686 	CDFileHeader tempHeader;
687 	for( XMP_Uns16 pos=1 ; pos <= numCF ; pos++ )
688 	{
689 		if ( (cdx) && (LFA_Tell( file ) == cdx) )
690 		{
691 			tempHeader.read( file ); //read, even if not use, to advance file pointer
692 		}
693 		else
694 		{
695 			tempHeader.read( file );
696 			// adjust b2 offset for files that were behind the xmp:
697 			//        may (if xmp moved to back)
698 			//        or may not (inPlace Update) make a difference
699 			if ( (x) && ( tempHeader.offsetLocalHeader > x) ) // if xmp existed before and this was a file behind it
700 				tempHeader.offsetLocalHeader += b2 - b;
701 			cd2l += tempHeader.size(); // prior offset change might have impact
702 			cdEntries.push_back( tempHeader );
703 		}
704 	}
705 
706 	//push in XMP packet as last one (new or not)
707 	cdEntries.push_back( xmpCDHeader );
708 	cdx2l = xmpCDHeader.size();
709 	cd2l += cdx2l; // true, no matter which order
710 
711 	//OLD  cd2l = : cdl - cdxl + cdx2l;	// (NB: cdxl might be 0)
712 	numCF2 = numCF + ( (cdx)?0:1 );	//xmp packet for the first time? -> add one more CF
713 
714 	XMP_Validate( numCF2 > 0, "no content files", kXMPErr_BadFileFormat );
715 	XMP_Validate( numCF2 <= 0xFFFE, "max number of 0xFFFE entries reached", kXMPErr_BadFileFormat );
716 
717 	cdx2 = cd2 + cd2l - cdx2l;	// xmp content entry comes last (since beyond inPlace Update)
718 
719 	// zip64 decision
720 	if ( ( cd2 + cd2l + hl ) > 0xffffffff )	// predict non-zip size ==> do we need a zip-64?
721 	{
722 		z2 = cd2 + cd2l;
723 		z2l = Zip64EndOfDirectory::FIXED_SIZE + Zip64Locator::TOTAL_SIZE;
724 	}
725 
726 	// header and output length,
727 	h2 = cd2 + cd2l + z2l; // (z2l might be 0)
728 	f2l = h2 + hl;
729 
730 	////////////////////////////////////////////////////////////////////////////////////////////////
731 	// read H (endOfCD), correct offset
732 	LFA_Seek(file, h, SEEK_SET);
733 
734 	endOfCD.read( file );
735 	if ( cd2 <= 0xffffffff )
736 		PutUns32LE( (XMP_Int32) cd2 , &endOfCD.fields[ endOfCD.o_CdOffset ] );
737 	else
738 		PutUns32LE( 0xffffffff , &endOfCD.fields[ endOfCD.o_CdOffset ] );
739 	PutUns16LE( numCF2, &endOfCD.fields[ endOfCD.o_CdNumEntriesDisk   ] );
740 	PutUns16LE( numCF2, &endOfCD.fields[ endOfCD.o_CdNumEntriesTotal  ] );
741 
742 	XMP_Enforce( cd2l <= 0xffffffff ); // _size_ of directory itself certainly under 4GB
743 	PutUns32LE( (XMP_Uns32)cd2l,   &endOfCD.fields[ endOfCD.o_CdSize ] );
744 
745 	////////////////////////////////////////////////////////////////////////////////////////////////
746 	// MOVING
747 	writeOut( file, file, false, false );
748 
749 	this->needsUpdate = false; //do last for safety reasons
750 }	// UCF_MetaHandler::UpdateFile
751 
752 // =================================================================================================
753 // UCF_MetaHandler::WriteFile
754 // ============================
WriteFile(LFA_FileRef sourceRef,const std::string & sourcePath)755 void UCF_MetaHandler::WriteFile ( LFA_FileRef sourceRef, const std::string & sourcePath )
756 {
757 	IgnoreParam ( sourcePath );
758 	XMP_Throw ( "UCF_MetaHandler::WriteFile: TO BE IMPLEMENTED", kXMPErr_Unimplemented );
759 }
760 
761 // =================================================================================================
762 // own approach to unify Update and WriteFile:
763 // ============================
764 
writeOut(LFA_FileRef sourceFile,LFA_FileRef targetFile,bool isRewrite,bool isInPlace)765 void UCF_MetaHandler::writeOut( LFA_FileRef sourceFile, LFA_FileRef targetFile, bool isRewrite, bool isInPlace)
766 {
767 	// isInPlace is only possible when it's not a complete rewrite
768 	XMP_Enforce( (!isInPlace) || (!isRewrite) );
769 
770 	/////////////////////////////////////////////////////////
771 	// A
772 	if (isRewrite)	//move over A block
773 		LFA_Move( sourceFile , 0 , targetFile, 0 , al );
774 
775 	/////////////////////////////////////////////////////////
776 	// B / X (not necessarily in this order)
777 	if ( !isInPlace ) // B does not change a thing (important optimization)
778 	{
779 		LFA_Seek( targetFile , b2 , SEEK_SET );
780 		LFA_Move( sourceFile , b , targetFile, b2 , bl );
781 	}
782 
783 	LFA_Seek( targetFile , x2 , SEEK_SET );
784 	xmpFileHeader.write( targetFile );
785 	LFA_Write( targetFile, finalPacketStr, finalPacketLen );
786 	//TODO: cover reverse case / inplace ...
787 
788 	/////////////////////////////////////////////////////////
789 	// CD
790 	// No Seek here on purpose.
791 	// This assert must still be valid
792 
793 	// if inPlace, the only thing that needs still correction is the CRC in CDX:
794 	if ( isInPlace )
795 	{
796 		XMP_Uns32 crc;	//TEMP, not actually needed
797 		crc = GetUns32LE( &xmpFileHeader.fields[FileHeader::o_crc32] );
798 
799 		// go there,
800 		// do the job (take value directly from (non-CD-)fileheader),
801 		// end of story.
802 		LFA_Seek( targetFile , cdx2 + CDFileHeader::o_crc32 , SEEK_SET );
803 		LFA_Write( targetFile, &xmpFileHeader.fields[FileHeader::o_crc32], 4);
804 
805 		return;
806 	}
807 
808 	LFA_Seek( targetFile , cd2 , SEEK_SET );
809 
810 	std::vector<CDFileHeader>::iterator iter;
811 	int tmptmp=1;
812 	for( iter = cdEntries.begin(); iter != cdEntries.end(); iter++ ) {
813 		CDFileHeader* p=&(*iter);
814 		XMP_Int64 before = LFA_Tell(targetFile);
815 		p->write( targetFile );
816 		XMP_Int64 total = LFA_Tell(targetFile) - before;
817 		XMP_Int64 tmpSize = p->size();
818 		tmptmp++;
819 	}
820 
821 	/////////////////////////////////////////////////////////
822 	// Z
823 	if ( z2 )  // yes, that simple
824 	{
825 		XMP_Assert( z2 == LFA_Tell(targetFile));
826 		LFA_Seek( targetFile , z2 , SEEK_SET );
827 
828 		//no use in copying, always construct from scratch
829 		Zip64EndOfDirectory zip64EndOfDirectory( cd2, cd2l, numCF2) ;
830 		Zip64Locator zip64Locator( z2 );
831 
832 		zip64EndOfDirectory.write( targetFile );
833 		zip64Locator.write( targetFile );
834 	}
835 
836 	/////////////////////////////////////////////////////////
837 	// H
838 	XMP_Assert( h2 == LFA_Tell(targetFile));
839 	endOfCD.write( targetFile );
840 
841 	XMP_Assert( f2l == LFA_Tell(targetFile));
842 	if ( f2l< fl)
843 		LFA_Truncate(targetFile,f2l);	//file may have shrunk
844 }
845 
846 
847