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