26 inline uint16 readUnalignedLittleEndianShort (
const void* buffer)
28 auto data = readUnaligned<uint16> (buffer);
32 inline 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;
74 static 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);
257 ZipFile::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);
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));
683 static ZIPTests zipTests;