MythTV  master
unzip.cpp
Go to the documentation of this file.
1 /****************************************************************************
2 ** Filename: unzip.cpp
3 ** Last updated [dd/mm/yyyy]: 07/09/2008
4 **
5 ** pkzip 2.0 decompression.
6 **
7 ** Some of the code has been inspired by other open source projects,
8 ** (mainly Info-Zip and Gilles Vollant's minizip).
9 ** Compression and decompression actually uses the zlib library.
10 **
11 ** Copyright (C) 2007-2008 Angius Fabrizio. All rights reserved.
12 **
13 ** This file is part of the OSDaB project (http://osdab.sourceforge.net/).
14 **
15 ** This file may be distributed and/or modified under the terms of the
16 ** GNU General Public License version 2 as published by the Free Software
17 ** Foundation and appearing in the file LICENSE.GPL included in the
18 ** packaging of this file.
19 **
20 ** This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE
21 ** WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
22 **
23 ** See the file LICENSE.GPL that came with this software distribution or
24 ** visit http://www.gnu.org/copyleft/gpl.html for GPL licensing information.
25 **
26 **********************************************************************/
27 
28 #include "unzip.h"
29 #include "unzip_p.h"
30 #include "zipentry_p.h"
31 
32 #include <QString>
33 #include <QStringList>
34 #include <QDir>
35 #include <QFile>
36 #include <QCoreApplication>
37 
38 // You can remove this #include if you replace the qDebug() statements.
39 #include <QtDebug>
40 
119 #define UNZIP_LOCAL_HEADER_SIZE 26
121 #define UNZIP_CD_ENTRY_SIZE_NS 42
123 #define UNZIP_DD_SIZE 12
125 #define UNZIP_EOCD_SIZE 22
127 #define UNZIP_LOCAL_ENC_HEADER_SIZE 12
129 
130 // Some offsets inside a CD record (excluding signature)
131 #define UNZIP_CD_OFF_VERSION 0
132 #define UNZIP_CD_OFF_GPFLAG 4
133 #define UNZIP_CD_OFF_CMETHOD 6
134 #define UNZIP_CD_OFF_MODT 8
135 #define UNZIP_CD_OFF_MODD 10
136 #define UNZIP_CD_OFF_CRC32 12
137 #define UNZIP_CD_OFF_CSIZE 16
138 #define UNZIP_CD_OFF_USIZE 20
139 #define UNZIP_CD_OFF_NAMELEN 24
140 #define UNZIP_CD_OFF_XLEN 26
141 #define UNZIP_CD_OFF_COMMLEN 28
142 #define UNZIP_CD_OFF_LHOFFSET 38
143 
144 // Some offsets inside a local header record (excluding signature)
145 #define UNZIP_LH_OFF_VERSION 0
146 #define UNZIP_LH_OFF_GPFLAG 2
147 #define UNZIP_LH_OFF_CMETHOD 4
148 #define UNZIP_LH_OFF_MODT 6
149 #define UNZIP_LH_OFF_MODD 8
150 #define UNZIP_LH_OFF_CRC32 10
151 #define UNZIP_LH_OFF_CSIZE 14
152 #define UNZIP_LH_OFF_USIZE 18
153 #define UNZIP_LH_OFF_NAMELEN 22
154 #define UNZIP_LH_OFF_XLEN 24
155 
156 // Some offsets inside a data descriptor record (excluding signature)
157 #define UNZIP_DD_OFF_CRC32 0
158 #define UNZIP_DD_OFF_CSIZE 4
159 #define UNZIP_DD_OFF_USIZE 8
160 
161 // Some offsets inside a EOCD record
162 #define UNZIP_EOCD_OFF_ENTRIES 6
163 #define UNZIP_EOCD_OFF_CDOFF 12
164 #define UNZIP_EOCD_OFF_COMMLEN 16
165 
172 #define UNZIP_VERSION 0x1B
173 #define UNZIP_VERSION_STRICT 0x14
175 
177 #define CRC32(c, b) crcTable[((int)(c)^(b)) & 0xff] ^ ((c) >> 8)
178 
180 #define UNZIP_CHECK_FOR_VALID_DATA \
181  {\
182  if (headers != nullptr)\
183  {\
184  qDebug() << "Corrupted zip archive. Some files might be extracted.";\
185  ec = headers->empty() ? UnZip::Corrupted : UnZip::PartiallyCorrupted;\
186  break;\
187  }\
188  delete device; \
189  device = nullptr; \
190  qDebug() << "Corrupted or invalid zip archive"; \
191  ec = UnZip::Corrupted; \
192  break; \
193  }
194 
195 
196 /************************************************************************
197  Public interface
198 *************************************************************************/
199 
204 {
205  d = new UnzipPrivate;
206 }
207 
212 {
213  closeArchive();
214  delete d;
215 }
216 
220 bool UnZip::isOpen() const
221 {
222  return d->device != nullptr;
223 }
224 
228 UnZip::ErrorCode UnZip::openArchive(const QString& filename)
229 {
230  QFile* file = new QFile(filename);
231 
232  if (!file->exists()) {
233  delete file;
234  return UnZip::FileNotFound;
235  }
236 
237  if (!file->open(QIODevice::ReadOnly)) {
238  delete file;
239  return UnZip::OpenFailed;
240  }
241 
242  return openArchive(file);
243 }
244 
251 {
252  if (device == nullptr)
253  {
254  qDebug() << "Invalid device.";
255  return UnZip::InvalidDevice;
256  }
257 
258  return d->openArchive(device);
259 }
260 
265 {
266  d->closeArchive();
267 }
268 
269 QString UnZip::archiveComment() const
270 {
271  if (d->device == nullptr)
272  return QString();
273  return d->comment;
274 }
275 
280 {
281  switch (c)
282  {
283  case Ok: return QCoreApplication::translate("UnZip", "ZIP operation completed successfully."); break;
284  case ZlibInit: return QCoreApplication::translate("UnZip", "Failed to initialize or load zlib library."); break;
285  case ZlibError: return QCoreApplication::translate("UnZip", "zlib library error."); break;
286  case OpenFailed: return QCoreApplication::translate("UnZip", "Unable to create or open file."); break;
287  case PartiallyCorrupted: return QCoreApplication::translate("UnZip", "Partially corrupted archive. Some files might be extracted."); break;
288  case Corrupted: return QCoreApplication::translate("UnZip", "Corrupted archive."); break;
289  case WrongPassword: return QCoreApplication::translate("UnZip", "Wrong password."); break;
290  case NoOpenArchive: return QCoreApplication::translate("UnZip", "No archive has been created yet."); break;
291  case FileNotFound: return QCoreApplication::translate("UnZip", "File or directory does not exist."); break;
292  case ReadFailed: return QCoreApplication::translate("UnZip", "File read error."); break;
293  case WriteFailed: return QCoreApplication::translate("UnZip", "File write error."); break;
294  case SeekFailed: return QCoreApplication::translate("UnZip", "File seek error."); break;
295  case CreateDirFailed: return QCoreApplication::translate("UnZip", "Unable to create a directory."); break;
296  case InvalidDevice: return QCoreApplication::translate("UnZip", "Invalid device."); break;
297  case InvalidArchive: return QCoreApplication::translate("UnZip", "Invalid or incompatible zip archive."); break;
298  case HeaderConsistencyError: return QCoreApplication::translate("UnZip", "Inconsistent headers. Archive might be corrupted."); break;
299  default: ;
300  }
301 
302  return QCoreApplication::translate("UnZip", "Unknown error.");
303 }
304 
308 bool UnZip::contains(const QString& file) const
309 {
310  if (d->headers == nullptr)
311  return false;
312 
313  return d->headers->contains(file);
314 }
315 
319 QStringList UnZip::fileList() const
320 {
321  return d->headers == nullptr ? QStringList() : d->headers->keys();
322 }
323 
327 QList<UnZip::ZipEntry> UnZip::entryList() const
328 {
329  QList<UnZip::ZipEntry> list;
330 
331  if (d->headers != nullptr)
332  {
333  for (QMap<QString,ZipEntryP*>::ConstIterator it = d->headers->constBegin(); it != d->headers->constEnd(); ++it)
334  {
335  const ZipEntryP* entry = it.value();
336  Q_ASSERT(entry != nullptr);
337 
338  ZipEntry z;
339 
340  z.filename = it.key();
341  if (!entry->comment.isEmpty())
342  z.comment = entry->comment;
343  z.compressedSize = entry->szComp;
344  z.uncompressedSize = entry->szUncomp;
345  z.crc32 = entry->crc;
346  z.lastModified = d->convertDateTime(entry->modDate, entry->modTime);
347 
348  z.compression = entry->compMethod == 0 ? NoCompression : entry->compMethod == 8 ? Deflated : UnknownCompression;
349  z.type = z.filename.endsWith("/") ? Directory : File;
350 
351  z.encrypted = entry->isEncrypted();
352 
353  list.append(z);
354  }
355  }
356 
357  return list;
358 }
359 
363 UnZip::ErrorCode UnZip::extractAll(const QString& dirname, ExtractionOptions options)
364 {
365  return extractAll(QDir(dirname), options);
366 }
367 
371 UnZip::ErrorCode UnZip::extractAll(const QDir& dir, ExtractionOptions options)
372 {
373  // this should only happen if we didn't call openArchive() yet
374  if (d->device == nullptr)
375  return NoOpenArchive;
376 
377  if (d->headers == nullptr)
378  return Ok;
379 
380  bool end = false;
381  for (QMap<QString,ZipEntryP*>::Iterator itr = d->headers->begin(); itr != d->headers->end(); ++itr)
382  {
383  ZipEntryP* entry = itr.value();
384  Q_ASSERT(entry != nullptr);
385 
386  if ((entry->isEncrypted()) && d->skipAllEncrypted)
387  continue;
388 
389  switch (d->extractFile(itr.key(), *entry, dir, options))
390  {
391  case Corrupted:
392  qDebug() << "Removing corrupted entry" << itr.key();
393  d->headers->erase(itr++);
394  if (itr == d->headers->end())
395  end = true;
396  break;
397  case CreateDirFailed:
398  break;
399  case Skip:
400  break;
401  case SkipAll:
402  d->skipAllEncrypted = true;
403  break;
404  default:
405  ;
406  }
407 
408  if (end)
409  break;
410  }
411 
412  return Ok;
413 }
414 
418 UnZip::ErrorCode UnZip::extractFile(const QString& filename, const QString& dirname, ExtractionOptions options)
419 {
420  return extractFile(filename, QDir(dirname), options);
421 }
422 
426 UnZip::ErrorCode UnZip::extractFile(const QString& filename, const QDir& dir, ExtractionOptions options)
427 {
428  QMap<QString,ZipEntryP*>::Iterator itr = d->headers->find(filename);
429  if (itr != d->headers->end())
430  {
431  ZipEntryP* entry = itr.value();
432  Q_ASSERT(entry != nullptr);
433  return d->extractFile(itr.key(), *entry, dir, options);
434  }
435 
436  return FileNotFound;
437 }
438 
442 UnZip::ErrorCode UnZip::extractFile(const QString& filename, QIODevice* dev, ExtractionOptions options)
443 {
444  if (dev == nullptr)
445  return InvalidDevice;
446 
447  QMap<QString,ZipEntryP*>::Iterator itr = d->headers->find(filename);
448  if (itr != d->headers->end()) {
449  ZipEntryP* entry = itr.value();
450  Q_ASSERT(entry != nullptr);
451  return d->extractFile(itr.key(), *entry, dev, options);
452  }
453 
454  return FileNotFound;
455 }
456 
461 UnZip::ErrorCode UnZip::extractFiles(const QStringList& filenames, const QString& dirname, ExtractionOptions options)
462 {
463  QDir dir(dirname);
464  ErrorCode ec;
465 
466  for (QStringList::ConstIterator itr = filenames.constBegin(); itr != filenames.constEnd(); ++itr)
467  {
468  ec = extractFile(*itr, dir, options);
469  if (ec == FileNotFound)
470  continue;
471  if (ec != Ok)
472  return ec;
473  }
474 
475  return Ok;
476 }
477 
482 UnZip::ErrorCode UnZip::extractFiles(const QStringList& filenames, const QDir& dir, ExtractionOptions options)
483 {
484  ErrorCode ec;
485 
486  for (QStringList::ConstIterator itr = filenames.constBegin(); itr != filenames.constEnd(); ++itr)
487  {
488  ec = extractFile(*itr, dir, options);
489  if (ec == FileNotFound)
490  continue;
491  if (ec != Ok)
492  return ec;
493  }
494 
495  return Ok;
496 }
497 
501 void UnZip::setPassword(const QString& pwd)
502 {
503  d->password = pwd;
504 }
505 
510 {
513  type = File;
514  encrypted = false;
515 }
516 
517 
518 /************************************************************************
519  Private interface
520 *************************************************************************/
521 
524 {
525  skipAllEncrypted = false;
526  headers = nullptr;
527  device = nullptr;
528 
529  memset(buffer1, 0, sizeof(buffer1));
530  memset(buffer2, 0, sizeof(buffer2));
531 
532  uBuffer = (unsigned char*) buffer1;
533  crcTable = (quint32*) get_crc_table();
534 
535  cdOffset = eocdOffset = 0;
536  cdEntryCount = 0;
537  unsupportedEntryCount = 0;
538 }
539 
542 {
543  Q_ASSERT(dev != nullptr);
544 
545  if (device != nullptr)
546  closeArchive();
547 
548  device = dev;
549 
550  if (!(device->isOpen() || device->open(QIODevice::ReadOnly)))
551  {
552  delete device;
553  device = nullptr;
554 
555  qDebug() << "Unable to open device for reading";
556  return UnZip::OpenFailed;
557  }
558 
559  UnZip::ErrorCode ec;
560 
561  ec = seekToCentralDirectory();
562  if (ec != UnZip::Ok)
563  {
564  closeArchive();
565  return ec;
566  }
567 
569  if (cdEntryCount == 0)
570  {
571  return UnZip::Ok;
572  }
573 
574  bool continueParsing = true;
575 
576  while (continueParsing)
577  {
578  if (device->read(buffer1, 4) != 4)
580 
581  if (! (buffer1[0] == 'P' && buffer1[1] == 'K' && buffer1[2] == 0x01 && buffer1[3] == 0x02) )
582  break;
583 
584  if ( (ec = parseCentralDirectoryRecord()) != UnZip::Ok )
585  break;
586  }
587 
588  if (ec != UnZip::Ok)
589  closeArchive();
590 
591  return ec;
592 }
593 
594 /*
595  \internal Parses a local header record and makes some consistency check
596  with the information stored in the Central Directory record for this entry
597  that has been previously parsed.
598  \todo Optional consistency check (as a ExtractionOptions flag)
599 
600  local file header signature 4 bytes (0x04034b50)
601  version needed to extract 2 bytes
602  general purpose bit flag 2 bytes
603  compression method 2 bytes
604  last mod file time 2 bytes
605  last mod file date 2 bytes
606  crc-32 4 bytes
607  compressed size 4 bytes
608  uncompressed size 4 bytes
609  file name length 2 bytes
610  extra field length 2 bytes
611 
612  file name (variable size)
613  extra field (variable size)
614 */
616 {
617  if (!device->seek(entry.lhOffset))
618  return UnZip::SeekFailed;
619 
620  // Test signature
621  if (device->read(buffer1, 4) != 4)
622  return UnZip::ReadFailed;
623 
624  if ((buffer1[0] != 'P') || (buffer1[1] != 'K') || (buffer1[2] != 0x03) || (buffer1[3] != 0x04))
625  return UnZip::InvalidArchive;
626 
627  if (device->read(buffer1, UNZIP_LOCAL_HEADER_SIZE) != UNZIP_LOCAL_HEADER_SIZE)
628  return UnZip::ReadFailed;
629 
630  /*
631  Check 3rd general purpose bit flag.
632 
633  "bit 3: If this bit is set, the fields crc-32, compressed size
634  and uncompressed size are set to zero in the local
635  header. The correct values are put in the data descriptor
636  immediately following the compressed data."
637  */
638  bool hasDataDescriptor = entry.hasDataDescriptor();
639 
640  bool checkFailed = false;
641 
642  if (!checkFailed)
643  checkFailed = entry.compMethod != getUShort(uBuffer, UNZIP_LH_OFF_CMETHOD);
644  if (!checkFailed)
645  checkFailed = entry.gpFlag[0] != uBuffer[UNZIP_LH_OFF_GPFLAG];
646  if (!checkFailed)
647  checkFailed = entry.gpFlag[1] != uBuffer[UNZIP_LH_OFF_GPFLAG + 1];
648  if (!checkFailed)
649  checkFailed = entry.modTime[0] != uBuffer[UNZIP_LH_OFF_MODT];
650  if (!checkFailed)
651  checkFailed = entry.modTime[1] != uBuffer[UNZIP_LH_OFF_MODT + 1];
652  if (!checkFailed)
653  checkFailed = entry.modDate[0] != uBuffer[UNZIP_LH_OFF_MODD];
654  if (!checkFailed)
655  checkFailed = entry.modDate[1] != uBuffer[UNZIP_LH_OFF_MODD + 1];
656  if (!hasDataDescriptor)
657  {
658  if (!checkFailed)
659  checkFailed = entry.crc != getULong(uBuffer, UNZIP_LH_OFF_CRC32);
660  if (!checkFailed)
661  checkFailed = entry.szComp != getULong(uBuffer, UNZIP_LH_OFF_CSIZE);
662  if (!checkFailed)
663  checkFailed = entry.szUncomp != getULong(uBuffer, UNZIP_LH_OFF_USIZE);
664  }
665 
666  if (checkFailed)
668 
669  // Check filename
670  quint16 szName = getUShort(uBuffer, UNZIP_LH_OFF_NAMELEN);
671  if (szName == 0)
673 
674  if (device->read(buffer2, szName) != szName)
675  return UnZip::ReadFailed;
676 
677  QString filename = QString::fromLatin1(buffer2, szName);
678  if (filename != path)
679  {
680  qDebug() << "Filename in local header mismatches.";
682  }
683 
684  // Skip extra field
685  quint16 szExtra = getUShort(uBuffer, UNZIP_LH_OFF_XLEN);
686  if (szExtra != 0)
687  {
688  if (!device->seek(device->pos() + szExtra))
689  return UnZip::SeekFailed;
690  }
691 
692  entry.dataOffset = device->pos();
693 
694  if (hasDataDescriptor)
695  {
696  /*
697  The data descriptor has this OPTIONAL signature: PK\7\8
698  We try to skip the compressed data relying on the size set in the
699  Central Directory record.
700  */
701  if (!device->seek(device->pos() + entry.szComp))
702  return UnZip::SeekFailed;
703 
704  // Read 4 bytes and check if there is a data descriptor signature
705  if (device->read(buffer2, 4) != 4)
706  return UnZip::ReadFailed;
707 
708  bool hasSignature = buffer2[0] == 'P' && buffer2[1] == 'K' && buffer2[2] == 0x07 && buffer2[3] == 0x08;
709  if (hasSignature)
710  {
711  if (device->read(buffer2, UNZIP_DD_SIZE) != UNZIP_DD_SIZE)
712  return UnZip::ReadFailed;
713  }
714  else
715  {
716  if (device->read(buffer2 + 4, UNZIP_DD_SIZE - 4) != UNZIP_DD_SIZE - 4)
717  return UnZip::ReadFailed;
718  }
719 
720  // DD: crc, compressed size, uncompressed size
721  if (
722  entry.crc != getULong((unsigned char*)buffer2, UNZIP_DD_OFF_CRC32) ||
723  entry.szComp != getULong((unsigned char*)buffer2, UNZIP_DD_OFF_CSIZE) ||
724  entry.szUncomp != getULong((unsigned char*)buffer2, UNZIP_DD_OFF_USIZE)
725  )
727  }
728 
729  return UnZip::Ok;
730 }
731 
754 {
755  qint64 length = device->size();
756  qint64 offset = length - UNZIP_EOCD_SIZE;
757 
758  if (length < UNZIP_EOCD_SIZE)
759  return UnZip::InvalidArchive;
760 
761  if (!device->seek( offset ))
762  return UnZip::SeekFailed;
763 
764  if (device->read(buffer1, UNZIP_EOCD_SIZE) != UNZIP_EOCD_SIZE)
765  return UnZip::ReadFailed;
766 
767  bool eocdFound = (buffer1[0] == 'P' && buffer1[1] == 'K' && buffer1[2] == 0x05 && buffer1[3] == 0x06);
768 
769  if (eocdFound)
770  {
771  // Zip file has no comment (the only variable length field in the EOCD record)
772  eocdOffset = offset;
773  }
774  else
775  {
776  qint64 read;
777  char* p = nullptr;
778 
779  offset -= UNZIP_EOCD_SIZE;
780 
781  if (offset <= 0)
782  return UnZip::InvalidArchive;
783 
784  if (!device->seek( offset ))
785  return UnZip::SeekFailed;
786 
787  while ((read = device->read(buffer1, UNZIP_EOCD_SIZE)) >= 0)
788  {
789  if ( (p = strstr(buffer1, "PK\5\6")) != nullptr)
790  {
791  // Seek to the start of the EOCD record so we can read it fully
792  // Yes... we could simply read the missing bytes and append them to the buffer
793  // but this is far easier so heck it!
794  device->seek( offset + (p - buffer1) );
795  eocdFound = true;
796  eocdOffset = offset + (p - buffer1);
797 
798  // Read EOCD record
799  if (device->read(buffer1, UNZIP_EOCD_SIZE) != UNZIP_EOCD_SIZE)
800  return UnZip::ReadFailed;
801 
802  break;
803  }
804 
805  // TODO: This is very slow and only a temporary bug fix. Need some pattern matching algorithm here.
806  offset -= 1 /*UNZIP_EOCD_SIZE*/;
807  if (offset <= 0)
808  return UnZip::InvalidArchive;
809 
810  if (!device->seek( offset ))
811  return UnZip::SeekFailed;
812  }
813  }
814 
815  if (!eocdFound)
816  return UnZip::InvalidArchive;
817 
818  // Parse EOCD to locate CD offset
819  offset = getULong((const unsigned char*)buffer1, UNZIP_EOCD_OFF_CDOFF + 4);
820 
821  cdOffset = offset;
822 
823  cdEntryCount = getUShort((const unsigned char*)buffer1, UNZIP_EOCD_OFF_ENTRIES + 4);
824 
825  quint16 commentLength = getUShort((const unsigned char*)buffer1, UNZIP_EOCD_OFF_COMMLEN + 4);
826  if (commentLength != 0)
827  {
828  QByteArray c = device->read(commentLength);
829  if (c.count() != commentLength)
830  return UnZip::ReadFailed;
831 
832  comment = c;
833  }
834 
835  // Seek to the start of the CD record
836  if (!device->seek( cdOffset ))
837  return UnZip::SeekFailed;
838 
839  return UnZip::Ok;
840 }
841 
879 {
880  // Read CD record
881  if (device->read(buffer1, UNZIP_CD_ENTRY_SIZE_NS) != UNZIP_CD_ENTRY_SIZE_NS)
882  return UnZip::ReadFailed;
883 
884  bool skipEntry = false;
885 
886  // Get compression type so we can skip non compatible algorithms
887  quint16 compMethod = getUShort(uBuffer, UNZIP_CD_OFF_CMETHOD);
888 
889  // Get variable size fields length so we can skip the whole record
890  // if necessary
891  quint16 szName = getUShort(uBuffer, UNZIP_CD_OFF_NAMELEN);
892  quint16 szExtra = getUShort(uBuffer, UNZIP_CD_OFF_XLEN);
893  quint16 szComment = getUShort(uBuffer, UNZIP_CD_OFF_COMMLEN);
894 
895  quint32 skipLength = szName + szExtra + szComment;
896 
898 
899  if ((compMethod != 0) && (compMethod != 8))
900  {
901  qDebug() << "Unsupported compression method. Skipping file.";
902  skipEntry = true;
903  }
904 
905  // Header parsing may be a problem if version is bigger than UNZIP_VERSION
906  if (!skipEntry && buffer1[UNZIP_CD_OFF_VERSION] > UNZIP_VERSION)
907  {
908  qDebug() << "Unsupported PKZip version. Skipping file.";
909  skipEntry = true;
910  }
911 
912  if (!skipEntry && szName == 0)
913  {
914  qDebug() << "Skipping file with no name.";
915  skipEntry = true;
916  }
917 
918  if (!skipEntry && device->read(buffer2, szName) != szName)
919  {
920  ec = UnZip::ReadFailed;
921  skipEntry = true;
922  }
923 
924  if (skipEntry)
925  {
926  if (ec == UnZip::Ok)
927  {
928  if (!device->seek( device->pos() + skipLength ))
929  ec = UnZip::SeekFailed;
930 
931  unsupportedEntryCount++;
932  }
933 
934  return ec;
935  }
936 
937  QString filename = QString::fromLatin1(buffer2, szName);
938 
939  ZipEntryP* h = new ZipEntryP;
940  h->compMethod = compMethod;
941 
942  h->gpFlag[0] = buffer1[UNZIP_CD_OFF_GPFLAG];
943  h->gpFlag[1] = buffer1[UNZIP_CD_OFF_GPFLAG + 1];
944 
945  h->modTime[0] = buffer1[UNZIP_CD_OFF_MODT];
946  h->modTime[1] = buffer1[UNZIP_CD_OFF_MODT + 1];
947 
948  h->modDate[0] = buffer1[UNZIP_CD_OFF_MODD];
949  h->modDate[1] = buffer1[UNZIP_CD_OFF_MODD + 1];
950 
951  h->crc = getULong(uBuffer, UNZIP_CD_OFF_CRC32);
952  h->szComp = getULong(uBuffer, UNZIP_CD_OFF_CSIZE);
953  h->szUncomp = getULong(uBuffer, UNZIP_CD_OFF_USIZE);
954 
955  // Skip extra field (if any)
956  if (szExtra != 0)
957  {
958  if (!device->seek( device->pos() + szExtra ))
959  {
960  delete h;
961  return UnZip::SeekFailed;
962  }
963  }
964 
965  // Read comment field (if any)
966  if (szComment != 0)
967  {
968  if (device->read(buffer2, szComment) != szComment)
969  {
970  delete h;
971  return UnZip::ReadFailed;
972  }
973 
974  h->comment = QString::fromLatin1(buffer2, szComment);
975  }
976 
977  h->lhOffset = getULong(uBuffer, UNZIP_CD_OFF_LHOFFSET);
978 
979  if (headers == nullptr)
980  headers = new QMap<QString, ZipEntryP*>();
981  headers->insert(filename, h);
982 
983  return UnZip::Ok;
984 }
985 
988 {
989  if (device == nullptr)
990  return;
991 
992  skipAllEncrypted = false;
993 
994  if (headers != nullptr)
995  {
996  qDeleteAll(*headers);
997  delete headers;
998  headers = nullptr;
999  }
1000 
1001  delete device; device = nullptr;
1002 
1003  cdOffset = eocdOffset = 0;
1004  cdEntryCount = 0;
1005  unsupportedEntryCount = 0;
1006 
1007  comment.clear();
1008 }
1009 
1011 UnZip::ErrorCode UnzipPrivate::extractFile(const QString& path, ZipEntryP& entry, const QDir& dir, UnZip::ExtractionOptions options)
1012 {
1013  QString name(path);
1014  QString dirname;
1015  QString directory;
1016 
1017  int pos = name.lastIndexOf('/');
1018 
1019  // This entry is for a directory
1020  if (pos == name.length() - 1)
1021  {
1022  if (options.testFlag(UnZip::SkipPaths))
1023  return UnZip::Ok;
1024 
1025  directory = QString("%1/%2").arg(dir.absolutePath()).arg(QDir::cleanPath(name));
1026  if (!createDirectory(directory))
1027  {
1028  qDebug() << QString("Unable to create directory: %1").arg(directory);
1029  return UnZip::CreateDirFailed;
1030  }
1031 
1032  return UnZip::Ok;
1033  }
1034 
1035  // Extract path from entry
1036  if (pos > 0)
1037  {
1038  // get directory part
1039  dirname = name.left(pos);
1040  if (options.testFlag(UnZip::SkipPaths))
1041  {
1042  directory = dir.absolutePath();
1043  }
1044  else
1045  {
1046  directory = QString("%1/%2").arg(dir.absolutePath()).arg(QDir::cleanPath(dirname));
1047  if (!createDirectory(directory))
1048  {
1049  qDebug() << QString("Unable to create directory: %1").arg(directory);
1050  return UnZip::CreateDirFailed;
1051  }
1052  }
1053  name = name.right(name.length() - pos - 1);
1054  } else directory = dir.absolutePath();
1055 
1056  name = QString("%1/%2").arg(directory).arg(name);
1057 
1058  QFile outFile(name);
1059 
1060  if (!outFile.open(QIODevice::WriteOnly))
1061  {
1062  qDebug() << QString("Unable to open %1 for writing").arg(name);
1063  return UnZip::OpenFailed;
1064  }
1065 
1067 
1068  UnZip::ErrorCode ec = extractFile(path, entry, &outFile, options);
1069 
1070  outFile.close();
1071 
1072  if (ec != UnZip::Ok)
1073  {
1074  if (!outFile.remove())
1075  qDebug() << QString("Unable to remove corrupted file: %1").arg(name);
1076  }
1077 
1078  return ec;
1079 }
1080 
1082 UnZip::ErrorCode UnzipPrivate::extractFile(const QString& path, ZipEntryP& entry, QIODevice* dev, UnZip::ExtractionOptions options)
1083 {
1084  Q_UNUSED(options);
1085  Q_ASSERT(dev != nullptr);
1086 
1087  if (!entry.lhEntryChecked)
1088  {
1089  UnZip::ErrorCode ec = parseLocalHeaderRecord(path, entry);
1090  entry.lhEntryChecked = true;
1091 
1092  if (ec != UnZip::Ok)
1093  return ec;
1094  }
1095 
1096  if (!device->seek(entry.dataOffset))
1097  return UnZip::SeekFailed;
1098 
1099  // Encryption keys
1100  quint32 keys[3];
1101 
1102  if (entry.isEncrypted())
1103  {
1104  UnZip::ErrorCode e = testPassword(keys, path, entry);
1105  if (e != UnZip::Ok)
1106  {
1107  qDebug() << QString("Unable to decrypt %1").arg(path);
1108  return e;
1109  }
1110  entry.szComp -= UNZIP_LOCAL_ENC_HEADER_SIZE; // remove encryption header size
1111  }
1112 
1113  if (entry.szComp == 0)
1114  {
1115  if (entry.crc != 0)
1116  return UnZip::Corrupted;
1117 
1118  return UnZip::Ok;
1119  }
1120 
1121  uInt rep = entry.szComp / UNZIP_READ_BUFFER;
1122  uInt rem = entry.szComp % UNZIP_READ_BUFFER;
1123  uInt cur = 0;
1124 
1125  // extract data
1126  qint64 read;
1127  quint64 tot = 0;
1128 
1129  quint32 myCRC = crc32(0L, Z_NULL, 0);
1130 
1131  if (entry.compMethod == 0)
1132  {
1133  while ( (read = device->read(buffer1, cur < rep ? UNZIP_READ_BUFFER : rem)) > 0 )
1134  {
1135  if (entry.isEncrypted())
1136  decryptBytes(keys, buffer1, read);
1137 
1138  myCRC = crc32(myCRC, uBuffer, read);
1139 
1140  if (dev->write(buffer1, read) != read)
1141  return UnZip::WriteFailed;
1142 
1143  cur++;
1144  tot += read;
1145 
1146  if (tot == entry.szComp)
1147  break;
1148  }
1149 
1150  if (read < 0)
1151  return UnZip::ReadFailed;
1152  }
1153  else if (entry.compMethod == 8)
1154  {
1155  /* Allocate inflate state */
1156  z_stream zstr;
1157  zstr.zalloc = Z_NULL;
1158  zstr.zfree = Z_NULL;
1159  zstr.opaque = Z_NULL;
1160  zstr.next_in = Z_NULL;
1161  zstr.avail_in = 0;
1162 
1163  int zret;
1164 
1165  // Use inflateInit2 with negative windowBits to get raw decompression
1166  if ( (zret = inflateInit2_(&zstr, -MAX_WBITS, ZLIB_VERSION, sizeof(z_stream))) != Z_OK )
1167  return UnZip::ZlibError;
1168 
1169  int szDecomp;
1170 
1171  // Decompress until deflate stream ends or end of file
1172  do
1173  {
1174  read = device->read(buffer1, cur < rep ? UNZIP_READ_BUFFER : rem);
1175  if (read == 0)
1176  break;
1177  if (read < 0)
1178  {
1179  (void)inflateEnd(&zstr);
1180  return UnZip::ReadFailed;
1181  }
1182 
1183  if (entry.isEncrypted())
1184  decryptBytes(keys, buffer1, read);
1185 
1186  cur++;
1187  tot += read;
1188 
1189  zstr.avail_in = (uInt) read;
1190  zstr.next_in = (Bytef*) buffer1;
1191 
1192 
1193  // Run inflate() on input until output buffer not full
1194  do {
1195  zstr.avail_out = UNZIP_READ_BUFFER;
1196  zstr.next_out = (Bytef*) buffer2;;
1197 
1198  zret = inflate(&zstr, Z_NO_FLUSH);
1199 
1200  switch (zret) {
1201  case Z_NEED_DICT:
1202  case Z_DATA_ERROR:
1203  case Z_MEM_ERROR:
1204  inflateEnd(&zstr);
1205  return UnZip::WriteFailed;
1206  default:
1207  ;
1208  }
1209 
1210  szDecomp = UNZIP_READ_BUFFER - zstr.avail_out;
1211  if (dev->write(buffer2, szDecomp) != szDecomp)
1212  {
1213  inflateEnd(&zstr);
1214  return UnZip::ZlibError;
1215  }
1216 
1217  myCRC = crc32(myCRC, (const Bytef*) buffer2, szDecomp);
1218 
1219  } while (zstr.avail_out == 0);
1220 
1221  }
1222  while (zret != Z_STREAM_END);
1223 
1224  inflateEnd(&zstr);
1225  }
1226 
1227  if (myCRC != entry.crc)
1228  return UnZip::Corrupted;
1229 
1230  return UnZip::Ok;
1231 }
1232 
1234 bool UnzipPrivate::createDirectory(const QString& path)
1235 {
1236  QDir d(path);
1237  if (!d.exists())
1238  {
1239  int sep = path.lastIndexOf("/");
1240  if (sep <= 0) return true;
1241 
1242  if (!createDirectory(path.left(sep)))
1243  return false;
1244 
1245  if (!d.mkdir(path))
1246  {
1247  qDebug() << QString("Unable to create directory: %1").arg(path);
1248  return false;
1249  }
1250  }
1251 
1252  return true;
1253 }
1254 
1258 quint32 UnzipPrivate::getULong(const unsigned char* data, quint32 offset) const
1259 {
1260  quint32 res = (quint32) data[offset];
1261  res |= (((quint32)data[offset+1]) << 8);
1262  res |= (((quint32)data[offset+2]) << 16);
1263  res |= (((quint32)data[offset+3]) << 24);
1264 
1265  return res;
1266 }
1267 
1271 quint64 UnzipPrivate::getULLong(const unsigned char* data, quint32 offset) const
1272 {
1273  quint64 res = (quint64) data[offset];
1274  res |= (((quint64)data[offset+1]) << 8);
1275  res |= (((quint64)data[offset+2]) << 16);
1276  res |= (((quint64)data[offset+3]) << 24);
1277  res |= (((quint64)data[offset+1]) << 32);
1278  res |= (((quint64)data[offset+2]) << 40);
1279  res |= (((quint64)data[offset+3]) << 48);
1280  res |= (((quint64)data[offset+3]) << 56);
1281 
1282  return res;
1283 }
1284 
1288 quint16 UnzipPrivate::getUShort(const unsigned char* data, quint32 offset) const
1289 {
1290  return (quint16) data[offset] | (((quint16)data[offset+1]) << 8);
1291 }
1292 
1296 int UnzipPrivate::decryptByte(quint32 key2) const
1297 {
1298  quint16 temp = ((quint16)(key2) & 0xffff) | 2;
1299  return ((temp * (temp ^ 1)) >> 8) & 0xff;
1300 }
1301 
1305 void UnzipPrivate::updateKeys(quint32* keys, int c) const
1306 {
1307  keys[0] = CRC32(keys[0], c);
1308  keys[1] += keys[0] & 0xff;
1309  keys[1] = keys[1] * 134775813L + 1;
1310  keys[2] = CRC32(keys[2], ((int)keys[1]) >> 24);
1311 }
1312 
1317 void UnzipPrivate::initKeys(const QString& pwd, quint32* keys) const
1318 {
1319  keys[0] = 305419896L;
1320  keys[1] = 591751049L;
1321  keys[2] = 878082192L;
1322 
1323  QByteArray pwdBytes = pwd.toLatin1();
1324  int sz = pwdBytes.size();
1325  const char* ascii = pwdBytes.data();
1326 
1327  for (int i=0; i<sz; ++i)
1328  updateKeys(keys, (int)ascii[i]);
1329 }
1330 
1336 UnZip::ErrorCode UnzipPrivate::testPassword(quint32* keys, const QString& file, const ZipEntryP& header)
1337 {
1338  Q_UNUSED(file);
1339 
1340  // read encryption keys
1341  if (device->read(buffer1, 12) != 12)
1342  return UnZip::Corrupted;
1343 
1344  // Replace this code if you want to i.e. call some dialog and ask the user for a password
1345  initKeys(password, keys);
1346  if (testKeys(header, keys))
1347  return UnZip::Ok;
1348 
1349  return UnZip::Skip;
1350 }
1351 
1355 bool UnzipPrivate::testKeys(const ZipEntryP& header, quint32* keys)
1356 {
1357  char lastByte;
1358 
1359  // decrypt encryption header
1360  for (int i=0; i<11; ++i)
1361  updateKeys(keys, lastByte = buffer1[i] ^ decryptByte(keys[2]));
1362  updateKeys(keys, lastByte = buffer1[11] ^ decryptByte(keys[2]));
1363 
1364  // if there is an extended header (bit in the gp flag) buffer[11] is a byte from the file time
1365  // with no extended header we have to check the crc high-order byte
1366  char c = ((header.gpFlag[0] & 0x08) == 8) ? header.modTime[1] : header.crc >> 24;
1367 
1368  return (lastByte == c);
1369 }
1370 
1374 void UnzipPrivate::decryptBytes(quint32* keys, char* buffer, qint64 read)
1375 {
1376  for (int i=0; i<(int)read; ++i)
1377  updateKeys(keys, buffer[i] ^= decryptByte(keys[2]));
1378 }
1379 
1383 QDateTime UnzipPrivate::convertDateTime(const unsigned char date[2], const unsigned char time[2]) const
1384 {
1385  QDateTime dt;
1386 
1387  // Usual PKZip low-byte to high-byte order
1388 
1389  // Date: 7 bits = years from 1980, 4 bits = month, 5 bits = day
1390  quint16 year = (date[1] >> 1) & 127;
1391  quint16 month = ((date[1] << 3) & 14) | ((date[0] >> 5) & 7);
1392  quint16 day = date[0] & 31;
1393 
1394  // Time: 5 bits hour, 6 bits minutes, 5 bits seconds with a 2sec precision
1395  quint16 hour = (time[1] >> 3) & 31;
1396  quint16 minutes = ((time[1] << 3) & 56) | ((time[0] >> 5) & 7);
1397  quint16 seconds = (time[0] & 31) * 2;
1398 
1399  dt = QDateTime(QDate(1980 + year, month, day),
1400  QTime(hour, minutes, seconds), Qt::UTC);
1401  return dt;
1402 }
#define UNZIP_EOCD_OFF_CDOFF
Definition: unzip.cpp:163
bool encrypted
Definition: unzip.h:109
UnZip::ErrorCode seekToCentralDirectory()
Definition: unzip.cpp:753
QString comment
Definition: unzip.h:98
ErrorCode extractFiles(const QStringList &filenames, const QString &dirname, ExtractionOptions options=ExtractPaths)
Definition: unzip.cpp:461
unsigned char modDate[2]
Definition: zipentry_p.h:66
#define UNZIP_CD_OFF_CSIZE
Definition: unzip.cpp:137
#define UNZIP_LH_OFF_GPFLAG
Definition: unzip.cpp:146
QString formatError(UnZip::ErrorCode c) const
Definition: unzip.cpp:279
QIODevice * device
Definition: unzip_p.h:63
#define UNZIP_DD_OFF_CRC32
Definition: unzip.cpp:157
void closeArchive()
Definition: unzip.cpp:264
quint32 crc
Definition: zipentry_p.h:67
ErrorCode openArchive(const QString &filename)
Definition: unzip.cpp:228
#define UNZIP_CD_OFF_CMETHOD
Definition: unzip.cpp:133
quint64 getULLong(const unsigned char *data, quint32 offset) const
Definition: unzip.cpp:1271
ErrorCode extractAll(const QString &dirname, ExtractionOptions options=ExtractPaths)
Definition: unzip.cpp:363
#define UNZIP_CD_OFF_LHOFFSET
Definition: unzip.cpp:142
QString comment
Definition: unzip_p.h:82
bool createDirectory(const QString &path)
Definition: unzip.cpp:1234
#define UNZIP_CD_OFF_MODT
Definition: unzip.cpp:134
bool hasDataDescriptor() const
Definition: zipentry_p.h:75
QStringList fileList() const
Definition: unzip.cpp:319
quint32 szUncomp
Definition: zipentry_p.h:69
unsigned char modTime[2]
Definition: zipentry_p.h:65
#define UNZIP_READ_BUFFER
Definition: unzip_p.h:49
bool skipAllEncrypted
Definition: unzip_p.h:59
void decryptBytes(quint32 *keys, char *buffer, qint64 read)
Definition: unzip.cpp:1374
CompressionMethod compression
Definition: unzip.h:106
#define UNZIP_DD_SIZE
Data descriptor size (excluding signature)
Definition: unzip.cpp:124
UnZip::ErrorCode parseLocalHeaderRecord(const QString &path, ZipEntryP &entry)
Definition: unzip.cpp:615
def read(device=None, features=[])
Definition: disc.py:35
ErrorCode extractFile(const QString &filename, const QString &dirname, ExtractionOptions options=ExtractPaths)
Definition: unzip.cpp:418
UnZip::ErrorCode extractFile(const QString &path, ZipEntryP &entry, const QDir &dir, UnZip::ExtractionOptions options)
Definition: unzip.cpp:1011
#define UNZIP_LOCAL_ENC_HEADER_SIZE
Local header entry encryption header size.
Definition: unzip.cpp:128
QDateTime lastModified
Definition: unzip.h:104
QString password
Definition: unzip_p.h:57
quint32 crc32
Definition: unzip.h:102
QString comment
Definition: zipentry_p.h:70
QDateTime convertDateTime(const unsigned char date[2], const unsigned char time[2]) const
Definition: unzip.cpp:1383
ErrorCode
Definition: unzip.h:52
FileType type
Definition: unzip.h:107
#define UNZIP_CD_ENTRY_SIZE_NS
Central Directory file entry size (excluding signature, excluding variable length fields)
Definition: unzip.cpp:122
void setPassword(const QString &pwd)
Definition: unzip.cpp:501
quint32 szComp
Definition: zipentry_p.h:68
void initKeys(const QString &pwd, quint32 *keys) const
Definition: unzip.cpp:1317
#define UNZIP_VERSION
Definition: unzip.cpp:172
#define UNZIP_DD_OFF_USIZE
Definition: unzip.cpp:159
virtual ~UnZip()
Definition: unzip.cpp:211
unsigned char gpFlag[2]
Definition: zipentry_p.h:63
QString filename
Definition: unzip.h:97
#define UNZIP_CHECK_FOR_VALID_DATA
Checks if some file has been already extracted.
Definition: unzip.cpp:180
#define UNZIP_LH_OFF_USIZE
Definition: unzip.cpp:152
bool isOpen() const
Definition: unzip.cpp:220
QList< ZipEntry > entryList() const
Definition: unzip.cpp:327
#define UNZIP_EOCD_OFF_COMMLEN
Definition: unzip.cpp:164
const char * name
Definition: ParseText.cpp:328
#define CRC32(c, b)
CRC32 routine.
Definition: unzip.cpp:177
quint16 getUShort(const unsigned char *data, quint32 offset) const
Definition: unzip.cpp:1288
quint32 lhOffset
Definition: zipentry_p.h:61
quint32 compressedSize
Definition: unzip.h:100
void closeArchive()
Definition: unzip.cpp:987
UnZip::ErrorCode openArchive(QIODevice *device)
Definition: unzip.cpp:541
#define UNZIP_LOCAL_HEADER_SIZE
Local header size (excluding signature, excluding variable length fields)
Definition: unzip.cpp:120
#define UNZIP_CD_OFF_COMMLEN
Definition: unzip.cpp:141
#define UNZIP_CD_OFF_XLEN
Definition: unzip.cpp:140
bool testKeys(const ZipEntryP &header, quint32 *keys)
Definition: unzip.cpp:1355
#define UNZIP_EOCD_OFF_ENTRIES
Definition: unzip.cpp:162
UnzipPrivate * d
Definition: unzip.h:146
#define UNZIP_LH_OFF_XLEN
Definition: unzip.cpp:154
#define UNZIP_CD_OFF_CRC32
Definition: unzip.cpp:136
#define UNZIP_LH_OFF_CMETHOD
Definition: unzip.cpp:147
#define UNZIP_CD_OFF_GPFLAG
Definition: unzip.cpp:132
static void initKeys(void)
quint32 dataOffset
Definition: zipentry_p.h:62
bool isEncrypted() const
Definition: zipentry_p.h:74
int decryptByte(quint32 key2) const
Definition: unzip.cpp:1296
quint32 getULong(const unsigned char *data, quint32 offset) const
Definition: unzip.cpp:1258
UnZip::ErrorCode parseCentralDirectoryRecord()
Definition: unzip.cpp:878
#define UNZIP_LH_OFF_MODD
Definition: unzip.cpp:149
bool lhEntryChecked
Definition: zipentry_p.h:72
UnZip::ErrorCode testPassword(quint32 *keys, const QString &file, const ZipEntryP &header)
Definition: unzip.cpp:1336
UnzipPrivate()
Definition: unzip.cpp:523
UnZip()
Definition: unzip.cpp:203
#define UNZIP_CD_OFF_VERSION
Definition: unzip.cpp:131
#define UNZIP_DD_OFF_CSIZE
Definition: unzip.cpp:158
#define UNZIP_LH_OFF_CRC32
Definition: unzip.cpp:150
#define UNZIP_CD_OFF_NAMELEN
Definition: unzip.cpp:139
#define UNZIP_CD_OFF_MODD
Definition: unzip.cpp:135
#define UNZIP_EOCD_SIZE
End Of Central Directory size (including signature, excluding variable length fields)
Definition: unzip.cpp:126
QString archiveComment() const
Definition: unzip.cpp:269
quint16 compMethod
Definition: zipentry_p.h:64
#define UNZIP_CD_OFF_USIZE
Definition: unzip.cpp:138
#define UNZIP_LH_OFF_MODT
Definition: unzip.cpp:148
void updateKeys(quint32 *keys, int c) const
Definition: unzip.cpp:1305
#define Z_NULL
Definition: unzip.h:37
QMap< QString, ZipEntryP * > * headers
Definition: unzip_p.h:61
#define UNZIP_LH_OFF_NAMELEN
Definition: unzip.cpp:153
static uint32_t crc32(const unsigned char *data, int len)
Definition: dsmcc.cpp:618
quint32 uncompressedSize
Definition: unzip.h:101
bool contains(const QString &file) const
Definition: unzip.cpp:308
#define UNZIP_LH_OFF_CSIZE
Definition: unzip.cpp:151
Ignores paths and extracts all the files to the same directory.
Definition: unzip.h:79