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