1 /****************************************************************************
3 ** Last updated [dd/mm/yyyy]: 01/02/2007
5 ** pkzip 2.0 file compression.
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"
32 // we only use this to seed the random number generator
37 #include <QStringList>
41 #include <QCoreApplication>
43 // You can remove this #include if you replace the qDebug() statements.
46 //! Local header size (including signature, excluding variable length fields)
47 #define ZIP_LOCAL_HEADER_SIZE 30
48 //! Encryption header size
49 #define ZIP_LOCAL_ENC_HEADER_SIZE 12
50 //! Data descriptor size (signature included)
51 #define ZIP_DD_SIZE_WS 16
52 //! Central Directory record size (signature included)
53 #define ZIP_CD_SIZE 46
54 //! End of Central Directory record size (signature included)
55 #define ZIP_EOCD_SIZE 22
57 // Some offsets inside a local header record (signature included)
58 #define ZIP_LH_OFF_VERS 4
59 #define ZIP_LH_OFF_GPFLAG 6
60 #define ZIP_LH_OFF_CMET 8
61 #define ZIP_LH_OFF_MODT 10
62 #define ZIP_LH_OFF_MODD 12
63 #define ZIP_LH_OFF_CRC 14
64 #define ZIP_LH_OFF_CSIZE 18
65 #define ZIP_LH_OFF_USIZE 22
66 #define ZIP_LH_OFF_NAMELEN 26
67 #define ZIP_LH_OFF_XLEN 28
69 // Some offsets inside a data descriptor record (including signature)
70 #define ZIP_DD_OFF_CRC32 4
71 #define ZIP_DD_OFF_CSIZE 8
72 #define ZIP_DD_OFF_USIZE 12
74 // Some offsets inside a Central Directory record (including signature)
75 #define ZIP_CD_OFF_MADEBY 4
76 #define ZIP_CD_OFF_VERSION 6
77 #define ZIP_CD_OFF_GPFLAG 8
78 #define ZIP_CD_OFF_CMET 10
79 #define ZIP_CD_OFF_MODT 12
80 #define ZIP_CD_OFF_MODD 14
81 #define ZIP_CD_OFF_CRC 16
82 #define ZIP_CD_OFF_CSIZE 20
83 #define ZIP_CD_OFF_USIZE 24
84 #define ZIP_CD_OFF_NAMELEN 28
85 #define ZIP_CD_OFF_XLEN 30
86 #define ZIP_CD_OFF_COMMLEN 32
87 #define ZIP_CD_OFF_DISKSTART 34
88 #define ZIP_CD_OFF_IATTR 36
89 #define ZIP_CD_OFF_EATTR 38
90 #define ZIP_CD_OFF_LHOFF 42
92 // Some offsets inside a EOCD record (including signature)
93 #define ZIP_EOCD_OFF_DISKNUM 4
94 #define ZIP_EOCD_OFF_CDDISKNUM 6
95 #define ZIP_EOCD_OFF_ENTRIES 8
96 #define ZIP_EOCD_OFF_CDENTRIES 10
97 #define ZIP_EOCD_OFF_CDSIZE 12
98 #define ZIP_EOCD_OFF_CDOFF 16
99 #define ZIP_EOCD_OFF_COMMLEN 20
101 //! PKZip version for archives created by this API
102 #define ZIP_VERSION 0x14
104 //! Do not store very small files as the compression headers overhead would be to big
105 #define ZIP_COMPRESSION_THRESHOLD 60
107 //! This macro updates a one-char-only CRC; it's the Info-Zip macro re-adapted
108 #define CRC32(c, b) crcTable[((int)c^b) & 0xff] ^ (c >> 8)
113 \brief Zip file compression.
115 Some quick usage examples.
118 Suppose you have this directory structure:
124 /root/dir1/dir1.2/file1.2.1
127 myZipInstance.addDirectory("/root/dir1");
130 Beheaves like any common zip software and creates a zip file with this structure:
136 dir1/dir1.2/file1.2.1
139 myZipInstance.addDirectory("/root/dir1", "myRoot/myFolder");
142 Adds a custom root to the paths and creates a zip file with this structure:
144 myRoot/myFolder/dir1/
145 myRoot/myFolder/dir1/file1.1
146 myRoot/myFolder/dir1/file1.2
147 myRoot/myFolder/dir1/dir1.1/
148 myRoot/myFolder/dir1/dir1.2/file1.2.1
151 myZipInstance.addDirectory("/root/dir1", Zip::AbsolutePaths);
154 Same as calling addDirectory(SOME_PATH, PARENT_PATH_of_SOME_PATH).
157 Preserves absolute paths and creates a zip file with this structure:
163 /root/dir1/dir1.2/file1.2.1
166 myZipInstance.setPassword("hellopass");
167 myZipInstance.addDirectory("/root/dir1", "/");
170 Adds and encrypts the files in /root/dir1, creating the following zip structure:
176 /dir1/dir1.2/file1.2.1
181 /*! \enum Zip::ErrorCode The result of a compression operation.
182 \value Zip::Ok No error occurred.
183 \value Zip::ZlibInit Failed to init or load the zlib library.
184 \value Zip::ZlibError The zlib library returned some error.
185 \value Zip::FileExists The file already exists and will not be overwritten.
186 \value Zip::OpenFailed Unable to create or open a device.
187 \value Zip::NoOpenArchive CreateArchive() has not been called yet.
188 \value Zip::FileNotFound File or directory does not exist.
189 \value Zip::ReadFailed Reading of a file failed.
190 \value Zip::WriteFailed Writing of a file failed.
191 \value Zip::SeekFailed Seek failed.
194 /*! \enum Zip::CompressionLevel Returns the result of a decompression operation.
195 \value Zip::Store No compression.
196 \value Zip::Deflate1 Deflate compression level 1(lowest compression).
197 \value Zip::Deflate1 Deflate compression level 2.
198 \value Zip::Deflate1 Deflate compression level 3.
199 \value Zip::Deflate1 Deflate compression level 4.
200 \value Zip::Deflate1 Deflate compression level 5.
201 \value Zip::Deflate1 Deflate compression level 6.
202 \value Zip::Deflate1 Deflate compression level 7.
203 \value Zip::Deflate1 Deflate compression level 8.
204 \value Zip::Deflate1 Deflate compression level 9 (maximum compression).
205 \value Zip::AutoCPU Adapt compression level to CPU speed (faster CPU => better compression).
206 \value Zip::AutoMIME Adapt compression level to MIME type of the file being compressed.
207 \value Zip::AutoFull Use both CPU and MIME type detection.
211 /************************************************************************
213 *************************************************************************/
216 Creates a new Zip file compressor.
224 Closes any open archive and releases used resources.
233 Returns true if there is an open archive.
235 bool Zip::isOpen() const
237 return d
->device
!= 0;
241 Sets the password to be used for the next files being added!
242 Files added before calling this method will use the previously
243 set password (if any).
244 Closing the archive won't clear the password!
246 void Zip::setPassword(const QString
& pwd
)
251 //! Convenience method, clears the current password.
252 void Zip::clearPassword()
257 //! Returns the currently used password.
258 QString
Zip::password() const
264 Attempts to create a new Zip archive. If \p overwrite is true and the file
265 already exist it will be overwritten.
266 Any open archive will be closed.
268 Zip::ErrorCode
Zip::createArchive(const QString
& filename
, bool overwrite
)
270 QFile
* file
= new QFile(filename
);
272 if (file
->exists() && !overwrite
) {
274 return Zip::FileExists
;
277 if (!file
->open(QIODevice::WriteOnly
)) {
279 return Zip::OpenFailed
;
282 Zip::ErrorCode ec
= createArchive(file
);
291 Attempts to create a new Zip archive. If there is another open archive this will be closed.
292 \warning The class takes ownership of the device!
294 Zip::ErrorCode
Zip::createArchive(QIODevice
* device
)
298 qDebug() << "Invalid device.";
299 return Zip::OpenFailed
;
302 return d
->createArchive(device
);
306 Returns the current archive comment.
308 QString
Zip::archiveComment() const
314 Sets the comment for this archive. Note: createArchive() should have been
317 void Zip::setArchiveComment(const QString
& comment
)
320 d
->comment
= comment
;
324 Convenience method, same as calling
325 Zip::addDirectory(const QString&,const QString&,CompressionLevel)
326 with an empty \p root parameter (or with the parent directory of \p path if the
327 AbsolutePaths options is set).
329 The ExtractionOptions are checked in the order they are defined in the zip.h heaser file.
330 This means that the last one overwrites the previous one (if some conflict occurs), i.e.
331 Zip::IgnorePaths | Zip::AbsolutePaths would be interpreted as Zip::IgnorePaths.
333 Zip::ErrorCode
Zip::addDirectory(const QString
& path
, CompressionOptions options
, CompressionLevel level
)
335 return addDirectory(path
, QString(), options
, level
);
339 Convenience method, same as calling Zip::addDirectory(const QString&,const QString&,CompressionOptions,CompressionLevel)
340 with the Zip::RelativePaths flag as compression option.
342 Zip::ErrorCode
Zip::addDirectory(const QString
& path
, const QString
& root
, CompressionLevel level
)
344 return addDirectory(path
, root
, Zip::RelativePaths
, level
);
348 Convenience method, same as calling Zip::addDirectory(const QString&,const QString&,CompressionOptions,CompressionLevel)
349 with the Zip::IgnorePaths flag as compression option and an empty \p root parameter.
351 Zip::ErrorCode
Zip::addDirectoryContents(const QString
& path
, CompressionLevel level
)
353 return addDirectory(path
, QString(), IgnorePaths
, level
);
357 Convenience method, same as calling Zip::addDirectory(const QString&,const QString&,CompressionOptions,CompressionLevel)
358 with the Zip::IgnorePaths flag as compression option.
360 Zip::ErrorCode
Zip::addDirectoryContents(const QString
& path
, const QString
& root
, CompressionLevel level
)
362 return addDirectory(path
, root
, IgnorePaths
, level
);
366 Recursively adds files contained in \p dir to the archive, using \p root as name for the root folder.
367 Stops adding files if some error occurs.
369 The ExtractionOptions are checked in the order they are defined in the zip.h heaser file.
370 This means that the last one overwrites the previous one (if some conflict occurs), i.e.
371 Zip::IgnorePaths | Zip::AbsolutePaths would be interpreted as Zip::IgnorePaths.
373 The \p root parameter is ignored with the Zip::IgnorePaths parameter and used as path prefix (a trailing /
374 is always added as directory separator!) otherwise (even with Zip::AbsolutePaths set!).
376 Zip::ErrorCode
Zip::addDirectory(const QString
& path
, const QString
& root
, CompressionOptions options
, CompressionLevel level
)
378 // qDebug() << QString("addDir(path=%1, root=%2)").arg(path, root);
380 // Bad boy didn't call createArchive() yet :)
382 return Zip::NoOpenArchive
;
386 return Zip::FileNotFound
;
388 // Remove any trailing separator
389 QString actualRoot
= root
.trimmed();
391 // Preserve Unix root
392 if (actualRoot
!= "/")
394 while (actualRoot
.endsWith("/") || actualRoot
.endsWith("\\"))
395 actualRoot
.truncate(actualRoot
.length() - 1);
398 // QDir::cleanPath() fixes some issues with QDir::dirName()
399 QFileInfo
current(QDir::cleanPath(path
));
401 if (!actualRoot
.isEmpty() && actualRoot
!= "/")
402 actualRoot
.append("/");
404 /* This part is quite confusing and needs some test or check */
405 /* An attempt to compress the / root directory evtl. using a root prefix should be a good test */
406 if (options
.testFlag(AbsolutePaths
) && !options
.testFlag(IgnorePaths
))
408 QString absolutePath
= d
->extractRoot(path
);
409 if (!absolutePath
.isEmpty() && absolutePath
!= "/")
410 absolutePath
.append("/");
411 actualRoot
.append(absolutePath
);
414 if (!options
.testFlag(IgnorePaths
))
416 actualRoot
= actualRoot
.append(QDir(current
.absoluteFilePath()).dirName());
417 actualRoot
.append("/");
420 // actualRoot now contains the path of the file relative to the zip archive
423 QFileInfoList list
= dir
.entryInfoList(
426 QDir::NoDotAndDotDot
|
429 ErrorCode ec
= Zip::Ok
;
430 bool filesAdded
= false;
432 CompressionOptions recursionOptions
;
433 if (options
.testFlag(IgnorePaths
))
434 recursionOptions
|= IgnorePaths
;
435 else recursionOptions
|= RelativePaths
;
437 for (int i
= 0; i
< list
.size() && ec
== Zip::Ok
; ++i
)
439 QFileInfo info
= list
.at(i
);
444 ec
= addDirectory(info
.absoluteFilePath(), actualRoot
, recursionOptions
, level
);
448 ec
= d
->createEntry(info
, actualRoot
, level
);
454 // We need an explicit record for this dir
455 // Non-empty directories don't need it because they have a path component in the filename
456 if (!filesAdded
&& !options
.testFlag(IgnorePaths
))
457 ec
= d
->createEntry(current
, actualRoot
, level
);
463 Closes the archive and writes any pending data.
465 Zip::ErrorCode
Zip::closeArchive()
467 Zip::ErrorCode ec
= d
->closeArchive();
473 Returns a locale translated error string for a given error code.
475 QString
Zip::formatError(Zip::ErrorCode c
) const
479 case Ok
: return QCoreApplication::translate("Zip", "ZIP operation completed successfully."); break;
480 case ZlibInit
: return QCoreApplication::translate("Zip", "Failed to initialize or load zlib library."); break;
481 case ZlibError
: return QCoreApplication::translate("Zip", "zlib library error."); break;
482 case OpenFailed
: return QCoreApplication::translate("Zip", "Unable to create or open file."); break;
483 case NoOpenArchive
: return QCoreApplication::translate("Zip", "No archive has been created yet."); break;
484 case FileNotFound
: return QCoreApplication::translate("Zip", "File or directory does not exist."); break;
485 case ReadFailed
: return QCoreApplication::translate("Zip", "File read error."); break;
486 case WriteFailed
: return QCoreApplication::translate("Zip", "File write error."); break;
487 case SeekFailed
: return QCoreApplication::translate("Zip", "File seek error."); break;
491 return QCoreApplication::translate("Zip", "Unknown error.");
495 /************************************************************************
497 *************************************************************************/
500 ZipPrivate::ZipPrivate()
505 // keep an unsigned pointer so we avoid to over bloat the code with casts
506 uBuffer
= (unsigned char*) buffer1
;
507 crcTable
= (quint32
*) get_crc_table();
511 ZipPrivate::~ZipPrivate()
517 Zip::ErrorCode
ZipPrivate::createArchive(QIODevice
* dev
)
526 if (!device
->isOpen())
528 if (!device
->open(QIODevice::ReadOnly
)) {
531 qDebug() << "Unable to open device for writing.";
532 return Zip::OpenFailed
;
536 headers
= new QMap
<QString
,ZipEntryP
*>;
540 //! \internal Writes a new entry in the zip file.
541 Zip::ErrorCode
ZipPrivate::createEntry(const QFileInfo
& file
, const QString
& root
, Zip::CompressionLevel level
)
543 //! \todo Automatic level detection (cpu, extension & file size)
545 // Directories and very small files are always stored
546 // (small files would get bigger due to the compression headers overhead)
548 // Need this for zlib
549 bool isPNGFile
= false;
550 bool dirOnly
= file
.isDir();
552 QString entryName
= root
;
559 entryName
.append(file
.fileName());
561 QString ext
= file
.completeSuffix().toLower();
562 isPNGFile
= ext
== "png";
564 if (file
.size() < ZIP_COMPRESSION_THRESHOLD
)
570 level
= Zip::Deflate5
;
573 level
= detectCompressionByMime(ext
);
576 level
= detectCompressionByMime(ext
);
583 // entryName contains the path as it should be written
584 // in the zip file records
585 // qDebug() << QString("addDir(file=%1, root=%2, entry=%3)").arg(file.absoluteFilePath(), root, entryName);
587 // create header and store it to write a central directory later
588 ZipEntryP
* h
= new ZipEntryP
;
590 h
->compMethod
= (level
== Zip::Store
) ? 0 : 0x0008;
592 // Set encryption bit and set the data descriptor bit
593 // so we can use mod time instead of crc for password check
594 bool encrypt
= !dirOnly
&& !password
.isEmpty();
598 QDateTime dt
= file
.lastModified();
600 h
->modDate
[1] = ((d
.year() - 1980) << 1) & 254;
601 h
->modDate
[1] |= ((d
.month() >> 3) & 1);
602 h
->modDate
[0] = ((d
.month() & 7) << 5) & 224;
603 h
->modDate
[0] |= d
.day();
606 h
->modTime
[1] = (t
.hour() << 3) & 248;
607 h
->modTime
[1] |= ((t
.minute() >> 3) & 7);
608 h
->modTime
[0] = ((t
.minute() & 7) << 5) & 224;
609 h
->modTime
[0] |= t
.second() / 2;
611 h
->szUncomp
= dirOnly
? 0 : file
.size();
613 // **** Write local file header ****
616 buffer1
[0] = 'P'; buffer1
[1] = 'K';
617 buffer1
[2] = 0x3; buffer1
[3] = 0x4;
619 // version needed to extract
620 buffer1
[ZIP_LH_OFF_VERS
] = ZIP_VERSION
;
621 buffer1
[ZIP_LH_OFF_VERS
+ 1] = 0;
623 // general purpose flag
624 buffer1
[ZIP_LH_OFF_GPFLAG
] = h
->gpFlag
[0];
625 buffer1
[ZIP_LH_OFF_GPFLAG
+ 1] = h
->gpFlag
[1];
627 // compression method
628 buffer1
[ZIP_LH_OFF_CMET
] = h
->compMethod
& 0xFF;
629 buffer1
[ZIP_LH_OFF_CMET
+ 1] = (h
->compMethod
>>8) & 0xFF;
631 // last mod file time
632 buffer1
[ZIP_LH_OFF_MODT
] = h
->modTime
[0];
633 buffer1
[ZIP_LH_OFF_MODT
+ 1] = h
->modTime
[1];
635 // last mod file date
636 buffer1
[ZIP_LH_OFF_MODD
] = h
->modDate
[0];
637 buffer1
[ZIP_LH_OFF_MODD
+ 1] = h
->modDate
[1];
639 // skip crc (4bytes) [14,15,16,17]
641 // skip compressed size but include evtl. encryption header (4bytes: [18,19,20,21])
642 buffer1
[ZIP_LH_OFF_CSIZE
] =
643 buffer1
[ZIP_LH_OFF_CSIZE
+ 1] =
644 buffer1
[ZIP_LH_OFF_CSIZE
+ 2] =
645 buffer1
[ZIP_LH_OFF_CSIZE
+ 3] = 0;
647 h
->szComp
= encrypt
? ZIP_LOCAL_ENC_HEADER_SIZE
: 0;
649 // uncompressed size [22,23,24,25]
650 setULong(h
->szUncomp
, buffer1
, ZIP_LH_OFF_USIZE
);
653 QByteArray entryNameBytes
= entryName
.toAscii();
654 int sz
= entryNameBytes
.size();
656 buffer1
[ZIP_LH_OFF_NAMELEN
] = sz
& 0xFF;
657 buffer1
[ZIP_LH_OFF_NAMELEN
+ 1] = (sz
>> 8) & 0xFF;
659 // extra field length
660 buffer1
[ZIP_LH_OFF_XLEN
] = buffer1
[ZIP_LH_OFF_XLEN
+ 1] = 0;
662 // Store offset to write crc and compressed size
663 h
->lhOffset
= device
->pos();
664 quint32 crcOffset
= h
->lhOffset
+ ZIP_LH_OFF_CRC
;
666 if (device
->write(buffer1
, ZIP_LOCAL_HEADER_SIZE
) != ZIP_LOCAL_HEADER_SIZE
)
669 return Zip::WriteFailed
;
672 // Write out filename
673 if (device
->write(entryNameBytes
) != sz
)
676 return Zip::WriteFailed
;
680 quint32 keys
[3] = { 0, 0, 0 };
684 // **** encryption header ****
686 // XOR with PI to ensure better random numbers
687 // with poorly implemented rand() as suggested by Info-Zip
688 srand(time(NULL
) ^ 3141592654UL);
692 for (int i
=0; i
<10; ++i
)
694 randByte
= (rand() >> 7) & 0xff;
695 buffer1
[i
] = decryptByte(keys
[2]) ^ randByte
;
696 updateKeys(keys
, randByte
);
699 // Encrypt encryption header
701 for (int i
=0; i
<10; ++i
)
703 randByte
= decryptByte(keys
[2]);
704 updateKeys(keys
, buffer1
[i
]);
705 buffer1
[i
] ^= randByte
;
708 // We don't know the CRC at this time, so we use the modification time
709 // as the last two bytes
710 randByte
= decryptByte(keys
[2]);
711 updateKeys(keys
, h
->modTime
[0]);
712 buffer1
[10] ^= randByte
;
714 randByte
= decryptByte(keys
[2]);
715 updateKeys(keys
, h
->modTime
[1]);
716 buffer1
[11] ^= randByte
;
718 // Write out encryption header
719 if (device
->write(buffer1
, ZIP_LOCAL_ENC_HEADER_SIZE
) != ZIP_LOCAL_ENC_HEADER_SIZE
)
722 return Zip::WriteFailed
;
727 quint32 crc
= crc32(0L, Z_NULL
, 0);
731 QFile
actualFile(file
.absoluteFilePath());
732 if (!actualFile
.open(QIODevice::ReadOnly
))
734 qDebug() << QString("An error occurred while opening %1").arg(file
.absoluteFilePath());
735 return Zip::OpenFailed
;
741 qint64 toRead
= actualFile
.size();
743 if (level
== Zip::Store
)
745 while ( (read
= actualFile
.read(buffer1
, ZIP_READ_BUFFER
)) > 0 )
747 crc
= crc32(crc
, uBuffer
, read
);
750 encryptBytes(keys
, buffer1
, read
);
752 if ( (written
= device
->write(buffer1
, read
)) != read
)
756 return Zip::WriteFailed
;
764 // Initialize zalloc, zfree and opaque before calling the init function
765 zstr
.zalloc
= Z_NULL
;
767 zstr
.opaque
= Z_NULL
;
771 // Use deflateInit2 with negative windowBits to get raw compression
772 if ((zret
= deflateInit2_(
778 isPNGFile
? Z_RLE
: Z_DEFAULT_STRATEGY
,
784 qDebug() << "Could not initialize zlib for compression";
786 return Zip::ZlibError
;
791 int flush
= Z_NO_FLUSH
;
795 read
= actualFile
.read(buffer1
, ZIP_READ_BUFFER
);
804 qDebug() << QString("Error while reading %1").arg(file
.absoluteFilePath());
806 return Zip::ReadFailed
;
809 crc
= crc32(crc
, uBuffer
, read
);
811 zstr
.next_in
= (Bytef
*) buffer1
;
812 zstr
.avail_in
= (uInt
)read
;
814 // Tell zlib if this is the last chunk we want to encode
815 // by setting the flush parameter to Z_FINISH
816 flush
= (totRead
== toRead
) ? Z_FINISH
: Z_NO_FLUSH
;
818 // Run deflate() on input until output buffer not full
819 // finish compression if all of source has been read in
822 zstr
.next_out
= (Bytef
*) buffer2
;
823 zstr
.avail_out
= ZIP_READ_BUFFER
;
825 zret
= deflate(&zstr
, flush
);
826 // State not clobbered
827 Q_ASSERT(zret
!= Z_STREAM_ERROR
);
829 // Write compressed data to file and empty buffer
830 compressed
= ZIP_READ_BUFFER
- zstr
.avail_out
;
833 encryptBytes(keys
, buffer2
, compressed
);
835 if (device
->write(buffer2
, compressed
) != compressed
)
839 qDebug() << QString("Error while writing %1").arg(file
.absoluteFilePath());
841 return Zip::WriteFailed
;
844 written
+= compressed
;
846 } while (zstr
.avail_out
== 0);
848 // All input will be used
849 Q_ASSERT(zstr
.avail_in
== 0);
851 } while (flush
!= Z_FINISH
);
853 // Stream will be complete
854 Q_ASSERT(zret
== Z_STREAM_END
);
858 } // if (level != STORE)
863 // Store end of entry offset
864 quint32 current
= device
->pos();
866 // Update crc and compressed size in local header
867 if (!device
->seek(crcOffset
))
870 return Zip::SeekFailed
;
873 h
->crc
= dirOnly
? 0 : crc
;
874 h
->szComp
+= written
;
876 setULong(h
->crc
, buffer1
, 0);
877 setULong(h
->szComp
, buffer1
, 4);
878 if ( device
->write(buffer1
, 8) != 8)
881 return Zip::WriteFailed
;
884 // Seek to end of entry
885 if (!device
->seek(current
))
888 return Zip::SeekFailed
;
891 if ((h
->gpFlag
[0] & 8) == 8)
893 // Write data descriptor
902 setULong(h
->crc
, buffer1
, ZIP_DD_OFF_CRC32
);
905 setULong(h
->szComp
, buffer1
, ZIP_DD_OFF_CSIZE
);
908 setULong(h
->szUncomp
, buffer1
, ZIP_DD_OFF_USIZE
);
910 if (device
->write(buffer1
, ZIP_DD_SIZE_WS
) != ZIP_DD_SIZE_WS
)
913 return Zip::WriteFailed
;
917 headers
->insert(entryName
, h
);
922 int ZipPrivate::decryptByte(quint32 key2
) const
924 quint16 temp
= ((quint16
)(key2
) & 0xffff) | 2;
925 return (int)(((temp
* (temp
^ 1)) >> 8) & 0xff);
928 //! \internal Writes an quint32 (4 bytes) to a byte array at given offset.
929 void ZipPrivate::setULong(quint32 v
, char* buffer
, unsigned int offset
)
931 buffer
[offset
+3] = ((v
>> 24) & 0xFF);
932 buffer
[offset
+2] = ((v
>> 16) & 0xFF);
933 buffer
[offset
+1] = ((v
>> 8) & 0xFF);
934 buffer
[offset
] = (v
& 0xFF);
937 //! \internal Initializes decryption keys using a password.
938 void ZipPrivate::initKeys(quint32
* keys
) const
940 // Encryption keys initialization constants are taken from the
941 // PKZip file format specification docs
942 keys
[0] = 305419896L;
943 keys
[1] = 591751049L;
944 keys
[2] = 878082192L;
946 QByteArray pwdBytes
= password
.toAscii();
947 int sz
= pwdBytes
.size();
948 const char* ascii
= pwdBytes
.data();
950 for (int i
=0; i
<sz
; ++i
)
951 updateKeys(keys
, (int)ascii
[i
]);
954 //! \internal Updates encryption keys.
955 void ZipPrivate::updateKeys(quint32
* keys
, int c
) const
957 keys
[0] = CRC32(keys
[0], c
);
958 keys
[1] += keys
[0] & 0xff;
959 keys
[1] = keys
[1] * 134775813L + 1;
960 keys
[2] = CRC32(keys
[2], ((int)keys
[1]) >> 24);
963 //! \internal Encrypts a byte array.
964 void ZipPrivate::encryptBytes(quint32
* keys
, char* buffer
, qint64 read
)
968 for (int i
=0; i
<(int)read
; ++i
)
971 buffer
[i
] ^= decryptByte(keys
[2]);
976 //! \internal Detects the best compression level for a given file extension.
977 Zip::CompressionLevel
ZipPrivate::detectCompressionByMime(const QString
& ext
)
979 // files really hard to compress
980 if ((ext
== "png") ||
999 // files slow and hard to compress
1000 if ((ext
== "exe") ||
1004 ) return Zip::Deflate2
;
1006 return Zip::Deflate9
;
1010 Closes the current archive and writes out pending data.
1012 Zip::ErrorCode
ZipPrivate::closeArchive()
1014 // Close current archive by writing out central directory
1015 // and free up resources
1026 quint32 szCentralDir
= 0;
1027 quint32 offCentralDir
= device
->pos();
1029 for (QMap
<QString
,ZipEntryP
*>::ConstIterator itr
= headers
->constBegin(); itr
!= headers
->constEnd(); ++itr
)
1039 // version made by (currently only MS-DOS/FAT - no symlinks or other stuff supported)
1040 buffer1
[ZIP_CD_OFF_MADEBY
] = buffer1
[ZIP_CD_OFF_MADEBY
+ 1] = 0;
1042 // version needed to extract
1043 buffer1
[ZIP_CD_OFF_VERSION
] = ZIP_VERSION
;
1044 buffer1
[ZIP_CD_OFF_VERSION
+ 1] = 0;
1046 // general purpose flag
1047 buffer1
[ZIP_CD_OFF_GPFLAG
] = h
->gpFlag
[0];
1048 buffer1
[ZIP_CD_OFF_GPFLAG
+ 1] = h
->gpFlag
[1];
1050 // compression method
1051 buffer1
[ZIP_CD_OFF_CMET
] = h
->compMethod
& 0xFF;
1052 buffer1
[ZIP_CD_OFF_CMET
+ 1] = (h
->compMethod
>> 8) & 0xFF;
1054 // last mod file time
1055 buffer1
[ZIP_CD_OFF_MODT
] = h
->modTime
[0];
1056 buffer1
[ZIP_CD_OFF_MODT
+ 1] = h
->modTime
[1];
1058 // last mod file date
1059 buffer1
[ZIP_CD_OFF_MODD
] = h
->modDate
[0];
1060 buffer1
[ZIP_CD_OFF_MODD
+ 1] = h
->modDate
[1];
1062 // crc (4bytes) [16,17,18,19]
1063 setULong(h
->crc
, buffer1
, ZIP_CD_OFF_CRC
);
1065 // compressed size (4bytes: [20,21,22,23])
1066 setULong(h
->szComp
, buffer1
, ZIP_CD_OFF_CSIZE
);
1068 // uncompressed size [24,25,26,27]
1069 setULong(h
->szUncomp
, buffer1
, ZIP_CD_OFF_USIZE
);
1072 QByteArray fileNameBytes
= itr
.key().toAscii();
1073 sz
= fileNameBytes
.size();
1074 buffer1
[ZIP_CD_OFF_NAMELEN
] = sz
& 0xFF;
1075 buffer1
[ZIP_CD_OFF_NAMELEN
+ 1] = (sz
>> 8) & 0xFF;
1077 // extra field length
1078 buffer1
[ZIP_CD_OFF_XLEN
] = buffer1
[ZIP_CD_OFF_XLEN
+ 1] = 0;
1080 // file comment length
1081 buffer1
[ZIP_CD_OFF_COMMLEN
] = buffer1
[ZIP_CD_OFF_COMMLEN
+ 1] = 0;
1083 // disk number start
1084 buffer1
[ZIP_CD_OFF_DISKSTART
] = buffer1
[ZIP_CD_OFF_DISKSTART
+ 1] = 0;
1086 // internal file attributes
1087 buffer1
[ZIP_CD_OFF_IATTR
] = buffer1
[ZIP_CD_OFF_IATTR
+ 1] = 0;
1089 // external file attributes
1090 buffer1
[ZIP_CD_OFF_EATTR
] =
1091 buffer1
[ZIP_CD_OFF_EATTR
+ 1] =
1092 buffer1
[ZIP_CD_OFF_EATTR
+ 2] =
1093 buffer1
[ZIP_CD_OFF_EATTR
+ 3] = 0;
1095 // relative offset of local header [42->45]
1096 setULong(h
->lhOffset
, buffer1
, ZIP_CD_OFF_LHOFF
);
1098 if (device
->write(buffer1
, ZIP_CD_SIZE
) != ZIP_CD_SIZE
)
1100 //! \todo See if we can detect QFile objects using the Qt Meta Object System
1102 if (!device->remove())
1103 qDebug() << tr("Unable to delete corrupted archive: %1").arg(device->fileName());
1105 return Zip::WriteFailed
;
1108 // Write out filename
1109 if ((unsigned int)device
->write(fileNameBytes
) != sz
)
1111 //! \todo SAME AS ABOVE: See if we can detect QFile objects using the Qt Meta Object System
1113 if (!device->remove())
1114 qDebug() << tr("Unable to delete corrupted archive: %1").arg(device->fileName());
1116 return Zip::WriteFailed
;
1119 szCentralDir
+= (ZIP_CD_SIZE
+ sz
);
1121 } // central dir headers loop
1124 // Write end of central directory
1132 // number of this disk
1133 buffer1
[ZIP_EOCD_OFF_DISKNUM
] = buffer1
[ZIP_EOCD_OFF_DISKNUM
+ 1] = 0;
1135 // number of disk with central directory
1136 buffer1
[ZIP_EOCD_OFF_CDDISKNUM
] = buffer1
[ZIP_EOCD_OFF_CDDISKNUM
+ 1] = 0;
1138 // number of entries in this disk
1139 sz
= headers
->count();
1140 buffer1
[ZIP_EOCD_OFF_ENTRIES
] = sz
& 0xFF;
1141 buffer1
[ZIP_EOCD_OFF_ENTRIES
+ 1] = (sz
>> 8) & 0xFF;
1143 // total number of entries
1144 buffer1
[ZIP_EOCD_OFF_CDENTRIES
] = buffer1
[ZIP_EOCD_OFF_ENTRIES
];
1145 buffer1
[ZIP_EOCD_OFF_CDENTRIES
+ 1] = buffer1
[ZIP_EOCD_OFF_ENTRIES
+ 1];
1147 // size of central directory [12->15]
1148 setULong(szCentralDir
, buffer1
, ZIP_EOCD_OFF_CDSIZE
);
1150 // central dir offset [16->19]
1151 setULong(offCentralDir
, buffer1
, ZIP_EOCD_OFF_CDOFF
);
1153 // ZIP file comment length
1154 QByteArray commentBytes
= comment
.toAscii();
1155 quint16 commentLength
= commentBytes
.size();
1157 if (commentLength
== 0)
1159 buffer1
[ZIP_EOCD_OFF_COMMLEN
] = buffer1
[ZIP_EOCD_OFF_COMMLEN
+ 1] = 0;
1163 buffer1
[ZIP_EOCD_OFF_COMMLEN
] = commentLength
& 0xFF;
1164 buffer1
[ZIP_EOCD_OFF_COMMLEN
+ 1] = (commentLength
>> 8) & 0xFF;
1167 if (device
->write(buffer1
, ZIP_EOCD_SIZE
) != ZIP_EOCD_SIZE
)
1169 //! \todo SAME AS ABOVE: See if we can detect QFile objects using the Qt Meta Object System
1171 if (!device->remove())
1172 qDebug() << tr("Unable to delete corrupted archive: %1").arg(device->fileName());
1174 return Zip::WriteFailed
;
1177 if (commentLength
!= 0)
1179 if ((unsigned int)device
->write(commentBytes
) != commentLength
)
1181 //! \todo SAME AS ABOVE: See if we can detect QFile objects using the Qt Meta Object System
1183 if (!device->remove())
1184 qDebug() << tr("Unable to delete corrupted archive: %1").arg(device->fileName());
1186 return Zip::WriteFailed
;
1194 void ZipPrivate::reset()
1200 qDeleteAll(*headers
);
1205 delete device
; device
= 0;
1208 //! \internal Returns the path of the parent directory
1209 QString
ZipPrivate::extractRoot(const QString
& p
)
1211 QDir
d(QDir::cleanPath(p
));
1218 return d
.absolutePath();