26inline uint16 readUnalignedLittleEndianShort (
const void* buffer)
32inline uint32 readUnalignedLittleEndianInt (
const void* buffer)
42 isCompressed = readUnalignedLittleEndianShort (buffer + 10) != 0;
43 entry.
fileTime = parseFileTime (readUnalignedLittleEndianShort (buffer + 12),
44 readUnalignedLittleEndianShort (buffer + 14));
45 compressedSize = (
int64) readUnalignedLittleEndianInt (buffer + 20);
47 streamOffset = (
int64) readUnalignedLittleEndianInt (buffer + 42);
58 auto year = (
int) (1980 + (date >> 9));
59 auto month = (
int) (((date >> 5) & 15) - 1);
60 auto day = (
int) (date & 31);
62 auto minutes = (
int) ((
time >> 5) & 63);
63 auto seconds = (
int) ((
time & 31) << 1);
65 return { year, month, day, hours, minutes, seconds };
69 int64 streamOffset, compressedSize;
74static int64 findCentralDirectoryFileHeader (
InputStream& input,
int& numEntries)
78 in.setPosition (in.getTotalLength());
79 auto pos = in.getPosition();
85 in.setPosition (pos - 22);
86 pos = in.getPosition();
87 memcpy (buffer + 22, buffer, 4);
89 if (in.read (buffer, 22) != 22)
92 for (
int i = 0; i < 22; ++i)
94 if (readUnalignedLittleEndianInt (buffer + i) == 0x06054b50)
96 in.setPosition (pos + i);
98 numEntries = readUnalignedLittleEndianShort (buffer + 10);
99 auto offset = (
int64) readUnalignedLittleEndianInt (buffer + 16);
103 in.setPosition (offset);
108 if (in.readInt() != 0x02014b50)
110 in.setPosition (offset - 4);
112 if (in.readInt() == 0x02014b50)
125static bool hasSymbolicPart (
const File& root,
const File& f)
127 jassert (root == f || f.isAChildOf (root));
129 for (
auto p = f; p != root; p = p.getParentDirectory())
131 if (p.isSymbolicLink())
143 zipEntryHolder (
zei),
144 inputStream (
zf.inputStream)
146 if (
zf.inputSource !=
nullptr)
148 streamToDelete.reset (file.inputSource->createInputStream());
149 inputStream = streamToDelete.get();
154 zf.streamCounter.numOpenStreams++;
160 if (inputStream !=
nullptr
162 && inputStream->
read (buffer, 30) == 30
173 if (inputStream !=
nullptr && inputStream == file.inputStream)
174 file.streamCounter.numOpenStreams--;
180 return zipEntryHolder.compressedSize;
190 if (inputStream ==
nullptr)
195 if (inputStream == file.inputStream)
198 inputStream->
setPosition (pos + zipEntryHolder.streamOffset + headerSize);
203 inputStream->
setPosition (pos + zipEntryHolder.streamOffset + headerSize);
241 : inputStream (stream)
244 streamToDelete.reset (inputStream);
270ZipFile::OpenStreamCounter::~OpenStreamCounter()
284 return entries.size();
289 if (
auto*
zei = entries[index])
290 return &(
zei->entry);
297 for (
int i = 0; i < entries.size(); ++i)
311 return getEntry (getIndexOfFileName (
fileName, ignoreCase));
318 if (
auto*
zei = entries[index])
322 if (
zei->isCompressed)
325 GZIPDecompressorInputStream::deflateFormat,
326 zei->entry.uncompressedSize);
338 for (
int i = 0; i < entries.size(); ++i)
339 if (&entries.getUnchecked (i)->entry == &entry)
347 std::sort (entries.begin(), entries.end(),
357 if (inputSource !=
nullptr)
359 in = inputSource->createInputStream();
379 for (
int i = 0; i < numEntries; ++i)
384 auto* buffer =
static_cast<const char*
> (
headerData.getData()) + pos;
385 auto fileNameLen = readUnalignedLittleEndianShort (buffer + 28u);
390 entries.add (
new ZipEntryHolder (buffer,
fileNameLen));
393 + readUnalignedLittleEndianShort (buffer + 30u)
394 + readUnalignedLittleEndianShort (buffer + 32u);
404 for (
int i = 0; i < entries.size(); ++i)
425 auto*
zei = entries.getUnchecked (index);
430 auto entryPath =
zei->entry.filename.replaceCharacter (
'\\',
'/');
442 return targetFile.createDirectory();
447 return Result::fail (
"Failed to open the zip file for reading");
449 if (targetFile.exists())
454 if (! targetFile.deleteFile())
455 return Result::fail (
"Failed to write to target file: " + targetFile.getFullPathName());
459 return Result::fail (
"Parent directory leads through symlink for target file: " + targetFile.getFullPathName());
461 if (! targetFile.getParentDirectory().createDirectory())
462 return Result::fail (
"Failed to create target folder: " + targetFile.getParentDirectory().getFullPathName());
464 if (
zei->entry.isSymbolicLink)
477 return Result::fail (
"Failed to write to target file: " + targetFile.getFullPathName());
482 targetFile.setCreationTime (
zei->entry.fileTime);
483 targetFile.setLastModificationTime (
zei->entry.fileTime);
484 targetFile.setLastAccessTime (
zei->entry.fileTime);
509 checksum = zlibNamespace::crc32 (0, (
uint8_t*)
relativePath.toRawUTF8(), (
unsigned int) uncompressedSize);
512 else if (compressionLevel > 0)
515 GZIPCompressorOutputStream::windowBitsRaw);
529 writeFlagsAndSizes (target);
530 target << storedPathname
539 target.
writeShort (symbolicLink ? 0x0314 : 0x0014);
540 writeFlagsAndSizes (target);
544 target.
writeInt ((
int) (symbolicLink ? 0xA1ED0000 : 0));
546 target << storedPathname;
556 int64 compressedSize = 0, uncompressedSize = 0, headerStart = 0;
557 int compressionLevel = 0;
558 unsigned long checksum = 0;
559 bool symbolicLink =
false;
569 if (stream ==
nullptr)
573 if (stream ==
nullptr)
578 uncompressedSize = 0;
579 const int bufferSize = 4096;
582 while (! stream->isExhausted())
584 auto bytesRead = stream->read (buffer, bufferSize);
589 checksum = zlibNamespace::crc32 (checksum, buffer, (
unsigned int) bytesRead);
590 target.
write (buffer, (
size_t) bytesRead);
591 uncompressedSize += bytesRead;
602 target.
writeShort ((! symbolicLink && compressionLevel > 0) ? (
short) 8 : (
short) 0);
603 writeTimeAndDate (target, fileTime);
636 for (
int i = 0; i < items.size(); ++i)
638 if (progress !=
nullptr)
639 *progress = (i + 0.5) / items.size();
641 if (! items.getUnchecked (i)->writeData (target,
fileStart))
647 for (
auto* item : items)
648 if (! item->writeDirectoryEntry (target))
662 if (progress !=
nullptr)
676 :
UnitTest (
"ZIP", UnitTestCategories::compression)
684 for (
auto& entryName : entryNames)
694 MemoryOutputStream mo (data,
false);
700 void runZipSlipTest()
710 {
"../../g/h",
false },
713 {
"m/n/../../",
false },
714 {
"o/p/../../../",
false } };
716 StringArray entryNames;
718 for (
const auto& testCase : testCases)
719 entryNames.add (testCase.first);
721 TemporaryFile tmpDir;
722 tmpDir.getFile().createDirectory();
723 auto data = createZipMemoryBlock (entryNames);
724 MemoryInputStream mi (data,
false);
727 for (
int i = 0; i < zip.getNumEntries(); ++i)
729 const auto result = zip.uncompressEntry (i, tmpDir.getFile());
730 const auto caseIt = testCases.
find (zip.getEntry (i)->filename);
732 if (caseIt != testCases.
end())
734 expect (result.wasOk() == caseIt->second,
735 zip.getEntry (i)->filename +
" was unexpectedly " + (result.wasOk() ?
"OK" :
"not OK"));
744 void runTest()
override
748 StringArray entryNames {
"first",
"second",
"third" };
749 auto data = createZipMemoryBlock (entryNames);
750 MemoryInputStream mi (data,
false);
753 expectEquals (zip.getNumEntries(), entryNames.size());
755 for (
auto& entryName : entryNames)
757 auto* entry = zip.getEntry (entryName);
762 beginTest (
"ZipSlip");
static constexpr uint32 littleEndianInt(const void *bytes) noexcept
Turns 4 bytes into a little-endian integer.
static constexpr uint16 littleEndianShort(const void *bytes) noexcept
Turns 2 bytes into a little-endian integer.
size_t sizeInBytes() const noexcept
Returns the number of bytes that are used to represent this string.
An output stream that writes into a local file.
bool failedToOpen() const noexcept
Returns true if the stream couldn't be opened for some reason.
Represents a local file or directory.
bool isSymbolicLink() const
Returns true if this file is a link or alias that can be followed using getLinkedTarget().
Time getLastModificationTime() const
Returns the last modification time of this file.
int64 getSize() const
Returns the size of the file in bytes.
String getFileName() const
Returns the last section of the pathname.
bool createSymbolicLink(const File &linkFileToCreate, bool overwriteExisting) const
Tries to create a symbolic link and returns a boolean to indicate success.
std::unique_ptr< FileInputStream > createInputStream() const
Creates a stream to read from this file.
static juce_wchar getSeparatorChar()
The system-specific file separator character.
String getNativeLinkedTarget() const
This returns the native path that the symbolic link points to.
bool exists() const
Checks whether the file actually exists.
A stream which uses zlib to compress the data written into it.
Automatically locks and unlocks a mutex object.
Holds a set of mappings between some key/value pairs.
ValueType & getReference(KeyTypeParameter keyToLookFor)
Returns a reference to the value corresponding to a given key.
Very simple container class to hold a pointer to some data on the heap.
A class to hold a resizable block of raw data.
Writes data to an internal memory buffer, which grows as required.
The base class for streams that write data to some kind of destination.
virtual bool write(const void *dataToWrite, size_t numberOfBytes)=0
Writes a block of data to the stream.
virtual int64 getPosition()=0
Returns the stream's current position.
virtual bool writeShort(short value)
Writes a 16-bit integer to the stream in a little-endian byte order.
virtual bool writeInt(int value)
Writes a 32-bit integer to the stream in a little-endian byte order.
Represents the 'success' or 'failure' of an operation, and holds an associated error message to descr...
static Result fail(const String &errorMessage) noexcept
Creates a 'failure' result.
static Result ok() noexcept
Creates and returns a 'successful' result.
A special array for holding a list of strings.
bool isEmpty() const noexcept
Returns true if the string contains no characters.
String replaceCharacter(juce_wchar characterToReplace, juce_wchar characterToInsertInstead) const
Returns a string with all occurrences of a character replaced with a different one.
static String fromUTF8(const char *utf8buffer, int bufferSizeBytes=-1)
Creates a String from a UTF-8 encoded buffer.
CharPointer_UTF8 toUTF8() const
Returns a pointer to a UTF-8 version of this string.
bool isNotEmpty() const noexcept
Returns true if the string contains at least one character.
Holds an absolute date and time.
int getDayOfMonth() const noexcept
Returns the day of the month (in this machine's local timezone).
int getMonth() const noexcept
Returns the number of the month (in this machine's local timezone).
int getYear() const noexcept
Returns the year (in this machine's local timezone).
int getMinutes() const noexcept
Returns the number of minutes, 0 to 59 (in this machine's local timezone).
int getHours() const noexcept
Returns the number of hours since midnight (in this machine's local timezone).
int getSeconds() const noexcept
Returns the number of seconds, 0 to 59.
This is a base class for classes that perform a unit test.
Used to create a new zip file.
Builder()
Creates an empty builder object.
void addEntry(InputStream *streamToRead, int compressionLevel, const String &storedPathName, Time fileModificationTime)
Adds a stream to the list of items which will be added to the archive.
bool writeToStream(OutputStream &target, double *progress) const
Generates the zip file, writing it to the specified stream.
void addFile(const File &fileToAdd, int compressionLevel, const String &storedPathName=String())
Adds a file to the list of items which will be added to the archive.
Decodes a ZIP file from a stream.
Result uncompressTo(const File &targetDirectory, bool shouldOverwriteFiles=true)
Uncompresses all of the files in the zip file.
InputStream * createStreamForEntry(int index)
Creates a stream that can read from one of the zip file's entries.
const ZipEntry * getEntry(int index) const noexcept
Returns a structure that describes one of the entries in the zip file.
int getNumEntries() const noexcept
Returns the number of items in the zip file.
String filename
The name of the file, which may also include a partial pathname.
int64 uncompressedSize
The file's original size.
ZipFile(const File &file)
Creates a ZipFile to read a specific file.
uint32 externalFileAttributes
Platform specific data.
bool isSymbolicLink
True if the zip entry is a symbolic link.
Result uncompressEntry(int index, const File &targetDirectory, bool shouldOverwriteFiles=true)
Uncompresses one of the entries from the zip file.
int getIndexOfFileName(const String &fileName, bool ignoreCase=false) const noexcept
Returns the index of the first entry with a given filename.
void sortEntriesByFilename()
Sorts the list of entries, based on the filename.
Time fileTime
The last time the file was modified.
Contains information about one of the entries in a ZipFile.
unsigned short uint16
A platform-independent 16-bit unsigned integer type.
constexpr Type jmin(Type a, Type b)
Returns the smaller of two values.
constexpr Type jmax(Type a, Type b)
Returns the larger of two values.
Type jlimit(Type lowerLimit, Type upperLimit, Type valueToConstrain) noexcept
Constrains a value to keep it within a given range.
Type unalignedPointerCast(void *ptr) noexcept
Casts a pointer to another type via void*, which suppresses the cast-align warning which sometimes ar...
unsigned int uint32
A platform-independent 32-bit unsigned integer type.
long long int64
A platform-independent 64-bit integer type.