/* ============================================================================== This file is part of the JUCE library - "Jules' Utility Class Extensions" Copyright 2004-10 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. ============================================================================== */ #include "../../core/juce_StandardHeader.h" BEGIN_JUCE_NAMESPACE #include "juce_ZipFile.h" #include "../streams/juce_GZIPDecompressorInputStream.h" #include "../streams/juce_BufferedInputStream.h" #include "../streams/juce_FileInputSource.h" #include "juce_FileInputStream.h" #include "juce_FileOutputStream.h" #include "../../threads/juce_ScopedLock.h" //============================================================================== class ZipFile::ZipEntryInfo { public: ZipFile::ZipEntry entry; int streamOffset; int compressedSize; bool compressed; }; //============================================================================== class ZipFile::ZipInputStream : public InputStream { public: //============================================================================== ZipInputStream (ZipFile& file_, ZipFile::ZipEntryInfo& zei) : file (file_), zipEntryInfo (zei), pos (0), headerSize (0), inputStream (0) { inputStream = file_.inputStream; if (file_.inputSource != 0) { inputStream = file.inputSource->createInputStream(); } else { #if JUCE_DEBUG file_.numOpenStreams++; #endif } char buffer [30]; if (inputStream != 0 && 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 != 0 && inputStream == file.inputStream) file.numOpenStreams--; #endif if (inputStream != file.inputStream) delete inputStream; } int64 getTotalLength() { return zipEntryInfo.compressedSize; } int read (void* buffer, int howMany) { if (headerSize <= 0) return 0; howMany = (int) jmin ((int64) howMany, zipEntryInfo.compressedSize - pos); if (inputStream == 0) return 0; int num; if (inputStream == file.inputStream) { const ScopedLock sl (file.lock); inputStream->setPosition (pos + zipEntryInfo.streamOffset + headerSize); num = inputStream->read (buffer, howMany); } else { inputStream->setPosition (pos + zipEntryInfo.streamOffset + headerSize); num = inputStream->read (buffer, howMany); } pos += num; return num; } bool isExhausted() { return headerSize <= 0 || pos >= zipEntryInfo.compressedSize; } int64 getPosition() { return pos; } bool setPosition (int64 newPos) { pos = jlimit ((int64) 0, (int64) zipEntryInfo.compressedSize, newPos); return true; } private: //============================================================================== ZipFile& file; ZipEntryInfo zipEntryInfo; int64 pos; int headerSize; InputStream* inputStream; ZipInputStream (const ZipInputStream&); ZipInputStream& operator= (const ZipInputStream&); }; //============================================================================== ZipFile::ZipFile (InputStream* const source_, const bool deleteStreamWhenDestroyed) : inputStream (source_) #if JUCE_DEBUG , numOpenStreams (0) #endif { if (deleteStreamWhenDestroyed) streamToDelete = inputStream; init(); } ZipFile::ZipFile (const File& file) : inputStream (0) #if JUCE_DEBUG , numOpenStreams (0) #endif { inputSource = new FileInputSource (file); init(); } ZipFile::ZipFile (InputSource* const inputSource_) : inputStream (0), inputSource (inputSource_) #if JUCE_DEBUG , numOpenStreams (0) #endif { init(); } ZipFile::~ZipFile() { #if JUCE_DEBUG entries.clear(); // 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 the file uses to read itself. jassert (numOpenStreams == 0); #endif } //============================================================================== int ZipFile::getNumEntries() const throw() { return entries.size(); } const ZipFile::ZipEntry* ZipFile::getEntry (const int index) const throw() { ZipEntryInfo* const zei = entries [index]; return zei != 0 ? &(zei->entry) : 0; } int ZipFile::getIndexOfFileName (const String& fileName) const throw() { 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 throw() { return getEntry (getIndexOfFileName (fileName)); } InputStream* ZipFile::createStreamForEntry (const int index) { ZipEntryInfo* const zei = entries[index]; InputStream* stream = 0; if (zei != 0) { 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; } class ZipFile::ZipFilenameComparator { public: int compareElements (const ZipFile::ZipEntryInfo* first, const ZipFile::ZipEntryInfo* second) { return first->entry.filename.compare (second->entry.filename); } }; void ZipFile::sortEntriesByFilename() { ZipFilenameComparator sorter; entries.sort (sorter); } //============================================================================== void ZipFile::init() { ScopedPointer toDelete; InputStream* in = inputStream; if (inputSource != 0) { in = inputSource->createInputStream(); toDelete = in; } if (in != 0) { 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 (headerData.getData()) + pos; const int fileNameLen = ByteOrder::littleEndianShort (buffer + 28); if (pos + 46 + fileNameLen > size) break; ZipEntryInfo* const zei = new ZipEntryInfo(); zei->entry.filename = String::fromUTF8 (buffer + 46, fileNameLen); const int time = ByteOrder::littleEndianShort (buffer + 12); const int date = ByteOrder::littleEndianShort (buffer + 14); 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; zei->entry.fileTime = Time (year, month, day, hours, minutes, seconds); zei->compressed = ByteOrder::littleEndianShort (buffer + 10) != 0; zei->compressedSize = ByteOrder::littleEndianInt (buffer + 20); zei->entry.uncompressedSize = ByteOrder::littleEndianInt (buffer + 24); zei->streamOffset = ByteOrder::littleEndianInt (buffer + 42); entries.add (zei); pos += 46 + fileNameLen + ByteOrder::littleEndianShort (buffer + 30) + ByteOrder::littleEndianShort (buffer + 32); } } } } } int ZipFile::findEndOfZipEntryTable (InputStream* input, int& numEntries) { BufferedInputStream in (input, 8192, false); in.setPosition (in.getTotalLength()); int64 pos = in.getPosition(); const int64 lowestPos = jmax ((int64) 0, pos - 1024); char buffer [32]; zeromem (buffer, sizeof (buffer)); 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 ByteOrder::littleEndianInt (buffer + 16); } } } return 0; } bool ZipFile::uncompressTo (const File& targetDirectory, const bool shouldOverwriteFiles) { for (int i = 0; i < entries.size(); ++i) if (! uncompressEntry (i, targetDirectory, shouldOverwriteFiles)) return false; return true; } bool ZipFile::uncompressEntry (const int index, const File& targetDirectory, bool shouldOverwriteFiles) { const ZipEntryInfo* zei = entries [index]; if (zei != 0) { const File targetFile (targetDirectory.getChildFile (zei->entry.filename)); if (zei->entry.filename.endsWithChar ('/')) { targetFile.createDirectory(); // (entry is a directory, not a file) } else { ScopedPointer in (createStreamForEntry (index)); if (in != 0) { if (shouldOverwriteFiles && ! targetFile.deleteFile()) return false; if ((! targetFile.exists()) && targetFile.getParentDirectory().createDirectory()) { ScopedPointer out (targetFile.createOutputStream()); if (out != 0) { out->writeFromInputStream (*in, -1); out = 0; targetFile.setCreationTime (zei->entry.fileTime); targetFile.setLastModificationTime (zei->entry.fileTime); targetFile.setLastAccessTime (zei->entry.fileTime); return true; } } } } } return false; } END_JUCE_NAMESPACE