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