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