26inline uint16 readUnalignedLittleEndianShort (
const void* buffer)
28 auto data = readUnaligned<uint16> (buffer);
32inline uint32 readUnalignedLittleEndianInt (
const void* buffer)
34 auto data = readUnaligned<uint32> (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);
49 auto externalFileAttributes = (int32) readUnalignedLittleEndianInt (buffer + 38);
50 auto fileType = (externalFileAttributes >> 28) & 0xf;
56 static Time parseFileTime (uint32 time, uint32 date)
noexcept
58 int year = 1980 + (date >> 9);
59 int month = ((date >> 5) & 15) - 1;
61 int hours = time >> 11;
62 int minutes = (time >> 5) & 63;
63 int 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();
80 auto lowestPos = jmax ((int64) 0, pos - 1048576);
83 while (pos > lowestPos)
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)
130 zipEntryHolder (zei),
131 inputStream (zf.inputStream)
133 if (zf.inputSource !=
nullptr)
135 streamToDelete.reset (file.inputSource->createInputStream());
136 inputStream = streamToDelete.get();
141 zf.streamCounter.numOpenStreams++;
147 if (inputStream !=
nullptr
149 && inputStream->
read (buffer, 30) == 30
160 if (inputStream !=
nullptr && inputStream == file.inputStream)
161 file.streamCounter.numOpenStreams--;
167 return zipEntryHolder.compressedSize;
170 int read (
void* buffer,
int howMany)
override
175 howMany = (int) jmin ((int64) howMany, zipEntryHolder.compressedSize - pos);
177 if (inputStream ==
nullptr)
182 if (inputStream == file.inputStream)
185 inputStream->
setPosition (pos + zipEntryHolder.streamOffset + headerSize);
186 num = inputStream->
read (buffer, howMany);
190 inputStream->
setPosition (pos + zipEntryHolder.streamOffset + headerSize);
191 num = inputStream->
read (buffer, howMany);
200 return headerSize <= 0 || pos >= zipEntryHolder.compressedSize;
210 pos = jlimit ((int64) 0, zipEntryHolder.compressedSize, newPos);
220 std::unique_ptr<InputStream> streamToDelete;
228 : inputStream (stream)
230 if (deleteStreamWhenDestroyed)
231 streamToDelete.reset (inputStream);
257ZipFile::OpenStreamCounter::~OpenStreamCounter()
264 jassert (numOpenStreams == 0);
271 return entries.size();
276 if (
auto* zei = entries[index])
277 return &(zei->entry);
284 for (
int i = 0; i < entries.size(); ++i)
286 auto& entryFilename = entries.getUnchecked (i)->entry.filename;
288 if (ignoreCase ? entryFilename.equalsIgnoreCase (fileName)
289 : entryFilename == fileName)
298 return getEntry (getIndexOfFileName (fileName, ignoreCase));
305 if (
auto* zei = entries[index])
309 if (zei->isCompressed)
312 GZIPDecompressorInputStream::deflateFormat,
313 zei->entry.uncompressedSize);
325 for (
int i = 0; i < entries.size(); ++i)
326 if (&entries.getUnchecked (i)->entry == &entry)
334 std::sort (entries.begin(), entries.end(),
341 std::unique_ptr<InputStream> toDelete;
344 if (inputSource !=
nullptr)
346 in = inputSource->createInputStream();
353 auto centralDirectoryPos = findCentralDirectoryFileHeader (*in, numEntries);
355 if (centralDirectoryPos >= 0 && centralDirectoryPos < in->getTotalLength())
357 auto size = (size_t) (in->
getTotalLength() - centralDirectoryPos);
360 MemoryBlock headerData;
366 for (
int i = 0; i < numEntries; ++i)
371 auto* buffer =
static_cast<const char*
> (headerData.getData()) + pos;
372 auto fileNameLen = readUnalignedLittleEndianShort (buffer + 28);
374 if (pos + 46 + fileNameLen > size)
377 entries.add (
new ZipEntryHolder (buffer, fileNameLen));
379 pos += 46 + fileNameLen
380 + readUnalignedLittleEndianShort (buffer + 30)
381 + readUnalignedLittleEndianShort (buffer + 32);
389 const bool shouldOverwriteFiles)
391 for (
int i = 0; i < entries.size(); ++i)
393 auto result =
uncompressEntry (i, targetDirectory, shouldOverwriteFiles);
404 auto* zei = entries.getUnchecked (index);
407 auto entryPath = zei->entry.filename;
409 auto entryPath = zei->entry.filename.replaceCharacter (
'\\',
'/');
412 if (entryPath.isEmpty())
415 auto targetFile = targetDirectory.
getChildFile (entryPath);
417 if (entryPath.endsWithChar (
'/') || entryPath.endsWithChar (
'\\'))
423 return Result::fail (
"Failed to open the zip file for reading");
425 if (targetFile.exists())
427 if (! shouldOverwriteFiles)
430 if (! targetFile.deleteFile())
431 return Result::fail (
"Failed to write to target file: " + targetFile.getFullPathName());
434 if (! targetFile.getParentDirectory().createDirectory())
435 return Result::fail (
"Failed to create target folder: " + targetFile.getParentDirectory().getFullPathName());
437 if (zei->entry.isSymbolicLink)
439 String originalFilePath (in->readEntireStreamAsString()
443 return Result::fail (
"Failed to create symbolic link: " + originalFilePath);
450 return Result::fail (
"Failed to write to target file: " + targetFile.getFullPathName());
455 targetFile.setCreationTime (zei->entry.fileTime);
456 targetFile.setLastModificationTime (zei->entry.fileTime);
457 targetFile.setLastAccessTime (zei->entry.fileTime);
467 : file (f), stream (s), storedPathname (storedPath), fileTime (time), compressionLevel (compression)
472 bool writeData (
OutputStream& target,
const int64 overallStartPosition)
480 uncompressedSize = relativePath.length();
482 checksum = zlibNamespace::crc32 (0, (uint8_t*) relativePath.toRawUTF8(), (
unsigned int) uncompressedSize);
483 compressedData << relativePath;
485 else if (compressionLevel > 0)
488 GZIPCompressorOutputStream::windowBitsRaw);
489 if (! writeSource (compressor))
494 if (! writeSource (compressedData))
498 compressedSize = (int64) compressedData.
getDataSize();
499 headerStart = target.
getPosition() - overallStartPosition;
502 writeFlagsAndSizes (target);
503 target << storedPathname
512 target.
writeShort (symbolicLink ? 0x0314 : 0x0014);
513 writeFlagsAndSizes (target);
517 target.
writeInt ((
int) (symbolicLink ? 0xA1ED0000 : 0));
518 target.
writeInt ((
int) (uint32) headerStart);
519 target << storedPathname;
526 std::unique_ptr<InputStream> stream;
529 int64 compressedSize = 0, uncompressedSize = 0, headerStart = 0;
530 int compressionLevel = 0;
531 unsigned long checksum = 0;
532 bool symbolicLink =
false;
542 if (stream ==
nullptr)
546 if (stream ==
nullptr)
551 uncompressedSize = 0;
552 const int bufferSize = 4096;
555 while (! stream->isExhausted())
557 auto bytesRead = stream->read (buffer, bufferSize);
562 checksum = zlibNamespace::crc32 (checksum, buffer, (
unsigned int) bytesRead);
563 target.
write (buffer, (
size_t) bytesRead);
564 uncompressedSize += bytesRead;
575 target.
writeShort ((! symbolicLink && compressionLevel > 0) ? (
short) 8 : (short) 0);
576 writeTimeAndDate (target, fileTime);
578 target.
writeInt ((
int) (uint32) compressedSize);
579 target.
writeInt ((
int) (uint32) uncompressedSize);
584 JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (
Item)
593 items.add (
new Item (file,
nullptr, compression,
600 jassert (stream !=
nullptr);
602 items.add (
new Item ({}, stream, compression, path, time));
609 for (
int i = 0; i < items.size(); ++i)
611 if (progress !=
nullptr)
612 *progress = (i + 0.5) / items.size();
614 if (! items.getUnchecked (i)->writeData (target, fileStart))
620 for (
auto* item : items)
621 if (! item->writeDirectoryEntry (target))
631 target.
writeInt ((
int) (directoryEnd - directoryStart));
632 target.
writeInt ((
int) (directoryStart - fileStart));
635 if (progress !=
nullptr)
648 void runTest()
override
653 StringArray entryNames {
"first",
"second",
"third" };
654 HashMap<String, MemoryBlock> blocks;
656 for (
auto& entryName : entryNames)
659 MemoryOutputStream mo (block,
false);
662 builder.
addEntry (
new MemoryInputStream (block,
false), 9, entryName, Time::getCurrentTime());
666 MemoryOutputStream mo (data,
false);
668 MemoryInputStream mi (data,
false);
672 expectEquals (zip.getNumEntries(), entryNames.size());
674 for (
auto& entryName : entryNames)
676 auto* entry = zip.getEntry (entryName);
677 std::unique_ptr<InputStream> input (zip.createStreamForEntry (*entry));
683static ZIPTests zipTests;
static JUCE_CONSTEXPR uint16 littleEndianShort(const void *bytes) noexcept
Turns 2 bytes into a little-endian integer.
static JUCE_CONSTEXPR uint32 littleEndianInt(const void *bytes) noexcept
Turns 4 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.
File getChildFile(StringRef relativeOrAbsolutePath) const
Returns a file that represents a relative (or absolute) sub-path of the current one.
bool createSymbolicLink(const File &linkFileToCreate, bool overwriteExisting) const
Tries to create a symbolic link and returns a boolean to indicate success.
static juce_wchar getSeparatorChar()
The system-specific file separator character.
FileInputStream * createInputStream() const
Creates a stream to read from this file.
String getNativeLinkedTarget() const
This returns the native path that the symbolic link points to.
bool exists() const
Checks whether the file actually exists.
Result createDirectory() const
Creates a new directory for this filename.
A stream which uses zlib to compress the data written into it.
Automatically locks and unlocks a mutex object.
Very simple container class to hold a pointer to some data on the heap.
Writes data to an internal memory buffer, which grows as required.
size_t getDataSize() const noexcept
Returns the number of bytes of data that have been written to the stream.
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.
String & getReference(int index) noexcept
Returns a reference to one of the strings in the array.
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.
ZipFile(const File &file)
Creates a ZipFile to read a specific file.
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.
String filename
The name of the file, which may also include a partial pathname.
int64 uncompressedSize
The file's original size.
bool isSymbolicLink
True if the zip entry is a symbolic link.
Time fileTime
The last time the file was modified.
Contains information about one of the entries in a ZipFile.