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 
229 {
230  auto* 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;
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  case Skip:
399  break;
400  case SkipAll:
401  d->skipAllEncrypted = true;
402  break;
403  default:
404  ;
405  }
406 
407  if (end)
408  break;
409  }
410 
411  return Ok;
412 }
413 
417 UnZip::ErrorCode UnZip::extractFile(const QString& filename, const QString& dirname, ExtractionOptions options)
418 {
419  return extractFile(filename, QDir(dirname), options);
420 }
421 
425 UnZip::ErrorCode UnZip::extractFile(const QString& filename, const QDir& dir, ExtractionOptions options)
426 {
427  QMap<QString,ZipEntryP*>::Iterator itr = d->headers->find(filename);
428  if (itr != d->headers->end())
429  {
430  ZipEntryP* entry = itr.value();
431  Q_ASSERT(entry != nullptr);
432  return d->extractFile(itr.key(), *entry, dir, options);
433  }
434 
435  return FileNotFound;
436 }
437 
441 UnZip::ErrorCode UnZip::extractFile(const QString& filename, QIODevice* dev, ExtractionOptions options)
442 {
443  if (dev == nullptr)
444  return InvalidDevice;
445 
446  QMap<QString,ZipEntryP*>::Iterator itr = d->headers->find(filename);
447  if (itr != d->headers->end()) {
448  ZipEntryP* entry = itr.value();
449  Q_ASSERT(entry != nullptr);
450  return d->extractFile(itr.key(), *entry, dev, options);
451  }
452 
453  return FileNotFound;
454 }
455 
460 UnZip::ErrorCode UnZip::extractFiles(const QStringList& filenames, const QString& dirname, ExtractionOptions options)
461 {
462  QDir dir(dirname);
463  ErrorCode ec = Ok;
464 
465  for (QStringList::ConstIterator itr = filenames.constBegin(); itr != filenames.constEnd(); ++itr)
466  {
467  ec = extractFile(*itr, dir, options);
468  if (ec == FileNotFound)
469  continue;
470  if (ec != Ok)
471  return ec;
472  }
473 
474  return Ok;
475 }
476 
481 UnZip::ErrorCode UnZip::extractFiles(const QStringList& filenames, const QDir& dir, ExtractionOptions options)
482 {
483  ErrorCode ec = Ok;
484 
485  for (QStringList::ConstIterator itr = filenames.constBegin(); itr != filenames.constEnd(); ++itr)
486  {
487  ec = extractFile(*itr, dir, options);
488  if (ec == FileNotFound)
489  continue;
490  if (ec != Ok)
491  return ec;
492  }
493 
494  return Ok;
495 }
496 
500 void UnZip::setPassword(const QString& pwd)
501 {
502  d->password = pwd;
503 }
504 
509 {
512  type = File;
513  encrypted = false;
514 }
515 
516 
517 /************************************************************************
518  Private interface
519 *************************************************************************/
520 
523 {
524  uBuffer = (unsigned char*) buffer1;
525  crcTable = (const quint32*) get_crc_table();
526 }
527 
530 {
531  Q_ASSERT(dev != nullptr);
532 
533  if (device != nullptr)
534  closeArchive();
535 
536  device = dev;
537 
538  if (!(device->isOpen() || device->open(QIODevice::ReadOnly)))
539  {
540  delete device;
541  device = nullptr;
542 
543  qDebug() << "Unable to open device for reading";
544  return UnZip::OpenFailed;
545  }
546 
548 
549  ec = seekToCentralDirectory();
550  if (ec != UnZip::Ok)
551  {
552  closeArchive();
553  return ec;
554  }
555 
557  if (cdEntryCount == 0)
558  {
559  return UnZip::Ok;
560  }
561 
562  bool continueParsing = true;
563 
564  while (continueParsing)
565  {
566  if (device->read(buffer1, 4) != 4)
568 
569  if (! (buffer1[0] == 'P' && buffer1[1] == 'K' && buffer1[2] == 0x01 && buffer1[3] == 0x02) )
570  break;
571 
572  if ( (ec = parseCentralDirectoryRecord()) != UnZip::Ok )
573  break;
574  }
575 
576  if (ec != UnZip::Ok)
577  closeArchive();
578 
579  return ec;
580 }
581 
582 /*
583  \internal Parses a local header record and makes some consistency check
584  with the information stored in the Central Directory record for this entry
585  that has been previously parsed.
586  \todo Optional consistency check (as a ExtractionOptions flag)
587 
588  local file header signature 4 bytes (0x04034b50)
589  version needed to extract 2 bytes
590  general purpose bit flag 2 bytes
591  compression method 2 bytes
592  last mod file time 2 bytes
593  last mod file date 2 bytes
594  crc-32 4 bytes
595  compressed size 4 bytes
596  uncompressed size 4 bytes
597  file name length 2 bytes
598  extra field length 2 bytes
599 
600  file name (variable size)
601  extra field (variable size)
602 */
604 {
605  if (!device->seek(entry.lhOffset))
606  return UnZip::SeekFailed;
607 
608  // Test signature
609  if (device->read(buffer1, 4) != 4)
610  return UnZip::ReadFailed;
611 
612  if ((buffer1[0] != 'P') || (buffer1[1] != 'K') || (buffer1[2] != 0x03) || (buffer1[3] != 0x04))
613  return UnZip::InvalidArchive;
614 
615  if (device->read(buffer1, UNZIP_LOCAL_HEADER_SIZE) != UNZIP_LOCAL_HEADER_SIZE)
616  return UnZip::ReadFailed;
617 
618  /*
619  Check 3rd general purpose bit flag.
620 
621  "bit 3: If this bit is set, the fields crc-32, compressed size
622  and uncompressed size are set to zero in the local
623  header. The correct values are put in the data descriptor
624  immediately following the compressed data."
625  */
626  bool hasDataDescriptor = entry.hasDataDescriptor();
627 
628  bool checkFailed = false;
629 
630  if (!checkFailed)
631  checkFailed = entry.compMethod != getUShort(uBuffer, UNZIP_LH_OFF_CMETHOD);
632  if (!checkFailed)
633  checkFailed = entry.gpFlag[0] != uBuffer[UNZIP_LH_OFF_GPFLAG];
634  if (!checkFailed)
635  checkFailed = entry.gpFlag[1] != uBuffer[UNZIP_LH_OFF_GPFLAG + 1];
636  if (!checkFailed)
637  checkFailed = entry.modTime[0] != uBuffer[UNZIP_LH_OFF_MODT];
638  if (!checkFailed)
639  checkFailed = entry.modTime[1] != uBuffer[UNZIP_LH_OFF_MODT + 1];
640  if (!checkFailed)
641  checkFailed = entry.modDate[0] != uBuffer[UNZIP_LH_OFF_MODD];
642  if (!checkFailed)
643  checkFailed = entry.modDate[1] != uBuffer[UNZIP_LH_OFF_MODD + 1];
644  if (!hasDataDescriptor)
645  {
646  if (!checkFailed)
647  checkFailed = entry.crc != getULong(uBuffer, UNZIP_LH_OFF_CRC32);
648  if (!checkFailed)
649  checkFailed = entry.szComp != getULong(uBuffer, UNZIP_LH_OFF_CSIZE);
650  if (!checkFailed)
651  checkFailed = entry.szUncomp != getULong(uBuffer, UNZIP_LH_OFF_USIZE);
652  }
653 
654  if (checkFailed)
656 
657  // Check filename
658  quint16 szName = getUShort(uBuffer, UNZIP_LH_OFF_NAMELEN);
659  if (szName == 0)
661 
662  if (device->read(buffer2, szName) != szName)
663  return UnZip::ReadFailed;
664 
665  QString filename = QString::fromLatin1(buffer2, szName);
666  if (filename != path)
667  {
668  qDebug() << "Filename in local header mismatches.";
670  }
671 
672  // Skip extra field
673  quint16 szExtra = getUShort(uBuffer, UNZIP_LH_OFF_XLEN);
674  if (szExtra != 0)
675  {
676  if (!device->seek(device->pos() + szExtra))
677  return UnZip::SeekFailed;
678  }
679 
680  entry.dataOffset = device->pos();
681 
682  if (hasDataDescriptor)
683  {
684  /*
685  The data descriptor has this OPTIONAL signature: PK\7\8
686  We try to skip the compressed data relying on the size set in the
687  Central Directory record.
688  */
689  if (!device->seek(device->pos() + entry.szComp))
690  return UnZip::SeekFailed;
691 
692  // Read 4 bytes and check if there is a data descriptor signature
693  if (device->read(buffer2, 4) != 4)
694  return UnZip::ReadFailed;
695 
696  bool hasSignature = buffer2[0] == 'P' && buffer2[1] == 'K' && buffer2[2] == 0x07 && buffer2[3] == 0x08;
697  if (hasSignature)
698  {
699  if (device->read(buffer2, UNZIP_DD_SIZE) != UNZIP_DD_SIZE)
700  return UnZip::ReadFailed;
701  }
702  else
703  {
704  if (device->read(buffer2 + 4, UNZIP_DD_SIZE - 4) != UNZIP_DD_SIZE - 4)
705  return UnZip::ReadFailed;
706  }
707 
708  // DD: crc, compressed size, uncompressed size
709  if (
710  entry.crc != getULong((unsigned char*)buffer2, UNZIP_DD_OFF_CRC32) ||
711  entry.szComp != getULong((unsigned char*)buffer2, UNZIP_DD_OFF_CSIZE) ||
712  entry.szUncomp != getULong((unsigned char*)buffer2, UNZIP_DD_OFF_USIZE)
713  )
715  }
716 
717  return UnZip::Ok;
718 }
719 
742 {
743  qint64 length = device->size();
744  qint64 offset = length - UNZIP_EOCD_SIZE;
745 
746  if (length < UNZIP_EOCD_SIZE)
747  return UnZip::InvalidArchive;
748 
749  if (!device->seek( offset ))
750  return UnZip::SeekFailed;
751 
752  if (device->read(buffer1, UNZIP_EOCD_SIZE) != UNZIP_EOCD_SIZE)
753  return UnZip::ReadFailed;
754 
755  bool eocdFound = (buffer1[0] == 'P' && buffer1[1] == 'K' && buffer1[2] == 0x05 && buffer1[3] == 0x06);
756 
757  if (eocdFound)
758  {
759  // Zip file has no comment (the only variable length field in the EOCD record)
760  eocdOffset = offset;
761  }
762  else
763  {
764  char* p = nullptr;
765 
766  offset -= UNZIP_EOCD_SIZE;
767 
768  if (offset <= 0)
769  return UnZip::InvalidArchive;
770 
771  if (!device->seek( offset ))
772  return UnZip::SeekFailed;
773 
774  while (device->read(buffer1, UNZIP_EOCD_SIZE) >= 0)
775  {
776  if ( (p = strstr(buffer1, "PK\5\6")) != nullptr)
777  {
778  // Seek to the start of the EOCD record so we can read it fully
779  // Yes... we could simply read the missing bytes and append them to the buffer
780  // but this is far easier so heck it!
781  device->seek( offset + (p - buffer1) );
782  eocdFound = true;
783  eocdOffset = offset + (p - buffer1);
784 
785  // Read EOCD record
786  if (device->read(buffer1, UNZIP_EOCD_SIZE) != UNZIP_EOCD_SIZE)
787  return UnZip::ReadFailed;
788 
789  break;
790  }
791 
792  // TODO: This is very slow and only a temporary bug fix. Need some pattern matching algorithm here.
793  offset -= 1 /*UNZIP_EOCD_SIZE*/;
794  if (offset <= 0)
795  return UnZip::InvalidArchive;
796 
797  if (!device->seek( offset ))
798  return UnZip::SeekFailed;
799  }
800  }
801 
802  if (!eocdFound)
803  return UnZip::InvalidArchive;
804 
805  // Parse EOCD to locate CD offset
806  offset = getULong((const unsigned char*)buffer1, UNZIP_EOCD_OFF_CDOFF + 4);
807 
808  cdOffset = offset;
809 
810  cdEntryCount = getUShort((const unsigned char*)buffer1, UNZIP_EOCD_OFF_ENTRIES + 4);
811 
812  quint16 commentLength = getUShort((const unsigned char*)buffer1, UNZIP_EOCD_OFF_COMMLEN + 4);
813  if (commentLength != 0)
814  {
815  QByteArray c = device->read(commentLength);
816  if (c.count() != commentLength)
817  return UnZip::ReadFailed;
818 
819  comment = c;
820  }
821 
822  // Seek to the start of the CD record
823  if (!device->seek( cdOffset ))
824  return UnZip::SeekFailed;
825 
826  return UnZip::Ok;
827 }
828 
866 {
867  // Read CD record
868  if (device->read(buffer1, UNZIP_CD_ENTRY_SIZE_NS) != UNZIP_CD_ENTRY_SIZE_NS)
869  return UnZip::ReadFailed;
870 
871  bool skipEntry = false;
872 
873  // Get compression type so we can skip non compatible algorithms
874  quint16 compMethod = getUShort(uBuffer, UNZIP_CD_OFF_CMETHOD);
875 
876  // Get variable size fields length so we can skip the whole record
877  // if necessary
878  quint16 szName = getUShort(uBuffer, UNZIP_CD_OFF_NAMELEN);
879  quint16 szExtra = getUShort(uBuffer, UNZIP_CD_OFF_XLEN);
880  quint16 szComment = getUShort(uBuffer, UNZIP_CD_OFF_COMMLEN);
881 
882  quint32 skipLength = szName + szExtra + szComment;
883 
885 
886  if ((compMethod != 0) && (compMethod != 8))
887  {
888  qDebug() << "Unsupported compression method. Skipping file.";
889  skipEntry = true;
890  }
891 
892  // Header parsing may be a problem if version is bigger than UNZIP_VERSION
893  if (!skipEntry && buffer1[UNZIP_CD_OFF_VERSION] > UNZIP_VERSION)
894  {
895  qDebug() << "Unsupported PKZip version. Skipping file.";
896  skipEntry = true;
897  }
898 
899  if (!skipEntry && szName == 0)
900  {
901  qDebug() << "Skipping file with no name.";
902  skipEntry = true;
903  }
904 
905  if (!skipEntry && device->read(buffer2, szName) != szName)
906  {
907  ec = UnZip::ReadFailed;
908  skipEntry = true;
909  }
910 
911  if (skipEntry)
912  {
913  if (ec == UnZip::Ok)
914  {
915  if (!device->seek( device->pos() + skipLength ))
916  ec = UnZip::SeekFailed;
917 
918  unsupportedEntryCount++;
919  }
920 
921  return ec;
922  }
923 
924  QString filename = QString::fromLatin1(buffer2, szName);
925 
926  auto* h = new ZipEntryP;
927  h->compMethod = compMethod;
928 
929  h->gpFlag[0] = buffer1[UNZIP_CD_OFF_GPFLAG];
930  h->gpFlag[1] = buffer1[UNZIP_CD_OFF_GPFLAG + 1];
931 
932  h->modTime[0] = buffer1[UNZIP_CD_OFF_MODT];
933  h->modTime[1] = buffer1[UNZIP_CD_OFF_MODT + 1];
934 
935  h->modDate[0] = buffer1[UNZIP_CD_OFF_MODD];
936  h->modDate[1] = buffer1[UNZIP_CD_OFF_MODD + 1];
937 
938  h->crc = getULong(uBuffer, UNZIP_CD_OFF_CRC32);
939  h->szComp = getULong(uBuffer, UNZIP_CD_OFF_CSIZE);
940  h->szUncomp = getULong(uBuffer, UNZIP_CD_OFF_USIZE);
941 
942  // Skip extra field (if any)
943  if (szExtra != 0)
944  {
945  if (!device->seek( device->pos() + szExtra ))
946  {
947  delete h;
948  return UnZip::SeekFailed;
949  }
950  }
951 
952  // Read comment field (if any)
953  if (szComment != 0)
954  {
955  if (device->read(buffer2, szComment) != szComment)
956  {
957  delete h;
958  return UnZip::ReadFailed;
959  }
960 
961  h->comment = QString::fromLatin1(buffer2, szComment);
962  }
963 
964  h->lhOffset = getULong(uBuffer, UNZIP_CD_OFF_LHOFFSET);
965 
966  if (headers == nullptr)
967  headers = new QMap<QString, ZipEntryP*>();
968  headers->insert(filename, h);
969 
970  return UnZip::Ok;
971 }
972 
975 {
976  if (device == nullptr)
977  return;
978 
979  skipAllEncrypted = false;
980 
981  if (headers != nullptr)
982  {
983  qDeleteAll(*headers);
984  delete headers;
985  headers = nullptr;
986  }
987 
988  delete device; device = nullptr;
989 
990  cdOffset = eocdOffset = 0;
991  cdEntryCount = 0;
992  unsupportedEntryCount = 0;
993 
994  comment.clear();
995 }
996 
998 UnZip::ErrorCode UnzipPrivate::extractFile(const QString& path, ZipEntryP& entry, const QDir& dir, UnZip::ExtractionOptions options)
999 {
1000  QString name(path);
1001  QString dirname;
1002  QString directory;
1003 
1004  int pos = name.lastIndexOf('/');
1005 
1006  // This entry is for a directory
1007  if (pos == name.length() - 1)
1008  {
1009  if (options.testFlag(UnZip::SkipPaths))
1010  return UnZip::Ok;
1011 
1012  directory = QString("%1/%2").arg(dir.absolutePath()).arg(QDir::cleanPath(name));
1013  if (!createDirectory(directory))
1014  {
1015  qDebug() << QString("Unable to create directory: %1").arg(directory);
1016  return UnZip::CreateDirFailed;
1017  }
1018 
1019  return UnZip::Ok;
1020  }
1021 
1022  // Extract path from entry
1023  if (pos > 0)
1024  {
1025  // get directory part
1026  dirname = name.left(pos);
1027  if (options.testFlag(UnZip::SkipPaths))
1028  {
1029  directory = dir.absolutePath();
1030  }
1031  else
1032  {
1033  directory = QString("%1/%2").arg(dir.absolutePath()).arg(QDir::cleanPath(dirname));
1034  if (!createDirectory(directory))
1035  {
1036  qDebug() << QString("Unable to create directory: %1").arg(directory);
1037  return UnZip::CreateDirFailed;
1038  }
1039  }
1040  name = name.right(name.length() - pos - 1);
1041  } else directory = dir.absolutePath();
1042 
1043  name = QString("%1/%2").arg(directory).arg(name);
1044 
1045  QFile outFile(name);
1046 
1047  if (!outFile.open(QIODevice::WriteOnly))
1048  {
1049  qDebug() << QString("Unable to open %1 for writing").arg(name);
1050  return UnZip::OpenFailed;
1051  }
1052 
1054 
1055  UnZip::ErrorCode ec = extractFile(path, entry, &outFile, options);
1056 
1057  outFile.close();
1058 
1059  if (ec != UnZip::Ok)
1060  {
1061  if (!outFile.remove())
1062  qDebug() << QString("Unable to remove corrupted file: %1").arg(name);
1063  }
1064 
1065  return ec;
1066 }
1067 
1069 UnZip::ErrorCode UnzipPrivate::extractFile(const QString& path, ZipEntryP& entry, QIODevice* dev, UnZip::ExtractionOptions options)
1070 {
1071  Q_UNUSED(options);
1072  Q_ASSERT(dev != nullptr);
1073 
1074  if (!entry.lhEntryChecked)
1075  {
1076  UnZip::ErrorCode ec = parseLocalHeaderRecord(path, entry);
1077  entry.lhEntryChecked = true;
1078 
1079  if (ec != UnZip::Ok)
1080  return ec;
1081  }
1082 
1083  if (!device->seek(entry.dataOffset))
1084  return UnZip::SeekFailed;
1085 
1086  // Encryption keys
1087  quint32 keys[3];
1088 
1089  if (entry.isEncrypted())
1090  {
1091  UnZip::ErrorCode e = testPassword(keys, path, entry);
1092  if (e != UnZip::Ok)
1093  {
1094  qDebug() << QString("Unable to decrypt %1").arg(path);
1095  return e;
1096  }
1097  entry.szComp -= UNZIP_LOCAL_ENC_HEADER_SIZE; // remove encryption header size
1098  }
1099 
1100  if (entry.szComp == 0)
1101  {
1102  if (entry.crc != 0)
1103  return UnZip::Corrupted;
1104 
1105  return UnZip::Ok;
1106  }
1107 
1108  uInt rep = entry.szComp / UNZIP_READ_BUFFER;
1109  uInt rem = entry.szComp % UNZIP_READ_BUFFER;
1110  uInt cur = 0;
1111 
1112  // extract data
1113  qint64 read = 0;
1114  quint64 tot = 0;
1115 
1116  quint32 myCRC = crc32(0L, Z_NULL, 0);
1117 
1118  if (entry.compMethod == 0)
1119  {
1120  while ( (read = device->read(buffer1, cur < rep ? UNZIP_READ_BUFFER : rem)) > 0 )
1121  {
1122  if (entry.isEncrypted())
1123  decryptBytes(keys, buffer1, read);
1124 
1125  myCRC = crc32(myCRC, uBuffer, read);
1126 
1127  if (dev->write(buffer1, read) != read)
1128  return UnZip::WriteFailed;
1129 
1130  cur++;
1131  tot += read;
1132 
1133  if (tot == entry.szComp)
1134  break;
1135  }
1136 
1137  if (read < 0)
1138  return UnZip::ReadFailed;
1139  }
1140  else if (entry.compMethod == 8)
1141  {
1142  /* Allocate inflate state */
1143  z_stream zstr;
1144  zstr.zalloc = Z_NULL;
1145  zstr.zfree = Z_NULL;
1146  zstr.opaque = Z_NULL;
1147  zstr.next_in = Z_NULL;
1148  zstr.avail_in = 0;
1149 
1150  int zret = Z_OK;
1151 
1152  // Use inflateInit2 with negative windowBits to get raw decompression
1153  if (inflateInit2_(&zstr, -MAX_WBITS, ZLIB_VERSION, sizeof(z_stream)) != Z_OK )
1154  return UnZip::ZlibError;
1155 
1156  int szDecomp = 0;
1157 
1158  // Decompress until deflate stream ends or end of file
1159  do
1160  {
1161  read = device->read(buffer1, cur < rep ? UNZIP_READ_BUFFER : rem);
1162  if (read == 0)
1163  break;
1164  if (read < 0)
1165  {
1166  (void)inflateEnd(&zstr);
1167  return UnZip::ReadFailed;
1168  }
1169 
1170  if (entry.isEncrypted())
1171  decryptBytes(keys, buffer1, read);
1172 
1173  cur++;
1174  tot += read;
1175 
1176  zstr.avail_in = (uInt) read;
1177  zstr.next_in = (Bytef*) buffer1;
1178 
1179 
1180  // Run inflate() on input until output buffer not full
1181  do {
1182  zstr.avail_out = UNZIP_READ_BUFFER;
1183  zstr.next_out = (Bytef*) buffer2;;
1184 
1185  zret = inflate(&zstr, Z_NO_FLUSH);
1186 
1187  switch (zret) {
1188  case Z_NEED_DICT:
1189  case Z_DATA_ERROR:
1190  case Z_MEM_ERROR:
1191  inflateEnd(&zstr);
1192  return UnZip::WriteFailed;
1193  default:
1194  ;
1195  }
1196 
1197  szDecomp = UNZIP_READ_BUFFER - zstr.avail_out;
1198  if (dev->write(buffer2, szDecomp) != szDecomp)
1199  {
1200  inflateEnd(&zstr);
1201  return UnZip::ZlibError;
1202  }
1203 
1204  myCRC = crc32(myCRC, (const Bytef*) buffer2, szDecomp);
1205 
1206  } while (zstr.avail_out == 0);
1207 
1208  }
1209  while (zret != Z_STREAM_END);
1210 
1211  inflateEnd(&zstr);
1212  }
1213 
1214  if (myCRC != entry.crc)
1215  return UnZip::Corrupted;
1216 
1217  return UnZip::Ok;
1218 }
1219 
1221 bool UnzipPrivate::createDirectory(const QString& path)
1222 {
1223  QDir d(path);
1224  if (!d.exists())
1225  {
1226  int sep = path.lastIndexOf("/");
1227  if (sep <= 0) return true;
1228 
1229  if (!createDirectory(path.left(sep)))
1230  return false;
1231 
1232  if (!d.mkdir(path))
1233  {
1234  qDebug() << QString("Unable to create directory: %1").arg(path);
1235  return false;
1236  }
1237  }
1238 
1239  return true;
1240 }
1241 
1245 quint32 UnzipPrivate::getULong(const unsigned char* data, quint32 offset)
1246 {
1247  auto res = (quint32) data[offset];
1248  res |= (((quint32)data[offset+1]) << 8);
1249  res |= (((quint32)data[offset+2]) << 16);
1250  res |= (((quint32)data[offset+3]) << 24);
1251 
1252  return res;
1253 }
1254 
1258 quint64 UnzipPrivate::getULLong(const unsigned char* data, quint32 offset)
1259 {
1260  auto res = (quint64) data[offset];
1261  res |= (((quint64)data[offset+1]) << 8);
1262  res |= (((quint64)data[offset+2]) << 16);
1263  res |= (((quint64)data[offset+3]) << 24);
1264  res |= (((quint64)data[offset+1]) << 32);
1265  res |= (((quint64)data[offset+2]) << 40);
1266  res |= (((quint64)data[offset+3]) << 48);
1267  res |= (((quint64)data[offset+3]) << 56);
1268 
1269  return res;
1270 }
1271 
1275 quint16 UnzipPrivate::getUShort(const unsigned char* data, quint32 offset)
1276 {
1277  return (quint16) data[offset] | (((quint16)data[offset+1]) << 8);
1278 }
1279 
1283 int UnzipPrivate::decryptByte(quint32 key2)
1284 {
1285  quint16 temp = ((quint16)(key2) & 0xffff) | 2;
1286  return ((temp * (temp ^ 1)) >> 8) & 0xff;
1287 }
1288 
1292 void UnzipPrivate::updateKeys(quint32* keys, int c) const
1293 {
1294  keys[0] = CRC32(keys[0], c);
1295  keys[1] += keys[0] & 0xff;
1296  keys[1] = keys[1] * 134775813L + 1;
1297  keys[2] = CRC32(keys[2], ((int)keys[1]) >> 24);
1298 }
1299 
1304 void UnzipPrivate::initKeys(const QString& pwd, quint32* keys) const
1305 {
1306  keys[0] = 305419896L;
1307  keys[1] = 591751049L;
1308  keys[2] = 878082192L;
1309 
1310  QByteArray pwdBytes = pwd.toLatin1();
1311  int sz = pwdBytes.size();
1312  const char* ascii = pwdBytes.data();
1313 
1314  for (int i=0; i<sz; ++i)
1315  updateKeys(keys, (int)ascii[i]);
1316 }
1317 
1323 UnZip::ErrorCode UnzipPrivate::testPassword(quint32* keys, const QString& file, const ZipEntryP& header)
1324 {
1325  Q_UNUSED(file);
1326 
1327  // read encryption keys
1328  if (device->read(buffer1, 12) != 12)
1329  return UnZip::Corrupted;
1330 
1331  // Replace this code if you want to i.e. call some dialog and ask the user for a password
1332  initKeys(password, keys);
1333  if (testKeys(header, keys))
1334  return UnZip::Ok;
1335 
1336  return UnZip::Skip;
1337 }
1338 
1342 bool UnzipPrivate::testKeys(const ZipEntryP& header, quint32* keys)
1343 {
1344  char lastByte = 0;
1345 
1346  // decrypt encryption header
1347  for (int i=0; i<11; ++i)
1348  updateKeys(keys, buffer1[i] ^ decryptByte(keys[2]));
1349  updateKeys(keys, lastByte = buffer1[11] ^ decryptByte(keys[2]));
1350 
1351  // if there is an extended header (bit in the gp flag) buffer[11] is a byte from the file time
1352  // with no extended header we have to check the crc high-order byte
1353  char c = ((header.gpFlag[0] & 0x08) == 8) ? header.modTime[1] : header.crc >> 24;
1354 
1355  return (lastByte == c);
1356 }
1357 
1361 void UnzipPrivate::decryptBytes(quint32* keys, char* buffer, qint64 read)
1362 {
1363  for (int i=0; i<(int)read; ++i)
1364  updateKeys(keys, buffer[i] ^= decryptByte(keys[2]));
1365 }
1366 
1370 QDateTime UnzipPrivate::convertDateTime(const unsigned char date[2], const unsigned char time[2])
1371 {
1372  QDateTime dt;
1373 
1374  // Usual PKZip low-byte to high-byte order
1375 
1376  // Date: 7 bits = years from 1980, 4 bits = month, 5 bits = day
1377  quint16 year = (date[1] >> 1) & 127;
1378  quint16 month = ((date[1] << 3) & 14) | ((date[0] >> 5) & 7);
1379  quint16 day = date[0] & 31;
1380 
1381  // Time: 5 bits hour, 6 bits minutes, 5 bits seconds with a 2sec precision
1382  quint16 hour = (time[1] >> 3) & 31;
1383  quint16 minutes = ((time[1] << 3) & 56) | ((time[0] >> 5) & 7);
1384  quint16 seconds = (time[0] & 31) * 2;
1385 
1386  dt = QDateTime(QDate(1980 + year, month, day),
1387  QTime(hour, minutes, seconds), Qt::UTC);
1388  return dt;
1389 }
#define UNZIP_EOCD_OFF_CDOFF
Definition: unzip.cpp:163
bool encrypted
Definition: unzip.h:109
UnZip::ErrorCode seekToCentralDirectory()
Definition: unzip.cpp:741
QString comment
Definition: unzip.h:98
ErrorCode extractFiles(const QStringList &filenames, const QString &dirname, ExtractionOptions options=ExtractPaths)
Definition: unzip.cpp:460
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
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
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:1221
#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
static QDateTime convertDateTime(const unsigned char date[2], const unsigned char time[2])
Definition: unzip.cpp:1370
bool skipAllEncrypted
Definition: unzip_p.h:59
void decryptBytes(quint32 *keys, char *buffer, qint64 read)
Definition: unzip.cpp:1361
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:603
static quint64 getULLong(const unsigned char *data, quint32 offset)
Definition: unzip.cpp:1258
def read(device=None, features=[])
Definition: disc.py:35
ErrorCode extractFile(const QString &filename, const QString &dirname, ExtractionOptions options=ExtractPaths)
Definition: unzip.cpp:417
UnZip::ErrorCode extractFile(const QString &path, ZipEntryP &entry, const QDir &dir, UnZip::ExtractionOptions options)
Definition: unzip.cpp:998
#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
static quint16 getUShort(const unsigned char *data, quint32 offset)
Definition: unzip.cpp:1275
QString comment
Definition: zipentry_p.h:70
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:500
quint32 szComp
Definition: zipentry_p.h:68
void initKeys(const QString &pwd, quint32 *keys) const
Definition: unzip.cpp:1304
#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
#define CRC32(c, b)
CRC32 routine.
Definition: unzip.cpp:177
quint32 lhOffset
Definition: zipentry_p.h:61
quint32 compressedSize
Definition: unzip.h:100
void closeArchive()
Definition: unzip.cpp:974
UnZip::ErrorCode openArchive(QIODevice *device)
Definition: unzip.cpp:529
static int decryptByte(quint32 key2)
Definition: unzip.cpp:1283
#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:1342
#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
static quint32 getULong(const unsigned char *data, quint32 offset)
Definition: unzip.cpp:1245
UnZip::ErrorCode parseCentralDirectoryRecord()
Definition: unzip.cpp:865
#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:1323
static QString formatError(UnZip::ErrorCode c)
Definition: unzip.cpp:279
UnzipPrivate()
Definition: unzip.cpp:522
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:1292
#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:613
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