1 /****************************************************************************
3 ** Last updated [dd/mm/yyyy]: 28/01/2007
5 ** pkzip 2.0 decompression.
7 ** Some of the code has been inspired by other open source projects,
8 ** (mainly Info-Zip and Gilles Vollant's minizip).
9 ** Compression and decompression actually uses the zlib library.
11 ** Copyright (C) 2007 Angius Fabrizio. All rights reserved.
13 ** This file is part of the OSDaB project (http://osdab.sourceforge.net/).
15 ** This file may be distributed and/or modified under the terms of the
16 ** GNU General Public License version 2 as published by the Free Software
17 ** Foundation and appearing in the file LICENSE.GPL included in the
18 ** packaging of this file.
20 ** This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE
21 ** WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
23 ** See the file LICENSE.GPL that came with this software distribution or
24 ** visit http://www.gnu.org/copyleft/gpl.html for GPL licensing information.
26 **********************************************************************/
30 #include "zipentry_p.h"
33 #include <QStringList>
36 #include <QCoreApplication>
38 // You can remove this #include if you replace the qDebug() statements.
44 \brief PKZip 2.0 file decompression.
45 Compatibility with later versions is not ensured as they may use
46 unsupported compression algorithms.
47 Versions after 2.7 may have an incompatible header format and thus be
48 completely incompatible.
51 /*! \enum UnZip::ErrorCode The result of a decompression operation.
52 \value UnZip::Ok No error occurred.
53 \value UnZip::ZlibInit Failed to init or load the zlib library.
54 \value UnZip::ZlibError The zlib library returned some error.
55 \value UnZip::OpenFailed Unable to create or open a device.
56 \value UnZip::PartiallyCorrupted Corrupted zip archive - some files could be extracted.
57 \value UnZip::Corrupted Corrupted or invalid zip archive.
58 \value UnZip::WrongPassword Unable to decrypt a password protected file.
59 \value UnZip::NoOpenArchive No archive has been opened yet.
60 \value UnZip::FileNotFound Unable to find the requested file in the archive.
61 \value UnZip::ReadFailed Reading of a file failed.
62 \value UnZip::WriteFailed Writing of a file failed.
63 \value UnZip::SeekFailed Seek failed.
64 \value UnZip::CreateDirFailed Could not create a directory.
65 \value UnZip::InvalidDevice A null device has been passed as parameter.
66 \value UnZip::InvalidArchive This is not a valid (or supported) ZIP archive.
67 \value UnZip::HeaderConsistencyError Local header record info does not match with the central directory record info. The archive may be corrupted.
69 \value UnZip::Skip Internal use only.
70 \value UnZip::SkipAll Internal use only.
73 /*! \enum UnZip::ExtractionOptions Some options for the file extraction methods.
74 \value UnZip::ExtractPaths Default. Does not ignore the path of the zipped files.
75 \value UnZip::SkipPaths Default. Ignores the path of the zipped files and extracts them all to the same root directory.
78 //! Local header size (excluding signature, excluding variable length fields)
79 #define UNZIP_LOCAL_HEADER_SIZE 26
80 //! Central Directory file entry size (excluding signature, excluding variable length fields)
81 #define UNZIP_CD_ENTRY_SIZE_NS 42
82 //! Data descriptor size (excluding signature)
83 #define UNZIP_DD_SIZE 12
84 //! End Of Central Directory size (including signature, excluding variable length fields)
85 #define UNZIP_EOCD_SIZE 22
86 //! Local header entry encryption header size
87 #define UNZIP_LOCAL_ENC_HEADER_SIZE 12
89 // Some offsets inside a CD record (excluding signature)
90 #define UNZIP_CD_OFF_VERSION 0
91 #define UNZIP_CD_OFF_GPFLAG 4
92 #define UNZIP_CD_OFF_CMETHOD 6
93 #define UNZIP_CD_OFF_MODT 8
94 #define UNZIP_CD_OFF_MODD 10
95 #define UNZIP_CD_OFF_CRC32 12
96 #define UNZIP_CD_OFF_CSIZE 16
97 #define UNZIP_CD_OFF_USIZE 20
98 #define UNZIP_CD_OFF_NAMELEN 24
99 #define UNZIP_CD_OFF_XLEN 26
100 #define UNZIP_CD_OFF_COMMLEN 28
101 #define UNZIP_CD_OFF_LHOFFSET 38
103 // Some offsets inside a local header record (excluding signature)
104 #define UNZIP_LH_OFF_VERSION 0
105 #define UNZIP_LH_OFF_GPFLAG 2
106 #define UNZIP_LH_OFF_CMETHOD 4
107 #define UNZIP_LH_OFF_MODT 6
108 #define UNZIP_LH_OFF_MODD 8
109 #define UNZIP_LH_OFF_CRC32 10
110 #define UNZIP_LH_OFF_CSIZE 14
111 #define UNZIP_LH_OFF_USIZE 18
112 #define UNZIP_LH_OFF_NAMELEN 22
113 #define UNZIP_LH_OFF_XLEN 24
115 // Some offsets inside a data descriptor record (excluding signature)
116 #define UNZIP_DD_OFF_CRC32 0
117 #define UNZIP_DD_OFF_CSIZE 4
118 #define UNZIP_DD_OFF_USIZE 8
120 // Some offsets inside a EOCD record
121 #define UNZIP_EOCD_OFF_ENTRIES 6
122 #define UNZIP_EOCD_OFF_CDOFF 12
123 #define UNZIP_EOCD_OFF_COMMLEN 16
126 Max version handled by this API.
127 0x1B = 2.7 --> full compatibility only up to version 2.0 (0x14)
128 versions from 2.1 to 2.7 may use unsupported compression methods
129 versions after 2.7 may have an incompatible header format
131 #define UNZIP_VERSION 0x1B
132 //! Full compatibility granted until this version
133 #define UNZIP_VERSION_STRICT 0x14
136 #define CRC32(c, b) crcTable[((int)c^b) & 0xff] ^ (c >> 8)
138 //! Checks if some file has been already extracted.
139 #define UNZIP_CHECK_FOR_VALID_DATA \
143 qDebug() << "Corrupted zip archive. Some files might be extracted.";\
144 ec = headers->size() != 0 ? UnZip::PartiallyCorrupted : UnZip::Corrupted;\
151 qDebug() << "Corrupted or invalid zip archive";\
152 ec = UnZip::Corrupted;\
158 /************************************************************************
160 *************************************************************************/
163 Creates a new Zip file decompressor.
167 d
= new UnzipPrivate
;
171 Closes any open archive and releases used resources.
180 Returns true if there is an open archive.
182 bool UnZip::isOpen() const
184 return d
->device
!= 0;
188 Opens a zip archive and reads the files list. Closes any previously opened archive.
190 UnZip::ErrorCode
UnZip::openArchive(const QString
& filename
)
192 QFile
* file
= new QFile(filename
);
194 if (!file
->exists()) {
196 return UnZip::FileNotFound
;
199 if (!file
->open(QIODevice::ReadOnly
)) {
201 return UnZip::OpenFailed
;
204 return openArchive(file
);
208 Opens a zip archive and reads the entries list.
209 Closes any previously opened archive.
210 \warning The class takes ownership of the device so don't delete it!
212 UnZip::ErrorCode
UnZip::openArchive(QIODevice
* device
)
216 qDebug() << "Invalid device.";
217 return UnZip::InvalidDevice
;
220 return d
->openArchive(device
);
224 Closes the archive and releases all the used resources (like cached passwords).
226 void UnZip::closeArchive()
231 QString
UnZip::archiveComment() const
239 Returns a locale translated error string for a given error code.
241 QString
UnZip::formatError(UnZip::ErrorCode c
) const
245 case Ok
: return QCoreApplication::translate("UnZip", "ZIP operation completed successfully."); break;
246 case ZlibInit
: return QCoreApplication::translate("UnZip", "Failed to initialize or load zlib library."); break;
247 case ZlibError
: return QCoreApplication::translate("UnZip", "zlib library error."); break;
248 case OpenFailed
: return QCoreApplication::translate("UnZip", "Unable to create or open file."); break;
249 case PartiallyCorrupted
: return QCoreApplication::translate("UnZip", "Partially corrupted archive. Some files might be extracted."); break;
250 case Corrupted
: return QCoreApplication::translate("UnZip", "Corrupted archive."); break;
251 case WrongPassword
: return QCoreApplication::translate("UnZip", "Wrong password."); break;
252 case NoOpenArchive
: return QCoreApplication::translate("UnZip", "No archive has been created yet."); break;
253 case FileNotFound
: return QCoreApplication::translate("UnZip", "File or directory does not exist."); break;
254 case ReadFailed
: return QCoreApplication::translate("UnZip", "File read error."); break;
255 case WriteFailed
: return QCoreApplication::translate("UnZip", "File write error."); break;
256 case SeekFailed
: return QCoreApplication::translate("UnZip", "File seek error."); break;
257 case CreateDirFailed
: return QCoreApplication::translate("UnZip", "Unable to create a directory."); break;
258 case InvalidDevice
: return QCoreApplication::translate("UnZip", "Invalid device."); break;
259 case InvalidArchive
: return QCoreApplication::translate("UnZip", "Invalid or incompatible zip archive."); break;
260 case HeaderConsistencyError
: return QCoreApplication::translate("UnZip", "Inconsistent headers. Archive might be corrupted."); break;
264 return QCoreApplication::translate("UnZip", "Unknown error.");
268 Returns true if the archive contains a file with the given path and name.
270 bool UnZip::contains(const QString
& file
) const
275 return d
->headers
->contains(file
);
279 Returns complete paths of files and directories in this archive.
281 QStringList
UnZip::fileList() const
283 return d
->headers
== 0 ? QStringList() : d
->headers
->keys();
287 Returns information for each (correctly parsed) entry of this archive.
289 QList
<UnZip::ZipEntry
> UnZip::entryList() const
291 QList
<UnZip::ZipEntry
> list
;
295 for (QMap
<QString
,ZipEntryP
*>::ConstIterator it
= d
->headers
->constBegin(); it
!= d
->headers
->constEnd(); ++it
)
297 const ZipEntryP
* entry
= it
.value();
298 Q_ASSERT(entry
!= 0);
302 z
.filename
= it
.key();
303 if (!entry
->comment
.isEmpty())
304 z
.comment
= entry
->comment
;
305 z
.compressedSize
= entry
->szComp
;
306 z
.uncompressedSize
= entry
->szUncomp
;
307 z
.crc32
= entry
->crc
;
308 z
.lastModified
= d
->convertDateTime(entry
->modDate
, entry
->modTime
);
310 z
.compression
= entry
->compMethod
== 0 ? NoCompression
: entry
->compMethod
== 8 ? Deflated
: UnknownCompression
;
311 z
.type
= z
.filename
.endsWith("/") ? Directory
: File
;
313 z
.encrypted
= entry
->isEncrypted();
323 Extracts the whole archive to a directory.
325 UnZip::ErrorCode
UnZip::extractAll(const QString
& dirname
, ExtractionOptions options
)
327 return extractAll(QDir(dirname
), options
);
331 Extracts the whole archive to a directory.
333 UnZip::ErrorCode
UnZip::extractAll(const QDir
& dir
, ExtractionOptions options
)
335 // this should only happen if we didn't call openArchive() yet
337 return NoOpenArchive
;
343 for (QMap
<QString
,ZipEntryP
*>::Iterator itr
= d
->headers
->begin(); itr
!= d
->headers
->end(); ++itr
)
345 ZipEntryP
* entry
= itr
.value();
346 Q_ASSERT(entry
!= 0);
348 if ((entry
->isEncrypted()) && d
->skipAllEncrypted
)
351 switch (d
->extractFile(itr
.key(), *entry
, dir
, options
))
354 qDebug() << "Removing corrupted entry" << itr
.key();
355 d
->headers
->erase(itr
++);
356 if (itr
== d
->headers
->end())
359 case CreateDirFailed
:
364 d
->skipAllEncrypted
= true;
378 Extracts a single file to a directory.
380 UnZip::ErrorCode
UnZip::extractFile(const QString
& filename
, const QString
& dirname
, ExtractionOptions options
)
382 return extractFile(filename
, QDir(dirname
), options
);
386 Extracts a single file to a directory.
388 UnZip::ErrorCode
UnZip::extractFile(const QString
& filename
, const QDir
& dir
, ExtractionOptions options
)
390 QMap
<QString
,ZipEntryP
*>::Iterator itr
= d
->headers
->find(filename
);
391 if (itr
!= d
->headers
->end())
393 ZipEntryP
* entry
= itr
.value();
394 Q_ASSERT(entry
!= 0);
395 return d
->extractFile(itr
.key(), *entry
, dir
, options
);
402 Extracts a single file to a directory.
404 UnZip::ErrorCode
UnZip::extractFile(const QString
& filename
, QIODevice
* dev
, ExtractionOptions options
)
407 return InvalidDevice
;
409 QMap
<QString
,ZipEntryP
*>::Iterator itr
= d
->headers
->find(filename
);
410 if (itr
!= d
->headers
->end()) {
411 ZipEntryP
* entry
= itr
.value();
412 Q_ASSERT(entry
!= 0);
413 return d
->extractFile(itr
.key(), *entry
, dev
, options
);
420 Extracts a list of files.
421 Stops extraction at the first error (but continues if a file does not exist in the archive).
423 UnZip::ErrorCode
UnZip::extractFiles(const QStringList
& filenames
, const QString
& dirname
, ExtractionOptions options
)
428 for (QStringList::ConstIterator itr
= filenames
.constBegin(); itr
!= filenames
.constEnd(); ++itr
)
430 ec
= extractFile(*itr
, dir
, options
);
431 if (ec
== FileNotFound
)
441 Extracts a list of files.
442 Stops extraction at the first error (but continues if a file does not exist in the archive).
444 UnZip::ErrorCode
UnZip::extractFiles(const QStringList
& filenames
, const QDir
& dir
, ExtractionOptions options
)
448 for (QStringList::ConstIterator itr
= filenames
.constBegin(); itr
!= filenames
.constEnd(); ++itr
)
450 ec
= extractFile(*itr
, dir
, options
);
451 if (ec
== FileNotFound
)
461 Remove/replace this method to add your own password retrieval routine.
463 void UnZip::setPassword(const QString
& pwd
)
469 ZipEntry constructor - initialize data. Type is set to File.
471 UnZip::ZipEntry::ZipEntry()
473 compressedSize
= uncompressedSize
= crc32
= 0;
474 compression
= NoCompression
;
480 /************************************************************************
482 *************************************************************************/
485 UnzipPrivate::UnzipPrivate()
487 skipAllEncrypted
= false;
491 uBuffer
= (unsigned char*) buffer1
;
492 crcTable
= (quint32
*) get_crc_table();
494 cdOffset
= eocdOffset
= 0;
496 unsupportedEntryCount
= 0;
499 //! \internal Parses a Zip archive.
500 UnZip::ErrorCode
UnzipPrivate::openArchive(QIODevice
* dev
)
509 if (!(device
->isOpen() || device
->open(QIODevice::ReadOnly
)))
514 qDebug() << "Unable to open device for reading";
515 return UnZip::OpenFailed
;
520 ec
= seekToCentralDirectory();
527 //! \todo Ignore CD entry count? CD may be corrupted.
528 if (cdEntryCount
== 0)
533 bool continueParsing
= true;
535 while (continueParsing
)
537 if (device
->read(buffer1
, 4) != 4)
538 UNZIP_CHECK_FOR_VALID_DATA
540 if (! (buffer1
[0] == 'P' && buffer1
[1] == 'K' && buffer1
[2] == 0x01 && buffer1
[3] == 0x02) )
543 if ( (ec
= parseCentralDirectoryRecord()) != UnZip::Ok
)
554 \internal Parses a local header record and makes some consistency check
555 with the information stored in the Central Directory record for this entry
556 that has been previously parsed.
557 \todo Optional consistency check (as a ExtractionOptions flag)
559 local file header signature 4 bytes (0x04034b50)
560 version needed to extract 2 bytes
561 general purpose bit flag 2 bytes
562 compression method 2 bytes
563 last mod file time 2 bytes
564 last mod file date 2 bytes
566 compressed size 4 bytes
567 uncompressed size 4 bytes
568 file name length 2 bytes
569 extra field length 2 bytes
571 file name (variable size)
572 extra field (variable size)
574 UnZip::ErrorCode
UnzipPrivate::parseLocalHeaderRecord(const QString
& path
, ZipEntryP
& entry
)
576 if (!device
->seek(entry
.lhOffset
))
577 return UnZip::SeekFailed
;
580 if (device
->read(buffer1
, 4) != 4)
581 return UnZip::ReadFailed
;
583 if ((buffer1
[0] != 'P') || (buffer1
[1] != 'K') || (buffer1
[2] != 0x03) || (buffer1
[3] != 0x04))
584 return UnZip::InvalidArchive
;
586 if (device
->read(buffer1
, UNZIP_LOCAL_HEADER_SIZE
) != UNZIP_LOCAL_HEADER_SIZE
)
587 return UnZip::ReadFailed
;
590 Check 3rd general purpose bit flag.
592 "bit 3: If this bit is set, the fields crc-32, compressed size
593 and uncompressed size are set to zero in the local
594 header. The correct values are put in the data descriptor
595 immediately following the compressed data."
597 bool hasDataDescriptor
= entry
.hasDataDescriptor();
599 bool checkFailed
= false;
602 checkFailed
= entry
.compMethod
!= getUShort(uBuffer
, UNZIP_LH_OFF_CMETHOD
);
604 checkFailed
= entry
.gpFlag
[0] != uBuffer
[UNZIP_LH_OFF_GPFLAG
];
606 checkFailed
= entry
.gpFlag
[1] != uBuffer
[UNZIP_LH_OFF_GPFLAG
+ 1];
608 checkFailed
= entry
.modTime
[0] != uBuffer
[UNZIP_LH_OFF_MODT
];
610 checkFailed
= entry
.modTime
[1] != uBuffer
[UNZIP_LH_OFF_MODT
+ 1];
612 checkFailed
= entry
.modDate
[0] != uBuffer
[UNZIP_LH_OFF_MODD
];
614 checkFailed
= entry
.modDate
[1] != uBuffer
[UNZIP_LH_OFF_MODD
+ 1];
615 if (!hasDataDescriptor
)
618 checkFailed
= entry
.crc
!= getULong(uBuffer
, UNZIP_LH_OFF_CRC32
);
620 checkFailed
= entry
.szComp
!= getULong(uBuffer
, UNZIP_LH_OFF_CSIZE
);
622 checkFailed
= entry
.szUncomp
!= getULong(uBuffer
, UNZIP_LH_OFF_USIZE
);
626 return UnZip::HeaderConsistencyError
;
629 quint16 szName
= getUShort(uBuffer
, UNZIP_LH_OFF_NAMELEN
);
631 return UnZip::HeaderConsistencyError
;
633 if (device
->read(buffer2
, szName
) != szName
)
634 return UnZip::ReadFailed
;
636 QString filename
= QString::fromAscii(buffer2
, szName
);
637 if (filename
!= path
)
639 qDebug() << "Filename in local header mismatches.";
640 return UnZip::HeaderConsistencyError
;
644 quint16 szExtra
= getUShort(uBuffer
, UNZIP_LH_OFF_XLEN
);
647 if (!device
->seek(device
->pos() + szExtra
))
648 return UnZip::SeekFailed
;
651 entry
.dataOffset
= device
->pos();
653 if (hasDataDescriptor
)
656 The data descriptor has this OPTIONAL signature: PK\7\8
657 We try to skip the compressed data relying on the size set in the
658 Central Directory record.
660 if (!device
->seek(device
->pos() + entry
.szComp
))
661 return UnZip::SeekFailed
;
663 // Read 4 bytes and check if there is a data descriptor signature
664 if (device
->read(buffer2
, 4) != 4)
665 return UnZip::ReadFailed
;
667 bool hasSignature
= buffer2
[0] == 'P' && buffer2
[1] == 'K' && buffer2
[2] == 0x07 && buffer2
[3] == 0x08;
670 if (device
->read(buffer2
, UNZIP_DD_SIZE
) != UNZIP_DD_SIZE
)
671 return UnZip::ReadFailed
;
675 if (device
->read(buffer2
+ 4, UNZIP_DD_SIZE
- 4) != UNZIP_DD_SIZE
- 4)
676 return UnZip::ReadFailed
;
679 // DD: crc, compressed size, uncompressed size
681 entry
.crc
!= getULong((unsigned char*)buffer2
, UNZIP_DD_OFF_CRC32
) ||
682 entry
.szComp
!= getULong((unsigned char*)buffer2
, UNZIP_DD_OFF_CSIZE
) ||
683 entry
.szUncomp
!= getULong((unsigned char*)buffer2
, UNZIP_DD_OFF_USIZE
)
685 return UnZip::HeaderConsistencyError
;
691 /*! \internal Attempts to find the start of the central directory record.
693 We seek the file back until we reach the "End Of Central Directory"
696 end of central dir signature 4 bytes (0x06054b50)
697 number of this disk 2 bytes
698 number of the disk with the
699 start of the central directory 2 bytes
700 total number of entries in the
701 central directory on this disk 2 bytes
702 total number of entries in
703 the central directory 2 bytes
704 size of the central directory 4 bytes
705 offset of start of central
706 directory with respect to
707 the starting disk number 4 bytes
708 .ZIP file comment length 2 bytes
709 --- SIZE UNTIL HERE: UNZIP_EOCD_SIZE ---
710 .ZIP file comment (variable size)
712 UnZip::ErrorCode
UnzipPrivate::seekToCentralDirectory()
714 qint64 length
= device
->size();
715 qint64 offset
= length
- UNZIP_EOCD_SIZE
;
717 if (length
< UNZIP_EOCD_SIZE
)
718 return UnZip::InvalidArchive
;
720 if (!device
->seek( offset
))
721 return UnZip::SeekFailed
;
723 if (device
->read(buffer1
, UNZIP_EOCD_SIZE
) != UNZIP_EOCD_SIZE
)
724 return UnZip::ReadFailed
;
726 bool eocdFound
= (buffer1
[0] == 'P' && buffer1
[1] == 'K' && buffer1
[2] == 0x05 && buffer1
[3] == 0x06);
730 // Zip file has no comment (the only variable length field in the EOCD record)
738 offset
-= UNZIP_EOCD_SIZE
;
741 return UnZip::InvalidArchive
;
743 if (!device
->seek( offset
))
744 return UnZip::SeekFailed
;
746 while ((read
= device
->read(buffer1
, UNZIP_EOCD_SIZE
)) >= 0)
748 if ( (p
= strstr(buffer1
, "PK\5\6")) != 0)
750 // Seek to the start of the EOCD record so we can read it fully
751 // Yes... we could simply read the missing bytes and append them to the buffer
752 // but this is far easier so heck it!
753 device
->seek( offset
+ (p
- buffer1
) );
755 eocdOffset
= offset
+ (p
- buffer1
);
758 if (device
->read(buffer1
, UNZIP_EOCD_SIZE
) != UNZIP_EOCD_SIZE
)
759 return UnZip::ReadFailed
;
764 offset
-= UNZIP_EOCD_SIZE
;
766 return UnZip::InvalidArchive
;
768 if (!device
->seek( offset
))
769 return UnZip::SeekFailed
;
774 return UnZip::InvalidArchive
;
776 // Parse EOCD to locate CD offset
777 offset
= getULong((const unsigned char*)buffer1
, UNZIP_EOCD_OFF_CDOFF
+ 4);
781 cdEntryCount
= getUShort((const unsigned char*)buffer1
, UNZIP_EOCD_OFF_ENTRIES
+ 4);
783 quint16 commentLength
= getUShort((const unsigned char*)buffer1
, UNZIP_EOCD_OFF_COMMLEN
+ 4);
784 if (commentLength
!= 0)
786 QByteArray c
= device
->read(commentLength
);
787 if (c
.count() != commentLength
)
788 return UnZip::ReadFailed
;
793 // Seek to the start of the CD record
794 if (!device
->seek( cdOffset
))
795 return UnZip::SeekFailed
;
801 \internal Parses a central directory record.
803 Central Directory record structure:
810 [digital signature] // PKZip 6.2 or later only
814 central file header signature 4 bytes (0x02014b50)
815 version made by 2 bytes
816 version needed to extract 2 bytes
817 general purpose bit flag 2 bytes
818 compression method 2 bytes
819 last mod file time 2 bytes
820 last mod file date 2 bytes
822 compressed size 4 bytes
823 uncompressed size 4 bytes
824 file name length 2 bytes
825 extra field length 2 bytes
826 file comment length 2 bytes
827 disk number start 2 bytes
828 internal file attributes 2 bytes
829 external file attributes 4 bytes
830 relative offset of local header 4 bytes
832 file name (variable size)
833 extra field (variable size)
834 file comment (variable size)
836 UnZip::ErrorCode
UnzipPrivate::parseCentralDirectoryRecord()
839 if (device
->read(buffer1
, UNZIP_CD_ENTRY_SIZE_NS
) != UNZIP_CD_ENTRY_SIZE_NS
)
840 return UnZip::ReadFailed
;
842 bool skipEntry
= false;
844 // Get compression type so we can skip non compatible algorithms
845 quint16 compMethod
= getUShort(uBuffer
, UNZIP_CD_OFF_CMETHOD
);
847 // Get variable size fields length so we can skip the whole record
849 quint16 szName
= getUShort(uBuffer
, UNZIP_CD_OFF_NAMELEN
);
850 quint16 szExtra
= getUShort(uBuffer
, UNZIP_CD_OFF_XLEN
);
851 quint16 szComment
= getUShort(uBuffer
, UNZIP_CD_OFF_COMMLEN
);
853 quint32 skipLength
= szName
+ szExtra
+ szComment
;
855 UnZip::ErrorCode ec
= UnZip::Ok
;
857 if ((compMethod
!= 0) && (compMethod
!= 8))
859 qDebug() << "Unsupported compression method. Skipping file.";
863 // Header parsing may be a problem if version is bigger than UNZIP_VERSION
864 if (!skipEntry
&& buffer1
[UNZIP_CD_OFF_VERSION
] > UNZIP_VERSION
)
866 qDebug() << "Unsupported PKZip version. Skipping file.";
870 if (!skipEntry
&& szName
== 0)
872 qDebug() << "Skipping file with no name.";
876 if (!skipEntry
&& device
->read(buffer2
, szName
) != szName
)
878 ec
= UnZip::ReadFailed
;
886 if (!device
->seek( device
->pos() + skipLength
))
887 ec
= UnZip::SeekFailed
;
889 unsupportedEntryCount
++;
895 QString filename
= QString::fromAscii(buffer2
, szName
);
897 ZipEntryP
* h
= new ZipEntryP
;
898 h
->compMethod
= compMethod
;
900 h
->gpFlag
[0] = buffer1
[UNZIP_CD_OFF_GPFLAG
];
901 h
->gpFlag
[1] = buffer1
[UNZIP_CD_OFF_GPFLAG
+ 1];
903 h
->modTime
[0] = buffer1
[UNZIP_CD_OFF_MODT
];
904 h
->modTime
[1] = buffer1
[UNZIP_CD_OFF_MODT
+ 1];
906 h
->modDate
[0] = buffer1
[UNZIP_CD_OFF_MODD
];
907 h
->modDate
[1] = buffer1
[UNZIP_CD_OFF_MODD
+ 1];
909 h
->crc
= getULong(uBuffer
, UNZIP_CD_OFF_CRC32
);
910 h
->szComp
= getULong(uBuffer
, UNZIP_CD_OFF_CSIZE
);
911 h
->szUncomp
= getULong(uBuffer
, UNZIP_CD_OFF_USIZE
);
913 // Skip extra field (if any)
916 if (!device
->seek( device
->pos() + szExtra
))
919 return UnZip::SeekFailed
;
923 // Read comment field (if any)
926 if (device
->read(buffer2
, szComment
) != szComment
)
929 return UnZip::ReadFailed
;
932 h
->comment
= QString::fromAscii(buffer2
, szComment
);
935 h
->lhOffset
= getULong(uBuffer
, UNZIP_CD_OFF_LHOFFSET
);
938 headers
= new QMap
<QString
, ZipEntryP
*>();
939 headers
->insert(filename
, h
);
944 //! \internal Closes the archive and resets the internal status.
945 void UnzipPrivate::closeArchive()
950 skipAllEncrypted
= false;
954 qDeleteAll(*headers
);
959 delete device
; device
= 0;
961 cdOffset
= eocdOffset
= 0;
963 unsupportedEntryCount
= 0;
969 UnZip::ErrorCode
UnzipPrivate::extractFile(const QString
& path
, ZipEntryP
& entry
, const QDir
& dir
, UnZip::ExtractionOptions options
)
975 int pos
= name
.lastIndexOf('/');
977 // This entry is for a directory
978 if (pos
== name
.length() - 1)
980 if (options
.testFlag(UnZip::SkipPaths
))
983 directory
= QString("%1/%2").arg(dir
.absolutePath()).arg(QDir::cleanPath(name
));
984 if (!createDirectory(directory
))
986 qDebug() << QString("Unable to create directory: %1").arg(directory
);
987 return UnZip::CreateDirFailed
;
993 // Extract path from entry
996 // get directory part
997 dirname
= name
.left(pos
);
998 if (options
.testFlag(UnZip::SkipPaths
))
1000 directory
= dir
.absolutePath();
1004 directory
= QString("%1/%2").arg(dir
.absolutePath()).arg(QDir::cleanPath(dirname
));
1005 if (!createDirectory(directory
))
1007 qDebug() << QString("Unable to create directory: %1").arg(directory
);
1008 return UnZip::CreateDirFailed
;
1011 name
= name
.right(name
.length() - pos
- 1);
1012 } else directory
= dir
.absolutePath();
1014 name
= QString("%1/%2").arg(directory
).arg(name
);
1016 QFile
outFile(name
);
1018 if (!outFile
.open(QIODevice::WriteOnly
))
1020 qDebug() << QString("Unable to open %1 for writing").arg(name
);
1021 return UnZip::OpenFailed
;
1024 //! \todo Set creation/last_modified date/time
1026 UnZip::ErrorCode ec
= extractFile(path
, entry
, &outFile
, options
);
1030 if (ec
!= UnZip::Ok
)
1032 if (!outFile
.remove())
1033 qDebug() << QString("Unable to remove corrupted file: %1").arg(name
);
1040 UnZip::ErrorCode
UnzipPrivate::extractFile(const QString
& path
, ZipEntryP
& entry
, QIODevice
* dev
, UnZip::ExtractionOptions options
)
1045 if (!entry
.lhEntryChecked
)
1047 UnZip::ErrorCode ec
= parseLocalHeaderRecord(path
, entry
);
1048 entry
.lhEntryChecked
= true;
1050 if (ec
!= UnZip::Ok
)
1054 if (!device
->seek(entry
.dataOffset
))
1055 return UnZip::SeekFailed
;
1060 if (entry
.isEncrypted())
1062 UnZip::ErrorCode e
= testPassword(keys
, path
, entry
);
1065 qDebug() << QString("Unable to decrypt %1").arg(path
);
1067 }//! Encryption header size
1068 entry
.szComp
-= UNZIP_LOCAL_ENC_HEADER_SIZE
; // remove encryption header size
1071 if (entry
.szComp
== 0)
1074 return UnZip::Corrupted
;
1079 uInt rep
= entry
.szComp
/ UNZIP_READ_BUFFER
;
1080 uInt rem
= entry
.szComp
% UNZIP_READ_BUFFER
;
1087 quint32 myCRC
= crc32(0L, Z_NULL
, 0);
1089 if (entry
.compMethod
== 0)
1091 while ( (read
= device
->read(buffer1
, cur
< rep
? UNZIP_READ_BUFFER
: rem
)) > 0 )
1093 if (entry
.isEncrypted())
1094 decryptBytes(keys
, buffer1
, read
);
1096 myCRC
= crc32(myCRC
, uBuffer
, read
);
1098 if (dev
->write(buffer1
, read
) != read
)
1099 return UnZip::WriteFailed
;
1104 if (tot
== entry
.szComp
)
1109 return UnZip::ReadFailed
;
1111 else if (entry
.compMethod
== 8)
1113 /* Allocate inflate state */
1115 zstr
.zalloc
= Z_NULL
;
1116 zstr
.zfree
= Z_NULL
;
1117 zstr
.opaque
= Z_NULL
;
1118 zstr
.next_in
= Z_NULL
;
1123 // Use inflateInit2 with negative windowBits to get raw decompression
1124 if ( (zret
= inflateInit2_(&zstr
, -MAX_WBITS
, ZLIB_VERSION
, sizeof(z_stream
))) != Z_OK
)
1125 return UnZip::ZlibError
;
1129 // Decompress until deflate stream ends or end of file
1132 read
= device
->read(buffer1
, cur
< rep
? UNZIP_READ_BUFFER
: rem
);
1137 (void)inflateEnd(&zstr
);
1138 return UnZip::ReadFailed
;
1141 if (entry
.isEncrypted())
1142 decryptBytes(keys
, buffer1
, read
);
1147 zstr
.avail_in
= (uInt
) read
;
1148 zstr
.next_in
= (Bytef
*) buffer1
;
1151 // Run inflate() on input until output buffer not full
1153 zstr
.avail_out
= UNZIP_READ_BUFFER
;
1154 zstr
.next_out
= (Bytef
*) buffer2
;;
1156 zret
= inflate(&zstr
, Z_NO_FLUSH
);
1163 return UnZip::WriteFailed
;
1168 szDecomp
= UNZIP_READ_BUFFER
- zstr
.avail_out
;
1169 if (dev
->write(buffer2
, szDecomp
) != szDecomp
)
1172 return UnZip::ZlibError
;
1175 myCRC
= crc32(myCRC
, (const Bytef
*) buffer2
, szDecomp
);
1177 } while (zstr
.avail_out
== 0);
1180 while (zret
!= Z_STREAM_END
);
1185 if (myCRC
!= entry
.crc
)
1186 return UnZip::Corrupted
;
1191 //! \internal Creates a new directory and all the needed parent directories.
1192 bool UnzipPrivate::createDirectory(const QString
& path
)
1197 int sep
= path
.lastIndexOf("/");
1198 if (sep
<= 0) return true;
1200 if (!createDirectory(path
.left(sep
)))
1205 qDebug() << QString("Unable to create directory: %1").arg(path
);
1214 \internal Reads an quint32 (4 bytes) from a byte array starting at given offset.
1216 quint32
UnzipPrivate::getULong(const unsigned char* data
, quint32 offset
) const
1218 quint32 res
= (quint32
) data
[offset
];
1219 res
|= (((quint32
)data
[offset
+1]) << 8);
1220 res
|= (((quint32
)data
[offset
+2]) << 16);
1221 res
|= (((quint32
)data
[offset
+3]) << 24);
1227 \internal Reads an quint64 (8 bytes) from a byte array starting at given offset.
1229 quint64
UnzipPrivate::getULLong(const unsigned char* data
, quint32 offset
) const
1231 quint64 res
= (quint64
) data
[offset
];
1232 res
|= (((quint64
)data
[offset
+1]) << 8);
1233 res
|= (((quint64
)data
[offset
+2]) << 16);
1234 res
|= (((quint64
)data
[offset
+3]) << 24);
1235 res
|= (((quint64
)data
[offset
+1]) << 32);
1236 res
|= (((quint64
)data
[offset
+2]) << 40);
1237 res
|= (((quint64
)data
[offset
+3]) << 48);
1238 res
|= (((quint64
)data
[offset
+3]) << 56);
1244 \internal Reads an quint16 (2 bytes) from a byte array starting at given offset.
1246 quint16
UnzipPrivate::getUShort(const unsigned char* data
, quint32 offset
) const
1248 return (quint16
) data
[offset
] | (((quint16
)data
[offset
+1]) << 8);
1252 \internal Return the next byte in the pseudo-random sequence
1254 int UnzipPrivate::decryptByte(quint32 key2
) const
1256 quint16 temp
= ((quint16
)(key2
) & 0xffff) | 2;
1257 return (int)(((temp
* (temp
^ 1)) >> 8) & 0xff);
1261 \internal Update the encryption keys with the next byte of plain text
1263 void UnzipPrivate::updateKeys(quint32
* keys
, int c
) const
1265 keys
[0] = CRC32(keys
[0], c
);
1266 keys
[1] += keys
[0] & 0xff;
1267 keys
[1] = keys
[1] * 134775813L + 1;
1268 keys
[2] = CRC32(keys
[2], ((int)keys
[1]) >> 24);
1272 \internal Initialize the encryption keys and the random header according to
1275 void UnzipPrivate::initKeys(const QString
& pwd
, quint32
* keys
) const
1277 keys
[0] = 305419896L;
1278 keys
[1] = 591751049L;
1279 keys
[2] = 878082192L;
1281 QByteArray pwdBytes
= pwd
.toAscii();
1282 int sz
= pwdBytes
.size();
1283 const char* ascii
= pwdBytes
.data();
1285 for (int i
=0; i
<sz
; ++i
)
1286 updateKeys(keys
, (int)ascii
[i
]);
1290 \internal Attempts to test a password without actually extracting a file.
1291 The \p file parameter can be used in the user interface or for debugging purposes
1292 as it is the name of the encrypted file for wich the password is being tested.
1294 UnZip::ErrorCode
UnzipPrivate::testPassword(quint32
* keys
, const QString
& file
, const ZipEntryP
& header
)
1298 // read encryption keys
1299 if (device
->read(buffer1
, 12) != 12)
1300 return UnZip::Corrupted
;
1302 // Replace this code if you want to i.e. call some dialog and ask the user for a password
1303 initKeys(password
, keys
);
1304 if (testKeys(header
, keys
))
1311 \internal Tests a set of keys on the encryption header.
1313 bool UnzipPrivate::testKeys(const ZipEntryP
& header
, quint32
* keys
)
1317 // decrypt encryption header
1318 for (int i
=0; i
<11; ++i
)
1319 updateKeys(keys
, lastByte
= buffer1
[i
] ^ decryptByte(keys
[2]));
1320 updateKeys(keys
, lastByte
= buffer1
[11] ^ decryptByte(keys
[2]));
1322 // if there is an extended header (bit in the gp flag) buffer[11] is a byte from the file time
1323 // with no extended header we have to check the crc high-order byte
1324 char c
= ((header
.gpFlag
[0] & 0x08) == 8) ? header
.modTime
[1] : header
.crc
>> 24;
1326 return (lastByte
== c
);
1330 \internal Decrypts an array of bytes long \p read.
1332 void UnzipPrivate::decryptBytes(quint32
* keys
, char* buffer
, qint64 read
)
1334 for (int i
=0; i
<(int)read
; ++i
)
1335 updateKeys(keys
, buffer
[i
] ^= decryptByte(keys
[2]));
1339 \internal Converts date and time values from ZIP format to a QDateTime object.
1341 QDateTime
UnzipPrivate::convertDateTime(const unsigned char date
[2], const unsigned char time
[2]) const
1345 // Usual PKZip low-byte to high-byte order
1347 // Date: 7 bits = years from 1980, 4 bits = month, 5 bits = day
1348 quint16 year
= (date
[1] >> 1) & 127;
1349 quint16 month
= ((date
[1] << 3) & 14) | ((date
[0] >> 5) & 7);
1350 quint16 day
= date
[0] & 31;
1352 // Time: 5 bits hour, 6 bits minutes, 5 bits seconds with a 2sec precision
1353 quint16 hour
= (time
[1] >> 3) & 31;
1354 quint16 minutes
= ((time
[1] << 3) & 56) | ((time
[0] >> 5) & 7);
1355 quint16 seconds
= (time
[0] & 31) * 2;
1357 dt
.setDate(QDate(1980 + year
, month
, day
));
1358 dt
.setTime(QTime(hour
, minutes
, seconds
));