10#ifndef QT_NO_TEXTODFWRITER
12#include "MarbleZipReader.h"
13#include "MarbleZipWriter.h"
18#include <qplatformdefs.h>
24#define S_IFREG 0100000
26#define S_IFDIR 0040000
29#define S_ISDIR(x) ((x) & S_IFDIR) > 0
32#define S_ISREG(x) ((x) & 0170000) == S_IFREG
35#define S_ISLNK(x) ((x) & S_IFLNK) > 0
64static inline uint readUInt(
const uchar *data)
66 return (data[0]) + (data[1] << 8) + (data[2] << 16) + (data[3] << 24);
69static inline ushort readUShort(
const uchar *data)
71 return (data[0]) + (data[1] << 8);
74static inline void writeUInt(uchar *data, uint i)
77 data[1] = (i >> 8) & 0xff;
78 data[2] = (i >> 16) & 0xff;
79 data[3] = (i >> 24) & 0xff;
82static inline void writeUShort(uchar *data, ushort i)
85 data[1] = (i >> 8) & 0xff;
88static inline void copyUInt(uchar *dest,
const uchar *src)
96static inline void copyUShort(uchar *dest,
const uchar *src)
102static void writeMSDosDate(uchar *dest,
const QDateTime &dt)
105 quint16 time = (dt.
time().hour() << 11)
106 | (dt.
time().minute() << 5)
107 | (dt.
time().second() >> 1);
109 dest[0] = time & 0xff;
112 quint16 date = ((dt.
date().year() - 1980) << 9)
113 | (dt.
date().month() << 5)
116 dest[2] = char(date);
117 dest[3] = char(date >> 8);
156static int inflate(Bytef *dest, ulong *destLen,
const Bytef *source, ulong sourceLen)
161 stream.next_in = (Bytef *)source;
162 stream.avail_in = (uInt)sourceLen;
163 if ((uLong)stream.avail_in != sourceLen)
166 stream.next_out = dest;
167 stream.avail_out = (uInt)*destLen;
168 if ((uLong)stream.avail_out != *destLen)
171 stream.zalloc = (alloc_func)
nullptr;
172 stream.zfree = (free_func)
nullptr;
174 err = inflateInit2(&stream, -MAX_WBITS);
178 err = inflate(&stream, Z_FINISH);
179 if (err != Z_STREAM_END) {
181 if (err == Z_NEED_DICT || (err == Z_BUF_ERROR && stream.avail_in == 0))
185 *destLen = stream.total_out;
187 err = inflateEnd(&stream);
191static int deflate(Bytef *dest, ulong *destLen,
const Bytef *source, ulong sourceLen)
196 stream.next_in = (Bytef *)source;
197 stream.avail_in = (uInt)sourceLen;
198 stream.next_out = dest;
199 stream.avail_out = (uInt)*destLen;
200 if ((uLong)stream.avail_out != *destLen)
203 stream.zalloc = (alloc_func)
nullptr;
204 stream.zfree = (free_func)
nullptr;
205 stream.opaque = (voidpf)
nullptr;
207 err = deflateInit2(&stream, Z_DEFAULT_COMPRESSION, Z_DEFLATED, -MAX_WBITS, 8, Z_DEFAULT_STRATEGY);
211 err = deflate(&stream, Z_FINISH);
212 if (err != Z_STREAM_END) {
214 return err == Z_OK ? Z_BUF_ERROR : err;
216 *destLen = stream.total_out;
218 err = deflateEnd(&stream);
252static QDateTime readMSDosDate(
const uchar *src)
254 uint dosDate = readUInt(src);
256 uDate = (quint64)(dosDate >> 16);
257 uint tm_mday = (uDate & 0x1f);
258 uint tm_mon = ((uDate & 0x1E0) >> 5);
259 uint tm_year = (((uDate & 0x0FE00) >> 9) + 1980);
260 uint tm_hour = ((dosDate & 0xF800) >> 11);
261 uint tm_min = ((dosDate & 0x7E0) >> 5);
262 uint tm_sec = ((dosDate & 0x1f) << 1);
264 return {
QDate(tm_year, tm_mon, tm_mday),
QTime(tm_hour, tm_min, tm_sec)};
267struct LocalFileHeader {
269 uchar version_needed[2];
270 uchar general_purpose_bits[2];
271 uchar compression_method[2];
272 uchar last_mod_file[4];
274 uchar compressed_size[4];
275 uchar uncompressed_size[4];
276 uchar file_name_length[2];
277 uchar extra_field_length[2];
280struct DataDescriptor {
282 uchar compressed_size[4];
283 uchar uncompressed_size[4];
286struct CentralFileHeader {
288 uchar version_made[2];
289 uchar version_needed[2];
290 uchar general_purpose_bits[2];
291 uchar compression_method[2];
292 uchar last_mod_file[4];
294 uchar compressed_size[4];
295 uchar uncompressed_size[4];
296 uchar file_name_length[2];
297 uchar extra_field_length[2];
298 uchar file_comment_length[2];
300 uchar internal_file_attributes[2];
301 uchar external_file_attributes[4];
302 uchar offset_local_header[4];
303 LocalFileHeader toLocalHeader()
const;
306struct EndOfDirectory {
309 uchar start_of_directory_disk[2];
310 uchar num_dir_entries_this_disk[2];
311 uchar num_dir_entries[2];
312 uchar directory_size[4];
313 uchar dir_start_offset[4];
314 uchar comment_length[2];
324MarbleZipReader::FileInfo::FileInfo()
333MarbleZipReader::FileInfo::~FileInfo() =
default;
335MarbleZipReader::FileInfo::FileInfo(
const FileInfo &other)
340MarbleZipReader::FileInfo &MarbleZipReader::FileInfo::operator=(
const FileInfo &other)
342 filePath = other.filePath;
344 isFile = other.isFile;
345 isSymLink = other.isSymLink;
346 permissions = other.permissions;
349 lastModified = other.lastModified;
353bool MarbleZipReader::FileInfo::isValid()
const
355 return isDir || isFile || isSymLink;
361 QZipPrivate(
QIODevice *device,
bool ownDev)
364 , dirtyFileTree(true)
365 , start_of_directory(0)
375 void fillFileInfo(
int index, MarbleZipReader::FileInfo &fileInfo)
const;
382 uint start_of_directory;
385void QZipPrivate::fillFileInfo(
int index, MarbleZipReader::FileInfo &fileInfo)
const
387 FileHeader header = fileHeaders.
at(index);
389 const quint32 mode = (qFromLittleEndian<quint32>(&header.h.external_file_attributes[0]) >> 16) & 0xFFFF;
391 fileInfo.isDir =
false;
392 fileInfo.isFile =
true;
393 fileInfo.isSymLink =
false;
396 fileInfo.isDir = S_ISDIR(mode);
397 fileInfo.isFile = S_ISREG(mode);
398 fileInfo.isSymLink = S_ISLNK(mode);
399 fileInfo.permissions = modeToPermissions(mode);
401 fileInfo.crc32 = readUInt(header.h.crc_32);
402 fileInfo.size = readUInt(header.h.uncompressed_size);
403 fileInfo.lastModified = readMSDosDate(header.h.last_mod_file);
406class MarbleZipReaderPrivate :
public QZipPrivate
409 MarbleZipReaderPrivate(
QIODevice *device,
bool ownDev)
410 : QZipPrivate(device, ownDev)
417 MarbleZipReader::Status
status;
420class MarbleZipWriterPrivate :
public QZipPrivate
423 MarbleZipWriterPrivate(
QIODevice *device,
bool ownDev)
424 : QZipPrivate(device, ownDev)
426 , permissions(
QFile::ReadOwner |
QFile::WriteOwner)
427 , compressionPolicy(MarbleZipWriter::AlwaysCompress)
431 MarbleZipWriter::Status
status;
433 MarbleZipWriter::CompressionPolicy compressionPolicy;
441 void addEntry(EntryType type,
const QString &fileName,
const QByteArray &contents);
444LocalFileHeader CentralFileHeader::toLocalHeader()
const
447 writeUInt(h.signature, 0x04034b50);
448 copyUShort(h.version_needed, version_needed);
449 copyUShort(h.general_purpose_bits, general_purpose_bits);
450 copyUShort(h.compression_method, compression_method);
451 copyUInt(h.last_mod_file, last_mod_file);
452 copyUInt(h.crc_32, crc_32);
453 copyUInt(h.compressed_size, compressed_size);
454 copyUInt(h.uncompressed_size, uncompressed_size);
455 copyUShort(h.file_name_length, file_name_length);
456 copyUShort(h.extra_field_length, extra_field_length);
460void MarbleZipReaderPrivate::scanFiles()
466 status = MarbleZipReader::FileOpenError;
471 status = MarbleZipReader::FileReadError;
475 dirtyFileTree =
false;
477 device->read((
char *)tmp, 4);
478 if (readUInt(tmp) != 0x04034b50) {
479 qWarning() <<
"QZip: not a zip file!";
485 int start_of_directory = -1;
486 int num_dir_entries = 0;
488 while (start_of_directory == -1) {
489 int pos = device->size() -
sizeof(EndOfDirectory) - i;
490 if (pos < 0 || i > 65535) {
491 qWarning() <<
"QZip: EndOfDirectory not found";
496 device->read((
char *)&eod,
sizeof(EndOfDirectory));
497 if (readUInt(eod.signature) == 0x06054b50)
503 start_of_directory = readUInt(eod.dir_start_offset);
504 num_dir_entries = readUShort(eod.num_dir_entries);
505 ZDEBUG(
"start_of_directory at %d, num_dir_entries=%d", start_of_directory, num_dir_entries);
506 int comment_length = readUShort(eod.comment_length);
507 if (comment_length != i)
508 qWarning() <<
"QZip: failed to parse zip file.";
509 comment = device->read(qMin(comment_length, i));
511 device->seek(start_of_directory);
512 for (i = 0; i < num_dir_entries; ++i) {
514 int read = device->read((
char *)&header.h,
sizeof(CentralFileHeader));
515 if (read < (
int)
sizeof(CentralFileHeader)) {
516 qWarning() <<
"QZip: Failed to read complete header, index may be incomplete";
519 if (readUInt(header.h.signature) != 0x02014b50) {
520 qWarning() <<
"QZip: invalid header signature, index may be incomplete";
524 int l = readUShort(header.h.file_name_length);
525 header.file_name = device->read(l);
526 if (header.file_name.length() != l) {
527 qWarning() <<
"QZip: Failed to read filename from zip index, index may be incomplete";
530 l = readUShort(header.h.extra_field_length);
531 header.extra_field = device->read(l);
532 if (header.extra_field.length() != l) {
533 qWarning() <<
"QZip: Failed to read extra field in zip file, skipping file, index may be incomplete";
536 l = readUShort(header.h.file_comment_length);
537 header.file_comment = device->read(l);
538 if (header.file_comment.length() != l) {
539 qWarning() <<
"QZip: Failed to read file comment, index may be incomplete";
543 ZDEBUG(
"found file '%s'", header.file_name.data());
544 fileHeaders.append(header);
548void MarbleZipWriterPrivate::addEntry(EntryType type,
const QString &fileName,
const QByteArray &contents )
551 static const char *entryTypes[] = {
"directory",
"file ",
"symlink "};
556 status = MarbleZipWriter::FileOpenError;
559 device->seek(start_of_directory);
562 MarbleZipWriter::CompressionPolicy compression = compressionPolicy;
563 if (compressionPolicy == MarbleZipWriter::AutoCompress) {
564 if (contents.
length() < 64)
565 compression = MarbleZipWriter::NeverCompress;
567 compression = MarbleZipWriter::AlwaysCompress;
571 memset(&header.h, 0,
sizeof(CentralFileHeader));
572 writeUInt(header.h.signature, 0x02014b50);
574 writeUShort(header.h.version_needed, 0x14);
575 writeUInt(header.h.uncompressed_size, contents.
length());
578 if (compression == MarbleZipWriter::AlwaysCompress) {
579 writeUShort(header.h.compression_method, 8);
581 ulong len = contents.
length();
583 len += (len >> 12) + (len >> 14) + 11;
587 res = deflate((uchar *)data.
data(), &len, (
const uchar *)contents.
constData(), contents.
length());
594 qWarning(
"QZip: Z_MEM_ERROR: Not enough memory to compress file, skipping");
601 }
while (res == Z_BUF_ERROR);
604 writeUInt(header.h.compressed_size, data.
length());
605 uint crc_32 = ::crc32(0,
nullptr, 0);
606 crc_32 = ::crc32(crc_32, (
const uchar *)contents.
constData(), contents.
length());
607 writeUInt(header.h.crc_32, crc_32);
610 if (header.file_name.size() > 0xffff) {
611 qWarning(
"QZip: Filename too long, chopping it to 65535 characters");
612 header.file_name = header.file_name.left(0xffff);
614 writeUShort(header.h.file_name_length, header.file_name.length());
617 writeUShort(header.h.version_made, 3 << 8);
620 quint32 mode = permissionsToMode(permissions);
632 writeUInt(header.h.external_file_attributes, mode << 16);
633 writeUInt(header.h.offset_local_header, start_of_directory);
635 fileHeaders.append(header);
637 LocalFileHeader h = header.h.toLocalHeader();
638 device->write((
const char *)&h,
sizeof(LocalFileHeader));
639 device->write(header.file_name);
641 start_of_directory = device->pos();
642 dirtyFileTree =
true;
717 MarbleZipReader::Status
status;
726 status = FilePermissionsError;
731 d =
new MarbleZipReaderPrivate(f.data(),
true);
741MarbleZipReader::MarbleZipReader(
QIODevice *device)
742 : d(new MarbleZipReaderPrivate(device, false))
750MarbleZipReader::~MarbleZipReader()
759QIODevice *MarbleZipReader::device()
const
767bool MarbleZipReader::isReadable()
const
775bool MarbleZipReader::exists()
const
777 auto f = qobject_cast<QFile *>(d->device);
790 for (
int i = 0; i < d->fileHeaders.size(); ++i) {
791 MarbleZipReader::FileInfo fi;
792 d->fillFileInfo(i, fi);
801int MarbleZipReader::count()
const
804 return d->fileHeaders.
count();
814MarbleZipReader::FileInfo MarbleZipReader::entryInfoAt(
int index)
const
817 MarbleZipReader::FileInfo fi;
818 if (index >= 0 && index < d->fileHeaders.count())
819 d->fillFileInfo(index, fi);
830 for (i = 0; i < d->fileHeaders.size(); ++i) {
834 if (i == d->fileHeaders.size())
837 FileHeader header = d->fileHeaders.at(i);
839 int compressed_size = readUInt(header.h.compressed_size);
840 int uncompressed_size = readUInt(header.h.uncompressed_size);
841 int start = readUInt(header.h.offset_local_header);
844 d->device->seek(
start);
846 d->device->read((
char *)&lh,
sizeof(LocalFileHeader));
847 uint skip = readUShort(lh.file_name_length) + readUShort(lh.extra_field_length);
848 d->device->seek(d->device->pos() + skip);
850 int compression_method = readUShort(lh.compression_method);
854 QByteArray compressed = d->device->read(compressed_size);
855 if (compression_method == 0) {
857 compressed.
truncate(uncompressed_size);
859 }
else if (compression_method == 8) {
862 compressed.
truncate(compressed_size);
864 ulong len = qMax(uncompressed_size, 1);
868 res = inflate((uchar *)baunzip.
data(), &len, (uchar *)compressed.
constData(), compressed_size);
872 if ((
int)len != baunzip.
size())
876 qWarning(
"QZip: Z_MEM_ERROR: Not enough memory");
882 qWarning(
"QZip: Z_DATA_ERROR: Input data is corrupted");
885 }
while (res == Z_BUF_ERROR);
888 qWarning() <<
"QZip: Unknown compression method";
897bool MarbleZipReader::extractAll(
const QString &destinationDir)
const
899 QDir baseDir(destinationDir);
903 for (
const FileInfo &fi : allFiles) {
906 if (!baseDir.mkpath(fi.filePath))
914 for (
const FileInfo &fi : allFiles) {
932 for (
const FileInfo &fi : allFiles) {
939 f.write(fileData(fi.filePath));
940 f.setPermissions(fi.permissions);
964MarbleZipReader::Status MarbleZipReader::status()
const
972void MarbleZipReader::close()
1000 MarbleZipWriter::Status
status;
1002 status = MarbleZipWriter::NoError;
1005 status = MarbleZipWriter::FileWriteError;
1007 status = MarbleZipWriter::FileOpenError;
1009 status = MarbleZipWriter::FilePermissionsError;
1011 status = MarbleZipWriter::FileError;
1014 d =
new MarbleZipWriterPrivate(f.data(),
true);
1024MarbleZipWriter::MarbleZipWriter(
QIODevice *device)
1025 : d(new MarbleZipWriterPrivate(device, false))
1030MarbleZipWriter::~MarbleZipWriter()
1039QIODevice *MarbleZipWriter::device()
const
1047bool MarbleZipWriter::isWritable()
const
1055bool MarbleZipWriter::exists()
const
1057 auto f = qobject_cast<QFile *>(d->device);
1084MarbleZipWriter::Status MarbleZipWriter::status()
const
1108void MarbleZipWriter::setCompressionPolicy(CompressionPolicy policy)
1110 d->compressionPolicy = policy;
1118MarbleZipWriter::CompressionPolicy MarbleZipWriter::compressionPolicy()
const
1120 return d->compressionPolicy;
1133 d->permissions = permissions;
1144 return d->permissions;
1175 bool opened =
false;
1179 d->status = FileOpenError;
1192void MarbleZipWriter::addDirectory(
const QString &dirName)
1198 d->addEntry(MarbleZipWriterPrivate::Directory, name,
QByteArray());
1206void MarbleZipWriter::addSymLink(
const QString &fileName,
const QString &destination)
1214void MarbleZipWriter::close()
1222 d->device->seek(d->start_of_directory);
1224 for (
int i = 0; i < d->fileHeaders.size(); ++i) {
1225 const FileHeader &header = d->fileHeaders.at(i);
1226 d->device->write((
const char *)&header.h,
sizeof(CentralFileHeader));
1227 d->device->write(header.file_name);
1228 d->device->write(header.extra_field);
1229 d->device->write(header.file_comment);
1231 int dir_size = d->device->pos() - d->start_of_directory;
1234 memset(&eod, 0,
sizeof(EndOfDirectory));
1235 writeUInt(eod.signature, 0x06054b50);
1238 writeUShort(eod.num_dir_entries_this_disk, d->fileHeaders.size());
1239 writeUShort(eod.num_dir_entries, d->fileHeaders.size());
1240 writeUInt(eod.directory_size, dir_size);
1241 writeUInt(eod.dir_start_offset, d->start_of_directory);
1242 writeUShort(eod.comment_length, d->comment.length());
1244 d->device->write((
const char *)&eod,
sizeof(EndOfDirectory));
1245 d->device->write(d->comment);
Q_SCRIPTABLE CaptureState status()
Q_SCRIPTABLE Q_NOREPLY void start()
QString name(GameStandardAction id)
QVariant read(const QByteArray &data, int versionOverride=0)
VehicleSection::Type type(QStringView coachNumber, QStringView coachClassification)
KIOCORE_EXPORT QString dir(const QString &fileClass)
Binds a QML item to a specific geodetic location in screen coordinates.
char at(qsizetype i) const const
const char * constData() const const
qsizetype length() const const
void resize(qsizetype newSize, char c)
qsizetype size() const const
void truncate(qsizetype pos)
QDateTime currentDateTime()
bool isValid() const const
QString fromNativeSeparators(const QString &pathName)
bool mkpath(const QString &dirPath) const const
QString decodeName(const QByteArray &localFileName)
QByteArray encodeName(const QString &fileName)
bool exists() const const
bool link(const QString &fileName, const QString &linkName)
virtual bool setPermissions(Permissions permissions) override
bool isReadable() const const
bool isWritable() const const
virtual bool open(QIODeviceBase::OpenMode mode)
QIODeviceBase::OpenMode openMode() const const
void append(QList< T > &&value)
qsizetype count() const const
QString & append(QChar ch)
bool endsWith(QChar c, Qt::CaseSensitivity cs) const const
QString fromLocal8Bit(QByteArrayView str)
bool isEmpty() const const
QByteArray toLocal8Bit() const const
QByteArray toUtf8() const const