PageRenderTime 210ms CodeModel.GetById 40ms app.highlight 134ms RepoModel.GetById 27ms app.codeStats 0ms

/mordor/zip.cpp

http://github.com/mozy/mordor
C++ | 819 lines | 771 code | 35 blank | 13 comment | 185 complexity | 42d354a397b42449d2e0e3bed40948e6 MD5 | raw file
  1// Copyright (c) 2010 - Mozy, Inc.
  2
  3#include "zip.h"
  4
  5#include "mordor/assert.h"
  6#include "mordor/log.h"
  7#include "mordor/streams/buffered.h"
  8#include "mordor/streams/deflate.h"
  9#include "mordor/streams/hash.h"
 10#include "mordor/streams/limited.h"
 11#include "mordor/streams/notify.h"
 12#include "mordor/streams/null.h"
 13#include "mordor/streams/singleplex.h"
 14#include "mordor/streams/transfer.h"
 15
 16namespace {
 17#pragma pack(push)
 18#pragma pack(1)
 19struct LocalFileHeader
 20{
 21    unsigned int signature;             // 0x04034b50
 22    unsigned short extractVersion;
 23    unsigned short generalPurposeFlags;
 24    unsigned short compressionMethod;
 25    unsigned short modifiedTime;
 26    unsigned short modifiedDate;
 27    unsigned int crc32;
 28    unsigned int compressedSize;
 29    unsigned int decompressedSize;
 30    unsigned short fileNameLength;
 31    unsigned short extraFieldLength;
 32    //char   FileName[FileNameLength];
 33    //       ExtraField[ExtraFieldLength]; // Array of ExtraField structures; length is in bytes
 34};
 35
 36struct DataDescriptor
 37{
 38    unsigned int signature;
 39    unsigned int crc32;
 40    unsigned int compressedSize;
 41    unsigned int uncompressedSize;
 42};
 43
 44struct DataDescriptor64
 45{
 46    unsigned int signature;
 47    unsigned int crc32;
 48    long long compressedSize;
 49    long long uncompressedSize;
 50};
 51
 52struct FileHeader
 53{
 54    unsigned int signature;
 55    unsigned short versionMadeBy;
 56    unsigned short extractVersion;
 57    unsigned short generalPurposeFlags;
 58    unsigned short compressionMethod;
 59    unsigned short modifiedTime;
 60    unsigned short modifiedDate;
 61    unsigned int crc32;
 62    unsigned int compressedSize;
 63    unsigned int uncompressedSize;
 64    unsigned short fileNameLength;
 65    unsigned short extraFieldLength;
 66    unsigned short fileCommentLength;
 67    unsigned short diskNumberStart;
 68    unsigned short internalFileAttributes;
 69    unsigned int externalFileAttributes;
 70    unsigned int localHeaderOffset;
 71    // char fileName[fileNameLength];
 72    // char extraField[extraFieldLength];
 73    // char fileComment[fileCommentLength];
 74};
 75
 76struct Zip64EndOfCentralDirectory
 77{
 78    unsigned int signature;
 79    unsigned long long sizeOfEndOfCentralDirectory;
 80    unsigned short versionMadeBy;
 81    unsigned short extractVersion;
 82    unsigned int numberOfThisDisk;
 83    unsigned int startOfCentralDirectoryDisk;
 84    unsigned long long centralDirectoryEntriesThisDisk;
 85    unsigned long long centralDirectoryEntries;
 86    unsigned long long sizeOfCentralDirectory;
 87    long long startOfCentralDirectoryOffset;
 88};
 89
 90struct Zip64EndOfCentralDirectoryLocator
 91{
 92    unsigned int signature;
 93    unsigned int endOfCentralDirectoryDisk;
 94    long long endOfCentralDirectoryOffset;
 95    unsigned int totalDisks;
 96};
 97
 98struct EndOfCentralDirectory
 99{
100    unsigned int signature;
101    unsigned short numberOfThisDisk;
102    unsigned short startOfCentralDirectoryDisk;
103    unsigned short centralDirectoryEntriesThisDisk;
104    unsigned short centralDirectoryEntries;
105    unsigned int sizeOfCentralDirectory;
106    unsigned int startOfCentralDirectoryOffset;
107    unsigned short commentLength;
108};
109#pragma pack(pop)
110}
111
112namespace Mordor {
113
114Zip::Zip(Stream::ptr stream, OpenMode mode)
115    : m_stream(stream),
116      m_currentFile(NULL),
117#ifdef MSVC
118#pragma warning(push)
119#pragma warning(disable: 4355)
120#endif
121      m_scratchFile(*this)
122#ifdef MSVC
123#pragma warning(pop)
124#endif
125{
126    if (!m_stream->supportsTell())
127        m_stream.reset(new LimitedStream(m_stream,
128            0x7fffffffffffffffll));
129    m_stream.reset(new BufferedStream(m_stream));
130
131    if (mode == INFER) {
132        if (m_stream->supportsWrite())
133            mode = WRITE;
134        else
135            mode = READ;
136    }
137    switch (mode) {
138        case READ:
139            MORDOR_ASSERT(m_stream->supportsRead());
140            if (m_stream->supportsSeek() && m_stream->supportsSize()) {
141                unsigned char buffer[65536];
142                long long fileSize = m_stream->size();
143                if (fileSize < (long long)sizeof(EndOfCentralDirectory))
144                    MORDOR_THROW_EXCEPTION(CorruptZipException());
145                size_t toRead = (size_t)(std::min)(65536ll, fileSize);
146                m_stream->seek(-(long long)toRead, Stream::END);
147                m_stream->read(buffer, toRead);
148                unsigned char *search = buffer + toRead -
149                    sizeof(EndOfCentralDirectory);
150                while (search >= buffer) {
151                    if (*(unsigned int *)search == 0x06054b50)
152                        break;
153                    --search;
154                }
155                if (search < buffer)
156                    MORDOR_THROW_EXCEPTION(CorruptZipException());
157                EndOfCentralDirectory &eocd = *(EndOfCentralDirectory *)search;
158                if (eocd.numberOfThisDisk != eocd.startOfCentralDirectoryDisk)
159                    MORDOR_THROW_EXCEPTION(SpannedZipNotSupportedException());
160
161                // Look for zip64 extensions
162                search -= sizeof(Zip64EndOfCentralDirectoryLocator);
163                long long startOfCentralDirectory;
164                unsigned long long entries = 0;
165                if (search >= buffer && *(unsigned int*)search == 0x07064b50) {
166                    Zip64EndOfCentralDirectoryLocator &z64eocdl =
167                        *(Zip64EndOfCentralDirectoryLocator *)search;
168                    if (z64eocdl.totalDisks > 1)
169                        MORDOR_THROW_EXCEPTION(SpannedZipNotSupportedException());
170                    m_stream->seek(z64eocdl.endOfCentralDirectoryOffset);
171                    Zip64EndOfCentralDirectory z64eocd;
172                    if (m_stream->read(&z64eocd,
173                        sizeof(Zip64EndOfCentralDirectory)) !=
174                        sizeof(Zip64EndOfCentralDirectory))
175                        MORDOR_THROW_EXCEPTION(UnexpectedEofException());
176                    if (z64eocd.startOfCentralDirectoryDisk !=
177                        z64eocd.numberOfThisDisk)
178                        MORDOR_THROW_EXCEPTION(SpannedZipNotSupportedException());
179                    startOfCentralDirectory =
180                        z64eocd.startOfCentralDirectoryOffset;
181                    entries = z64eocd.centralDirectoryEntries;
182                } else {
183                    startOfCentralDirectory =
184                        eocd.startOfCentralDirectoryOffset;
185                    entries = eocd.centralDirectoryEntries;
186                }
187                m_stream->seek(startOfCentralDirectory);
188                for (unsigned long long i = 0; i < entries; ++i) {
189                    FileHeader header;
190                    if (m_stream->read(&header, sizeof(FileHeader))
191                        != sizeof(FileHeader))
192                        MORDOR_THROW_EXCEPTION(UnexpectedEofException());
193                    if (header.signature != 0x02014b50)
194                        MORDOR_THROW_EXCEPTION(CorruptZipException());
195                    ZipEntry file(*this);
196                    if (header.fileNameLength) {
197                        file.m_filename.resize(header.fileNameLength);
198                        if (m_stream->read(&file.m_filename[0],
199                            header.fileNameLength) != header.fileNameLength)
200                            MORDOR_THROW_EXCEPTION(UnexpectedEofException());
201                    }
202                    file.m_crc = header.crc32;
203                    file.m_compressedSize = header.compressedSize;
204                    file.m_compressionMethod = header.compressionMethod;
205                    file.m_size = header.uncompressedSize;
206                    file.m_startOffset = header.localHeaderOffset;
207                    file.m_flags = header.generalPurposeFlags;
208                    if (header.extraFieldLength) {
209                        std::string extraFields;
210                        extraFields.resize(header.extraFieldLength);
211                        size_t read = m_stream->read(&extraFields[0],
212                            header.extraFieldLength);
213                        if (read != header.extraFieldLength)
214                            MORDOR_THROW_EXCEPTION(UnexpectedEofException());
215                        const unsigned char *extra =
216                            (const unsigned char *)extraFields.c_str();
217                        const unsigned char *end = extra +
218                            header.extraFieldLength;
219                        while (true) {
220                            if (extra + 2 >= end)
221                                break;
222                            unsigned short type =
223                                *(const unsigned short *)extra;
224                            extra += 2;
225                            if (extra + 2 >= end)
226                                break;
227                            unsigned short length =
228                                *(const unsigned short *)extra;
229                            if (extra + length >= end)
230                                break;
231                            if (type == 0x0001u) {
232                                unsigned short expectedLength = 0;
233                                if (header.uncompressedSize == 0xffffffffu)
234                                    expectedLength += 8;
235                                if (header.compressedSize == 0xffffffffu)
236                                    expectedLength += 8;
237                                if (header.localHeaderOffset == 0xffffffffu)
238                                    expectedLength += 8;
239                                if (header.diskNumberStart == 0xffffu)
240                                    expectedLength += 4;
241                                if (length != expectedLength) {
242                                    extra += length;
243                                    continue;
244                                }
245                                if (header.uncompressedSize == 0xffffffffu) {
246                                    file.m_size = *(const long long *)extra;
247                                    extra += 8;
248                                }
249                                if (header.compressedSize == 0xffffffffu) {
250                                    file.m_compressedSize =
251                                        *(const long long *)extra;
252                                    extra += 8;
253                                }
254                                if (header.localHeaderOffset == 0xffffffffu)
255                                    file.m_startOffset =
256                                        *(const long long *)extra;
257                            } else {
258                                extra += length;
259                            }
260                        }
261                    }
262                    m_centralDirectory.insert(std::make_pair(file.m_filename,
263                        file));
264                }
265
266                // Return stream pointer to 0 for getNextFile to work properly
267                m_stream->seek(0);
268            }
269            break;
270        case WRITE:
271            MORDOR_ASSERT(m_stream->supportsWrite());
272            break;
273        default:
274            MORDOR_NOTREACHED();
275    }
276    m_mode = mode;
277}
278
279ZipEntry &
280Zip::addFile()
281{
282    if (m_currentFile) {
283        MORDOR_ASSERT(m_currentFile == &m_scratchFile);
284        m_scratchFile.close();
285        m_centralDirectory.insert(std::make_pair(m_scratchFile.filename(),
286            m_scratchFile));
287        m_scratchFile.clear();
288    } else {
289        m_currentFile = &m_scratchFile;
290    }
291    return m_scratchFile;
292}
293
294void
295Zip::close()
296{
297    MORDOR_ASSERT(m_mode == WRITE);
298    if (m_currentFile) {
299        MORDOR_ASSERT(m_currentFile == &m_scratchFile);
300        m_scratchFile.close();
301        m_centralDirectory.insert(std::make_pair(m_scratchFile.filename(),
302            m_scratchFile));
303        m_scratchFile.clear();
304    }
305    unsigned long long centralDirectorySize = 0;
306    unsigned long long centralDirectoryRecords = 0;
307    long long startOfCentralDirectory = m_stream->tell();
308    for (ZipEntries::const_iterator it = m_centralDirectory.begin();
309        it != m_centralDirectory.end();
310        ++it) {
311        const ZipEntry &file = it->second;
312        FileHeader header;
313        header.signature = 0x02014b50;
314        header.versionMadeBy = 10;
315        header.extractVersion = 10;
316        header.generalPurposeFlags = file.m_flags;
317        header.compressionMethod = file.m_compressionMethod;
318        header.modifiedTime = 0;
319        header.modifiedDate = 0;
320        header.crc32 = file.m_crc;
321        header.compressedSize = (unsigned int)std::min<long long>(0xffffffff,
322            file.m_compressedSize);
323        header.uncompressedSize = (unsigned int)std::min<long long>(0xffffffff,
324            file.m_size);
325        header.fileNameLength = (unsigned short)file.m_filename.size();
326        header.extraFieldLength = 0;
327        header.fileCommentLength = (unsigned short)file.m_comment.size();
328        header.diskNumberStart = 0;
329        header.internalFileAttributes = 0;
330        header.externalFileAttributes = 0;
331        header.localHeaderOffset = (unsigned int)std::min<long long>(0xffffffff,
332            file.m_startOffset);
333        std::string extraFields;
334        if (header.compressedSize == 0xffffffffu ||
335            header.uncompressedSize == 0xffffffffu ||
336            header.localHeaderOffset == 0xffffffffu) {
337            header.extraFieldLength += 4;
338            if (header.uncompressedSize == 0xffffffffu)
339                header.extraFieldLength += 8;
340            if (header.compressedSize == 0xffffffffu)
341                header.extraFieldLength += 8;
342            if (header.localHeaderOffset == 0xffffffffu)
343                header.extraFieldLength += 8;
344            extraFields.resize(header.extraFieldLength);
345            unsigned char *extra = (unsigned char *)&extraFields[0];
346            *(unsigned short *)extra = 0x0001;
347            extra += 2;
348            *(unsigned short *)extra = header.extraFieldLength - 4u;
349            extra += 2;
350            if (header.uncompressedSize == 0xffffffffu) {
351                *(long long *)extra = file.m_size;
352                extra += 8;
353            }
354            if (header.compressedSize == 0xffffffffu) {
355                *(long long *)extra = file.m_compressedSize;
356                extra += 8;
357            }
358            if (header.localHeaderOffset == 0xffffffffu) {
359                *(long long *)extra = file.m_startOffset;
360                extra += 8;
361            }
362        }
363        m_stream->write(&header, sizeof(FileHeader));
364        if (header.fileNameLength)
365            m_stream->write(file.m_filename.c_str(), file.m_filename.size());
366        if (header.extraFieldLength)
367            m_stream->write(extraFields.c_str(), extraFields.size());
368        if (header.fileCommentLength)
369            m_stream->write(file.m_comment.c_str(), file.m_comment.size());
370        ++centralDirectoryRecords;
371        centralDirectorySize += sizeof(FileHeader) + header.fileNameLength +
372            header.extraFieldLength + header.fileCommentLength;
373    }
374    if (centralDirectoryRecords >= 0xffffu ||
375        centralDirectorySize >= 0xffffffffu ||
376        startOfCentralDirectory >= 0xffffffffu) {
377        Zip64EndOfCentralDirectory eocd;
378        eocd.signature = 0x06064b50;
379        eocd.sizeOfEndOfCentralDirectory =
380            sizeof(Zip64EndOfCentralDirectory) - 12;
381        eocd.versionMadeBy = 10;
382        eocd.extractVersion = 10;
383        eocd.numberOfThisDisk = 0;
384        eocd.startOfCentralDirectoryDisk = 0;
385        eocd.centralDirectoryEntriesThisDisk = centralDirectoryRecords;
386        eocd.centralDirectoryEntries = centralDirectoryRecords;
387        eocd.sizeOfCentralDirectory = centralDirectorySize;
388        eocd.startOfCentralDirectoryOffset = startOfCentralDirectory;
389        Zip64EndOfCentralDirectoryLocator locator;
390        locator.signature = 0x07064b50;
391        locator.endOfCentralDirectoryDisk = 0;
392        locator.endOfCentralDirectoryOffset = m_stream->tell();
393        locator.totalDisks = 1;
394        m_stream->write(&eocd, sizeof(Zip64EndOfCentralDirectory));
395        m_stream->write(&locator, sizeof(Zip64EndOfCentralDirectoryLocator));
396    }
397    EndOfCentralDirectory eocd;
398    eocd.signature = 0x06054b50;
399    eocd.numberOfThisDisk = 0;
400    eocd.startOfCentralDirectoryDisk = 0;
401    eocd.centralDirectoryEntriesThisDisk = (unsigned short)
402        std::min<unsigned long long>(0xffffu, centralDirectoryRecords);
403    eocd.centralDirectoryEntries = eocd.centralDirectoryEntriesThisDisk;
404    eocd.sizeOfCentralDirectory = (unsigned int)
405        std::min<unsigned long long>(0xffffffffu, centralDirectorySize);
406    eocd.startOfCentralDirectoryOffset = (unsigned int)
407        std::min<unsigned long long>(0xffffffffu, startOfCentralDirectory);
408    eocd.commentLength = 0;
409    m_stream->write(&eocd, sizeof(EndOfCentralDirectory));
410    m_stream->close();
411}
412
413ZipEntry const *
414Zip::getNextEntry()
415{
416    if (m_currentFile) {
417        if (!m_stream->supportsSeek()) {
418            transferStream(m_currentFile->stream(), NullStream::get());
419        } else {
420            m_stream->seek(m_currentFile->m_startOffset +
421                sizeof(LocalFileHeader) +
422                m_currentFile->m_filename.size() +
423                m_currentFile->m_extraFieldsLength +
424                m_currentFile->m_compressedSize);
425        }
426    }
427    LocalFileHeader header;
428    m_scratchFile.clear();
429    m_scratchFile.m_startOffset = m_stream->tell();
430    m_currentFile = NULL;
431    if (m_stream->read(&header, sizeof(LocalFileHeader)) !=
432        sizeof(LocalFileHeader))
433        return NULL;
434    if (header.signature != 0x04034b50)
435        return NULL;
436    if (header.fileNameLength) {
437        m_scratchFile.m_filename.resize(header.fileNameLength);
438        if (m_stream->read(&m_scratchFile.m_filename[0],
439            header.fileNameLength) != header.fileNameLength)
440            MORDOR_THROW_EXCEPTION(UnexpectedEofException());
441    }
442    m_scratchFile.m_crc = header.crc32;
443    m_scratchFile.m_compressedSize = header.compressedSize;
444    m_scratchFile.m_size = header.decompressedSize;
445    m_scratchFile.m_compressionMethod = header.compressionMethod;
446    m_currentFile = &m_scratchFile;
447    std::pair<ZipEntries::iterator, ZipEntries::iterator> range =
448        m_centralDirectory.equal_range(m_scratchFile.m_filename);
449    while (range.first != range.second) {
450        if (range.first->second.m_startOffset == m_scratchFile.m_startOffset) {
451            m_currentFile = &range.first->second;
452            break;
453        }
454        ++range.first;
455    }
456    if (header.extraFieldLength) {
457        if (m_currentFile != &m_scratchFile) {
458            // We got an entry from the central directory; no need to bother
459            // reading the extra fields from the local header
460            MORDOR_ASSERT(m_stream->supportsSeek());
461            m_stream->seek(header.extraFieldLength, Stream::CURRENT);
462        } else {
463            std::string extraFields;
464            extraFields.resize(header.extraFieldLength);
465            size_t read = m_stream->read(&extraFields[0],
466                header.extraFieldLength);
467            if (read != header.extraFieldLength)
468                MORDOR_THROW_EXCEPTION(UnexpectedEofException());
469            const unsigned char *extra =
470                (const unsigned char *)extraFields.c_str();
471            const unsigned char *end = extra + header.extraFieldLength;
472            while (true) {
473                if (extra + 2 >= end)
474                    break;
475                unsigned short type = *(const unsigned short *)extra;
476                extra += 2;
477                if (extra + 2 >= end)
478                    break;
479                unsigned short length = *(const unsigned short *)extra;
480                if (extra + length >= end)
481                    break;
482                if (type == 0x0001u) {
483                    unsigned short expectedLength = 0;
484                    if (header.decompressedSize == 0xffffffffu)
485                        expectedLength += 8;
486                    if (header.compressedSize == 0xffffffffu)
487                        expectedLength += 8;
488                    if (length != expectedLength) {
489                        extra += length;
490                        continue;
491                    }
492                    if (header.decompressedSize == 0xffffffffu) {
493                        m_scratchFile.m_size = *(const long long *)extra;
494                        extra += 8;
495                    }
496                    if (header.compressedSize == 0xffffffffu)
497                        m_scratchFile.m_compressedSize =
498                            *(const long long *)extra;
499                } else {
500                    extra += length;
501                }
502            }
503        }
504    }
505
506    if (!m_deflateStream) {
507        MORDOR_ASSERT(!m_compressedStream);
508        MORDOR_ASSERT(!m_deflateStream);
509        m_compressedStream.reset(
510            new LimitedStream(m_stream, m_currentFile->m_compressedSize,
511                false));
512        m_deflateStream.reset(new DeflateStream(
513            m_compressedStream));
514    } else {
515        MORDOR_ASSERT(m_compressedStream);
516        MORDOR_ASSERT(m_deflateStream);
517        m_compressedStream->reset(m_currentFile->m_compressedSize);
518        m_deflateStream->reset();
519    }
520    switch (header.compressionMethod) {
521        case 0:
522            m_fileStream = m_compressedStream;
523            break;
524        case 8:
525            m_fileStream = m_deflateStream;
526            break;
527        default:
528            MORDOR_THROW_EXCEPTION(
529                UnsupportedCompressionMethodException());
530    }
531    if (header.generalPurposeFlags & 0x0008) {
532        if (!m_notifyStream)
533            m_notifyStream.reset(new NotifyStream(
534                m_fileStream));
535        m_notifyStream->parent(m_fileStream);
536        m_notifyStream->notifyOnEof = boost::bind(&Zip::onFileEOF, this);
537        m_fileStream = m_notifyStream;
538    }
539    return m_currentFile;
540}
541
542const ZipEntries &
543Zip::getAllEntries()
544{
545    MORDOR_ASSERT(m_stream->supportsSeek() && m_stream->supportsSize());
546    return m_centralDirectory;
547}
548
549void
550Zip::onFileEOF()
551{
552    MORDOR_ASSERT(m_currentFile);
553    MORDOR_ASSERT(m_notifyStream);
554    m_notifyStream->notifyOnEof = NULL;
555
556    unsigned char buffer[24];
557    size_t expectedSize = 12;
558    // TODO: if you read the same file more than once, this will break
559    if (m_currentFile->m_compressedSize == 0xffffffffll)
560        expectedSize += 4;
561    if (m_currentFile->m_size == 0xffffffffll)
562        expectedSize += 4;
563    if (m_stream->read(buffer, expectedSize) != expectedSize)
564        MORDOR_THROW_EXCEPTION(UnexpectedEofException());
565    unsigned char *start = buffer;
566    if (*(unsigned int *)buffer == 0x08074b50) {
567        // Had a signature, read an additional four bytes
568        start += 4u;
569        if (m_stream->read(buffer + expectedSize, 4u) != 4u)
570            MORDOR_THROW_EXCEPTION(UnexpectedEofException());
571    }
572    m_currentFile->m_crc = *(unsigned int *)start;
573    start += 4;
574    if (m_currentFile->m_compressedSize == 0xffffffffll) {
575        m_currentFile->m_compressedSize = *(long long *)start;
576        start += 8;
577    } else {
578        m_currentFile->m_compressedSize = *(unsigned int *)start;
579        start += 4;
580    }
581    if (m_currentFile->m_size == 0xffffffffll)
582        m_currentFile->m_size = *(long long *)start;
583    else
584        m_currentFile->m_size = *(unsigned int *)start;
585}
586
587void
588ZipEntry::filename(const std::string &filename)
589{
590    m_filename = filename;
591}
592
593void
594ZipEntry::comment(const std::string &comment)
595{
596    m_comment = comment;
597}
598
599void
600ZipEntry::size(long long size)
601{
602    m_size = size;
603}
604
605void
606ZipEntry::compressionMethod(unsigned short method)
607{
608    m_compressionMethod = method;
609}
610
611Stream::ptr
612ZipEntry::stream()
613{
614    m_startOffset = m_outer->m_stream->tell();
615    commit();
616    if (m_outer->m_compressedStream) {
617        MORDOR_ASSERT(m_outer->m_crcStream);
618        MORDOR_ASSERT(m_outer->m_uncompressedStream);
619        m_outer->m_compressedStream->reset();
620        if (m_outer->m_deflateStream) {
621            m_outer->m_deflateStream->reset();
622        }
623        m_outer->m_crcStream->reset();
624        m_outer->m_uncompressedStream->reset(
625            m_size == -1ll ? 0x7fffffffffffffffll : m_size);
626    } else {
627        MORDOR_ASSERT(!m_outer->m_crcStream);
628        MORDOR_ASSERT(!m_outer->m_uncompressedStream);
629        m_outer->m_compressedStream.reset(
630            new LimitedStream(m_outer->m_stream, 0x7fffffffffffffffll));
631
632        if (m_compressionMethod == 0) { // no compression
633            m_outer->m_crcStream.reset(
634                new CRC32Stream(m_outer->m_compressedStream, CRC32Stream::IEEE, false));
635        } else if (m_compressionMethod == 8) {
636            MORDOR_ASSERT(!m_outer->m_deflateStream);
637            m_outer->m_deflateStream.reset(
638                new DeflateStream(m_outer->m_compressedStream, false));
639            m_outer->m_crcStream.reset(
640                new CRC32Stream(m_outer->m_deflateStream));
641        } else {
642            MORDOR_THROW_EXCEPTION(UnsupportedCompressionMethodException());
643        }
644
645        m_outer->m_uncompressedStream.reset(
646            new LimitedStream(m_outer->m_crcStream,
647                m_size == -1ll ? 0x7fffffffffffffffll : m_size));
648    }
649    return m_outer->m_uncompressedStream;
650}
651
652Stream::ptr
653ZipEntry::stream() const
654{
655    if (m_outer->m_currentFile != this) {
656        MORDOR_ASSERT(m_outer->m_stream->supportsSeek());
657        m_outer->m_stream->seek(m_startOffset);
658        LocalFileHeader header;
659        size_t read = m_outer->m_stream->read(&header, sizeof(LocalFileHeader));
660        if (read < sizeof(LocalFileHeader))
661            MORDOR_THROW_EXCEPTION(UnexpectedEofException());
662        if (header.signature != 0x04034b50)
663            MORDOR_THROW_EXCEPTION(CorruptZipException());
664        if (header.fileNameLength + header.extraFieldLength)
665            m_outer->m_stream->seek(header.fileNameLength +
666                header.extraFieldLength, Stream::CURRENT);
667
668        if (!m_outer->m_deflateStream) {
669            MORDOR_ASSERT(!m_outer->m_compressedStream);
670            MORDOR_ASSERT(!m_outer->m_deflateStream);
671            m_outer->m_compressedStream.reset(
672                new LimitedStream(m_outer->m_stream, m_compressedSize, false));
673            m_outer->m_deflateStream.reset(new DeflateStream(
674                m_outer->m_compressedStream));
675        } else {
676            MORDOR_ASSERT(m_outer->m_compressedStream);
677            MORDOR_ASSERT(m_outer->m_deflateStream);
678            m_outer->m_compressedStream->reset(m_compressedSize);
679            m_outer->m_deflateStream->reset();
680        }
681        switch (header.compressionMethod) {
682            case 0:
683                m_outer->m_fileStream = m_outer->m_compressedStream;
684                break;
685            case 8:
686                m_outer->m_fileStream = m_outer->m_deflateStream;
687                break;
688            default:
689                MORDOR_THROW_EXCEPTION(
690                    UnsupportedCompressionMethodException());
691        }
692        if (header.generalPurposeFlags & 0x0008) {
693            if (!m_outer->m_notifyStream)
694                m_outer->m_notifyStream.reset(new NotifyStream(
695                    m_outer->m_fileStream));
696            m_outer->m_notifyStream->parent(m_outer->m_fileStream);
697            m_outer->m_notifyStream->notifyOnEof = boost::bind(&Zip::onFileEOF,
698                m_outer);
699            m_outer->m_fileStream = m_outer->m_notifyStream;
700        }
701    } else {
702        MORDOR_ASSERT(m_outer->m_fileStream);
703    }
704    return m_outer->m_fileStream;
705}
706
707void
708ZipEntry::commit()
709{
710    if (!m_outer->m_stream->supportsSeek())
711        m_flags = 0x0808;
712    LocalFileHeader header;
713
714    header.signature = 0x04034b50;
715    header.extractVersion = 10;
716    header.generalPurposeFlags = m_flags;
717    header.compressionMethod = m_compressionMethod;
718    header.modifiedTime = 0;
719    header.modifiedDate = 0;
720    header.crc32 = 0;
721    // Enabling Zip64
722    if (m_size == -1ll || m_size >= 0xffffffffll) {
723        header.compressedSize = 0xffffffffu;
724        header.decompressedSize = 0xffffffffu;
725    } else {
726        header.compressedSize = 0;
727        header.decompressedSize = 0;
728    }
729    header.fileNameLength = (unsigned int)m_filename.size();
730    std::string extraFields;
731    if ((m_size == -1ll || m_size >= 0xffffffffll) && !(m_flags & 0x0008)) {
732        extraFields.resize(20);
733        unsigned char *extra = (unsigned char *)&extraFields[0];
734        *(unsigned short *)extra = 0x0001u;
735        extra += 2;
736        *(unsigned short *)extra = 16u;
737        extra += 2;
738    }
739    header.extraFieldLength = m_extraFieldsLength = (unsigned short)extraFields.size();
740    m_outer->m_stream->write(&header, sizeof(LocalFileHeader));
741    m_outer->m_stream->write(m_filename.c_str(), m_filename.size());
742    if (header.extraFieldLength)
743        m_outer->m_stream->write(extraFields.c_str(), extraFields.size());
744}
745
746void
747ZipEntry::close()
748{
749    MORDOR_ASSERT(m_size == -1ll ||
750        m_outer->m_uncompressedStream->tell() == m_size);
751    m_outer->m_uncompressedStream->close();
752    bool zip64 = (m_size == -1ll || m_size >= 0xffffffffll);
753    m_size = m_outer->m_uncompressedStream->tell();
754    if (m_outer->m_stream->supportsSeek()) {
755        m_compressedSize = m_outer->m_compressedStream->tell() - (
756            m_startOffset + sizeof(LocalFileHeader) + m_filename.size() + m_extraFieldsLength);
757    } else {
758        m_compressedSize = m_outer->m_compressedStream->tell();
759    }
760    m_outer->m_crcStream->hash(&m_crc, sizeof(unsigned int));
761    m_crc = byteswapOnLittleEndian(m_crc);
762    if (m_flags & 0x0008) {
763        if (zip64) {
764            DataDescriptor64 dd;
765            dd.signature = 0x08074b50;
766            dd.crc32 = m_crc;
767            dd.compressedSize = m_compressedSize;
768            dd.uncompressedSize = m_size;
769            m_outer->m_stream->write(&dd, sizeof(DataDescriptor64));
770        } else {
771            DataDescriptor dd;
772            dd.signature = 0x08074b50;
773            dd.crc32 = m_crc;
774            dd.compressedSize = (unsigned int)m_compressedSize;
775            dd.uncompressedSize = (unsigned int)m_size;
776            m_outer->m_stream->write(&dd, sizeof(DataDescriptor));
777        }
778    }
779    if (m_outer->m_stream->supportsSeek()) {
780        long long current = m_outer->m_stream->tell();
781        m_outer->m_stream->seek(m_startOffset + 14);
782        m_outer->m_stream->write(&m_crc, 4);
783        unsigned int size = (unsigned int)std::min<long long>(
784            0xffffffffu, m_compressedSize);
785        m_outer->m_stream->write(&size, 4);
786        size = (unsigned int)std::min<long long>(0xffffffffu, m_size);
787        m_outer->m_stream->write(&size, 4);
788        if (zip64 && !(m_flags & 0x0008)) {
789            std::string extraFields;
790            extraFields.resize(20);
791            unsigned char *extra = (unsigned char *)&extraFields[0];
792            *(unsigned short *)extra = 0x0001u;
793            extra += 2;
794            *(unsigned short *)extra = 16u;
795            extra += 2;
796            *(long long *)extra = m_size;
797            extra += 8;
798            *(long long *)extra = m_compressedSize;
799            extra += 8;
800            m_outer->m_stream->seek(4 + m_filename.size(), Stream::CURRENT);
801            m_outer->m_stream->write(extraFields.c_str(), extraFields.size());
802        }
803        m_outer->m_stream->seek(current);
804    }
805}
806
807void
808ZipEntry::clear()
809{
810    m_filename.clear();
811    m_comment.clear();
812    m_size = m_compressedSize = -1ll;
813    m_startOffset = 0;
814    m_crc = 0;
815    m_flags = 0x0800;
816    m_extraFieldsLength = 0;
817}
818
819}