FFSM++ 1.1.0
French Forest Sector Model ++
Loading...
Searching...
No Matches
zip.cpp
Go to the documentation of this file.
1/****************************************************************************
2** Filename: zip.cpp
3** Last updated [dd/mm/yyyy]: 01/02/2007
4**
5** pkzip 2.0 file compression.
6**
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.
10**
11** Copyright (C) 2007 Angius Fabrizio. All rights reserved.
12**
13** This file is part of the OSDaB project (http://osdab.sourceforge.net/).
14**
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.
19**
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.
22**
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.
25**
26**********************************************************************/
27
28#include "zip.h"
29#include "zip_p.h"
30#include "zipentry_p.h"
31
32// we only use this to seed the random number generator
33#include <time.h>
34
35#include <QMap>
36#include <QString>
37#include <QStringList>
38#include <QDir>
39#include <QFile>
40#include <QDateTime>
41#include <QCoreApplication>
42
43// You can remove this #include if you replace the qDebug() statements.
44#include <QtDebug>
45
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
56
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
68
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
73
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
91
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
100
101//! PKZip version for archives created by this API
102#define ZIP_VERSION 0x14
103
104//! Do not store very small files as the compression headers overhead would be to big
105#define ZIP_COMPRESSION_THRESHOLD 60
106
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)
109
110/*!
111 \class Zip zip.h
112
113 \brief Zip file compression.
114
115 Some quick usage examples.
116
117 \verbatim
118 Suppose you have this directory structure:
119
120 /root/dir1/
121 /root/dir1/file1.1
122 /root/dir1/file1.2
123 /root/dir1/dir1.1/
124 /root/dir1/dir1.2/file1.2.1
125
126 EXAMPLE 1:
127 myZipInstance.addDirectory("/root/dir1");
128
129 RESULT:
130 Beheaves like any common zip software and creates a zip file with this structure:
131
132 dir1/
133 dir1/file1.1
134 dir1/file1.2
135 dir1/dir1.1/
136 dir1/dir1.2/file1.2.1
137
138 EXAMPLE 2:
139 myZipInstance.addDirectory("/root/dir1", "myRoot/myFolder");
140
141 RESULT:
142 Adds a custom root to the paths and creates a zip file with this structure:
143
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
149
150 EXAMPLE 3:
151 myZipInstance.addDirectory("/root/dir1", Zip::AbsolutePaths);
152
153 NOTE:
154 Same as calling addDirectory(SOME_PATH, PARENT_PATH_of_SOME_PATH).
155
156 RESULT:
157 Preserves absolute paths and creates a zip file with this structure:
158
159 /root/dir1/
160 /root/dir1/file1.1
161 /root/dir1/file1.2
162 /root/dir1/dir1.1/
163 /root/dir1/dir1.2/file1.2.1
164
165 EXAMPLE 4:
166 myZipInstance.setPassword("hellopass");
167 myZipInstance.addDirectory("/root/dir1", "/");
168
169 RESULT:
170 Adds and encrypts the files in /root/dir1, creating the following zip structure:
171
172 /dir1/
173 /dir1/file1.1
174 /dir1/file1.2
175 /dir1/dir1.1/
176 /dir1/dir1.2/file1.2.1
177
178 \endverbatim
179*/
180
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.
192*/
193
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.
208*/
209
210
211/************************************************************************
212 Public interface
213*************************************************************************/
214
215/*!
216 Creates a new Zip file compressor.
217*/
219{
220 d = new ZipPrivate;
221}
222
223/*!
224 Closes any open archive and releases used resources.
225*/
227{
228 closeArchive();
229 delete d;
230}
231
232/*!
233 Returns true if there is an open archive.
234*/
235bool Zip::isOpen() const
236{
237 return d->device != 0;
238}
239
240/*!
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!
245*/
246void Zip::setPassword(const QString& pwd)
247{
248 d->password = pwd;
249}
250
251//! Convenience method, clears the current password.
253{
254 d->password.clear();
255}
256
257//! Returns the currently used password.
258QString Zip::password() const
259{
260 return d->password;
261}
262
263/*!
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.
267 */
268Zip::ErrorCode Zip::createArchive(const QString& filename, bool overwrite)
269{
270 QFile* file = new QFile(filename);
271
272 if (file->exists() && !overwrite) {
273 delete file;
274 return Zip::FileExists;
275 }
276
277 if (!file->open(QIODevice::WriteOnly)) {
278 delete file;
279 return Zip::OpenFailed;
280 }
281
282 Zip::ErrorCode ec = createArchive(file);
283 if (ec != Zip::Ok) {
284 file->remove();
285 }
286
287 return ec;
288}
289
290/*!
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!
293 */
295{
296 if (device == 0)
297 {
298 qDebug() << "Invalid device.";
299 return Zip::OpenFailed;
300 }
301
302 return d->createArchive(device);
303}
304
305/*!
306 Returns the current archive comment.
307*/
308QString Zip::archiveComment() const
309{
310 return d->comment;
311}
312
313/*!
314 Sets the comment for this archive. Note: createArchive() should have been
315 called before.
316*/
317void Zip::setArchiveComment(const QString& comment)
318{
319 if (d->device != 0)
320 d->comment = comment;
321}
322
323/*!
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).
328
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.
332 */
333Zip::ErrorCode Zip::addDirectory(const QString& path, CompressionOptions options, CompressionLevel level)
334{
335 return addDirectory(path, QString(), options, level);
336}
337
338/*!
339 Convenience method, same as calling Zip::addDirectory(const QString&,const QString&,CompressionOptions,CompressionLevel)
340 with the Zip::RelativePaths flag as compression option.
341 */
342Zip::ErrorCode Zip::addDirectory(const QString& path, const QString& root, CompressionLevel level)
343{
344 return addDirectory(path, root, Zip::RelativePaths, level);
345}
346
347/*!
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.
350*/
352{
353 return addDirectory(path, QString(), IgnorePaths, level);
354}
355
356/*!
357 Convenience method, same as calling Zip::addDirectory(const QString&,const QString&,CompressionOptions,CompressionLevel)
358 with the Zip::IgnorePaths flag as compression option.
359*/
360Zip::ErrorCode Zip::addDirectoryContents(const QString& path, const QString& root, CompressionLevel level)
361{
362 return addDirectory(path, root, IgnorePaths, level);
363}
364
365/*!
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.
368
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.
372
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!).
375*/
376Zip::ErrorCode Zip::addDirectory(const QString& path, const QString& root, CompressionOptions options, CompressionLevel level)
377{
378 // qDebug() << QString("addDir(path=%1, root=%2)").arg(path, root);
379
380 // Bad boy didn't call createArchive() yet :)
381 if (d->device == 0)
382 return Zip::NoOpenArchive;
383
384 QDir dir(path);
385 if (!dir.exists())
386 return Zip::FileNotFound;
387
388 // Remove any trailing separator
389 QString actualRoot = root.trimmed();
390
391 // Preserve Unix root
392 if (actualRoot != "/")
393 {
394 while (actualRoot.endsWith("/") || actualRoot.endsWith("\\"))
395 actualRoot.truncate(actualRoot.length() - 1);
396 }
397
398 // QDir::cleanPath() fixes some issues with QDir::dirName()
399 QFileInfo current(QDir::cleanPath(path));
400
401 if (!actualRoot.isEmpty() && actualRoot != "/")
402 actualRoot.append("/");
403
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))
407 {
408 QString absolutePath = d->extractRoot(path);
409 if (!absolutePath.isEmpty() && absolutePath != "/")
410 absolutePath.append("/");
411 actualRoot.append(absolutePath);
412 }
413
414 if (!options.testFlag(IgnorePaths))
415 {
416 actualRoot = actualRoot.append(QDir(current.absoluteFilePath()).dirName());
417 actualRoot.append("/");
418 }
419
420 // actualRoot now contains the path of the file relative to the zip archive
421 // with a trailing /
422
423 QFileInfoList list = dir.entryInfoList(
424 QDir::Files |
425 QDir::Dirs |
426 QDir::NoDotAndDotDot |
427 QDir::NoSymLinks);
428
429 ErrorCode ec = Zip::Ok;
430 bool filesAdded = false;
431
432 CompressionOptions recursionOptions;
433 if (options.testFlag(IgnorePaths))
434 recursionOptions |= IgnorePaths;
435 else recursionOptions |= RelativePaths;
436
437 for (int i = 0; i < list.size() && ec == Zip::Ok; ++i)
438 {
439 QFileInfo info = list.at(i);
440
441 if (info.isDir())
442 {
443 // Recursion :)
444 ec = addDirectory(info.absoluteFilePath(), actualRoot, recursionOptions, level);
445 }
446 else
447 {
448 ec = d->createEntry(info, actualRoot, level);
449 filesAdded = true;
450 }
451 }
452
453
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);
458
459 return ec;
460}
461
462/*!
463 Closes the archive and writes any pending data.
464*/
466{
468 d->reset();
469 return ec;
470}
471
472/*!
473 Returns a locale translated error string for a given error code.
474*/
476{
477 switch (c)
478 {
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;
488 default: ;
489 }
490
491 return QCoreApplication::translate("Zip", "Unknown error.");
492}
493
494
495/************************************************************************
496 Private interface
497*************************************************************************/
498
499//! \internal
501{
502 headers = 0;
503 device = 0;
504
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();
508}
509
510//! \internal
515
516//! \internal
518{
519 Q_ASSERT(dev != 0);
520
521 if (device != 0)
522 closeArchive();
523
524 device = dev;
525
526 if (!device->isOpen())
527 {
528 if (!device->open(QIODevice::ReadOnly)) {
529 delete device;
530 device = 0;
531 qDebug() << "Unable to open device for writing.";
532 return Zip::OpenFailed;
533 }
534 }
535
536 headers = new QMap<QString,ZipEntryP*>;
537 return Zip::Ok;
538}
539
540//! \internal Writes a new entry in the zip file.
541Zip::ErrorCode ZipPrivate::createEntry(const QFileInfo& file, const QString& root, Zip::CompressionLevel level)
542{
543 //! \todo Automatic level detection (cpu, extension & file size)
544
545 // Directories and very small files are always stored
546 // (small files would get bigger due to the compression headers overhead)
547
548 // Need this for zlib
549 bool isPNGFile = false;
550 bool dirOnly = file.isDir();
551
552 QString entryName = root;
553
554 // Directory entry
555 if (dirOnly)
556 level = Zip::Store;
557 else
558 {
559 entryName.append(file.fileName());
560
561 QString ext = file.completeSuffix().toLower();
562 isPNGFile = ext == "png";
563
564 if (file.size() < ZIP_COMPRESSION_THRESHOLD)
565 level = Zip::Store;
566 else
567 switch (level)
568 {
569 case Zip::AutoCPU:
570 level = Zip::Deflate5;
571 break;
572 case Zip::AutoMIME:
573 level = detectCompressionByMime(ext);
574 break;
575 case Zip::AutoFull:
576 level = detectCompressionByMime(ext);
577 break;
578 default:
579 ;
580 }
581 }
582
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);
586
587 // create header and store it to write a central directory later
588 ZipEntryP* h = new ZipEntryP;
589
590 h->compMethod = (level == Zip::Store) ? 0 : 0x0008;
591
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();
595 if (encrypt)
596 h->gpFlag[0] |= 9;
597
598 QDateTime dt = file.lastModified();
599 QDate d = dt.date();
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();
604
605 QTime t = dt.time();
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;
610
611 h->szUncomp = dirOnly ? 0 : file.size();
612
613 // **** Write local file header ****
614
615 // signature
616 buffer1[0] = 'P'; buffer1[1] = 'K';
617 buffer1[2] = 0x3; buffer1[3] = 0x4;
618
619 // version needed to extract
621 buffer1[ZIP_LH_OFF_VERS + 1] = 0;
622
623 // general purpose flag
625 buffer1[ZIP_LH_OFF_GPFLAG + 1] = h->gpFlag[1];
626
627 // compression method
629 buffer1[ZIP_LH_OFF_CMET + 1] = (h->compMethod>>8) & 0xFF;
630
631 // last mod file time
633 buffer1[ZIP_LH_OFF_MODT + 1] = h->modTime[1];
634
635 // last mod file date
637 buffer1[ZIP_LH_OFF_MODD + 1] = h->modDate[1];
638
639 // skip crc (4bytes) [14,15,16,17]
640
641 // skip compressed size but include evtl. encryption header (4bytes: [18,19,20,21])
645 buffer1[ZIP_LH_OFF_CSIZE + 3] = 0;
646
647 h->szComp = encrypt ? ZIP_LOCAL_ENC_HEADER_SIZE : 0;
648
649 // uncompressed size [22,23,24,25]
651
652 // filename length
653 //QByteArray entryNameBytes = entryName.toAscii();
654 QByteArray entryNameBytes = entryName.toLatin1(); // Qt5
655 int sz = entryNameBytes.size();
656
657 buffer1[ZIP_LH_OFF_NAMELEN] = sz & 0xFF;
658 buffer1[ZIP_LH_OFF_NAMELEN + 1] = (sz >> 8) & 0xFF;
659
660 // extra field length
662
663 // Store offset to write crc and compressed size
664 h->lhOffset = device->pos();
665 quint32 crcOffset = h->lhOffset + ZIP_LH_OFF_CRC;
666
668 {
669 delete h;
670 return Zip::WriteFailed;
671 }
672
673 // Write out filename
674 if (device->write(entryNameBytes) != sz)
675 {
676 delete h;
677 return Zip::WriteFailed;
678 }
679
680 // Encryption keys
681 quint32 keys[3] = { 0, 0, 0 };
682
683 if (encrypt)
684 {
685 // **** encryption header ****
686
687 // XOR with PI to ensure better random numbers
688 // with poorly implemented rand() as suggested by Info-Zip
689 srand(time(NULL) ^ 3141592654UL);
690 int randByte;
691
692 initKeys(keys);
693 for (int i=0; i<10; ++i)
694 {
695 randByte = (rand() >> 7) & 0xff;
696 buffer1[i] = decryptByte(keys[2]) ^ randByte;
697 updateKeys(keys, randByte);
698 }
699
700 // Encrypt encryption header
701 initKeys(keys);
702 for (int i=0; i<10; ++i)
703 {
704 randByte = decryptByte(keys[2]);
705 updateKeys(keys, buffer1[i]);
706 buffer1[i] ^= randByte;
707 }
708
709 // We don't know the CRC at this time, so we use the modification time
710 // as the last two bytes
711 randByte = decryptByte(keys[2]);
712 updateKeys(keys, h->modTime[0]);
713 buffer1[10] ^= randByte;
714
715 randByte = decryptByte(keys[2]);
716 updateKeys(keys, h->modTime[1]);
717 buffer1[11] ^= randByte;
718
719 // Write out encryption header
721 {
722 delete h;
723 return Zip::WriteFailed;
724 }
725 }
726
727 qint64 written = 0;
728 quint32 crc = crc32(0L, Z_NULL, 0);
729
730 if (!dirOnly)
731 {
732 QFile actualFile(file.absoluteFilePath());
733 if (!actualFile.open(QIODevice::ReadOnly))
734 {
735 qDebug() << QString("An error occurred while opening %1").arg(file.absoluteFilePath());
736 return Zip::OpenFailed;
737 }
738
739 // Write file data
740 qint64 read = 0;
741 qint64 totRead = 0;
742 qint64 toRead = actualFile.size();
743
744 if (level == Zip::Store)
745 {
746 while ( (read = actualFile.read(buffer1, ZIP_READ_BUFFER)) > 0 )
747 {
748 crc = crc32(crc, uBuffer, read);
749
750 if (password != 0)
751 encryptBytes(keys, buffer1, read);
752
753 if ( (written = device->write(buffer1, read)) != read )
754 {
755 actualFile.close();
756 delete h;
757 return Zip::WriteFailed;
758 }
759 }
760 }
761 else
762 {
763 z_stream zstr;
764
765 // Initialize zalloc, zfree and opaque before calling the init function
766 zstr.zalloc = Z_NULL;
767 zstr.zfree = Z_NULL;
768 zstr.opaque = Z_NULL;
769
770 int zret;
771
772 // Use deflateInit2 with negative windowBits to get raw compression
773 if ((zret = deflateInit2_(
774 &zstr,
775 (int)level,
776 Z_DEFLATED,
777 -MAX_WBITS,
778 8,
779 isPNGFile ? Z_RLE : Z_DEFAULT_STRATEGY,
780 ZLIB_VERSION,
781 sizeof(z_stream)
782 )) != Z_OK )
783 {
784 actualFile.close();
785 qDebug() << "Could not initialize zlib for compression";
786 delete h;
787 return Zip::ZlibError;
788 }
789
790 qint64 compressed;
791
792 int flush = Z_NO_FLUSH;
793
794 do
795 {
796 read = actualFile.read(buffer1, ZIP_READ_BUFFER);
797 totRead += read;
798
799 if (read == 0)
800 break;
801 if (read < 0)
802 {
803 actualFile.close();
804 deflateEnd(&zstr);
805 qDebug() << QString("Error while reading %1").arg(file.absoluteFilePath());
806 delete h;
807 return Zip::ReadFailed;
808 }
809
810 crc = crc32(crc, uBuffer, read);
811
812 zstr.next_in = (Bytef*) buffer1;
813 zstr.avail_in = (uInt)read;
814
815 // Tell zlib if this is the last chunk we want to encode
816 // by setting the flush parameter to Z_FINISH
817 flush = (totRead == toRead) ? Z_FINISH : Z_NO_FLUSH;
818
819 // Run deflate() on input until output buffer not full
820 // finish compression if all of source has been read in
821 do
822 {
823 zstr.next_out = (Bytef*) buffer2;
824 zstr.avail_out = ZIP_READ_BUFFER;
825
826 zret = deflate(&zstr, flush);
827 // State not clobbered
828 Q_ASSERT(zret != Z_STREAM_ERROR);
829
830 // Write compressed data to file and empty buffer
831 compressed = ZIP_READ_BUFFER - zstr.avail_out;
832
833 if (password != 0)
834 encryptBytes(keys, buffer2, compressed);
835
836 if (device->write(buffer2, compressed) != compressed)
837 {
838 deflateEnd(&zstr);
839 actualFile.close();
840 qDebug() << QString("Error while writing %1").arg(file.absoluteFilePath());
841 delete h;
842 return Zip::WriteFailed;
843 }
844
845 written += compressed;
846
847 } while (zstr.avail_out == 0);
848
849 // All input will be used
850 Q_ASSERT(zstr.avail_in == 0);
851
852 } while (flush != Z_FINISH);
853
854 // Stream will be complete
855 Q_ASSERT(zret == Z_STREAM_END);
856
857 deflateEnd(&zstr);
858
859 } // if (level != STORE)
860
861 actualFile.close();
862 }
863
864 // Store end of entry offset
865 quint32 current = device->pos();
866
867 // Update crc and compressed size in local header
868 if (!device->seek(crcOffset))
869 {
870 delete h;
871 return Zip::SeekFailed;
872 }
873
874 h->crc = dirOnly ? 0 : crc;
875 h->szComp += written;
876
877 setULong(h->crc, buffer1, 0);
878 setULong(h->szComp, buffer1, 4);
879 if ( device->write(buffer1, 8) != 8)
880 {
881 delete h;
882 return Zip::WriteFailed;
883 }
884
885 // Seek to end of entry
886 if (!device->seek(current))
887 {
888 delete h;
889 return Zip::SeekFailed;
890 }
891
892 if ((h->gpFlag[0] & 8) == 8)
893 {
894 // Write data descriptor
895
896 // Signature: PK\7\8
897 buffer1[0] = 'P';
898 buffer1[1] = 'K';
899 buffer1[2] = 0x07;
900 buffer1[3] = 0x08;
901
902 // CRC
904
905 // Compressed size
907
908 // Uncompressed size
910
912 {
913 delete h;
914 return Zip::WriteFailed;
915 }
916 }
917
918 headers->insert(entryName, h);
919 return Zip::Ok;
920}
921
922//! \internal
923int ZipPrivate::decryptByte(quint32 key2) const
924{
925 quint16 temp = ((quint16)(key2) & 0xffff) | 2;
926 return (int)(((temp * (temp ^ 1)) >> 8) & 0xff);
927}
928
929//! \internal Writes an quint32 (4 bytes) to a byte array at given offset.
930void ZipPrivate::setULong(quint32 v, char* buffer, unsigned int offset)
931{
932 buffer[offset+3] = ((v >> 24) & 0xFF);
933 buffer[offset+2] = ((v >> 16) & 0xFF);
934 buffer[offset+1] = ((v >> 8) & 0xFF);
935 buffer[offset] = (v & 0xFF);
936}
937
938//! \internal Initializes decryption keys using a password.
939void ZipPrivate::initKeys(quint32* keys) const
940{
941 // Encryption keys initialization constants are taken from the
942 // PKZip file format specification docs
943 keys[0] = 305419896L;
944 keys[1] = 591751049L;
945 keys[2] = 878082192L;
946
947 //QByteArray pwdBytes = password.toAscii();
948 QByteArray pwdBytes = password.toLatin1();
949 int sz = pwdBytes.size();
950 const char* ascii = pwdBytes.data();
951
952 for (int i=0; i<sz; ++i)
953 updateKeys(keys, (int)ascii[i]);
954}
955
956//! \internal Updates encryption keys.
957void ZipPrivate::updateKeys(quint32* keys, int c) const
958{
959 keys[0] = CRC32(keys[0], c);
960 keys[1] += keys[0] & 0xff;
961 keys[1] = keys[1] * 134775813L + 1;
962 keys[2] = CRC32(keys[2], ((int)keys[1]) >> 24);
963}
964
965//! \internal Encrypts a byte array.
966void ZipPrivate::encryptBytes(quint32* keys, char* buffer, qint64 read)
967{
968 char t;
969
970 for (int i=0; i<(int)read; ++i)
971 {
972 t = buffer[i];
973 buffer[i] ^= decryptByte(keys[2]);
974 updateKeys(keys, t);
975 }
976}
977
978//! \internal Detects the best compression level for a given file extension.
980{
981 // files really hard to compress
982 if ((ext == "png") ||
983 (ext == "jpg") ||
984 (ext == "jpeg") ||
985 (ext == "mp3") ||
986 (ext == "ogg") ||
987 (ext == "ogm") ||
988 (ext == "avi") ||
989 (ext == "mov") ||
990 (ext == "rm") ||
991 (ext == "ra") ||
992 (ext == "zip") ||
993 (ext == "rar") ||
994 (ext == "bz2") ||
995 (ext == "gz") ||
996 (ext == "7z") ||
997 (ext == "z") ||
998 (ext == "jar")
999 ) return Zip::Store;
1000
1001 // files slow and hard to compress
1002 if ((ext == "exe") ||
1003 (ext == "bin") ||
1004 (ext == "rpm") ||
1005 (ext == "deb")
1006 ) return Zip::Deflate2;
1007
1008 return Zip::Deflate9;
1009}
1010
1011/*!
1012 Closes the current archive and writes out pending data.
1013*/
1015{
1016 // Close current archive by writing out central directory
1017 // and free up resources
1018
1019 if (device == 0)
1020 return Zip::Ok;
1021
1022 if (headers == 0)
1023 return Zip::Ok;
1024
1025 const ZipEntryP* h;
1026
1027 unsigned int sz;
1028 quint32 szCentralDir = 0;
1029 quint32 offCentralDir = device->pos();
1030
1031 for (QMap<QString,ZipEntryP*>::ConstIterator itr = headers->constBegin(); itr != headers->constEnd(); ++itr)
1032 {
1033 h = itr.value();
1034
1035 // signature
1036 buffer1[0] = 'P';
1037 buffer1[1] = 'K';
1038 buffer1[2] = 0x01;
1039 buffer1[3] = 0x02;
1040
1041 // version made by (currently only MS-DOS/FAT - no symlinks or other stuff supported)
1043
1044 // version needed to extract
1046 buffer1[ZIP_CD_OFF_VERSION + 1] = 0;
1047
1048 // general purpose flag
1050 buffer1[ZIP_CD_OFF_GPFLAG + 1] = h->gpFlag[1];
1051
1052 // compression method
1053 buffer1[ZIP_CD_OFF_CMET] = h->compMethod & 0xFF;
1054 buffer1[ZIP_CD_OFF_CMET + 1] = (h->compMethod >> 8) & 0xFF;
1055
1056 // last mod file time
1058 buffer1[ZIP_CD_OFF_MODT + 1] = h->modTime[1];
1059
1060 // last mod file date
1062 buffer1[ZIP_CD_OFF_MODD + 1] = h->modDate[1];
1063
1064 // crc (4bytes) [16,17,18,19]
1066
1067 // compressed size (4bytes: [20,21,22,23])
1069
1070 // uncompressed size [24,25,26,27]
1072
1073 // filename
1074 //QByteArray fileNameBytes = itr.key().toAscii();
1075 QByteArray fileNameBytes = itr.key().toLatin1();
1076 sz = fileNameBytes.size();
1077 buffer1[ZIP_CD_OFF_NAMELEN] = sz & 0xFF;
1078 buffer1[ZIP_CD_OFF_NAMELEN + 1] = (sz >> 8) & 0xFF;
1079
1080 // extra field length
1082
1083 // file comment length
1085
1086 // disk number start
1088
1089 // internal file attributes
1091
1092 // external file attributes
1096 buffer1[ZIP_CD_OFF_EATTR + 3] = 0;
1097
1098 // relative offset of local header [42->45]
1100
1101 if (device->write(buffer1, ZIP_CD_SIZE) != ZIP_CD_SIZE)
1102 {
1103 //! \todo See if we can detect QFile objects using the Qt Meta Object System
1104 /*
1105 if (!device->remove())
1106 qDebug() << tr("Unable to delete corrupted archive: %1").arg(device->fileName());
1107 */
1108 return Zip::WriteFailed;
1109 }
1110
1111 // Write out filename
1112 if ((unsigned int)device->write(fileNameBytes) != sz)
1113 {
1114 //! \todo SAME AS ABOVE: See if we can detect QFile objects using the Qt Meta Object System
1115 /*
1116 if (!device->remove())
1117 qDebug() << tr("Unable to delete corrupted archive: %1").arg(device->fileName());
1118 */
1119 return Zip::WriteFailed;
1120 }
1121
1122 szCentralDir += (ZIP_CD_SIZE + sz);
1123
1124 } // central dir headers loop
1125
1126
1127 // Write end of central directory
1128
1129 // signature
1130 buffer1[0] = 'P';
1131 buffer1[1] = 'K';
1132 buffer1[2] = 0x05;
1133 buffer1[3] = 0x06;
1134
1135 // number of this disk
1137
1138 // number of disk with central directory
1140
1141 // number of entries in this disk
1142 sz = headers->count();
1143 buffer1[ZIP_EOCD_OFF_ENTRIES] = sz & 0xFF;
1144 buffer1[ZIP_EOCD_OFF_ENTRIES + 1] = (sz >> 8) & 0xFF;
1145
1146 // total number of entries
1149
1150 // size of central directory [12->15]
1151 setULong(szCentralDir, buffer1, ZIP_EOCD_OFF_CDSIZE);
1152
1153 // central dir offset [16->19]
1154 setULong(offCentralDir, buffer1, ZIP_EOCD_OFF_CDOFF);
1155
1156 // ZIP file comment length
1157 //QByteArray commentBytes = comment.toAscii();
1158 QByteArray commentBytes = comment.toLatin1();
1159 quint16 commentLength = commentBytes.size();
1160
1161 if (commentLength == 0)
1162 {
1164 }
1165 else
1166 {
1167 buffer1[ZIP_EOCD_OFF_COMMLEN] = commentLength & 0xFF;
1168 buffer1[ZIP_EOCD_OFF_COMMLEN + 1] = (commentLength >> 8) & 0xFF;
1169 }
1170
1171 if (device->write(buffer1, ZIP_EOCD_SIZE) != ZIP_EOCD_SIZE)
1172 {
1173 //! \todo SAME AS ABOVE: See if we can detect QFile objects using the Qt Meta Object System
1174 /*
1175 if (!device->remove())
1176 qDebug() << tr("Unable to delete corrupted archive: %1").arg(device->fileName());
1177 */
1178 return Zip::WriteFailed;
1179 }
1180
1181 if (commentLength != 0)
1182 {
1183 if ((unsigned int)device->write(commentBytes) != commentLength)
1184 {
1185 //! \todo SAME AS ABOVE: See if we can detect QFile objects using the Qt Meta Object System
1186 /*
1187 if (!device->remove())
1188 qDebug() << tr("Unable to delete corrupted archive: %1").arg(device->fileName());
1189 */
1190 return Zip::WriteFailed;
1191 }
1192 }
1193
1194 return Zip::Ok;
1195}
1196
1197//! \internal
1199{
1200 comment.clear();
1201
1202 if (headers != 0)
1203 {
1204 qDeleteAll(*headers);
1205 delete headers;
1206 headers = 0;
1207 }
1208
1209 delete device; device = 0;
1210}
1211
1212//! \internal Returns the path of the parent directory
1213QString ZipPrivate::extractRoot(const QString& p)
1214{
1215 QDir d(QDir::cleanPath(p));
1216 if (!d.exists())
1217 return QString();
1218
1219 if (!d.cdUp())
1220 return QString();
1221
1222 return d.absolutePath();
1223}
unsigned char gpFlag[2]
Definition zipentry_p.h:63
quint32 crc
Definition zipentry_p.h:67
quint32 szComp
Definition zipentry_p.h:68
quint32 szUncomp
Definition zipentry_p.h:69
unsigned char modTime[2]
Definition zipentry_p.h:65
quint16 compMethod
Definition zipentry_p.h:64
unsigned char modDate[2]
Definition zipentry_p.h:66
quint32 lhOffset
Definition zipentry_p.h:61
char buffer2[ZIP_READ_BUFFER]
Definition zip_p.h:65
QIODevice * device
Definition zip_p.h:62
QString comment
Definition zip_p.h:71
Zip::ErrorCode createArchive(QIODevice *device)
Definition zip.cpp:517
ZipPrivate()
Definition zip.cpp:500
unsigned char * uBuffer
Definition zip_p.h:67
const quint32 * crcTable
Definition zip_p.h:69
void initKeys(quint32 *keys) const
Definition zip.cpp:939
int decryptByte(quint32 key2) const
Definition zip.cpp:923
void updateKeys(quint32 *keys, int c) const
Definition zip.cpp:957
virtual ~ZipPrivate()
Definition zip.cpp:511
Zip::ErrorCode createEntry(const QFileInfo &file, const QString &root, Zip::CompressionLevel level)
Definition zip.cpp:541
Zip::CompressionLevel detectCompressionByMime(const QString &ext)
Definition zip.cpp:979
void reset()
Definition zip.cpp:1198
void encryptBytes(quint32 *keys, char *buffer, qint64 read)
Definition zip.cpp:966
QString extractRoot(const QString &p)
Definition zip.cpp:1213
QString password
Definition zip_p.h:72
char buffer1[ZIP_READ_BUFFER]
Definition zip_p.h:64
void setULong(quint32 v, char *buffer, unsigned int offset)
Definition zip.cpp:930
Zip::ErrorCode closeArchive()
Definition zip.cpp:1014
QMap< QString, ZipEntryP * > * headers
Definition zip_p.h:60
bool isOpen() const
Definition zip.cpp:235
void setPassword(const QString &pwd)
Definition zip.cpp:246
ErrorCode createArchive(const QString &file, bool overwrite=true)
Definition zip.cpp:268
@ IgnorePaths
Do not store paths. All the files are put in the (evtl. user defined) root of the zip file.
Definition zip.h:77
@ AbsolutePaths
Preserve absolute paths.
Definition zip.h:75
@ RelativePaths
Does not preserve absolute paths in the zip file when adding a file/directory (default)
Definition zip.h:73
virtual ~Zip()
Definition zip.cpp:226
ErrorCode
Definition zip.h:49
@ OpenFailed
Definition zip.h:54
@ ZlibError
Definition zip.h:52
@ Ok
Definition zip.h:50
@ NoOpenArchive
Definition zip.h:55
@ ReadFailed
Definition zip.h:57
@ SeekFailed
Definition zip.h:59
@ FileNotFound
Definition zip.h:56
@ FileExists
Definition zip.h:53
@ WriteFailed
Definition zip.h:58
@ ZlibInit
Definition zip.h:51
void clearPassword()
Convenience method, clears the current password.
Definition zip.cpp:252
ErrorCode addDirectory(const QString &path, CompressionOptions options=RelativePaths, CompressionLevel level=AutoFull)
Definition zip.cpp:333
Zip()
Definition zip.cpp:218
QString password() const
Returns the currently used password.
Definition zip.cpp:258
QString formatError(ErrorCode c) const
Definition zip.cpp:475
CompressionLevel
Definition zip.h:63
@ Deflate5
Definition zip.h:66
@ Deflate9
Definition zip.h:66
@ AutoMIME
Definition zip.h:67
@ Deflate2
Definition zip.h:65
@ Store
Definition zip.h:64
@ AutoCPU
Definition zip.h:67
@ AutoFull
Definition zip.h:67
ZipPrivate * d
Definition zip.h:108
void setArchiveComment(const QString &comment)
Definition zip.cpp:317
ErrorCode addDirectoryContents(const QString &path, CompressionLevel level=AutoFull)
Definition zip.cpp:351
ErrorCode closeArchive()
Definition zip.cpp:465
QString archiveComment() const
Definition zip.cpp:308
#define CRC32(c, b)
CRC32 routine.
Definition unzip.cpp:136
#define ZIP_CD_OFF_LHOFF
Definition zip.cpp:90
#define ZIP_VERSION
PKZip version for archives created by this API.
Definition zip.cpp:102
#define ZIP_EOCD_OFF_CDSIZE
Definition zip.cpp:97
#define ZIP_CD_SIZE
Central Directory record size (signature included)
Definition zip.cpp:53
#define ZIP_LH_OFF_VERS
Definition zip.cpp:58
#define ZIP_CD_OFF_CSIZE
Definition zip.cpp:82
#define ZIP_CD_OFF_XLEN
Definition zip.cpp:85
#define ZIP_LH_OFF_MODD
Definition zip.cpp:62
#define ZIP_CD_OFF_GPFLAG
Definition zip.cpp:77
#define ZIP_CD_OFF_MADEBY
Definition zip.cpp:75
#define ZIP_CD_OFF_COMMLEN
Definition zip.cpp:86
#define ZIP_CD_OFF_MODD
Definition zip.cpp:80
#define ZIP_EOCD_SIZE
End of Central Directory record size (signature included)
Definition zip.cpp:55
#define ZIP_CD_OFF_CRC
Definition zip.cpp:81
#define ZIP_DD_SIZE_WS
Data descriptor size (signature included)
Definition zip.cpp:51
#define ZIP_LH_OFF_NAMELEN
Definition zip.cpp:66
#define ZIP_LH_OFF_XLEN
Definition zip.cpp:67
#define ZIP_COMPRESSION_THRESHOLD
Do not store very small files as the compression headers overhead would be to big.
Definition zip.cpp:105
#define ZIP_DD_OFF_CRC32
Definition zip.cpp:70
#define ZIP_LH_OFF_CSIZE
Definition zip.cpp:64
#define ZIP_LOCAL_ENC_HEADER_SIZE
Encryption header size.
Definition zip.cpp:49
#define ZIP_CD_OFF_USIZE
Definition zip.cpp:83
#define ZIP_CD_OFF_DISKSTART
Definition zip.cpp:87
#define ZIP_LH_OFF_USIZE
Definition zip.cpp:65
#define ZIP_CD_OFF_CMET
Definition zip.cpp:78
#define ZIP_CD_OFF_NAMELEN
Definition zip.cpp:84
#define ZIP_CD_OFF_IATTR
Definition zip.cpp:88
#define ZIP_CD_OFF_VERSION
Definition zip.cpp:76
#define ZIP_LH_OFF_GPFLAG
Definition zip.cpp:59
#define ZIP_CD_OFF_EATTR
Definition zip.cpp:89
#define ZIP_LH_OFF_CRC
Definition zip.cpp:63
#define ZIP_DD_OFF_USIZE
Definition zip.cpp:72
#define ZIP_EOCD_OFF_ENTRIES
Definition zip.cpp:95
#define ZIP_EOCD_OFF_DISKNUM
Definition zip.cpp:93
#define ZIP_CD_OFF_MODT
Definition zip.cpp:79
#define ZIP_EOCD_OFF_CDENTRIES
Definition zip.cpp:96
#define ZIP_LH_OFF_CMET
Definition zip.cpp:60
#define ZIP_DD_OFF_CSIZE
Definition zip.cpp:71
#define ZIP_EOCD_OFF_COMMLEN
Definition zip.cpp:99
#define ZIP_LH_OFF_MODT
Definition zip.cpp:61
#define ZIP_LOCAL_HEADER_SIZE
Local header size (including signature, excluding variable length fields)
Definition zip.cpp:47
#define ZIP_EOCD_OFF_CDDISKNUM
Definition zip.cpp:94
#define ZIP_EOCD_OFF_CDOFF
Definition zip.cpp:98
#define ZIP_READ_BUFFER
Definition zip_p.h:52