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 and contributors,
22 see quazip/(un)zip.h files for details. Basically it's the zlib license.
23 */
24 
25 #include "quazipdir.h"
26 #include "quazip_qt_compat.h"
27 
28 #include <QtCore/QSet>
29 #include <QtCore/QSharedData>
30 
31 /// \cond internal
32 class QuaZipDirPrivate: public QSharedData {
33     friend class QuaZipDir;
34 private:
QuaZipDirPrivate(QuaZip * zip,const QString & dir=QString ())35     QuaZipDirPrivate(QuaZip *zip, const QString &dir = QString()):
36         zip(zip), dir(dir), caseSensitivity(QuaZip::csDefault),
37         filter(QDir::NoFilter), sorting(QDir::NoSort) {}
38     QuaZip *zip;
39     QString dir;
40     QuaZip::CaseSensitivity caseSensitivity;
41     QDir::Filters filter;
42     QStringList nameFilters;
43     QDir::SortFlags sorting;
44     template<typename TFileInfoList>
45     bool entryInfoList(QStringList nameFilters, QDir::Filters filter,
46         QDir::SortFlags sort, TFileInfoList &result) const;
simplePath() const47     inline QString simplePath() const {return QDir::cleanPath(dir);}
48 };
49 /// \endcond
50 
QuaZipDir(const QuaZipDir & that)51 QuaZipDir::QuaZipDir(const QuaZipDir &that):
52     d(that.d)
53 {
54 }
55 
QuaZipDir(QuaZip * zip,const QString & dir)56 QuaZipDir::QuaZipDir(QuaZip *zip, const QString &dir):
57     d(new QuaZipDirPrivate(zip, dir))
58 {
59     if (d->dir.startsWith(QLatin1String("/")))
60         d->dir = d->dir.mid(1);
61 }
62 
~QuaZipDir()63 QuaZipDir::~QuaZipDir()
64 {
65 }
66 
operator ==(const QuaZipDir & that)67 bool QuaZipDir::operator==(const QuaZipDir &that)
68 {
69     return d->zip == that.d->zip && d->dir == that.d->dir;
70 }
71 
operator =(const QuaZipDir & that)72 QuaZipDir& QuaZipDir::operator=(const QuaZipDir &that)
73 {
74     this->d = that.d;
75     return *this;
76 }
77 
operator [](int pos) const78 QString QuaZipDir::operator[](int pos) const
79 {
80     return entryList().at(pos);
81 }
82 
caseSensitivity() const83 QuaZip::CaseSensitivity QuaZipDir::caseSensitivity() const
84 {
85     return d->caseSensitivity;
86 }
87 
cd(const QString & directoryName)88 bool QuaZipDir::cd(const QString &directoryName)
89 {
90     if (directoryName == QLatin1String("/")) {
91         d->dir = QLatin1String("");
92         return true;
93     }
94     QString dirName = directoryName;
95     if (dirName.endsWith(QLatin1String("/")))
96         dirName.chop(1);
97     if (dirName.contains(QLatin1String("/"))) {
98         QuaZipDir dir(*this);
99         if (dirName.startsWith(QLatin1String("/"))) {
100 #ifdef QUAZIP_QUAZIPDIR_DEBUG
101             qDebug("QuaZipDir::cd(%s): going to /",
102                     dirName.toUtf8().constData());
103 #endif
104             if (!dir.cd(QLatin1String("/")))
105                 return false;
106         }
107         QStringList path = dirName.split(QLatin1String("/"), SkipEmptyParts);
108         for (QStringList::const_iterator i = path.constBegin();
109                 i != path.constEnd();
110                 ++i) {
111             const QString &step = *i;
112 #ifdef QUAZIP_QUAZIPDIR_DEBUG
113             qDebug("QuaZipDir::cd(%s): going to %s",
114                     dirName.toUtf8().constData(),
115                     step.toUtf8().constData());
116 #endif
117             if (!dir.cd(step))
118                 return false;
119         }
120         d->dir = dir.path();
121         return true;
122     } else { // no '/'
123         if (dirName == QLatin1String(".")) {
124             return true;
125         } else if (dirName == QLatin1String("..")) {
126             if (isRoot()) {
127                 return false;
128             } else {
129                 int slashPos = d->dir.lastIndexOf(QLatin1String("/"));
130                 if (slashPos == -1) {
131                     d->dir = QLatin1String("");
132                 } else {
133                     d->dir = d->dir.left(slashPos);
134                 }
135                 return true;
136             }
137         } else { // a simple subdirectory
138             if (exists(dirName)) {
139                 if (isRoot())
140                     d->dir = dirName;
141                 else
142                     d->dir += QLatin1String("/") + dirName;
143                 return true;
144             } else {
145                 return false;
146             }
147         }
148     }
149 }
150 
cdUp()151 bool QuaZipDir::cdUp()
152 {
153     return cd(QLatin1String(".."));
154 }
155 
count() const156 uint QuaZipDir::count() const
157 {
158     return entryList().count();
159 }
160 
dirName() const161 QString QuaZipDir::dirName() const
162 {
163     return QDir(d->dir).dirName();
164 }
165 
QuaZipDir_getFileInfo(QuaZip * zip,bool * ok,const QString & relativeName,bool isReal)166 QuaZipFileInfo64 QuaZipDir_getFileInfo(QuaZip *zip, bool *ok,
167                                   const QString &relativeName,
168                                   bool isReal)
169 {
170     QuaZipFileInfo64 info;
171     if (isReal) {
172         *ok = zip->getCurrentFileInfo(&info);
173     } else {
174         *ok = true;
175         info.compressedSize = 0;
176         info.crc = 0;
177         info.diskNumberStart = 0;
178         info.externalAttr = 0;
179         info.flags = 0;
180         info.internalAttr = 0;
181         info.method = 0;
182         info.uncompressedSize = 0;
183         info.versionCreated = info.versionNeeded = 0;
184     }
185     info.name = relativeName;
186     return info;
187 }
188 
QuaZipDir_convertInfoList(const QList<QuaZipFileInfo64> & from,QList<QuaZipFileInfo64> & to)189 static void QuaZipDir_convertInfoList(const QList<QuaZipFileInfo64> &from,
190                                       QList<QuaZipFileInfo64> &to)
191 {
192     to = from;
193 }
194 
QuaZipDir_convertInfoList(const QList<QuaZipFileInfo64> & from,QStringList & to)195 static void QuaZipDir_convertInfoList(const QList<QuaZipFileInfo64> &from,
196                                       QStringList &to)
197 {
198     to.clear();
199     for (QList<QuaZipFileInfo64>::const_iterator i = from.constBegin();
200             i != from.constEnd();
201             ++i) {
202         to.append(i->name);
203     }
204 }
205 
QuaZipDir_convertInfoList(const QList<QuaZipFileInfo64> & from,QList<QuaZipFileInfo> & to)206 static void QuaZipDir_convertInfoList(const QList<QuaZipFileInfo64> &from,
207                                       QList<QuaZipFileInfo> &to)
208 {
209     to.clear();
210     for (QList<QuaZipFileInfo64>::const_iterator i = from.constBegin();
211             i != from.constEnd();
212             ++i) {
213         QuaZipFileInfo info32;
214         i->toQuaZipFileInfo(info32);
215         to.append(info32);
216     }
217 }
218 
219 /// \cond internal
220 /**
221   An utility class to restore the current file.
222   */
223 class QuaZipDirRestoreCurrent {
224 public:
QuaZipDirRestoreCurrent(QuaZip * zip)225     inline QuaZipDirRestoreCurrent(QuaZip *zip):
226         zip(zip), currentFile(zip->getCurrentFileName()) {}
~QuaZipDirRestoreCurrent()227     inline ~QuaZipDirRestoreCurrent()
228     {
229         zip->setCurrentFile(currentFile);
230     }
231 private:
232     QuaZip *zip;
233     QString currentFile;
234 };
235 /// \endcond
236 
237 /// \cond internal
238 class QuaZipDirComparator
239 {
240     private:
241         QDir::SortFlags sort;
242         static QString getExtension(const QString &name);
243         int compareStrings(const QString &string1, const QString &string2);
244     public:
QuaZipDirComparator(QDir::SortFlags sort)245         inline QuaZipDirComparator(QDir::SortFlags sort): sort(sort) {}
246         bool operator()(const QuaZipFileInfo64 &info1, const QuaZipFileInfo64 &info2);
247 };
248 
getExtension(const QString & name)249 QString QuaZipDirComparator::getExtension(const QString &name)
250 {
251     if (name.endsWith(QLatin1String(".")) || name.indexOf(QLatin1String("."), 1) == -1) {
252         return QLatin1String("");
253     } else {
254         return name.mid(name.lastIndexOf(QLatin1String(".")) + 1);
255     }
256 
257 }
258 
compareStrings(const QString & string1,const QString & string2)259 int QuaZipDirComparator::compareStrings(const QString &string1,
260         const QString &string2)
261 {
262     if (sort & QDir::LocaleAware) {
263         if (sort & QDir::IgnoreCase) {
264             return string1.toLower().localeAwareCompare(string2.toLower());
265         } else {
266             return string1.localeAwareCompare(string2);
267         }
268     } else {
269         return string1.compare(string2, (sort & QDir::IgnoreCase)
270                 ? Qt::CaseInsensitive : Qt::CaseSensitive);
271     }
272 }
273 
operator ()(const QuaZipFileInfo64 & info1,const QuaZipFileInfo64 & info2)274 bool QuaZipDirComparator::operator()(const QuaZipFileInfo64 &info1,
275         const QuaZipFileInfo64 &info2)
276 {
277     QDir::SortFlags order = sort
278         & (QDir::Name | QDir::Time | QDir::Size | QDir::Type);
279     if ((sort & QDir::DirsFirst) == QDir::DirsFirst
280             || (sort & QDir::DirsLast) == QDir::DirsLast) {
281         if (info1.name.endsWith(QLatin1String("/")) && !info2.name.endsWith(QLatin1String("/")))
282             return (sort & QDir::DirsFirst) == QDir::DirsFirst;
283         else if (!info1.name.endsWith(QLatin1String("/")) && info2.name.endsWith(QLatin1String("/")))
284             return (sort & QDir::DirsLast) == QDir::DirsLast;
285     }
286     bool result;
287     int extDiff;
288     switch (order) {
289         case QDir::Name:
290             result = compareStrings(info1.name, info2.name) < 0;
291             break;
292         case QDir::Type:
293             extDiff = compareStrings(getExtension(info1.name),
294                     getExtension(info2.name));
295             if (extDiff == 0) {
296                 result = compareStrings(info1.name, info2.name) < 0;
297             } else {
298                 result = extDiff < 0;
299             }
300             break;
301         case QDir::Size:
302             if (info1.uncompressedSize == info2.uncompressedSize) {
303                 result = compareStrings(info1.name, info2.name) < 0;
304             } else {
305                 result = info1.uncompressedSize < info2.uncompressedSize;
306             }
307             break;
308         case QDir::Time:
309             if (info1.dateTime == info2.dateTime) {
310                 result = compareStrings(info1.name, info2.name) < 0;
311             } else {
312                 result = info1.dateTime < info2.dateTime;
313             }
314             break;
315         default:
316             qWarning("QuaZipDirComparator(): Invalid sort mode 0x%2X",
317                     static_cast<unsigned>(sort));
318             return false;
319     }
320     return (sort & QDir::Reversed) ? !result : result;
321 }
322 
323 template<typename TFileInfoList>
entryInfoList(QStringList nameFilters,QDir::Filters filter,QDir::SortFlags sort,TFileInfoList & result) const324 bool QuaZipDirPrivate::entryInfoList(QStringList nameFilters,
325     QDir::Filters filter, QDir::SortFlags sort, TFileInfoList &result) const
326 {
327     QString basePath = simplePath();
328     if (!basePath.isEmpty())
329         basePath += QLatin1String("/");
330     int baseLength = basePath.length();
331     result.clear();
332     QuaZipDirRestoreCurrent saveCurrent(zip);
333     if (!zip->goToFirstFile()) {
334         return zip->getZipError() == UNZ_OK;
335     }
336     QDir::Filters fltr = filter;
337     if (fltr == QDir::NoFilter)
338         fltr = this->filter;
339     if (fltr == QDir::NoFilter)
340         fltr = QDir::AllEntries;
341     QStringList nmfltr = nameFilters;
342     if (nmfltr.isEmpty())
343         nmfltr = this->nameFilters;
344     QSet<QString> dirsFound;
345     QList<QuaZipFileInfo64> list;
346     do {
347         QString name = zip->getCurrentFileName();
348         if (!name.startsWith(basePath))
349             continue;
350         QString relativeName = name.mid(baseLength);
351         if (relativeName.isEmpty())
352             continue;
353         bool isDir = false;
354         bool isReal = true;
355         if (relativeName.contains(QLatin1String("/"))) {
356             int indexOfSlash = relativeName.indexOf(QLatin1String("/"));
357             // something like "subdir/"
358             isReal = indexOfSlash == relativeName.length() - 1;
359             relativeName = relativeName.left(indexOfSlash + 1);
360             if (dirsFound.contains(relativeName))
361                 continue;
362             isDir = true;
363         }
364         dirsFound.insert(relativeName);
365         if ((fltr & QDir::Dirs) == 0 && isDir)
366             continue;
367         if ((fltr & QDir::Files) == 0 && !isDir)
368             continue;
369         if (!nmfltr.isEmpty() && !QDir::match(nmfltr, relativeName))
370             continue;
371         bool ok;
372         QuaZipFileInfo64 info = QuaZipDir_getFileInfo(zip, &ok, relativeName,
373             isReal);
374         if (!ok) {
375             return false;
376         }
377         list.append(info);
378     } while (zip->goToNextFile());
379     QDir::SortFlags srt = sort;
380     if (srt == QDir::NoSort)
381         srt = sorting;
382 #ifdef QUAZIP_QUAZIPDIR_DEBUG
383     qDebug("QuaZipDirPrivate::entryInfoList(): before sort:");
384     foreach (QuaZipFileInfo64 info, list) {
385         qDebug("%s\t%s", info.name.toUtf8().constData(),
386                 info.dateTime.toString(Qt::ISODate).toUtf8().constData());
387     }
388 #endif
389     if (srt != QDir::NoSort && (srt & QDir::Unsorted) != QDir::Unsorted) {
390         if (QuaZip::convertCaseSensitivity(caseSensitivity)
391                 == Qt::CaseInsensitive)
392             srt |= QDir::IgnoreCase;
393         QuaZipDirComparator lessThan(srt);
394         quazip_sort(list.begin(), list.end(), lessThan);
395     }
396     QuaZipDir_convertInfoList(list, result);
397     return true;
398 }
399 
400 /// \endcond
401 
entryInfoList(const QStringList & nameFilters,QDir::Filters filters,QDir::SortFlags sort) const402 QList<QuaZipFileInfo> QuaZipDir::entryInfoList(const QStringList &nameFilters,
403     QDir::Filters filters, QDir::SortFlags sort) const
404 {
405     QList<QuaZipFileInfo> result;
406     if (d->entryInfoList(nameFilters, filters, sort, result))
407         return result;
408     else
409         return QList<QuaZipFileInfo>();
410 }
411 
entryInfoList(QDir::Filters filters,QDir::SortFlags sort) const412 QList<QuaZipFileInfo> QuaZipDir::entryInfoList(QDir::Filters filters,
413     QDir::SortFlags sort) const
414 {
415     return entryInfoList(QStringList(), filters, sort);
416 }
417 
entryInfoList64(const QStringList & nameFilters,QDir::Filters filters,QDir::SortFlags sort) const418 QList<QuaZipFileInfo64> QuaZipDir::entryInfoList64(const QStringList &nameFilters,
419     QDir::Filters filters, QDir::SortFlags sort) const
420 {
421     QList<QuaZipFileInfo64> result;
422     if (d->entryInfoList(nameFilters, filters, sort, result))
423         return result;
424     else
425         return QList<QuaZipFileInfo64>();
426 }
427 
entryInfoList64(QDir::Filters filters,QDir::SortFlags sort) const428 QList<QuaZipFileInfo64> QuaZipDir::entryInfoList64(QDir::Filters filters,
429     QDir::SortFlags sort) const
430 {
431     return entryInfoList64(QStringList(), filters, sort);
432 }
433 
entryList(const QStringList & nameFilters,QDir::Filters filters,QDir::SortFlags sort) const434 QStringList QuaZipDir::entryList(const QStringList &nameFilters,
435     QDir::Filters filters, QDir::SortFlags sort) const
436 {
437     QStringList result;
438     if (d->entryInfoList(nameFilters, filters, sort, result))
439         return result;
440     else
441         return QStringList();
442 }
443 
entryList(QDir::Filters filters,QDir::SortFlags sort) const444 QStringList QuaZipDir::entryList(QDir::Filters filters,
445     QDir::SortFlags sort) const
446 {
447     return entryList(QStringList(), filters, sort);
448 }
449 
exists(const QString & filePath) const450 bool QuaZipDir::exists(const QString &filePath) const
451 {
452     if (filePath == QLatin1String("/") || filePath.isEmpty())
453         return true;
454     QString fileName = filePath;
455     if (fileName.endsWith(QLatin1String("/")))
456         fileName.chop(1);
457     if (fileName.contains(QLatin1String("/"))) {
458         QFileInfo fileInfo(fileName);
459 #ifdef QUAZIP_QUAZIPDIR_DEBUG
460         qDebug("QuaZipDir::exists(): fileName=%s, fileInfo.fileName()=%s, "
461                 "fileInfo.path()=%s", fileName.toUtf8().constData(),
462                 fileInfo.fileName().toUtf8().constData(),
463                 fileInfo.path().toUtf8().constData());
464 #endif
465         QuaZipDir dir(*this);
466         return dir.cd(fileInfo.path()) && dir.exists(fileInfo.fileName());
467     } else {
468         if (fileName == QLatin1String("..")) {
469             return !isRoot();
470         } else if (fileName == QLatin1String(".")) {
471             return true;
472         } else {
473             QStringList entries = entryList(QDir::AllEntries, QDir::NoSort);
474 #ifdef QUAZIP_QUAZIPDIR_DEBUG
475             qDebug("QuaZipDir::exists(): looking for %s",
476                     fileName.toUtf8().constData());
477             for (QStringList::const_iterator i = entries.constBegin();
478                     i != entries.constEnd();
479                     ++i) {
480                 qDebug("QuaZipDir::exists(): entry: %s",
481                         i->toUtf8().constData());
482             }
483 #endif
484             Qt::CaseSensitivity cs = QuaZip::convertCaseSensitivity(
485                     d->caseSensitivity);
486             if (filePath.endsWith(QLatin1String("/"))) {
487                 return entries.contains(filePath, cs);
488             } else {
489                 return entries.contains(fileName, cs)
490                     || entries.contains(fileName + QLatin1String("/"), cs);
491             }
492         }
493     }
494 }
495 
exists() const496 bool QuaZipDir::exists() const
497 {
498     return QuaZipDir(d->zip).exists(d->dir);
499 }
500 
filePath(const QString & fileName) const501 QString QuaZipDir::filePath(const QString &fileName) const
502 {
503     return QDir(d->dir).filePath(fileName);
504 }
505 
filter()506 QDir::Filters QuaZipDir::filter()
507 {
508     return d->filter;
509 }
510 
isRoot() const511 bool QuaZipDir::isRoot() const
512 {
513     return d->simplePath().isEmpty();
514 }
515 
nameFilters() const516 QStringList QuaZipDir::nameFilters() const
517 {
518     return d->nameFilters;
519 }
520 
path() const521 QString QuaZipDir::path() const
522 {
523     return d->dir;
524 }
525 
relativeFilePath(const QString & fileName) const526 QString QuaZipDir::relativeFilePath(const QString &fileName) const
527 {
528     return QDir(QLatin1String("/") + d->dir).relativeFilePath(fileName);
529 }
530 
setCaseSensitivity(QuaZip::CaseSensitivity caseSensitivity)531 void QuaZipDir::setCaseSensitivity(QuaZip::CaseSensitivity caseSensitivity)
532 {
533     d->caseSensitivity = caseSensitivity;
534 }
535 
setFilter(QDir::Filters filters)536 void QuaZipDir::setFilter(QDir::Filters filters)
537 {
538     d->filter = filters;
539 }
540 
setNameFilters(const QStringList & nameFilters)541 void QuaZipDir::setNameFilters(const QStringList &nameFilters)
542 {
543     d->nameFilters = nameFilters;
544 }
545 
setPath(const QString & path)546 void QuaZipDir::setPath(const QString &path)
547 {
548     QString newDir = path;
549     if (newDir == QLatin1String("/")) {
550         d->dir = QLatin1String("");
551     } else {
552         if (newDir.endsWith(QLatin1String("/")))
553             newDir.chop(1);
554         if (newDir.startsWith(QLatin1String("/")))
555             newDir = newDir.mid(1);
556         d->dir = newDir;
557     }
558 }
559 
setSorting(QDir::SortFlags sort)560 void QuaZipDir::setSorting(QDir::SortFlags sort)
561 {
562     d->sorting = sort;
563 }
564 
sorting() const565 QDir::SortFlags QuaZipDir::sorting() const
566 {
567     return d->sorting;
568 }
569