1
/****************************************************************************
2
**
3
** Copyright (C) 2012 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
** GNU Lesser General Public License Usage
11
** This file may be used under the terms of the GNU Lesser General Public
12
** License version 2.1 as published by the Free Software Foundation and
13
** appearing in the file LICENSE.LGPL included in the packaging of this
14
** file. Please review the following information to ensure the GNU Lesser
15
** General Public License version 2.1 requirements will be met:
16
** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
17
**
18
** In addition, as a special exception, Nokia gives you certain additional
19
** rights. These rights are described in the Nokia Qt LGPL Exception
20
** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
21
**
22
** GNU General Public License Usage
23
** Alternatively, this file may be used under the terms of the GNU General
24
** Public License version 3.0 as published by the Free Software Foundation
25
** and appearing in the file LICENSE.GPL included in the packaging of this
26
** file. Please review the following information to ensure the GNU General
27
** Public License version 3.0 requirements will be met:
28
** http://www.gnu.org/copyleft/gpl.html.
29
**
30
** Other Usage
31
** Alternatively, this file may be used in accordance with the terms and
32
** conditions contained in a signed written agreement between you and Nokia.
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
static QDateTime readMSDosDate(const uchar *src)
284
{
285
    uint dosDate = readUInt(src);
286
    quint64 uDate;
287
    uDate = (quint64)(dosDate >> 16);
288
    uint tm_mday = (uDate & 0x1f);
289
    uint tm_mon =  ((uDate & 0x1E0) >> 5);
290
    uint tm_year = (((uDate & 0x0FE00) >> 9) + 1980);
291
    uint tm_hour = ((dosDate & 0xF800) >> 11);
292
    uint tm_min =  ((dosDate & 0x7E0) >> 5);
293
    uint tm_sec =  ((dosDate & 0x1f) << 1);
294
295
    return QDateTime(QDate(tm_year, tm_mon, tm_mday), QTime(tm_hour, tm_min, tm_sec));
296
}
297
298
struct LocalFileHeader
299
{
300
    uchar signature[4]; //  0x04034b50
301
    uchar version_needed[2];
302
    uchar general_purpose_bits[2];
303
    uchar compression_method[2];
304
    uchar last_mod_file[4];
305
    uchar crc_32[4];
306
    uchar compressed_size[4];
307
    uchar uncompressed_size[4];
308
    uchar file_name_length[2];
309
    uchar extra_field_length[2];
310
};
311
312
struct DataDescriptor
313
{
314
    uchar crc_32[4];
315
    uchar compressed_size[4];
316
    uchar uncompressed_size[4];
317
};
318
319
struct CentralFileHeader
320
{
321
    uchar signature[4]; // 0x02014b50
322
    uchar version_made[2];
323
    uchar version_needed[2];
324
    uchar general_purpose_bits[2];
325
    uchar compression_method[2];
326
    uchar last_mod_file[4];
327
    uchar crc_32[4];
328
    uchar compressed_size[4];
329
    uchar uncompressed_size[4];
330
    uchar file_name_length[2];
331
    uchar extra_field_length[2];
332
    uchar file_comment_length[2];
333
    uchar disk_start[2];
334
    uchar internal_file_attributes[2];
335
    uchar external_file_attributes[4];
336
    uchar offset_local_header[4];
337
    LocalFileHeader toLocalHeader() const;
338
};
339
340
struct EndOfDirectory
341
{
342
    uchar signature[4]; // 0x06054b50
343
    uchar this_disk[2];
344
    uchar start_of_directory_disk[2];
345
    uchar num_dir_entries_this_disk[2];
346
    uchar num_dir_entries[2];
347
    uchar directory_size[4];
348
    uchar dir_start_offset[4];
349
    uchar comment_length[2];
350
};
351
352
struct FileHeader
353
{
354
    CentralFileHeader h;
355
    QByteArray file_name;
356
    QByteArray extra_field;
357
    QByteArray file_comment;
358
};
359
360
QZipReader::FileInfo::FileInfo()
361
    : isDir(false), isFile(false), isSymLink(false), crc32(0), size(0)
362
{
363
}
364
365
QZipReader::FileInfo::~FileInfo()
366
{
367
}
368
369
QZipReader::FileInfo::FileInfo(const FileInfo &other)
370
{
371
    operator=(other);
372
}
373
374
QZipReader::FileInfo& QZipReader::FileInfo::operator=(const FileInfo &other)
375
{
376
    filePath = other.filePath;
377
    isDir = other.isDir;
378
    isFile = other.isFile;
379
    isSymLink = other.isSymLink;
380
    permissions = other.permissions;
381
    crc32 = other.crc32;
382
    size = other.size;
383
    lastModified = other.lastModified;
384
    return *this;
385
}
386
387
bool QZipReader::FileInfo::isValid() const
388
{
389
    return isDir || isFile || isSymLink;
390
}
391
392
class QZipPrivate
393
{
394
public:
395
    QZipPrivate(QIODevice *device, bool ownDev)
396
        : device(device), ownDevice(ownDev), dirtyFileTree(true), start_of_directory(0)
397
    {
398
    }
399
400
    ~QZipPrivate()
401
    {
402
        if (ownDevice)
403
            delete device;
404
    }
405
406
    void fillFileInfo(int index, QZipReader::FileInfo &fileInfo) const;
407
408
    QIODevice *device;
409
    bool ownDevice;
410
    bool dirtyFileTree;
411
    QList<FileHeader> fileHeaders;
412
    QByteArray comment;
413
    uint start_of_directory;
414
};
415
416
void QZipPrivate::fillFileInfo(int index, QZipReader::FileInfo &fileInfo) const
417
{
418
    FileHeader header = fileHeaders.at(index);
419
    fileInfo.filePath = QString::fromLocal8Bit(header.file_name);
420
    const quint32 mode = (qFromLittleEndian<quint32>(&header.h.external_file_attributes[0]) >> 16) & 0xFFFF;
421
    fileInfo.isDir = S_ISDIR(mode);
422
    fileInfo.isFile = S_ISREG(mode);
423
    fileInfo.isSymLink = S_ISLNK(mode);
424
    fileInfo.permissions = modeToPermissions(mode);
425
    fileInfo.crc32 = readUInt(header.h.crc_32);
426
    fileInfo.size = readUInt(header.h.uncompressed_size);
427
    fileInfo.lastModified = readMSDosDate(header.h.last_mod_file);
428
}
429
430
class QZipReaderPrivate : public QZipPrivate
431
{
432
public:
433
    QZipReaderPrivate(QIODevice *device, bool ownDev)
434
        : QZipPrivate(device, ownDev), status(QZipReader::NoError)
435
    {
436
    }
437
438
    void scanFiles();
439
440
    QZipReader::Status status;
441
};
442
443
class QZipWriterPrivate : public QZipPrivate
444
{
445
public:
446
    QZipWriterPrivate(QIODevice *device, bool ownDev)
447
        : QZipPrivate(device, ownDev),
448
        status(QZipWriter::NoError),
449
        permissions(QFile::ReadOwner | QFile::WriteOwner),
450
        compressionPolicy(QZipWriter::AlwaysCompress)
451
    {
452
    }
453
454
    QZipWriter::Status status;
455
    QFile::Permissions permissions;
456
    QZipWriter::CompressionPolicy compressionPolicy;
457
458
    enum EntryType { Directory, File, Symlink };
459
460
    void addEntry(EntryType type, const QString &fileName, const QByteArray &contents);
461
};
462
463
LocalFileHeader CentralFileHeader::toLocalHeader() const
464
{
465
    LocalFileHeader h;
466
    writeUInt(h.signature, 0x04034b50);
467
    copyUShort(h.version_needed, version_needed);
468
    copyUShort(h.general_purpose_bits, general_purpose_bits);
469
    copyUShort(h.compression_method, compression_method);
470
    copyUInt(h.last_mod_file, last_mod_file);
471
    copyUInt(h.crc_32, crc_32);
472
    copyUInt(h.compressed_size, compressed_size);
473
    copyUInt(h.uncompressed_size, uncompressed_size);
474
    copyUShort(h.file_name_length, file_name_length);
475
    copyUShort(h.extra_field_length, extra_field_length);
476
    return h;
477
}
478
479
void QZipReaderPrivate::scanFiles()
480
{
481
    if (!dirtyFileTree)
482
        return;
483
484
    if (! (device->isOpen() || device->open(QIODevice::ReadOnly))) {
485
        status = QZipReader::FileOpenError;
486
        return;
487
    }
488
489
    if ((device->openMode() & QIODevice::ReadOnly) == 0) { // only read the index from readable files.
490
        status = QZipReader::FileReadError;
491
        return;
492
    }
493
494
    dirtyFileTree = false;
495
    uchar tmp[4];
496
    device->read((char *)tmp, 4);
497
    if (readUInt(tmp) != 0x04034b50) {
498
        qWarning() << "QZip: not a zip file!";
499
        return;
500
    }
501
502
    // find EndOfDirectory header
503
    int i = 0;
504
    int start_of_directory = -1;
505
    int num_dir_entries = 0;
506
    EndOfDirectory eod;
507
    while (start_of_directory == -1) {
508
        int pos = device->size() - sizeof(EndOfDirectory) - i;
509
        if (pos < 0 || i > 65535) {
510
            qWarning() << "QZip: EndOfDirectory not found";
511
            return;
512
        }
513
514
        device->seek(pos);
515
        device->read((char *)&eod, sizeof(EndOfDirectory));
516
        if (readUInt(eod.signature) == 0x06054b50)
517
            break;
518
        ++i;
519
    }
520
521
    // have the eod
522
    start_of_directory = readUInt(eod.dir_start_offset);
523
    num_dir_entries = readUShort(eod.num_dir_entries);
524
    ZDEBUG("start_of_directory at %d, num_dir_entries=%d", start_of_directory, num_dir_entries);
525
    int comment_length = readUShort(eod.comment_length);
526
    if (comment_length != i)
527
        qWarning() << "QZip: failed to parse zip file.";
528
    comment = device->read(qMin(comment_length, i));
529
530
531
    device->seek(start_of_directory);
532
    for (i = 0; i < num_dir_entries; ++i) {
533
        FileHeader header;
534
        int read = device->read((char *) &header.h, sizeof(CentralFileHeader));
535
        if (read < (int)sizeof(CentralFileHeader)) {
536
            qWarning() << "QZip: Failed to read complete header, index may be incomplete";
537
            break;
538
        }
539
        if (readUInt(header.h.signature) != 0x02014b50) {
540
            qWarning() << "QZip: invalid header signature, index may be incomplete";
541
            break;
542
        }
543
544
        int l = readUShort(header.h.file_name_length);
545
        header.file_name = device->read(l);
546
        if (header.file_name.length() != l) {
547
            qWarning() << "QZip: Failed to read filename from zip index, index may be incomplete";
548
            break;
549
        }
550
        l = readUShort(header.h.extra_field_length);
551
        header.extra_field = device->read(l);
552
        if (header.extra_field.length() != l) {
553
            qWarning() << "QZip: Failed to read extra field in zip file, skipping file, index may be incomplete";
554
            break;
555
        }
556
        l = readUShort(header.h.file_comment_length);
557
        header.file_comment = device->read(l);
558
        if (header.file_comment.length() != l) {
559
            qWarning() << "QZip: Failed to read read file comment, index may be incomplete";
560
            break;
561
        }
562
563
        ZDEBUG("found file '%s'", header.file_name.data());
564
        fileHeaders.append(header);
565
    }
566
}
567
568
void QZipWriterPrivate::addEntry(EntryType type, const QString &fileName, const QByteArray &contents/*, QFile::Permissions permissions, QZip::Method m*/)
569
{
570
#ifndef NDEBUG
571
    static const char *entryTypes[] = {
572
        "directory",
573
        "file     ",
574
        "symlink  " };
575
    ZDEBUG() << "adding" << entryTypes[type] <<":" << fileName.toUtf8().data() << (type == 2 ? QByteArray(" -> " + contents).constData() : "");
576
#endif
577
578
    if (! (device->isOpen() || device->open(QIODevice::WriteOnly))) {
579
        status = QZipWriter::FileOpenError;
580
        return;
581
    }
582
    device->seek(start_of_directory);
583
584
    // don't compress small files
585
    QZipWriter::CompressionPolicy compression = compressionPolicy;
586
    if (compressionPolicy == QZipWriter::AutoCompress) {
587
        if (contents.length() < 64)
588
            compression = QZipWriter::NeverCompress;
589
        else
590
            compression = QZipWriter::AlwaysCompress;
591
    }
592
593
    FileHeader header;
594
    memset(&header.h, 0, sizeof(CentralFileHeader));
595
    writeUInt(header.h.signature, 0x02014b50);
596
597
    writeUShort(header.h.version_needed, 0x14);
598
    writeUInt(header.h.uncompressed_size, contents.length());
599
    writeMSDosDate(header.h.last_mod_file, QDateTime::currentDateTime());
600
    QByteArray data = contents;
601
    if (compression == QZipWriter::AlwaysCompress) {
602
        writeUShort(header.h.compression_method, 8);
603
604
       ulong len = contents.length();
605
        // shamelessly copied form zlib
606
        len += (len >> 12) + (len >> 14) + 11;
607
        int res;
608
        do {
609
            data.resize(len);
610
            res = deflate((uchar*)data.data(), &len, (const uchar*)contents.constData(), contents.length());
611
612
            switch (res) {
613
            case Z_OK:
614
                data.resize(len);
615
                break;
616
            case Z_MEM_ERROR:
617
                qWarning("QZip: Z_MEM_ERROR: Not enough memory to compress file, skipping");
618
                data.resize(0);
619
                break;
620
            case Z_BUF_ERROR:
621
                len *= 2;
622
                break;
623
            }
624
        } while (res == Z_BUF_ERROR);
625
    }
626
// TODO add a check if data.length() > contents.length().  Then try to store the original and revert the compression method to be uncompressed
627
    writeUInt(header.h.compressed_size, data.length());
628
    uint crc_32 = ::crc32(0, 0, 0);
629
    crc_32 = ::crc32(crc_32, (const uchar *)contents.constData(), contents.length());
630
    writeUInt(header.h.crc_32, crc_32);
631
632
    header.file_name = fileName.toLocal8Bit();
633
    if (header.file_name.size() > 0xffff) {
634
        qWarning("QZip: Filename too long, chopping it to 65535 characters");
635
        header.file_name = header.file_name.left(0xffff);
636
    }
637
    writeUShort(header.h.file_name_length, header.file_name.length());
638
    //h.extra_field_length[2];
639
640
    writeUShort(header.h.version_made, 3 << 8);
641
    //uchar internal_file_attributes[2];
642
    //uchar external_file_attributes[4];
643
    quint32 mode = permissionsToMode(permissions);
644
    switch (type) {
645
        case File: mode |= S_IFREG; break;
646
        case Directory: mode |= S_IFDIR; break;
647
        case Symlink: mode |= S_IFLNK; break;
648
    }
649
    writeUInt(header.h.external_file_attributes, mode << 16);
650
    writeUInt(header.h.offset_local_header, start_of_directory);
651
652
653
    fileHeaders.append(header);
654
655
    LocalFileHeader h = header.h.toLocalHeader();
656
    device->write((const char *)&h, sizeof(LocalFileHeader));
657
    device->write(header.file_name);
658
    device->write(data);
659
    start_of_directory = device->pos();
660
    dirtyFileTree = true;
661
}
662
663
//////////////////////////////  Reader
664
665
/*!
666
    \class QZipReader::FileInfo
667
    \internal
668
    Represents one entry in the zip table of contents.
669
*/
670
671
/*!
672
    \variable FileInfo::filePath
673
    The full filepath inside the archive.
674
*/
675
676
/*!
677
    \variable FileInfo::isDir
678
    A boolean type indicating if the entry is a directory.
679
*/
680
681
/*!
682
    \variable FileInfo::isFile
683
    A boolean type, if it is one this entry is a file.
684
*/
685
686
/*!
687
    \variable FileInfo::isSymLink
688
    A boolean type, if it is one this entry is symbolic link.
689
*/
690
691
/*!
692
    \variable FileInfo::permissions
693
    A list of flags for the permissions of this entry.
694
*/
695
696
/*!
697
    \variable FileInfo::crc32
698
    The calculated checksum as a crc32 type.
699
*/
700
701
/*!
702
    \variable FileInfo::size
703
    The total size of the unpacked content.
704
*/
705
706
/*!
707
    \variable FileInfo::d
708
    \internal
709
    private pointer.
710
*/
711
712
/*!
713
    \class QZipReader
714
    \internal
715
    \since 4.5
716
717
    \brief the QZipReader class provides a way to inspect the contents of a zip
718
    archive and extract individual files from it.
719
720
    QZipReader can be used to read a zip archive either from a file or from any
721
    device. An in-memory QBuffer for instance.  The reader can be used to read
722
    which files are in the archive using fileInfoList() and entryInfoAt() but
723
    also to extract individual files using fileData() or even to extract all
724
    files in the archive using extractAll()
725
*/
726
727
/*!
728
    Create a new zip archive that operates on the \a fileName.  The file will be
729
    opened with the \a mode.
730
*/
731
QZipReader::QZipReader(const QString &archive, QIODevice::OpenMode mode)
732
{
733
    QScopedPointer<QFile> f(new QFile(archive));
734
    f->open(mode);
735
    QZipReader::Status status;
736
    if (f->error() == QFile::NoError)
737
        status = NoError;
738
    else {
739
        if (f->error() == QFile::ReadError)
740
            status = FileReadError;
741
        else if (f->error() == QFile::OpenError)
742
            status = FileOpenError;
743
        else if (f->error() == QFile::PermissionsError)
744
            status = FilePermissionsError;
745
        else
746
            status = FileError;
747
    }
748
749
    d = new QZipReaderPrivate(f.data(), /*ownDevice=*/true);
750
    f.take();
751
    d->status = status;
752
}
753
754
/*!
755
    Create a new zip archive that operates on the archive found in \a device.
756
    You have to open the device previous to calling the constructor and only a
757
    device that is readable will be scanned for zip filecontent.
758
 */
