|
MythTV
0.26-pre
|
00001 /**************************************************************************** 00002 ** Filename: unzip.cpp 00003 ** Last updated [dd/mm/yyyy]: 07/09/2008 00004 ** 00005 ** pkzip 2.0 decompression. 00006 ** 00007 ** Some of the code has been inspired by other open source projects, 00008 ** (mainly Info-Zip and Gilles Vollant's minizip). 00009 ** Compression and decompression actually uses the zlib library. 00010 ** 00011 ** Copyright (C) 2007-2008 Angius Fabrizio. All rights reserved. 00012 ** 00013 ** This file is part of the OSDaB project (http://osdab.sourceforge.net/). 00014 ** 00015 ** This file may be distributed and/or modified under the terms of the 00016 ** GNU General Public License version 2 as published by the Free Software 00017 ** Foundation and appearing in the file LICENSE.GPL included in the 00018 ** packaging of this file. 00019 ** 00020 ** This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE 00021 ** WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. 00022 ** 00023 ** See the file LICENSE.GPL that came with this software distribution or 00024 ** visit http://www.gnu.org/copyleft/gpl.html for GPL licensing information. 00025 ** 00026 **********************************************************************/ 00027 00028 #include "unzip.h" 00029 #include "unzip_p.h" 00030 #include "zipentry_p.h" 00031 00032 #include <QString> 00033 #include <QStringList> 00034 #include <QDir> 00035 #include <QFile> 00036 #include <QCoreApplication> 00037 00038 // You can remove this #include if you replace the qDebug() statements. 00039 #include <QtDebug> 00040 00078 00079 #define UNZIP_LOCAL_HEADER_SIZE 26 00080 00081 #define UNZIP_CD_ENTRY_SIZE_NS 42 00082 00083 #define UNZIP_DD_SIZE 12 00084 00085 #define UNZIP_EOCD_SIZE 22 00086 00087 #define UNZIP_LOCAL_ENC_HEADER_SIZE 12 00088 00089 // Some offsets inside a CD record (excluding signature) 00090 #define UNZIP_CD_OFF_VERSION 0 00091 #define UNZIP_CD_OFF_GPFLAG 4 00092 #define UNZIP_CD_OFF_CMETHOD 6 00093 #define UNZIP_CD_OFF_MODT 8 00094 #define UNZIP_CD_OFF_MODD 10 00095 #define UNZIP_CD_OFF_CRC32 12 00096 #define UNZIP_CD_OFF_CSIZE 16 00097 #define UNZIP_CD_OFF_USIZE 20 00098 #define UNZIP_CD_OFF_NAMELEN 24 00099 #define UNZIP_CD_OFF_XLEN 26 00100 #define UNZIP_CD_OFF_COMMLEN 28 00101 #define UNZIP_CD_OFF_LHOFFSET 38 00102 00103 // Some offsets inside a local header record (excluding signature) 00104 #define UNZIP_LH_OFF_VERSION 0 00105 #define UNZIP_LH_OFF_GPFLAG 2 00106 #define UNZIP_LH_OFF_CMETHOD 4 00107 #define UNZIP_LH_OFF_MODT 6 00108 #define UNZIP_LH_OFF_MODD 8 00109 #define UNZIP_LH_OFF_CRC32 10 00110 #define UNZIP_LH_OFF_CSIZE 14 00111 #define UNZIP_LH_OFF_USIZE 18 00112 #define UNZIP_LH_OFF_NAMELEN 22 00113 #define UNZIP_LH_OFF_XLEN 24 00114 00115 // Some offsets inside a data descriptor record (excluding signature) 00116 #define UNZIP_DD_OFF_CRC32 0 00117 #define UNZIP_DD_OFF_CSIZE 4 00118 #define UNZIP_DD_OFF_USIZE 8 00119 00120 // Some offsets inside a EOCD record 00121 #define UNZIP_EOCD_OFF_ENTRIES 6 00122 #define UNZIP_EOCD_OFF_CDOFF 12 00123 #define UNZIP_EOCD_OFF_COMMLEN 16 00124 00131 #define UNZIP_VERSION 0x1B 00132 00133 #define UNZIP_VERSION_STRICT 0x14 00134 00136 #define CRC32(c, b) crcTable[((int)c^b) & 0xff] ^ (c >> 8) 00137 00139 #define UNZIP_CHECK_FOR_VALID_DATA \ 00140 {\ 00141 if (headers != 0)\ 00142 {\ 00143 qDebug() << "Corrupted zip archive. Some files might be extracted.";\ 00144 ec = headers->size() != 0 ? UnZip::PartiallyCorrupted : UnZip::Corrupted;\ 00145 break;\ 00146 }\ 00147 else\ 00148 {\ 00149 delete device;\ 00150 device = 0;\ 00151 qDebug() << "Corrupted or invalid zip archive";\ 00152 ec = UnZip::Corrupted;\ 00153 break;\ 00154 }\ 00155 } 00156 00157 00158 /************************************************************************ 00159 Public interface 00160 *************************************************************************/ 00161 00165 UnZip::UnZip() 00166 { 00167 d = new UnzipPrivate; 00168 } 00169 00173 UnZip::~UnZip() 00174 { 00175 closeArchive(); 00176 delete d; 00177 } 00178 00182 bool UnZip::isOpen() const 00183 { 00184 return d->device != 0; 00185 } 00186 00190 UnZip::ErrorCode UnZip::openArchive(const QString& filename) 00191 { 00192 QFile* file = new QFile(filename); 00193 00194 if (!file->exists()) { 00195 delete file; 00196 return UnZip::FileNotFound; 00197 } 00198 00199 if (!file->open(QIODevice::ReadOnly)) { 00200 delete file; 00201 return UnZip::OpenFailed; 00202 } 00203 00204 return openArchive(file); 00205 } 00206 00212 UnZip::ErrorCode UnZip::openArchive(QIODevice* device) 00213 { 00214 if (device == 0) 00215 { 00216 qDebug() << "Invalid device."; 00217 return UnZip::InvalidDevice; 00218 } 00219 00220 return d->openArchive(device); 00221 } 00222 00226 void UnZip::closeArchive() 00227 { 00228 d->closeArchive(); 00229 } 00230 00231 QString UnZip::archiveComment() const 00232 { 00233 if (d->device == 0) 00234 return QString(); 00235 return d->comment; 00236 } 00237 00241 QString UnZip::formatError(UnZip::ErrorCode c) const 00242 { 00243 switch (c) 00244 { 00245 case Ok: return QCoreApplication::translate("UnZip", "ZIP operation completed successfully."); break; 00246 case ZlibInit: return QCoreApplication::translate("UnZip", "Failed to initialize or load zlib library."); break; 00247 case ZlibError: return QCoreApplication::translate("UnZip", "zlib library error."); break; 00248 case OpenFailed: return QCoreApplication::translate("UnZip", "Unable to create or open file."); break; 00249 case PartiallyCorrupted: return QCoreApplication::translate("UnZip", "Partially corrupted archive. Some files might be extracted."); break; 00250 case Corrupted: return QCoreApplication::translate("UnZip", "Corrupted archive."); break; 00251 case WrongPassword: return QCoreApplication::translate("UnZip", "Wrong password."); break; 00252 case NoOpenArchive: return QCoreApplication::translate("UnZip", "No archive has been created yet."); break; 00253 case FileNotFound: return QCoreApplication::translate("UnZip", "File or directory does not exist."); break; 00254 case ReadFailed: return QCoreApplication::translate("UnZip", "File read error."); break; 00255 case WriteFailed: return QCoreApplication::translate("UnZip", "File write error."); break; 00256 case SeekFailed: return QCoreApplication::translate("UnZip", "File seek error."); break; 00257 case CreateDirFailed: return QCoreApplication::translate("UnZip", "Unable to create a directory."); break; 00258 case InvalidDevice: return QCoreApplication::translate("UnZip", "Invalid device."); break; 00259 case InvalidArchive: return QCoreApplication::translate("UnZip", "Invalid or incompatible zip archive."); break; 00260 case HeaderConsistencyError: return QCoreApplication::translate("UnZip", "Inconsistent headers. Archive might be corrupted."); break; 00261 default: ; 00262 } 00263 00264 return QCoreApplication::translate("UnZip", "Unknown error."); 00265 } 00266 00270 bool UnZip::contains(const QString& file) const 00271 { 00272 if (d->headers == 0) 00273 return false; 00274 00275 return d->headers->contains(file); 00276 } 00277 00281 QStringList UnZip::fileList() const 00282 { 00283 return d->headers == 0 ? QStringList() : d->headers->keys(); 00284 } 00285 00289 QList<UnZip::ZipEntry> UnZip::entryList() const 00290 { 00291 QList<UnZip::ZipEntry> list; 00292 00293 if (d->headers != 0) 00294 { 00295 for (QMap<QString,ZipEntryP*>::ConstIterator it = d->headers->constBegin(); it != d->headers->constEnd(); ++it) 00296 { 00297 const ZipEntryP* entry = it.value(); 00298 Q_ASSERT(entry != 0); 00299 00300 ZipEntry z; 00301 00302 z.filename = it.key(); 00303 if (!entry->comment.isEmpty()) 00304 z.comment = entry->comment; 00305 z.compressedSize = entry->szComp; 00306 z.uncompressedSize = entry->szUncomp; 00307 z.crc32 = entry->crc; 00308 z.lastModified = d->convertDateTime(entry->modDate, entry->modTime); 00309 00310 z.compression = entry->compMethod == 0 ? NoCompression : entry->compMethod == 8 ? Deflated : UnknownCompression; 00311 z.type = z.filename.endsWith("/") ? Directory : File; 00312 00313 z.encrypted = entry->isEncrypted(); 00314 00315 list.append(z); 00316 } 00317 } 00318 00319 return list; 00320 } 00321 00325 UnZip::ErrorCode UnZip::extractAll(const QString& dirname, ExtractionOptions options) 00326 { 00327 return extractAll(QDir(dirname), options); 00328 } 00329 00333 UnZip::ErrorCode UnZip::extractAll(const QDir& dir, ExtractionOptions options) 00334 { 00335 // this should only happen if we didn't call openArchive() yet 00336 if (d->device == 0) 00337 return NoOpenArchive; 00338 00339 if (d->headers == 0) 00340 return Ok; 00341 00342 bool end = false; 00343 for (QMap<QString,ZipEntryP*>::Iterator itr = d->headers->begin(); itr != d->headers->end(); ++itr) 00344 { 00345 ZipEntryP* entry = itr.value(); 00346 Q_ASSERT(entry != 0); 00347 00348 if ((entry->isEncrypted()) && d->skipAllEncrypted) 00349 continue; 00350 00351 switch (d->extractFile(itr.key(), *entry, dir, options)) 00352 { 00353 case Corrupted: 00354 qDebug() << "Removing corrupted entry" << itr.key(); 00355 d->headers->erase(itr++); 00356 if (itr == d->headers->end()) 00357 end = true; 00358 break; 00359 case CreateDirFailed: 00360 break; 00361 case Skip: 00362 break; 00363 case SkipAll: 00364 d->skipAllEncrypted = true; 00365 break; 00366 default: 00367 ; 00368 } 00369 00370 if (end) 00371 break; 00372 } 00373 00374 return Ok; 00375 } 00376 00380 UnZip::ErrorCode UnZip::extractFile(const QString& filename, const QString& dirname, ExtractionOptions options) 00381 { 00382 return extractFile(filename, QDir(dirname), options); 00383 } 00384 00388 UnZip::ErrorCode UnZip::extractFile(const QString& filename, const QDir& dir, ExtractionOptions options) 00389 { 00390 QMap<QString,ZipEntryP*>::Iterator itr = d->headers->find(filename); 00391 if (itr != d->headers->end()) 00392 { 00393 ZipEntryP* entry = itr.value(); 00394 Q_ASSERT(entry != 0); 00395 return d->extractFile(itr.key(), *entry, dir, options); 00396 } 00397 00398 return FileNotFound; 00399 } 00400 00404 UnZip::ErrorCode UnZip::extractFile(const QString& filename, QIODevice* dev, ExtractionOptions options) 00405 { 00406 if (dev == 0) 00407 return InvalidDevice; 00408 00409 QMap<QString,ZipEntryP*>::Iterator itr = d->headers->find(filename); 00410 if (itr != d->headers->end()) { 00411 ZipEntryP* entry = itr.value(); 00412 Q_ASSERT(entry != 0); 00413 return d->extractFile(itr.key(), *entry, dev, options); 00414 } 00415 00416 return FileNotFound; 00417 } 00418 00423 UnZip::ErrorCode UnZip::extractFiles(const QStringList& filenames, const QString& dirname, ExtractionOptions options) 00424 { 00425 QDir dir(dirname); 00426 ErrorCode ec; 00427 00428 for (QStringList::ConstIterator itr = filenames.constBegin(); itr != filenames.constEnd(); ++itr) 00429 { 00430 ec = extractFile(*itr, dir, options); 00431 if (ec == FileNotFound) 00432 continue; 00433 if (ec != Ok) 00434 return ec; 00435 } 00436 00437 return Ok; 00438 } 00439 00444 UnZip::ErrorCode UnZip::extractFiles(const QStringList& filenames, const QDir& dir, ExtractionOptions options) 00445 { 00446 ErrorCode ec; 00447 00448 for (QStringList::ConstIterator itr = filenames.constBegin(); itr != filenames.constEnd(); ++itr) 00449 { 00450 ec = extractFile(*itr, dir, options); 00451 if (ec == FileNotFound) 00452 continue; 00453 if (ec != Ok) 00454 return ec; 00455 } 00456 00457 return Ok; 00458 } 00459 00463 void UnZip::setPassword(const QString& pwd) 00464 { 00465 d->password = pwd; 00466 } 00467 00471 UnZip::ZipEntry::ZipEntry() 00472 { 00473 compressedSize = uncompressedSize = crc32 = 0; 00474 compression = NoCompression; 00475 type = File; 00476 encrypted = false; 00477 } 00478 00479 00480 /************************************************************************ 00481 Private interface 00482 *************************************************************************/ 00483 00485 UnzipPrivate::UnzipPrivate() 00486 { 00487 skipAllEncrypted = false; 00488 headers = 0; 00489 device = 0; 00490 00491 uBuffer = (unsigned char*) buffer1; 00492 crcTable = (quint32*) get_crc_table(); 00493 00494 cdOffset = eocdOffset = 0; 00495 cdEntryCount = 0; 00496 unsupportedEntryCount = 0; 00497 } 00498 00500 UnZip::ErrorCode UnzipPrivate::openArchive(QIODevice* dev) 00501 { 00502 Q_ASSERT(dev != 0); 00503 00504 if (device != 0) 00505 closeArchive(); 00506 00507 device = dev; 00508 00509 if (!(device->isOpen() || device->open(QIODevice::ReadOnly))) 00510 { 00511 delete device; 00512 device = 0; 00513 00514 qDebug() << "Unable to open device for reading"; 00515 return UnZip::OpenFailed; 00516 } 00517 00518 UnZip::ErrorCode ec; 00519 00520 ec = seekToCentralDirectory(); 00521 if (ec != UnZip::Ok) 00522 { 00523 closeArchive(); 00524 return ec; 00525 } 00526 00528 if (cdEntryCount == 0) 00529 { 00530 return UnZip::Ok; 00531 } 00532 00533 bool continueParsing = true; 00534 00535 while (continueParsing) 00536 { 00537 if (device->read(buffer1, 4) != 4) 00538 UNZIP_CHECK_FOR_VALID_DATA 00539 00540 if (! (buffer1[0] == 'P' && buffer1[1] == 'K' && buffer1[2] == 0x01 && buffer1[3] == 0x02) ) 00541 break; 00542 00543 if ( (ec = parseCentralDirectoryRecord()) != UnZip::Ok ) 00544 break; 00545 } 00546 00547 if (ec != UnZip::Ok) 00548 closeArchive(); 00549 00550 return ec; 00551 } 00552 00553 /* 00554 \internal Parses a local header record and makes some consistency check 00555 with the information stored in the Central Directory record for this entry 00556 that has been previously parsed. 00557 \todo Optional consistency check (as a ExtractionOptions flag) 00558 00559 local file header signature 4 bytes (0x04034b50) 00560 version needed to extract 2 bytes 00561 general purpose bit flag 2 bytes 00562 compression method 2 bytes 00563 last mod file time 2 bytes 00564 last mod file date 2 bytes 00565 crc-32 4 bytes 00566 compressed size 4 bytes 00567 uncompressed size 4 bytes 00568 file name length 2 bytes 00569 extra field length 2 bytes 00570 00571 file name (variable size) 00572 extra field (variable size) 00573 */ 00574 UnZip::ErrorCode UnzipPrivate::parseLocalHeaderRecord(const QString& path, ZipEntryP& entry) 00575 { 00576 if (!device->seek(entry.lhOffset)) 00577 return UnZip::SeekFailed; 00578 00579 // Test signature 00580 if (device->read(buffer1, 4) != 4) 00581 return UnZip::ReadFailed; 00582 00583 if ((buffer1[0] != 'P') || (buffer1[1] != 'K') || (buffer1[2] != 0x03) || (buffer1[3] != 0x04)) 00584 return UnZip::InvalidArchive; 00585 00586 if (device->read(buffer1, UNZIP_LOCAL_HEADER_SIZE) != UNZIP_LOCAL_HEADER_SIZE) 00587 return UnZip::ReadFailed; 00588 00589 /* 00590 Check 3rd general purpose bit flag. 00591 00592 "bit 3: If this bit is set, the fields crc-32, compressed size 00593 and uncompressed size are set to zero in the local 00594 header. The correct values are put in the data descriptor 00595 immediately following the compressed data." 00596 */ 00597 bool hasDataDescriptor = entry.hasDataDescriptor(); 00598 00599 bool checkFailed = false; 00600 00601 if (!checkFailed) 00602 checkFailed = entry.compMethod != getUShort(uBuffer, UNZIP_LH_OFF_CMETHOD); 00603 if (!checkFailed) 00604 checkFailed = entry.gpFlag[0] != uBuffer[UNZIP_LH_OFF_GPFLAG]; 00605 if (!checkFailed) 00606 checkFailed = entry.gpFlag[1] != uBuffer[UNZIP_LH_OFF_GPFLAG + 1]; 00607 if (!checkFailed) 00608 checkFailed = entry.modTime[0] != uBuffer[UNZIP_LH_OFF_MODT]; 00609 if (!checkFailed) 00610 checkFailed = entry.modTime[1] != uBuffer[UNZIP_LH_OFF_MODT + 1]; 00611 if (!checkFailed) 00612 checkFailed = entry.modDate[0] != uBuffer[UNZIP_LH_OFF_MODD]; 00613 if (!checkFailed) 00614 checkFailed = entry.modDate[1] != uBuffer[UNZIP_LH_OFF_MODD + 1]; 00615 if (!hasDataDescriptor) 00616 { 00617 if (!checkFailed) 00618 checkFailed = entry.crc != getULong(uBuffer, UNZIP_LH_OFF_CRC32); 00619 if (!checkFailed) 00620 checkFailed = entry.szComp != getULong(uBuffer, UNZIP_LH_OFF_CSIZE); 00621 if (!checkFailed) 00622 checkFailed = entry.szUncomp != getULong(uBuffer, UNZIP_LH_OFF_USIZE); 00623 } 00624 00625 if (checkFailed) 00626 return UnZip::HeaderConsistencyError; 00627 00628 // Check filename 00629 quint16 szName = getUShort(uBuffer, UNZIP_LH_OFF_NAMELEN); 00630 if (szName == 0) 00631 return UnZip::HeaderConsistencyError; 00632 00633 if (device->read(buffer2, szName) != szName) 00634 return UnZip::ReadFailed; 00635 00636 QString filename = QString::fromAscii(buffer2, szName); 00637 if (filename != path) 00638 { 00639 qDebug() << "Filename in local header mismatches."; 00640 return UnZip::HeaderConsistencyError; 00641 } 00642 00643 // Skip extra field 00644 quint16 szExtra = getUShort(uBuffer, UNZIP_LH_OFF_XLEN); 00645 if (szExtra != 0) 00646 { 00647 if (!device->seek(device->pos() + szExtra)) 00648 return UnZip::SeekFailed; 00649 } 00650 00651 entry.dataOffset = device->pos(); 00652 00653 if (hasDataDescriptor) 00654 { 00655 /* 00656 The data descriptor has this OPTIONAL signature: PK\7\8 00657 We try to skip the compressed data relying on the size set in the 00658 Central Directory record. 00659 */ 00660 if (!device->seek(device->pos() + entry.szComp)) 00661 return UnZip::SeekFailed; 00662 00663 // Read 4 bytes and check if there is a data descriptor signature 00664 if (device->read(buffer2, 4) != 4) 00665 return UnZip::ReadFailed; 00666 00667 bool hasSignature = buffer2[0] == 'P' && buffer2[1] == 'K' && buffer2[2] == 0x07 && buffer2[3] == 0x08; 00668 if (hasSignature) 00669 { 00670 if (device->read(buffer2, UNZIP_DD_SIZE) != UNZIP_DD_SIZE) 00671 return UnZip::ReadFailed; 00672 } 00673 else 00674 { 00675 if (device->read(buffer2 + 4, UNZIP_DD_SIZE - 4) != UNZIP_DD_SIZE - 4) 00676 return UnZip::ReadFailed; 00677 } 00678 00679 // DD: crc, compressed size, uncompressed size 00680 if ( 00681 entry.crc != getULong((unsigned char*)buffer2, UNZIP_DD_OFF_CRC32) || 00682 entry.szComp != getULong((unsigned char*)buffer2, UNZIP_DD_OFF_CSIZE) || 00683 entry.szUncomp != getULong((unsigned char*)buffer2, UNZIP_DD_OFF_USIZE) 00684 ) 00685 return UnZip::HeaderConsistencyError; 00686 } 00687 00688 return UnZip::Ok; 00689 } 00690 00712 UnZip::ErrorCode UnzipPrivate::seekToCentralDirectory() 00713 { 00714 qint64 length = device->size(); 00715 qint64 offset = length - UNZIP_EOCD_SIZE; 00716 00717 if (length < UNZIP_EOCD_SIZE) 00718 return UnZip::InvalidArchive; 00719 00720 if (!device->seek( offset )) 00721 return UnZip::SeekFailed; 00722 00723 if (device->read(buffer1, UNZIP_EOCD_SIZE) != UNZIP_EOCD_SIZE) 00724 return UnZip::ReadFailed; 00725 00726 bool eocdFound = (buffer1[0] == 'P' && buffer1[1] == 'K' && buffer1[2] == 0x05 && buffer1[3] == 0x06); 00727 00728 if (eocdFound) 00729 { 00730 // Zip file has no comment (the only variable length field in the EOCD record) 00731 eocdOffset = offset; 00732 } 00733 else 00734 { 00735 qint64 read; 00736 char* p = 0; 00737 00738 offset -= UNZIP_EOCD_SIZE; 00739 00740 if (offset <= 0) 00741 return UnZip::InvalidArchive; 00742 00743 if (!device->seek( offset )) 00744 return UnZip::SeekFailed; 00745 00746 while ((read = device->read(buffer1, UNZIP_EOCD_SIZE)) >= 0) 00747 { 00748 if ( (p = strstr(buffer1, "PK\5\6")) != 0) 00749 { 00750 // Seek to the start of the EOCD record so we can read it fully 00751 // Yes... we could simply read the missing bytes and append them to the buffer 00752 // but this is far easier so heck it! 00753 device->seek( offset + (p - buffer1) ); 00754 eocdFound = true; 00755 eocdOffset = offset + (p - buffer1); 00756 00757 // Read EOCD record 00758 if (device->read(buffer1, UNZIP_EOCD_SIZE) != UNZIP_EOCD_SIZE) 00759 return UnZip::ReadFailed; 00760 00761 break; 00762 } 00763 00764 // TODO: This is very slow and only a temporary bug fix. Need some pattern matching algorithm here. 00765 offset -= 1 /*UNZIP_EOCD_SIZE*/; 00766 if (offset <= 0) 00767 return UnZip::InvalidArchive; 00768 00769 if (!device->seek( offset )) 00770 return UnZip::SeekFailed; 00771 } 00772 } 00773 00774 if (!eocdFound) 00775 return UnZip::InvalidArchive; 00776 00777 // Parse EOCD to locate CD offset 00778 offset = getULong((const unsigned char*)buffer1, UNZIP_EOCD_OFF_CDOFF + 4); 00779 00780 cdOffset = offset; 00781 00782 cdEntryCount = getUShort((const unsigned char*)buffer1, UNZIP_EOCD_OFF_ENTRIES + 4); 00783 00784 quint16 commentLength = getUShort((const unsigned char*)buffer1, UNZIP_EOCD_OFF_COMMLEN + 4); 00785 if (commentLength != 0) 00786 { 00787 QByteArray c = device->read(commentLength); 00788 if (c.count() != commentLength) 00789 return UnZip::ReadFailed; 00790 00791 comment = c; 00792 } 00793 00794 // Seek to the start of the CD record 00795 if (!device->seek( cdOffset )) 00796 return UnZip::SeekFailed; 00797 00798 return UnZip::Ok; 00799 } 00800 00837 UnZip::ErrorCode UnzipPrivate::parseCentralDirectoryRecord() 00838 { 00839 // Read CD record 00840 if (device->read(buffer1, UNZIP_CD_ENTRY_SIZE_NS) != UNZIP_CD_ENTRY_SIZE_NS) 00841 return UnZip::ReadFailed; 00842 00843 bool skipEntry = false; 00844 00845 // Get compression type so we can skip non compatible algorithms 00846 quint16 compMethod = getUShort(uBuffer, UNZIP_CD_OFF_CMETHOD); 00847 00848 // Get variable size fields length so we can skip the whole record 00849 // if necessary 00850 quint16 szName = getUShort(uBuffer, UNZIP_CD_OFF_NAMELEN); 00851 quint16 szExtra = getUShort(uBuffer, UNZIP_CD_OFF_XLEN); 00852 quint16 szComment = getUShort(uBuffer, UNZIP_CD_OFF_COMMLEN); 00853 00854 quint32 skipLength = szName + szExtra + szComment; 00855 00856 UnZip::ErrorCode ec = UnZip::Ok; 00857 00858 if ((compMethod != 0) && (compMethod != 8)) 00859 { 00860 qDebug() << "Unsupported compression method. Skipping file."; 00861 skipEntry = true; 00862 } 00863 00864 // Header parsing may be a problem if version is bigger than UNZIP_VERSION 00865 if (!skipEntry && buffer1[UNZIP_CD_OFF_VERSION] > UNZIP_VERSION) 00866 { 00867 qDebug() << "Unsupported PKZip version. Skipping file."; 00868 skipEntry = true; 00869 } 00870 00871 if (!skipEntry && szName == 0) 00872 { 00873 qDebug() << "Skipping file with no name."; 00874 skipEntry = true; 00875 } 00876 00877 if (!skipEntry && device->read(buffer2, szName) != szName) 00878 { 00879 ec = UnZip::ReadFailed; 00880 skipEntry = true; 00881 } 00882 00883 if (skipEntry) 00884 { 00885 if (ec == UnZip::Ok) 00886 { 00887 if (!device->seek( device->pos() + skipLength )) 00888 ec = UnZip::SeekFailed; 00889 00890 unsupportedEntryCount++; 00891 } 00892 00893 return ec; 00894 } 00895 00896 QString filename = QString::fromAscii(buffer2, szName); 00897 00898 ZipEntryP* h = new ZipEntryP; 00899 h->compMethod = compMethod; 00900 00901 h->gpFlag[0] = buffer1[UNZIP_CD_OFF_GPFLAG]; 00902 h->gpFlag[1] = buffer1[UNZIP_CD_OFF_GPFLAG + 1]; 00903 00904 h->modTime[0] = buffer1[UNZIP_CD_OFF_MODT]; 00905 h->modTime[1] = buffer1[UNZIP_CD_OFF_MODT + 1]; 00906 00907 h->modDate[0] = buffer1[UNZIP_CD_OFF_MODD]; 00908 h->modDate[1] = buffer1[UNZIP_CD_OFF_MODD + 1]; 00909 00910 h->crc = getULong(uBuffer, UNZIP_CD_OFF_CRC32); 00911 h->szComp = getULong(uBuffer, UNZIP_CD_OFF_CSIZE); 00912 h->szUncomp = getULong(uBuffer, UNZIP_CD_OFF_USIZE); 00913 00914 // Skip extra field (if any) 00915 if (szExtra != 0) 00916 { 00917 if (!device->seek( device->pos() + szExtra )) 00918 { 00919 delete h; 00920 return UnZip::SeekFailed; 00921 } 00922 } 00923 00924 // Read comment field (if any) 00925 if (szComment != 0) 00926 { 00927 if (device->read(buffer2, szComment) != szComment) 00928 { 00929 delete h; 00930 return UnZip::ReadFailed; 00931 } 00932 00933 h->comment = QString::fromAscii(buffer2, szComment); 00934 } 00935 00936 h->lhOffset = getULong(uBuffer, UNZIP_CD_OFF_LHOFFSET); 00937 00938 if (headers == 0) 00939 headers = new QMap<QString, ZipEntryP*>(); 00940 headers->insert(filename, h); 00941 00942 return UnZip::Ok; 00943 } 00944 00946 void UnzipPrivate::closeArchive() 00947 { 00948 if (device == 0) 00949 return; 00950 00951 skipAllEncrypted = false; 00952 00953 if (headers != 0) 00954 { 00955 qDeleteAll(*headers); 00956 delete headers; 00957 headers = 0; 00958 } 00959 00960 delete device; device = 0; 00961 00962 cdOffset = eocdOffset = 0; 00963 cdEntryCount = 0; 00964 unsupportedEntryCount = 0; 00965 00966 comment.clear(); 00967 } 00968 00970 UnZip::ErrorCode UnzipPrivate::extractFile(const QString& path, ZipEntryP& entry, const QDir& dir, UnZip::ExtractionOptions options) 00971 { 00972 QString name(path); 00973 QString dirname; 00974 QString directory; 00975 00976 int pos = name.lastIndexOf('/'); 00977 00978 // This entry is for a directory 00979 if (pos == name.length() - 1) 00980 { 00981 if (options.testFlag(UnZip::SkipPaths)) 00982 return UnZip::Ok; 00983 00984 directory = QString("%1/%2").arg(dir.absolutePath()).arg(QDir::cleanPath(name)); 00985 if (!createDirectory(directory)) 00986 { 00987 qDebug() << QString("Unable to create directory: %1").arg(directory); 00988 return UnZip::CreateDirFailed; 00989 } 00990 00991 return UnZip::Ok; 00992 } 00993 00994 // Extract path from entry 00995 if (pos > 0) 00996 { 00997 // get directory part 00998 dirname = name.left(pos); 00999 if (options.testFlag(UnZip::SkipPaths)) 01000 { 01001 directory = dir.absolutePath(); 01002 } 01003 else 01004 { 01005 directory = QString("%1/%2").arg(dir.absolutePath()).arg(QDir::cleanPath(dirname)); 01006 if (!createDirectory(directory)) 01007 { 01008 qDebug() << QString("Unable to create directory: %1").arg(directory); 01009 return UnZip::CreateDirFailed; 01010 } 01011 } 01012 name = name.right(name.length() - pos - 1); 01013 } else directory = dir.absolutePath(); 01014 01015 name = QString("%1/%2").arg(directory).arg(name); 01016 01017 QFile outFile(name); 01018 01019 if (!outFile.open(QIODevice::WriteOnly)) 01020 { 01021 qDebug() << QString("Unable to open %1 for writing").arg(name); 01022 return UnZip::OpenFailed; 01023 } 01024 01026 01027 UnZip::ErrorCode ec = extractFile(path, entry, &outFile, options); 01028 01029 outFile.close(); 01030 01031 if (ec != UnZip::Ok) 01032 { 01033 if (!outFile.remove()) 01034 qDebug() << QString("Unable to remove corrupted file: %1").arg(name); 01035 } 01036 01037 return ec; 01038 } 01039 01041 UnZip::ErrorCode UnzipPrivate::extractFile(const QString& path, ZipEntryP& entry, QIODevice* dev, UnZip::ExtractionOptions options) 01042 { 01043 Q_UNUSED(options); 01044 Q_ASSERT(dev != 0); 01045 01046 if (!entry.lhEntryChecked) 01047 { 01048 UnZip::ErrorCode ec = parseLocalHeaderRecord(path, entry); 01049 entry.lhEntryChecked = true; 01050 01051 if (ec != UnZip::Ok) 01052 return ec; 01053 } 01054 01055 if (!device->seek(entry.dataOffset)) 01056 return UnZip::SeekFailed; 01057 01058 // Encryption keys 01059 quint32 keys[3]; 01060 01061 if (entry.isEncrypted()) 01062 { 01063 UnZip::ErrorCode e = testPassword(keys, path, entry); 01064 if (e != UnZip::Ok) 01065 { 01066 qDebug() << QString("Unable to decrypt %1").arg(path); 01067 return e; 01068 } 01069 entry.szComp -= UNZIP_LOCAL_ENC_HEADER_SIZE; // remove encryption header size 01070 } 01071 01072 if (entry.szComp == 0) 01073 { 01074 if (entry.crc != 0) 01075 return UnZip::Corrupted; 01076 01077 return UnZip::Ok; 01078 } 01079 01080 uInt rep = entry.szComp / UNZIP_READ_BUFFER; 01081 uInt rem = entry.szComp % UNZIP_READ_BUFFER; 01082 uInt cur = 0; 01083 01084 // extract data 01085 qint64 read; 01086 quint64 tot = 0; 01087 01088 quint32 myCRC = crc32(0L, Z_NULL, 0); 01089 01090 if (entry.compMethod == 0) 01091 { 01092 while ( (read = device->read(buffer1, cur < rep ? UNZIP_READ_BUFFER : rem)) > 0 ) 01093 { 01094 if (entry.isEncrypted()) 01095 decryptBytes(keys, buffer1, read); 01096 01097 myCRC = crc32(myCRC, uBuffer, read); 01098 01099 if (dev->write(buffer1, read) != read) 01100 return UnZip::WriteFailed; 01101 01102 cur++; 01103 tot += read; 01104 01105 if (tot == entry.szComp) 01106 break; 01107 } 01108 01109 if (read < 0) 01110 return UnZip::ReadFailed; 01111 } 01112 else if (entry.compMethod == 8) 01113 { 01114 /* Allocate inflate state */ 01115 z_stream zstr; 01116 zstr.zalloc = Z_NULL; 01117 zstr.zfree = Z_NULL; 01118 zstr.opaque = Z_NULL; 01119 zstr.next_in = Z_NULL; 01120 zstr.avail_in = 0; 01121 01122 int zret; 01123 01124 // Use inflateInit2 with negative windowBits to get raw decompression 01125 if ( (zret = inflateInit2_(&zstr, -MAX_WBITS, ZLIB_VERSION, sizeof(z_stream))) != Z_OK ) 01126 return UnZip::ZlibError; 01127 01128 int szDecomp; 01129 01130 // Decompress until deflate stream ends or end of file 01131 do 01132 { 01133 read = device->read(buffer1, cur < rep ? UNZIP_READ_BUFFER : rem); 01134 if (read == 0) 01135 break; 01136 if (read < 0) 01137 { 01138 (void)inflateEnd(&zstr); 01139 return UnZip::ReadFailed; 01140 } 01141 01142 if (entry.isEncrypted()) 01143 decryptBytes(keys, buffer1, read); 01144 01145 cur++; 01146 tot += read; 01147 01148 zstr.avail_in = (uInt) read; 01149 zstr.next_in = (Bytef*) buffer1; 01150 01151 01152 // Run inflate() on input until output buffer not full 01153 do { 01154 zstr.avail_out = UNZIP_READ_BUFFER; 01155 zstr.next_out = (Bytef*) buffer2;; 01156 01157 zret = inflate(&zstr, Z_NO_FLUSH); 01158 01159 switch (zret) { 01160 case Z_NEED_DICT: 01161 case Z_DATA_ERROR: 01162 case Z_MEM_ERROR: 01163 inflateEnd(&zstr); 01164 return UnZip::WriteFailed; 01165 default: 01166 ; 01167 } 01168 01169 szDecomp = UNZIP_READ_BUFFER - zstr.avail_out; 01170 if (dev->write(buffer2, szDecomp) != szDecomp) 01171 { 01172 inflateEnd(&zstr); 01173 return UnZip::ZlibError; 01174 } 01175 01176 myCRC = crc32(myCRC, (const Bytef*) buffer2, szDecomp); 01177 01178 } while (zstr.avail_out == 0); 01179 01180 } 01181 while (zret != Z_STREAM_END); 01182 01183 inflateEnd(&zstr); 01184 } 01185 01186 if (myCRC != entry.crc) 01187 return UnZip::Corrupted; 01188 01189 return UnZip::Ok; 01190 } 01191 01193 bool UnzipPrivate::createDirectory(const QString& path) 01194 { 01195 QDir d(path); 01196 if (!d.exists()) 01197 { 01198 int sep = path.lastIndexOf("/"); 01199 if (sep <= 0) return true; 01200 01201 if (!createDirectory(path.left(sep))) 01202 return false; 01203 01204 if (!d.mkdir(path)) 01205 { 01206 qDebug() << QString("Unable to create directory: %1").arg(path); 01207 return false; 01208 } 01209 } 01210 01211 return true; 01212 } 01213 01217 quint32 UnzipPrivate::getULong(const unsigned char* data, quint32 offset) const 01218 { 01219 quint32 res = (quint32) data[offset]; 01220 res |= (((quint32)data[offset+1]) << 8); 01221 res |= (((quint32)data[offset+2]) << 16); 01222 res |= (((quint32)data[offset+3]) << 24); 01223 01224 return res; 01225 } 01226 01230 quint64 UnzipPrivate::getULLong(const unsigned char* data, quint32 offset) const 01231 { 01232 quint64 res = (quint64) data[offset]; 01233 res |= (((quint64)data[offset+1]) << 8); 01234 res |= (((quint64)data[offset+2]) << 16); 01235 res |= (((quint64)data[offset+3]) << 24); 01236 res |= (((quint64)data[offset+1]) << 32); 01237 res |= (((quint64)data[offset+2]) << 40); 01238 res |= (((quint64)data[offset+3]) << 48); 01239 res |= (((quint64)data[offset+3]) << 56); 01240 01241 return res; 01242 } 01243 01247 quint16 UnzipPrivate::getUShort(const unsigned char* data, quint32 offset) const 01248 { 01249 return (quint16) data[offset] | (((quint16)data[offset+1]) << 8); 01250 } 01251 01255 int UnzipPrivate::decryptByte(quint32 key2) const 01256 { 01257 quint16 temp = ((quint16)(key2) & 0xffff) | 2; 01258 return (int)(((temp * (temp ^ 1)) >> 8) & 0xff); 01259 } 01260 01264 void UnzipPrivate::updateKeys(quint32* keys, int c) const 01265 { 01266 keys[0] = CRC32(keys[0], c); 01267 keys[1] += keys[0] & 0xff; 01268 keys[1] = keys[1] * 134775813L + 1; 01269 keys[2] = CRC32(keys[2], ((int)keys[1]) >> 24); 01270 } 01271 01276 void UnzipPrivate::initKeys(const QString& pwd, quint32* keys) const 01277 { 01278 keys[0] = 305419896L; 01279 keys[1] = 591751049L; 01280 keys[2] = 878082192L; 01281 01282 QByteArray pwdBytes = pwd.toAscii(); 01283 int sz = pwdBytes.size(); 01284 const char* ascii = pwdBytes.data(); 01285 01286 for (int i=0; i<sz; ++i) 01287 updateKeys(keys, (int)ascii[i]); 01288 } 01289 01295 UnZip::ErrorCode UnzipPrivate::testPassword(quint32* keys, const QString& file, const ZipEntryP& header) 01296 { 01297 Q_UNUSED(file); 01298 01299 // read encryption keys 01300 if (device->read(buffer1, 12) != 12) 01301 return UnZip::Corrupted; 01302 01303 // Replace this code if you want to i.e. call some dialog and ask the user for a password 01304 initKeys(password, keys); 01305 if (testKeys(header, keys)) 01306 return UnZip::Ok; 01307 01308 return UnZip::Skip; 01309 } 01310 01314 bool UnzipPrivate::testKeys(const ZipEntryP& header, quint32* keys) 01315 { 01316 char lastByte; 01317 01318 // decrypt encryption header 01319 for (int i=0; i<11; ++i) 01320 updateKeys(keys, lastByte = buffer1[i] ^ decryptByte(keys[2])); 01321 updateKeys(keys, lastByte = buffer1[11] ^ decryptByte(keys[2])); 01322 01323 // if there is an extended header (bit in the gp flag) buffer[11] is a byte from the file time 01324 // with no extended header we have to check the crc high-order byte 01325 char c = ((header.gpFlag[0] & 0x08) == 8) ? header.modTime[1] : header.crc >> 24; 01326 01327 return (lastByte == c); 01328 } 01329 01333 void UnzipPrivate::decryptBytes(quint32* keys, char* buffer, qint64 read) 01334 { 01335 for (int i=0; i<(int)read; ++i) 01336 updateKeys(keys, buffer[i] ^= decryptByte(keys[2])); 01337 } 01338 01342 QDateTime UnzipPrivate::convertDateTime(const unsigned char date[2], const unsigned char time[2]) const 01343 { 01344 QDateTime dt; 01345 01346 // Usual PKZip low-byte to high-byte order 01347 01348 // Date: 7 bits = years from 1980, 4 bits = month, 5 bits = day 01349 quint16 year = (date[1] >> 1) & 127; 01350 quint16 month = ((date[1] << 3) & 14) | ((date[0] >> 5) & 7); 01351 quint16 day = date[0] & 31; 01352 01353 // Time: 5 bits hour, 6 bits minutes, 5 bits seconds with a 2sec precision 01354 quint16 hour = (time[1] >> 3) & 31; 01355 quint16 minutes = ((time[1] << 3) & 56) | ((time[0] >> 5) & 7); 01356 quint16 seconds = (time[0] & 31) * 2; 01357 01358 dt.setDate(QDate(1980 + year, month, day)); 01359 dt.setTime(QTime(hour, minutes, seconds)); 01360 return dt; 01361 }
1.7.6.1