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