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.

572 lines
17KB

  1. /*
  2. ==============================================================================
  3. This file is part of the JUCE library - "Jules' Utility Class Extensions"
  4. Copyright 2004-11 by Raw Material Software Ltd.
  5. ------------------------------------------------------------------------------
  6. JUCE can be redistributed and/or modified under the terms of the GNU General
  7. Public License (Version 2), as published by the Free Software Foundation.
  8. A copy of the license is included in the JUCE distribution, or can be found
  9. online at www.gnu.org/licenses.
  10. JUCE is distributed in the hope that it will be useful, but WITHOUT ANY
  11. WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
  12. A PARTICULAR PURPOSE. See the GNU General Public License for more details.
  13. ------------------------------------------------------------------------------
  14. To release a closed-source product which uses JUCE, commercial licenses are
  15. available: visit www.rawmaterialsoftware.com/juce for more information.
  16. ==============================================================================
  17. */
  18. BEGIN_JUCE_NAMESPACE
  19. //==============================================================================
  20. class ZipFile::ZipEntryInfo
  21. {
  22. public:
  23. ZipFile::ZipEntry entry;
  24. size_t streamOffset;
  25. size_t compressedSize;
  26. bool compressed;
  27. };
  28. //==============================================================================
  29. class ZipFile::ZipInputStream : public InputStream
  30. {
  31. public:
  32. ZipInputStream (ZipFile& file_, ZipFile::ZipEntryInfo& zei)
  33. : file (file_),
  34. zipEntryInfo (zei),
  35. pos (0),
  36. headerSize (0),
  37. inputStream (file_.inputStream)
  38. {
  39. if (file_.inputSource != nullptr)
  40. {
  41. inputStream = streamToDelete = file.inputSource->createInputStream();
  42. }
  43. else
  44. {
  45. #if JUCE_DEBUG
  46. file_.numOpenStreams++;
  47. #endif
  48. }
  49. char buffer [30];
  50. if (inputStream != nullptr
  51. && inputStream->setPosition (zei.streamOffset)
  52. && inputStream->read (buffer, 30) == 30
  53. && ByteOrder::littleEndianInt (buffer) == 0x04034b50)
  54. {
  55. headerSize = 30 + ByteOrder::littleEndianShort (buffer + 26)
  56. + ByteOrder::littleEndianShort (buffer + 28);
  57. }
  58. }
  59. ~ZipInputStream()
  60. {
  61. #if JUCE_DEBUG
  62. if (inputStream != nullptr && inputStream == file.inputStream)
  63. file.numOpenStreams--;
  64. #endif
  65. }
  66. int64 getTotalLength()
  67. {
  68. return zipEntryInfo.compressedSize;
  69. }
  70. int read (void* buffer, int howMany)
  71. {
  72. if (headerSize <= 0)
  73. return 0;
  74. howMany = (int) jmin ((int64) howMany, (int64) (zipEntryInfo.compressedSize - pos));
  75. if (inputStream == nullptr)
  76. return 0;
  77. int num;
  78. if (inputStream == file.inputStream)
  79. {
  80. const ScopedLock sl (file.lock);
  81. inputStream->setPosition (pos + zipEntryInfo.streamOffset + headerSize);
  82. num = inputStream->read (buffer, howMany);
  83. }
  84. else
  85. {
  86. inputStream->setPosition (pos + zipEntryInfo.streamOffset + headerSize);
  87. num = inputStream->read (buffer, howMany);
  88. }
  89. pos += num;
  90. return num;
  91. }
  92. bool isExhausted()
  93. {
  94. return headerSize <= 0 || pos >= (int64) zipEntryInfo.compressedSize;
  95. }
  96. int64 getPosition()
  97. {
  98. return pos;
  99. }
  100. bool setPosition (int64 newPos)
  101. {
  102. pos = jlimit ((int64) 0, (int64) zipEntryInfo.compressedSize, newPos);
  103. return true;
  104. }
  105. private:
  106. ZipFile& file;
  107. ZipEntryInfo zipEntryInfo;
  108. int64 pos;
  109. int headerSize;
  110. InputStream* inputStream;
  111. ScopedPointer<InputStream> streamToDelete;
  112. JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ZipInputStream);
  113. };
  114. //==============================================================================
  115. ZipFile::ZipFile (InputStream* const source_, const bool deleteStreamWhenDestroyed)
  116. : inputStream (source_)
  117. #if JUCE_DEBUG
  118. , numOpenStreams (0)
  119. #endif
  120. {
  121. if (deleteStreamWhenDestroyed)
  122. streamToDelete = inputStream;
  123. init();
  124. }
  125. ZipFile::ZipFile (const File& file)
  126. : inputStream (nullptr)
  127. #if JUCE_DEBUG
  128. , numOpenStreams (0)
  129. #endif
  130. {
  131. inputSource = new FileInputSource (file);
  132. init();
  133. }
  134. ZipFile::ZipFile (InputSource* const inputSource_)
  135. : inputStream (nullptr),
  136. inputSource (inputSource_)
  137. #if JUCE_DEBUG
  138. , numOpenStreams (0)
  139. #endif
  140. {
  141. init();
  142. }
  143. ZipFile::~ZipFile()
  144. {
  145. #if JUCE_DEBUG
  146. entries.clear();
  147. /* If you hit this assertion, it means you've created a stream to read one of the items in the
  148. zipfile, but you've forgotten to delete that stream object before deleting the file..
  149. Streams can't be kept open after the file is deleted because they need to share the input
  150. stream that the file uses to read itself.
  151. */
  152. jassert (numOpenStreams == 0);
  153. #endif
  154. }
  155. //==============================================================================
  156. int ZipFile::getNumEntries() const noexcept
  157. {
  158. return entries.size();
  159. }
  160. const ZipFile::ZipEntry* ZipFile::getEntry (const int index) const noexcept
  161. {
  162. ZipEntryInfo* const zei = entries [index];
  163. return zei != nullptr ? &(zei->entry) : nullptr;
  164. }
  165. int ZipFile::getIndexOfFileName (const String& fileName) const noexcept
  166. {
  167. for (int i = 0; i < entries.size(); ++i)
  168. if (entries.getUnchecked (i)->entry.filename == fileName)
  169. return i;
  170. return -1;
  171. }
  172. const ZipFile::ZipEntry* ZipFile::getEntry (const String& fileName) const noexcept
  173. {
  174. return getEntry (getIndexOfFileName (fileName));
  175. }
  176. InputStream* ZipFile::createStreamForEntry (const int index)
  177. {
  178. ZipEntryInfo* const zei = entries[index];
  179. InputStream* stream = nullptr;
  180. if (zei != nullptr)
  181. {
  182. stream = new ZipInputStream (*this, *zei);
  183. if (zei->compressed)
  184. {
  185. stream = new GZIPDecompressorInputStream (stream, true, true,
  186. zei->entry.uncompressedSize);
  187. // (much faster to unzip in big blocks using a buffer..)
  188. stream = new BufferedInputStream (stream, 32768, true);
  189. }
  190. }
  191. return stream;
  192. }
  193. class ZipFile::ZipFilenameComparator
  194. {
  195. public:
  196. int compareElements (const ZipFile::ZipEntryInfo* first, const ZipFile::ZipEntryInfo* second)
  197. {
  198. return first->entry.filename.compare (second->entry.filename);
  199. }
  200. };
  201. void ZipFile::sortEntriesByFilename()
  202. {
  203. ZipFilenameComparator sorter;
  204. entries.sort (sorter);
  205. }
  206. //==============================================================================
  207. void ZipFile::init()
  208. {
  209. ScopedPointer <InputStream> toDelete;
  210. InputStream* in = inputStream;
  211. if (inputSource != nullptr)
  212. {
  213. in = inputSource->createInputStream();
  214. toDelete = in;
  215. }
  216. if (in != nullptr)
  217. {
  218. int numEntries = 0;
  219. int pos = findEndOfZipEntryTable (*in, numEntries);
  220. if (pos >= 0 && pos < in->getTotalLength())
  221. {
  222. const int size = (int) (in->getTotalLength() - pos);
  223. in->setPosition (pos);
  224. MemoryBlock headerData;
  225. if (in->readIntoMemoryBlock (headerData, size) == size)
  226. {
  227. pos = 0;
  228. for (int i = 0; i < numEntries; ++i)
  229. {
  230. if (pos + 46 > size)
  231. break;
  232. const char* const buffer = static_cast <const char*> (headerData.getData()) + pos;
  233. const int fileNameLen = ByteOrder::littleEndianShort (buffer + 28);
  234. if (pos + 46 + fileNameLen > size)
  235. break;
  236. ZipEntryInfo* const zei = new ZipEntryInfo();
  237. zei->entry.filename = String::fromUTF8 (buffer + 46, fileNameLen);
  238. const int time = ByteOrder::littleEndianShort (buffer + 12);
  239. const int date = ByteOrder::littleEndianShort (buffer + 14);
  240. const int year = 1980 + (date >> 9);
  241. const int month = ((date >> 5) & 15) - 1;
  242. const int day = date & 31;
  243. const int hours = time >> 11;
  244. const int minutes = (time >> 5) & 63;
  245. const int seconds = (time & 31) << 1;
  246. zei->entry.fileTime = Time (year, month, day, hours, minutes, seconds);
  247. zei->compressed = ByteOrder::littleEndianShort (buffer + 10) != 0;
  248. zei->compressedSize = (size_t) ByteOrder::littleEndianInt (buffer + 20);
  249. zei->entry.uncompressedSize = ByteOrder::littleEndianInt (buffer + 24);
  250. zei->streamOffset = ByteOrder::littleEndianInt (buffer + 42);
  251. entries.add (zei);
  252. pos += 46 + fileNameLen
  253. + ByteOrder::littleEndianShort (buffer + 30)
  254. + ByteOrder::littleEndianShort (buffer + 32);
  255. }
  256. }
  257. }
  258. }
  259. }
  260. int ZipFile::findEndOfZipEntryTable (InputStream& input, int& numEntries)
  261. {
  262. BufferedInputStream in (input, 8192);
  263. in.setPosition (in.getTotalLength());
  264. int64 pos = in.getPosition();
  265. const int64 lowestPos = jmax ((int64) 0, pos - 1024);
  266. char buffer [32] = { 0 };
  267. while (pos > lowestPos)
  268. {
  269. in.setPosition (pos - 22);
  270. pos = in.getPosition();
  271. memcpy (buffer + 22, buffer, 4);
  272. if (in.read (buffer, 22) != 22)
  273. return 0;
  274. for (int i = 0; i < 22; ++i)
  275. {
  276. if (ByteOrder::littleEndianInt (buffer + i) == 0x06054b50)
  277. {
  278. in.setPosition (pos + i);
  279. in.read (buffer, 22);
  280. numEntries = ByteOrder::littleEndianShort (buffer + 10);
  281. return (int) ByteOrder::littleEndianInt (buffer + 16);
  282. }
  283. }
  284. }
  285. return 0;
  286. }
  287. Result ZipFile::uncompressTo (const File& targetDirectory,
  288. const bool shouldOverwriteFiles)
  289. {
  290. for (int i = 0; i < entries.size(); ++i)
  291. {
  292. Result result (uncompressEntry (i, targetDirectory, shouldOverwriteFiles));
  293. if (result.failed())
  294. return result;
  295. }
  296. return Result::ok();
  297. }
  298. Result ZipFile::uncompressEntry (const int index,
  299. const File& targetDirectory,
  300. bool shouldOverwriteFiles)
  301. {
  302. const ZipEntryInfo* zei = entries.getUnchecked (index);
  303. const File targetFile (targetDirectory.getChildFile (zei->entry.filename));
  304. if (zei->entry.filename.endsWithChar ('/'))
  305. {
  306. return targetFile.createDirectory(); // (entry is a directory, not a file)
  307. }
  308. else
  309. {
  310. ScopedPointer<InputStream> in (createStreamForEntry (index));
  311. if (in == nullptr)
  312. return Result::fail ("Failed to open the zip file for reading");
  313. if (targetFile.exists())
  314. {
  315. if (! shouldOverwriteFiles)
  316. return Result::ok();
  317. if (! targetFile.deleteFile())
  318. return Result::fail ("Failed to write to target file: " + targetFile.getFullPathName());
  319. }
  320. if (! targetFile.getParentDirectory().createDirectory())
  321. return Result::fail ("Failed to create target folder: " + targetFile.getParentDirectory().getFullPathName());
  322. {
  323. FileOutputStream out (targetFile);
  324. if (out.failedToOpen())
  325. return Result::fail ("Failed to write to target file: " + targetFile.getFullPathName());
  326. out << *in;
  327. }
  328. targetFile.setCreationTime (zei->entry.fileTime);
  329. targetFile.setLastModificationTime (zei->entry.fileTime);
  330. targetFile.setLastAccessTime (zei->entry.fileTime);
  331. return Result::ok();
  332. }
  333. }
  334. //=============================================================================
  335. extern unsigned long juce_crc32 (unsigned long crc, const unsigned char* buf, unsigned len);
  336. class ZipFile::Builder::Item
  337. {
  338. public:
  339. Item (const File& file_, const int compressionLevel_, const String& storedPathName_)
  340. : file (file_),
  341. storedPathname (storedPathName_.isEmpty() ? file_.getFileName() : storedPathName_),
  342. compressionLevel (compressionLevel_),
  343. compressedSize (0),
  344. headerStart (0)
  345. {
  346. }
  347. bool writeData (OutputStream& target, const int64 overallStartPosition)
  348. {
  349. MemoryOutputStream compressedData;
  350. if (compressionLevel > 0)
  351. {
  352. GZIPCompressorOutputStream compressor (&compressedData, compressionLevel, false,
  353. GZIPCompressorOutputStream::windowBitsRaw);
  354. if (! writeSource (compressor))
  355. return false;
  356. }
  357. else
  358. {
  359. if (! writeSource (compressedData))
  360. return false;
  361. }
  362. compressedSize = (int) compressedData.getDataSize();
  363. headerStart = (int) (target.getPosition() - overallStartPosition);
  364. target.writeInt (0x04034b50);
  365. writeFlagsAndSizes (target);
  366. target << storedPathname
  367. << compressedData;
  368. return true;
  369. }
  370. bool writeDirectoryEntry (OutputStream& target)
  371. {
  372. target.writeInt (0x02014b50);
  373. target.writeShort (20); // version written
  374. writeFlagsAndSizes (target);
  375. target.writeShort (0); // comment length
  376. target.writeShort (0); // start disk num
  377. target.writeShort (0); // internal attributes
  378. target.writeInt (0); // external attributes
  379. target.writeInt (headerStart);
  380. target << storedPathname;
  381. return true;
  382. }
  383. private:
  384. const File file;
  385. String storedPathname;
  386. int compressionLevel, compressedSize, headerStart;
  387. unsigned long checksum;
  388. void writeTimeAndDate (OutputStream& target) const
  389. {
  390. const Time t (file.getLastModificationTime());
  391. target.writeShort ((short) (t.getSeconds() + (t.getMinutes() << 5) + (t.getHours() << 11)));
  392. target.writeShort ((short) (t.getDayOfMonth() + ((t.getMonth() + 1) << 5) + ((t.getYear() - 1980) << 9)));
  393. }
  394. bool writeSource (OutputStream& target)
  395. {
  396. checksum = 0;
  397. FileInputStream input (file);
  398. if (input.failedToOpen())
  399. return false;
  400. const int bufferSize = 2048;
  401. HeapBlock<unsigned char> buffer (bufferSize);
  402. while (! input.isExhausted())
  403. {
  404. const int bytesRead = input.read (buffer, bufferSize);
  405. if (bytesRead < 0)
  406. return false;
  407. checksum = juce_crc32 (checksum, buffer, (unsigned int) bytesRead);
  408. target.write (buffer, bytesRead);
  409. }
  410. return true;
  411. }
  412. void writeFlagsAndSizes (OutputStream& target) const
  413. {
  414. target.writeShort (10); // version needed
  415. target.writeShort (0); // flags
  416. target.writeShort (compressionLevel > 0 ? (short) 8 : (short) 0);
  417. writeTimeAndDate (target);
  418. target.writeInt ((int) checksum);
  419. target.writeInt (compressedSize);
  420. target.writeInt ((int) file.getSize());
  421. target.writeShort ((short) storedPathname.toUTF8().sizeInBytes() - 1);
  422. target.writeShort (0); // extra field length
  423. }
  424. JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (Item);
  425. };
  426. //=============================================================================
  427. ZipFile::Builder::Builder() {}
  428. ZipFile::Builder::~Builder() {}
  429. void ZipFile::Builder::addFile (const File& fileToAdd, const int compressionLevel, const String& storedPathName)
  430. {
  431. items.add (new Item (fileToAdd, compressionLevel, storedPathName));
  432. }
  433. bool ZipFile::Builder::writeToStream (OutputStream& target) const
  434. {
  435. const int64 fileStart = target.getPosition();
  436. int i;
  437. for (i = 0; i < items.size(); ++i)
  438. if (! items.getUnchecked (i)->writeData (target, fileStart))
  439. return false;
  440. const int64 directoryStart = target.getPosition();
  441. for (i = 0; i < items.size(); ++i)
  442. if (! items.getUnchecked (i)->writeDirectoryEntry (target))
  443. return false;
  444. const int64 directoryEnd = target.getPosition();
  445. target.writeInt (0x06054b50);
  446. target.writeShort (0);
  447. target.writeShort (0);
  448. target.writeShort ((short) items.size());
  449. target.writeShort ((short) items.size());
  450. target.writeInt ((int) (directoryEnd - directoryStart));
  451. target.writeInt ((int) (directoryStart - fileStart));
  452. target.writeShort (0);
  453. target.flush();
  454. return true;
  455. }
  456. END_JUCE_NAMESPACE