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

Last change on this file since 371 was 2, checked in by Dmitry A. Kuminov, 16 years ago

Initially imported qt-all-opensource-src-4.5.1 from Trolltech.

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