zip.cpp

Go to the documentation of this file.
00001 /****************************************************************************
00002 ** Filename: zip.cpp
00003 ** Last updated [dd/mm/yyyy]: 01/02/2007
00004 **
00005 ** pkzip 2.0 file compression.
00006 **
00007 ** Some of the code has been inspired by other open source projects,
00008 ** (mainly Info-Zip and Gilles Vollant's minizip).
00009 ** Compression and decompression actually uses the zlib library.
00010 **
00011 ** Copyright (C) 2007 Angius Fabrizio. All rights reserved.
00012 **
00013 ** This file is part of the OSDaB project (http://osdab.sourceforge.net/).
00014 **
00015 ** This file may be distributed and/or modified under the terms of the
00016 ** GNU General Public License version 2 as published by the Free Software
00017 ** Foundation and appearing in the file LICENSE.GPL included in the
00018 ** packaging of this file.
00019 **
00020 ** This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE
00021 ** WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
00022 **
00023 ** See the file LICENSE.GPL that came with this software distribution or
00024 ** visit http://www.gnu.org/copyleft/gpl.html for GPL licensing information.
00025 **
00026 **********************************************************************/
00027 
00028 #include "zip.h"
00029 #include "zip_p.h"
00030 #include "zipentry_p.h"
00031 
00032 // we only use this to seed the random number generator
00033 #include <time.h>
00034 
00035 #include <QMap>
00036 #include <QString>
00037 #include <QStringList>
00038 #include <QDir>
00039 #include <QFile>
00040 #include <QDateTime>
00041 #include <QCoreApplication>
00042 
00043 // You can remove this #include if you replace the qDebug() statements.
00044 #include <QtDebug>
00045 
00047 #define ZIP_LOCAL_HEADER_SIZE 30
00049 #define ZIP_LOCAL_ENC_HEADER_SIZE 12
00051 #define ZIP_DD_SIZE_WS 16
00053 #define ZIP_CD_SIZE 46
00055 #define ZIP_EOCD_SIZE 22
00056 
00057 // Some offsets inside a local header record (signature included)
00058 #define ZIP_LH_OFF_VERS 4
00059 #define ZIP_LH_OFF_GPFLAG 6
00060 #define ZIP_LH_OFF_CMET 8
00061 #define ZIP_LH_OFF_MODT 10
00062 #define ZIP_LH_OFF_MODD 12
00063 #define ZIP_LH_OFF_CRC 14
00064 #define ZIP_LH_OFF_CSIZE 18
00065 #define ZIP_LH_OFF_USIZE 22
00066 #define ZIP_LH_OFF_NAMELEN 26
00067 #define ZIP_LH_OFF_XLEN 28
00068 
00069 // Some offsets inside a data descriptor record (including signature)
00070 #define ZIP_DD_OFF_CRC32 4
00071 #define ZIP_DD_OFF_CSIZE 8
00072 #define ZIP_DD_OFF_USIZE 12
00073 
00074 // Some offsets inside a Central Directory record (including signature)
00075 #define ZIP_CD_OFF_MADEBY 4
00076 #define ZIP_CD_OFF_VERSION 6
00077 #define ZIP_CD_OFF_GPFLAG 8
00078 #define ZIP_CD_OFF_CMET 10
00079 #define ZIP_CD_OFF_MODT 12
00080 #define ZIP_CD_OFF_MODD 14
00081 #define ZIP_CD_OFF_CRC 16
00082 #define ZIP_CD_OFF_CSIZE 20
00083 #define ZIP_CD_OFF_USIZE 24
00084 #define ZIP_CD_OFF_NAMELEN 28
00085 #define ZIP_CD_OFF_XLEN 30
00086 #define ZIP_CD_OFF_COMMLEN 32
00087 #define ZIP_CD_OFF_DISKSTART 34
00088 #define ZIP_CD_OFF_IATTR 36
00089 #define ZIP_CD_OFF_EATTR 38
00090 #define ZIP_CD_OFF_LHOFF 42
00091 
00092 // Some offsets inside a EOCD record (including signature)
00093 #define ZIP_EOCD_OFF_DISKNUM 4
00094 #define ZIP_EOCD_OFF_CDDISKNUM 6
00095 #define ZIP_EOCD_OFF_ENTRIES 8
00096 #define ZIP_EOCD_OFF_CDENTRIES 10
00097 #define ZIP_EOCD_OFF_CDSIZE 12
00098 #define ZIP_EOCD_OFF_CDOFF 16
00099 #define ZIP_EOCD_OFF_COMMLEN 20
00100 
00102 #define ZIP_VERSION 0x14
00103 
00105 #define ZIP_COMPRESSION_THRESHOLD 60
00106 
00108 #define CRC32(c, b) crcTable[((int)c^b) & 0xff] ^ (c >> 8)
00109 
00211 /************************************************************************
00212  Public interface
00213 *************************************************************************/
00214 
00218 Zip::Zip()
00219 {
00220     d = new ZipPrivate;
00221 }
00222 
00226 Zip::~Zip()
00227 {
00228     closeArchive();
00229     delete d;
00230 }
00231 
00235 bool Zip::isOpen() const
00236 {
00237     return d->device != 0;
00238 }
00239 
00246 void Zip::setPassword(const QString& pwd)
00247 {
00248     d->password = pwd;
00249 }
00250 
00252 void Zip::clearPassword()
00253 {
00254     d->password.clear();
00255 }
00256 
00258 QString Zip::password() const
00259 {
00260     return d->password;
00261 }
00262 
00268 Zip::ErrorCode Zip::createArchive(const QString& filename, bool overwrite)
00269 {
00270     QFile* file = new QFile(filename);
00271 
00272     if (file->exists() && !overwrite) {
00273         delete file;
00274         return Zip::FileExists;
00275     }
00276 
00277     if (!file->open(QIODevice::WriteOnly)) {
00278         delete file;
00279         return Zip::OpenFailed;
00280     }
00281 
00282     Zip::ErrorCode ec = createArchive(file);
00283     if (ec != Zip::Ok) {
00284         file->remove();
00285     }
00286 
00287     return ec;
00288 }
00289 
00294 Zip::ErrorCode Zip::createArchive(QIODevice* device)
00295 {
00296     if (device == 0)
00297     {
00298         qDebug() << "Invalid device.";
00299         return Zip::OpenFailed;
00300     }
00301 
00302     return d->createArchive(device);
00303 }
00304 
00308 QString Zip::archiveComment() const
00309 {
00310     return d->comment;
00311 }
00312 
00317 void Zip::setArchiveComment(const QString& comment)
00318 {
00319     if (d->device != 0)
00320         d->comment = comment;
00321 }
00322 
00333 Zip::ErrorCode Zip::addDirectory(const QString& path, CompressionOptions options, CompressionLevel level)
00334 {
00335     return addDirectory(path, QString(), options, level);
00336 }
00337 
00342 Zip::ErrorCode Zip::addDirectory(const QString& path, const QString& root, CompressionLevel level)
00343 {
00344     return addDirectory(path, root, Zip::RelativePaths, level);
00345 }
00346 
00351 Zip::ErrorCode Zip::addDirectoryContents(const QString& path, CompressionLevel level)
00352 {
00353     return addDirectory(path, QString(), IgnorePaths, level);
00354 }
00355 
00360 Zip::ErrorCode Zip::addDirectoryContents(const QString& path, const QString& root, CompressionLevel level)
00361 {
00362     return addDirectory(path, root, IgnorePaths, level);
00363 }
00364 
00376 Zip::ErrorCode Zip::addDirectory(const QString& path, const QString& root, CompressionOptions options, CompressionLevel level)
00377 {
00378     // qDebug() << QString("addDir(path=%1, root=%2)").arg(path, root);
00379 
00380     // Bad boy didn't call createArchive() yet :)
00381     if (d->device == 0)
00382         return Zip::NoOpenArchive;
00383 
00384     QDir dir(path);
00385     if (!dir.exists())
00386         return Zip::FileNotFound;
00387 
00388     // Remove any trailing separator
00389     QString actualRoot = root.trimmed();
00390 
00391     // Preserve Unix root
00392     if (actualRoot != "/")
00393     {
00394         while (actualRoot.endsWith("/") || actualRoot.endsWith("\\"))
00395             actualRoot.truncate(actualRoot.length() - 1);
00396     }
00397 
00398     // QDir::cleanPath() fixes some issues with QDir::dirName()
00399     QFileInfo current(QDir::cleanPath(path));
00400 
00401     if (!actualRoot.isEmpty() && actualRoot != "/")
00402         actualRoot.append("/");
00403 
00404     /* This part is quite confusing and needs some test or check */
00405     /* An attempt to compress the / root directory evtl. using a root prefix should be a good test */
00406     if (options.testFlag(AbsolutePaths) && !options.testFlag(IgnorePaths))
00407     {
00408         QString absolutePath = d->extractRoot(path);
00409         if (!absolutePath.isEmpty() && absolutePath != "/")
00410             absolutePath.append("/");
00411         actualRoot.append(absolutePath);
00412     }
00413 
00414     if (!options.testFlag(IgnorePaths))
00415     {
00416         actualRoot = actualRoot.append(QDir(current.absoluteFilePath()).dirName());
00417         actualRoot.append("/");
00418     }
00419 
00420     // actualRoot now contains the path of the file relative to the zip archive
00421     // with a trailing /
00422 
00423     QFileInfoList list = dir.entryInfoList(
00424         QDir::Files |
00425         QDir::Dirs |
00426         QDir::NoDotAndDotDot |
00427         QDir::NoSymLinks);
00428 
00429     ErrorCode ec = Zip::Ok;
00430     bool filesAdded = false;
00431 
00432     CompressionOptions recursionOptions;
00433     if (options.testFlag(IgnorePaths))
00434         recursionOptions |= IgnorePaths;
00435     else recursionOptions |= RelativePaths;
00436 
00437     for (int i = 0; i < list.size() && ec == Zip::Ok; ++i)
00438     {
00439         QFileInfo info = list.at(i);
00440 
00441         if (info.isDir())
00442         {
00443             // Recursion :)
00444             ec = addDirectory(info.absoluteFilePath(), actualRoot, recursionOptions, level);
00445         }
00446         else
00447         {
00448             ec = d->createEntry(info, actualRoot, level);
00449             filesAdded = true;
00450         }
00451     }
00452 
00453 
00454     // We need an explicit record for this dir
00455     // Non-empty directories don't need it because they have a path component in the filename
00456     if (!filesAdded && !options.testFlag(IgnorePaths))
00457         ec = d->createEntry(current, actualRoot, level);
00458 
00459     return ec;
00460 }
00461 
00465 Zip::ErrorCode Zip::closeArchive()
00466 {
00467     Zip::ErrorCode ec = d->closeArchive();
00468     d->reset();
00469     return ec;
00470 }
00471 
00475 QString Zip::formatError(Zip::ErrorCode c) const
00476 {
00477     switch (c)
00478     {
00479     case Ok: return QCoreApplication::translate("Zip", "ZIP operation completed successfully."); break;
00480     case ZlibInit: return QCoreApplication::translate("Zip", "Failed to initialize or load zlib library."); break;
00481     case ZlibError: return QCoreApplication::translate("Zip", "zlib library error."); break;
00482     case OpenFailed: return QCoreApplication::translate("Zip", "Unable to create or open file."); break;
00483     case NoOpenArchive: return QCoreApplication::translate("Zip", "No archive has been created yet."); break;
00484     case FileNotFound: return QCoreApplication::translate("Zip", "File or directory does not exist."); break;
00485     case ReadFailed: return QCoreApplication::translate("Zip", "File read error."); break;
00486     case WriteFailed: return QCoreApplication::translate("Zip", "File write error."); break;
00487     case SeekFailed: return QCoreApplication::translate("Zip", "File seek error."); break;
00488     default: ;
00489     }
00490 
00491     return QCoreApplication::translate("Zip", "Unknown error.");
00492 }
00493 
00494 
00495 /************************************************************************
00496  Private interface
00497 *************************************************************************/
00498 
00500 ZipPrivate::ZipPrivate()
00501 {
00502     headers = 0;
00503     device = 0;
00504 
00505     // keep an unsigned pointer so we avoid to over bloat the code with casts
00506     uBuffer = (unsigned char*) buffer1;
00507     crcTable = (quint32*) get_crc_table();
00508 }
00509 
00511 ZipPrivate::~ZipPrivate()
00512 {
00513     closeArchive();
00514 }
00515 
00517 Zip::ErrorCode ZipPrivate::createArchive(QIODevice* dev)
00518 {
00519     Q_ASSERT(dev != 0);
00520 
00521     if (device != 0)
00522         closeArchive();
00523 
00524     device = dev;
00525 
00526     if (!device->isOpen())
00527     {
00528         if (!device->open(QIODevice::ReadOnly)) {
00529             delete device;
00530             device = 0;
00531             qDebug() << "Unable to open device for writing.";
00532             return Zip::OpenFailed;
00533         }
00534     }
00535 
00536     headers = new QMap<QString,ZipEntryP*>;
00537     return Zip::Ok;
00538 }
00539 
00541 Zip::ErrorCode ZipPrivate::createEntry(const QFileInfo& file, const QString& root, Zip::CompressionLevel level)
00542 {
00544 
00545     // Directories and very small files are always stored
00546     // (small files would get bigger due to the compression headers overhead)
00547 
00548     // Need this for zlib
00549     bool isPNGFile = false;
00550     bool dirOnly = file.isDir();
00551 
00552     QString entryName = root;
00553 
00554     // Directory entry
00555     if (dirOnly)
00556         level = Zip::Store;
00557     else
00558     {
00559         entryName.append(file.fileName());
00560 
00561         QString ext = file.completeSuffix().toLower();
00562         isPNGFile = ext == "png";
00563 
00564         if (file.size() < ZIP_COMPRESSION_THRESHOLD)
00565             level = Zip::Store;
00566         else
00567             switch (level)
00568             {
00569             case Zip::AutoCPU:
00570                 level = Zip::Deflate5;
00571                 break;
00572             case Zip::AutoMIME:
00573                 level = detectCompressionByMime(ext);
00574                 break;
00575             case Zip::AutoFull:
00576                 level = detectCompressionByMime(ext);
00577                 break;
00578             default:
00579                 ;
00580             }
00581     }
00582 
00583     // entryName contains the path as it should be written
00584     // in the zip file records
00585     // qDebug() << QString("addDir(file=%1, root=%2, entry=%3)").arg(file.absoluteFilePath(), root, entryName);
00586 
00587     // create header and store it to write a central directory later
00588     ZipEntryP* h = new ZipEntryP;
00589 
00590     h->compMethod = (level == Zip::Store) ? 0 : 0x0008;
00591 
00592     // Set encryption bit and set the data descriptor bit
00593     // so we can use mod time instead of crc for password check
00594     bool encrypt = !dirOnly && !password.isEmpty();
00595     if (encrypt)
00596         h->gpFlag[0] |= 9;
00597 
00598     QDateTime dt = file.lastModified();
00599     QDate d = dt.date();
00600     h->modDate[1] = ((d.year() - 1980) << 1) & 254;
00601     h->modDate[1] |= ((d.month() >> 3) & 1);
00602     h->modDate[0] = ((d.month() & 7) << 5) & 224;
00603     h->modDate[0] |= d.day();
00604 
00605     QTime t = dt.time();
00606     h->modTime[1] = (t.hour() << 3) & 248;
00607     h->modTime[1] |= ((t.minute() >> 3) & 7);
00608     h->modTime[0] = ((t.minute() & 7) << 5) & 224;
00609     h->modTime[0] |= t.second() / 2;
00610 
00611     h->szUncomp = dirOnly ? 0 : file.size();
00612 
00613     // **** Write local file header ****
00614 
00615     // signature
00616     buffer1[0] = 'P'; buffer1[1] = 'K';
00617     buffer1[2] = 0x3; buffer1[3] = 0x4;
00618 
00619     // version needed to extract
00620     buffer1[ZIP_LH_OFF_VERS] = ZIP_VERSION;
00621     buffer1[ZIP_LH_OFF_VERS + 1] = 0;
00622 
00623     // general purpose flag
00624     buffer1[ZIP_LH_OFF_GPFLAG] = h->gpFlag[0];
00625     buffer1[ZIP_LH_OFF_GPFLAG + 1] = h->gpFlag[1];
00626 
00627     // compression method
00628     buffer1[ZIP_LH_OFF_CMET] = h->compMethod & 0xFF;
00629     buffer1[ZIP_LH_OFF_CMET + 1] = (h->compMethod>>8) & 0xFF;
00630 
00631     // last mod file time
00632     buffer1[ZIP_LH_OFF_MODT] = h->modTime[0];
00633     buffer1[ZIP_LH_OFF_MODT + 1] = h->modTime[1];
00634 
00635     // last mod file date
00636     buffer1[ZIP_LH_OFF_MODD] = h->modDate[0];
00637     buffer1[ZIP_LH_OFF_MODD + 1] = h->modDate[1];
00638 
00639     // skip crc (4bytes) [14,15,16,17]
00640 
00641     // skip compressed size but include evtl. encryption header (4bytes: [18,19,20,21])
00642     buffer1[ZIP_LH_OFF_CSIZE] =
00643     buffer1[ZIP_LH_OFF_CSIZE + 1] =
00644     buffer1[ZIP_LH_OFF_CSIZE + 2] =
00645     buffer1[ZIP_LH_OFF_CSIZE + 3] = 0;
00646 
00647     h->szComp = encrypt ? ZIP_LOCAL_ENC_HEADER_SIZE : 0;
00648 
00649     // uncompressed size [22,23,24,25]
00650     setULong(h->szUncomp, buffer1, ZIP_LH_OFF_USIZE);
00651 
00652     // filename length
00653     QByteArray entryNameBytes = entryName.toAscii();
00654     int sz = entryNameBytes.size();
00655 
00656     buffer1[ZIP_LH_OFF_NAMELEN] = sz & 0xFF;
00657     buffer1[ZIP_LH_OFF_NAMELEN + 1] = (sz >> 8) & 0xFF;
00658 
00659     // extra field length
00660     buffer1[ZIP_LH_OFF_XLEN] = buffer1[ZIP_LH_OFF_XLEN + 1] = 0;
00661 
00662     // Store offset to write crc and compressed size
00663     h->lhOffset = device->pos();
00664     quint32 crcOffset = h->lhOffset + ZIP_LH_OFF_CRC;
00665 
00666     if (device->write(buffer1, ZIP_LOCAL_HEADER_SIZE) != ZIP_LOCAL_HEADER_SIZE)
00667     {
00668         delete h;
00669         return Zip::WriteFailed;
00670     }
00671 
00672     // Write out filename
00673     if (device->write(entryNameBytes) != sz)
00674     {
00675         delete h;
00676         return Zip::WriteFailed;
00677     }
00678 
00679     // Encryption keys
00680     quint32 keys[3] = { 0, 0, 0 };
00681 
00682     if (encrypt)
00683     {
00684         // **** encryption header ****
00685 
00686         // XOR with PI to ensure better random numbers
00687         // with poorly implemented rand() as suggested by Info-Zip
00688         srand(time(NULL) ^ 3141592654UL);
00689         int randByte;
00690 
00691         initKeys(keys);
00692         for (int i=0; i<10; ++i)
00693         {
00694             randByte = (rand() >> 7) & 0xff;
00695             buffer1[i] = decryptByte(keys[2]) ^ randByte;
00696             updateKeys(keys, randByte);
00697         }
00698 
00699         // Encrypt encryption header
00700         initKeys(keys);
00701         for (int i=0; i<10; ++i)
00702         {
00703             randByte = decryptByte(keys[2]);
00704             updateKeys(keys, buffer1[i]);
00705             buffer1[i] ^= randByte;
00706         }
00707 
00708         // We don't know the CRC at this time, so we use the modification time
00709         // as the last two bytes
00710         randByte = decryptByte(keys[2]);
00711         updateKeys(keys, h->modTime[0]);
00712         buffer1[10] ^= randByte;
00713 
00714         randByte = decryptByte(keys[2]);
00715         updateKeys(keys, h->modTime[1]);
00716         buffer1[11] ^= randByte;
00717 
00718         // Write out encryption header
00719         if (device->write(buffer1, ZIP_LOCAL_ENC_HEADER_SIZE) != ZIP_LOCAL_ENC_HEADER_SIZE)
00720         {
00721             delete h;
00722             return Zip::WriteFailed;
00723         }
00724     }
00725 
00726     qint64 written = 0;
00727     quint32 crc = crc32(0L, Z_NULL, 0);
00728 
00729     if (!dirOnly)
00730     {
00731         QFile actualFile(file.absoluteFilePath());
00732         if (!actualFile.open(QIODevice::ReadOnly))
00733         {
00734             qDebug() << QString("An error occurred while opening %1").arg(file.absoluteFilePath());
00735             return Zip::OpenFailed;
00736         }
00737 
00738         // Write file data
00739         qint64 read = 0;
00740         qint64 totRead = 0;
00741         qint64 toRead = actualFile.size();
00742 
00743         if (level == Zip::Store)
00744         {
00745             while ( (read = actualFile.read(buffer1, ZIP_READ_BUFFER)) > 0 )
00746             {
00747                 crc = crc32(crc, uBuffer, read);
00748 
00749                 if (password != 0)
00750                     encryptBytes(keys, buffer1, read);
00751 
00752                 if ( (written = device->write(buffer1, read)) != read )
00753                 {
00754                     actualFile.close();
00755                     delete h;
00756                     return Zip::WriteFailed;
00757                 }
00758             }
00759         }
00760         else
00761         {
00762             z_stream zstr;
00763 
00764             // Initialize zalloc, zfree and opaque before calling the init function
00765             zstr.zalloc = Z_NULL;
00766             zstr.zfree = Z_NULL;
00767             zstr.opaque = Z_NULL;
00768 
00769             int zret;
00770 
00771             // Use deflateInit2 with negative windowBits to get raw compression
00772             if ((zret = deflateInit2_(
00773                     &zstr,
00774                     (int)level,
00775                     Z_DEFLATED,
00776                     -MAX_WBITS,
00777                     8,
00778                     isPNGFile ? Z_RLE : Z_DEFAULT_STRATEGY,
00779                     ZLIB_VERSION,
00780                     sizeof(z_stream)
00781                 )) != Z_OK )
00782             {
00783                 actualFile.close();
00784                 qDebug() << "Could not initialize zlib for compression";
00785                 delete h;
00786                 return Zip::ZlibError;
00787             }
00788 
00789             qint64 compressed;
00790 
00791             int flush = Z_NO_FLUSH;
00792 
00793             do
00794             {
00795                 read = actualFile.read(buffer1, ZIP_READ_BUFFER);
00796                 totRead += read;
00797 
00798                 if (read == 0)
00799                     break;
00800                 if (read < 0)
00801                 {
00802                     actualFile.close();
00803                     deflateEnd(&zstr);
00804                     qDebug() << QString("Error while reading %1").arg(file.absoluteFilePath());
00805                     delete h;
00806                     return Zip::ReadFailed;
00807                 }
00808 
00809                 crc = crc32(crc, uBuffer, read);
00810 
00811                 zstr.next_in = (Bytef*) buffer1;
00812                 zstr.avail_in = (uInt)read;
00813 
00814                 // Tell zlib if this is the last chunk we want to encode
00815                 // by setting the flush parameter to Z_FINISH
00816                 flush = (totRead == toRead) ? Z_FINISH : Z_NO_FLUSH;
00817 
00818                 // Run deflate() on input until output buffer not full
00819                 // finish compression if all of source has been read in
00820                 do
00821                 {
00822                     zstr.next_out = (Bytef*) buffer2;
00823                     zstr.avail_out = ZIP_READ_BUFFER;
00824 
00825                     zret = deflate(&zstr, flush);
00826                     // State not clobbered
00827                     Q_ASSERT(zret != Z_STREAM_ERROR);
00828 
00829                     // Write compressed data to file and empty buffer
00830                     compressed = ZIP_READ_BUFFER - zstr.avail_out;
00831 
00832                     if (password != 0)
00833                         encryptBytes(keys, buffer2, compressed);
00834 
00835                     if (device->write(buffer2, compressed) != compressed)
00836                     {
00837                         deflateEnd(&zstr);
00838                         actualFile.close();
00839                         qDebug() << QString("Error while writing %1").arg(file.absoluteFilePath());
00840                         delete h;
00841                         return Zip::WriteFailed;
00842                     }
00843 
00844                     written += compressed;
00845 
00846                 } while (zstr.avail_out == 0);
00847 
00848                 // All input will be used
00849                 Q_ASSERT(zstr.avail_in == 0);
00850 
00851             } while (flush != Z_FINISH);
00852 
00853             // Stream will be complete
00854             Q_ASSERT(zret == Z_STREAM_END);
00855 
00856             deflateEnd(&zstr);
00857 
00858         } // if (level != STORE)
00859 
00860         actualFile.close();
00861     }
00862 
00863     // Store end of entry offset
00864     quint32 current = device->pos();
00865 
00866     // Update crc and compressed size in local header
00867     if (!device->seek(crcOffset))
00868     {
00869         delete h;
00870         return Zip::SeekFailed;
00871     }
00872 
00873     h->crc = dirOnly ? 0 : crc;
00874     h->szComp += written;
00875 
00876     setULong(h->crc, buffer1, 0);
00877     setULong(h->szComp, buffer1, 4);
00878     if ( device->write(buffer1, 8) != 8)
00879     {
00880         delete h;
00881         return Zip::WriteFailed;
00882     }
00883 
00884     // Seek to end of entry
00885     if (!device->seek(current))
00886     {
00887         delete h;
00888         return Zip::SeekFailed;
00889     }
00890 
00891     if ((h->gpFlag[0] & 8) == 8)
00892     {
00893         // Write data descriptor
00894 
00895         // Signature: PK\7\8
00896         buffer1[0] = 'P';
00897         buffer1[1] = 'K';
00898         buffer1[2] = 0x07;
00899         buffer1[3] = 0x08;
00900 
00901         // CRC
00902         setULong(h->crc, buffer1, ZIP_DD_OFF_CRC32);
00903 
00904         // Compressed size
00905         setULong(h->szComp, buffer1, ZIP_DD_OFF_CSIZE);
00906 
00907         // Uncompressed size
00908         setULong(h->szUncomp, buffer1, ZIP_DD_OFF_USIZE);
00909 
00910         if (device->write(buffer1, ZIP_DD_SIZE_WS) != ZIP_DD_SIZE_WS)
00911         {
00912             delete h;
00913             return Zip::WriteFailed;
00914         }
00915     }
00916 
00917     headers->insert(entryName, h);
00918     return Zip::Ok;
00919 }
00920 
00922 int ZipPrivate::decryptByte(quint32 key2) const
00923 {
00924     quint16 temp = ((quint16)(key2) & 0xffff) | 2;
00925     return (int)(((temp * (temp ^ 1)) >> 8) & 0xff);
00926 }
00927 
00929 void ZipPrivate::setULong(quint32 v, char* buffer, unsigned int offset)
00930 {
00931     buffer[offset+3] = ((v >> 24) & 0xFF);
00932     buffer[offset+2] = ((v >> 16) & 0xFF);
00933     buffer[offset+1] = ((v >> 8) & 0xFF);
00934     buffer[offset] = (v & 0xFF);
00935 }
00936 
00938 void ZipPrivate::initKeys(quint32* keys) const
00939 {
00940     // Encryption keys initialization constants are taken from the
00941     // PKZip file format specification docs
00942     keys[0] = 305419896L;
00943     keys[1] = 591751049L;
00944     keys[2] = 878082192L;
00945 
00946     QByteArray pwdBytes = password.toAscii();
00947     int sz = pwdBytes.size();
00948     const char* ascii = pwdBytes.data();
00949 
00950     for (int i=0; i<sz; ++i)
00951         updateKeys(keys, (int)ascii[i]);
00952 }
00953 
00955 void ZipPrivate::updateKeys(quint32* keys, int c) const
00956 {
00957     keys[0] = CRC32(keys[0], c);
00958     keys[1] += keys[0] & 0xff;
00959     keys[1] = keys[1] * 134775813L + 1;
00960     keys[2] = CRC32(keys[2], ((int)keys[1]) >> 24);
00961 }
00962 
00964 void ZipPrivate::encryptBytes(quint32* keys, char* buffer, qint64 read)
00965 {
00966     char t;
00967 
00968     for (int i=0; i<(int)read; ++i)
00969     {
00970         t = buffer[i];
00971         buffer[i] ^= decryptByte(keys[2]);
00972         updateKeys(keys, t);
00973     }
00974 }
00975 
00977 Zip::CompressionLevel ZipPrivate::detectCompressionByMime(const QString& ext)
00978 {
00979     // files really hard to compress
00980     if ((ext == "png") ||
00981         (ext == "jpg") ||
00982         (ext == "jpeg") ||
00983         (ext == "mp3") ||
00984         (ext == "ogg") ||
00985         (ext == "ogm") ||
00986         (ext == "avi") ||
00987         (ext == "mov") ||
00988         (ext == "rm") ||
00989         (ext == "ra") ||
00990         (ext == "zip") ||
00991         (ext == "rar") ||
00992         (ext == "bz2") ||
00993         (ext == "gz") ||
00994         (ext == "7z") ||
00995         (ext == "z") ||
00996         (ext == "jar")
00997     ) return Zip::Store;
00998 
00999     // files slow and hard to compress
01000     if ((ext == "exe") ||
01001         (ext == "bin") ||
01002         (ext == "rpm") ||
01003         (ext == "deb")
01004     ) return Zip::Deflate2;
01005 
01006     return Zip::Deflate9;
01007 }
01008 
01012 Zip::ErrorCode ZipPrivate::closeArchive()
01013 {
01014     // Close current archive by writing out central directory
01015     // and free up resources
01016 
01017     if (device == 0)
01018         return Zip::Ok;
01019 
01020     if (headers == 0)
01021         return Zip::Ok;
01022 
01023     const ZipEntryP* h;
01024 
01025     unsigned int sz;
01026     quint32 szCentralDir = 0;
01027     quint32 offCentralDir = device->pos();
01028 
01029     for (QMap<QString,ZipEntryP*>::ConstIterator itr = headers->constBegin(); itr != headers->constEnd(); ++itr)
01030     {
01031         h = itr.value();
01032 
01033         // signature
01034         buffer1[0] = 'P';
01035         buffer1[1] = 'K';
01036         buffer1[2] = 0x01;
01037         buffer1[3] = 0x02;
01038 
01039         // version made by  (currently only MS-DOS/FAT - no symlinks or other stuff supported)
01040         buffer1[ZIP_CD_OFF_MADEBY] = buffer1[ZIP_CD_OFF_MADEBY + 1] = 0;
01041 
01042         // version needed to extract
01043         buffer1[ZIP_CD_OFF_VERSION] = ZIP_VERSION;
01044         buffer1[ZIP_CD_OFF_VERSION + 1] = 0;
01045 
01046         // general purpose flag
01047         buffer1[ZIP_CD_OFF_GPFLAG] = h->gpFlag[0];
01048         buffer1[ZIP_CD_OFF_GPFLAG + 1] = h->gpFlag[1];
01049 
01050         // compression method
01051         buffer1[ZIP_CD_OFF_CMET] = h->compMethod & 0xFF;
01052         buffer1[ZIP_CD_OFF_CMET + 1] = (h->compMethod >> 8) & 0xFF;
01053 
01054         // last mod file time
01055         buffer1[ZIP_CD_OFF_MODT] = h->modTime[0];
01056         buffer1[ZIP_CD_OFF_MODT + 1] = h->modTime[1];
01057 
01058         // last mod file date
01059         buffer1[ZIP_CD_OFF_MODD] = h->modDate[0];
01060         buffer1[ZIP_CD_OFF_MODD + 1] = h->modDate[1];
01061 
01062         // crc (4bytes) [16,17,18,19]
01063         setULong(h->crc, buffer1, ZIP_CD_OFF_CRC);
01064 
01065         // compressed size (4bytes: [20,21,22,23])
01066         setULong(h->szComp, buffer1, ZIP_CD_OFF_CSIZE);
01067 
01068         // uncompressed size [24,25,26,27]
01069         setULong(h->szUncomp, buffer1, ZIP_CD_OFF_USIZE);
01070 
01071         // filename
01072         QByteArray fileNameBytes = itr.key().toAscii();
01073         sz = fileNameBytes.size();
01074         buffer1[ZIP_CD_OFF_NAMELEN] = sz & 0xFF;
01075         buffer1[ZIP_CD_OFF_NAMELEN + 1] = (sz >> 8) & 0xFF;
01076 
01077         // extra field length
01078         buffer1[ZIP_CD_OFF_XLEN] = buffer1[ZIP_CD_OFF_XLEN + 1] = 0;
01079 
01080         // file comment length
01081         buffer1[ZIP_CD_OFF_COMMLEN] = buffer1[ZIP_CD_OFF_COMMLEN + 1] = 0;
01082 
01083         // disk number start
01084         buffer1[ZIP_CD_OFF_DISKSTART] = buffer1[ZIP_CD_OFF_DISKSTART + 1] = 0;
01085 
01086         // internal file attributes
01087         buffer1[ZIP_CD_OFF_IATTR] = buffer1[ZIP_CD_OFF_IATTR + 1] = 0;
01088 
01089         // external file attributes
01090         buffer1[ZIP_CD_OFF_EATTR] =
01091         buffer1[ZIP_CD_OFF_EATTR + 1] =
01092         buffer1[ZIP_CD_OFF_EATTR + 2] =
01093         buffer1[ZIP_CD_OFF_EATTR + 3] = 0;
01094 
01095         // relative offset of local header [42->45]
01096         setULong(h->lhOffset, buffer1, ZIP_CD_OFF_LHOFF);
01097 
01098         if (device->write(buffer1, ZIP_CD_SIZE) != ZIP_CD_SIZE)
01099         {
01101             /*
01102             if (!device->remove())
01103                 qDebug() << tr("Unable to delete corrupted archive: %1").arg(device->fileName());
01104             */
01105             return Zip::WriteFailed;
01106         }
01107 
01108         // Write out filename
01109         if ((unsigned int)device->write(fileNameBytes) != sz)
01110         {
01112             /*
01113             if (!device->remove())
01114                 qDebug() << tr("Unable to delete corrupted archive: %1").arg(device->fileName());
01115                 */
01116             return Zip::WriteFailed;
01117         }
01118 
01119         szCentralDir += (ZIP_CD_SIZE + sz);
01120 
01121     } // central dir headers loop
01122 
01123 
01124     // Write end of central directory
01125 
01126     // signature
01127     buffer1[0] = 'P';
01128     buffer1[1] = 'K';
01129     buffer1[2] = 0x05;
01130     buffer1[3] = 0x06;
01131 
01132     // number of this disk
01133     buffer1[ZIP_EOCD_OFF_DISKNUM] = buffer1[ZIP_EOCD_OFF_DISKNUM + 1] = 0;
01134 
01135     // number of disk with central directory
01136     buffer1[ZIP_EOCD_OFF_CDDISKNUM] = buffer1[ZIP_EOCD_OFF_CDDISKNUM + 1] = 0;
01137 
01138     // number of entries in this disk
01139     sz = headers->count();
01140     buffer1[ZIP_EOCD_OFF_ENTRIES] = sz & 0xFF;
01141     buffer1[ZIP_EOCD_OFF_ENTRIES + 1] = (sz >> 8) & 0xFF;
01142 
01143     // total number of entries
01144     buffer1[ZIP_EOCD_OFF_CDENTRIES] = buffer1[ZIP_EOCD_OFF_ENTRIES];
01145     buffer1[ZIP_EOCD_OFF_CDENTRIES + 1] = buffer1[ZIP_EOCD_OFF_ENTRIES + 1];
01146 
01147     // size of central directory [12->15]
01148     setULong(szCentralDir, buffer1, ZIP_EOCD_OFF_CDSIZE);
01149 
01150     // central dir offset [16->19]
01151     setULong(offCentralDir, buffer1, ZIP_EOCD_OFF_CDOFF);
01152 
01153     // ZIP file comment length
01154     QByteArray commentBytes = comment.toAscii();
01155     quint16 commentLength = commentBytes.size();
01156 
01157     if (commentLength == 0)
01158     {
01159         buffer1[ZIP_EOCD_OFF_COMMLEN] = buffer1[ZIP_EOCD_OFF_COMMLEN + 1] = 0;
01160     }
01161     else
01162     {
01163         buffer1[ZIP_EOCD_OFF_COMMLEN] = commentLength & 0xFF;
01164         buffer1[ZIP_EOCD_OFF_COMMLEN + 1] = (commentLength >> 8) & 0xFF;
01165     }
01166 
01167     if (device->write(buffer1, ZIP_EOCD_SIZE) != ZIP_EOCD_SIZE)
01168     {
01170         /*
01171         if (!device->remove())
01172             qDebug() << tr("Unable to delete corrupted archive: %1").arg(device->fileName());
01173             */
01174         return Zip::WriteFailed;
01175     }
01176 
01177     if (commentLength != 0)
01178     {
01179         if ((unsigned int)device->write(commentBytes) != commentLength)
01180         {
01182             /*
01183             if (!device->remove())
01184                 qDebug() << tr("Unable to delete corrupted archive: %1").arg(device->fileName());
01185                 */
01186             return Zip::WriteFailed;
01187         }
01188     }
01189 
01190     return Zip::Ok;
01191 }
01192 
01194 void ZipPrivate::reset()
01195 {
01196     comment.clear();
01197 
01198     if (headers != 0)
01199     {
01200         qDeleteAll(*headers);
01201         delete headers;
01202         headers = 0;
01203     }
01204 
01205     delete device; device = 0;
01206 }
01207 
01209 QString ZipPrivate::extractRoot(const QString& p)
01210 {
01211     QDir d(QDir::cleanPath(p));
01212     if (!d.exists())
01213         return QString();
01214 
01215     if (!d.cdUp())
01216         return QString();
01217 
01218     return d.absolutePath();
01219 }