From 3569b73965e647548e0a37ec27dfc274ebfb748b Mon Sep 17 00:00:00 2001 From: wmayer Date: Mon, 5 Sep 2022 18:53:37 +0200 Subject: [PATCH] Base: [skip ci] Add function to read the CD of a zip file from a std::istream --- src/Base/CMakeLists.txt | 2 + src/Base/ZipHeader.cpp | 171 ++++++++++++++++++++++++++++++++++++++++ src/Base/ZipHeader.h | 82 +++++++++++++++++++ 3 files changed, 255 insertions(+) create mode 100644 src/Base/ZipHeader.cpp create mode 100644 src/Base/ZipHeader.h diff --git a/src/Base/CMakeLists.txt b/src/Base/CMakeLists.txt index c51994e433..227d6b6534 100644 --- a/src/Base/CMakeLists.txt +++ b/src/Base/CMakeLists.txt @@ -277,6 +277,7 @@ SET(FreeCADBase_CPP_SRCS ViewProj.cpp Writer.cpp XMLTools.cpp + ZipHeader.cpp ) SET(SWIG_HEADERS @@ -339,6 +340,7 @@ SET(FreeCADBase_HPP_SRCS ViewProj.h Writer.h XMLTools.h + ZipHeader.h ) SET(FreeCADBase_SRCS diff --git a/src/Base/ZipHeader.cpp b/src/Base/ZipHeader.cpp new file mode 100644 index 0000000000..19a8c36339 --- /dev/null +++ b/src/Base/ZipHeader.cpp @@ -0,0 +1,171 @@ +/*************************************************************************** + * Copyright (c) 2022 Werner Mayer * + * * + * This file is part of the FreeCAD CAx development system. * + * * + * This library is free software; you can redistribute it and/or * + * modify it under the terms of the GNU Library General Public * + * License as published by the Free Software Foundation; either * + * version 2 of the License, or (at your option) any later version. * + * * + * This library is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU Library General Public License for more details. * + * * + * You should have received a copy of the GNU Library General Public * + * License along with this library; see the file COPYING.LIB. If not, * + * write to the Free Software Foundation, Inc., 59 Temple Place, * + * Suite 330, Boston, MA 02111-1307, USA * + * * + ***************************************************************************/ + + +#include "PreCompiled.h" + +#include "ZipHeader.h" +#include + +using zipios::ConstEntryPointer; +using zipios::FileCollection; +using zipios::ZipHeader; + + +ZipHeader::ZipHeader(std::istream &inp, int s_off, int e_off) + : _input(inp) + , _vs(s_off, e_off) +{ + init(_input); +} + +/** Create a copy of this instance. */ +FileCollection *ZipHeader::clone() const +{ + return new ZipHeader(*this); +} + +void ZipHeader::close() +{ + _valid = false; +} + +std::istream *ZipHeader::getInputStream(const ConstEntryPointer &entry) +{ + if (!_valid) + throw zipios::InvalidStateException("Attempt to use an invalid FileCollection"); + return getInputStream(entry->getName()); +} + +std::istream *ZipHeader::getInputStream(const std::string &entry_name, MatchPath matchpath) +{ + if (!_valid) + throw zipios::InvalidStateException("Attempt to use an invalid ZipHeader"); + + zipios::ConstEntryPointer ent = getEntry(entry_name, matchpath); + + if (!ent) + return nullptr; + else + return new zipios::ZipInputStream(_input, + static_cast(ent.get())->getLocalHeaderOffset() + _vs.startOffset()); +} + +bool ZipHeader::init(std::istream &_zipfile) +{ + // Check stream error state + if (!_zipfile) { + setError("Error reading from file"); + return false; + } + + _valid = readCentralDirectory(_zipfile); + return _valid; +} + +bool ZipHeader::readCentralDirectory(std::istream &_zipfile) +{ + // Find and read eocd. + if (!readEndOfCentralDirectory(_zipfile)) + throw zipios::FCollException("Unable to find zip structure: End-of-central-directory"); + + // Position read pointer to start of first entry in central dir. + _vs.vseekg(_zipfile, _eocd.offset(), std::ios::beg); + + int entry_num = 0; + // Giving the default argument in the next line to keep Visual C++ quiet + _entries.resize(_eocd.totalCount(), nullptr); + while ((entry_num < _eocd.totalCount())) { + zipios::ZipCDirEntry *ent = new zipios::ZipCDirEntry; + _entries[entry_num] = ent; + _zipfile >> *ent; + if (!_zipfile) { + if (_zipfile.bad()) + throw zipios::IOException("Error reading zip file while reading zip file central directory"); + else if (_zipfile.fail()) + throw zipios::FCollException("Zip file consistency problem. Failure while reading zip file central directory"); + else if (_zipfile.eof()) + throw zipios::IOException("Premature end of file while reading zip file central directory"); + } + ++entry_num; + } + + // Consistency check. eocd should start here + + int pos = _vs.vtellg(_zipfile); + _vs.vseekg(_zipfile, 0, std::ios::end); + int remaining = static_cast(_vs.vtellg(_zipfile)) - pos; + if (remaining != _eocd.eocdOffSetFromEnd()) + throw zipios::FCollException("Zip file consistency problem. Zip file data fields are inconsistent with zip file layout"); + + // Consistency check 2, are local headers consistent with + // cd headers + if (!confirmLocalHeaders(_zipfile)) + throw zipios::FCollException("Zip file consistency problem. Zip file data fields are inconsistent with zip file layout"); + + return true; +} + +bool ZipHeader::readEndOfCentralDirectory(std::istream &_zipfile) +{ + zipios::BackBuffer bb(_zipfile, _vs); + int read_p = -1; + bool found = false; + while (!found) { + if (read_p < 0) { + if (!bb.readChunk(read_p)) { + found = false; + break; + } + } + if (_eocd.read(bb, read_p)) { + found = true; + break; + } + --read_p; + } + + return found; +} + +bool ZipHeader::confirmLocalHeaders(std::istream &_zipfile) +{ + zipios::Entries::const_iterator it; + zipios::ZipCDirEntry *ent; + int inconsistencies = 0; + zipios::ZipLocalEntry zlh; + for (it = _entries.begin(); it != _entries.end(); ++it) { + ent = static_cast((*it).get()); + _vs.vseekg(_zipfile, ent->getLocalHeaderOffset(), std::ios::beg); + _zipfile >> zlh; + if (!_zipfile || zlh != *ent) { + inconsistencies++; + _zipfile.clear(); + } + } + return !inconsistencies; +} + +void ZipHeader::setError(std::string /*error_str*/) +{ + _valid = false; +} diff --git a/src/Base/ZipHeader.h b/src/Base/ZipHeader.h new file mode 100644 index 0000000000..876d519de8 --- /dev/null +++ b/src/Base/ZipHeader.h @@ -0,0 +1,82 @@ +/*************************************************************************** + * Copyright (c) 2022 Werner Mayer * + * * + * This file is part of the FreeCAD CAx development system. * + * * + * This library is free software; you can redistribute it and/or * + * modify it under the terms of the GNU Library General Public * + * License as published by the Free Software Foundation; either * + * version 2 of the License, or (at your option) any later version. * + * * + * This library is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU Library General Public License for more details. * + * * + * You should have received a copy of the GNU Library General Public * + * License along with this library; see the file COPYING.LIB. If not, * + * write to the Free Software Foundation, Inc., 59 Temple Place, * + * Suite 330, Boston, MA 02111-1307, USA * + * * + ***************************************************************************/ + + +#ifndef ZIPIOS_ZIP_HEADER_H +#define ZIPIOS_ZIP_HEADER_H + +#include +#include +#include + +namespace zipios +{ + +/** ZipHeader is a FileCollection, where the files are stored in a .zip file. + * The class is similar to zipios' ZipFile class with the difference that instead of + * a file a std::istream can be passed. + */ +class BaseExport ZipHeader : public FileCollection { +public: + /** Opens the zip file name. If the zip "file" is + embedded in a file that contains other data, e.g. a binary + program, the offset of the zip file start and end must be + specified. + @param inp The input stream of the zip file to open. + @param s_off Offset relative to the start of the file, that + indicates the beginning of the zip file. + @param e_off Offset relative to the end of the file, that + indicates the end of the zip file. The offset is a positive number, + even though the offset is towards the beginning of the file. + @throw FColException Thrown if the specified file name is not a valid zip + archive. + @throw IOException Thrown if an I/O problem is encountered, while the directory + of the specified zip archive is being read. */ + explicit ZipHeader(std::istream &inp, int s_off = 0, int e_off = 0); + + /** Create a copy of this instance. */ + FileCollection *clone() const override; + + ~ZipHeader() override = default; + + void close() override; + + std::istream *getInputStream(const ConstEntryPointer &entry) override; + std::istream *getInputStream(const string &entry_name, MatchPath matchpath = MATCH) override; + +private: + std::istream& _input; + VirtualSeeker _vs; + EndOfCentralDirectory _eocd; + + bool init(std::istream &_zipfile); + bool readCentralDirectory(std::istream &_zipfile); + bool readEndOfCentralDirectory(std::istream &_zipfile); + bool confirmLocalHeaders(std::istream &_zipfile); + void setError(std::string error_str); +}; + +} //namespace zipios + + +#endif // ZIPIOS_ZIP_HEADER_H +