OpenShot Audio Library | OpenShotAudio 0.3.2
Loading...
Searching...
No Matches
juce_ZipFile.cpp
1/*
2 ==============================================================================
3
4 This file is part of the JUCE library.
5 Copyright (c) 2017 - ROLI Ltd.
6
7 JUCE is an open source library subject to commercial or open-source
8 licensing.
9
10 The code included in this file is provided under the terms of the ISC license
11 http://www.isc.org/downloads/software-support-policy/isc-license. Permission
12 To use, copy, modify, and/or distribute this software for any purpose with or
13 without fee is hereby granted provided that the above copyright notice and
14 this permission notice appear in all copies.
15
16 JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
17 EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
18 DISCLAIMED.
19
20 ==============================================================================
21*/
22
23namespace juce
24{
25
26inline uint16 readUnalignedLittleEndianShort (const void* buffer)
27{
28 auto data = readUnaligned<uint16> (buffer);
29 return ByteOrder::littleEndianShort (&data);
30}
31
32inline uint32 readUnalignedLittleEndianInt (const void* buffer)
33{
34 auto data = readUnaligned<uint32> (buffer);
35 return ByteOrder::littleEndianInt (&data);
36}
37
38struct ZipFile::ZipEntryHolder
39{
40 ZipEntryHolder (const char* buffer, int fileNameLen)
41 {
42 isCompressed = readUnalignedLittleEndianShort (buffer + 10) != 0;
43 entry.fileTime = parseFileTime (readUnalignedLittleEndianShort (buffer + 12),
44 readUnalignedLittleEndianShort (buffer + 14));
45 compressedSize = (int64) readUnalignedLittleEndianInt (buffer + 20);
46 entry.uncompressedSize = (int64) readUnalignedLittleEndianInt (buffer + 24);
47 streamOffset = (int64) readUnalignedLittleEndianInt (buffer + 42);
48
49 entry.externalFileAttributes = readUnalignedLittleEndianInt (buffer + 38);
50 auto fileType = (entry.externalFileAttributes >> 28) & 0xf;
51 entry.isSymbolicLink = (fileType == 0xA);
52
53 entry.filename = String::fromUTF8 (buffer + 46, fileNameLen);
54 }
55
56 static Time parseFileTime (uint32 time, uint32 date) noexcept
57 {
58 auto year = (int) (1980 + (date >> 9));
59 auto month = (int) (((date >> 5) & 15) - 1);
60 auto day = (int) (date & 31);
61 auto hours = (int) time >> 11;
62 auto minutes = (int) ((time >> 5) & 63);
63 auto seconds = (int) ((time & 31) << 1);
64
65 return { year, month, day, hours, minutes, seconds };
66 }
67
68 ZipEntry entry;
69 int64 streamOffset, compressedSize;
70 bool isCompressed;
71};
72
73//==============================================================================
74static int64 findCentralDirectoryFileHeader (InputStream& input, int& numEntries)
75{
76 BufferedInputStream in (input, 8192);
77
78 in.setPosition (in.getTotalLength());
79 auto pos = in.getPosition();
80 auto lowestPos = jmax ((int64) 0, pos - 1048576);
81 char buffer[32] = {};
82
83 while (pos > lowestPos)
84 {
85 in.setPosition (pos - 22);
86 pos = in.getPosition();
87 memcpy (buffer + 22, buffer, 4);
88
89 if (in.read (buffer, 22) != 22)
90 return 0;
91
92 for (int i = 0; i < 22; ++i)
93 {
94 if (readUnalignedLittleEndianInt (buffer + i) == 0x06054b50)
95 {
96 in.setPosition (pos + i);
97 in.read (buffer, 22);
98 numEntries = readUnalignedLittleEndianShort (buffer + 10);
99 auto offset = (int64) readUnalignedLittleEndianInt (buffer + 16);
100
101 if (offset >= 4)
102 {
103 in.setPosition (offset);
104
105 // This is a workaround for some zip files which seem to contain the
106 // wrong offset for the central directory - instead of including the
107 // header, they point to the byte immediately after it.
108 if (in.readInt() != 0x02014b50)
109 {
110 in.setPosition (offset - 4);
111
112 if (in.readInt() == 0x02014b50)
113 offset -= 4;
114 }
115 }
116
117 return offset;
118 }
119 }
120 }
121
122 return 0;
123}
124
125//==============================================================================
126struct ZipFile::ZipInputStream : public InputStream
127{
128 ZipInputStream (ZipFile& zf, const ZipFile::ZipEntryHolder& zei)
129 : file (zf),
130 zipEntryHolder (zei),
131 inputStream (zf.inputStream)
132 {
133 if (zf.inputSource != nullptr)
134 {
135 streamToDelete.reset (file.inputSource->createInputStream());
136 inputStream = streamToDelete.get();
137 }
138 else
139 {
140 #if JUCE_DEBUG
141 zf.streamCounter.numOpenStreams++;
142 #endif
143 }
144
145 char buffer[30];
146
147 if (inputStream != nullptr
148 && inputStream->setPosition (zei.streamOffset)
149 && inputStream->read (buffer, 30) == 30
150 && ByteOrder::littleEndianInt (buffer) == 0x04034b50)
151 {
152 headerSize = 30 + ByteOrder::littleEndianShort (buffer + 26)
153 + ByteOrder::littleEndianShort (buffer + 28);
154 }
155 }
156
157 ~ZipInputStream() override
158 {
159 #if JUCE_DEBUG
160 if (inputStream != nullptr && inputStream == file.inputStream)
161 file.streamCounter.numOpenStreams--;
162 #endif
163 }
164
165 int64 getTotalLength() override
166 {
167 return zipEntryHolder.compressedSize;
168 }
169
170 int read (void* buffer, int howMany) override
171 {
172 if (headerSize <= 0)
173 return 0;
174
175 howMany = (int) jmin ((int64) howMany, zipEntryHolder.compressedSize - pos);
176
177 if (inputStream == nullptr)
178 return 0;
179
180 int num;
181
182 if (inputStream == file.inputStream)
183 {
184 const ScopedLock sl (file.lock);
185 inputStream->setPosition (pos + zipEntryHolder.streamOffset + headerSize);
186 num = inputStream->read (buffer, howMany);
187 }
188 else
189 {
190 inputStream->setPosition (pos + zipEntryHolder.streamOffset + headerSize);
191 num = inputStream->read (buffer, howMany);
192 }
193
194 pos += num;
195 return num;
196 }
197
198 bool isExhausted() override
199 {
200 return headerSize <= 0 || pos >= zipEntryHolder.compressedSize;
201 }
202
203 int64 getPosition() override
204 {
205 return pos;
206 }
207
208 bool setPosition (int64 newPos) override
209 {
210 pos = jlimit ((int64) 0, zipEntryHolder.compressedSize, newPos);
211 return true;
212 }
213
214private:
215 ZipFile& file;
216 ZipEntryHolder zipEntryHolder;
217 int64 pos = 0;
218 int headerSize = 0;
219 InputStream* inputStream;
220 std::unique_ptr<InputStream> streamToDelete;
221
222 JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ZipInputStream)
223};
224
225
226//==============================================================================
228 : inputStream (stream)
229{
231 streamToDelete.reset (inputStream);
232
233 init();
234}
235
236ZipFile::ZipFile (InputStream& stream) : inputStream (&stream)
237{
238 init();
239}
240
241ZipFile::ZipFile (const File& file) : inputSource (new FileInputSource (file))
242{
243 init();
244}
245
246ZipFile::ZipFile (InputSource* source) : inputSource (source)
247{
248 init();
249}
250
252{
253 entries.clear();
254}
255
256#if JUCE_DEBUG
257ZipFile::OpenStreamCounter::~OpenStreamCounter()
258{
259 /* If you hit this assertion, it means you've created a stream to read one of the items in the
260 zipfile, but you've forgotten to delete that stream object before deleting the file..
261 Streams can't be kept open after the file is deleted because they need to share the input
262 stream that is managed by the ZipFile object.
263 */
264 jassert (numOpenStreams == 0);
265}
266#endif
267
268//==============================================================================
270{
271 return entries.size();
272}
273
274const ZipFile::ZipEntry* ZipFile::getEntry (const int index) const noexcept
275{
276 if (auto* zei = entries[index])
277 return &(zei->entry);
278
279 return nullptr;
280}
281
282int ZipFile::getIndexOfFileName (const String& fileName, bool ignoreCase) const noexcept
283{
284 for (int i = 0; i < entries.size(); ++i)
285 {
286 auto& entryFilename = entries.getUnchecked (i)->entry.filename;
287
288 if (ignoreCase ? entryFilename.equalsIgnoreCase (fileName)
290 return i;
291 }
292
293 return -1;
294}
295
296const ZipFile::ZipEntry* ZipFile::getEntry (const String& fileName, bool ignoreCase) const noexcept
297{
298 return getEntry (getIndexOfFileName (fileName, ignoreCase));
299}
300
302{
303 InputStream* stream = nullptr;
304
305 if (auto* zei = entries[index])
306 {
307 stream = new ZipInputStream (*this, *zei);
308
309 if (zei->isCompressed)
310 {
311 stream = new GZIPDecompressorInputStream (stream, true,
312 GZIPDecompressorInputStream::deflateFormat,
313 zei->entry.uncompressedSize);
314
315 // (much faster to unzip in big blocks using a buffer..)
316 stream = new BufferedInputStream (stream, 32768, true);
317 }
318 }
319
320 return stream;
321}
322
324{
325 for (int i = 0; i < entries.size(); ++i)
326 if (&entries.getUnchecked (i)->entry == &entry)
327 return createStreamForEntry (i);
328
329 return nullptr;
330}
331
333{
334 std::sort (entries.begin(), entries.end(),
335 [] (const ZipEntryHolder* e1, const ZipEntryHolder* e2) { return e1->entry.filename < e2->entry.filename; });
336}
337
338//==============================================================================
339void ZipFile::init()
340{
341 std::unique_ptr<InputStream> toDelete;
342 InputStream* in = inputStream;
343
344 if (inputSource != nullptr)
345 {
346 in = inputSource->createInputStream();
347 toDelete.reset (in);
348 }
349
350 if (in != nullptr)
351 {
352 int numEntries = 0;
353 auto centralDirectoryPos = findCentralDirectoryFileHeader (*in, numEntries);
354
355 if (centralDirectoryPos >= 0 && centralDirectoryPos < in->getTotalLength())
356 {
357 auto size = (size_t) (in->getTotalLength() - centralDirectoryPos);
358
359 in->setPosition (centralDirectoryPos);
360 MemoryBlock headerData;
361
362 if (in->readIntoMemoryBlock (headerData, (ssize_t) size) == size)
363 {
364 size_t pos = 0;
365
366 for (int i = 0; i < numEntries; ++i)
367 {
368 if (pos + 46 > size)
369 break;
370
371 auto* buffer = static_cast<const char*> (headerData.getData()) + pos;
372 auto fileNameLen = readUnalignedLittleEndianShort (buffer + 28u);
373
374 if (pos + 46 + fileNameLen > size)
375 break;
376
377 entries.add (new ZipEntryHolder (buffer, fileNameLen));
378
379 pos += 46u + fileNameLen
380 + readUnalignedLittleEndianShort (buffer + 30u)
381 + readUnalignedLittleEndianShort (buffer + 32u);
382 }
383 }
384 }
385 }
386}
387
389 const bool shouldOverwriteFiles)
390{
391 for (int i = 0; i < entries.size(); ++i)
392 {
394
395 if (result.failed())
396 return result;
397 }
398
399 return Result::ok();
400}
401
403{
404 auto* zei = entries.getUnchecked (index);
405
406 #if JUCE_WINDOWS
407 auto entryPath = zei->entry.filename;
408 #else
409 auto entryPath = zei->entry.filename.replaceCharacter ('\\', '/');
410 #endif
411
412 if (entryPath.isEmpty())
413 return Result::ok();
414
415 auto targetFile = targetDirectory.getChildFile (entryPath);
416
417 if (entryPath.endsWithChar ('/') || entryPath.endsWithChar ('\\'))
418 return targetFile.createDirectory(); // (entry is a directory, not a file)
419
420 std::unique_ptr<InputStream> in (createStreamForEntry (index));
421
422 if (in == nullptr)
423 return Result::fail ("Failed to open the zip file for reading");
424
425 if (targetFile.exists())
426 {
428 return Result::ok();
429
430 if (! targetFile.deleteFile())
431 return Result::fail ("Failed to write to target file: " + targetFile.getFullPathName());
432 }
433
434 if (! targetFile.getParentDirectory().createDirectory())
435 return Result::fail ("Failed to create target folder: " + targetFile.getParentDirectory().getFullPathName());
436
437 if (zei->entry.isSymbolicLink)
438 {
439 String originalFilePath (in->readEntireStreamAsString()
440 .replaceCharacter (L'/', File::getSeparatorChar()));
441
442 if (! File::createSymbolicLink (targetFile, originalFilePath, true))
443 return Result::fail ("Failed to create symbolic link: " + originalFilePath);
444 }
445 else
446 {
447 FileOutputStream out (targetFile);
448
449 if (out.failedToOpen())
450 return Result::fail ("Failed to write to target file: " + targetFile.getFullPathName());
451
452 out << *in;
453 }
454
455 targetFile.setCreationTime (zei->entry.fileTime);
456 targetFile.setLastModificationTime (zei->entry.fileTime);
457 targetFile.setLastAccessTime (zei->entry.fileTime);
458
459 return Result::ok();
460}
461
462
463//==============================================================================
464struct ZipFile::Builder::Item
465{
466 Item (const File& f, InputStream* s, int compression, const String& storedPath, Time time)
467 : file (f), stream (s), storedPathname (storedPath), fileTime (time), compressionLevel (compression)
468 {
469 symbolicLink = (file.exists() && file.isSymbolicLink());
470 }
471
472 bool writeData (OutputStream& target, const int64 overallStartPosition)
473 {
474 MemoryOutputStream compressedData ((size_t) file.getSize());
475
476 if (symbolicLink)
477 {
478 auto relativePath = file.getNativeLinkedTarget().replaceCharacter (File::getSeparatorChar(), L'/');
479
480 uncompressedSize = relativePath.length();
481
482 checksum = zlibNamespace::crc32 (0, (uint8_t*) relativePath.toRawUTF8(), (unsigned int) uncompressedSize);
483 compressedData << relativePath;
484 }
485 else if (compressionLevel > 0)
486 {
487 GZIPCompressorOutputStream compressor (compressedData, compressionLevel,
488 GZIPCompressorOutputStream::windowBitsRaw);
489 if (! writeSource (compressor))
490 return false;
491 }
492 else
493 {
494 if (! writeSource (compressedData))
495 return false;
496 }
497
498 compressedSize = (int64) compressedData.getDataSize();
499 headerStart = target.getPosition() - overallStartPosition;
500
501 target.writeInt (0x04034b50);
502 writeFlagsAndSizes (target);
503 target << storedPathname
504 << compressedData;
505
506 return true;
507 }
508
509 bool writeDirectoryEntry (OutputStream& target)
510 {
511 target.writeInt (0x02014b50);
512 target.writeShort (symbolicLink ? 0x0314 : 0x0014);
513 writeFlagsAndSizes (target);
514 target.writeShort (0); // comment length
515 target.writeShort (0); // start disk num
516 target.writeShort (0); // internal attributes
517 target.writeInt ((int) (symbolicLink ? 0xA1ED0000 : 0)); // external attributes
518 target.writeInt ((int) (uint32) headerStart);
519 target << storedPathname;
520
521 return true;
522 }
523
524private:
525 const File file;
526 std::unique_ptr<InputStream> stream;
527 String storedPathname;
528 Time fileTime;
529 int64 compressedSize = 0, uncompressedSize = 0, headerStart = 0;
530 int compressionLevel = 0;
531 unsigned long checksum = 0;
532 bool symbolicLink = false;
533
534 static void writeTimeAndDate (OutputStream& target, Time t)
535 {
536 target.writeShort ((short) (t.getSeconds() + (t.getMinutes() << 5) + (t.getHours() << 11)));
537 target.writeShort ((short) (t.getDayOfMonth() + ((t.getMonth() + 1) << 5) + ((t.getYear() - 1980) << 9)));
538 }
539
540 bool writeSource (OutputStream& target)
541 {
542 if (stream == nullptr)
543 {
544 stream.reset (file.createInputStream());
545
546 if (stream == nullptr)
547 return false;
548 }
549
550 checksum = 0;
551 uncompressedSize = 0;
552 const int bufferSize = 4096;
553 HeapBlock<unsigned char> buffer (bufferSize);
554
555 while (! stream->isExhausted())
556 {
557 auto bytesRead = stream->read (buffer, bufferSize);
558
559 if (bytesRead < 0)
560 return false;
561
562 checksum = zlibNamespace::crc32 (checksum, buffer, (unsigned int) bytesRead);
563 target.write (buffer, (size_t) bytesRead);
564 uncompressedSize += bytesRead;
565 }
566
567 stream.reset();
568 return true;
569 }
570
571 void writeFlagsAndSizes (OutputStream& target) const
572 {
573 target.writeShort (10); // version needed
574 target.writeShort ((short) (1 << 11)); // this flag indicates UTF-8 filename encoding
575 target.writeShort ((! symbolicLink && compressionLevel > 0) ? (short) 8 : (short) 0); //symlink target path is not compressed
576 writeTimeAndDate (target, fileTime);
577 target.writeInt ((int) checksum);
578 target.writeInt ((int) (uint32) compressedSize);
579 target.writeInt ((int) (uint32) uncompressedSize);
580 target.writeShort ((short) storedPathname.toUTF8().sizeInBytes() - 1);
581 target.writeShort (0); // extra field length
582 }
583
584 JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (Item)
585};
586
587//==============================================================================
590
591void ZipFile::Builder::addFile (const File& file, int compression, const String& path)
592{
593 items.add (new Item (file, nullptr, compression,
594 path.isEmpty() ? file.getFileName() : path,
596}
597
598void ZipFile::Builder::addEntry (InputStream* stream, int compression, const String& path, Time time)
599{
600 jassert (stream != nullptr); // must not be null!
601 jassert (path.isNotEmpty());
602 items.add (new Item ({}, stream, compression, path, time));
603}
604
605bool ZipFile::Builder::writeToStream (OutputStream& target, double* const progress) const
606{
607 auto fileStart = target.getPosition();
608
609 for (int i = 0; i < items.size(); ++i)
610 {
611 if (progress != nullptr)
612 *progress = (i + 0.5) / items.size();
613
614 if (! items.getUnchecked (i)->writeData (target, fileStart))
615 return false;
616 }
617
618 auto directoryStart = target.getPosition();
619
620 for (auto* item : items)
621 if (! item->writeDirectoryEntry (target))
622 return false;
623
624 auto directoryEnd = target.getPosition();
625
626 target.writeInt (0x06054b50);
627 target.writeShort (0);
628 target.writeShort (0);
629 target.writeShort ((short) items.size());
630 target.writeShort ((short) items.size());
631 target.writeInt ((int) (directoryEnd - directoryStart));
632 target.writeInt ((int) (directoryStart - fileStart));
633 target.writeShort (0);
634
635 if (progress != nullptr)
636 *progress = 1.0;
637
638 return true;
639}
640
641
642//==============================================================================
643//==============================================================================
644#if JUCE_UNIT_TESTS
645
646struct ZIPTests : public UnitTest
647{
648 ZIPTests()
649 : UnitTest ("ZIP", UnitTestCategories::compression)
650 {}
651
652 void runTest() override
653 {
654 beginTest ("ZIP");
655
657 StringArray entryNames { "first", "second", "third" };
658 HashMap<String, MemoryBlock> blocks;
659
660 for (auto& entryName : entryNames)
661 {
662 auto& block = blocks.getReference (entryName);
663 MemoryOutputStream mo (block, false);
664 mo << entryName;
665 mo.flush();
666 builder.addEntry (new MemoryInputStream (block, false), 9, entryName, Time::getCurrentTime());
667 }
668
669 MemoryBlock data;
670 MemoryOutputStream mo (data, false);
671 builder.writeToStream (mo, nullptr);
672 MemoryInputStream mi (data, false);
673
674 ZipFile zip (mi);
675
676 expectEquals (zip.getNumEntries(), entryNames.size());
677
678 for (auto& entryName : entryNames)
679 {
680 auto* entry = zip.getEntry (entryName);
681 std::unique_ptr<InputStream> input (zip.createStreamForEntry (*entry));
682 expectEquals (input->readEntireStreamAsString(), entryName);
683 }
684 }
685};
686
687static ZIPTests zipTests;
688
689#endif
690
691} // namespace juce
ElementType getUnchecked(int index) const
Definition juce_Array.h:252
bool isEmpty() const noexcept
Definition juce_Array.h:222
int size() const noexcept
Definition juce_Array.h:215
void add(const ElementType &newElement)
Definition juce_Array.h:418
ElementType & getReference(int index) noexcept
Definition juce_Array.h:267
static JUCE_CONSTEXPR uint16 littleEndianShort(const void *bytes) noexcept
static JUCE_CONSTEXPR uint32 littleEndianInt(const void *bytes) noexcept
Time getLastModificationTime() const
String getFileName() const
bool createSymbolicLink(const File &linkFileToCreate, bool overwriteExisting) const
static juce_wchar getSeparatorChar()
virtual bool setPosition(int64 newPosition)=0
virtual int64 getTotalLength()=0
virtual size_t readIntoMemoryBlock(MemoryBlock &destBlock, ssize_t maxNumBytesToRead=-1)
virtual int64 getPosition()=0
virtual bool writeShort(short value)
virtual bool writeInt(int value)
int size() const noexcept
ObjectClass * getUnchecked(int index) const noexcept
void clear(bool deleteObjects=true)
ObjectClass * add(ObjectClass *newObject)
ObjectClass ** begin() noexcept
ObjectClass ** end() noexcept
static Result fail(const String &errorMessage) noexcept
static Result ok() noexcept
Definition juce_Result.h:61
bool isEmpty() const noexcept
static String fromUTF8(const char *utf8buffer, int bufferSizeBytes=-1)
bool isNotEmpty() const noexcept
void addEntry(InputStream *streamToRead, int compressionLevel, const String &storedPathName, Time fileModificationTime)
bool writeToStream(OutputStream &target, double *progress) const
void addFile(const File &fileToAdd, int compressionLevel, const String &storedPathName=String())
Result uncompressTo(const File &targetDirectory, bool shouldOverwriteFiles=true)
InputStream * createStreamForEntry(int index)
const ZipEntry * getEntry(int index) const noexcept
int getNumEntries() const noexcept
ZipFile(const File &file)
Result uncompressEntry(int index, const File &targetDirectory, bool shouldOverwriteFiles=true)
int getIndexOfFileName(const String &fileName, bool ignoreCase=false) const noexcept
void sortEntriesByFilename()