Loading...
Searching...
No Matches
juce_ZipFile.cpp
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2022 - Raw Material Software Limited
JUCE is an open source library subject to commercial or open-source
licensing.
The code included in this file is provided under the terms of the ISC license
http://www.isc.org/downloads/software-support-policy/isc-license. 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.
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
DISCLAIMED.
==============================================================================
*/
namespace juce
{
inline uint16 readUnalignedLittleEndianShort (const void* buffer)
{
auto data = readUnaligned<uint16> (buffer);
return ByteOrder::littleEndianShort (&data);
}
inline uint32 readUnalignedLittleEndianInt (const void* buffer)
{
auto data = readUnaligned<uint32> (buffer);
return ByteOrder::littleEndianInt (&data);
}
struct ZipFile::ZipEntryHolder
{
ZipEntryHolder (const char* buffer, int fileNameLen)
{
isCompressed = readUnalignedLittleEndianShort (buffer + 10) != 0;
entry.fileTime = parseFileTime (readUnalignedLittleEndianShort (buffer + 12),
readUnalignedLittleEndianShort (buffer + 14));
compressedSize = (int64) readUnalignedLittleEndianInt (buffer + 20);
entry.uncompressedSize = (int64) readUnalignedLittleEndianInt (buffer + 24);
streamOffset = (int64) readUnalignedLittleEndianInt (buffer + 42);
entry.externalFileAttributes = readUnalignedLittleEndianInt (buffer + 38);
auto fileType = (entry.externalFileAttributes >> 28) & 0xf;
entry.isSymbolicLink = (fileType == 0xA);
entry.filename = String::fromUTF8 (buffer + 46, fileNameLen);
}
static Time parseFileTime (uint32 time, uint32 date) noexcept
{
auto year = (int) (1980 + (date >> 9));
auto month = (int) (((date >> 5) & 15) - 1);
auto day = (int) (date & 31);
auto hours = (int) time >> 11;
auto minutes = (int) ((time >> 5) & 63);
auto seconds = (int) ((time & 31) << 1);
return { year, month, day, hours, minutes, seconds };
}
ZipEntry entry;
int64 streamOffset, compressedSize;
bool isCompressed;
};
//==============================================================================
static int64 findCentralDirectoryFileHeader (InputStream& input, int& numEntries)
{
BufferedInputStream in (input, 8192);
in.setPosition (in.getTotalLength());
auto pos = in.getPosition();
auto lowestPos = jmax ((int64) 0, pos - 1048576);
char buffer[32] = {};
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 (readUnalignedLittleEndianInt (buffer + i) == 0x06054b50)
{
in.setPosition (pos + i);
in.read (buffer, 22);
numEntries = readUnalignedLittleEndianShort (buffer + 10);
auto offset = (int64) readUnalignedLittleEndianInt (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;
}
static bool hasSymbolicPart (const File& root, const File& f)
{
jassert (root == f || f.isAChildOf (root));
for (auto p = f; p != root; p = p.getParentDirectory())
{
if (p.isSymbolicLink())
return true;
}
return false;
}
//==============================================================================
struct ZipFile::ZipInputStream final : public InputStream
{
ZipInputStream (ZipFile& zf, const ZipFile::ZipEntryHolder& zei)
: file (zf),
zipEntryHolder (zei),
inputStream (zf.inputStream)
{
if (zf.inputSource != nullptr)
{
streamToDelete.reset (file.inputSource->createInputStream());
inputStream = streamToDelete.get();
}
else
{
#if JUCE_DEBUG
zf.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() override
{
#if JUCE_DEBUG
if (inputStream != nullptr && inputStream == file.inputStream)
file.streamCounter.numOpenStreams--;
#endif
}
int64 getTotalLength() override
{
return zipEntryHolder.compressedSize;
}
int read (void* buffer, int howMany) override
{
if (headerSize <= 0)
return 0;
howMany = (int) jmin ((int64) howMany, 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() override
{
return headerSize <= 0 || pos >= zipEntryHolder.compressedSize;
}
int64 getPosition() override
{
return pos;
}
bool setPosition (int64 newPos) override
{
pos = jlimit ((int64) 0, zipEntryHolder.compressedSize, newPos);
return true;
}
private:
ZipFile& file;
ZipEntryHolder zipEntryHolder;
int64 pos = 0;
int headerSize = 0;
InputStream* inputStream;
std::unique_ptr<InputStream> streamToDelete;
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ZipInputStream)
};
//==============================================================================
ZipFile::ZipFile (InputStream* stream, bool deleteStreamWhenDestroyed)
: inputStream (stream)
{
if (deleteStreamWhenDestroyed)
streamToDelete.reset (inputStream);
init();
}
ZipFile::ZipFile (InputStream& stream) : inputStream (&stream)
{
init();
}
ZipFile::ZipFile (const File& file) : inputSource (new FileInputSource (file))
{
init();
}
ZipFile::ZipFile (InputSource* source) : 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 (auto* zei = entries[index])
return &(zei->entry);
return nullptr;
}
int ZipFile::getIndexOfFileName (const String& fileName, bool ignoreCase) const noexcept
{
for (int i = 0; i < entries.size(); ++i)
{
auto& entryFilename = entries.getUnchecked (i)->entry.filename;
if (ignoreCase ? entryFilename.equalsIgnoreCase (fileName)
: entryFilename == fileName)
return i;
}
return -1;
}
const ZipFile::ZipEntry* ZipFile::getEntry (const String& fileName, bool ignoreCase) const noexcept
{
return getEntry (getIndexOfFileName (fileName, ignoreCase));
}
InputStream* ZipFile::createStreamForEntry (const int index)
{
InputStream* stream = nullptr;
if (auto* zei = entries[index])
{
stream = new ZipInputStream (*this, *zei);
if (zei->isCompressed)
{
stream = new GZIPDecompressorInputStream (stream, true,
GZIPDecompressorInputStream::deflateFormat,
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()
{
std::sort (entries.begin(), entries.end(),
[] (const ZipEntryHolder* e1, const ZipEntryHolder* e2) { return e1->entry.filename < e2->entry.filename; });
}
//==============================================================================
void ZipFile::init()
{
std::unique_ptr<InputStream> toDelete;
InputStream* in = inputStream;
if (inputSource != nullptr)
{
in = inputSource->createInputStream();
toDelete.reset (in);
}
if (in != nullptr)
{
int numEntries = 0;
auto centralDirectoryPos = findCentralDirectoryFileHeader (*in, numEntries);
if (centralDirectoryPos >= 0 && centralDirectoryPos < in->getTotalLength())
{
auto size = (size_t) (in->getTotalLength() - centralDirectoryPos);
in->setPosition (centralDirectoryPos);
MemoryBlock headerData;
if (in->readIntoMemoryBlock (headerData, (ssize_t) size) == size)
{
size_t pos = 0;
for (int i = 0; i < numEntries; ++i)
{
if (pos + 46 > size)
break;
auto* buffer = static_cast<const char*> (headerData.getData()) + pos;
auto fileNameLen = readUnalignedLittleEndianShort (buffer + 28u);
if (pos + 46 + fileNameLen > size)
break;
entries.add (new ZipEntryHolder (buffer, fileNameLen));
pos += 46u + fileNameLen
+ readUnalignedLittleEndianShort (buffer + 30u)
+ readUnalignedLittleEndianShort (buffer + 32u);
}
}
}
}
}
Result ZipFile::uncompressTo (const File& targetDirectory,
const bool shouldOverwriteFiles)
{
for (int i = 0; i < entries.size(); ++i)
{
auto result = uncompressEntry (i, targetDirectory, shouldOverwriteFiles);
if (result.failed())
return result;
}
return Result::ok();
}
Result ZipFile::uncompressEntry (int index, const File& targetDirectory, bool shouldOverwriteFiles)
{
return uncompressEntry (index,
targetDirectory,
shouldOverwriteFiles ? OverwriteFiles::yes : OverwriteFiles::no,
FollowSymlinks::no);
}
Result ZipFile::uncompressEntry (int index, const File& targetDirectory, OverwriteFiles overwriteFiles, FollowSymlinks followSymlinks)
{
auto* zei = entries.getUnchecked (index);
#if JUCE_WINDOWS
auto entryPath = zei->entry.filename;
#else
auto entryPath = zei->entry.filename.replaceCharacter ('\\', '/');
#endif
if (entryPath.isEmpty())
return Result::ok();
auto targetFile = targetDirectory.getChildFile (entryPath);
if (! targetFile.isAChildOf (targetDirectory))
return Result::fail ("Entry " + entryPath + " is outside the target directory");
if (entryPath.endsWithChar ('/') || entryPath.endsWithChar ('\\'))
return targetFile.createDirectory(); // (entry is a directory, not a file)
std::unique_ptr<InputStream> in (createStreamForEntry (index));
if (in == nullptr)
return Result::fail ("Failed to open the zip file for reading");
if (targetFile.exists())
{
if (overwriteFiles == OverwriteFiles::no)
return Result::ok();
if (! targetFile.deleteFile())
return Result::fail ("Failed to write to target file: " + targetFile.getFullPathName());
}
if (followSymlinks == FollowSymlinks::no && hasSymbolicPart (targetDirectory, targetFile.getParentDirectory()))
return Result::fail ("Parent directory leads through symlink for target file: " + targetFile.getFullPathName());
if (! targetFile.getParentDirectory().createDirectory())
return Result::fail ("Failed to create target folder: " + targetFile.getParentDirectory().getFullPathName());
if (zei->entry.isSymbolicLink)
{
String originalFilePath (in->readEntireStreamAsString()
.replaceCharacter (L'/', File::getSeparatorChar()));
if (! File::createSymbolicLink (targetFile, originalFilePath, true))
return Result::fail ("Failed to create symbolic link: " + originalFilePath);
}
else
{
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();
}
//==============================================================================
struct ZipFile::Builder::Item
{
Item (const File& f, InputStream* s, int compression, const String& storedPath, Time time)
: file (f), stream (s), storedPathname (storedPath), fileTime (time), compressionLevel (compression)
{
symbolicLink = (file.exists() && file.isSymbolicLink());
}
bool writeData (OutputStream& target, const int64 overallStartPosition)
{
MemoryOutputStream compressedData ((size_t) file.getSize());
if (symbolicLink)
{
auto relativePath = file.getNativeLinkedTarget().replaceCharacter (File::getSeparatorChar(), L'/');
uncompressedSize = relativePath.length();
checksum = zlibNamespace::crc32 (0, (uint8_t*) relativePath.toRawUTF8(), (unsigned int) uncompressedSize);
compressedData << relativePath;
}
else if (compressionLevel > 0)
{
GZIPCompressorOutputStream compressor (compressedData, compressionLevel,
GZIPCompressorOutputStream::windowBitsRaw);
if (! writeSource (compressor))
return false;
}
else
{
if (! writeSource (compressedData))
return false;
}
compressedSize = (int64) compressedData.getDataSize();
headerStart = target.getPosition() - overallStartPosition;
target.writeInt (0x04034b50);
writeFlagsAndSizes (target);
target << storedPathname
<< compressedData;
return true;
}
bool writeDirectoryEntry (OutputStream& target)
{
target.writeInt (0x02014b50);
target.writeShort (symbolicLink ? 0x0314 : 0x0014);
writeFlagsAndSizes (target);
target.writeShort (0); // comment length
target.writeShort (0); // start disk num
target.writeShort (0); // internal attributes
target.writeInt ((int) (symbolicLink ? 0xA1ED0000 : 0)); // external attributes
target.writeInt ((int) (uint32) headerStart);
target << storedPathname;
return true;
}
private:
const File file;
std::unique_ptr<InputStream> stream;
String storedPathname;
Time fileTime;
int64 compressedSize = 0, uncompressedSize = 0, headerStart = 0;
int compressionLevel = 0;
unsigned long checksum = 0;
bool symbolicLink = false;
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())
{
auto 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.reset();
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 ((! symbolicLink && compressionLevel > 0) ? (short) 8 : (short) 0); //symlink target path is not compressed
writeTimeAndDate (target, fileTime);
target.writeInt ((int) checksum);
target.writeInt ((int) (uint32) compressedSize);
target.writeInt ((int) (uint32) uncompressedSize);
target.writeShort (static_cast<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, 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 ({}, stream, compression, path, time));
}
bool ZipFile::Builder::writeToStream (OutputStream& target, double* const progress) const
{
auto 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;
}
auto directoryStart = target.getPosition();
for (auto* item : items)
if (! item->writeDirectoryEntry (target))
return false;
auto 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;
}
//==============================================================================
//==============================================================================
#if JUCE_UNIT_TESTS
struct ZIPTests final : public UnitTest
{
ZIPTests()
: UnitTest ("ZIP", UnitTestCategories::compression)
{}
static MemoryBlock createZipMemoryBlock (const StringArray& entryNames)
{
ZipFile::Builder builder;
HashMap<String, MemoryBlock> blocks;
for (auto& entryName : entryNames)
{
auto& block = blocks.getReference (entryName);
MemoryOutputStream mo (block, false);
mo << entryName;
mo.flush();
builder.addEntry (new MemoryInputStream (block, false), 9, entryName, Time::getCurrentTime());
}
MemoryBlock data;
MemoryOutputStream mo (data, false);
builder.writeToStream (mo, nullptr);
return data;
}
void runZipSlipTest()
{
const std::map<String, bool> testCases = { { "a", true },
#if JUCE_WINDOWS
{ "C:/b", false },
#else
{ "/b", false },
#endif
{ "c/d", true },
{ "../e/f", false },
{ "../../g/h", false },
{ "i/../j", true },
{ "k/l/../", true },
{ "m/n/../../", false },
{ "o/p/../../../", false } };
StringArray entryNames;
for (const auto& testCase : testCases)
entryNames.add (testCase.first);
TemporaryFile tmpDir;
tmpDir.getFile().createDirectory();
auto data = createZipMemoryBlock (entryNames);
MemoryInputStream mi (data, false);
ZipFile zip (mi);
for (int i = 0; i < zip.getNumEntries(); ++i)
{
const auto result = zip.uncompressEntry (i, tmpDir.getFile());
const auto caseIt = testCases.find (zip.getEntry (i)->filename);
if (caseIt != testCases.end())
{
expect (result.wasOk() == caseIt->second,
zip.getEntry (i)->filename + " was unexpectedly " + (result.wasOk() ? "OK" : "not OK"));
}
else
{
expect (false);
}
}
}
void runTest() override
{
beginTest ("ZIP");
StringArray entryNames { "first", "second", "third" };
auto data = createZipMemoryBlock (entryNames);
MemoryInputStream mi (data, false);
ZipFile zip (mi);
expectEquals (zip.getNumEntries(), entryNames.size());
for (auto& entryName : entryNames)
{
auto* entry = zip.getEntry (entryName);
std::unique_ptr<InputStream> input (zip.createStreamForEntry (*entry));
expectEquals (input->readEntireStreamAsString(), entryName);
}
beginTest ("ZipSlip");
runZipSlipTest();
}
};
static ZIPTests zipTests;
#endif
} // namespace juce
juce::BufferedInputStream
Definition juce_BufferedInputStream.h:37
juce::ByteOrder::littleEndianInt
static constexpr uint32 littleEndianInt(const void *bytes) noexcept
Definition juce_ByteOrder.h:199
juce::ByteOrder::littleEndianShort
static constexpr uint16 littleEndianShort(const void *bytes) noexcept
Definition juce_ByteOrder.h:198
juce::FileInputSource
Definition juce_FileInputSource.h:35
juce::FileOutputStream
Definition juce_FileOutputStream.h:35
juce::FileOutputStream::failedToOpen
bool failedToOpen() const noexcept
Definition juce_FileOutputStream.h:86
juce::File
Definition juce_File.h:45
juce::File::isSymbolicLink
bool isSymbolicLink() const
juce::File::getLastModificationTime
Time getLastModificationTime() const
Definition juce_File.cpp:538
juce::File::getSize
int64 getSize() const
juce::File::getFileName
String getFileName() const
Definition juce_File.cpp:372
juce::File::getChildFile
File getChildFile(StringRef relativeOrAbsolutePath) const
Definition juce_File.cpp:420
juce::File::createSymbolicLink
bool createSymbolicLink(const File &linkFileToCreate, bool overwriteExisting) const
Definition juce_File.cpp:1002
juce::File::getSeparatorChar
static juce_wchar getSeparatorChar()
juce::File::getParentDirectory
File getParentDirectory() const
Definition juce_File.cpp:358
juce::File::getNativeLinkedTarget
String getNativeLinkedTarget() const
juce::File::exists
bool exists() const
juce::GZIPDecompressorInputStream
Definition juce_GZIPDecompressorInputStream.h:39
juce::HashMap
Definition juce_HashMap.h:104
juce::HashMap::getReference
ValueType & getReference(KeyTypeParameter keyToLookFor)
Definition juce_HashMap.h:185
juce::InputSource
Definition juce_InputSource.h:38
juce::InputStream
Definition juce_InputStream.h:37
juce::InputStream::getPosition
virtual int64 getPosition()=0
juce::InputStream::setPosition
virtual bool setPosition(int64 newPosition)=0
juce::InputStream::isExhausted
virtual bool isExhausted()=0
juce::InputStream::getTotalLength
virtual int64 getTotalLength()=0
juce::InputStream::readIntoMemoryBlock
virtual size_t readIntoMemoryBlock(MemoryBlock &destBlock, ssize_t maxNumBytesToRead=-1)
Definition juce_InputStream.cpp:223
juce::MemoryBlock
Definition juce_MemoryBlock.h:33
juce::MemoryInputStream
Definition juce_MemoryInputStream.h:36
juce::MemoryOutputStream
Definition juce_MemoryOutputStream.h:36
juce::OutputStream
Definition juce_OutputStream.h:38
juce::OutputStream::getPosition
virtual int64 getPosition()=0
juce::OutputStream::writeShort
virtual bool writeShort(short value)
Definition juce_OutputStream.cpp:97
juce::OutputStream::writeInt
virtual bool writeInt(int value)
Definition juce_OutputStream.cpp:109
juce::OwnedArray::add
ObjectClass * add(ObjectClass *newObject)
Definition juce_OwnedArray.h:293
juce::Result
Definition juce_Result.h:57
juce::Result::fail
static Result fail(const String &errorMessage) noexcept
Definition juce_Result.cpp:65
juce::Result::ok
static Result ok() noexcept
Definition juce_Result.h:61
juce::StringArray
Definition juce_StringArray.h:35
juce::String
Definition juce_String.h:53
juce::String::isEmpty
bool isEmpty() const noexcept
Definition juce_String.h:310
juce::String::replaceCharacter
String replaceCharacter(juce_wchar characterToReplace, juce_wchar characterToInsertInstead) const
Definition juce_String.cpp:1344
juce::String::fromUTF8
static String fromUTF8(const char *utf8buffer, int bufferSizeBytes=-1)
Definition juce_String.cpp:2125
juce::String::isNotEmpty
bool isNotEmpty() const noexcept
Definition juce_String.h:316
juce::Time
Definition juce_Time.h:37
juce::Time::getCurrentTime
static Time JUCE_CALLTYPE getCurrentTime() noexcept
Definition juce_Time.cpp:233
juce::UnitTest
Definition juce_UnitTest.h:70
juce::ZipFile::Builder
Definition juce_ZipFile.h:209
juce::ZipFile::Builder::Builder
Builder()
Definition juce_ZipFile.cpp:615
juce::ZipFile::Builder::addEntry
void addEntry(InputStream *streamToRead, int compressionLevel, const String &storedPathName, Time fileModificationTime)
Definition juce_ZipFile.cpp:625
juce::ZipFile::Builder::writeToStream
bool writeToStream(OutputStream &target, double *progress) const
Definition juce_ZipFile.cpp:632
juce::ZipFile::Builder::addFile
void addFile(const File &fileToAdd, int compressionLevel, const String &storedPathName=String())
Definition juce_ZipFile.cpp:618
juce::ZipFile::Builder::~Builder
~Builder()
Definition juce_ZipFile.cpp:616
juce::ZipFile::uncompressTo
Result uncompressTo(const File &targetDirectory, bool shouldOverwriteFiles=true)
Definition juce_ZipFile.cpp:401
juce::ZipFile::createStreamForEntry
InputStream * createStreamForEntry(int index)
Definition juce_ZipFile.cpp:314
juce::ZipFile::getEntry
const ZipEntry * getEntry(int index) const noexcept
Definition juce_ZipFile.cpp:287
juce::ZipFile::~ZipFile
~ZipFile()
Definition juce_ZipFile.cpp:264
juce::ZipFile::getNumEntries
int getNumEntries() const noexcept
Definition juce_ZipFile.cpp:282
juce::ZipFile::ZipFile
ZipFile(const File &file)
Definition juce_ZipFile.cpp:254
juce::ZipFile::uncompressEntry
Result uncompressEntry(int index, const File &targetDirectory, bool shouldOverwriteFiles=true)
Definition juce_ZipFile.cpp:415
juce::ZipFile::getIndexOfFileName
int getIndexOfFileName(const String &fileName, bool ignoreCase=false) const noexcept
Definition juce_ZipFile.cpp:295
juce::ZipFile::sortEntriesByFilename
void sortEntriesByFilename()
Definition juce_ZipFile.cpp:345
juce::ZipFile::ZipEntry
Definition juce_ZipFile.h:73