Export QTextImageHandler and add accessor for image
[profile/ivi/qtbase.git] / src / gui / text / qzip.cpp
1 /****************************************************************************
2 **
3 ** Copyright (C) 2011 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