source: trunk/src/gui/text/qzip.cpp

Last change on this file was 846, checked in by Dmitry A. Kuminov, 14 years ago

trunk: Merged in qt 4.7.2 sources from branches/vendor/nokia/qt.

File size: 35.2 KB
Line 
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** Commercial Usage
11** Licensees holding valid Qt Commercial licenses may use this file in
12** accordance with the Qt Commercial License Agreement provided with the
13** Software or, alternatively, in accordance with the terms contained in
14** a written agreement between you and Nokia.
15**
16** GNU Lesser General Public License Usage
17** Alternatively, this file may be used under the terms of the GNU Lesser
18** General Public License version 2.1 as published by the Free Software
19** Foundation and appearing in the file LICENSE.LGPL included in the
20** packaging of this file. Please review the following information to
21** ensure the GNU Lesser General Public License version 2.1 requirements
22** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
23**
24** In addition, as a special exception, Nokia gives you certain additional
25** rights. These rights are described in the Nokia Qt LGPL Exception
26** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
27**
28** GNU General Public License Usage
29** Alternatively, this file may be used under the terms of the GNU
30** General Public License version 3.0 as published by the Free Software
31** Foundation and appearing in the file LICENSE.GPL included in the
32** packaging of this file. Please review the following information to
33** ensure the GNU General Public License version 3.0 requirements will be
34** met: http://www.gnu.org/copyleft/gpl.html.
35**
36** If you have questions regarding the use of this file, please contact
37** Nokia at qt-info@nokia.com.
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
93QT_BEGIN_NAMESPACE
94
95static inline uint readUInt(const uchar *data)
96{
97 return (data[0]) + (data[1]<<8) + (data[2]<<16) + (data[3]<<24);
98}
99
100static inline ushort readUShort(const uchar *data)
101{
102 return (data[0]) + (data[1]<<8);
103}
104
105static 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
113static inline void writeUShort(uchar *data, ushort i)
114{
115 data[0] = i & 0xff;
116 data[1] = (i>>8) & 0xff;
117}
118
119static 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
127static inline void copyUShort(uchar *dest, const uchar *src)
128{
129 dest[0] = src[0];
130 dest[1] = src[1];
131}
132
133static 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
159static 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
189static 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
224static 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
253static 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
283static 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
298struct 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
312struct DataDescriptor
313{
314 uchar crc_32[4];
315 uchar compressed_size[4];
316 uchar uncompressed_size[4];
317};
318
319struct 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
340struct 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
352struct FileHeader
353{
354 CentralFileHeader h;
355 QByteArray file_name;
356 QByteArray extra_field;
357 QByteArray file_comment;
358};
359
360QZipReader::FileInfo::FileInfo()
361 : isDir(false), isFile(false), isSymLink(false), crc32(0), size(0)
362{
363}
364
365QZipReader::FileInfo::~FileInfo()
366{
367}
368
369QZipReader::FileInfo::FileInfo(const FileInfo &other)
370{
371 operator=(other);
372}
373
374QZipReader::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
387bool QZipReader::FileInfo::isValid() const
388{
389 return isDir || isFile || isSymLink;
390}
391
392class QZipPrivate
393{
394public:
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
416void 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
430class QZipReaderPrivate : public QZipPrivate
431{
432public:
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
443class QZipWriterPrivate : public QZipPrivate
444{
445public:
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
463LocalFileHeader 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
479void 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
568void 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 ? (" -> " + 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*/
731QZipReader::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 */
759QZipReader::QZipReader(QIODevice *device)
760 : d(new QZipReaderPrivate(device, /*ownDevice=*/false))
761{
762 Q_ASSERT(device);
763}
764
765/*!
766 Desctructor
767*/
768QZipReader::~QZipReader()
769{
770 close();
771 delete d;
772}
773
774/*!
775 Returns device used for reading zip archive.
776*/
777QIODevice* 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*/
785bool QZipReader::isReadable() const
786{
787 return d->device->isReadable();
788}
789
790/*!
791 Returns true if the file exists; otherwise returns false.
792*/
793bool 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*/
804QList<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*/
820int 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*/
833QZipReader::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*/
845QByteArray 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*/
917bool 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*/
983QZipReader::Status QZipReader::status() const
984{
985 return d->status;
986}
987
988/*!
989 Close the zip file.
990*/
991void 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*/
1016QZipWriter::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 */
1044QZipWriter::QZipWriter(QIODevice *device)
1045 : d(new QZipWriterPrivate(device, /*ownDevice=*/false))
1046{
1047 Q_ASSERT(device);
1048}
1049
1050QZipWriter::~QZipWriter()
1051{
1052 close();
1053 delete d;
1054}
1055
1056/*!
1057 Returns device used for writing zip archive.
1058*/
1059QIODevice* 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*/
1067bool QZipWriter::isWritable() const
1068{
1069 return d->device->isWritable();
1070}
1071
1072/*!
1073 Returns true if the file exists; otherwise returns false.
1074*/
1075bool 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*/
1099QZipWriter::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*/
1120void 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*/
1130QZipWriter::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*/
1143void 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*/
1154QFile::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*/
1171void 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*/
1183void 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*/
1204void 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*/
1218void 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*/
1226void 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
1261QT_END_NAMESPACE
1262
1263#endif // QT_NO_TEXTODFWRITER
Note: See TracBrowser for help on using the repository browser.