Index: source/lib/file/archive/archive_zip.cpp =================================================================== --- source/lib/file/archive/archive_zip.cpp +++ source/lib/file/archive/archive_zip.cpp @@ -1,4 +1,4 @@ -/* Copyright (C) 2017 Wildfire Games. +/* Copyright (C) 2019 Wildfire Games. * * Permission is hereby granted, free of charge, to any person obtaining * a copy of this software and associated documentation files (the @@ -269,6 +269,11 @@ cd_size = (size_t)read_le32(&m_cd_size); } + off_t getCommentLength() const + { + return static_cast(read_le16(&m_comment_len)); + } + private: u32 m_magic; u16 m_diskNum; @@ -521,8 +526,41 @@ io::Operation op(*file.get(), buf, scanSize, ofs); RETURN_STATUS_IF_ERR(io::Run(op)); + // Scanning for ECDR first assumes no comment exists + // (standard case), so ECDR structure exists right at + // end of file + off_t offsetInBlock = scanSize - sizeof(ECDR); + const ECDR* ecdr = nullptr; + + for (off_t commentSize = 0; (commentSize <= offsetInBlock) && !ecdr; ++commentSize) + { + const u8 *pECDRTest = buf + offsetInBlock - commentSize; + if(*(const u32*)pECDRTest == ecdr_magic) + { + // Signature matches, test whether comment + // fills up the whole space following the + // ECDR + ecdr = (const ECDR*)pECDRTest; + if (commentSize != ecdr->getCommentLength()) + { + // Signature matches but there is some other data between + // header, comment and EOF. There are three possibilities + // for this: + // 1) Header file format and size differ from what we expect + // 2) File has been truncated + // 3) The magic id occurs inside a zip comment + ecdr = nullptr; + } + else + { + // Seems like a valid archive header before an archive-level + // comment + break; + } + } + } + // look for ECDR in buffer - const ECDR* ecdr = (const ECDR*)FindRecord(buf, scanSize, buf, ecdr_magic, sizeof(ECDR)); if(!ecdr) return INFO::CANNOT_HANDLE; @@ -535,16 +573,10 @@ const size_t maxScanSize = 66000u; // see below UniqueRange buf(io::Allocate(maxScanSize)); - // expected case: ECDR at EOF; no file comment - Status ret = ScanForEcdr(file, fileSize, (u8*)buf.get(), sizeof(ECDR), cd_numEntries, cd_ofs, cd_size); - if(ret == INFO::OK) - return INFO::OK; - // worst case: ECDR precedes 64 KiB of file comment - ret = ScanForEcdr(file, fileSize, (u8*)buf.get(), maxScanSize, cd_numEntries, cd_ofs, cd_size); + Status ret = ScanForEcdr(file, fileSize, static_cast(buf.get()), maxScanSize, cd_numEntries, cd_ofs, cd_size); if(ret == INFO::OK) return INFO::OK; - // both ECDR scans failed - this is not a valid Zip file. io::Operation op(*file.get(), buf.get(), sizeof(LFH)); RETURN_STATUS_IF_ERR(io::Run(op)); // the Zip file has an LFH but lacks an ECDR. this can happen if Index: source/lib/file/archive/tests/test_archive_zip.h =================================================================== --- source/lib/file/archive/tests/test_archive_zip.h +++ source/lib/file/archive/tests/test_archive_zip.h @@ -0,0 +1,60 @@ +/* Copyright (C) 2019 Wildfire Games. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY + * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, + * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE + * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +#include + +#include "lib/self_test.h" +#include "lib/file/archive/archive_zip.h" + +class TestArchiveZip : public CxxTest::TestSuite +{ +public: + void test_scan_suspiciousZipFile() + { + resultbuffer = ""; + PIArchiveReader testee = + CreateArchiveReader_Zip( + DataDir() / "mods" / "_test.lib" / "test.zip"); + + TS_ASSERT(nullptr != testee); + + testee->ReadEntries(TestArchiveZip::ArchiveEntryCallback, 0); + + TS_ASSERT_EQUALS("buildzipwithcomment.sh", resultbuffer); + } + +private: + static std::string resultbuffer; + + static void ArchiveEntryCallback( + const VfsPath& path, + const CFileInfo& UNUSED(fileInfo), + PIArchiveFile UNUSED(archiveFile), + uintptr_t UNUSED(cbData)) + { + resultbuffer = path.string8(); + } +}; + +// Implementation of the static buffer used to communicate with ArchiveEntryCallback +std::string TestArchiveZip::resultbuffer; +