759
QZipReader::QZipReader(QIODevice *device)
760
    : d(new QZipReaderPrivate(device, /*ownDevice=*/false))
761
{
762
    Q_ASSERT(device);
763
}
764
765
/*!
766
    Desctructor
767
*/
768
QZipReader::~QZipReader()
769
{
770
    close();
771
    delete d;
772
}
773
774
/*!
775
    Returns device used for reading zip archive.
776
*/
777
QIODevice* QZipReader::device() const
778
{
779
    return d->device;
780
}
781
782
/*!
783
    Returns true if the user can read the file; otherwise returns false.
784
*/
785
bool QZipReader::isReadable() const
786
{
787
    return d->device->isReadable();
788
}
789
790
/*!
791
    Returns true if the file exists; otherwise returns false.
792
*/
793
bool QZipReader::exists() const
794
{
795
    QFile *f = qobject_cast<QFile*> (d->device);
796
    if (f == 0)
797
        return true;
798
    return f->exists();
799
}
800
801
/*!
802
    Returns the list of files the archive contains.
803
*/
804
QList<QZipReader::FileInfo> QZipReader::fileInfoList() const
805
{
806
    d->scanFiles();
807
    QList<QZipReader::FileInfo> files;
808
    for (int i = 0; i < d->fileHeaders.size(); ++i) {
809
        QZipReader::FileInfo fi;
810
        d->fillFileInfo(i, fi);
811
        files.append(fi);
812
    }
813
    return files;
814
815
}
816
817
/*!
818
    Return the number of items in the zip archive.
819
*/
820
int QZipReader::count() const
821
{
822
    d->scanFiles();
823
    return d->fileHeaders.count();
824
}
825
826
/*!
827
    Returns a FileInfo of an entry in the zipfile.
828
    The \a index is the index into the directory listing of the zipfile.
829
    Returns an invalid FileInfo if \a index is out of boundaries.
830
831
    \sa fileInfoList()
832
*/
833
QZipReader::FileInfo QZipReader::entryInfoAt(int index) const
834
{
835
    d->scanFiles();
836
    QZipReader::FileInfo fi;
837
    if (index >= 0 && index < d->fileHeaders.count())
838
        d->fillFileInfo(index, fi);
839
    return fi;
840
}
841
842
/*!
843
    Fetch the file contents from the zip archive and return the uncompressed bytes.
844
*/
845
QByteArray QZipReader::fileData(const QString &fileName) const
846
{
847
    d->scanFiles();
848
    int i;
849
    for (i = 0; i < d->fileHeaders.size(); ++i) {
850
        if (QString::fromLocal8Bit(d->fileHeaders.at(i).file_name) == fileName)
851
            break;
852
    }
853
    if (i == d->fileHeaders.size())
854
        return QByteArray();
855
856
    FileHeader header = d->fileHeaders.at(i);
857
858
    int compressed_size = readUInt(header.h.compressed_size);
859
    int uncompressed_size = readUInt(header.h.uncompressed_size);
860
    int start = readUInt(header.h.offset_local_header);
861
    //qDebug("uncompressing file %d: local header at %d", i, start);
862
863
    d->device->seek(start);
864
    LocalFileHeader lh;
865
    d->device->read((char *)&lh, sizeof(LocalFileHeader));
866
    uint skip = readUShort(lh.file_name_length) + readUShort(lh.extra_field_length);
867
    d->device->seek(d->device->pos() + skip);
868
869
    int compression_method = readUShort(lh.compression_method);
870
    //qDebug("file=%s: compressed_size=%d, uncompressed_size=%d", fileName.toLocal8Bit().data(), compressed_size, uncompressed_size);
871
872
    //qDebug("file at %lld", d->device->pos());
873
    QByteArray compressed = d->device->read(compressed_size);
874
    if (compression_method == 0) {
875
        // no compression
876
        compressed.truncate(uncompressed_size);
877
        return compressed;
878
    } else if (compression_method == 8) {
879
        // Deflate
880
        //qDebug("compressed=%d", compressed.size());
881
        compressed.truncate(compressed_size);
882
        QByteArray baunzip;
883
        ulong len = qMax(uncompressed_size,  1);
884
        int res;
885
        do {
886
            baunzip.resize(len);
887
            res = inflate((uchar*)baunzip.data(), &len,
888
                          (uchar*)compressed.constData(), compressed_size);
889
890
            switch (res) {
891
            case Z_OK:
892
                if ((int)len != baunzip.size())
893
                    baunzip.resize(len);
894
                break;
895
            case Z_MEM_ERROR:
896
                qWarning("QZip: Z_MEM_ERROR: Not enough memory");
897
                break;
898
            case Z_BUF_ERROR:
899
                len *= 2;
900
                break;
901
            case Z_DATA_ERROR:
902
                qWarning("QZip: Z_DATA_ERROR: Input data is corrupted");
903
                break;
904
            }
905
        } while (res == Z_BUF_ERROR);
906
        return baunzip;
907
    }
908
    qWarning() << "QZip: Unknown compression method";
909
    return QByteArray();
910
}
911
912
/*!
913
    Extracts the full contents of the zip file into \a destinationDir on
914
    the local filesystem.
915
    In case writing or linking a file fails, the extraction will be aborted.
916
*/
917
bool QZipReader::extractAll(const QString &destinationDir) const
918
{
919
    QDir baseDir(destinationDir);
920
921
    // create directories first
922
    QList<FileInfo> allFiles = fileInfoList();
923
    foreach (FileInfo fi, allFiles) {
924
        const QString absPath = destinationDir + QDir::separator() + fi.filePath;
925
        if (fi.isDir) {
926
            if (!baseDir.mkpath(fi.filePath))
927
                return false;
928
            if (!QFile::setPermissions(absPath, fi.permissions))
929
                return false;
930
        }
931
    }
932
933
    // set up symlinks
934
    foreach (FileInfo fi, allFiles) {
935
        const QString absPath = destinationDir + QDir::separator() + fi.filePath;
936
        if (fi.isSymLink) {
937
            QString destination = QFile::decodeName(fileData(fi.filePath));
938
            if (destination.isEmpty())
939
                return false;
940
            QFileInfo linkFi(absPath);
941
            if (!QFile::exists(linkFi.absolutePath()))
942
                QDir::root().mkpath(linkFi.absolutePath());
943
            if (!QFile::link(destination, absPath))
944
                return false;
945
            /* cannot change permission of links
946
            if (!QFile::setPermissions(absPath, fi.permissions))
947
                return false;
948
            */
949
        }
950
    }
951
952
    foreach (FileInfo fi, allFiles) {
953
        const QString absPath = destinationDir + QDir::separator() + fi.filePath;
954
        if (fi.isFile) {
955
            QFile f(absPath);
956
            if (!f.open(QIODevice::WriteOnly))
957
                return false;
958
            f.write(fileData(fi.filePath));
959
            f.setPermissions(fi.permissions);
960
            f.close();
961
        }
962
    }
963
964
    return true;
965
}
966
967
/*!
968
    \enum QZipReader::Status
969
970
    The following status values are possible:
971
972
    \value NoError  No error occurred.
973
    \value FileReadError    An error occurred when reading from the file.
974
    \value FileOpenError    The file could not be opened.
975
    \value FilePermissionsError The file could not be accessed.
976
    \value FileError        Another file error occurred.
977
*/
978
979
/*!
980
    Returns a status code indicating the first error that was met by QZipReader,
981
    or QZipReader::NoError if no error occurred.
982
*/
983
QZipReader::Status QZipReader::status() const
984
{
985
    return d->status;
986
}
987
988
/*!
989
    Close the zip file.
990
*/
991
void QZipReader::close()
992
{
993
    d->device->close();
994
}
995
996
////////////////////////////// Writer
997
998
/*!
999
    \class QZipWriter
1000
    \internal
1001
    \since 4.5
1002
1003
    \brief the QZipWriter class provides a way to create a new zip archive.
1004
1005
    QZipWriter can be used to create a zip archive containing any number of files
1006
    and directories. The files in the archive will be compressed in a way that is
1007
    compatible with common zip reader applications.
1008
*/
1009
1010
1011
/*!
1012
    Create a new zip archive that operates on the \a archive filename.  The file will
1013
    be opened with the \a mode.
1014
    \sa isValid()
1015
*/
1016
QZipWriter::QZipWriter(const QString &fileName, QIODevice::OpenMode mode)
1017
{
1018
    QScopedPointer<QFile> f(new QFile(fileName));
1019
    f->open(mode);
1020
    QZipWriter::Status status;
1021
    if (f->error() == QFile::NoError)
1022
        status = QZipWriter::NoError;
1023
    else {
1024
        if (f->error() == QFile::WriteError)
1025
            status = QZipWriter::FileWriteError;
1026
        else if (f->error() == QFile::OpenError)
1027
            status = QZipWriter::FileOpenError;
1028
        else if (f->error() == QFile::PermissionsError)
1029
            status = QZipWriter::FilePermissionsError;
1030
        else
1031
            status = QZipWriter::FileError;
1032
    }
1033
1034
    d = new QZipWriterPrivate(f.data(), /*ownDevice=*/true);
1035
    f.take();
1036
    d->status = status;
1037
}
1038
1039
/*!
1040
    Create a new zip archive that operates on the archive found in \a device.
1041
    You have to open the device previous to calling the constructor and
1042
    only a device that is readable will be scanned for zip filecontent.
1043
 */
