File indexing completed on 2024-05-12 15:59:59

0001 /*
0002  * SPDX-FileCopyrightText: 2019 Boudewijn Rempt <boud@valdyas.org>
0003  *
0004  * SPDX-License-Identifier: LGPL-2.0-or-later
0005  */
0006 #include "KoQuaZipStore.h"
0007 #include "KoStore_p.h"
0008 
0009 #include <StoreDebug.h>
0010 
0011 #include <zlib.h>
0012 #include <quazip.h>
0013 #include <quazipfile.h>
0014 #include <quazipdir.h>
0015 #include <quazipfileinfo.h>
0016 #include <quazipnewinfo.h>
0017 
0018 #include <QTemporaryFile>
0019 #include <QTextCodec>
0020 #include <QByteArray>
0021 #include <QBuffer>
0022 
0023 #include <KConfig>
0024 #include <KSharedConfig>
0025 #include <KConfigGroup>
0026 
0027 struct KoQuaZipStore::Private {
0028 
0029     Private() {}
0030     ~Private() {}
0031 
0032     QuaZip *archive {0};
0033     QuaZipFile *currentFile {0};
0034     QStringList directoryListCache;
0035     bool directoryListCached {false};
0036     int compressionLevel {Z_DEFAULT_COMPRESSION};
0037     bool usingSaveFile {false};
0038     QByteArray cache;
0039     QBuffer buffer;
0040 };
0041 
0042 
0043 KoQuaZipStore::KoQuaZipStore(const QString &_filename, KoStore::Mode _mode, const QByteArray &appIdentification, bool writeMimetype)
0044     : KoStore(_mode, writeMimetype)
0045     , dd(new Private())
0046 {
0047     Q_D(KoStore);
0048     d->localFileName = _filename;
0049     dd->archive = new QuaZip(_filename);
0050     init(appIdentification);
0051 
0052 }
0053 
0054 KoQuaZipStore::KoQuaZipStore(QIODevice *dev, KoStore::Mode _mode, const QByteArray &appIdentification, bool writeMimetype)
0055     : KoStore(_mode, writeMimetype)
0056     , dd(new Private())
0057 {
0058     dd->archive = new QuaZip(dev);
0059     init(appIdentification);
0060 }
0061 
0062 KoQuaZipStore::~KoQuaZipStore()
0063 {
0064     Q_D(KoStore);
0065 
0066     if (d->good && dd->currentFile && dd->currentFile->isOpen()) {
0067         dd->currentFile->close();
0068     }
0069 
0070     if (!d->finalized) {
0071         finalize();
0072     }
0073 
0074     delete dd->archive;
0075 
0076     if (dd->currentFile) {
0077         delete dd->currentFile;
0078     }
0079 }
0080 
0081 void KoQuaZipStore::setCompressionEnabled(bool enabled)
0082 {
0083 
0084     if (enabled) {
0085         dd->compressionLevel = Z_DEFAULT_COMPRESSION;
0086     }
0087     else {
0088         dd->compressionLevel = Z_NO_COMPRESSION;
0089     }
0090 }
0091 
0092 qint64 KoQuaZipStore::write(const char *_data, qint64 _len)
0093 {
0094     Q_D(KoStore);
0095     if (_len == 0) return 0;
0096 
0097     if (!d->isOpen) {
0098         errorStore << "KoStore: You must open before writing" << endl;
0099         return 0;
0100     }
0101 
0102     if (d->mode != Write) {
0103         errorStore << "KoStore: Can not write to store that is opened for reading" << endl;
0104         return 0;
0105     }
0106 
0107     qint64 nwritten = dd->buffer.write(_data, _len);
0108     d->size += nwritten;
0109     return nwritten;
0110 }
0111 
0112 QStringList KoQuaZipStore::directoryList() const
0113 {
0114     // If in Read mode, we can assume the directory listing won't change between invocations.
0115     if(mode() == Read) {
0116         if(!dd->directoryListCached) {
0117             dd->directoryListCache = dd->archive->getFileNameList();
0118             dd->directoryListCached = true;
0119         }
0120         return dd->directoryListCache;
0121     }
0122     else {
0123         return dd->archive->getFileNameList();
0124     }
0125 }
0126 
0127 void KoQuaZipStore::init(const QByteArray &appIdentification)
0128 {
0129     Q_D(KoStore);
0130 
0131     bool enableZip64 = false;
0132     if (appIdentification == "application/x-krita") {
0133         enableZip64 = KSharedConfig::openConfig()->group("").readEntry<bool>("UseZip64", false);
0134     }
0135 
0136     dd->archive->setDataDescriptorWritingEnabled(false);
0137     dd->archive->setZip64Enabled(enableZip64);
0138     dd->archive->setFileNameCodec("UTF-8");
0139     dd->usingSaveFile = dd->archive->getIoDevice() && dd->archive->getIoDevice()->inherits("QSaveFile");
0140     dd->archive->setAutoClose(!dd->usingSaveFile);
0141 
0142     d->good = dd->archive->open(d->mode == Write ? QuaZip::mdCreate : QuaZip::mdUnzip);
0143 
0144     if (!d->good) {
0145         return;
0146     }
0147 
0148     if (d->mode == Write) {
0149         if (d->writeMimetype) {
0150             QuaZipFile f(dd->archive);
0151             QuaZipNewInfo newInfo("mimetype");
0152             newInfo.setPermissions(QFileDevice::ReadOwner | QFileDevice::ReadGroup | QFileDevice::ReadOther);
0153             if (!f.open(QIODevice::WriteOnly, newInfo, 0, 0, 0, Z_NO_COMPRESSION)) {
0154                 d->good = false;
0155                 return;
0156             }
0157             f.write(appIdentification);
0158             f.close();
0159         }
0160     }
0161     else {
0162         debugStore << dd->archive->getEntriesCount() << directoryList();
0163         d->good = dd->archive->getEntriesCount();
0164     }
0165 }
0166 
0167 bool KoQuaZipStore::doFinalize()
0168 {
0169     Q_D(KoStore);
0170 
0171     d->stream = 0;
0172     if (d->good && !dd->usingSaveFile) {
0173         dd->archive->close();
0174     }
0175     return dd->archive->getZipError() == ZIP_OK;
0176 
0177 }
0178 
0179 bool KoQuaZipStore::openWrite(const QString &name)
0180 {
0181     Q_D(KoStore);
0182     QString fixedPath = name;
0183     fixedPath.replace("//", "/");
0184 
0185     delete d->stream;
0186     d->stream = 0; // Not used when writing
0187 
0188     delete dd->currentFile;
0189     dd->currentFile = new QuaZipFile(dd->archive);
0190     QuaZipNewInfo newInfo(fixedPath);
0191     newInfo.setPermissions(QFileDevice::ReadOwner | QFileDevice::ReadGroup | QFileDevice::ReadOther);
0192     bool r = dd->currentFile->open(QIODevice::WriteOnly, newInfo, 0, 0, Z_DEFLATED, dd->compressionLevel);
0193     if (!r) {
0194         qWarning() << "Could not open" << name << dd->currentFile->getZipError();
0195     }
0196 
0197     dd->cache = QByteArray();
0198     dd->buffer.setBuffer(&dd->cache);
0199     dd->buffer.open(QBuffer::WriteOnly);
0200 
0201     return r;
0202 }
0203 
0204 bool KoQuaZipStore::openRead(const QString &name)
0205 {
0206     Q_D(KoStore);
0207 
0208     QString fixedPath = name;
0209     fixedPath.replace("//", "/");
0210 
0211     delete d->stream;
0212     d->stream = 0;
0213     delete dd->currentFile;
0214     dd->currentFile = 0;
0215 
0216     if (!currentPath().isEmpty() && !fixedPath.startsWith(currentPath())) {
0217         fixedPath = currentPath() + '/' + fixedPath;
0218     }
0219 
0220     if (!d->substituteThis.isEmpty()) {
0221         fixedPath = fixedPath.replace(d->substituteThis, d->substituteWith);
0222     }
0223 
0224     if (!dd->archive->setCurrentFile(fixedPath)) {
0225         qWarning() << "\t\tCould not set current file" << dd->archive->getZipError() << fixedPath;
0226         return false;
0227     }
0228 
0229     dd->currentFile = new QuaZipFile(dd->archive);
0230     if (!dd->currentFile->open(QIODevice::ReadOnly)) {
0231         qWarning() << "\t\t\tBut could not open!!!" << dd->archive->getZipError();
0232         return false;
0233     }
0234     d->stream = dd->currentFile;
0235     d->size = dd->currentFile->size();
0236     return true;
0237 }
0238 
0239 bool KoQuaZipStore::closeWrite()
0240 {
0241     Q_D(KoStore);
0242 
0243     bool r = true;
0244     if (dd->currentFile->write(dd->cache) != dd->cache.size()) {
0245         // write() returns number of bytes written, or -1 in case of error
0246         // let's allow write 0 bytes in the cache, when needed
0247         qWarning() << "Could not write buffer to the file";
0248         r = false;
0249     }
0250     dd->buffer.close();
0251     dd->currentFile->close();
0252     d->stream = 0;
0253     return (r && dd->currentFile->getZipError() == ZIP_OK);
0254 }
0255 
0256 bool KoQuaZipStore::closeRead()
0257 {
0258     Q_D(KoStore);
0259     d->stream = 0;
0260     return true;
0261 }
0262 
0263 bool KoQuaZipStore::enterRelativeDirectory(const QString & /*path*/)
0264 {
0265     return true;
0266 }
0267 
0268 bool KoQuaZipStore::enterAbsoluteDirectory(const QString &path)
0269 {
0270     QString fixedPath = path;
0271     fixedPath.replace("//", "/");
0272 
0273     if (fixedPath.isEmpty()) {
0274         fixedPath = "/";
0275     }
0276 
0277     QuaZipDir currentDir (dd->archive, fixedPath);
0278 
0279     return currentDir.exists();
0280 }
0281 
0282 bool KoQuaZipStore::fileExists(const QString &absPath) const
0283 {
0284     Q_D(const KoStore);
0285 
0286     QString fixedPath = absPath;
0287     fixedPath.replace("//", "/");
0288 
0289     if (!d->substituteThis.isEmpty()) {
0290         fixedPath = fixedPath.replace(d->substituteThis, d->substituteWith);
0291     }
0292 
0293     return directoryList().contains(fixedPath);
0294 }