diff options
Diffstat (limited to 'shell/source/win32/zipfile/zipfile.cxx')
-rw-r--r-- | shell/source/win32/zipfile/zipfile.cxx | 473 |
1 files changed, 393 insertions, 80 deletions
diff --git a/shell/source/win32/zipfile/zipfile.cxx b/shell/source/win32/zipfile/zipfile.cxx index 30c46b05df2b..de99aa671e62 100644 --- a/shell/source/win32/zipfile/zipfile.cxx +++ b/shell/source/win32/zipfile/zipfile.cxx @@ -29,6 +29,8 @@ #include "zipexcptn.hxx" #include "internal/zipfile.hxx" #include "internal/global.hxx" +#include "internal/types.hxx" +#include "internal/stream_helper.hxx" #include <malloc.h> #include <algorithm> @@ -36,19 +38,289 @@ #include <string.h> -namespace internal +namespace +{ + +struct LocalFileHeader +{ + unsigned short min_version; + unsigned short general_flag; + unsigned short compression; + unsigned short lastmod_time; + unsigned short lastmod_date; + unsigned crc32; + unsigned compressed_size; + unsigned uncompressed_size; + unsigned short filename_size; + unsigned short extra_field_size; + std::string filename; + std::string extra_field; + LocalFileHeader() + : min_version(0), general_flag(0), compression(0), lastmod_time(0), lastmod_date(0), + crc32(0), compressed_size(0), uncompressed_size(0), filename_size(0), extra_field_size(0), + filename(), extra_field() {} + ~LocalFileHeader() {} +}; + +struct CentralDirectoryEntry +{ + unsigned short creator_version; + unsigned short min_version; + unsigned short general_flag; + unsigned short compression; + unsigned short lastmod_time; + unsigned short lastmod_date; + unsigned crc32; + unsigned compressed_size; + unsigned uncompressed_size; + unsigned short filename_size; + unsigned short extra_field_size; + unsigned short file_comment_size; + unsigned short disk_num; + unsigned short internal_attr; + unsigned external_attr; + unsigned offset; + std::string filename; + std::string extra_field; + std::string file_comment; + CentralDirectoryEntry() + : creator_version(0), min_version(0), general_flag(0), compression(0), lastmod_time(0), + lastmod_date(0), crc32(0), compressed_size(0), uncompressed_size(0), filename_size(0), + extra_field_size(0), file_comment_size(0), disk_num(0), internal_attr(0), + external_attr(0), offset(0), filename(), extra_field(), file_comment() {} + ~CentralDirectoryEntry() {} +}; + +struct CentralDirectoryEnd +{ + unsigned short disk_num; + unsigned short cdir_disk; + unsigned short disk_entries; + unsigned short cdir_entries; + unsigned cdir_size; + unsigned cdir_offset; + unsigned short comment_size; + std::string comment; + CentralDirectoryEnd() + : disk_num(0), cdir_disk(0), disk_entries(0), cdir_entries(0), + cdir_size(0), cdir_offset(0), comment_size(0), comment() {} + ~CentralDirectoryEnd() {} +}; + +#define CDIR_ENTRY_SIG 0x02014b50 +#define LOC_FILE_HEADER_SIG 0x04034b50 +#define CDIR_END_SIG 0x06054b50 + +static unsigned char readByte(StreamInterface *stream) +{ + if (!stream || stream->stell() == -1) + throw IOException(-1); + unsigned char tmpBuf; + unsigned long numBytesRead = stream->sread(&tmpBuf, 1); + if (numBytesRead != 1) + throw IOException(-1); + return tmpBuf; +} + +static unsigned short readShort(StreamInterface *stream) +{ + unsigned short p0 = (unsigned short)readByte(stream); + unsigned short p1 = (unsigned short)readByte(stream); + return (unsigned short)(p0|(p1<<8)); +} + +static unsigned readInt(StreamInterface *stream) +{ + unsigned p0 = (unsigned)readByte(stream); + unsigned p1 = (unsigned)readByte(stream); + unsigned p2 = (unsigned)readByte(stream); + unsigned p3 = (unsigned)readByte(stream); + return (unsigned)(p0|(p1<<8)|(p2<<16)|(p3<<24)); +} + +static bool readCentralDirectoryEnd(StreamInterface *stream, CentralDirectoryEnd &end) +{ + try + { + unsigned signature = readInt(stream); + if (signature != CDIR_END_SIG) + return false; + + end.disk_num = readShort(stream); + end.cdir_disk = readShort(stream); + end.disk_entries = readShort(stream); + end.cdir_entries = readShort(stream); + end.cdir_size = readInt(stream); + end.cdir_offset = readInt(stream); + end.comment_size = readShort(stream); + end.comment.clear(); + for (unsigned short i = 0; i < end.comment_size; i++) + end.comment.append(1,(char)readByte(stream)); + } + catch (...) + { + return false; + } + return true; +} + +static bool readCentralDirectoryEntry(StreamInterface *stream, CentralDirectoryEntry &entry) +{ + try + { + unsigned signature = readInt(stream); + if (signature != CDIR_ENTRY_SIG) + return false; + + entry.creator_version = readShort(stream); + entry.min_version = readShort(stream); + entry.general_flag = readShort(stream); + entry.compression = readShort(stream); + entry.lastmod_time = readShort(stream); + entry.lastmod_date = readShort(stream); + entry.crc32 = readInt(stream); + entry.compressed_size = readInt(stream); + entry.uncompressed_size = readInt(stream); + entry.filename_size = readShort(stream); + entry.extra_field_size = readShort(stream); + entry.file_comment_size = readShort(stream); + entry.disk_num = readShort(stream); + entry.internal_attr = readShort(stream); + entry.external_attr = readInt(stream); + entry.offset = readInt(stream); + unsigned short i = 0; + entry.filename.clear(); + for (i=0; i < entry.filename_size; i++) + entry.filename.append(1,(char)readByte(stream)); + entry.extra_field.clear(); + for (i=0; i < entry.extra_field_size; i++) + entry.extra_field.append(1,(char)readByte(stream)); + entry.file_comment.clear(); + for (i=0; i < entry.file_comment_size; i++) + entry.file_comment.append(1,(char)readByte(stream)); + } + catch (...) + { + return false; + } + return true; +} + +static bool readLocalFileHeader(StreamInterface *stream, LocalFileHeader &header) +{ + try + { + unsigned signature = readInt(stream); + if (signature != LOC_FILE_HEADER_SIG) + return false; + + header.min_version = readShort(stream); + header.general_flag = readShort(stream); + header.compression = readShort(stream); + header.lastmod_time = readShort(stream); + header.lastmod_date = readShort(stream); + header.crc32 = readInt(stream); + header.compressed_size = readInt(stream); + header.uncompressed_size = readInt(stream); + header.filename_size = readShort(stream); + header.extra_field_size = readShort(stream); + unsigned short i = 0; + header.filename.clear(); + for (i=0; i < header.filename_size; i++) + header.filename.append(1,(char)readByte(stream)); + header.extra_field.clear(); + for (i=0; i < header.extra_field_size; i++) + header.extra_field.append(1,(char)readByte(stream)); + } + catch (...) + { + return false; + } + return true; +} + +static bool areHeadersConsistent(const LocalFileHeader &header, const CentralDirectoryEntry &entry) +{ + if (header.min_version != entry.min_version) + return false; + if (header.general_flag != entry.general_flag) + return false; + if (header.compression != entry.compression) + return false; + if (!(header.general_flag & 0x08)) + { + if (header.crc32 != entry.crc32) + return false; + if (header.compressed_size != entry.compressed_size) + return false; + if (header.uncompressed_size != entry.uncompressed_size) + return false; + } + return true; +} + +static bool findCentralDirectoryEnd(StreamInterface *stream, long &startOffset) { - /* for case in-sensitive string comparison */ - struct stricmp : public std::unary_function<std::string, bool> + stream->sseek(startOffset, SEEK_SET); + try { - stricmp(const std::string& str) : str_(str) - {} + while (stream->stell() != -1) + { + unsigned signature = readInt(stream); + if (signature == CDIR_END_SIG) + { + stream->sseek(-4, SEEK_CUR); + startOffset = stream->stell(); + return true; + } + else + stream->sseek(-3, SEEK_CUR); + } + } + catch (...) + { + return false; + } + return false; +} + +static bool isZipStream(StreamInterface *stream, long &startOffset) +{ + if (!findCentralDirectoryEnd(stream, startOffset)) + return false; + CentralDirectoryEnd end; + if (!readCentralDirectoryEnd(stream, end)) + return false; + stream->sseek(end.cdir_offset, SEEK_SET); + CentralDirectoryEntry entry; + if (!readCentralDirectoryEntry(stream, entry)) + return false; + stream->sseek(entry.offset, SEEK_SET); + LocalFileHeader header; + if (!readLocalFileHeader(stream, header)) + return false; + if (!areHeadersConsistent(header, entry)) + return false; + return true; +} + +} // anonymous namespace + +namespace internal +{ +/* for case in-sensitive string comparison */ +struct stricmp : public std::unary_function<std::string, bool> +{ + stricmp(const std::string &str) : str_(str) + {} - bool operator() (const std::string& other) - { return (0 == _stricmp(str_.c_str(), other.c_str())); } + bool operator() (const std::string &other) + { + return (0 == _stricmp(str_.c_str(), other.c_str())); + } - std::string str_; - }; + std::string str_; +}; } // namespace internal /** Checks whether a file is a zip file or not @@ -113,21 +385,26 @@ bool ZipFile::IsValidZipFileVersionNumber(void* /* stream*/) WrongZipVersionException if the zip file cannot be uncompressed with the used zlib version */ -ZipFile::ZipFile(const std::string& FileName) +ZipFile::ZipFile(const std::string &FileName) : + m_pStream(0), + m_bShouldFree(true), + m_iStartOffset(0) { - m_uzFile = unzOpen(FileName.c_str()); - - if (0 == m_uzFile) - throw IOException(-1); + m_pStream = new FileStream(FileName.c_str()); + if (m_pStream && !isZipStream(m_pStream, m_iStartOffset)) + { + delete m_pStream; + m_pStream = 0; + } } -ZipFile::ZipFile(void* stream, zlib_filefunc_def* fa) +ZipFile::ZipFile(StreamInterface *stream) : + m_pStream(stream), + m_bShouldFree(false), + m_iStartOffset(0) { - fa->opaque = stream; - m_uzFile = unzOpen2((const char *)NULL, fa); - - if (0 == m_uzFile) - throw IOException(-1); + if (!isZipStream(stream, m_iStartOffset)) + m_pStream = 0; } @@ -135,41 +412,84 @@ ZipFile::ZipFile(void* stream, zlib_filefunc_def* fa) */ ZipFile::~ZipFile() { - unzClose(m_uzFile); + if (m_pStream && m_bShouldFree) + delete m_pStream; } +#define CHUNK 16384 + /** Provides an interface to read the uncompressed data of a content of the zip file @precond The specified content must exist in this file ppstm must not be NULL */ void ZipFile::GetUncompressedContent( - const std::string& ContentName, /*inout*/ ZipContentBuffer_t& ContentBuffer) + const std::string &ContentName, /*inout*/ ZipContentBuffer_t &ContentBuffer) { - int rc = unzLocateFile(m_uzFile, ContentName.c_str(), 0); - - if (UNZ_END_OF_LIST_OF_FILE == rc) - throw ZipContentMissException(rc); - - unz_file_info finfo; - unzGetCurrentFileInfo(m_uzFile, &finfo, 0, 0, 0, 0, 0, 0); - - ContentBuffer.resize(finfo.uncompressed_size); - - rc = unzOpenCurrentFile(m_uzFile); - - if (UNZ_OK != rc) - throw ZipException(rc); - - rc = unzReadCurrentFile(m_uzFile, &ContentBuffer[0], finfo.uncompressed_size); - - if (rc < 0) - throw ZipException(rc); - - rc = unzCloseCurrentFile(m_uzFile); - - if (rc < 0) - throw ZipException(rc); + long startOffset = m_iStartOffset; + if (!findCentralDirectoryEnd(m_pStream, startOffset)) + return; + CentralDirectoryEnd end; + if (!readCentralDirectoryEnd(m_pStream, end)) + return; + m_pStream->sseek(end.cdir_offset, SEEK_SET); + CentralDirectoryEntry entry; + while (m_pStream->stell() != -1 && m_pStream->stell() < startOffset && (unsigned long)m_pStream->stell() < end.cdir_offset + end.cdir_size) + { + if (!readCentralDirectoryEntry(m_pStream, entry)) + return; + if (ContentName.length() == entry.filename_size && !_stricmp(entry.filename.c_str(), ContentName.c_str())) + break; + } + if (ContentName.length() != entry.filename_size) + return; + if (_stricmp(entry.filename.c_str(), ContentName.c_str())) + return; + m_pStream->sseek(entry.offset, SEEK_SET); + LocalFileHeader header; + if (!readLocalFileHeader(m_pStream, header)) + return; + if (!areHeadersConsistent(header, entry)) + return; + ContentBuffer.clear(); + ContentBuffer = ZipContentBuffer_t(entry.uncompressed_size); + if (!entry.compression) + m_pStream->sread((unsigned char *)&ContentBuffer[0], entry.uncompressed_size); + else + { + int ret; + z_stream strm; + + /* allocate inflate state */ + strm.zalloc = Z_NULL; + strm.zfree = Z_NULL; + strm.opaque = Z_NULL; + strm.avail_in = 0; + strm.next_in = Z_NULL; + ret = inflateInit(&strm); + if (ret != Z_OK) + return; + + std::vector<unsigned char> tmpBuffer(entry.compressed_size); + if (entry.compressed_size != m_pStream->sread(&tmpBuffer[0], entry.compressed_size)) + return; + + strm.avail_in = entry.compressed_size; + strm.next_in = reinterpret_cast<Bytef *>(&tmpBuffer[0]); + + strm.avail_out = entry.uncompressed_size; + strm.next_out = reinterpret_cast<Bytef *>(&ContentBuffer[0]); + ret = inflate(&strm, Z_FINISH); + switch (ret) + { + case Z_NEED_DICT: + case Z_DATA_ERROR: + case Z_MEM_ERROR: + (void)inflateEnd(&strm); + ContentBuffer.clear(); + return; + } + } } /** Returns a list with the content names contained within this file @@ -178,36 +498,28 @@ void ZipFile::GetUncompressedContent( ZipFile::DirectoryPtr_t ZipFile::GetDirectory() const { DirectoryPtr_t dir(new Directory_t()); - - long lmax = GetFileLongestFileNameLength() + 1; - char* szFileName = reinterpret_cast<char*>(_alloca(lmax)); - - if (!szFileName) - throw ZipException(ERROR_NOT_ENOUGH_MEMORY); - - int rc = unzGoToFirstFile(m_uzFile); - - while (UNZ_OK == rc && UNZ_END_OF_LIST_OF_FILE != rc) + long startOffset = m_iStartOffset; + if (!findCentralDirectoryEnd(m_pStream, startOffset)) + return dir; + CentralDirectoryEnd end; + if (!readCentralDirectoryEnd(m_pStream, end)) + return dir; + m_pStream->sseek(end.cdir_offset, SEEK_SET); + CentralDirectoryEntry entry; + while (m_pStream->stell() != -1 && m_pStream->stell() < startOffset && (unsigned long)m_pStream->stell() < end.cdir_offset + end.cdir_size) { - unz_file_info finfo; - unzGetCurrentFileInfo( - m_uzFile, &finfo, szFileName, lmax, 0, 0, 0, 0); - - dir->push_back(szFileName); - - rc = unzGoToNextFile(m_uzFile); + if (!readCentralDirectoryEntry(m_pStream, entry)) + return dir; + if (entry.filename_size) + dir->push_back(entry.filename); } - - if (UNZ_OK != rc && UNZ_END_OF_LIST_OF_FILE != rc) - throw ZipException(rc); - return dir; } /** Convinience query function may even realized with iterating over a ZipFileDirectory returned by GetDirectory */ -bool ZipFile::HasContent(const std::string& ContentName) const +bool ZipFile::HasContent(const std::string &ContentName) const { //#i34314# we need to compare package content names //case in-sensitive as it is not defined that such @@ -226,20 +538,21 @@ bool ZipFile::HasContent(const std::string& ContentName) const long ZipFile::GetFileLongestFileNameLength() const { long lmax = 0; - unz_file_info finfo; - - int rc = unzGoToFirstFile(m_uzFile); - - while (UNZ_OK == rc && UNZ_END_OF_LIST_OF_FILE != rc) + long startOffset = m_iStartOffset; + if (!findCentralDirectoryEnd(m_pStream, startOffset)) + return lmax; + CentralDirectoryEnd end; + if (!readCentralDirectoryEnd(m_pStream, end)) + return lmax; + m_pStream->sseek(end.cdir_offset, SEEK_SET); + CentralDirectoryEntry entry; + while (m_pStream->stell() != -1 && m_pStream->stell() < startOffset && (unsigned long)m_pStream->stell() < end.cdir_offset + end.cdir_size) { - unzGetCurrentFileInfo(m_uzFile, &finfo, 0, 0, 0, 0, 0, 0); - lmax = std::max<long>(lmax, finfo.size_filename); - rc = unzGoToNextFile(m_uzFile); + if (!readCentralDirectoryEntry(m_pStream, entry)) + return lmax; + if (entry.filename_size > lmax) + lmax = entry.filename_size; } - - if (UNZ_OK != rc && UNZ_END_OF_LIST_OF_FILE != rc) - throw ZipException(rc); - return lmax; } |