|
|
@@ -23,17 +23,16 @@ |
|
|
|
namespace juce
|
|
|
|
{
|
|
|
|
|
|
|
|
class ZipFile::ZipEntryHolder
|
|
|
|
struct ZipFile::ZipEntryHolder
|
|
|
|
{
|
|
|
|
public:
|
|
|
|
ZipEntryHolder (const char* const buffer, const int fileNameLen)
|
|
|
|
ZipEntryHolder (const char* buffer, int fileNameLen)
|
|
|
|
{
|
|
|
|
isCompressed = ByteOrder::littleEndianShort (buffer + 10) != 0;
|
|
|
|
entry.fileTime = parseFileTime ((uint32) ByteOrder::littleEndianShort (buffer + 12),
|
|
|
|
(uint32) ByteOrder::littleEndianShort (buffer + 14));
|
|
|
|
compressedSize = (int64) (uint32) ByteOrder::littleEndianInt (buffer + 20);
|
|
|
|
entry.uncompressedSize = (int64) (uint32) ByteOrder::littleEndianInt (buffer + 24);
|
|
|
|
streamOffset = (int64) (uint32) ByteOrder::littleEndianInt (buffer + 42);
|
|
|
|
entry.fileTime = parseFileTime (ByteOrder::littleEndianShort (buffer + 12),
|
|
|
|
ByteOrder::littleEndianShort (buffer + 14));
|
|
|
|
compressedSize = (int64) ByteOrder::littleEndianInt (buffer + 20);
|
|
|
|
entry.uncompressedSize = (int64) ByteOrder::littleEndianInt (buffer + 24);
|
|
|
|
streamOffset = (int64) ByteOrder::littleEndianInt (buffer + 42);
|
|
|
|
entry.filename = String::fromUTF8 (buffer + 46, fileNameLen);
|
|
|
|
}
|
|
|
|
|
|
|
@@ -45,72 +44,81 @@ public: |
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
ZipEntry entry;
|
|
|
|
int64 streamOffset, compressedSize;
|
|
|
|
bool isCompressed;
|
|
|
|
|
|
|
|
private:
|
|
|
|
static Time parseFileTime (uint32 time, uint32 date) noexcept
|
|
|
|
{
|
|
|
|
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 = (int) ((time & 31) << 1);
|
|
|
|
|
|
|
|
return Time (year, month, day, hours, minutes, seconds);
|
|
|
|
int year = 1980 + (date >> 9);
|
|
|
|
int month = ((date >> 5) & 15) - 1;
|
|
|
|
int day = date & 31;
|
|
|
|
int hours = time >> 11;
|
|
|
|
int minutes = (time >> 5) & 63;
|
|
|
|
int seconds = (int) ((time & 31) << 1);
|
|
|
|
|
|
|
|
return { year, month, day, hours, minutes, seconds };
|
|
|
|
}
|
|
|
|
|
|
|
|
ZipEntry entry;
|
|
|
|
int64 streamOffset, compressedSize;
|
|
|
|
bool isCompressed;
|
|
|
|
};
|
|
|
|
|
|
|
|
//==============================================================================
|
|
|
|
namespace
|
|
|
|
static int64 findCentralDirectoryFileHeader (InputStream& input, int& numEntries)
|
|
|
|
{
|
|
|
|
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);
|
|
|
|
BufferedInputStream in (input, 8192);
|
|
|
|
|
|
|
|
char buffer [32] = { 0 };
|
|
|
|
in.setPosition (in.getTotalLength());
|
|
|
|
auto pos = in.getPosition();
|
|
|
|
auto lowestPos = jmax ((int64) 0, pos - 1024);
|
|
|
|
char buffer[32] = {};
|
|
|
|
|
|
|
|
while (pos > lowestPos)
|
|
|
|
{
|
|
|
|
in.setPosition (pos - 22);
|
|
|
|
pos = in.getPosition();
|
|
|
|
memcpy (buffer + 22, buffer, 4);
|
|
|
|
while (pos > lowestPos)
|
|
|
|
{
|
|
|
|
in.setPosition (pos - 22);
|
|
|
|
pos = in.getPosition();
|
|
|
|
memcpy (buffer + 22, buffer, 4);
|
|
|
|
|
|
|
|
if (in.read (buffer, 22) != 22)
|
|
|
|
return 0;
|
|
|
|
if (in.read (buffer, 22) != 22)
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
for (int i = 0; i < 22; ++i)
|
|
|
|
for (int i = 0; i < 22; ++i)
|
|
|
|
{
|
|
|
|
if (ByteOrder::littleEndianInt (buffer + i) == 0x06054b50)
|
|
|
|
{
|
|
|
|
if (ByteOrder::littleEndianInt (buffer + i) == 0x06054b50)
|
|
|
|
{
|
|
|
|
in.setPosition (pos + i);
|
|
|
|
in.read (buffer, 22);
|
|
|
|
numEntries = ByteOrder::littleEndianShort (buffer + 10);
|
|
|
|
in.setPosition (pos + i);
|
|
|
|
in.read (buffer, 22);
|
|
|
|
numEntries = ByteOrder::littleEndianShort (buffer + 10);
|
|
|
|
auto offset = (int64) ByteOrder::littleEndianInt (buffer + 16);
|
|
|
|
|
|
|
|
return (int) ByteOrder::littleEndianInt (buffer + 16);
|
|
|
|
if (offset >= 4)
|
|
|
|
{
|
|
|
|
in.setPosition (offset);
|
|
|
|
|
|
|
|
// This is a workaround for some zip files which seem to contain the
|
|
|
|
// wrong offset for the central directory - instead of including the
|
|
|
|
// header, they point to the byte immediately after it.
|
|
|
|
if (in.readInt() != 0x02014b50)
|
|
|
|
{
|
|
|
|
in.setPosition (offset - 4);
|
|
|
|
|
|
|
|
if (in.readInt() == 0x02014b50)
|
|
|
|
offset -= 4;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return offset;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
//==============================================================================
|
|
|
|
class ZipFile::ZipInputStream : public InputStream
|
|
|
|
struct ZipFile::ZipInputStream : public InputStream
|
|
|
|
{
|
|
|
|
public:
|
|
|
|
ZipInputStream (ZipFile& zf, ZipFile::ZipEntryHolder& zei)
|
|
|
|
ZipInputStream (ZipFile& zf, const ZipFile::ZipEntryHolder& zei)
|
|
|
|
: file (zf),
|
|
|
|
zipEntryHolder (zei),
|
|
|
|
pos (0),
|
|
|
|
headerSize (0),
|
|
|
|
inputStream (zf.inputStream)
|
|
|
|
{
|
|
|
|
if (zf.inputSource != nullptr)
|
|
|
@@ -124,7 +132,7 @@ public: |
|
|
|
#endif
|
|
|
|
}
|
|
|
|
|
|
|
|
char buffer [30];
|
|
|
|
char buffer[30];
|
|
|
|
|
|
|
|
if (inputStream != nullptr
|
|
|
|
&& inputStream->setPosition (zei.streamOffset)
|
|
|
@@ -196,8 +204,8 @@ public: |
|
|
|
private:
|
|
|
|
ZipFile& file;
|
|
|
|
ZipEntryHolder zipEntryHolder;
|
|
|
|
int64 pos;
|
|
|
|
int headerSize;
|
|
|
|
int64 pos = 0;
|
|
|
|
int headerSize = 0;
|
|
|
|
InputStream* inputStream;
|
|
|
|
ScopedPointer<InputStream> streamToDelete;
|
|
|
|
|
|
|
@@ -206,7 +214,7 @@ private: |
|
|
|
|
|
|
|
|
|
|
|
//==============================================================================
|
|
|
|
ZipFile::ZipFile (InputStream* const stream, const bool deleteStreamWhenDestroyed)
|
|
|
|
ZipFile::ZipFile (InputStream* stream, bool deleteStreamWhenDestroyed)
|
|
|
|
: inputStream (stream)
|
|
|
|
{
|
|
|
|
if (deleteStreamWhenDestroyed)
|
|
|
@@ -215,22 +223,17 @@ ZipFile::ZipFile (InputStream* const stream, const bool deleteStreamWhenDestroye |
|
|
|
init();
|
|
|
|
}
|
|
|
|
|
|
|
|
ZipFile::ZipFile (InputStream& stream)
|
|
|
|
: inputStream (&stream)
|
|
|
|
ZipFile::ZipFile (InputStream& stream) : inputStream (&stream)
|
|
|
|
{
|
|
|
|
init();
|
|
|
|
}
|
|
|
|
|
|
|
|
ZipFile::ZipFile (const File& file)
|
|
|
|
: inputStream (nullptr),
|
|
|
|
inputSource (new FileInputSource (file))
|
|
|
|
ZipFile::ZipFile (const File& file) : inputSource (new FileInputSource (file))
|
|
|
|
{
|
|
|
|
init();
|
|
|
|
}
|
|
|
|
|
|
|
|
ZipFile::ZipFile (InputSource* const source)
|
|
|
|
: inputStream (nullptr),
|
|
|
|
inputSource (source)
|
|
|
|
ZipFile::ZipFile (InputSource* source) : inputSource (source)
|
|
|
|
{
|
|
|
|
init();
|
|
|
|
}
|
|
|
@@ -260,7 +263,7 @@ int ZipFile::getNumEntries() const noexcept |
|
|
|
|
|
|
|
const ZipFile::ZipEntry* ZipFile::getEntry (const int index) const noexcept
|
|
|
|
{
|
|
|
|
if (ZipEntryHolder* const zei = entries [index])
|
|
|
|
if (auto* zei = entries[index])
|
|
|
|
return &(zei->entry);
|
|
|
|
|
|
|
|
return nullptr;
|
|
|
@@ -289,7 +292,7 @@ InputStream* ZipFile::createStreamForEntry (const int index) |
|
|
|
{
|
|
|
|
InputStream* stream = nullptr;
|
|
|
|
|
|
|
|
if (ZipEntryHolder* const zei = entries[index])
|
|
|
|
if (auto* zei = entries[index])
|
|
|
|
{
|
|
|
|
stream = new ZipInputStream (*this, *zei);
|
|
|
|
|
|
|
@@ -337,27 +340,26 @@ void ZipFile::init() |
|
|
|
if (in != nullptr)
|
|
|
|
{
|
|
|
|
int numEntries = 0;
|
|
|
|
int pos = findEndOfZipEntryTable (*in, numEntries);
|
|
|
|
auto centralDirectoryPos = findCentralDirectoryFileHeader (*in, numEntries);
|
|
|
|
|
|
|
|
if (pos >= 0 && pos < in->getTotalLength())
|
|
|
|
if (centralDirectoryPos >= 0 && centralDirectoryPos < in->getTotalLength())
|
|
|
|
{
|
|
|
|
const int size = (int) (in->getTotalLength() - pos);
|
|
|
|
auto size = (size_t) (in->getTotalLength() - centralDirectoryPos);
|
|
|
|
|
|
|
|
in->setPosition (pos);
|
|
|
|
in->setPosition (centralDirectoryPos);
|
|
|
|
MemoryBlock headerData;
|
|
|
|
|
|
|
|
if (in->readIntoMemoryBlock (headerData, size) == (size_t) size)
|
|
|
|
if (in->readIntoMemoryBlock (headerData, (ssize_t) size) == size)
|
|
|
|
{
|
|
|
|
pos = 0;
|
|
|
|
size_t 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);
|
|
|
|
auto* buffer = static_cast<const char*> (headerData.getData()) + pos;
|
|
|
|
auto fileNameLen = ByteOrder::littleEndianShort (buffer + 28);
|
|
|
|
|
|
|
|
if (pos + 46 + fileNameLen > size)
|
|
|
|
break;
|
|
|
@@ -378,7 +380,8 @@ Result ZipFile::uncompressTo (const File& targetDirectory, |
|
|
|
{
|
|
|
|
for (int i = 0; i < entries.size(); ++i)
|
|
|
|
{
|
|
|
|
Result result (uncompressEntry (i, targetDirectory, shouldOverwriteFiles));
|
|
|
|
auto result = uncompressEntry (i, targetDirectory, shouldOverwriteFiles);
|
|
|
|
|
|
|
|
if (result.failed())
|
|
|
|
return result;
|
|
|
|
}
|
|
|
@@ -386,19 +389,20 @@ Result ZipFile::uncompressTo (const File& targetDirectory, |
|
|
|
return Result::ok();
|
|
|
|
}
|
|
|
|
|
|
|
|
Result ZipFile::uncompressEntry (const int index,
|
|
|
|
const File& targetDirectory,
|
|
|
|
bool shouldOverwriteFiles)
|
|
|
|
Result ZipFile::uncompressEntry (int index, const File& targetDirectory, bool shouldOverwriteFiles)
|
|
|
|
{
|
|
|
|
const ZipEntryHolder* zei = entries.getUnchecked (index);
|
|
|
|
auto* zei = entries.getUnchecked (index);
|
|
|
|
|
|
|
|
#if JUCE_WINDOWS
|
|
|
|
const String entryPath (zei->entry.filename);
|
|
|
|
auto entryPath = zei->entry.filename;
|
|
|
|
#else
|
|
|
|
const String entryPath (zei->entry.filename.replaceCharacter ('\\', '/'));
|
|
|
|
auto entryPath = zei->entry.filename.replaceCharacter ('\\', '/');
|
|
|
|
#endif
|
|
|
|
|
|
|
|
const File targetFile (targetDirectory.getChildFile (entryPath));
|
|
|
|
if (entryPath.isEmpty())
|
|
|
|
return Result::ok();
|
|
|
|
|
|
|
|
auto targetFile = targetDirectory.getChildFile (entryPath);
|
|
|
|
|
|
|
|
if (entryPath.endsWithChar ('/') || entryPath.endsWithChar ('\\'))
|
|
|
|
return targetFile.createDirectory(); // (entry is a directory, not a file)
|
|
|
@@ -438,13 +442,10 @@ Result ZipFile::uncompressEntry (const int index, |
|
|
|
|
|
|
|
|
|
|
|
//==============================================================================
|
|
|
|
class ZipFile::Builder::Item
|
|
|
|
struct ZipFile::Builder::Item
|
|
|
|
{
|
|
|
|
public:
|
|
|
|
Item (const File& f, InputStream* s, int compression, const String& storedPath, Time time)
|
|
|
|
: file (f), stream (s), storedPathname (storedPath), fileTime (time),
|
|
|
|
compressedSize (0), uncompressedSize (0), headerStart (0),
|
|
|
|
compressionLevel (compression), checksum (0)
|
|
|
|
: file (f), stream (s), storedPathname (storedPath), fileTime (time), compressionLevel (compression)
|
|
|
|
{
|
|
|
|
}
|
|
|
|
|
|
|
@@ -496,9 +497,9 @@ private: |
|
|
|
ScopedPointer<InputStream> stream;
|
|
|
|
String storedPathname;
|
|
|
|
Time fileTime;
|
|
|
|
int64 compressedSize, uncompressedSize, headerStart;
|
|
|
|
int compressionLevel;
|
|
|
|
unsigned long checksum;
|
|
|
|
int64 compressedSize = 0, uncompressedSize = 0, headerStart = 0;
|
|
|
|
int compressionLevel = 0;
|
|
|
|
unsigned long checksum = 0;
|
|
|
|
|
|
|
|
static void writeTimeAndDate (OutputStream& target, Time t)
|
|
|
|
{
|
|
|
@@ -523,7 +524,7 @@ private: |
|
|
|
|
|
|
|
while (! stream->isExhausted())
|
|
|
|
{
|
|
|
|
const int bytesRead = stream->read (buffer, bufferSize);
|
|
|
|
auto bytesRead = stream->read (buffer, bufferSize);
|
|
|
|
|
|
|
|
if (bytesRead < 0)
|
|
|
|
return false;
|
|
|
@@ -557,7 +558,7 @@ private: |
|
|
|
ZipFile::Builder::Builder() {}
|
|
|
|
ZipFile::Builder::~Builder() {}
|
|
|
|
|
|
|
|
void ZipFile::Builder::addFile (const File& file, const int compression, const String& path)
|
|
|
|
void ZipFile::Builder::addFile (const File& file, int compression, const String& path)
|
|
|
|
{
|
|
|
|
items.add (new Item (file, nullptr, compression,
|
|
|
|
path.isEmpty() ? file.getFileName() : path,
|
|
|
@@ -568,12 +569,12 @@ void ZipFile::Builder::addEntry (InputStream* stream, int compression, const Str |
|
|
|
{
|
|
|
|
jassert (stream != nullptr); // must not be null!
|
|
|
|
jassert (path.isNotEmpty());
|
|
|
|
items.add (new Item (File(), stream, compression, path, time));
|
|
|
|
items.add (new Item ({}, stream, compression, path, time));
|
|
|
|
}
|
|
|
|
|
|
|
|
bool ZipFile::Builder::writeToStream (OutputStream& target, double* const progress) const
|
|
|
|
{
|
|
|
|
const int64 fileStart = target.getPosition();
|
|
|
|
auto fileStart = target.getPosition();
|
|
|
|
|
|
|
|
for (int i = 0; i < items.size(); ++i)
|
|
|
|
{
|
|
|
@@ -584,13 +585,13 @@ bool ZipFile::Builder::writeToStream (OutputStream& target, double* const progre |
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
const int64 directoryStart = target.getPosition();
|
|
|
|
auto directoryStart = target.getPosition();
|
|
|
|
|
|
|
|
for (int i = 0; i < items.size(); ++i)
|
|
|
|
if (! items.getUnchecked (i)->writeDirectoryEntry (target))
|
|
|
|
for (auto* item : items)
|
|
|
|
if (! item->writeDirectoryEntry (target))
|
|
|
|
return false;
|
|
|
|
|
|
|
|
const int64 directoryEnd = target.getPosition();
|
|
|
|
auto directoryEnd = target.getPosition();
|
|
|
|
|
|
|
|
target.writeInt (0x06054b50);
|
|
|
|
target.writeShort (0);
|
|
|
|