juce_ZipFile.cpp 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607
  1. /*
  2. ==============================================================================
  3. This file is part of the juce_core module of the JUCE library.
  4. Copyright (c) 2015 - ROLI Ltd.
  5. Permission to use, copy, modify, and/or distribute this software for any purpose with
  6. or without fee is hereby granted, provided that the above copyright notice and this
  7. permission notice appear in all copies.
  8. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD
  9. TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN
  10. NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL
  11. DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER
  12. IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
  13. CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
  14. ------------------------------------------------------------------------------
  15. NOTE! This permissive ISC license applies ONLY to files within the juce_core module!
  16. All other JUCE modules are covered by a dual GPL/commercial license, so if you are
  17. using any other modules, be sure to check that you also comply with their license.
  18. For more details, visit www.juce.com
  19. ==============================================================================
  20. */
  21. class ZipFile::ZipEntryHolder
  22. {
  23. public:
  24. ZipEntryHolder (const char* const buffer, const int fileNameLen)
  25. {
  26. isCompressed = ByteOrder::littleEndianShort (buffer + 10) != 0;
  27. entry.fileTime = parseFileTime ((uint32) ByteOrder::littleEndianShort (buffer + 12),
  28. (uint32) ByteOrder::littleEndianShort (buffer + 14));
  29. compressedSize = (int64) (uint32) ByteOrder::littleEndianInt (buffer + 20);
  30. entry.uncompressedSize = (int64) (uint32) ByteOrder::littleEndianInt (buffer + 24);
  31. streamOffset = (int64) (uint32) ByteOrder::littleEndianInt (buffer + 42);
  32. entry.filename = String::fromUTF8 (buffer + 46, fileNameLen);
  33. }
  34. struct FileNameComparator
  35. {
  36. static int compareElements (const ZipEntryHolder* e1, const ZipEntryHolder* e2) noexcept
  37. {
  38. return e1->entry.filename.compare (e2->entry.filename);
  39. }
  40. };
  41. ZipEntry entry;
  42. int64 streamOffset, compressedSize;
  43. bool isCompressed;
  44. private:
  45. static Time parseFileTime (uint32 time, uint32 date) noexcept
  46. {
  47. const int year = 1980 + (date >> 9);
  48. const int month = ((date >> 5) & 15) - 1;
  49. const int day = date & 31;
  50. const int hours = time >> 11;
  51. const int minutes = (time >> 5) & 63;
  52. const int seconds = (int) ((time & 31) << 1);
  53. return Time (year, month, day, hours, minutes, seconds);
  54. }
  55. };
  56. //==============================================================================
  57. namespace
  58. {
  59. int findEndOfZipEntryTable (InputStream& input, int& numEntries)
  60. {
  61. BufferedInputStream in (input, 8192);
  62. in.setPosition (in.getTotalLength());
  63. int64 pos = in.getPosition();
  64. const int64 lowestPos = jmax ((int64) 0, pos - 1024);
  65. char buffer [32] = { 0 };
  66. while (pos > lowestPos)
  67. {
  68. in.setPosition (pos - 22);
  69. pos = in.getPosition();
  70. memcpy (buffer + 22, buffer, 4);
  71. if (in.read (buffer, 22) != 22)
  72. return 0;
  73. for (int i = 0; i < 22; ++i)
  74. {
  75. if (ByteOrder::littleEndianInt (buffer + i) == 0x06054b50)
  76. {
  77. in.setPosition (pos + i);
  78. in.read (buffer, 22);
  79. numEntries = ByteOrder::littleEndianShort (buffer + 10);
  80. return (int) ByteOrder::littleEndianInt (buffer + 16);
  81. }
  82. }
  83. }
  84. return 0;
  85. }
  86. }
  87. //==============================================================================
  88. class ZipFile::ZipInputStream : public InputStream
  89. {
  90. public:
  91. ZipInputStream (ZipFile& zf, ZipFile::ZipEntryHolder& zei)
  92. : file (zf),
  93. zipEntryHolder (zei),
  94. pos (0),
  95. headerSize (0),
  96. inputStream (zf.inputStream)
  97. {
  98. if (zf.inputSource != nullptr)
  99. {
  100. inputStream = streamToDelete = file.inputSource->createInputStream();
  101. }
  102. else
  103. {
  104. #if JUCE_DEBUG
  105. zf.streamCounter.numOpenStreams++;
  106. #endif
  107. }
  108. char buffer [30];
  109. if (inputStream != nullptr
  110. && inputStream->setPosition (zei.streamOffset)
  111. && inputStream->read (buffer, 30) == 30
  112. && ByteOrder::littleEndianInt (buffer) == 0x04034b50)
  113. {
  114. headerSize = 30 + ByteOrder::littleEndianShort (buffer + 26)
  115. + ByteOrder::littleEndianShort (buffer + 28);
  116. }
  117. }
  118. ~ZipInputStream()
  119. {
  120. #if JUCE_DEBUG
  121. if (inputStream != nullptr && inputStream == file.inputStream)
  122. file.streamCounter.numOpenStreams--;
  123. #endif
  124. }
  125. int64 getTotalLength() override
  126. {
  127. return zipEntryHolder.compressedSize;
  128. }
  129. int read (void* buffer, int howMany) override
  130. {
  131. if (headerSize <= 0)
  132. return 0;
  133. howMany = (int) jmin ((int64) howMany, zipEntryHolder.compressedSize - pos);
  134. if (inputStream == nullptr)
  135. return 0;
  136. int num;
  137. if (inputStream == file.inputStream)
  138. {
  139. const ScopedLock sl (file.lock);
  140. inputStream->setPosition (pos + zipEntryHolder.streamOffset + headerSize);
  141. num = inputStream->read (buffer, howMany);
  142. }
  143. else
  144. {
  145. inputStream->setPosition (pos + zipEntryHolder.streamOffset + headerSize);
  146. num = inputStream->read (buffer, howMany);
  147. }
  148. pos += num;
  149. return num;
  150. }
  151. bool isExhausted() override
  152. {
  153. return headerSize <= 0 || pos >= zipEntryHolder.compressedSize;
  154. }
  155. int64 getPosition() override
  156. {
  157. return pos;
  158. }
  159. bool setPosition (int64 newPos) override
  160. {
  161. pos = jlimit ((int64) 0, zipEntryHolder.compressedSize, newPos);
  162. return true;
  163. }
  164. private:
  165. ZipFile& file;
  166. ZipEntryHolder zipEntryHolder;
  167. int64 pos;
  168. int headerSize;
  169. InputStream* inputStream;
  170. ScopedPointer<InputStream> streamToDelete;
  171. JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ZipInputStream)
  172. };
  173. //==============================================================================
  174. ZipFile::ZipFile (InputStream* const stream, const bool deleteStreamWhenDestroyed)
  175. : inputStream (stream)
  176. {
  177. if (deleteStreamWhenDestroyed)
  178. streamToDelete = inputStream;
  179. init();
  180. }
  181. ZipFile::ZipFile (InputStream& stream)
  182. : inputStream (&stream)
  183. {
  184. init();
  185. }
  186. ZipFile::ZipFile (const File& file)
  187. : inputStream (nullptr),
  188. inputSource (new FileInputSource (file))
  189. {
  190. init();
  191. }
  192. ZipFile::ZipFile (InputSource* const source)
  193. : inputStream (nullptr),
  194. inputSource (source)
  195. {
  196. init();
  197. }
  198. ZipFile::~ZipFile()
  199. {
  200. entries.clear();
  201. }
  202. #if JUCE_DEBUG
  203. ZipFile::OpenStreamCounter::~OpenStreamCounter()
  204. {
  205. /* If you hit this assertion, it means you've created a stream to read one of the items in the
  206. zipfile, but you've forgotten to delete that stream object before deleting the file..
  207. Streams can't be kept open after the file is deleted because they need to share the input
  208. stream that is managed by the ZipFile object.
  209. */
  210. jassert (numOpenStreams == 0);
  211. }
  212. #endif
  213. //==============================================================================
  214. int ZipFile::getNumEntries() const noexcept
  215. {
  216. return entries.size();
  217. }
  218. const ZipFile::ZipEntry* ZipFile::getEntry (const int index) const noexcept
  219. {
  220. if (ZipEntryHolder* const zei = entries [index])
  221. return &(zei->entry);
  222. return nullptr;
  223. }
  224. int ZipFile::getIndexOfFileName (const String& fileName) const noexcept
  225. {
  226. for (int i = 0; i < entries.size(); ++i)
  227. if (entries.getUnchecked (i)->entry.filename == fileName)
  228. return i;
  229. return -1;
  230. }
  231. const ZipFile::ZipEntry* ZipFile::getEntry (const String& fileName) const noexcept
  232. {
  233. return getEntry (getIndexOfFileName (fileName));
  234. }
  235. InputStream* ZipFile::createStreamForEntry (const int index)
  236. {
  237. InputStream* stream = nullptr;
  238. if (ZipEntryHolder* const zei = entries[index])
  239. {
  240. stream = new ZipInputStream (*this, *zei);
  241. if (zei->isCompressed)
  242. {
  243. stream = new GZIPDecompressorInputStream (stream, true,
  244. GZIPDecompressorInputStream::deflateFormat,
  245. zei->entry.uncompressedSize);
  246. // (much faster to unzip in big blocks using a buffer..)
  247. stream = new BufferedInputStream (stream, 32768, true);
  248. }
  249. }
  250. return stream;
  251. }
  252. InputStream* ZipFile::createStreamForEntry (const ZipEntry& entry)
  253. {
  254. for (int i = 0; i < entries.size(); ++i)
  255. if (&entries.getUnchecked (i)->entry == &entry)
  256. return createStreamForEntry (i);
  257. return nullptr;
  258. }
  259. void ZipFile::sortEntriesByFilename()
  260. {
  261. ZipEntryHolder::FileNameComparator sorter;
  262. entries.sort (sorter);
  263. }
  264. //==============================================================================
  265. void ZipFile::init()
  266. {
  267. ScopedPointer<InputStream> toDelete;
  268. InputStream* in = inputStream;
  269. if (inputSource != nullptr)
  270. {
  271. in = inputSource->createInputStream();
  272. toDelete = in;
  273. }
  274. if (in != nullptr)
  275. {
  276. int numEntries = 0;
  277. int pos = findEndOfZipEntryTable (*in, numEntries);
  278. if (pos >= 0 && pos < in->getTotalLength())
  279. {
  280. const int size = (int) (in->getTotalLength() - pos);
  281. in->setPosition (pos);
  282. MemoryBlock headerData;
  283. if (in->readIntoMemoryBlock (headerData, size) == (size_t) size)
  284. {
  285. pos = 0;
  286. for (int i = 0; i < numEntries; ++i)
  287. {
  288. if (pos + 46 > size)
  289. break;
  290. const char* const buffer = static_cast<const char*> (headerData.getData()) + pos;
  291. const int fileNameLen = ByteOrder::littleEndianShort (buffer + 28);
  292. if (pos + 46 + fileNameLen > size)
  293. break;
  294. entries.add (new ZipEntryHolder (buffer, fileNameLen));
  295. pos += 46 + fileNameLen
  296. + ByteOrder::littleEndianShort (buffer + 30)
  297. + ByteOrder::littleEndianShort (buffer + 32);
  298. }
  299. }
  300. }
  301. }
  302. }
  303. Result ZipFile::uncompressTo (const File& targetDirectory,
  304. const bool shouldOverwriteFiles)
  305. {
  306. for (int i = 0; i < entries.size(); ++i)
  307. {
  308. Result result (uncompressEntry (i, targetDirectory, shouldOverwriteFiles));
  309. if (result.failed())
  310. return result;
  311. }
  312. return Result::ok();
  313. }
  314. Result ZipFile::uncompressEntry (const int index,
  315. const File& targetDirectory,
  316. bool shouldOverwriteFiles)
  317. {
  318. const ZipEntryHolder* zei = entries.getUnchecked (index);
  319. #if JUCE_WINDOWS
  320. const String entryPath (zei->entry.filename);
  321. #else
  322. const String entryPath (zei->entry.filename.replaceCharacter ('\\', '/'));
  323. #endif
  324. const File targetFile (targetDirectory.getChildFile (entryPath));
  325. if (entryPath.endsWithChar ('/') || entryPath.endsWithChar ('\\'))
  326. return targetFile.createDirectory(); // (entry is a directory, not a file)
  327. ScopedPointer<InputStream> in (createStreamForEntry (index));
  328. if (in == nullptr)
  329. return Result::fail ("Failed to open the zip file for reading");
  330. if (targetFile.exists())
  331. {
  332. if (! shouldOverwriteFiles)
  333. return Result::ok();
  334. if (! targetFile.deleteFile())
  335. return Result::fail ("Failed to write to target file: " + targetFile.getFullPathName());
  336. }
  337. if (! targetFile.getParentDirectory().createDirectory())
  338. return Result::fail ("Failed to create target folder: " + targetFile.getParentDirectory().getFullPathName());
  339. {
  340. FileOutputStream out (targetFile);
  341. if (out.failedToOpen())
  342. return Result::fail ("Failed to write to target file: " + targetFile.getFullPathName());
  343. out << *in;
  344. }
  345. targetFile.setCreationTime (zei->entry.fileTime);
  346. targetFile.setLastModificationTime (zei->entry.fileTime);
  347. targetFile.setLastAccessTime (zei->entry.fileTime);
  348. return Result::ok();
  349. }
  350. //==============================================================================
  351. class ZipFile::Builder::Item
  352. {
  353. public:
  354. Item (const File& f, InputStream* s, int compression, const String& storedPath, Time time)
  355. : file (f), stream (s), storedPathname (storedPath), fileTime (time),
  356. compressedSize (0), uncompressedSize (0), headerStart (0),
  357. compressionLevel (compression), checksum (0)
  358. {
  359. }
  360. bool writeData (OutputStream& target, const int64 overallStartPosition)
  361. {
  362. MemoryOutputStream compressedData ((size_t) file.getSize());
  363. if (compressionLevel > 0)
  364. {
  365. GZIPCompressorOutputStream compressor (&compressedData, compressionLevel, false,
  366. GZIPCompressorOutputStream::windowBitsRaw);
  367. if (! writeSource (compressor))
  368. return false;
  369. }
  370. else
  371. {
  372. if (! writeSource (compressedData))
  373. return false;
  374. }
  375. compressedSize = (int64) compressedData.getDataSize();
  376. headerStart = target.getPosition() - overallStartPosition;
  377. target.writeInt (0x04034b50);
  378. writeFlagsAndSizes (target);
  379. target << storedPathname
  380. << compressedData;
  381. return true;
  382. }
  383. bool writeDirectoryEntry (OutputStream& target)
  384. {
  385. target.writeInt (0x02014b50);
  386. target.writeShort (20); // version written
  387. writeFlagsAndSizes (target);
  388. target.writeShort (0); // comment length
  389. target.writeShort (0); // start disk num
  390. target.writeShort (0); // internal attributes
  391. target.writeInt (0); // external attributes
  392. target.writeInt ((int) (uint32) headerStart);
  393. target << storedPathname;
  394. return true;
  395. }
  396. private:
  397. const File file;
  398. ScopedPointer<InputStream> stream;
  399. String storedPathname;
  400. Time fileTime;
  401. int64 compressedSize, uncompressedSize, headerStart;
  402. int compressionLevel;
  403. unsigned long checksum;
  404. static void writeTimeAndDate (OutputStream& target, Time t)
  405. {
  406. target.writeShort ((short) (t.getSeconds() + (t.getMinutes() << 5) + (t.getHours() << 11)));
  407. target.writeShort ((short) (t.getDayOfMonth() + ((t.getMonth() + 1) << 5) + ((t.getYear() - 1980) << 9)));
  408. }
  409. bool writeSource (OutputStream& target)
  410. {
  411. if (stream == nullptr)
  412. {
  413. stream = file.createInputStream();
  414. if (stream == nullptr)
  415. return false;
  416. }
  417. checksum = 0;
  418. uncompressedSize = 0;
  419. const int bufferSize = 4096;
  420. HeapBlock<unsigned char> buffer (bufferSize);
  421. while (! stream->isExhausted())
  422. {
  423. const int bytesRead = stream->read (buffer, bufferSize);
  424. if (bytesRead < 0)
  425. return false;
  426. checksum = zlibNamespace::crc32 (checksum, buffer, (unsigned int) bytesRead);
  427. target.write (buffer, (size_t) bytesRead);
  428. uncompressedSize += bytesRead;
  429. }
  430. stream = nullptr;
  431. return true;
  432. }
  433. void writeFlagsAndSizes (OutputStream& target) const
  434. {
  435. target.writeShort (10); // version needed
  436. target.writeShort ((short) (1 << 11)); // this flag indicates UTF-8 filename encoding
  437. target.writeShort (compressionLevel > 0 ? (short) 8 : (short) 0);
  438. writeTimeAndDate (target, fileTime);
  439. target.writeInt ((int) checksum);
  440. target.writeInt ((int) (uint32) compressedSize);
  441. target.writeInt ((int) (uint32) uncompressedSize);
  442. target.writeShort ((short) storedPathname.toUTF8().sizeInBytes() - 1);
  443. target.writeShort (0); // extra field length
  444. }
  445. JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (Item)
  446. };
  447. //==============================================================================
  448. ZipFile::Builder::Builder() {}
  449. ZipFile::Builder::~Builder() {}
  450. void ZipFile::Builder::addFile (const File& file, const int compression, const String& path)
  451. {
  452. items.add (new Item (file, nullptr, compression,
  453. path.isEmpty() ? file.getFileName() : path,
  454. file.getLastModificationTime()));
  455. }
  456. void ZipFile::Builder::addEntry (InputStream* stream, int compression, const String& path, Time time)
  457. {
  458. jassert (stream != nullptr); // must not be null!
  459. jassert (path.isNotEmpty());
  460. items.add (new Item (File(), stream, compression, path, time));
  461. }
  462. bool ZipFile::Builder::writeToStream (OutputStream& target, double* const progress) const
  463. {
  464. const int64 fileStart = target.getPosition();
  465. for (int i = 0; i < items.size(); ++i)
  466. {
  467. if (progress != nullptr)
  468. *progress = (i + 0.5) / items.size();
  469. if (! items.getUnchecked (i)->writeData (target, fileStart))
  470. return false;
  471. }
  472. const int64 directoryStart = target.getPosition();
  473. for (int i = 0; i < items.size(); ++i)
  474. if (! items.getUnchecked (i)->writeDirectoryEntry (target))
  475. return false;
  476. const int64 directoryEnd = target.getPosition();
  477. target.writeInt (0x06054b50);
  478. target.writeShort (0);
  479. target.writeShort (0);
  480. target.writeShort ((short) items.size());
  481. target.writeShort ((short) items.size());
  482. target.writeInt ((int) (directoryEnd - directoryStart));
  483. target.writeInt ((int) (directoryStart - fileStart));
  484. target.writeShort (0);
  485. if (progress != nullptr)
  486. *progress = 1.0;
  487. return true;
  488. }