util/src/gui/text/qzip.cpp
changeset 7 f7bc934e204c
equal deleted inserted replaced
3:41300fa6a67c 7:f7bc934e204c
       
     1 /****************************************************************************
       
     2 **
       
     3 ** Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies).
       
     4 ** All rights reserved.
       
     5 ** Contact: Nokia Corporation (qt-info@nokia.com)
       
     6 **
       
     7 ** This file is part of the QtGui module of the Qt Toolkit.
       
     8 **
       
     9 ** $QT_BEGIN_LICENSE:LGPL$
       
    10 ** No Commercial Usage
       
    11 ** This file contains pre-release code and may not be distributed.
       
    12 ** You may use this file in accordance with the terms and conditions
       
    13 ** contained in the Technology Preview License Agreement accompanying
       
    14 ** this package.
       
    15 **
       
    16 ** GNU Lesser General Public License Usage
       
    17 ** Alternatively, this file may be used under the terms of the GNU Lesser
       
    18 ** General Public License version 2.1 as published by the Free Software
       
    19 ** Foundation and appearing in the file LICENSE.LGPL included in the
       
    20 ** packaging of this file.  Please review the following information to
       
    21 ** ensure the GNU Lesser General Public License version 2.1 requirements
       
    22 ** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
       
    23 **
       
    24 ** In addition, as a special exception, Nokia gives you certain additional
       
    25 ** rights.  These rights are described in the Nokia Qt LGPL Exception
       
    26 ** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
       
    27 **
       
    28 ** If you have questions regarding the use of this file, please contact
       
    29 ** Nokia at qt-info@nokia.com.
       
    30 **
       
    31 **
       
    32 **
       
    33 **
       
    34 **
       
    35 **
       
    36 **
       
    37 **
       
    38 ** $QT_END_LICENSE$
       
    39 **
       
    40 ****************************************************************************/
       
    41 
       
    42 #include <qglobal.h>
       
    43 
       
    44 #ifndef QT_NO_TEXTODFWRITER
       
    45 
       
    46 #include "qzipreader_p.h"
       
    47 #include "qzipwriter_p.h"
       
    48 #include <qdatetime.h>
       
    49 #include <qplatformdefs.h>
       
    50 #include <qendian.h>
       
    51 #include <qdebug.h>
       
    52 #include <qdir.h>
       
    53 
       
    54 #include <zlib.h>
       
    55 
       
    56 #if defined(Q_OS_WIN)
       
    57 #  undef S_IFREG
       
    58 #  define S_IFREG 0100000
       
    59 #  ifndef S_IFDIR
       
    60 #    define S_IFDIR 0040000
       
    61 #  endif
       
    62 #  ifndef S_ISDIR
       
    63 #    define S_ISDIR(x) ((x) & S_IFDIR) > 0
       
    64 #  endif
       
    65 #  ifndef S_ISREG
       
    66 #    define S_ISREG(x) ((x) & 0170000) == S_IFREG
       
    67 #  endif
       
    68 #  define S_IFLNK 020000
       
    69 #  define S_ISLNK(x) ((x) & S_IFLNK) > 0
       
    70 #  ifndef S_IRUSR
       
    71 #    define S_IRUSR 0400
       
    72 #  endif
       
    73 #  ifndef S_IWUSR
       
    74 #    define S_IWUSR 0200
       
    75 #  endif
       
    76 #  ifndef S_IXUSR
       
    77 #    define S_IXUSR 0100
       
    78 #  endif
       
    79 #  define S_IRGRP 0040
       
    80 #  define S_IWGRP 0020
       
    81 #  define S_IXGRP 0010
       
    82 #  define S_IROTH 0004
       
    83 #  define S_IWOTH 0002
       
    84 #  define S_IXOTH 0001
       
    85 #endif
       
    86 
       
    87 #if 0
       
    88 #define ZDEBUG qDebug
       
    89 #else
       
    90 #define ZDEBUG if (0) qDebug
       
    91 #endif
       
    92 
       
    93 QT_BEGIN_NAMESPACE
       
    94 
       
    95 static inline uint readUInt(const uchar *data)
       
    96 {
       
    97     return (data[0]) + (data[1]<<8) + (data[2]<<16) + (data[3]<<24);
       
    98 }
       
    99 
       
   100 static inline ushort readUShort(const uchar *data)
       
   101 {
       
   102     return (data[0]) + (data[1]<<8);
       
   103 }
       
   104 
       
   105 static inline void writeUInt(uchar *data, uint i)
       
   106 {
       
   107     data[0] = i & 0xff;
       
   108     data[1] = (i>>8) & 0xff;
       
   109     data[2] = (i>>16) & 0xff;
       
   110     data[3] = (i>>24) & 0xff;
       
   111 }
       
   112 
       
   113 static inline void writeUShort(uchar *data, ushort i)
       
   114 {
       
   115     data[0] = i & 0xff;
       
   116     data[1] = (i>>8) & 0xff;
       
   117 }
       
   118 
       
   119 static inline void copyUInt(uchar *dest, const uchar *src)
       
   120 {
       
   121     dest[0] = src[0];
       
   122     dest[1] = src[1];
       
   123     dest[2] = src[2];
       
   124     dest[3] = src[3];
       
   125 }
       
   126 
       
   127 static inline void copyUShort(uchar *dest, const uchar *src)
       
   128 {
       
   129     dest[0] = src[0];
       
   130     dest[1] = src[1];
       
   131 }
       
   132 
       
   133 static void writeMSDosDate(uchar *dest, const QDateTime& dt)
       
   134 {
       
   135     if (dt.isValid()) {
       
   136         quint16 time =
       
   137             (dt.time().hour() << 11)    // 5 bit hour
       
   138             | (dt.time().minute() << 5)   // 6 bit minute
       
   139             | (dt.time().second() >> 1);  // 5 bit double seconds
       
   140 
       
   141         dest[0] = time & 0xff;
       
   142         dest[1] = time >> 8;
       
   143 
       
   144         quint16 date =
       
   145             ((dt.date().year() - 1980) << 9) // 7 bit year 1980-based
       
   146             | (dt.date().month() << 5)           // 4 bit month
       
   147             | (dt.date().day());                 // 5 bit day
       
   148 
       
   149         dest[2] = char(date);
       
   150         dest[3] = char(date >> 8);
       
   151     } else {
       
   152         dest[0] = 0;
       
   153         dest[1] = 0;
       
   154         dest[2] = 0;
       
   155         dest[3] = 0;
       
   156     }
       
   157 }
       
   158 
       
   159 static quint32 permissionsToMode(QFile::Permissions perms)
       
   160 {
       
   161     quint32 mode = 0;
       
   162     if (perms & QFile::ReadOwner)
       
   163         mode |= S_IRUSR;
       
   164     if (perms & QFile::WriteOwner)
       
   165         mode |= S_IWUSR;
       
   166     if (perms & QFile::ExeOwner)
       
   167         mode |= S_IXUSR;
       
   168     if (perms & QFile::ReadUser)
       
   169         mode |= S_IRUSR;
       
   170     if (perms & QFile::WriteUser)
       
   171         mode |= S_IWUSR;
       
   172     if (perms & QFile::ExeUser)
       
   173         mode |= S_IXUSR;
       
   174     if (perms & QFile::ReadGroup)
       
   175         mode |= S_IRGRP;
       
   176     if (perms & QFile::WriteGroup)
       
   177         mode |= S_IWGRP;
       
   178     if (perms & QFile::ExeGroup)
       
   179         mode |= S_IXGRP;
       
   180     if (perms & QFile::ReadOther)
       
   181         mode |= S_IROTH;
       
   182     if (perms & QFile::WriteOther)
       
   183         mode |= S_IWOTH;
       
   184     if (perms & QFile::ExeOther)
       
   185         mode |= S_IXOTH;
       
   186     return mode;
       
   187 }
       
   188 
       
   189 static int inflate(Bytef *dest, ulong *destLen, const Bytef *source, ulong sourceLen)
       
   190 {
       
   191     z_stream stream;
       
   192     int err;
       
   193 
       
   194     stream.next_in = (Bytef*)source;
       
   195     stream.avail_in = (uInt)sourceLen;
       
   196     if ((uLong)stream.avail_in != sourceLen)
       
   197         return Z_BUF_ERROR;
       
   198 
       
   199     stream.next_out = dest;
       
   200     stream.avail_out = (uInt)*destLen;
       
   201     if ((uLong)stream.avail_out != *destLen)
       
   202         return Z_BUF_ERROR;
       
   203 
       
   204     stream.zalloc = (alloc_func)0;
       
   205     stream.zfree = (free_func)0;
       
   206 
       
   207     err = inflateInit2(&stream, -MAX_WBITS);
       
   208     if (err != Z_OK)
       
   209         return err;
       
   210 
       
   211     err = inflate(&stream, Z_FINISH);
       
   212     if (err != Z_STREAM_END) {
       
   213         inflateEnd(&stream);
       
   214         if (err == Z_NEED_DICT || (err == Z_BUF_ERROR && stream.avail_in == 0))
       
   215             return Z_DATA_ERROR;
       
   216         return err;
       
   217     }
       
   218     *destLen = stream.total_out;
       
   219 
       
   220     err = inflateEnd(&stream);
       
   221     return err;
       
   222 }
       
   223 
       
   224 static int deflate (Bytef *dest, ulong *destLen, const Bytef *source, ulong sourceLen)
       
   225 {
       
   226     z_stream stream;
       
   227     int err;
       
   228 
       
   229     stream.next_in = (Bytef*)source;
       
   230     stream.avail_in = (uInt)sourceLen;
       
   231     stream.next_out = dest;
       
   232     stream.avail_out = (uInt)*destLen;
       
   233     if ((uLong)stream.avail_out != *destLen) return Z_BUF_ERROR;
       
   234 
       
   235     stream.zalloc = (alloc_func)0;
       
   236     stream.zfree = (free_func)0;
       
   237     stream.opaque = (voidpf)0;
       
   238 
       
   239     err = deflateInit2(&stream, Z_DEFAULT_COMPRESSION, Z_DEFLATED, -MAX_WBITS, 8, Z_DEFAULT_STRATEGY);
       
   240     if (err != Z_OK) return err;
       
   241 
       
   242     err = deflate(&stream, Z_FINISH);
       
   243     if (err != Z_STREAM_END) {
       
   244         deflateEnd(&stream);
       
   245         return err == Z_OK ? Z_BUF_ERROR : err;
       
   246     }
       
   247     *destLen = stream.total_out;
       
   248 
       
   249     err = deflateEnd(&stream);
       
   250     return err;
       
   251 }
       
   252 
       
   253 static QFile::Permissions modeToPermissions(quint32 mode)
       
   254 {
       
   255     QFile::Permissions ret;
       
   256     if (mode & S_IRUSR)
       
   257         ret |= QFile::ReadOwner;
       
   258     if (mode & S_IWUSR)
       
   259         ret |= QFile::WriteOwner;
       
   260     if (mode & S_IXUSR)
       
   261         ret |= QFile::ExeOwner;
       
   262     if (mode & S_IRUSR)
       
   263         ret |= QFile::ReadUser;
       
   264     if (mode & S_IWUSR)
       
   265         ret |= QFile::WriteUser;
       
   266     if (mode & S_IXUSR)
       
   267         ret |= QFile::ExeUser;
       
   268     if (mode & S_IRGRP)
       
   269         ret |= QFile::ReadGroup;
       
   270     if (mode & S_IWGRP)
       
   271         ret |= QFile::WriteGroup;
       
   272     if (mode & S_IXGRP)
       
   273         ret |= QFile::ExeGroup;
       
   274     if (mode & S_IROTH)
       
   275         ret |= QFile::ReadOther;
       
   276     if (mode & S_IWOTH)
       
   277         ret |= QFile::WriteOther;
       
   278     if (mode & S_IXOTH)
       
   279         ret |= QFile::ExeOther;
       
   280     return ret;
       
   281 }
       
   282 
       
   283 struct LocalFileHeader
       
   284 {
       
   285     uchar signature[4]; //  0x04034b50
       
   286     uchar version_needed[2];
       
   287     uchar general_purpose_bits[2];
       
   288     uchar compression_method[2];
       
   289     uchar last_mod_file[4];
       
   290     uchar crc_32[4];
       
   291     uchar compressed_size[4];
       
   292     uchar uncompressed_size[4];
       
   293     uchar file_name_length[2];
       
   294     uchar extra_field_length[2];
       
   295 };
       
   296 
       
   297 struct DataDescriptor
       
   298 {
       
   299     uchar crc_32[4];
       
   300     uchar compressed_size[4];
       
   301     uchar uncompressed_size[4];
       
   302 };
       
   303 
       
   304 struct CentralFileHeader
       
   305 {
       
   306     uchar signature[4]; // 0x02014b50
       
   307     uchar version_made[2];
       
   308     uchar version_needed[2];
       
   309     uchar general_purpose_bits[2];
       
   310     uchar compression_method[2];
       
   311     uchar last_mod_file[4];
       
   312     uchar crc_32[4];
       
   313     uchar compressed_size[4];
       
   314     uchar uncompressed_size[4];
       
   315     uchar file_name_length[2];
       
   316     uchar extra_field_length[2];
       
   317     uchar file_comment_length[2];
       
   318     uchar disk_start[2];
       
   319     uchar internal_file_attributes[2];
       
   320     uchar external_file_attributes[4];
       
   321     uchar offset_local_header[4];
       
   322     LocalFileHeader toLocalHeader() const;
       
   323 };
       
   324 
       
   325 struct EndOfDirectory
       
   326 {
       
   327     uchar signature[4]; // 0x06054b50
       
   328     uchar this_disk[2];
       
   329     uchar start_of_directory_disk[2];
       
   330     uchar num_dir_entries_this_disk[2];
       
   331     uchar num_dir_entries[2];
       
   332     uchar directory_size[4];
       
   333     uchar dir_start_offset[4];
       
   334     uchar comment_length[2];
       
   335 };
       
   336 
       
   337 struct FileHeader
       
   338 {
       
   339     CentralFileHeader h;
       
   340     QByteArray file_name;
       
   341     QByteArray extra_field;
       
   342     QByteArray file_comment;
       
   343 };
       
   344 
       
   345 QZipReader::FileInfo::FileInfo()
       
   346     : isDir(false), isFile(true), isSymLink(false), crc32(0), size(0)
       
   347 {
       
   348 }
       
   349 
       
   350 QZipReader::FileInfo::~FileInfo()
       
   351 {
       
   352 }
       
   353 
       
   354 QZipReader::FileInfo::FileInfo(const FileInfo &other)
       
   355 {
       
   356     operator=(other);
       
   357 }
       
   358 
       
   359 QZipReader::FileInfo& QZipReader::FileInfo::operator=(const FileInfo &other)
       
   360 {
       
   361     filePath = other.filePath;
       
   362     isDir = other.isDir;
       
   363     isFile = other.isFile;
       
   364     isSymLink = other.isSymLink;
       
   365     permissions = other.permissions;
       
   366     crc32 = other.crc32;
       
   367     size = other.size;
       
   368     return *this;
       
   369 }
       
   370 
       
   371 class QZipPrivate
       
   372 {
       
   373 public:
       
   374     QZipPrivate(QIODevice *device, bool ownDev)
       
   375         : device(device), ownDevice(ownDev), dirtyFileTree(true), start_of_directory(0)
       
   376     {
       
   377     }
       
   378 
       
   379     ~QZipPrivate()
       
   380     {
       
   381         if (ownDevice)
       
   382             delete device;
       
   383     }
       
   384 
       
   385     void fillFileInfo(int index, QZipReader::FileInfo &fileInfo) const;
       
   386 
       
   387     QIODevice *device;
       
   388     bool ownDevice;
       
   389     bool dirtyFileTree;
       
   390     QList<FileHeader> fileHeaders;
       
   391     QByteArray comment;
       
   392     uint start_of_directory;
       
   393 };
       
   394 
       
   395 void QZipPrivate::fillFileInfo(int index, QZipReader::FileInfo &fileInfo) const
       
   396 {
       
   397     FileHeader header = fileHeaders.at(index);
       
   398     fileInfo.filePath = QString::fromLocal8Bit(header.file_name);
       
   399     const quint32 mode = (qFromLittleEndian<quint32>(&header.h.external_file_attributes[0]) >> 16) & 0xFFFF;
       
   400     fileInfo.isDir = S_ISDIR(mode);
       
   401     fileInfo.isFile = S_ISREG(mode);
       
   402     fileInfo.isSymLink = S_ISLNK(mode);
       
   403     fileInfo.permissions = modeToPermissions(mode);
       
   404     fileInfo.crc32 = readUInt(header.h.crc_32);
       
   405     fileInfo.size = readUInt(header.h.uncompressed_size);
       
   406 }
       
   407 
       
   408 class QZipReaderPrivate : public QZipPrivate
       
   409 {
       
   410 public:
       
   411     QZipReaderPrivate(QIODevice *device, bool ownDev)
       
   412         : QZipPrivate(device, ownDev), status(QZipReader::NoError)
       
   413     {
       
   414     }
       
   415 
       
   416     void scanFiles();
       
   417 
       
   418     QZipReader::Status status;
       
   419 };
       
   420 
       
   421 class QZipWriterPrivate : public QZipPrivate
       
   422 {
       
   423 public:
       
   424     QZipWriterPrivate(QIODevice *device, bool ownDev)
       
   425         : QZipPrivate(device, ownDev),
       
   426         status(QZipWriter::NoError),
       
   427         permissions(QFile::ReadOwner | QFile::WriteOwner),
       
   428         compressionPolicy(QZipWriter::AlwaysCompress)
       
   429     {
       
   430     }
       
   431 
       
   432     QZipWriter::Status status;
       
   433     QFile::Permissions permissions;
       
   434     QZipWriter::CompressionPolicy compressionPolicy;
       
   435 
       
   436     enum EntryType { Directory, File, Symlink };
       
   437 
       
   438     void addEntry(EntryType type, const QString &fileName, const QByteArray &contents);
       
   439 };
       
   440 
       
   441 LocalFileHeader CentralFileHeader::toLocalHeader() const
       
   442 {
       
   443     LocalFileHeader h;
       
   444     writeUInt(h.signature, 0x04034b50);
       
   445     copyUShort(h.version_needed, version_needed);
       
   446     copyUShort(h.general_purpose_bits, general_purpose_bits);
       
   447     copyUShort(h.compression_method, compression_method);
       
   448     copyUInt(h.last_mod_file, last_mod_file);
       
   449     copyUInt(h.crc_32, crc_32);
       
   450     copyUInt(h.compressed_size, compressed_size);
       
   451     copyUInt(h.uncompressed_size, uncompressed_size);
       
   452     copyUShort(h.file_name_length, file_name_length);
       
   453     copyUShort(h.extra_field_length, extra_field_length);
       
   454     return h;
       
   455 }
       
   456 
       
   457 void QZipReaderPrivate::scanFiles()
       
   458 {
       
   459     if (!dirtyFileTree)
       
   460         return;
       
   461 
       
   462     if (! (device->isOpen() || device->open(QIODevice::ReadOnly))) {
       
   463         status = QZipReader::FileOpenError;
       
   464         return;
       
   465     }
       
   466 
       
   467     if ((device->openMode() & QIODevice::ReadOnly) == 0) { // only read the index from readable files.
       
   468         status = QZipReader::FileReadError;
       
   469         return;
       
   470     }
       
   471 
       
   472     dirtyFileTree = false;
       
   473     uchar tmp[4];
       
   474     device->read((char *)tmp, 4);
       
   475     if (readUInt(tmp) != 0x04034b50) {
       
   476         qWarning() << "QZip: not a zip file!";
       
   477         return;
       
   478     }
       
   479 
       
   480     // find EndOfDirectory header
       
   481     int i = 0;
       
   482     int start_of_directory = -1;
       
   483     int num_dir_entries = 0;
       
   484     EndOfDirectory eod;
       
   485     while (start_of_directory == -1) {
       
   486         int pos = device->size() - sizeof(EndOfDirectory) - i;
       
   487         if (pos < 0 || i > 65535) {
       
   488             qWarning() << "QZip: EndOfDirectory not found";
       
   489             return;
       
   490         }
       
   491 
       
   492         device->seek(pos);
       
   493         device->read((char *)&eod, sizeof(EndOfDirectory));
       
   494         if (readUInt(eod.signature) == 0x06054b50)
       
   495             break;
       
   496         ++i;
       
   497     }
       
   498 
       
   499     // have the eod
       
   500     start_of_directory = readUInt(eod.dir_start_offset);
       
   501     num_dir_entries = readUShort(eod.num_dir_entries);
       
   502     ZDEBUG("start_of_directory at %d, num_dir_entries=%d", start_of_directory, num_dir_entries);
       
   503     int comment_length = readUShort(eod.comment_length);
       
   504     if (comment_length != i)
       
   505         qWarning() << "QZip: failed to parse zip file.";
       
   506     comment = device->read(qMin(comment_length, i));
       
   507 
       
   508 
       
   509     device->seek(start_of_directory);
       
   510     for (i = 0; i < num_dir_entries; ++i) {
       
   511         FileHeader header;
       
   512         int read = device->read((char *) &header.h, sizeof(CentralFileHeader));
       
   513         if (read < (int)sizeof(CentralFileHeader)) {
       
   514             qWarning() << "QZip: Failed to read complete header, index may be incomplete";
       
   515             break;
       
   516         }
       
   517         if (readUInt(header.h.signature) != 0x02014b50) {
       
   518             qWarning() << "QZip: invalid header signature, index may be incomplete";
       
   519             break;
       
   520         }
       
   521 
       
   522         int l = readUShort(header.h.file_name_length);
       
   523         header.file_name = device->read(l);
       
   524         if (header.file_name.length() != l) {
       
   525             qWarning() << "QZip: Failed to read filename from zip index, index may be incomplete";
       
   526             break;
       
   527         }
       
   528         l = readUShort(header.h.extra_field_length);
       
   529         header.extra_field = device->read(l);
       
   530         if (header.extra_field.length() != l) {
       
   531             qWarning() << "QZip: Failed to read extra field in zip file, skipping file, index may be incomplete";
       
   532             break;
       
   533         }
       
   534         l = readUShort(header.h.file_comment_length);
       
   535         header.file_comment = device->read(l);
       
   536         if (header.file_comment.length() != l) {
       
   537             qWarning() << "QZip: Failed to read read file comment, index may be incomplete";
       
   538             break;
       
   539         }
       
   540 
       
   541         ZDEBUG("found file '%s'", header.file_name.data());
       
   542         fileHeaders.append(header);
       
   543     }
       
   544 }
       
   545 
       
   546 void QZipWriterPrivate::addEntry(EntryType type, const QString &fileName, const QByteArray &contents/*, QFile::Permissions permissions, QZip::Method m*/)
       
   547 {
       
   548 #ifndef NDEBUG
       
   549     static const char *entryTypes[] = {
       
   550         "directory",
       
   551         "file     ",
       
   552         "symlink  " };
       
   553     ZDEBUG() << "adding" << entryTypes[type] <<":" << fileName.toUtf8().data() << (type == 2 ? (" -> " + contents).constData() : "");
       
   554 #endif
       
   555 
       
   556     if (! (device->isOpen() || device->open(QIODevice::WriteOnly))) {
       
   557         status = QZipWriter::FileOpenError;
       
   558         return;
       
   559     }
       
   560     device->seek(start_of_directory);
       
   561 
       
   562     // don't compress small files
       
   563     QZipWriter::CompressionPolicy compression = compressionPolicy;
       
   564     if (compressionPolicy == QZipWriter::AutoCompress) {
       
   565         if (contents.length() < 64)
       
   566             compression = QZipWriter::NeverCompress;
       
   567         else
       
   568             compression = QZipWriter::AlwaysCompress;
       
   569     }
       
   570 
       
   571     FileHeader header;
       
   572     memset(&header.h, 0, sizeof(CentralFileHeader));
       
   573     writeUInt(header.h.signature, 0x02014b50);
       
   574 
       
   575     writeUShort(header.h.version_needed, 0x14);
       
   576     writeUInt(header.h.uncompressed_size, contents.length());
       
   577     writeMSDosDate(header.h.last_mod_file, QDateTime::currentDateTime());
       
   578     QByteArray data = contents;
       
   579     if (compression == QZipWriter::AlwaysCompress) {
       
   580         writeUShort(header.h.compression_method, 8);
       
   581 
       
   582        ulong len = contents.length();
       
   583         // shamelessly copied form zlib
       
   584         len += (len >> 12) + (len >> 14) + 11;
       
   585         int res;
       
   586         do {
       
   587             data.resize(len);
       
   588             res = deflate((uchar*)data.data(), &len, (const uchar*)contents.constData(), contents.length());
       
   589 
       
   590             switch (res) {
       
   591             case Z_OK:
       
   592                 data.resize(len);
       
   593                 break;
       
   594             case Z_MEM_ERROR:
       
   595                 qWarning("QZip: Z_MEM_ERROR: Not enough memory to compress file, skipping");
       
   596                 data.resize(0);
       
   597                 break;
       
   598             case Z_BUF_ERROR:
       
   599                 len *= 2;
       
   600                 break;
       
   601             }
       
   602         } while (res == Z_BUF_ERROR);
       
   603     }
       
   604 // TODO add a check if data.length() > contents.length().  Then try to store the original and revert the compression method to be uncompressed
       
   605     writeUInt(header.h.compressed_size, data.length());
       
   606     uint crc_32 = ::crc32(0, 0, 0);
       
   607     crc_32 = ::crc32(crc_32, (const uchar *)contents.constData(), contents.length());
       
   608     writeUInt(header.h.crc_32, crc_32);
       
   609 
       
   610     header.file_name = fileName.toLocal8Bit();
       
   611     if (header.file_name.size() > 0xffff) {
       
   612         qWarning("QZip: Filename too long, chopping it to 65535 characters");
       
   613         header.file_name = header.file_name.left(0xffff);
       
   614     }
       
   615     writeUShort(header.h.file_name_length, header.file_name.length());
       
   616     //h.extra_field_length[2];
       
   617 
       
   618     writeUShort(header.h.version_made, 3 << 8);
       
   619     //uchar internal_file_attributes[2];
       
   620     //uchar external_file_attributes[4];
       
   621     quint32 mode = permissionsToMode(permissions);
       
   622     switch (type) {
       
   623         case File: mode |= S_IFREG; break;
       
   624         case Directory: mode |= S_IFDIR; break;
       
   625         case Symlink: mode |= S_IFLNK; break;
       
   626     }
       
   627     writeUInt(header.h.external_file_attributes, mode << 16);
       
   628     writeUInt(header.h.offset_local_header, start_of_directory);
       
   629 
       
   630 
       
   631     fileHeaders.append(header);
       
   632 
       
   633     LocalFileHeader h = header.h.toLocalHeader();
       
   634     device->write((const char *)&h, sizeof(LocalFileHeader));
       
   635     device->write(header.file_name);
       
   636     device->write(data);
       
   637     start_of_directory = device->pos();
       
   638     dirtyFileTree = true;
       
   639 }
       
   640 
       
   641 //////////////////////////////  Reader
       
   642 
       
   643 /*!
       
   644     \class QZipReader::FileInfo
       
   645     \internal
       
   646     Represents one entry in the zip table of contents.
       
   647 */
       
   648 
       
   649 /*!
       
   650     \variable FileInfo::filePath
       
   651     The full filepath inside the archive.
       
   652 */
       
   653 
       
   654 /*!
       
   655     \variable FileInfo::isDir
       
   656     A boolean type indicating if the entry is a directory.
       
   657 */
       
   658 
       
   659 /*!
       
   660     \variable FileInfo::isFile
       
   661     A boolean type, if it is one this entry is a file.
       
   662 */
       
   663 
       
   664 /*!
       
   665     \variable FileInfo::isSymLink
       
   666     A boolean type, if it is one this entry is symbolic link.
       
   667 */
       
   668 
       
   669 /*!
       
   670     \variable FileInfo::permissions
       
   671     A list of flags for the permissions of this entry.
       
   672 */
       
   673 
       
   674 /*!
       
   675     \variable FileInfo::crc32
       
   676     The calculated checksum as a crc32 type.
       
   677 */
       
   678 
       
   679 /*!
       
   680     \variable FileInfo::size
       
   681     The total size of the unpacked content.
       
   682 */
       
   683 
       
   684 /*!
       
   685     \variable FileInfo::d
       
   686     \internal
       
   687     private pointer.
       
   688 */
       
   689 
       
   690 /*!
       
   691     \class QZipReader
       
   692     \internal
       
   693     \since 4.5
       
   694 
       
   695     \brief the QZipReader class provides a way to inspect the contents of a zip
       
   696     archive and extract individual files from it.
       
   697 
       
   698     QZipReader can be used to read a zip archive either from a file or from any
       
   699     device. An in-memory QBuffer for instance.  The reader can be used to read
       
   700     which files are in the archive using fileInfoList() and entryInfoAt() but
       
   701     also to extract individual files using fileData() or even to extract all
       
   702     files in the archive using extractAll()
       
   703 */
       
   704 
       
   705 /*!
       
   706     Create a new zip archive that operates on the \a fileName.  The file will be
       
   707     opened with the \a mode.
       
   708 */
       
   709 QZipReader::QZipReader(const QString &archive, QIODevice::OpenMode mode)
       
   710 {
       
   711     QScopedPointer<QFile> f(new QFile(archive));
       
   712     f->open(mode);
       
   713     QZipReader::Status status;
       
   714     if (f->error() == QFile::NoError)
       
   715         status = NoError;
       
   716     else {
       
   717         if (f->error() == QFile::ReadError)
       
   718             status = FileReadError;
       
   719         else if (f->error() == QFile::OpenError)
       
   720             status = FileOpenError;
       
   721         else if (f->error() == QFile::PermissionsError)
       
   722             status = FilePermissionsError;
       
   723         else
       
   724             status = FileError;
       
   725     }
       
   726 
       
   727     d = new QZipReaderPrivate(f.data(), /*ownDevice=*/true);
       
   728     f.take();
       
   729     d->status = status;
       
   730 }
       
   731 
       
   732 /*!
       
   733     Create a new zip archive that operates on the archive found in \a device.
       
   734     You have to open the device previous to calling the constructor and only a
       
   735     device that is readable will be scanned for zip filecontent.
       
   736  */
       
   737 QZipReader::QZipReader(QIODevice *device)
       
   738     : d(new QZipReaderPrivate(device, /*ownDevice=*/false))
       
   739 {
       
   740     Q_ASSERT(device);
       
   741 }
       
   742 
       
   743 /*!
       
   744     Desctructor
       
   745 */
       
   746 QZipReader::~QZipReader()
       
   747 {
       
   748     close();
       
   749     delete d;
       
   750 }
       
   751 
       
   752 /*!
       
   753     Returns true if the user can read the file; otherwise returns false.
       
   754 */
       
   755 bool QZipReader::isReadable() const
       
   756 {
       
   757     return d->device->isReadable();
       
   758 }
       
   759 
       
   760 /*!
       
   761     Returns true if the file exists; otherwise returns false.
       
   762 */
       
   763 bool QZipReader::exists() const
       
   764 {
       
   765     QFile *f = qobject_cast<QFile*> (d->device);
       
   766     if (f == 0)
       
   767         return true;
       
   768     return f->exists();
       
   769 }
       
   770 
       
   771 /*!
       
   772     Returns the list of files the archive contains.
       
   773 */
       
   774 QList<QZipReader::FileInfo> QZipReader::fileInfoList() const
       
   775 {
       
   776     d->scanFiles();
       
   777     QList<QZipReader::FileInfo> files;
       
   778     for (int i = 0; i < d->fileHeaders.size(); ++i) {
       
   779         QZipReader::FileInfo fi;
       
   780         d->fillFileInfo(i, fi);
       
   781         files.append(fi);
       
   782     }
       
   783     return files;
       
   784 
       
   785 }
       
   786 
       
   787 /*!
       
   788     Return the number of items in the zip archive.
       
   789 */
       
   790 int QZipReader::count() const
       
   791 {
       
   792     d->scanFiles();
       
   793     return d->fileHeaders.count();
       
   794 }
       
   795 
       
   796 /*!
       
   797     Returns a FileInfo of an entry in the zipfile.
       
   798     The \a index is the index into the directoy listing of the zipfile.
       
   799 
       
   800     \sa fileInfoList()
       
   801 */
       
   802 QZipReader::FileInfo QZipReader::entryInfoAt(int index) const
       
   803 {
       
   804     d->scanFiles();
       
   805     QZipReader::FileInfo fi;
       
   806     d->fillFileInfo(index, fi);
       
   807     return fi;
       
   808 }
       
   809 
       
   810 /*!
       
   811     Fetch the file contents from the zip archive and return the uncompressed bytes.
       
   812 */
       
   813 QByteArray QZipReader::fileData(const QString &fileName) const
       
   814 {
       
   815     d->scanFiles();
       
   816     int i;
       
   817     for (i = 0; i < d->fileHeaders.size(); ++i) {
       
   818         if (QString::fromLocal8Bit(d->fileHeaders.at(i).file_name) == fileName)
       
   819             break;
       
   820     }
       
   821     if (i == d->fileHeaders.size())
       
   822         return QByteArray();
       
   823 
       
   824     FileHeader header = d->fileHeaders.at(i);
       
   825 
       
   826     int compressed_size = readUInt(header.h.compressed_size);
       
   827     int uncompressed_size = readUInt(header.h.uncompressed_size);
       
   828     int start = readUInt(header.h.offset_local_header);
       
   829     //qDebug("uncompressing file %d: local header at %d", i, start);
       
   830 
       
   831     d->device->seek(start);
       
   832     LocalFileHeader lh;
       
   833     d->device->read((char *)&lh, sizeof(LocalFileHeader));
       
   834     uint skip = readUShort(lh.file_name_length) + readUShort(lh.extra_field_length);
       
   835     d->device->seek(d->device->pos() + skip);
       
   836 
       
   837     int compression_method = readUShort(lh.compression_method);
       
   838     //qDebug("file=%s: compressed_size=%d, uncompressed_size=%d", fileName.toLocal8Bit().data(), compressed_size, uncompressed_size);
       
   839 
       
   840     //qDebug("file at %lld", d->device->pos());
       
   841     QByteArray compressed = d->device->read(compressed_size);
       
   842     if (compression_method == 0) {
       
   843         // no compression
       
   844         compressed.truncate(uncompressed_size);
       
   845         return compressed;
       
   846     } else if (compression_method == 8) {
       
   847         // Deflate
       
   848         //qDebug("compressed=%d", compressed.size());
       
   849         compressed.truncate(compressed_size);
       
   850         QByteArray baunzip;
       
   851         ulong len = qMax(uncompressed_size,  1);
       
   852         int res;
       
   853         do {
       
   854             baunzip.resize(len);
       
   855             res = inflate((uchar*)baunzip.data(), &len,
       
   856                           (uchar*)compressed.constData(), compressed_size);
       
   857 
       
   858             switch (res) {
       
   859             case Z_OK:
       
   860                 if ((int)len != baunzip.size())
       
   861                     baunzip.resize(len);
       
   862                 break;
       
   863             case Z_MEM_ERROR:
       
   864                 qWarning("QZip: Z_MEM_ERROR: Not enough memory");
       
   865                 break;
       
   866             case Z_BUF_ERROR:
       
   867                 len *= 2;
       
   868                 break;
       
   869             case Z_DATA_ERROR:
       
   870                 qWarning("QZip: Z_DATA_ERROR: Input data is corrupted");
       
   871                 break;
       
   872             }
       
   873         } while (res == Z_BUF_ERROR);
       
   874         return baunzip;
       
   875     }
       
   876     qWarning() << "QZip: Unknown compression method";
       
   877     return QByteArray();
       
   878 }
       
   879 
       
   880 /*!
       
   881     Extracts the full contents of the zip file into \a destinationDir on
       
   882     the local filesystem.
       
   883     In case writing or linking a file fails, the extraction will be aborted.
       
   884 */
       
   885 bool QZipReader::extractAll(const QString &destinationDir) const
       
   886 {
       
   887     QDir baseDir(destinationDir);
       
   888 
       
   889     // create directories first
       
   890     QList<FileInfo> allFiles = fileInfoList();
       
   891     foreach (FileInfo fi, allFiles) {
       
   892         const QString absPath = destinationDir + QDir::separator() + fi.filePath;
       
   893         if (fi.isDir) {
       
   894             if (!baseDir.mkpath(fi.filePath))
       
   895                 return false;
       
   896             if (!QFile::setPermissions(absPath, fi.permissions))
       
   897                 return false;
       
   898         }
       
   899     }
       
   900 
       
   901     // set up symlinks
       
   902     foreach (FileInfo fi, allFiles) {
       
   903         const QString absPath = destinationDir + QDir::separator() + fi.filePath;
       
   904         if (fi.isSymLink) {
       
   905             QString destination = QFile::decodeName(fileData(fi.filePath));
       
   906             if (destination.isEmpty())
       
   907                 return false;
       
   908             QFileInfo linkFi(absPath);
       
   909             if (!QFile::exists(linkFi.absolutePath()))
       
   910                 QDir::root().mkpath(linkFi.absolutePath());
       
   911             if (!QFile::link(destination, absPath))
       
   912                 return false;
       
   913             /* cannot change permission of links
       
   914             if (!QFile::setPermissions(absPath, fi.permissions))
       
   915                 return false;
       
   916             */
       
   917         }
       
   918     }
       
   919 
       
   920     foreach (FileInfo fi, allFiles) {
       
   921         const QString absPath = destinationDir + QDir::separator() + fi.filePath;
       
   922         if (fi.isFile) {
       
   923             QFile f(absPath);
       
   924             if (!f.open(QIODevice::WriteOnly))
       
   925                 return false;
       
   926             f.write(fileData(fi.filePath));
       
   927             f.setPermissions(fi.permissions);
       
   928             f.close();
       
   929         }
       
   930     }
       
   931 
       
   932     return true;
       
   933 }
       
   934 
       
   935 /*!
       
   936     \enum QZipReader::Status
       
   937 
       
   938     The following status values are possible:
       
   939 
       
   940     \value NoError  No error occurred.
       
   941     \value FileReadError    An error occurred when reading from the file.
       
   942     \value FileOpenError    The file could not be opened.
       
   943     \value FilePermissionsError The file could not be accessed.
       
   944     \value FileError        Another file error occurred.
       
   945 */
       
   946 
       
   947 /*!
       
   948     Returns a status code indicating the first error that was met by QZipReader,
       
   949     or QZipReader::NoError if no error occurred.
       
   950 */
       
   951 QZipReader::Status QZipReader::status() const
       
   952 {
       
   953     return d->status;
       
   954 }
       
   955 
       
   956 /*!
       
   957     Close the zip file.
       
   958 */
       
   959 void QZipReader::close()
       
   960 {
       
   961     d->device->close();
       
   962 }
       
   963 
       
   964 ////////////////////////////// Writer
       
   965 
       
   966 /*!
       
   967     \class QZipWriter
       
   968     \internal
       
   969     \since 4.5
       
   970 
       
   971     \brief the QZipWriter class provides a way to create a new zip archive.
       
   972 
       
   973     QZipWriter can be used to create a zip archive containing any number of files
       
   974     and directories. The files in the archive will be compressed in a way that is
       
   975     compatible with common zip reader applications.
       
   976 */
       
   977 
       
   978 
       
   979 /*!
       
   980     Create a new zip archive that operates on the \a archive filename.  The file will
       
   981     be opened with the \a mode.
       
   982     \sa isValid()
       
   983 */
       
   984 QZipWriter::QZipWriter(const QString &fileName, QIODevice::OpenMode mode)
       
   985 {
       
   986     QScopedPointer<QFile> f(new QFile(fileName));
       
   987     f->open(mode);
       
   988     QZipWriter::Status status;
       
   989     if (f->error() == QFile::NoError)
       
   990         status = QZipWriter::NoError;
       
   991     else {
       
   992         if (f->error() == QFile::WriteError)
       
   993             status = QZipWriter::FileWriteError;
       
   994         else if (f->error() == QFile::OpenError)
       
   995             status = QZipWriter::FileOpenError;
       
   996         else if (f->error() == QFile::PermissionsError)
       
   997             status = QZipWriter::FilePermissionsError;
       
   998         else
       
   999             status = QZipWriter::FileError;
       
  1000     }
       
  1001 
       
  1002     d = new QZipWriterPrivate(f.data(), /*ownDevice=*/true);
       
  1003     f.take();
       
  1004     d->status = status;
       
  1005 }
       
  1006 
       
  1007 /*!
       
  1008     Create a new zip archive that operates on the archive found in \a device.
       
  1009     You have to open the device previous to calling the constructor and
       
  1010     only a device that is readable will be scanned for zip filecontent.
       
  1011  */
       
  1012 QZipWriter::QZipWriter(QIODevice *device)
       
  1013     : d(new QZipWriterPrivate(device, /*ownDevice=*/false))
       
  1014 {
       
  1015     Q_ASSERT(device);
       
  1016 }
       
  1017 
       
  1018 QZipWriter::~QZipWriter()
       
  1019 {
       
  1020     close();
       
  1021     delete d;
       
  1022 }
       
  1023 
       
  1024 /*!
       
  1025     Returns true if the user can write to the archive; otherwise returns false.
       
  1026 */
       
  1027 bool QZipWriter::isWritable() const
       
  1028 {
       
  1029     return d->device->isWritable();
       
  1030 }
       
  1031 
       
  1032 /*!
       
  1033     Returns true if the file exists; otherwise returns false.
       
  1034 */
       
  1035 bool QZipWriter::exists() const
       
  1036 {
       
  1037     QFile *f = qobject_cast<QFile*> (d->device);
       
  1038     if (f == 0)
       
  1039         return true;
       
  1040     return f->exists();
       
  1041 }
       
  1042 
       
  1043 /*!
       
  1044     \enum QZipWriter::Status
       
  1045 
       
  1046     The following status values are possible:
       
  1047 
       
  1048     \value NoError  No error occurred.
       
  1049     \value FileWriteError    An error occurred when writing to the device.
       
  1050     \value FileOpenError    The file could not be opened.
       
  1051     \value FilePermissionsError The file could not be accessed.
       
  1052     \value FileError        Another file error occurred.
       
  1053 */
       
  1054 
       
  1055 /*!
       
  1056     Returns a status code indicating the first error that was met by QZipWriter,
       
  1057     or QZipWriter::NoError if no error occurred.
       
  1058 */
       
  1059 QZipWriter::Status QZipWriter::status() const
       
  1060 {
       
  1061     return d->status;
       
  1062 }
       
  1063 
       
  1064 /*!
       
  1065     \enum QZipWriter::CompressionPolicy
       
  1066 
       
  1067     \value AlwaysCompress   A file that is added is compressed.
       
  1068     \value NeverCompress    A file that is added will be stored without changes.
       
  1069     \value AutoCompress     A file that is added will be compressed only if that will give a smaller file.
       
  1070 */
       
  1071 
       
  1072 /*!
       
  1073      Sets the policy for compressing newly added files to the new \a policy.
       
  1074 
       
  1075     \note the default policy is AlwaysCompress
       
  1076 
       
  1077     \sa compressionPolicy()
       
  1078     \sa addFile()
       
  1079 */
       
  1080 void QZipWriter::setCompressionPolicy(CompressionPolicy policy)
       
  1081 {
       
  1082     d->compressionPolicy = policy;
       
  1083 }
       
  1084 
       
  1085 /*!
       
  1086      Returns the currently set compression policy.
       
  1087     \sa setCompressionPolicy()
       
  1088     \sa addFile()
       
  1089 */
       
  1090 QZipWriter::CompressionPolicy QZipWriter::compressionPolicy() const
       
  1091 {
       
  1092     return d->compressionPolicy;
       
  1093 }
       
  1094 
       
  1095 /*!
       
  1096     Sets the permissions that will be used for newly added files.
       
  1097 
       
  1098     \note the default permissions are QFile::ReadOwner | QFile::WriteOwner.
       
  1099 
       
  1100     \sa creationPermissions()
       
  1101     \sa addFile()
       
  1102 */
       
  1103 void QZipWriter::setCreationPermissions(QFile::Permissions permissions)
       
  1104 {
       
  1105     d->permissions = permissions;
       
  1106 }
       
  1107 
       
  1108 /*!
       
  1109      Returns the currently set creation permissions.
       
  1110 
       
  1111     \sa setCreationPermissions()
       
  1112     \sa addFile()
       
  1113 */
       
  1114 QFile::Permissions QZipWriter::creationPermissions() const
       
  1115 {
       
  1116     return d->permissions;
       
  1117 }
       
  1118 
       
  1119 /*!
       
  1120     Add a file to the archive with \a data as the file contents.
       
  1121     The file will be stored in the archive using the \a fileName which
       
  1122     includes the full path in the archive.
       
  1123 
       
  1124     The new file will get the file permissions based on the current
       
  1125     creationPermissions and it will be compressed using the zip compression
       
  1126     based on the current compression policy.
       
  1127 
       
  1128     \sa setCreationPermissions()
       
  1129     \sa setCompressionPolicy()
       
  1130 */
       
  1131 void QZipWriter::addFile(const QString &fileName, const QByteArray &data)
       
  1132 {
       
  1133     d->addEntry(QZipWriterPrivate::File, fileName, data);
       
  1134 }
       
  1135 
       
  1136 /*!
       
  1137     Add a file to the archive with \a device as the source of the contents.
       
  1138     The contents returned from QIODevice::readAll() will be used as the
       
  1139     filedata.
       
  1140     The file will be stored in the archive using the \a fileName which
       
  1141     includes the full path in the archive.
       
  1142 */
       
  1143 void QZipWriter::addFile(const QString &fileName, QIODevice *device)
       
  1144 {
       
  1145     Q_ASSERT(device);
       
  1146     QIODevice::OpenMode mode = device->openMode();
       
  1147     bool opened = false;
       
  1148     if ((mode & QIODevice::ReadOnly) == 0) {
       
  1149         opened = true;
       
  1150         if (! device->open(QIODevice::ReadOnly)) {
       
  1151             d->status = FileOpenError;
       
  1152             return;
       
  1153         }
       
  1154     }
       
  1155     d->addEntry(QZipWriterPrivate::File, fileName, device->readAll());
       
  1156     if (opened)
       
  1157         device->close();
       
  1158 }
       
  1159 
       
  1160 /*!
       
  1161     Create a new directory in the archive with the specified \a dirName and
       
  1162     the \a permissions;
       
  1163 */
       
  1164 void QZipWriter::addDirectory(const QString &dirName)
       
  1165 {
       
  1166     QString name = dirName;
       
  1167     // separator is mandatory
       
  1168     if (!name.endsWith(QDir::separator()))
       
  1169         name.append(QDir::separator());
       
  1170     d->addEntry(QZipWriterPrivate::Directory, name, QByteArray());
       
  1171 }
       
  1172 
       
  1173 /*!
       
  1174     Create a new symbolic link in the archive with the specified \a dirName
       
  1175     and the \a permissions;
       
  1176     A symbolic link contains the destination (relative) path and name.
       
  1177 */
       
  1178 void QZipWriter::addSymLink(const QString &fileName, const QString &destination)
       
  1179 {
       
  1180     d->addEntry(QZipWriterPrivate::Symlink, fileName, QFile::encodeName(destination));
       
  1181 }
       
  1182 
       
  1183 /*!
       
  1184    Closes the zip file.
       
  1185 */
       
  1186 void QZipWriter::close()
       
  1187 {
       
  1188     if (!(d->device->openMode() & QIODevice::WriteOnly)) {
       
  1189         d->device->close();
       
  1190         return;
       
  1191     }
       
  1192 
       
  1193     //qDebug("QZip::close writing directory, %d entries", d->fileHeaders.size());
       
  1194     d->device->seek(d->start_of_directory);
       
  1195     // write new directory
       
  1196     for (int i = 0; i < d->fileHeaders.size(); ++i) {
       
  1197         const FileHeader &header = d->fileHeaders.at(i);
       
  1198         d->device->write((const char *)&header.h, sizeof(CentralFileHeader));
       
  1199         d->device->write(header.file_name);
       
  1200         d->device->write(header.extra_field);
       
  1201         d->device->write(header.file_comment);
       
  1202     }
       
  1203     int dir_size = d->device->pos() - d->start_of_directory;
       
  1204     // write end of directory
       
  1205     EndOfDirectory eod;
       
  1206     memset(&eod, 0, sizeof(EndOfDirectory));
       
  1207     writeUInt(eod.signature, 0x06054b50);
       
  1208     //uchar this_disk[2];
       
  1209     //uchar start_of_directory_disk[2];
       
  1210     writeUShort(eod.num_dir_entries_this_disk, d->fileHeaders.size());
       
  1211     writeUShort(eod.num_dir_entries, d->fileHeaders.size());
       
  1212     writeUInt(eod.directory_size, dir_size);
       
  1213     writeUInt(eod.dir_start_offset, d->start_of_directory);
       
  1214     writeUShort(eod.comment_length, d->comment.length());
       
  1215 
       
  1216     d->device->write((const char *)&eod, sizeof(EndOfDirectory));
       
  1217     d->device->write(d->comment);
       
  1218     d->device->close();
       
  1219 }
       
  1220 
       
  1221 QT_END_NAMESPACE
       
  1222 
       
  1223 #endif // QT_NO_TEXTODFWRITER