LCOV - code coverage report
Current view: top level - source/lib/file/archive - archive_zip.cpp (source / functions) Hit Total Coverage
Test: 0 A.D. test coverage report Lines: 108 300 36.0 %
Date: 2023-01-19 00:18:29 Functions: 23 44 52.3 %

          Line data    Source code
       1             : /* Copyright (C) 2021 Wildfire Games.
       2             :  *
       3             :  * Permission is hereby granted, free of charge, to any person obtaining
       4             :  * a copy of this software and associated documentation files (the
       5             :  * "Software"), to deal in the Software without restriction, including
       6             :  * without limitation the rights to use, copy, modify, merge, publish,
       7             :  * distribute, sublicense, and/or sell copies of the Software, and to
       8             :  * permit persons to whom the Software is furnished to do so, subject to
       9             :  * the following conditions:
      10             :  *
      11             :  * The above copyright notice and this permission notice shall be included
      12             :  * in all copies or substantial portions of the Software.
      13             :  *
      14             :  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
      15             :  * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
      16             :  * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
      17             :  * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
      18             :  * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
      19             :  * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
      20             :  * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
      21             :  */
      22             : 
      23             : /*
      24             :  * archive backend for Zip files.
      25             :  */
      26             : 
      27             : #include "precompiled.h"
      28             : #include "lib/file/archive/archive_zip.h"
      29             : 
      30             : #include <time.h>
      31             : #include <limits>
      32             : 
      33             : #include "lib/utf8.h"
      34             : #include "lib/bits.h"
      35             : #include "lib/byte_order.h"
      36             : #include "lib/allocators/pool.h"
      37             : #include "lib/sysdep/filesystem.h"
      38             : #include "lib/file/archive/archive.h"
      39             : #include "lib/file/archive/codec_zlib.h"
      40             : #include "lib/file/archive/stream.h"
      41             : #include "lib/file/file.h"
      42             : #include "lib/file/io/io.h"
      43             : 
      44             : //-----------------------------------------------------------------------------
      45             : // timestamp conversion: DOS FAT <-> Unix time_t
      46             : //-----------------------------------------------------------------------------
      47             : 
      48           1 : static time_t time_t_from_FAT(u32 fat_timedate)
      49             : {
      50           1 :     const u32 fat_time = bits(fat_timedate, 0, 15);
      51           1 :     const u32 fat_date = bits(fat_timedate, 16, 31);
      52             : 
      53             :     struct tm t;                            // struct tm format:
      54           1 :     t.tm_sec   = bits(fat_time, 0,4) * 2;   // [0,59]
      55           1 :     t.tm_min   = bits(fat_time, 5,10);      // [0,59]
      56           1 :     t.tm_hour  = bits(fat_time, 11,15);     // [0,23]
      57           1 :     t.tm_mday  = bits(fat_date, 0,4);       // [1,31]
      58           1 :     t.tm_mon   = bits(fat_date, 5,8) - 1;   // [0,11]
      59           1 :     t.tm_year  = bits(fat_date, 9,15) + 80; // since 1900
      60           1 :     t.tm_isdst = -1;    // unknown - let libc determine
      61             : 
      62             :     // otherwise: totally bogus, and at the limit of 32-bit time_t
      63           1 :     ENSURE(t.tm_year < 138);
      64             : 
      65           1 :     time_t ret = mktime(&t);
      66           1 :     ENSURE(ret != (time_t)-1);  // mktime shouldn't fail
      67           1 :     return ret;
      68             : }
      69             : 
      70             : 
      71           0 : static u32 FAT_from_time_t(time_t time)
      72             : {
      73             :     // (values are adjusted for DST)
      74           0 :     struct tm* t = localtime(&time);
      75             : 
      76           0 :     const u16 fat_time = u16(
      77           0 :         (t->tm_sec/2) |          // 5
      78           0 :         (u16(t->tm_min) << 5) | // 6
      79           0 :         (u16(t->tm_hour) << 11)    // 5
      80             :         );
      81             : 
      82           0 :     const u16 fat_date = u16(
      83           0 :         (t->tm_mday) |            // 5
      84           0 :         (u16(t->tm_mon+1) << 5) | // 4
      85           0 :         (u16(t->tm_year-80) << 9) // 7
      86             :         );
      87             : 
      88           0 :     u32 fat_timedate = u32_from_u16(fat_date, fat_time);
      89           0 :     return fat_timedate;
      90             : }
      91             : 
      92             : 
      93             : //-----------------------------------------------------------------------------
      94             : // Zip archive definitions
      95             : //-----------------------------------------------------------------------------
      96             : 
      97             : static const u32 cdfh_magic = FOURCC_LE('P','K','\1','\2');
      98             : static const u32  lfh_magic = FOURCC_LE('P','K','\3','\4');
      99             : static const u32 ecdr_magic = FOURCC_LE('P','K','\5','\6');
     100             : 
     101             : enum ZipMethod
     102             : {
     103             :     ZIP_METHOD_NONE    = 0,
     104             :     ZIP_METHOD_DEFLATE = 8
     105             : };
     106             : 
     107             : #pragma pack(push, 1)
     108             : 
     109             : class LFH
     110             : {
     111             : public:
     112           0 :     void Init(const CFileInfo& fileInfo, off_t csize, ZipMethod method, u32 checksum, const Path& pathname)
     113             :     {
     114           0 :         const std::string pathnameUTF8 = utf8_from_wstring(pathname.string());
     115           0 :         const size_t pathnameSize = pathnameUTF8.length();
     116             : 
     117           0 :         m_magic     = lfh_magic;
     118           0 :         m_x1        = to_le16(0);
     119           0 :         m_flags     = to_le16(0);
     120           0 :         m_method    = to_le16(u16_from_larger(method));
     121           0 :         m_fat_mtime = to_le32(FAT_from_time_t(fileInfo.MTime()));
     122           0 :         m_crc       = to_le32(checksum);
     123           0 :         m_csize     = to_le32(u32_from_larger(csize));
     124           0 :         m_usize     = to_le32(u32_from_larger(fileInfo.Size()));
     125           0 :         m_fn_len    = to_le16(u16_from_larger(pathnameSize));
     126           0 :         m_e_len     = to_le16(0);
     127             : 
     128           0 :         memcpy((char*)this + sizeof(LFH), pathnameUTF8.c_str(), pathnameSize);
     129           0 :     }
     130             : 
     131           0 :     size_t Size() const
     132             :     {
     133           0 :         ENSURE(m_magic == lfh_magic);
     134           0 :         size_t size = sizeof(LFH);
     135           0 :         size += read_le16(&m_fn_len);
     136           0 :         size += read_le16(&m_e_len);
     137             :         // note: LFH doesn't have a comment field!
     138           0 :         return size;
     139             :     }
     140             : 
     141             : private:
     142             :     u32 m_magic;
     143             :     u16 m_x1;           // version needed
     144             :     u16 m_flags;
     145             :     u16 m_method;
     146             :     u32 m_fat_mtime;    // last modified time (DOS FAT format)
     147             :     u32 m_crc;
     148             :     u32 m_csize;
     149             :     u32 m_usize;
     150             :     u16 m_fn_len;
     151             :     u16 m_e_len;
     152             : };
     153             : 
     154             : cassert(sizeof(LFH) == 30);
     155             : 
     156             : 
     157             : class CDFH
     158             : {
     159             : public:
     160           0 :     void Init(const CFileInfo& fileInfo, off_t ofs, off_t csize, ZipMethod method, u32 checksum, const Path& pathname, size_t slack)
     161             :     {
     162           0 :         const std::string pathnameUTF8 = utf8_from_wstring(pathname.string());
     163           0 :         const size_t pathnameLength = pathnameUTF8.length();
     164             : 
     165           0 :         m_magic     = cdfh_magic;
     166           0 :         m_x1        = to_le32(0);
     167           0 :         m_flags     = to_le16(0);
     168           0 :         m_method    = to_le16(u16_from_larger(method));
     169           0 :         m_fat_mtime = to_le32(FAT_from_time_t(fileInfo.MTime()));
     170           0 :         m_crc       = to_le32(checksum);
     171           0 :         m_csize     = to_le32(u32_from_larger(csize));
     172           0 :         m_usize     = to_le32(u32_from_larger(fileInfo.Size()));
     173           0 :         m_fn_len    = to_le16(u16_from_larger(pathnameLength));
     174           0 :         m_e_len     = to_le16(0);
     175           0 :         m_c_len     = to_le16(u16_from_larger((size_t)slack));
     176           0 :         m_x2        = to_le32(0);
     177           0 :         m_x3        = to_le32(0);
     178           0 :         m_lfh_ofs   = to_le32(u32_from_larger(ofs));
     179             : 
     180           0 :         memcpy((char*)this + sizeof(CDFH), pathnameUTF8.c_str(), pathnameLength);
     181           0 :     }
     182             : 
     183           1 :     Path Pathname() const
     184             :     {
     185           1 :         const size_t length = (size_t)read_le16(&m_fn_len);
     186           1 :         const char* pathname = (const char*)this + sizeof(CDFH); // not 0-terminated!
     187           1 :         return Path(std::string(pathname, length));
     188             :     }
     189             : 
     190           1 :     off_t HeaderOffset() const
     191             :     {
     192           1 :         return read_le32(&m_lfh_ofs);
     193             :     }
     194             : 
     195           1 :     off_t USize() const
     196             :     {
     197           1 :         return (off_t)read_le32(&m_usize);
     198             :     }
     199             : 
     200           1 :     off_t CSize() const
     201             :     {
     202           1 :         return (off_t)read_le32(&m_csize);
     203             :     }
     204             : 
     205           1 :     ZipMethod Method() const
     206             :     {
     207           1 :         return (ZipMethod)read_le16(&m_method);
     208             :     }
     209             : 
     210           1 :     u32 Checksum() const
     211             :     {
     212           1 :         return read_le32(&m_crc);
     213             :     }
     214             : 
     215           1 :     time_t MTime() const
     216             :     {
     217           1 :         const u32 fat_mtime = read_le32(&m_fat_mtime);
     218           1 :         return time_t_from_FAT(fat_mtime);
     219             :     }
     220             : 
     221           1 :     size_t Size() const
     222             :     {
     223           1 :         size_t size = sizeof(CDFH);
     224           1 :         size += read_le16(&m_fn_len);
     225           1 :         size += read_le16(&m_e_len);
     226           1 :         size += read_le16(&m_c_len);
     227           1 :         return size;
     228             :     }
     229             : 
     230             : private:
     231             :     u32 m_magic;
     232             :     u32 m_x1;           // versions
     233             :     u16 m_flags;
     234             :     u16 m_method;
     235             :     u32 m_fat_mtime;    // last modified time (DOS FAT format)
     236             :     u32 m_crc;
     237             :     u32 m_csize;
     238             :     u32 m_usize;
     239             :     u16 m_fn_len;
     240             :     u16 m_e_len;
     241             :     u16 m_c_len;
     242             :     u32 m_x2;           // spanning
     243             :     u32 m_x3;           // attributes
     244             :     u32 m_lfh_ofs;
     245             : };
     246             : 
     247             : cassert(sizeof(CDFH) == 46);
     248             : 
     249             : 
     250             : class ECDR
     251             : {
     252             : public:
     253           0 :     void Init(size_t cd_numEntries, off_t cd_ofs, size_t cd_size)
     254             :     {
     255           0 :         m_magic         = ecdr_magic;
     256           0 :         m_diskNum       = to_le16(0);
     257           0 :         m_cd_diskNum    = to_le16(0);
     258           0 :         m_cd_numEntriesOnDisk = to_le16(u16_from_larger(cd_numEntries));
     259           0 :         m_cd_numEntries = m_cd_numEntriesOnDisk;
     260           0 :         m_cd_size       = to_le32(u32_from_larger(cd_size));
     261           0 :         m_cd_ofs        = to_le32(u32_from_larger(cd_ofs));
     262           0 :         m_comment_len   = to_le16(0);
     263           0 :     }
     264             : 
     265           1 :     void Decompose(size_t& cd_numEntries, off_t& cd_ofs, size_t& cd_size) const
     266             :     {
     267           1 :         cd_numEntries = (size_t)read_le16(&m_cd_numEntries);
     268           1 :         cd_ofs       = (off_t)read_le32(&m_cd_ofs);
     269           1 :         cd_size      = (size_t)read_le32(&m_cd_size);
     270           1 :     }
     271             : 
     272           1 :     off_t GetCommentLength() const
     273             :     {
     274           1 :         return static_cast<off_t>(read_le16(&m_comment_len));
     275             :     }
     276             : 
     277             : private:
     278             :     u32 m_magic;
     279             :     u16 m_diskNum;
     280             :     u16 m_cd_diskNum;
     281             :     u16 m_cd_numEntriesOnDisk;
     282             :     u16 m_cd_numEntries;
     283             :     u32 m_cd_size;
     284             :     u32 m_cd_ofs;
     285             :     u16 m_comment_len;
     286             : };
     287             : 
     288             : cassert(sizeof(ECDR) == 22);
     289             : 
     290             : #pragma pack(pop)
     291             : 
     292             : 
     293             : //-----------------------------------------------------------------------------
     294             : // ArchiveFile_Zip
     295             : //-----------------------------------------------------------------------------
     296             : 
     297           1 : class ArchiveFile_Zip : public IArchiveFile
     298             : {
     299             : public:
     300           1 :     ArchiveFile_Zip(const PFile& file, off_t ofs, off_t csize, u32 checksum, ZipMethod method)
     301           1 :         : m_file(file), m_ofs(ofs)
     302             :         , m_csize(csize), m_checksum(checksum), m_method((u16)method)
     303           1 :         , m_flags(NeedsFixup)
     304             :     {
     305           1 :     }
     306             : 
     307           0 :     virtual size_t Precedence() const
     308             :     {
     309           0 :         return 2u;
     310             :     }
     311             : 
     312           0 :     virtual wchar_t LocationCode() const
     313             :     {
     314           0 :         return 'A';
     315             :     }
     316             : 
     317           0 :     virtual OsPath Path() const
     318             :     {
     319           0 :         return m_file->Pathname();
     320             :     }
     321             : 
     322           0 :     virtual Status Load(const OsPath& UNUSED(name), const std::shared_ptr<u8>& buf, size_t size) const
     323             :     {
     324           0 :         AdjustOffset();
     325             : 
     326           0 :         PICodec codec;
     327           0 :         switch(m_method)
     328             :         {
     329           0 :         case ZIP_METHOD_NONE:
     330           0 :             codec = CreateCodec_ZLibNone();
     331           0 :             break;
     332           0 :         case ZIP_METHOD_DEFLATE:
     333           0 :             codec = CreateDecompressor_ZLibDeflate();
     334           0 :             break;
     335           0 :         default:
     336           0 :             WARN_RETURN(ERR::ARCHIVE_UNKNOWN_METHOD);
     337             :         }
     338             : 
     339           0 :         Stream stream(codec);
     340           0 :         stream.SetOutputBuffer(buf.get(), size);
     341           0 :         io::Operation op(*m_file.get(), 0, m_csize, m_ofs);
     342           0 :         StreamFeeder streamFeeder(stream);
     343           0 :         RETURN_STATUS_IF_ERR(io::Run(op, io::Parameters(), streamFeeder));
     344           0 :         RETURN_STATUS_IF_ERR(stream.Finish());
     345             : #if CODEC_COMPUTE_CHECKSUM
     346           0 :         ENSURE(m_checksum == stream.Checksum());
     347             : #endif
     348             : 
     349           0 :         return INFO::OK;
     350             :     }
     351             : 
     352             : private:
     353             :     enum Flags
     354             :     {
     355             :         // indicates m_ofs points to a "local file header" instead of
     356             :         // the file data. a fixup routine is called when reading the file;
     357             :         // it skips past the LFH and clears this flag.
     358             :         // this is somewhat of a hack, but vital to archive open performance.
     359             :         // without it, we'd have to scan through the entire archive file,
     360             :         // which can take *seconds*.
     361             :         // (we cannot use the information in CDFH, because its 'extra' field
     362             :         // has been observed to differ from that of the LFH)
     363             :         // since we read the LFH right before the rest of the file, the block
     364             :         // cache will absorb the IO cost.
     365             :         NeedsFixup = 1
     366             :     };
     367             : 
     368             :     struct LFH_Copier
     369             :     {
     370           0 :         LFH_Copier(u8* lfh_dst, size_t lfh_bytes_remaining)
     371           0 :             : lfh_dst(lfh_dst), lfh_bytes_remaining(lfh_bytes_remaining)
     372             :         {
     373           0 :         }
     374             : 
     375             :         // this code grabs an LFH struct from file block(s) that are
     376             :         // passed to the callback. usually, one call copies the whole thing,
     377             :         // but the LFH may straddle a block boundary.
     378             :         //
     379             :         // rationale: this allows using temp buffers for zip_fixup_lfh,
     380             :         // which avoids involving the file buffer manager and thus
     381             :         // avoids cluttering the trace and cache contents.
     382           0 :         Status operator()(const u8* block, size_t size) const
     383             :         {
     384           0 :             ENSURE(size <= lfh_bytes_remaining);
     385           0 :             memcpy(lfh_dst, block, size);
     386           0 :             lfh_dst += size;
     387           0 :             lfh_bytes_remaining -= size;
     388             : 
     389           0 :             return INFO::OK;
     390             :         }
     391             : 
     392             :         mutable u8* lfh_dst;
     393             :         mutable size_t lfh_bytes_remaining;
     394             :     };
     395             : 
     396             :     /**
     397             :      * fix up m_ofs (adjust it to point to cdata instead of the LFH).
     398             :      *
     399             :      * note: we cannot use CDFH filename and extra field lengths to skip
     400             :      * past LFH since that may not mirror CDFH (has happened).
     401             :      *
     402             :      * this is called at file-open time instead of while mounting to
     403             :      * reduce seeks: since reading the file will typically follow, the
     404             :      * block cache entirely absorbs the IO cost.
     405             :      **/
     406           0 :     void AdjustOffset() const
     407             :     {
     408           0 :         if(!(m_flags & NeedsFixup))
     409           0 :             return;
     410           0 :         m_flags &= ~NeedsFixup;
     411             : 
     412             :         // performance note: this ends up reading one file block, which is
     413             :         // only in the block cache if the file starts in the same block as a
     414             :         // previously read file (i.e. both are small).
     415             :         LFH lfh;
     416           0 :         io::Operation op(*m_file.get(), 0, sizeof(LFH), m_ofs);
     417           0 :         if(io::Run(op, io::Parameters(), LFH_Copier((u8*)&lfh, sizeof(LFH))) == INFO::OK)
     418           0 :             m_ofs += (off_t)lfh.Size();
     419             :     }
     420             : 
     421             :     PFile m_file;
     422             : 
     423             :     // all relevant LFH/CDFH fields not covered by CFileInfo
     424             :     mutable off_t m_ofs;
     425             :     off_t m_csize;
     426             :     u32 m_checksum;
     427             :     u16 m_method;
     428             :     mutable u16 m_flags;
     429             : };
     430             : 
     431             : 
     432             : //-----------------------------------------------------------------------------
     433             : // ArchiveReader_Zip
     434             : //-----------------------------------------------------------------------------
     435             : 
     436           2 : class ArchiveReader_Zip : public IArchiveReader
     437             : {
     438             : public:
     439           1 :     ArchiveReader_Zip(const OsPath& pathname)
     440           1 :         : m_file(new File(pathname, O_RDONLY))
     441             :     {
     442           2 :         CFileInfo fileInfo;
     443           1 :         GetFileInfo(pathname, &fileInfo);
     444           1 :         m_fileSize = fileInfo.Size();
     445           1 :         const size_t minFileSize = sizeof(LFH)+sizeof(CDFH)+sizeof(ECDR);
     446           1 :         ENSURE(m_fileSize >= off_t(minFileSize));
     447           1 :     }
     448             : 
     449           1 :     virtual Status ReadEntries(ArchiveEntryCallback cb, uintptr_t cbData)
     450             :     {
     451             :         // locate and read Central Directory
     452           1 :         off_t cd_ofs = 0;
     453           1 :         size_t cd_numEntries = 0;
     454           1 :         size_t cd_size = 0;
     455           1 :         RETURN_STATUS_IF_ERR(LocateCentralDirectory(m_file, m_fileSize, cd_ofs, cd_numEntries, cd_size));
     456           2 :         io::BufferPtr buf(io::Allocate(cd_size));
     457             : 
     458           1 :         io::Operation op(*m_file.get(), buf.get(), cd_size, cd_ofs);
     459           1 :         RETURN_STATUS_IF_ERR(io::Run(op));
     460             : 
     461             :         // iterate over Central Directory
     462           1 :         const u8* pos = buf.get();
     463           2 :         for(size_t i = 0; i < cd_numEntries; i++)
     464             :         {
     465             :             // scan for next CDFH
     466           1 :             CDFH* cdfh = (CDFH*)FindRecord(buf.get(), cd_size, pos, cdfh_magic, sizeof(CDFH));
     467           1 :             if(!cdfh)
     468           0 :                 WARN_RETURN(ERR::CORRUPTED);
     469             : 
     470           2 :             const Path relativePathname(cdfh->Pathname());
     471           1 :             if(!relativePathname.IsDirectory())
     472             :             {
     473           2 :                 const OsPath name = relativePathname.Filename();
     474           2 :                 CFileInfo fileInfo(name, cdfh->USize(), cdfh->MTime());
     475           2 :                 std::shared_ptr<ArchiveFile_Zip> archiveFile = std::make_shared<ArchiveFile_Zip>(m_file, cdfh->HeaderOffset(), cdfh->CSize(), cdfh->Checksum(), cdfh->Method());
     476           1 :                 cb(relativePathname, fileInfo, archiveFile, cbData);
     477             :             }
     478             : 
     479           1 :             pos += cdfh->Size();
     480             :         }
     481             : 
     482           1 :         return INFO::OK;
     483             :     }
     484             : 
     485             : private:
     486             :     /**
     487             :      * Scan buffer for a Zip file record.
     488             :      *
     489             :      * @param buf
     490             :      * @param size
     491             :      * @param start position within buffer
     492             :      * @param magic signature of record
     493             :      * @param recordSize size of record (including signature)
     494             :      * @return pointer to record within buffer or 0 if not found.
     495             :      **/
     496           1 :     static const u8* FindRecord(const u8* buf, size_t size, const u8* start, u32 magic, size_t recordSize)
     497             :     {
     498             :         // (don't use <start> as the counter - otherwise we can't tell if
     499             :         // scanning within the buffer was necessary.)
     500           1 :         for(const u8* p = start; p <= buf+size-recordSize; p++)
     501             :         {
     502             :             // found it
     503           1 :             if(*(u32*)p == magic)
     504             :             {
     505           1 :                 ENSURE(p == start); // otherwise, the archive is a bit broken
     506           1 :                 return p;
     507             :             }
     508             :         }
     509             : 
     510             :         // passed EOF, didn't find it.
     511             :         // note: do not warn - this happens in the initial ECDR search at
     512             :         // EOF if the archive contains a comment field.
     513           0 :         return 0;
     514             :     }
     515             : 
     516             :     // search for ECDR in the last <maxScanSize> bytes of the file.
     517             :     // if found, fill <dst_ecdr> with a copy of the (little-endian) ECDR and
     518             :     // return INFO::OK, otherwise IO error or ERR::CORRUPTED.
     519           1 :     static Status ScanForEcdr(const PFile& file, off_t fileSize, u8* buf, size_t maxScanSize, size_t& cd_numEntries, off_t& cd_ofs, size_t& cd_size)
     520             :     {
     521             :         // don't scan more than the entire file
     522           1 :         const size_t scanSize = std::min(maxScanSize, size_t(fileSize));
     523             : 
     524             :         // read desired chunk of file into memory
     525           1 :         const off_t ofs = fileSize - off_t(scanSize);
     526           1 :         io::Operation op(*file.get(), buf, scanSize, ofs);
     527           1 :         RETURN_STATUS_IF_ERR(io::Run(op));
     528             : 
     529             :         // Scanning for ECDR first assumes no comment exists
     530             :         // (standard case), so ECDR structure exists right at
     531             :         // end of file
     532           1 :         off_t offsetInBlock = scanSize - sizeof(ECDR);
     533           1 :         const ECDR* ecdr = nullptr;
     534             : 
     535          63 :         for (off_t commentSize = 0; commentSize <= offsetInBlock && !ecdr; ++commentSize)
     536             :         {
     537          63 :             const u8 *pECDRTest = buf + offsetInBlock - commentSize;
     538          63 :             if (*reinterpret_cast<const u32*>(pECDRTest) == ecdr_magic)
     539             :             {
     540             :                 // Signature matches, test whether comment
     541             :                 // fills up the whole space following the
     542             :                 // ECDR
     543           1 :                 ecdr = reinterpret_cast<const ECDR*>(pECDRTest);
     544           1 :                 if (commentSize != ecdr->GetCommentLength())
     545             :                 {
     546             :                     // Signature matches but there is some other data between
     547             :                     // header, comment and EOF. There are three possibilities
     548             :                     // for this:
     549             :                     // 1) Header file format and size differ from what we expect
     550             :                     // 2) File has been truncated
     551             :                     // 3) The magic id occurs inside a zip comment
     552           0 :                     ecdr = nullptr;
     553             :                 }
     554             :                 else
     555             :                 {
     556             :                     // Seems like a valid archive header before an archive-level
     557             :                     // comment
     558           1 :                     break;
     559             :                 }
     560             :             }
     561             :         }
     562             : 
     563           1 :         if(!ecdr)
     564           0 :             return INFO::CANNOT_HANDLE;
     565             : 
     566           1 :         ecdr->Decompose(cd_numEntries, cd_ofs, cd_size);
     567           1 :         return INFO::OK;
     568             :     }
     569             : 
     570           1 :     static Status LocateCentralDirectory(const PFile& file, off_t fileSize, off_t& cd_ofs, size_t& cd_numEntries, size_t& cd_size)
     571             :     {
     572           1 :         const size_t maxScanSize = 66000u;  // see below
     573           2 :         io::BufferPtr buf(io::Allocate(maxScanSize));
     574           1 :         Status ret = ScanForEcdr(file, fileSize, static_cast<u8*>(buf.get()), maxScanSize, cd_numEntries, cd_ofs, cd_size);
     575           1 :         if(ret == INFO::OK)
     576           1 :             return INFO::OK;
     577             : 
     578           0 :         io::Operation op(*file.get(), buf.get(), sizeof(LFH));
     579           0 :         RETURN_STATUS_IF_ERR(io::Run(op));
     580             :         // the Zip file has an LFH but lacks an ECDR. this can happen if
     581             :         // the user hard-exits while an archive is being written.
     582             :         // notes:
     583             :         // - return ERR::CORRUPTED so VFS will not include this file.
     584             :         // - we could work around this by scanning all LFHs, but won't bother
     585             :         //   because it'd be slow.
     586             :         // - do not warn - the corrupt archive will be deleted on next
     587             :         //   successful archive builder run anyway.
     588           0 :         if(FindRecord(buf.get(), sizeof(LFH), buf.get(), lfh_magic, sizeof(LFH)))
     589           0 :             return ERR::CORRUPTED;  // NOWARN
     590             :         // totally bogus
     591             :         else
     592           0 :             WARN_RETURN(ERR::ARCHIVE_UNKNOWN_FORMAT);
     593             :     }
     594             : 
     595             :     PFile m_file;
     596             :     off_t m_fileSize;
     597             : };
     598             : 
     599           1 : PIArchiveReader CreateArchiveReader_Zip(const OsPath& archivePathname)
     600             : {
     601             :     try
     602             :     {
     603           1 :         return PIArchiveReader(new ArchiveReader_Zip(archivePathname));
     604             :     }
     605           0 :     catch(Status)
     606             :     {
     607           0 :         return PIArchiveReader();
     608             :     }
     609             : }
     610             : 
     611             : 
     612             : //-----------------------------------------------------------------------------
     613             : // ArchiveWriter_Zip
     614             : //-----------------------------------------------------------------------------
     615             : 
     616             : class ArchiveWriter_Zip : public IArchiveWriter
     617             : {
     618             : public:
     619           0 :     ArchiveWriter_Zip(const OsPath& archivePathname, bool noDeflate)
     620           0 :         : m_file(new File(archivePathname, O_WRONLY)), m_fileSize(0)
     621           0 :         , m_numEntries(0), m_noDeflate(noDeflate)
     622             :     {
     623           0 :         THROW_STATUS_IF_ERR(pool_create(&m_cdfhPool, 10*MiB, 0));
     624           0 :     }
     625             : 
     626           0 :     ~ArchiveWriter_Zip()
     627           0 :     {
     628             :         // append an ECDR to the CDFH list (this allows us to
     629             :         // write out both to the archive file in one burst)
     630           0 :         const size_t cd_size = m_cdfhPool.da.pos;
     631           0 :         ECDR* ecdr = (ECDR*)pool_alloc(&m_cdfhPool, sizeof(ECDR));
     632           0 :         if(!ecdr)
     633           0 :             std::terminate();
     634           0 :         const off_t cd_ofs = m_fileSize;
     635           0 :         ecdr->Init(m_numEntries, cd_ofs, cd_size);
     636             : 
     637           0 :         if(write(m_file->Descriptor(), m_cdfhPool.da.base, cd_size+sizeof(ECDR)) < 0)
     638           0 :             DEBUG_WARN_ERR(ERR::IO);    // no way to return error code
     639             : 
     640           0 :         (void)pool_destroy(&m_cdfhPool);
     641           0 :     }
     642             : 
     643           0 :     Status AddFile(const OsPath& pathname, const OsPath& pathnameInArchive)
     644             :     {
     645           0 :         CFileInfo fileInfo;
     646           0 :         RETURN_STATUS_IF_ERR(GetFileInfo(pathname, &fileInfo));
     647             : 
     648           0 :         PFile file(new File);
     649           0 :         RETURN_STATUS_IF_ERR(file->Open(pathname, O_RDONLY));
     650             : 
     651           0 :         return AddFileOrMemory(fileInfo, pathnameInArchive, file, NULL);
     652             :     }
     653             : 
     654           0 :     Status AddMemory(const u8* data, size_t size, time_t mtime, const OsPath& pathnameInArchive)
     655             :     {
     656           0 :         CFileInfo fileInfo(pathnameInArchive, size, mtime);
     657             : 
     658           0 :         return AddFileOrMemory(fileInfo, pathnameInArchive, PFile(), data);
     659             :     }
     660             : 
     661           0 :     Status AddFileOrMemory(const CFileInfo& fileInfo, const OsPath& pathnameInArchive, const PFile& file, const u8* data)
     662             :     {
     663           0 :         ENSURE((file && !data) || (data && !file));
     664             : 
     665           0 :         const off_t usize = fileInfo.Size();
     666             :         // skip 0-length files.
     667             :         // rationale: zip.cpp needs to determine whether a CDFH entry is
     668             :         // a file or directory (the latter are written by some programs but
     669             :         // not needed - they'd only pollute the file table).
     670             :         // it looks like checking for usize=csize=0 is the safest way -
     671             :         // relying on file attributes (which are system-dependent!) is
     672             :         // even less safe.
     673             :         // we thus skip 0-length files to avoid confusing them with directories.
     674           0 :         if(!usize)
     675           0 :             return INFO::SKIPPED;
     676             : 
     677           0 :         const size_t pathnameLength = pathnameInArchive.string().length();
     678             : 
     679             :         // choose method and the corresponding codec
     680             :         ZipMethod method;
     681           0 :         PICodec codec;
     682           0 :         if(m_noDeflate || IsFileTypeIncompressible(pathnameInArchive))
     683             :         {
     684           0 :             method = ZIP_METHOD_NONE;
     685           0 :             codec = CreateCodec_ZLibNone();
     686             :         }
     687             :         else
     688             :         {
     689           0 :             method = ZIP_METHOD_DEFLATE;
     690           0 :             codec = CreateCompressor_ZLibDeflate();
     691             :         }
     692             : 
     693             :         // allocate memory
     694           0 :         const size_t csizeMax = codec->MaxOutputSize(size_t(usize));
     695           0 :         io::BufferPtr buf(io::Allocate(sizeof(LFH) + pathnameLength + csizeMax));
     696             : 
     697             :         // read and compress file contents
     698             :         size_t csize; u32 checksum;
     699             :         {
     700           0 :             u8* cdata = buf.get() + sizeof(LFH) + pathnameLength;
     701           0 :             Stream stream(codec);
     702           0 :             stream.SetOutputBuffer(cdata, csizeMax);
     703           0 :             StreamFeeder streamFeeder(stream);
     704           0 :             if(file)
     705             :             {
     706           0 :                 io::Operation op(*file.get(), 0, usize);
     707           0 :                 RETURN_STATUS_IF_ERR(io::Run(op, io::Parameters(), streamFeeder));
     708             :             }
     709             :             else
     710             :             {
     711           0 :                 RETURN_STATUS_IF_ERR(streamFeeder(data, usize));
     712             :             }
     713           0 :             RETURN_STATUS_IF_ERR(stream.Finish());
     714           0 :             csize = stream.OutSize();
     715           0 :             checksum = stream.Checksum();
     716             :         }
     717             : 
     718             :         // build LFH
     719             :         {
     720           0 :             LFH* lfh = reinterpret_cast<LFH*>(buf.get());
     721           0 :             lfh->Init(fileInfo, (off_t)csize, method, checksum, pathnameInArchive);
     722             :         }
     723             : 
     724             :         // append a CDFH to the central directory (in memory)
     725           0 :         const off_t ofs = m_fileSize;
     726           0 :         const size_t prev_pos = m_cdfhPool.da.pos;  // (required to determine padding size)
     727           0 :         const size_t cdfhSize = sizeof(CDFH) + pathnameLength;
     728           0 :         CDFH* cdfh = (CDFH*)pool_alloc(&m_cdfhPool, cdfhSize);
     729           0 :         if(!cdfh)
     730           0 :             WARN_RETURN(ERR::NO_MEM);
     731           0 :         const size_t slack = m_cdfhPool.da.pos - prev_pos - cdfhSize;
     732           0 :         cdfh->Init(fileInfo, ofs, (off_t)csize, method, checksum, pathnameInArchive, slack);
     733           0 :         m_numEntries++;
     734             : 
     735             :         // write LFH, pathname and cdata to file
     736           0 :         const size_t packageSize = sizeof(LFH) + pathnameLength + csize;
     737           0 :         if(write(m_file->Descriptor(), buf.get(), packageSize) < 0)
     738           0 :             WARN_RETURN(ERR::IO);
     739           0 :         m_fileSize += (off_t)packageSize;
     740             : 
     741           0 :         return INFO::OK;
     742             :     }
     743             : 
     744             : private:
     745           0 :     static bool IsFileTypeIncompressible(const OsPath& pathname)
     746             :     {
     747           0 :         const OsPath extension = pathname.Extension();
     748             : 
     749             :         // file extensions that we don't want to compress
     750             :         static const wchar_t* incompressibleExtensions[] =
     751             :         {
     752             :             L".zip", L".rar",
     753             :             L".jpg", L".jpeg", L".png",
     754             :             L".ogg", L".mp3"
     755             :         };
     756             : 
     757           0 :         for(size_t i = 0; i < ARRAY_SIZE(incompressibleExtensions); i++)
     758             :         {
     759           0 :             if(extension == incompressibleExtensions[i])
     760           0 :                 return true;
     761             :         }
     762             : 
     763           0 :         return false;
     764             :     }
     765             : 
     766             :     PFile m_file;
     767             :     off_t m_fileSize;
     768             : 
     769             :     Pool m_cdfhPool;
     770             :     size_t m_numEntries;
     771             : 
     772             :     bool m_noDeflate;
     773             : };
     774             : 
     775           0 : PIArchiveWriter CreateArchiveWriter_Zip(const OsPath& archivePathname, bool noDeflate)
     776             : {
     777             :     try
     778             :     {
     779           0 :         return PIArchiveWriter(new ArchiveWriter_Zip(archivePathname, noDeflate));
     780             :     }
     781           0 :     catch(Status)
     782             :     {
     783           0 :         return PIArchiveWriter();
     784             :     }
     785           3 : }

Generated by: LCOV version 1.13