|
- /*
- ==============================================================================
-
- This file is part of the JUCE library - "Jules' Utility Class Extensions"
- Copyright 2004-11 by Raw Material Software Ltd.
-
- ------------------------------------------------------------------------------
-
- JUCE can be redistributed and/or modified under the terms of the GNU General
- Public License (Version 2), as published by the Free Software Foundation.
- A copy of the license is included in the JUCE distribution, or can be found
- online at www.gnu.org/licenses.
-
- JUCE is distributed in the hope that it will be useful, but WITHOUT ANY
- WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
- A PARTICULAR PURPOSE. See the GNU General Public License for more details.
-
- ------------------------------------------------------------------------------
-
- To release a closed-source product which uses JUCE, commercial licenses are
- available: visit www.rawmaterialsoftware.com/juce for more information.
-
- ==============================================================================
- */
-
- class ZipFile::ZipEntryHolder
- {
- public:
- ZipEntryHolder (const char* const buffer, const int fileNameLen)
- {
- entry.filename = String::fromUTF8 (buffer + 46, fileNameLen);
-
- const int time = ByteOrder::littleEndianShort (buffer + 12);
- const int date = ByteOrder::littleEndianShort (buffer + 14);
- entry.fileTime = getFileTimeFromRawEncodings (time, date);
-
- compressed = ByteOrder::littleEndianShort (buffer + 10) != 0;
- compressedSize = (size_t) ByteOrder::littleEndianInt (buffer + 20);
- entry.uncompressedSize = ByteOrder::littleEndianInt (buffer + 24);
-
- streamOffset = ByteOrder::littleEndianInt (buffer + 42);
- }
-
- struct FileNameComparator
- {
- static int compareElements (const ZipEntryHolder* first, const ZipEntryHolder* second)
- {
- return first->entry.filename.compare (second->entry.filename);
- }
- };
-
- ZipEntry entry;
- size_t streamOffset;
- size_t compressedSize;
- bool compressed;
-
- private:
- static Time getFileTimeFromRawEncodings (int time, int date)
- {
- const int year = 1980 + (date >> 9);
- const int month = ((date >> 5) & 15) - 1;
- const int day = date & 31;
- const int hours = time >> 11;
- const int minutes = (time >> 5) & 63;
- const int seconds = (time & 31) << 1;
-
- return Time (year, month, day, hours, minutes, seconds);
- }
- };
-
- //==============================================================================
- namespace
- {
- int findEndOfZipEntryTable (InputStream& input, int& numEntries)
- {
- BufferedInputStream in (input, 8192);
-
- in.setPosition (in.getTotalLength());
- int64 pos = in.getPosition();
- const int64 lowestPos = jmax ((int64) 0, pos - 1024);
-
- char buffer [32] = { 0 };
-
- while (pos > lowestPos)
- {
- in.setPosition (pos - 22);
- pos = in.getPosition();
- memcpy (buffer + 22, buffer, 4);
-
- if (in.read (buffer, 22) != 22)
- return 0;
-
- for (int i = 0; i < 22; ++i)
- {
- if (ByteOrder::littleEndianInt (buffer + i) == 0x06054b50)
- {
- in.setPosition (pos + i);
- in.read (buffer, 22);
- numEntries = ByteOrder::littleEndianShort (buffer + 10);
-
- return (int) ByteOrder::littleEndianInt (buffer + 16);
- }
- }
- }
-
- return 0;
- }
- }
-
- //==============================================================================
- class ZipFile::ZipInputStream : public InputStream
- {
- public:
- ZipInputStream (ZipFile& file_, ZipFile::ZipEntryHolder& zei)
- : file (file_),
- zipEntryHolder (zei),
- pos (0),
- headerSize (0),
- inputStream (file_.inputStream)
- {
- if (file_.inputSource != nullptr)
- {
- inputStream = streamToDelete = file.inputSource->createInputStream();
- }
- else
- {
- #if JUCE_DEBUG
- file_.streamCounter.numOpenStreams++;
- #endif
- }
-
- char buffer [30];
-
- if (inputStream != nullptr
- && inputStream->setPosition (zei.streamOffset)
- && inputStream->read (buffer, 30) == 30
- && ByteOrder::littleEndianInt (buffer) == 0x04034b50)
- {
- headerSize = 30 + ByteOrder::littleEndianShort (buffer + 26)
- + ByteOrder::littleEndianShort (buffer + 28);
- }
- }
-
- ~ZipInputStream()
- {
- #if JUCE_DEBUG
- if (inputStream != nullptr && inputStream == file.inputStream)
- file.streamCounter.numOpenStreams--;
- #endif
- }
-
- int64 getTotalLength()
- {
- return zipEntryHolder.compressedSize;
- }
-
- int read (void* buffer, int howMany)
- {
- if (headerSize <= 0)
- return 0;
-
- howMany = (int) jmin ((int64) howMany, (int64) (zipEntryHolder.compressedSize - pos));
-
- if (inputStream == nullptr)
- return 0;
-
- int num;
-
- if (inputStream == file.inputStream)
- {
- const ScopedLock sl (file.lock);
- inputStream->setPosition (pos + zipEntryHolder.streamOffset + headerSize);
- num = inputStream->read (buffer, howMany);
- }
- else
- {
- inputStream->setPosition (pos + zipEntryHolder.streamOffset + headerSize);
- num = inputStream->read (buffer, howMany);
- }
-
- pos += num;
- return num;
- }
-
- bool isExhausted()
- {
- return headerSize <= 0 || pos >= (int64) zipEntryHolder.compressedSize;
- }
-
- int64 getPosition()
- {
- return pos;
- }
-
- bool setPosition (int64 newPos)
- {
- pos = jlimit ((int64) 0, (int64) zipEntryHolder.compressedSize, newPos);
- return true;
- }
-
- private:
- ZipFile& file;
- ZipEntryHolder zipEntryHolder;
- int64 pos;
- int headerSize;
- InputStream* inputStream;
- ScopedPointer<InputStream> streamToDelete;
-
- JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ZipInputStream);
- };
-
-
- //==============================================================================
- ZipFile::ZipFile (InputStream* const stream, const bool deleteStreamWhenDestroyed)
- : inputStream (stream)
- {
- if (deleteStreamWhenDestroyed)
- streamToDelete = inputStream;
-
- init();
- }
-
- ZipFile::ZipFile (InputStream& stream)
- : inputStream (&stream)
- {
- init();
- }
-
- ZipFile::ZipFile (const File& file)
- : inputStream (nullptr),
- inputSource (new FileInputSource (file))
- {
- init();
- }
-
- ZipFile::ZipFile (InputSource* const inputSource_)
- : inputStream (nullptr),
- inputSource (inputSource_)
- {
- init();
- }
-
- ZipFile::~ZipFile()
- {
- entries.clear();
- }
-
- #if JUCE_DEBUG
- ZipFile::OpenStreamCounter::~OpenStreamCounter()
- {
- /* If you hit this assertion, it means you've created a stream to read one of the items in the
- zipfile, but you've forgotten to delete that stream object before deleting the file..
- Streams can't be kept open after the file is deleted because they need to share the input
- stream that is managed by the ZipFile object.
- */
- jassert (numOpenStreams == 0);
- }
- #endif
-
- //==============================================================================
- int ZipFile::getNumEntries() const noexcept
- {
- return entries.size();
- }
-
- const ZipFile::ZipEntry* ZipFile::getEntry (const int index) const noexcept
- {
- ZipEntryHolder* const zei = entries [index];
- return zei != nullptr ? &(zei->entry) : nullptr;
- }
-
- int ZipFile::getIndexOfFileName (const String& fileName) const noexcept
- {
- for (int i = 0; i < entries.size(); ++i)
- if (entries.getUnchecked (i)->entry.filename == fileName)
- return i;
-
- return -1;
- }
-
- const ZipFile::ZipEntry* ZipFile::getEntry (const String& fileName) const noexcept
- {
- return getEntry (getIndexOfFileName (fileName));
- }
-
- InputStream* ZipFile::createStreamForEntry (const int index)
- {
- ZipEntryHolder* const zei = entries[index];
- InputStream* stream = nullptr;
-
- if (zei != nullptr)
- {
- stream = new ZipInputStream (*this, *zei);
-
- if (zei->compressed)
- {
- stream = new GZIPDecompressorInputStream (stream, true, true,
- zei->entry.uncompressedSize);
-
- // (much faster to unzip in big blocks using a buffer..)
- stream = new BufferedInputStream (stream, 32768, true);
- }
- }
-
- return stream;
- }
-
- void ZipFile::sortEntriesByFilename()
- {
- ZipEntryHolder::FileNameComparator sorter;
- entries.sort (sorter);
- }
-
- //==============================================================================
- void ZipFile::init()
- {
- ScopedPointer <InputStream> toDelete;
- InputStream* in = inputStream;
-
- if (inputSource != nullptr)
- {
- in = inputSource->createInputStream();
- toDelete = in;
- }
-
- if (in != nullptr)
- {
- int numEntries = 0;
- int pos = findEndOfZipEntryTable (*in, numEntries);
-
- if (pos >= 0 && pos < in->getTotalLength())
- {
- const int size = (int) (in->getTotalLength() - pos);
-
- in->setPosition (pos);
- MemoryBlock headerData;
-
- if (in->readIntoMemoryBlock (headerData, size) == size)
- {
- pos = 0;
-
- for (int i = 0; i < numEntries; ++i)
- {
- if (pos + 46 > size)
- break;
-
- const char* const buffer = static_cast <const char*> (headerData.getData()) + pos;
-
- const int fileNameLen = ByteOrder::littleEndianShort (buffer + 28);
-
- if (pos + 46 + fileNameLen > size)
- break;
-
- entries.add (new ZipEntryHolder (buffer, fileNameLen));
-
- pos += 46 + fileNameLen
- + ByteOrder::littleEndianShort (buffer + 30)
- + ByteOrder::littleEndianShort (buffer + 32);
- }
- }
- }
- }
- }
-
- Result ZipFile::uncompressTo (const File& targetDirectory,
- const bool shouldOverwriteFiles)
- {
- for (int i = 0; i < entries.size(); ++i)
- {
- Result result (uncompressEntry (i, targetDirectory, shouldOverwriteFiles));
- if (result.failed())
- return result;
- }
-
- return Result::ok();
- }
-
- Result ZipFile::uncompressEntry (const int index,
- const File& targetDirectory,
- bool shouldOverwriteFiles)
- {
- const ZipEntryHolder* zei = entries.getUnchecked (index);
-
- const File targetFile (targetDirectory.getChildFile (zei->entry.filename));
-
- if (zei->entry.filename.endsWithChar ('/'))
- return targetFile.createDirectory(); // (entry is a directory, not a file)
-
- ScopedPointer<InputStream> in (createStreamForEntry (index));
-
- if (in == nullptr)
- return Result::fail ("Failed to open the zip file for reading");
-
- if (targetFile.exists())
- {
- if (! shouldOverwriteFiles)
- return Result::ok();
-
- if (! targetFile.deleteFile())
- return Result::fail ("Failed to write to target file: " + targetFile.getFullPathName());
- }
-
- if (! targetFile.getParentDirectory().createDirectory())
- return Result::fail ("Failed to create target folder: " + targetFile.getParentDirectory().getFullPathName());
-
- {
- FileOutputStream out (targetFile);
-
- if (out.failedToOpen())
- return Result::fail ("Failed to write to target file: " + targetFile.getFullPathName());
-
- out << *in;
- }
-
- targetFile.setCreationTime (zei->entry.fileTime);
- targetFile.setLastModificationTime (zei->entry.fileTime);
- targetFile.setLastAccessTime (zei->entry.fileTime);
-
- return Result::ok();
- }
-
-
- //=============================================================================
- extern unsigned long juce_crc32 (unsigned long crc, const unsigned char* buf, unsigned len);
-
- class ZipFile::Builder::Item
- {
- public:
- Item (const File& file_, const int compressionLevel_, const String& storedPathName_)
- : file (file_),
- storedPathname (storedPathName_.isEmpty() ? file_.getFileName() : storedPathName_),
- compressionLevel (compressionLevel_),
- compressedSize (0),
- headerStart (0)
- {
- }
-
- bool writeData (OutputStream& target, const int64 overallStartPosition)
- {
- MemoryOutputStream compressedData;
-
- if (compressionLevel > 0)
- {
- GZIPCompressorOutputStream compressor (&compressedData, compressionLevel, false,
- GZIPCompressorOutputStream::windowBitsRaw);
- if (! writeSource (compressor))
- return false;
- }
- else
- {
- if (! writeSource (compressedData))
- return false;
- }
-
- compressedSize = (int) compressedData.getDataSize();
- headerStart = (int) (target.getPosition() - overallStartPosition);
-
- target.writeInt (0x04034b50);
- writeFlagsAndSizes (target);
- target << storedPathname
- << compressedData;
-
- return true;
- }
-
- bool writeDirectoryEntry (OutputStream& target)
- {
- target.writeInt (0x02014b50);
- target.writeShort (20); // version written
- writeFlagsAndSizes (target);
- target.writeShort (0); // comment length
- target.writeShort (0); // start disk num
- target.writeShort (0); // internal attributes
- target.writeInt (0); // external attributes
- target.writeInt (headerStart);
- target << storedPathname;
-
- return true;
- }
-
- private:
- const File file;
- String storedPathname;
- int compressionLevel, compressedSize, headerStart;
- unsigned long checksum;
-
- void writeTimeAndDate (OutputStream& target) const
- {
- const Time t (file.getLastModificationTime());
- target.writeShort ((short) (t.getSeconds() + (t.getMinutes() << 5) + (t.getHours() << 11)));
- target.writeShort ((short) (t.getDayOfMonth() + ((t.getMonth() + 1) << 5) + ((t.getYear() - 1980) << 9)));
- }
-
- bool writeSource (OutputStream& target)
- {
- checksum = 0;
- FileInputStream input (file);
-
- if (input.failedToOpen())
- return false;
-
- const int bufferSize = 2048;
- HeapBlock<unsigned char> buffer (bufferSize);
-
- while (! input.isExhausted())
- {
- const int bytesRead = input.read (buffer, bufferSize);
-
- if (bytesRead < 0)
- return false;
-
- checksum = juce_crc32 (checksum, buffer, (unsigned int) bytesRead);
- target.write (buffer, bytesRead);
- }
-
- return true;
- }
-
- void writeFlagsAndSizes (OutputStream& target) const
- {
- target.writeShort (10); // version needed
- target.writeShort (0); // flags
- target.writeShort (compressionLevel > 0 ? (short) 8 : (short) 0);
- writeTimeAndDate (target);
- target.writeInt ((int) checksum);
- target.writeInt (compressedSize);
- target.writeInt ((int) file.getSize());
- target.writeShort ((short) storedPathname.toUTF8().sizeInBytes() - 1);
- target.writeShort (0); // extra field length
- }
-
- JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (Item);
- };
-
- //=============================================================================
- ZipFile::Builder::Builder() {}
- ZipFile::Builder::~Builder() {}
-
- void ZipFile::Builder::addFile (const File& fileToAdd, const int compressionLevel, const String& storedPathName)
- {
- items.add (new Item (fileToAdd, compressionLevel, storedPathName));
- }
-
- bool ZipFile::Builder::writeToStream (OutputStream& target, double* const progress) const
- {
- const int64 fileStart = target.getPosition();
-
- for (int i = 0; i < items.size(); ++i)
- {
- if (progress != nullptr)
- *progress = (i + 0.5) / items.size();
-
- if (! items.getUnchecked (i)->writeData (target, fileStart))
- return false;
- }
-
- const int64 directoryStart = target.getPosition();
-
- for (int i = 0; i < items.size(); ++i)
- if (! items.getUnchecked (i)->writeDirectoryEntry (target))
- return false;
-
- const int64 directoryEnd = target.getPosition();
-
- target.writeInt (0x06054b50);
- target.writeShort (0);
- target.writeShort (0);
- target.writeShort ((short) items.size());
- target.writeShort ((short) items.size());
- target.writeInt ((int) (directoryEnd - directoryStart));
- target.writeInt ((int) (directoryStart - fileStart));
- target.writeShort (0);
-
- if (progress != nullptr)
- *progress = 1.0;
-
- return true;
- }
|