Merge branch 'master' into app-list-itemsize
[kugel-rb.git] / rbutil / rbutilqt / zip / zip.cpp
blob9ecd5531ef66bd12ea42cc624cc8d7cd44e7f58e
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.
11 ** Copyright (C) 2007-2008 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 **********************************************************************/
28 #include "zip.h"
29 #include "zip_p.h"
30 #include "zipentry_p.h"
32 // we only use this to seed the random number generator
33 #include <time.h>
35 #include <QMap>
36 #include <QString>
37 #include <QStringList>
38 #include <QDir>
39 #include <QFile>
40 #include <QDateTime>
41 #include <QCoreApplication>
43 // You can remove this #include if you replace the qDebug() statements.
44 #include <QtDebug>
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)
111 \class Zip zip.h
113 \brief Zip file compression.
115 Some quick usage examples.
117 \verbatim
118 Suppose you have this directory structure:
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
126 EXAMPLE 1:
127 myZipInstance.addDirectory("/root/dir1");
129 RESULT:
130 Beheaves like any common zip software and creates a zip file with this structure:
132 dir1/
133 dir1/file1.1
134 dir1/file1.2
135 dir1/dir1.1/
136 dir1/dir1.2/file1.2.1
138 EXAMPLE 2:
139 myZipInstance.addDirectory("/root/dir1", "myRoot/myFolder");
141 RESULT:
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
150 EXAMPLE 3:
151 myZipInstance.addDirectory("/root/dir1", Zip::AbsolutePaths);
153 NOTE:
154 Same as calling addDirectory(SOME_PATH, PARENT_PATH_of_SOME_PATH).
156 RESULT:
157 Preserves absolute paths and creates a zip file with this structure:
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
165 EXAMPLE 4:
166 myZipInstance.setPassword("hellopass");
167 myZipInstance.addDirectory("/root/dir1", "/");
169 RESULT:
170 Adds and encrypts the files in /root/dir1, creating the following zip structure:
172 /dir1/
173 /dir1/file1.1
174 /dir1/file1.2
175 /dir1/dir1.1/
176 /dir1/dir1.2/file1.2.1
178 \endverbatim
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 /************************************************************************
212 Public interface
213 *************************************************************************/
216 Creates a new Zip file compressor.
218 Zip::Zip()
220 d = new ZipPrivate;
224 Closes any open archive and releases used resources.
226 Zip::~Zip()
228 closeArchive();
229 delete d;
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)
248 d->password = pwd;
251 //! Convenience method, clears the current password.
252 void Zip::clearPassword()
254 d->password.clear();
257 //! Returns the currently used password.
258 QString Zip::password() const
260 return d->password;
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) {
273 delete file;
274 return Zip::FileExists;
277 if (!file->open(QIODevice::WriteOnly)) {
278 delete file;
279 return Zip::OpenFailed;
282 Zip::ErrorCode ec = createArchive(file);
283 if (ec != Zip::Ok) {
284 file->remove();
287 return ec;
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)
296 if (device == 0)
298 qDebug() << "Invalid device.";
299 return Zip::OpenFailed;
302 return d->createArchive(device);
306 Returns the current archive comment.
308 QString Zip::archiveComment() const
310 return d->comment;
314 Sets the comment for this archive. Note: createArchive() should have been
315 called before.
317 void Zip::setArchiveComment(const QString& comment)
319 if (d->device != 0)
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 :)
381 if (d->device == 0)
382 return Zip::NoOpenArchive;
384 QDir dir(path);
385 if (!dir.exists())
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
421 // with a trailing /
423 QFileInfoList list = dir.entryInfoList(
424 QDir::Files |
425 QDir::Dirs |
426 QDir::NoDotAndDotDot |
427 QDir::NoSymLinks);
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);
441 if (info.isDir())
443 // Recursion :)
444 progress();
445 ec = addDirectory(info.absoluteFilePath(), actualRoot, recursionOptions, level);
447 else
449 progress();
450 ec = d->createEntry(info, actualRoot, level);
451 filesAdded = true;
456 // We need an explicit record for this dir
457 // Non-empty directories don't need it because they have a path component in the filename
458 if (!filesAdded && !options.testFlag(IgnorePaths))
459 ec = d->createEntry(current, actualRoot, level);
461 return ec;
465 Closes the archive and writes any pending data.
467 Zip::ErrorCode Zip::closeArchive()
469 Zip::ErrorCode ec = d->closeArchive();
470 d->reset();
471 return ec;
475 Returns a locale translated error string for a given error code.
477 QString Zip::formatError(Zip::ErrorCode c) const
479 switch (c)
481 case Ok: return QCoreApplication::translate("Zip", "ZIP operation completed successfully."); break;
482 case ZlibInit: return QCoreApplication::translate("Zip", "Failed to initialize or load zlib library."); break;
483 case ZlibError: return QCoreApplication::translate("Zip", "zlib library error."); break;
484 case OpenFailed: return QCoreApplication::translate("Zip", "Unable to create or open file."); break;
485 case NoOpenArchive: return QCoreApplication::translate("Zip", "No archive has been created yet."); break;
486 case FileNotFound: return QCoreApplication::translate("Zip", "File or directory does not exist."); break;
487 case ReadFailed: return QCoreApplication::translate("Zip", "File read error."); break;
488 case WriteFailed: return QCoreApplication::translate("Zip", "File write error."); break;
489 case SeekFailed: return QCoreApplication::translate("Zip", "File seek error."); break;
490 default: ;
493 return QCoreApplication::translate("Zip", "Unknown error.");
497 /************************************************************************
498 Private interface
499 *************************************************************************/
501 //! \internal
502 ZipPrivate::ZipPrivate()
504 headers = 0;
505 device = 0;
507 // keep an unsigned pointer so we avoid to over bloat the code with casts
508 uBuffer = (unsigned char*) buffer1;
509 crcTable = (quint32*) get_crc_table();
512 //! \internal
513 ZipPrivate::~ZipPrivate()
515 closeArchive();
518 //! \internal
519 Zip::ErrorCode ZipPrivate::createArchive(QIODevice* dev)
521 Q_ASSERT(dev != 0);
523 if (device != 0)
524 closeArchive();
526 device = dev;
528 if (!device->isOpen())
530 if (!device->open(QIODevice::ReadOnly)) {
531 delete device;
532 device = 0;
533 qDebug() << "Unable to open device for writing.";
534 return Zip::OpenFailed;
538 headers = new QMap<QString,ZipEntryP*>;
539 return Zip::Ok;
542 //! \internal Writes a new entry in the zip file.
543 Zip::ErrorCode ZipPrivate::createEntry(const QFileInfo& file, const QString& root, Zip::CompressionLevel level)
545 //! \todo Automatic level detection (cpu, extension & file size)
547 // Directories and very small files are always stored
548 // (small files would get bigger due to the compression headers overhead)
550 // Need this for zlib
551 bool isPNGFile = false;
552 bool dirOnly = file.isDir();
554 QString entryName = root;
556 // Directory entry
557 if (dirOnly)
558 level = Zip::Store;
559 else
561 entryName.append(file.fileName());
563 QString ext = file.completeSuffix().toLower();
564 isPNGFile = ext == "png";
566 if (file.size() < ZIP_COMPRESSION_THRESHOLD)
567 level = Zip::Store;
568 else
569 switch (level)
571 case Zip::AutoCPU:
572 level = Zip::Deflate5;
573 break;
574 case Zip::AutoMIME:
575 level = detectCompressionByMime(ext);
576 break;
577 case Zip::AutoFull:
578 level = detectCompressionByMime(ext);
579 break;
580 default:
585 // entryName contains the path as it should be written
586 // in the zip file records
587 // qDebug() << QString("addDir(file=%1, root=%2, entry=%3)").arg(file.absoluteFilePath(), root, entryName);
589 // create header and store it to write a central directory later
590 ZipEntryP* h = new ZipEntryP;
592 h->compMethod = (level == Zip::Store) ? 0 : 0x0008;
594 // Set encryption bit and set the data descriptor bit
595 // so we can use mod time instead of crc for password check
596 bool encrypt = !dirOnly && !password.isEmpty();
597 if (encrypt)
598 h->gpFlag[0] |= 9;
600 QDateTime dt = file.lastModified();
601 QDate d = dt.date();
602 h->modDate[1] = ((d.year() - 1980) << 1) & 254;
603 h->modDate[1] |= ((d.month() >> 3) & 1);
604 h->modDate[0] = ((d.month() & 7) << 5) & 224;
605 h->modDate[0] |= d.day();
607 QTime t = dt.time();
608 h->modTime[1] = (t.hour() << 3) & 248;
609 h->modTime[1] |= ((t.minute() >> 3) & 7);
610 h->modTime[0] = ((t.minute() & 7) << 5) & 224;
611 h->modTime[0] |= t.second() / 2;
613 h->szUncomp = dirOnly ? 0 : file.size();
615 // **** Write local file header ****
617 // signature
618 buffer1[0] = 'P'; buffer1[1] = 'K';
619 buffer1[2] = 0x3; buffer1[3] = 0x4;
621 // version needed to extract
622 buffer1[ZIP_LH_OFF_VERS] = ZIP_VERSION;
623 buffer1[ZIP_LH_OFF_VERS + 1] = 0;
625 // general purpose flag
626 buffer1[ZIP_LH_OFF_GPFLAG] = h->gpFlag[0];
627 buffer1[ZIP_LH_OFF_GPFLAG + 1] = h->gpFlag[1];
629 // compression method
630 buffer1[ZIP_LH_OFF_CMET] = h->compMethod & 0xFF;
631 buffer1[ZIP_LH_OFF_CMET + 1] = (h->compMethod>>8) & 0xFF;
633 // last mod file time
634 buffer1[ZIP_LH_OFF_MODT] = h->modTime[0];
635 buffer1[ZIP_LH_OFF_MODT + 1] = h->modTime[1];
637 // last mod file date
638 buffer1[ZIP_LH_OFF_MODD] = h->modDate[0];
639 buffer1[ZIP_LH_OFF_MODD + 1] = h->modDate[1];
641 // skip crc (4bytes) [14,15,16,17]
643 // skip compressed size but include evtl. encryption header (4bytes: [18,19,20,21])
644 buffer1[ZIP_LH_OFF_CSIZE] =
645 buffer1[ZIP_LH_OFF_CSIZE + 1] =
646 buffer1[ZIP_LH_OFF_CSIZE + 2] =
647 buffer1[ZIP_LH_OFF_CSIZE + 3] = 0;
649 h->szComp = encrypt ? ZIP_LOCAL_ENC_HEADER_SIZE : 0;
651 // uncompressed size [22,23,24,25]
652 setULong(h->szUncomp, buffer1, ZIP_LH_OFF_USIZE);
654 // filename length
655 QByteArray entryNameBytes = entryName.toAscii();
656 int sz = entryNameBytes.size();
658 buffer1[ZIP_LH_OFF_NAMELEN] = sz & 0xFF;
659 buffer1[ZIP_LH_OFF_NAMELEN + 1] = (sz >> 8) & 0xFF;
661 // extra field length
662 buffer1[ZIP_LH_OFF_XLEN] = buffer1[ZIP_LH_OFF_XLEN + 1] = 0;
664 // Store offset to write crc and compressed size
665 h->lhOffset = device->pos();
666 quint32 crcOffset = h->lhOffset + ZIP_LH_OFF_CRC;
668 if (device->write(buffer1, ZIP_LOCAL_HEADER_SIZE) != ZIP_LOCAL_HEADER_SIZE)
670 delete h;
671 return Zip::WriteFailed;
674 // Write out filename
675 if (device->write(entryNameBytes) != sz)
677 delete h;
678 return Zip::WriteFailed;
681 // Encryption keys
682 quint32 keys[3] = { 0, 0, 0 };
684 if (encrypt)
686 // **** encryption header ****
688 // XOR with PI to ensure better random numbers
689 // with poorly implemented rand() as suggested by Info-Zip
690 srand(time(NULL) ^ 3141592654UL);
691 int randByte;
693 initKeys(keys);
694 for (int i=0; i<10; ++i)
696 randByte = (rand() >> 7) & 0xff;
697 buffer1[i] = decryptByte(keys[2]) ^ randByte;
698 updateKeys(keys, randByte);
701 // Encrypt encryption header
702 initKeys(keys);
703 for (int i=0; i<10; ++i)
705 randByte = decryptByte(keys[2]);
706 updateKeys(keys, buffer1[i]);
707 buffer1[i] ^= randByte;
710 // We don't know the CRC at this time, so we use the modification time
711 // as the last two bytes
712 randByte = decryptByte(keys[2]);
713 updateKeys(keys, h->modTime[0]);
714 buffer1[10] ^= randByte;
716 randByte = decryptByte(keys[2]);
717 updateKeys(keys, h->modTime[1]);
718 buffer1[11] ^= randByte;
720 // Write out encryption header
721 if (device->write(buffer1, ZIP_LOCAL_ENC_HEADER_SIZE) != ZIP_LOCAL_ENC_HEADER_SIZE)
723 delete h;
724 return Zip::WriteFailed;
728 qint64 written = 0;
729 quint32 crc = crc32(0L, Z_NULL, 0);
731 if (!dirOnly)
733 QFile actualFile(file.absoluteFilePath());
734 if (!actualFile.open(QIODevice::ReadOnly))
736 qDebug() << QString("An error occurred while opening %1").arg(file.absoluteFilePath());
737 return Zip::OpenFailed;
740 // Write file data
741 qint64 read = 0;
742 qint64 totRead = 0;
743 qint64 toRead = actualFile.size();
745 if (level == Zip::Store)
747 while ( (read = actualFile.read(buffer1, ZIP_READ_BUFFER)) > 0 )
749 crc = crc32(crc, uBuffer, read);
751 if (password != 0)
752 encryptBytes(keys, buffer1, read);
754 if ( (written = device->write(buffer1, read)) != read )
756 actualFile.close();
757 delete h;
758 return Zip::WriteFailed;
762 else
764 z_stream zstr;
766 // Initialize zalloc, zfree and opaque before calling the init function
767 zstr.zalloc = Z_NULL;
768 zstr.zfree = Z_NULL;
769 zstr.opaque = Z_NULL;
771 int zret;
773 // Use deflateInit2 with negative windowBits to get raw compression
774 if ((zret = deflateInit2_(
775 &zstr,
776 (int)level,
777 Z_DEFLATED,
778 -MAX_WBITS,
780 isPNGFile ? Z_RLE : Z_DEFAULT_STRATEGY,
781 ZLIB_VERSION,
782 sizeof(z_stream)
783 )) != Z_OK )
785 actualFile.close();
786 qDebug() << "Could not initialize zlib for compression";
787 delete h;
788 return Zip::ZlibError;
791 qint64 compressed;
793 int flush = Z_NO_FLUSH;
797 read = actualFile.read(buffer1, ZIP_READ_BUFFER);
798 totRead += read;
800 if (read == 0)
801 break;
802 if (read < 0)
804 actualFile.close();
805 deflateEnd(&zstr);
806 qDebug() << QString("Error while reading %1").arg(file.absoluteFilePath());
807 delete h;
808 return Zip::ReadFailed;
811 crc = crc32(crc, uBuffer, read);
813 zstr.next_in = (Bytef*) buffer1;
814 zstr.avail_in = (uInt)read;
816 // Tell zlib if this is the last chunk we want to encode
817 // by setting the flush parameter to Z_FINISH
818 flush = (totRead == toRead) ? Z_FINISH : Z_NO_FLUSH;
820 // Run deflate() on input until output buffer not full
821 // finish compression if all of source has been read in
824 zstr.next_out = (Bytef*) buffer2;
825 zstr.avail_out = ZIP_READ_BUFFER;
827 zret = deflate(&zstr, flush);
828 // State not clobbered
829 Q_ASSERT(zret != Z_STREAM_ERROR);
831 // Write compressed data to file and empty buffer
832 compressed = ZIP_READ_BUFFER - zstr.avail_out;
834 if (password != 0)
835 encryptBytes(keys, buffer2, compressed);
837 if (device->write(buffer2, compressed) != compressed)
839 deflateEnd(&zstr);
840 actualFile.close();
841 qDebug() << QString("Error while writing %1").arg(file.absoluteFilePath());
842 delete h;
843 return Zip::WriteFailed;
846 written += compressed;
848 } while (zstr.avail_out == 0);
850 // All input will be used
851 Q_ASSERT(zstr.avail_in == 0);
853 } while (flush != Z_FINISH);
855 // Stream will be complete
856 Q_ASSERT(zret == Z_STREAM_END);
858 deflateEnd(&zstr);
860 } // if (level != STORE)
862 actualFile.close();
865 // Store end of entry offset
866 quint32 current = device->pos();
868 // Update crc and compressed size in local header
869 if (!device->seek(crcOffset))
871 delete h;
872 return Zip::SeekFailed;
875 h->crc = dirOnly ? 0 : crc;
876 h->szComp += written;
878 setULong(h->crc, buffer1, 0);
879 setULong(h->szComp, buffer1, 4);
880 if ( device->write(buffer1, 8) != 8)
882 delete h;
883 return Zip::WriteFailed;
886 // Seek to end of entry
887 if (!device->seek(current))
889 delete h;
890 return Zip::SeekFailed;
893 if ((h->gpFlag[0] & 8) == 8)
895 // Write data descriptor
897 // Signature: PK\7\8
898 buffer1[0] = 'P';
899 buffer1[1] = 'K';
900 buffer1[2] = 0x07;
901 buffer1[3] = 0x08;
903 // CRC
904 setULong(h->crc, buffer1, ZIP_DD_OFF_CRC32);
906 // Compressed size
907 setULong(h->szComp, buffer1, ZIP_DD_OFF_CSIZE);
909 // Uncompressed size
910 setULong(h->szUncomp, buffer1, ZIP_DD_OFF_USIZE);
912 if (device->write(buffer1, ZIP_DD_SIZE_WS) != ZIP_DD_SIZE_WS)
914 delete h;
915 return Zip::WriteFailed;
919 headers->insert(entryName, h);
920 return Zip::Ok;
923 //! \internal
924 int ZipPrivate::decryptByte(quint32 key2) const
926 quint16 temp = ((quint16)(key2) & 0xffff) | 2;
927 return (int)(((temp * (temp ^ 1)) >> 8) & 0xff);
930 //! \internal Writes an quint32 (4 bytes) to a byte array at given offset.
931 void ZipPrivate::setULong(quint32 v, char* buffer, unsigned int offset)
933 buffer[offset+3] = ((v >> 24) & 0xFF);
934 buffer[offset+2] = ((v >> 16) & 0xFF);
935 buffer[offset+1] = ((v >> 8) & 0xFF);
936 buffer[offset] = (v & 0xFF);
939 //! \internal Initializes decryption keys using a password.
940 void ZipPrivate::initKeys(quint32* keys) const
942 // Encryption keys initialization constants are taken from the
943 // PKZip file format specification docs
944 keys[0] = 305419896L;
945 keys[1] = 591751049L;
946 keys[2] = 878082192L;
948 QByteArray pwdBytes = password.toAscii();
949 int sz = pwdBytes.size();
950 const char* ascii = pwdBytes.data();
952 for (int i=0; i<sz; ++i)
953 updateKeys(keys, (int)ascii[i]);
956 //! \internal Updates encryption keys.
957 void ZipPrivate::updateKeys(quint32* keys, int c) const
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);
965 //! \internal Encrypts a byte array.
966 void ZipPrivate::encryptBytes(quint32* keys, char* buffer, qint64 read)
968 char t;
970 for (int i=0; i<(int)read; ++i)
972 t = buffer[i];
973 buffer[i] ^= decryptByte(keys[2]);
974 updateKeys(keys, t);
978 //! \internal Detects the best compression level for a given file extension.
979 Zip::CompressionLevel ZipPrivate::detectCompressionByMime(const QString& ext)
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;
1001 // files slow and hard to compress
1002 if ((ext == "exe") ||
1003 (ext == "bin") ||
1004 (ext == "rpm") ||
1005 (ext == "deb")
1006 ) return Zip::Deflate2;
1008 return Zip::Deflate9;
1012 Closes the current archive and writes out pending data.
1014 Zip::ErrorCode ZipPrivate::closeArchive()
1016 // Close current archive by writing out central directory
1017 // and free up resources
1019 if (device == 0)
1020 return Zip::Ok;
1022 if (headers == 0)
1023 return Zip::Ok;
1025 const ZipEntryP* h;
1027 unsigned int sz;
1028 quint32 szCentralDir = 0;
1029 quint32 offCentralDir = device->pos();
1031 for (QMap<QString,ZipEntryP*>::ConstIterator itr = headers->constBegin(); itr != headers->constEnd(); ++itr)
1033 h = itr.value();
1035 // signature
1036 buffer1[0] = 'P';
1037 buffer1[1] = 'K';
1038 buffer1[2] = 0x01;
1039 buffer1[3] = 0x02;
1041 // version made by (currently only MS-DOS/FAT - no symlinks or other stuff supported)
1042 buffer1[ZIP_CD_OFF_MADEBY] = buffer1[ZIP_CD_OFF_MADEBY + 1] = 0;
1044 // version needed to extract
1045 buffer1[ZIP_CD_OFF_VERSION] = ZIP_VERSION;
1046 buffer1[ZIP_CD_OFF_VERSION + 1] = 0;
1048 // general purpose flag
1049 buffer1[ZIP_CD_OFF_GPFLAG] = h->gpFlag[0];
1050 buffer1[ZIP_CD_OFF_GPFLAG + 1] = h->gpFlag[1];
1052 // compression method
1053 buffer1[ZIP_CD_OFF_CMET] = h->compMethod & 0xFF;
1054 buffer1[ZIP_CD_OFF_CMET + 1] = (h->compMethod >> 8) & 0xFF;
1056 // last mod file time
1057 buffer1[ZIP_CD_OFF_MODT] = h->modTime[0];
1058 buffer1[ZIP_CD_OFF_MODT + 1] = h->modTime[1];
1060 // last mod file date
1061 buffer1[ZIP_CD_OFF_MODD] = h->modDate[0];
1062 buffer1[ZIP_CD_OFF_MODD + 1] = h->modDate[1];
1064 // crc (4bytes) [16,17,18,19]
1065 setULong(h->crc, buffer1, ZIP_CD_OFF_CRC);
1067 // compressed size (4bytes: [20,21,22,23])
1068 setULong(h->szComp, buffer1, ZIP_CD_OFF_CSIZE);
1070 // uncompressed size [24,25,26,27]
1071 setULong(h->szUncomp, buffer1, ZIP_CD_OFF_USIZE);
1073 // filename
1074 QByteArray fileNameBytes = itr.key().toAscii();
1075 sz = fileNameBytes.size();
1076 buffer1[ZIP_CD_OFF_NAMELEN] = sz & 0xFF;
1077 buffer1[ZIP_CD_OFF_NAMELEN + 1] = (sz >> 8) & 0xFF;
1079 // extra field length
1080 buffer1[ZIP_CD_OFF_XLEN] = buffer1[ZIP_CD_OFF_XLEN + 1] = 0;
1082 // file comment length
1083 buffer1[ZIP_CD_OFF_COMMLEN] = buffer1[ZIP_CD_OFF_COMMLEN + 1] = 0;
1085 // disk number start
1086 buffer1[ZIP_CD_OFF_DISKSTART] = buffer1[ZIP_CD_OFF_DISKSTART + 1] = 0;
1088 // internal file attributes
1089 buffer1[ZIP_CD_OFF_IATTR] = buffer1[ZIP_CD_OFF_IATTR + 1] = 0;
1091 // external file attributes
1092 buffer1[ZIP_CD_OFF_EATTR] =
1093 buffer1[ZIP_CD_OFF_EATTR + 1] =
1094 buffer1[ZIP_CD_OFF_EATTR + 2] =
1095 buffer1[ZIP_CD_OFF_EATTR + 3] = 0;
1097 // relative offset of local header [42->45]
1098 setULong(h->lhOffset, buffer1, ZIP_CD_OFF_LHOFF);
1100 if (device->write(buffer1, ZIP_CD_SIZE) != ZIP_CD_SIZE)
1102 //! \todo See if we can detect QFile objects using the Qt Meta Object System
1104 if (!device->remove())
1105 qDebug() << tr("Unable to delete corrupted archive: %1").arg(device->fileName());
1107 return Zip::WriteFailed;
1110 // Write out filename
1111 if ((unsigned int)device->write(fileNameBytes) != sz)
1113 //! \todo SAME AS ABOVE: See if we can detect QFile objects using the Qt Meta Object System
1115 if (!device->remove())
1116 qDebug() << tr("Unable to delete corrupted archive: %1").arg(device->fileName());
1118 return Zip::WriteFailed;
1121 szCentralDir += (ZIP_CD_SIZE + sz);
1123 } // central dir headers loop
1126 // Write end of central directory
1128 // signature
1129 buffer1[0] = 'P';
1130 buffer1[1] = 'K';
1131 buffer1[2] = 0x05;
1132 buffer1[3] = 0x06;
1134 // number of this disk
1135 buffer1[ZIP_EOCD_OFF_DISKNUM] = buffer1[ZIP_EOCD_OFF_DISKNUM + 1] = 0;
1137 // number of disk with central directory
1138 buffer1[ZIP_EOCD_OFF_CDDISKNUM] = buffer1[ZIP_EOCD_OFF_CDDISKNUM + 1] = 0;
1140 // number of entries in this disk
1141 sz = headers->count();
1142 buffer1[ZIP_EOCD_OFF_ENTRIES] = sz & 0xFF;
1143 buffer1[ZIP_EOCD_OFF_ENTRIES + 1] = (sz >> 8) & 0xFF;
1145 // total number of entries
1146 buffer1[ZIP_EOCD_OFF_CDENTRIES] = buffer1[ZIP_EOCD_OFF_ENTRIES];
1147 buffer1[ZIP_EOCD_OFF_CDENTRIES + 1] = buffer1[ZIP_EOCD_OFF_ENTRIES + 1];
1149 // size of central directory [12->15]
1150 setULong(szCentralDir, buffer1, ZIP_EOCD_OFF_CDSIZE);
1152 // central dir offset [16->19]
1153 setULong(offCentralDir, buffer1, ZIP_EOCD_OFF_CDOFF);
1155 // ZIP file comment length
1156 QByteArray commentBytes = comment.toAscii();
1157 quint16 commentLength = commentBytes.size();
1159 if (commentLength == 0)
1161 buffer1[ZIP_EOCD_OFF_COMMLEN] = buffer1[ZIP_EOCD_OFF_COMMLEN + 1] = 0;
1163 else
1165 buffer1[ZIP_EOCD_OFF_COMMLEN] = commentLength & 0xFF;
1166 buffer1[ZIP_EOCD_OFF_COMMLEN + 1] = (commentLength >> 8) & 0xFF;
1169 if (device->write(buffer1, ZIP_EOCD_SIZE) != ZIP_EOCD_SIZE)
1171 //! \todo SAME AS ABOVE: See if we can detect QFile objects using the Qt Meta Object System
1173 if (!device->remove())
1174 qDebug() << tr("Unable to delete corrupted archive: %1").arg(device->fileName());
1176 return Zip::WriteFailed;
1179 if (commentLength != 0)
1181 if ((unsigned int)device->write(commentBytes) != commentLength)
1183 //! \todo SAME AS ABOVE: See if we can detect QFile objects using the Qt Meta Object System
1185 if (!device->remove())
1186 qDebug() << tr("Unable to delete corrupted archive: %1").arg(device->fileName());
1188 return Zip::WriteFailed;
1192 return Zip::Ok;
1195 //! \internal
1196 void ZipPrivate::reset()
1198 comment.clear();
1200 if (headers != 0)
1202 qDeleteAll(*headers);
1203 delete headers;
1204 headers = 0;
1207 delete device; device = 0;
1210 //! \internal Returns the path of the parent directory
1211 QString ZipPrivate::extractRoot(const QString& p)
1213 QDir d(QDir::cleanPath(p));
1214 if (!d.exists())
1215 return QString();
1217 if (!d.cdUp())
1218 return QString();
1220 return d.absolutePath();