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