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