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 }