1 /*
2 Copyright (C) 2005-2014 Sergey A. Tachenov
3 
4 This file is part of QuaZip.
5 
6 QuaZip is free software: you can redistribute it and/or modify
7 it under the terms of the GNU Lesser General Public License as published by
8 the Free Software Foundation, either version 2.1 of the License, or
9 (at your option) any later version.
10 
11 QuaZip is distributed in the hope that it will be useful,
12 but WITHOUT ANY WARRANTY; without even the implied warranty of
13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14 GNU Lesser General Public License for more details.
15 
16 You should have received a copy of the GNU Lesser General Public License
17 along with QuaZip.  If not, see <http://www.gnu.org/licenses/>.
18 
19 See COPYING file for the full LGPL text.
20 
21 Original ZIP package is copyrighted by Gilles Vollant, see
22 quazip/(un)zip.h files for details, basically it's zlib license.
23  **/
24 
25 #include "quazipfile.h"
26 
27 #include "quazipfileinfo.h"
28 
29 using namespace std;
30 
31 #define QUAZIP_VERSION_MADE_BY 0x1Eu
32 
33 /// The implementation class for QuaZip.
34 /**
35 \internal
36 
37 This class contains all the private stuff for the QuaZipFile class, thus
38 allowing to preserve binary compatibility between releases, the
39 technique known as the Pimpl (private implementation) idiom.
40 */
41 class QuaZipFilePrivate {
42   friend class QuaZipFile;
43   private:
44     Q_DISABLE_COPY(QuaZipFilePrivate)
45     /// The pointer to the associated QuaZipFile instance.
46     QuaZipFile *q;
47     /// The QuaZip object to work with.
48     QuaZip *zip;
49     /// The file name.
50     QString fileName;
51     /// Case sensitivity mode.
52     QuaZip::CaseSensitivity caseSensitivity;
53     /// Whether this file is opened in the raw mode.
54     bool raw;
55     /// Write position to keep track of.
56     /**
57       QIODevice::pos() is broken for non-seekable devices, so we need
58       our own position.
59       */
60     qint64 writePos;
61     /// Uncompressed size to write along with a raw file.
62     quint64 uncompressedSize;
63     /// CRC to write along with a raw file.
64     quint32 crc;
65     /// Whether \ref zip points to an internal QuaZip instance.
66     /**
67       This is true if the archive was opened by name, rather than by
68       supplying an existing QuaZip instance.
69       */
70     bool internal;
71     /// The last error.
72     int zipError;
73     /// Resets \ref zipError.
resetZipError() const74     inline void resetZipError() const {setZipError(UNZ_OK);}
75     /// Sets the zip error.
76     /**
77       This function is marked as const although it changes one field.
78       This allows to call it from const functions that don't change
79       anything by themselves.
80       */
81     void setZipError(int zipError) const;
82     /// The constructor for the corresponding QuaZipFile constructor.
QuaZipFilePrivate(QuaZipFile * q)83     inline QuaZipFilePrivate(QuaZipFile *q):
84       q(q),
85       zip(nullptr),
86       caseSensitivity(QuaZip::csDefault),
87       raw(false),
88       writePos(0),
89       uncompressedSize(0),
90       crc(0),
91       internal(true),
92       zipError(UNZ_OK) {}
93     /// The constructor for the corresponding QuaZipFile constructor.
QuaZipFilePrivate(QuaZipFile * q,const QString & zipName)94     inline QuaZipFilePrivate(QuaZipFile *q, const QString &zipName):
95       q(q),
96       caseSensitivity(QuaZip::csDefault),
97       raw(false),
98       writePos(0),
99       uncompressedSize(0),
100       crc(0),
101       internal(true),
102       zipError(UNZ_OK)
103       {
104         zip=new QuaZip(zipName);
105       }
106     /// The constructor for the corresponding QuaZipFile constructor.
QuaZipFilePrivate(QuaZipFile * q,const QString & zipName,const QString & fileName,QuaZip::CaseSensitivity cs)107     inline QuaZipFilePrivate(QuaZipFile *q, const QString &zipName, const QString &fileName,
108         QuaZip::CaseSensitivity cs):
109       q(q),
110       raw(false),
111       writePos(0),
112       uncompressedSize(0),
113       crc(0),
114       internal(true),
115       zipError(UNZ_OK)
116       {
117         zip=new QuaZip(zipName);
118         this->fileName=fileName;
119         if (this->fileName.startsWith(QLatin1String("/")))
120             this->fileName = this->fileName.mid(1);
121         this->caseSensitivity=cs;
122       }
123     /// The constructor for the QuaZipFile constructor accepting a file name.
QuaZipFilePrivate(QuaZipFile * q,QuaZip * zip)124     inline QuaZipFilePrivate(QuaZipFile *q, QuaZip *zip):
125       q(q),
126       zip(zip),
127       raw(false),
128       writePos(0),
129       uncompressedSize(0),
130       crc(0),
131       internal(false),
132       zipError(UNZ_OK) {}
133     /// The destructor.
~QuaZipFilePrivate()134     inline ~QuaZipFilePrivate()
135     {
136       if (internal)
137         delete zip;
138     }
139 };
140 
QuaZipFile()141 QuaZipFile::QuaZipFile():
142   p(new QuaZipFilePrivate(this))
143 {
144 }
145 
QuaZipFile(QObject * parent)146 QuaZipFile::QuaZipFile(QObject *parent):
147   QIODevice(parent),
148   p(new QuaZipFilePrivate(this))
149 {
150 }
151 
QuaZipFile(const QString & zipName,QObject * parent)152 QuaZipFile::QuaZipFile(const QString& zipName, QObject *parent):
153   QIODevice(parent),
154   p(new QuaZipFilePrivate(this, zipName))
155 {
156 }
157 
QuaZipFile(const QString & zipName,const QString & fileName,QuaZip::CaseSensitivity cs,QObject * parent)158 QuaZipFile::QuaZipFile(const QString& zipName, const QString& fileName,
159     QuaZip::CaseSensitivity cs, QObject *parent):
160   QIODevice(parent),
161   p(new QuaZipFilePrivate(this, zipName, fileName, cs))
162 {
163 }
164 
QuaZipFile(QuaZip * zip,QObject * parent)165 QuaZipFile::QuaZipFile(QuaZip *zip, QObject *parent):
166   QIODevice(parent),
167   p(new QuaZipFilePrivate(this, zip))
168 {
169 }
170 
~QuaZipFile()171 QuaZipFile::~QuaZipFile()
172 {
173   if (isOpen())
174     close();
175   delete p;
176 }
177 
getZipName() const178 QString QuaZipFile::getZipName() const
179 {
180   return p->zip==nullptr ? QString() : p->zip->getZipName();
181 }
182 
getZip() const183 QuaZip *QuaZipFile::getZip() const
184 {
185     return p->internal ? nullptr : p->zip;
186 }
187 
getActualFileName() const188 QString QuaZipFile::getActualFileName()const
189 {
190   p->setZipError(UNZ_OK);
191   if (p->zip == nullptr || (openMode() & WriteOnly))
192     return QString();
193   QString name=p->zip->getCurrentFileName();
194   if(name.isNull())
195     p->setZipError(p->zip->getZipError());
196   return name;
197 }
198 
setZipName(const QString & zipName)199 void QuaZipFile::setZipName(const QString& zipName)
200 {
201   if(isOpen()) {
202     qWarning("QuaZipFile::setZipName(): file is already open - can not set ZIP name");
203     return;
204   }
205   if(p->zip!=nullptr && p->internal)
206     delete p->zip;
207   p->zip=new QuaZip(zipName);
208   p->internal=true;
209 }
210 
setZip(QuaZip * zip)211 void QuaZipFile::setZip(QuaZip *zip)
212 {
213   if(isOpen()) {
214     qWarning("QuaZipFile::setZip(): file is already open - can not set ZIP");
215     return;
216   }
217   if(p->zip!=nullptr && p->internal)
218     delete p->zip;
219   p->zip=zip;
220   p->fileName=QString();
221   p->internal=false;
222 }
223 
setFileName(const QString & fileName,QuaZip::CaseSensitivity cs)224 void QuaZipFile::setFileName(const QString& fileName, QuaZip::CaseSensitivity cs)
225 {
226   if(p->zip==nullptr) {
227     qWarning("QuaZipFile::setFileName(): call setZipName() first");
228     return;
229   }
230   if(!p->internal) {
231     qWarning("QuaZipFile::setFileName(): should not be used when not using internal QuaZip");
232     return;
233   }
234   if(isOpen()) {
235     qWarning("QuaZipFile::setFileName(): can not set file name for already opened file");
236     return;
237   }
238   p->fileName=fileName;
239   if (p->fileName.startsWith(QLatin1String("/")))
240       p->fileName = p->fileName.mid(1);
241   p->caseSensitivity=cs;
242 }
243 
setZipError(int zipError) const244 void QuaZipFilePrivate::setZipError(int zipError) const
245 {
246   QuaZipFilePrivate *fakeThis = const_cast<QuaZipFilePrivate*>(this); // non-const
247   fakeThis->zipError=zipError;
248   if(zipError==UNZ_OK)
249     q->setErrorString(QString());
250   else
251     q->setErrorString(QuaZipFile::tr("ZIP/UNZIP API error %1").arg(zipError));
252 }
253 
open(OpenMode mode)254 bool QuaZipFile::open(OpenMode mode)
255 {
256   return open(mode, nullptr);
257 }
258 
open(OpenMode mode,int * method,int * level,bool raw,const char * password)259 bool QuaZipFile::open(OpenMode mode, int *method, int *level, bool raw, const char *password)
260 {
261   p->resetZipError();
262   if(isOpen()) {
263     qWarning("QuaZipFile::open(): already opened");
264     return false;
265   }
266   if(mode&Unbuffered) {
267     qWarning("QuaZipFile::open(): Unbuffered mode is not supported");
268     return false;
269   }
270   if((mode&ReadOnly)&&!(mode&WriteOnly)) {
271     if(p->internal) {
272       if(!p->zip->open(QuaZip::mdUnzip)) {
273         p->setZipError(p->zip->getZipError());
274         return false;
275       }
276       if(!p->zip->setCurrentFile(p->fileName, p->caseSensitivity)) {
277         p->setZipError(p->zip->getZipError());
278         p->zip->close();
279         return false;
280       }
281     } else {
282       if(p->zip==nullptr) {
283         qWarning("QuaZipFile::open(): zip is null");
284         return false;
285       }
286       if(p->zip->getMode()!=QuaZip::mdUnzip) {
287         qWarning("QuaZipFile::open(): file open mode %d incompatible with ZIP open mode %d",
288             (int)mode, (int)p->zip->getMode());
289         return false;
290       }
291       if(!p->zip->hasCurrentFile()) {
292         qWarning("QuaZipFile::open(): zip does not have current file");
293         return false;
294       }
295     }
296     p->setZipError(unzOpenCurrentFile3(p->zip->getUnzFile(), method, level, (int)raw, password));
297     if(p->zipError==UNZ_OK) {
298       setOpenMode(mode);
299       p->raw=raw;
300       return true;
301     } else
302       return false;
303   }
304   qWarning("QuaZipFile::open(): open mode %d not supported by this function", (int)mode);
305   return false;
306 }
307 
open(OpenMode mode,const QuaZipNewInfo & info,const char * password,quint32 crc,int method,int level,bool raw,int windowBits,int memLevel,int strategy)308 bool QuaZipFile::open(OpenMode mode, const QuaZipNewInfo& info,
309     const char *password, quint32 crc,
310     int method, int level, bool raw,
311     int windowBits, int memLevel, int strategy)
312 {
313   zip_fileinfo info_z;
314   p->resetZipError();
315   if(isOpen()) {
316     qWarning("QuaZipFile::open(): already opened");
317     return false;
318   }
319   if((mode&WriteOnly)&&!(mode&ReadOnly)) {
320     if(p->internal) {
321       qWarning("QuaZipFile::open(): write mode is incompatible with internal QuaZip approach");
322       return false;
323     }
324     if(p->zip==nullptr) {
325       qWarning("QuaZipFile::open(): zip is null");
326       return false;
327     }
328     if(p->zip->getMode()!=QuaZip::mdCreate&&p->zip->getMode()!=QuaZip::mdAppend&&p->zip->getMode()!=QuaZip::mdAdd) {
329       qWarning("QuaZipFile::open(): file open mode %d incompatible with ZIP open mode %d",
330           (int)mode, (int)p->zip->getMode());
331       return false;
332     }
333     info_z.tmz_date.tm_year=info.dateTime.date().year();
334     info_z.tmz_date.tm_mon=info.dateTime.date().month() - 1;
335     info_z.tmz_date.tm_mday=info.dateTime.date().day();
336     info_z.tmz_date.tm_hour=info.dateTime.time().hour();
337     info_z.tmz_date.tm_min=info.dateTime.time().minute();
338     info_z.tmz_date.tm_sec=info.dateTime.time().second();
339     info_z.dosDate = 0;
340     info_z.internal_fa=(uLong)info.internalAttr;
341     info_z.external_fa=(uLong)info.externalAttr;
342     if (p->zip->isDataDescriptorWritingEnabled())
343         zipSetFlags(p->zip->getZipFile(), ZIP_WRITE_DATA_DESCRIPTOR);
344     else
345         zipClearFlags(p->zip->getZipFile(), ZIP_WRITE_DATA_DESCRIPTOR);
346     p->setZipError(zipOpenNewFileInZip4_64(p->zip->getZipFile(),
347           p->zip->isUtf8Enabled()
348             ? info.name.toUtf8().constData()
349             : p->zip->getFileNameCodec()->fromUnicode(info.name).constData(),
350           &info_z,
351           info.extraLocal.constData(), info.extraLocal.length(),
352           info.extraGlobal.constData(), info.extraGlobal.length(),
353           p->zip->isUtf8Enabled()
354             ? info.comment.toUtf8().constData()
355             : p->zip->getCommentCodec()->fromUnicode(info.comment).constData(),
356           method, level, (int)raw,
357           windowBits, memLevel, strategy,
358           password, (uLong)crc,
359           (p->zip->getOsCode() << 8) | QUAZIP_VERSION_MADE_BY,
360           0,
361           p->zip->isZip64Enabled()));
362     if(p->zipError==UNZ_OK) {
363       p->writePos=0;
364       setOpenMode(mode);
365       p->raw=raw;
366       if(raw) {
367         p->crc=crc;
368         p->uncompressedSize=info.uncompressedSize;
369       }
370       return true;
371     } else
372       return false;
373   }
374   qWarning("QuaZipFile::open(): open mode %d not supported by this function", (int)mode);
375   return false;
376 }
377 
isSequential() const378 bool QuaZipFile::isSequential()const
379 {
380   return true;
381 }
382 
pos() const383 qint64 QuaZipFile::pos()const
384 {
385   if(p->zip==nullptr) {
386     qWarning("QuaZipFile::pos(): call setZipName() or setZip() first");
387     return -1;
388   }
389   if(!isOpen()) {
390     qWarning("QuaZipFile::pos(): file is not open");
391     return -1;
392   }
393   if(openMode()&ReadOnly)
394       // QIODevice::pos() is broken for sequential devices,
395       // but thankfully bytesAvailable() returns the number of
396       // bytes buffered, so we know how far ahead we are.
397     return unztell64(p->zip->getUnzFile()) - QIODevice::bytesAvailable();
398   else
399     return p->writePos;
400 }
401 
atEnd() const402 bool QuaZipFile::atEnd()const
403 {
404   if(p->zip==nullptr) {
405     qWarning("QuaZipFile::atEnd(): call setZipName() or setZip() first");
406     return false;
407   }
408   if(!isOpen()) {
409     qWarning("QuaZipFile::atEnd(): file is not open");
410     return false;
411   }
412   if(openMode()&ReadOnly)
413       // the same problem as with pos()
414     return QIODevice::bytesAvailable() == 0
415         && unzeof(p->zip->getUnzFile())==1;
416   else
417     return true;
418 }
419 
size() const420 qint64 QuaZipFile::size()const
421 {
422   if(!isOpen()) {
423     qWarning("QuaZipFile::atEnd(): file is not open");
424     return -1;
425   }
426   if(openMode()&ReadOnly)
427     return p->raw?csize():usize();
428   else
429     return p->writePos;
430 }
431 
csize() const432 qint64 QuaZipFile::csize()const
433 {
434   unz_file_info64 info_z;
435   p->setZipError(UNZ_OK);
436   if(p->zip==nullptr||p->zip->getMode()!=QuaZip::mdUnzip) return -1;
437   p->setZipError(unzGetCurrentFileInfo64(p->zip->getUnzFile(), &info_z, nullptr, 0, nullptr, 0, nullptr, 0));
438   if(p->zipError!=UNZ_OK)
439     return -1;
440   return info_z.compressed_size;
441 }
442 
usize() const443 qint64 QuaZipFile::usize()const
444 {
445   unz_file_info64 info_z;
446   p->setZipError(UNZ_OK);
447   if(p->zip==nullptr||p->zip->getMode()!=QuaZip::mdUnzip) return -1;
448   p->setZipError(unzGetCurrentFileInfo64(p->zip->getUnzFile(), &info_z, nullptr, 0, nullptr, 0, nullptr, 0));
449   if(p->zipError!=UNZ_OK)
450     return -1;
451   return info_z.uncompressed_size;
452 }
453 
getFileInfo(QuaZipFileInfo * info)454 bool QuaZipFile::getFileInfo(QuaZipFileInfo *info)
455 {
456     QuaZipFileInfo64 info64;
457     if (getFileInfo(&info64)) {
458         info64.toQuaZipFileInfo(*info);
459         return true;
460     } else {
461         return false;
462     }
463 }
464 
getFileInfo(QuaZipFileInfo64 * info)465 bool QuaZipFile::getFileInfo(QuaZipFileInfo64 *info)
466 {
467     if(p->zip==nullptr||p->zip->getMode()!=QuaZip::mdUnzip) return false;
468     p->zip->getCurrentFileInfo(info);
469     p->setZipError(p->zip->getZipError());
470     return p->zipError==UNZ_OK;
471 }
472 
close()473 void QuaZipFile::close()
474 {
475   p->resetZipError();
476   if(p->zip==nullptr||!p->zip->isOpen()) return;
477   if(!isOpen()) {
478     qWarning("QuaZipFile::close(): file isn't open");
479     return;
480   }
481   if(openMode()&ReadOnly)
482     p->setZipError(unzCloseCurrentFile(p->zip->getUnzFile()));
483   else if(openMode()&WriteOnly)
484     if(isRaw()) p->setZipError(zipCloseFileInZipRaw64(p->zip->getZipFile(), p->uncompressedSize, p->crc));
485     else p->setZipError(zipCloseFileInZip(p->zip->getZipFile()));
486   else {
487     qWarning("Wrong open mode: %d", (int)openMode());
488     return;
489   }
490   if(p->zipError==UNZ_OK) setOpenMode(QIODevice::NotOpen);
491   else return;
492   if(p->internal) {
493     p->zip->close();
494     p->setZipError(p->zip->getZipError());
495   }
496 }
497 
readData(char * data,qint64 maxSize)498 qint64 QuaZipFile::readData(char *data, qint64 maxSize)
499 {
500   p->setZipError(UNZ_OK);
501   qint64 bytesRead=unzReadCurrentFile(p->zip->getUnzFile(), data, (unsigned)maxSize);
502   if (bytesRead < 0) {
503     p->setZipError((int) bytesRead);
504     return -1;
505   }
506   return bytesRead;
507 }
508 
writeData(const char * data,qint64 maxSize)509 qint64 QuaZipFile::writeData(const char* data, qint64 maxSize)
510 {
511   p->setZipError(ZIP_OK);
512   p->setZipError(zipWriteInFileInZip(p->zip->getZipFile(), data, (uint)maxSize));
513   if(p->zipError!=ZIP_OK) return -1;
514   else {
515     p->writePos+=maxSize;
516     return maxSize;
517   }
518 }
519 
getFileName() const520 QString QuaZipFile::getFileName() const
521 {
522   return p->fileName;
523 }
524 
getCaseSensitivity() const525 QuaZip::CaseSensitivity QuaZipFile::getCaseSensitivity() const
526 {
527   return p->caseSensitivity;
528 }
529 
isRaw() const530 bool QuaZipFile::isRaw() const
531 {
532   return p->raw;
533 }
534 
getZipError() const535 int QuaZipFile::getZipError() const
536 {
537   return p->zipError;
538 }
539 
bytesAvailable() const540 qint64 QuaZipFile::bytesAvailable() const
541 {
542     return size() - pos();
543 }
544 
getLocalExtraField()545 QByteArray QuaZipFile::getLocalExtraField()
546 {
547     int size = unzGetLocalExtrafield(p->zip->getUnzFile(), nullptr, 0);
548     QByteArray extra(size, '\0');
549     int err = unzGetLocalExtrafield(p->zip->getUnzFile(), extra.data(), static_cast<uint>(extra.size()));
550     if (err < 0) {
551         p->setZipError(err);
552         return QByteArray();
553     }
554     return extra;
555 }
556 
getExtModTime()557 QDateTime QuaZipFile::getExtModTime()
558 {
559     return QuaZipFileInfo64::getExtTime(getLocalExtraField(), QUAZIP_EXTRA_EXT_MOD_TIME_FLAG);
560 }
561 
getExtAcTime()562 QDateTime QuaZipFile::getExtAcTime()
563 {
564     return QuaZipFileInfo64::getExtTime(getLocalExtraField(), QUAZIP_EXTRA_EXT_AC_TIME_FLAG);
565 }
566 
getExtCrTime()567 QDateTime QuaZipFile::getExtCrTime()
568 {
569     return QuaZipFileInfo64::getExtTime(getLocalExtraField(), QUAZIP_EXTRA_EXT_CR_TIME_FLAG);
570 }
571