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

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

trunk: Merged in qt 4.6.1 sources.

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