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

Last change on this file since 769 was 769, checked in by Dmitry A. Kuminov, 15 years ago

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

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