diff options
Diffstat (limited to 'shell/source/win32/zipfile/zipfile.cxx')
-rw-r--r-- | shell/source/win32/zipfile/zipfile.cxx | 552 |
1 files changed, 552 insertions, 0 deletions
diff --git a/shell/source/win32/zipfile/zipfile.cxx b/shell/source/win32/zipfile/zipfile.cxx new file mode 100644 index 0000000000..19203e631e --- /dev/null +++ b/shell/source/win32/zipfile/zipfile.cxx @@ -0,0 +1,552 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include "zipexcptn.hxx" +#include <zipfile.hxx> +#include <global.hxx> +#include <types.hxx> +#include <stream_helper.hxx> + +#include <malloc.h> +#include <algorithm> +#include <memory> + +#include <string.h> + +#include <o3tl/safeint.hxx> + +#include <zlib.h> + +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() {} +}; + +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() {} +}; + +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() {} +}; + +#define CDIR_ENTRY_SIG 0x02014b50 +#define LOC_FILE_HEADER_SIG 0x04034b50 +#define CDIR_END_SIG 0x06054b50 + +// This little lot performs in a truly appalling way without +// buffering eg. on an IStream. + +unsigned short readShort(StreamInterface *stream) +{ + if (!stream || stream->stell() == -1) + throw IOException(-1); + unsigned short tmpBuf; + unsigned long numBytesRead = stream->sread( + reinterpret_cast<unsigned char *>( &tmpBuf ), 2); + if (numBytesRead != 2) + throw IOException(-1); + return tmpBuf; +} + +unsigned readInt(StreamInterface *stream) +{ + if (!stream || stream->stell() == -1) + throw IOException(-1); + unsigned tmpBuf; + unsigned long numBytesRead = stream->sread( + reinterpret_cast<unsigned char *>( &tmpBuf ), 4); + if (numBytesRead != 4) + throw IOException(-1); + return tmpBuf; +} + +std::string readString(StreamInterface *stream, unsigned long size) +{ + if (!stream || stream->stell() == -1) + throw IOException(-1); + auto tmp = std::make_unique<unsigned char[]>(size); + unsigned long numBytesRead = stream->sread(tmp.get(), size); + if (numBytesRead != size) + { + throw IOException(-1); + } + + std::string aStr(reinterpret_cast<char *>(tmp.get()), size); + return aStr; +} + +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.assign(readString(stream, end.comment_size)); + } + catch (...) + { + return false; + } + return true; +} + +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); + entry.filename.assign(readString(stream, entry.filename_size)); + entry.extra_field.assign(readString(stream, entry.extra_field_size)); + entry.file_comment.assign(readString(stream, entry.file_comment_size)); + } + catch (...) + { + return false; + } + return true; +} + +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); + header.filename.assign(readString(stream, header.filename_size)); + header.extra_field.assign(readString(stream, header.extra_field_size)); + } + catch (...) + { + return false; + } + return true; +} + +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; +} + +#define BLOCK_SIZE 0x800 + +bool findSignatureAtOffset(StreamInterface *stream, unsigned long nOffset) +{ + // read in reasonably sized chunk, and read more, to get overlapping sigs + unsigned char aBuffer[ BLOCK_SIZE + 4 ]; + + stream->sseek(nOffset, SEEK_SET); + + unsigned long nBytesRead = stream->sread(aBuffer, sizeof(aBuffer)); + + for (long n = nBytesRead - 4; n >= 0; n--) + { + if (aBuffer[n ] == 0x50 && aBuffer[n+1] == 0x4b && + aBuffer[n+2] == 0x05 && aBuffer[n+3] == 0x06) + { // a palpable hit ... + stream->sseek(nOffset + n, SEEK_SET); + return true; + } + } + + return false; +} + +bool findCentralDirectoryEnd(StreamInterface *stream) +{ + if (!stream) + return false; + + stream->sseek(0,SEEK_END); + + long nLength = stream->stell(); + if (nLength == -1) + return false; + + try + { + for (long nOffset = nLength - BLOCK_SIZE - 4; + nOffset > 0; nOffset -= BLOCK_SIZE) + { + if (findSignatureAtOffset(stream, nOffset)) + return true; + } + return findSignatureAtOffset(stream, 0); + } + catch (...) + { + return false; + } +} + +bool isZipStream(StreamInterface *stream) +{ + if (!findCentralDirectoryEnd(stream)) + 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 +{ + +namespace { + +/* for case in-sensitive string comparison */ +struct stricmp +{ + explicit stricmp(const std::string &str) : str_(str) + {} + + bool operator() (const std::string &other) + { + return (0 == _stricmp(str_.c_str(), other.c_str())); + } + + std::string str_; +}; + +} + +} // namespace internal + +/** Checks whether a file is a zip file or not + + @precond The given parameter must be a string with length > 0 + The file must exist + The file must be readable for the current user + + @returns true if the file is a zip file + false if the file is not a zip file + + @throws ParameterException if the given file name is empty + IOException if the specified file doesn't exist + AccessViolationException if read access to the file is denied +*/ +bool ZipFile::IsZipFile(const Filepath_t& /*FileName*/) +{ + return true; +} + +bool ZipFile::IsZipFile(void* /*stream*/) +{ + return true; +} + + +/** Returns whether the version of the specified zip file may be uncompressed with the + currently used zlib version or not + + @precond The given parameter must be a string with length > 0 + The file must exist + The file must be readable for the current user + The file must be a valid zip file + + @returns true if the file may be uncompressed with the currently used zlib + false if the file may not be uncompressed with the currently used zlib + + @throws ParameterException if the given file name is empty + IOException if the specified file doesn't exist or is no zip file + AccessViolationException if read access to the file is denied +*/ +bool ZipFile::IsValidZipFileVersionNumber(const Filepath_t& /*FileName*/) +{ + return true; +} + +bool ZipFile::IsValidZipFileVersionNumber(void* /* stream*/) +{ + return true; +} + + +/** Constructs a zip file from a zip file + + @precond The given parameter must be a string with length > 0 + The file must exist + The file must be readable for the current user + + @throws ParameterException if the given file name is empty + IOException if the specified file doesn't exist or is no valid zip file + AccessViolationException if read access to the file is denied + WrongZipVersionException if the zip file cannot be uncompressed + with the used zlib version +*/ +ZipFile::ZipFile(const Filepath_t &FileName) : + m_pStream(nullptr), + m_bShouldFree(true) +{ + m_pStream = new FileStream(FileName.c_str()); + if (!isZipStream(m_pStream)) + { + delete m_pStream; + m_pStream = nullptr; + } +} + +ZipFile::ZipFile(StreamInterface *stream) : + m_pStream(stream), + m_bShouldFree(false) +{ + if (!isZipStream(stream)) + m_pStream = nullptr; +} + + +/** Destroys a zip file +*/ +ZipFile::~ZipFile() +{ + if (m_pStream && m_bShouldFree) + delete m_pStream; +} + +/** 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) +{ + if (!findCentralDirectoryEnd(m_pStream)) + return; + CentralDirectoryEnd end; + if (!readCentralDirectoryEnd(m_pStream, end)) + return; + m_pStream->sseek(end.cdir_offset, SEEK_SET); + CentralDirectoryEntry entry; + while (m_pStream->stell() != -1 && o3tl::make_unsigned(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(reinterpret_cast<unsigned char *>(ContentBuffer.data()), 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 = inflateInit2(&strm,-MAX_WBITS); + if (ret != Z_OK) + return; + + std::vector<unsigned char> tmpBuffer(entry.compressed_size); + if (entry.compressed_size != m_pStream->sread(tmpBuffer.data(), entry.compressed_size)) + return; + + strm.avail_in = entry.compressed_size; + strm.next_in = reinterpret_cast<Bytef *>(tmpBuffer.data()); + + strm.avail_out = entry.uncompressed_size; + strm.next_out = reinterpret_cast<Bytef *>(ContentBuffer.data()); + 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; + } + (void)inflateEnd(&strm); + } +} + +/** Returns a list with the content names contained within this file + +*/ +ZipFile::DirectoryPtr_t ZipFile::GetDirectory() const +{ + DirectoryPtr_t dir(new Directory_t()); + if (!findCentralDirectoryEnd(m_pStream)) + 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 && o3tl::make_unsigned(m_pStream->stell()) < end.cdir_offset + end.cdir_size) + { + if (!readCentralDirectoryEntry(m_pStream, entry)) + return dir; + if (entry.filename_size) + dir->push_back(entry.filename); + } + return dir; +} + +/** Convenience query function may even realized with + iterating over a ZipFileDirectory returned by + GetDirectory */ +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 + //names must be handled case sensitive + DirectoryPtr_t dir = GetDirectory(); + + return std::any_of(dir->begin(), dir->end(), internal::stricmp(ContentName)); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ |