The JUCE cross-platform C++ framework, with DISTRHO/KXStudio specific changes
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

611 lines
18KB

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