1044
QZipWriter::QZipWriter(QIODevice *device)
1045
    : d(new QZipWriterPrivate(device, /*ownDevice=*/false))
1046
{
1047
    Q_ASSERT(device);
1048
}
1049
1050
QZipWriter::~QZipWriter()
1051
{
1052
    close();
1053
    delete d;
1054
}
1055
1056
/*!
1057
    Returns device used for writing zip archive.
1058
*/
1059
QIODevice* QZipWriter::device() const
1060
{
1061
    return d->device;
1062
}
1063
1064
/*!
1065
    Returns true if the user can write to the archive; otherwise returns false.
1066
*/
1067
bool QZipWriter::isWritable() const
1068
{
1069
    return d->device->isWritable();
1070
}
1071
1072
/*!
1073
    Returns true if the file exists; otherwise returns false.
1074
*/
1075
bool QZipWriter::exists() const
1076
{
1077
    QFile *f = qobject_cast<QFile*> (d->device);
1078
    if (f == 0)
1079
        return true;
1080
    return f->exists();
1081
}
1082
1083
/*!
1084
    \enum QZipWriter::Status
1085
1086
    The following status values are possible:
1087
1088
    \value NoError  No error occurred.
1089
    \value FileWriteError    An error occurred when writing to the device.
1090
    \value FileOpenError    The file could not be opened.
1091
    \value FilePermissionsError The file could not be accessed.
1092
    \value FileError        Another file error occurred.
1093
*/
1094
1095
/*!
1096
    Returns a status code indicating the first error that was met by QZipWriter,
1097
    or QZipWriter::NoError if no error occurred.
1098
*/
1099
QZipWriter::Status QZipWriter::status() const
1100
{
1101
    return d->status;
1102
}
1103
1104
/*!
1105
    \enum QZipWriter::CompressionPolicy
1106
1107
    \value AlwaysCompress   A file that is added is compressed.
1108
    \value NeverCompress    A file that is added will be stored without changes.
1109
    \value AutoCompress     A file that is added will be compressed only if that will give a smaller file.
1110
*/
1111
1112
/*!
1113
     Sets the policy for compressing newly added files to the new \a policy.
1114
1115
    \note the default policy is AlwaysCompress
1116
1117
    \sa compressionPolicy()
1118
    \sa addFile()
1119
*/
1120
void QZipWriter::setCompressionPolicy(CompressionPolicy policy)
1121
{
1122
    d->compressionPolicy = policy;
1123
}
1124
1125
/*!
1126
     Returns the currently set compression policy.
1127
    \sa setCompressionPolicy()
1128
    \sa addFile()
1129
*/
1130
QZipWriter::CompressionPolicy QZipWriter::compressionPolicy() const
1131
{
1132
    return d->compressionPolicy;
1133
}
1134
1135
/*!
1136
    Sets the permissions that will be used for newly added files.
1137
1138
    \note the default permissions are QFile::ReadOwner | QFile::WriteOwner.
1139
1140
    \sa creationPermissions()
1141
    \sa addFile()
1142
*/
1143
void QZipWriter::setCreationPermissions(QFile::Permissions permissions)
1144
{
1145
    d->permissions = permissions;
1146
}
1147
1148
/*!
1149
     Returns the currently set creation permissions.
1150
1151
    \sa setCreationPermissions()
1152
    \sa addFile()
1153
*/
1154
QFile::Permissions QZipWriter::creationPermissions() const
1155
{
1156
    return d->permissions;
1157
}
1158
1159
/*!
1160
    Add a file to the archive with \a data as the file contents.
1161
    The file will be stored in the archive using the \a fileName which
1162
    includes the full path in the archive.
1163
1164
    The new file will get the file permissions based on the current
1165
    creationPermissions and it will be compressed using the zip compression
1166
    based on the current compression policy.
1167
1168
    \sa setCreationPermissions()
1169
    \sa setCompressionPolicy()
1170
*/
1171
void QZipWriter::addFile(const QString &fileName, const QByteArray &data)
1172
{
1173
    d->addEntry(QZipWriterPrivate::File, fileName, data);
1174
}
1175
1176
/*!
1177
    Add a file to the archive with \a device as the source of the contents.
1178
    The contents returned from QIODevice::readAll() will be used as the
1179
    filedata.
1180
    The file will be stored in the archive using the \a fileName which
1181
    includes the full path in the archive.
1182
*/
1183
void QZipWriter::addFile(const QString &fileName, QIODevice *device)
1184
{
1185
    Q_ASSERT(device);
1186
    QIODevice::OpenMode mode = device->openMode();
1187
    bool opened = false;
1188
    if ((mode & QIODevice::ReadOnly) == 0) {
1189
        opened = true;
1190
        if (! device->open(QIODevice::ReadOnly)) {
1191
            d->status = FileOpenError;
1192
            return;
1193
        }
1194
    }
1195
    d->addEntry(QZipWriterPrivate::File, fileName, device->readAll());
1196
    if (opened)
1197
        device->close();
1198
}
1199
1200
/*!
1201
    Create a new directory in the archive with the specified \a dirName and
1202
    the \a permissions;
1203
*/
1204
void QZipWriter::addDirectory(const QString &dirName)
1205
{
1206
    QString name = dirName;
1207
    // separator is mandatory
1208
    if (!name.endsWith(QDir::separator()))
1209
        name.append(QDir::separator());
1210
    d->addEntry(QZipWriterPrivate::Directory, name, QByteArray());
1211
}
1212
1213
/*!
1214
    Create a new symbolic link in the archive with the specified \a dirName
1215
    and the \a permissions;
1216
    A symbolic link contains the destination (relative) path and name.
1217
*/
1218
void QZipWriter::addSymLink(const QString &fileName, const QString &destination)
1219
{
1220
    d->addEntry(QZipWriterPrivate::Symlink, fileName, QFile::encodeName(destination));
1221
}
1222
1223
/*!
1224
   Closes the zip file.
1225
*/
1226
void QZipWriter::close()
1227
{
1228
    if (!(d->device->openMode() & QIODevice::WriteOnly)) {
1229
        d->device->close();
1230
        return;
1231
    }
1232
1233
    //qDebug("QZip::close writing directory, %d entries", d->fileHeaders.size());
1234
    d->device->seek(d->start_of_directory);
1235
    // write new directory
1236
    for (int i = 0; i < d->fileHeaders.size(); ++i) {
1237
        const FileHeader &header = d->fileHeaders.at(i);
1238
        d->device->write((const char *)&header.h, sizeof(CentralFileHeader));
1239
        d->device->write(header.file_name);
1240
        d->device->write(header.extra_field);
1241
        d->device->write(header.file_comment);
1242
    }
1243
    int dir_size = d->device->pos() - d->start_of_directory;
1244
    // write end of directory
1245
    EndOfDirectory eod;
1246
    memset(&eod, 0, sizeof(EndOfDirectory));
1247
    writeUInt(eod.signature, 0x06054b50);
1248
    //uchar this_disk[2];
1249
    //uchar start_of_directory_disk[2];
1250
    writeUShort(eod.num_dir_entries_this_disk, d->fileHeaders.size());
1251
    writeUShort(eod.num_dir_entries, d->fileHeaders.size());
1252
    writeUInt(eod.directory_size, dir_size);
1253
    writeUInt(eod.dir_start_offset, d->start_of_directory);
1254
    writeUShort(eod.comment_length, d->comment.length());
1255
1256
    d->device->write((const char *)&eod, sizeof(EndOfDirectory));
1257
    d->device->write(d->comment);
1258
    d->device->close();
1259
}
1260
1261
QT_END_NAMESPACE
1262
1263
#endif // QT_NO_TEXTODFWRITER