1
0
Fork 0
mirror of https://github.com/juce-framework/JUCE.git synced 2026-01-11 23:54:18 +00:00
JUCE/src/io/files/juce_ZipFile.cpp

584 lines
17 KiB
C++

/*
==============================================================================
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.
==============================================================================
*/
#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 "../streams/juce_MemoryOutputStream.h"
#include "../streams/juce_GZIPCompressorOutputStream.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 = streamToDelete = 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
}
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;
ScopedPointer<InputStream> streamToDelete;
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (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 <InputStream> 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 <const char*> (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);
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 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 ('/'))
{
return targetFile.createDirectory(); // (entry is a directory, not a file)
}
else
{
ScopedPointer<InputStream> in (createStreamForEntry (index));
if (in != 0)
{
if (shouldOverwriteFiles && ! targetFile.deleteFile())
return false;
if ((! targetFile.exists()) && targetFile.getParentDirectory().createDirectory())
{
ScopedPointer<FileOutputStream> 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;
}
//=============================================================================
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 = 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;
uint32 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;
ScopedPointer<FileInputStream> input (file.createInputStream());
if (input == 0)
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, 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 (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) const
{
const int64 fileStart = target.getPosition();
int i;
for (i = 0; i < items.size(); ++i)
if (! items.getUnchecked (i)->writeData (target, fileStart))
return false;
const int64 directoryStart = target.getPosition();
for (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);
target.flush();
return true;
}
END_JUCE_NAMESPACE