123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609 |
- /*
- ==============================================================================
- This file is part of the juce_core module of the JUCE library.
- Copyright (c) 2013 - Raw Material Software Ltd.
- Permission to use, copy, modify, and/or distribute this software for any purpose with
- or without fee is hereby granted, provided that the above copyright notice and this
- permission notice appear in all copies.
- THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD
- TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN
- NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL
- DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER
- IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
- CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
- ------------------------------------------------------------------------------
- NOTE! This permissive ISC license applies ONLY to files within the juce_core module!
- All other JUCE modules are covered by a dual GPL/commercial license, so if you are
- using any other modules, be sure to check that you also comply with their license.
- For more details, visit www.juce.com
- ==============================================================================
- */
- 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& zf, ZipFile::ZipEntryHolder& zei)
- : file (zf),
- zipEntryHolder (zei),
- pos (0),
- headerSize (0),
- inputStream (zf.inputStream)
- {
- if (zf.inputSource != nullptr)
- {
- inputStream = streamToDelete = file.inputSource->createInputStream();
- }
- else
- {
- #if JUCE_DEBUG
- zf.streamCounter.numOpenStreams++;
- #endif
- }
- char buffer [30];
- if (inputStream != nullptr
- && inputStream->setPosition ((int64) 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 (int64) 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 + (int64) zipEntryHolder.streamOffset + headerSize);
- num = inputStream->read (buffer, howMany);
- }
- else
- {
- inputStream->setPosition (pos + (int64) 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 source)
- : inputStream (nullptr),
- inputSource (source)
- {
- 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
- {
- if (ZipEntryHolder* const zei = entries [index])
- return &(zei->entry);
- return 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)
- {
- InputStream* stream = nullptr;
- if (ZipEntryHolder* const zei = entries[index])
- {
- stream = new ZipInputStream (*this, *zei);
- if (zei->compressed)
- {
- stream = new GZIPDecompressorInputStream (stream, true, true, (int64) zei->entry.uncompressedSize);
- // (much faster to unzip in big blocks using a buffer..)
- stream = new BufferedInputStream (stream, 32768, true);
- }
- }
- return stream;
- }
- InputStream* ZipFile::createStreamForEntry (const ZipEntry& entry)
- {
- for (int i = 0; i < entries.size(); ++i)
- if (&entries.getUnchecked (i)->entry == &entry)
- return createStreamForEntry (i);
- return nullptr;
- }
- 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);
- #if JUCE_WINDOWS
- const String entryPath (zei->entry.filename);
- #else
- const String entryPath (zei->entry.filename.replaceCharacter ('\\', '/'));
- #endif
- const File targetFile (targetDirectory.getChildFile (entryPath));
- if (entryPath.endsWithChar ('/') || entryPath.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();
- }
- //=============================================================================
- class ZipFile::Builder::Item
- {
- public:
- Item (const File& f, InputStream* s, const int compression, const String& storedPath, Time time)
- : file (f), stream (s), storedPathname (storedPath),
- fileTime (time), compressionLevel (compression),
- compressedSize (0), uncompressedSize (0), headerStart (0), checksum (0)
- {
- }
- bool writeData (OutputStream& target, const int64 overallStartPosition)
- {
- MemoryOutputStream compressedData ((size_t) file.getSize());
- 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;
- ScopedPointer<InputStream> stream;
- String storedPathname;
- Time fileTime;
- int compressionLevel, compressedSize, uncompressedSize, headerStart;
- unsigned long checksum;
- static void writeTimeAndDate (OutputStream& target, Time t)
- {
- 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)
- {
- if (stream == nullptr)
- {
- stream = file.createInputStream();
- if (stream == nullptr)
- return false;
- }
- checksum = 0;
- uncompressedSize = 0;
- const int bufferSize = 4096;
- HeapBlock<unsigned char> buffer (bufferSize);
- while (! stream->isExhausted())
- {
- const int bytesRead = stream->read (buffer, bufferSize);
- if (bytesRead < 0)
- return false;
- checksum = zlibNamespace::crc32 (checksum, buffer, (unsigned int) bytesRead);
- target.write (buffer, (size_t) bytesRead);
- uncompressedSize += bytesRead;
- }
- stream = nullptr;
- return true;
- }
- void writeFlagsAndSizes (OutputStream& target) const
- {
- target.writeShort (10); // version needed
- target.writeShort ((short) (1 << 11)); // this flag indicates UTF-8 filename encoding
- target.writeShort (compressionLevel > 0 ? (short) 8 : (short) 0);
- writeTimeAndDate (target, fileTime);
- target.writeInt ((int) checksum);
- target.writeInt (compressedSize);
- target.writeInt (uncompressedSize);
- 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& file, const int compression, const String& path)
- {
- items.add (new Item (file, nullptr, compression,
- path.isEmpty() ? file.getFileName() : path,
- file.getLastModificationTime()));
- }
- void ZipFile::Builder::addEntry (InputStream* stream, int compression, const String& path, Time time)
- {
- jassert (stream != nullptr); // must not be null!
- jassert (path.isNotEmpty());
- items.add (new Item (File(), stream, compression, path, time));
- }
- 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;
- }
|