diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-15 05:54:39 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-15 05:54:39 +0000 |
commit | 267c6f2ac71f92999e969232431ba04678e7437e (patch) | |
tree | 358c9467650e1d0a1d7227a21dac2e3d08b622b2 /writerperfect/source/common/WPXSvInputStream.cxx | |
parent | Initial commit. (diff) | |
download | libreoffice-267c6f2ac71f92999e969232431ba04678e7437e.tar.xz libreoffice-267c6f2ac71f92999e969232431ba04678e7437e.zip |
Adding upstream version 4:24.2.0.upstream/4%24.2.0
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to '')
-rw-r--r-- | writerperfect/source/common/WPXSvInputStream.cxx | 785 |
1 files changed, 785 insertions, 0 deletions
diff --git a/writerperfect/source/common/WPXSvInputStream.cxx b/writerperfect/source/common/WPXSvInputStream.cxx new file mode 100644 index 0000000000..dee82500de --- /dev/null +++ b/writerperfect/source/common/WPXSvInputStream.cxx @@ -0,0 +1,785 @@ +/* -*- 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/. + */ + +#include <WPXSvInputStream.hxx> + +#include <com/sun/star/packages/zip/XZipFileAccess2.hpp> +#include <com/sun/star/uno/XComponentContext.hpp> + +#include <comphelper/processfactory.hxx> +#include <comphelper/seekableinput.hxx> +#include <o3tl/safeint.hxx> +#include <rtl/string.hxx> +#include <sal/log.hxx> + +#include <unotools/streamwrap.hxx> +#include <unotools/ucbstreamhelper.hxx> + +#include <climits> +#include <limits> +#include <memory> +#include <string_view> +#include <unordered_map> +#include <utility> +#include <vector> + +namespace writerperfect +{ +using namespace ::com::sun::star::uno; +using namespace ::com::sun::star::io; + +namespace container = com::sun::star::container; +namespace packages = com::sun::star::packages; + +namespace +{ +class PositionHolder +{ +public: + explicit PositionHolder(const Reference<XSeekable>& rxSeekable); + ~PositionHolder(); + PositionHolder(const PositionHolder&) = delete; + PositionHolder& operator=(const PositionHolder&) = delete; + +private: + const Reference<XSeekable> mxSeekable; + const sal_uInt64 mnPosition; +}; +} // anonymous namespace + +PositionHolder::PositionHolder(const Reference<XSeekable>& rxSeekable) + : mxSeekable(rxSeekable) + , mnPosition(rxSeekable->getPosition()) +{ +} + +PositionHolder::~PositionHolder() +{ + try + { + mxSeekable->seek(mnPosition); + } + catch (...) + { + } +} + +static OUString lcl_normalizeSubStreamPath(const OUString& rPath) +{ + // accept paths which begin by '/' + // TODO: maybe this should do a full normalization + if (rPath.startsWith("/") && rPath.getLength() >= 2) + return rPath.copy(1); + return rPath; +} + +static OUString concatPath(std::u16string_view lhs, const OUString& rhs) +{ + if (lhs.empty()) + return rhs; + return OUString::Concat(lhs) + "/" + rhs; +} + +namespace +{ +struct OLEStreamData +{ + OLEStreamData(OString aName, OString rvngName) + : name(std::move(aName)) + , RVNGname(std::move(rvngName)) + { + } + + tools::SvRef<SotStorageStream> stream; + + /** Name of the stream. + * + * This is not @c OUString, because we need to be able to + * produce const char* from it. + */ + OString name; + /** librevenge name of the stream. + * + * This is not @c OUString, because we need to be able to + * produce const char* from it. + */ + OString RVNGname; +}; +} // anonymous namespace + +typedef std::unordered_map<OUString, std::size_t> NameMap_t; +typedef std::unordered_map<OUString, tools::SvRef<SotStorage>> OLEStorageMap_t; + +/** Representation of an OLE2 storage. + * + * This class tries to bring a bit of sanity to use of SotStorage with + * respect to the needs of @c librevenge::RVNGInputStream API. It + * holds all nested storages for the whole lifetime (more precisely, + * since initialization, which is performed by calling @c + * initialize()), thus ensuring that no created stream is destroyed + * just because its parent storage went out of scope. It also holds a + * bidirectional map of stream names to their indexes (index of a + * stream is determined by deep-first traversal), which is also + * populated during initialization (member variables @c maStreams and + * @c maNameMap). + * + * Streams are created on demand (and saved, for the same reason as + * storages). + */ +struct OLEStorageImpl +{ + OLEStorageImpl(); + + void initialize(std::unique_ptr<SvStream> pStream); + + tools::SvRef<SotStorageStream> getStream(const OUString& rPath); + tools::SvRef<SotStorageStream> const& getStream(std::size_t nId); + +private: + void traverse(const tools::SvRef<SotStorage>& rStorage, std::u16string_view rPath); + + tools::SvRef<SotStorageStream> createStream(const OUString& rPath); + +public: + tools::SvRef<SotStorage> mxRootStorage; //< root storage of the OLE2 + OLEStorageMap_t maStorageMap; //< map of all sub storages by name + ::std::vector<OLEStreamData> maStreams; //< list of streams and their names + NameMap_t maNameMap; //< map of stream names to indexes (into @c maStreams) + bool mbInitialized; +}; + +OLEStorageImpl::OLEStorageImpl() + : mbInitialized(false) +{ +} + +void OLEStorageImpl::initialize(std::unique_ptr<SvStream> pStream) +{ + if (!pStream) + return; + + mxRootStorage = new SotStorage(pStream.release(), true); + + traverse(mxRootStorage, u""); + + mbInitialized = true; +} + +tools::SvRef<SotStorageStream> OLEStorageImpl::getStream(const OUString& rPath) +{ + const OUString aPath(lcl_normalizeSubStreamPath(rPath)); + NameMap_t::iterator aIt = maNameMap.find(aPath); + + // For the while don't return stream in this situation. + // Later, given how libcdr's zip stream implementation behaves, + // return the first stream in the storage if there is one. + if (maNameMap.end() == aIt) + return tools::SvRef<SotStorageStream>(); + + if (!maStreams[aIt->second].stream.is()) + maStreams[aIt->second].stream + = createStream(OStringToOUString(maStreams[aIt->second].name, RTL_TEXTENCODING_UTF8)); + + return maStreams[aIt->second].stream; +} + +tools::SvRef<SotStorageStream> const& OLEStorageImpl::getStream(const std::size_t nId) +{ + if (!maStreams[nId].stream.is()) + maStreams[nId].stream + = createStream(OStringToOUString(maStreams[nId].name, RTL_TEXTENCODING_UTF8)); + + return maStreams[nId].stream; +} + +void OLEStorageImpl::traverse(const tools::SvRef<SotStorage>& rStorage, std::u16string_view rPath) +{ + SvStorageInfoList infos; + + rStorage->FillInfoList(&infos); + + for (const auto& info : infos) + { + if (info.IsStream()) + { + OUString baseName = info.GetName(), rvngName = baseName; + // librevenge::RVNGOLEStream ignores the first character when is a control code, so ... + if (!rvngName.isEmpty() && rvngName.toChar() < 32) + rvngName = rvngName.copy(1); + maStreams.emplace_back( + OUStringToOString(concatPath(rPath, baseName), RTL_TEXTENCODING_UTF8), + OUStringToOString(concatPath(rPath, rvngName), RTL_TEXTENCODING_UTF8)); + maNameMap[concatPath(rPath, rvngName)] = maStreams.size() - 1; + } + else if (info.IsStorage()) + { + const OUString aPath = concatPath(rPath, info.GetName()); + tools::SvRef<SotStorage> aStorage + = rStorage->OpenSotStorage(info.GetName(), StreamMode::STD_READ); + maStorageMap[aPath] = aStorage; + + // deep-first traversal + traverse(aStorage, aPath); + } + else + { + SAL_WARN("writerperfect", + "OLEStorageImpl::traverse: invalid storage entry, neither stream nor file"); + } + } +} + +tools::SvRef<SotStorageStream> OLEStorageImpl::createStream(const OUString& rPath) +{ + const sal_Int32 nDelim = rPath.lastIndexOf(u'/'); + + if (-1 == nDelim) + return mxRootStorage->OpenSotStream(rPath, StreamMode::STD_READ); + + const OUString aDir = rPath.copy(0, nDelim); + const OUString aName = rPath.copy(nDelim + 1); + + const OLEStorageMap_t::const_iterator aIt = maStorageMap.find(aDir); + + if (maStorageMap.end() == aIt) + return nullptr; + + return aIt->second->OpenSotStream(aName, StreamMode::STD_READ); +} + +namespace +{ +struct ZipStreamData +{ + explicit ZipStreamData(OString aName); + + css::uno::Reference<css::io::XInputStream> xStream; + + /** Name of the stream. + * + * This is not @c OUString, because we need to be able to + * produce const char* from it. + */ + OString aName; +}; +} // anonymous namespace + +/** Representation of a Zip storage. + * + * This is quite similar to OLEStorageImpl, except that we do not need + * to keep all storages (folders) open. + */ +struct ZipStorageImpl +{ + explicit ZipStorageImpl(const css::uno::Reference<css::container::XNameAccess>& rxContainer); + + /** Initialize for access. + * + * This creates a bidirectional map of stream names to their + * indexes (index of a stream is determined by deep-first + * traversal). + */ + void initialize(); + + Reference<css::io::XInputStream> getStream(const OUString& rPath); + Reference<css::io::XInputStream> const& getStream(std::size_t nId); + +private: + void traverse(const Reference<container::XNameAccess>& rxEnum); + + Reference<XInputStream> createStream(const OUString& rPath); + +public: + Reference<container::XNameAccess> mxContainer; //< root of the Zip + ::std::vector<ZipStreamData> maStreams; //< list of streams and their names + NameMap_t maNameMap; //< map of stream names to indexes (into @c maStreams) + bool mbInitialized; +}; + +ZipStreamData::ZipStreamData(OString _aName) + : aName(std::move(_aName)) +{ +} + +ZipStorageImpl::ZipStorageImpl(const Reference<container::XNameAccess>& rxContainer) + : mxContainer(rxContainer) + , mbInitialized(false) +{ + assert(mxContainer.is()); +} + +void ZipStorageImpl::initialize() +{ + traverse(mxContainer); + + mbInitialized = true; +} + +Reference<XInputStream> ZipStorageImpl::getStream(const OUString& rPath) +{ + const OUString aPath(lcl_normalizeSubStreamPath(rPath)); + NameMap_t::iterator aIt = maNameMap.find(aPath); + + // For the while don't return stream in this situation. + // Later, given how libcdr's zip stream implementation behaves, + // return the first stream in the storage if there is one. + if (maNameMap.end() == aIt) + return Reference<XInputStream>(); + + if (!maStreams[aIt->second].xStream.is()) + maStreams[aIt->second].xStream = createStream(aPath); + + return maStreams[aIt->second].xStream; +} + +Reference<XInputStream> const& ZipStorageImpl::getStream(const std::size_t nId) +{ + if (!maStreams[nId].xStream.is()) + maStreams[nId].xStream + = createStream(OStringToOUString(maStreams[nId].aName, RTL_TEXTENCODING_UTF8)); + + return maStreams[nId].xStream; +} + +void ZipStorageImpl::traverse(const Reference<container::XNameAccess>& rxContainer) +{ + const Sequence<OUString> lNames = rxContainer->getElementNames(); + + maStreams.reserve(lNames.getLength()); + + for (const auto& rName : lNames) + { + if (!rName.endsWith("/")) // skip dirs + { + maStreams.emplace_back(OUStringToOString(rName, RTL_TEXTENCODING_UTF8)); + maNameMap[rName] = maStreams.size() - 1; + } + } +} + +Reference<XInputStream> ZipStorageImpl::createStream(const OUString& rPath) +{ + Reference<XInputStream> xStream; + + try + { + const Reference<XInputStream> xInputStream(mxContainer->getByName(rPath), UNO_QUERY_THROW); + const Reference<XSeekable> xSeekable(xInputStream, UNO_QUERY); + + if (xSeekable.is()) + xStream = xInputStream; + else + xStream.set(new comphelper::OSeekableInputWrapper( + xInputStream, comphelper::getProcessComponentContext())); + } + catch (const Exception&) + { + // nothing needed + } + + return xStream; +} + +WPXSvInputStream::WPXSvInputStream(Reference<XInputStream> const& xStream) + : mxStream(xStream) + , mxSeekable(xStream, UNO_QUERY) + , maData(0) + , mnLength(0) + , mnReadBufferPos(0) + , mbCheckedOLE(false) + , mbCheckedZip(false) +{ + if (!xStream.is() || !mxStream.is()) + mnLength = 0; + else + { + if (!mxSeekable.is()) + mnLength = 0; + else + { + try + { + mnLength = mxSeekable->getLength(); + if (0 < mxSeekable->getPosition()) + mxSeekable->seek(0); + } + catch (...) + { + SAL_WARN("writerperfect", "mnLength = mxSeekable->getLength() threw exception"); + mnLength = 0; + } + } + } +} + +const unsigned char* WPXSvInputStream::read(unsigned long numBytes, unsigned long& numBytesRead) +{ + numBytesRead = 0; + + if (numBytes == 0 || isEnd()) + return nullptr; + + numBytesRead = mxStream->readSomeBytes(maData, numBytes); + if (numBytesRead == 0) + return nullptr; + + return reinterpret_cast<const unsigned char*>(maData.getConstArray()); +} + +tools::Long WPXSvInputStream::tellImpl() +{ + if ((mnLength == 0) || !mxStream.is() || !mxSeekable.is()) + return -1; + else + { + const sal_Int64 tmpPosition = mxSeekable->getPosition(); + if (tmpPosition < 0) + return -1; +#if SAL_TYPES_SIZEOFLONG == 4 + if (tmpPosition > LONG_MAX) + return -1; +#endif + return static_cast<tools::Long>(tmpPosition); + } +} + +int WPXSvInputStream::seek(tools::Long offset) +{ + if ((mnLength == 0) || !mxStream.is() || !mxSeekable.is()) + return -1; + + const sal_Int64 tmpPosition = mxSeekable->getPosition(); + if (tmpPosition < 0) + return -1; + + try + { + mxSeekable->seek(offset); + } + catch (...) + { + SAL_WARN("writerperfect", "mxSeekable->seek(offset) threw exception"); + return -1; + } + + return 0; +} + +bool WPXSvInputStream::isStructured() +{ + if ((mnLength == 0) || !mxStream.is() || !mxSeekable.is()) + return false; + + PositionHolder pos(mxSeekable); + mxSeekable->seek(0); + + if (isOLE()) + return true; + + mxSeekable->seek(0); + + return isZip(); +} + +unsigned WPXSvInputStream::subStreamCount() +{ + if ((mnLength == 0) || !mxStream.is() || !mxSeekable.is()) + return 0; + + PositionHolder pos(mxSeekable); + mxSeekable->seek(0); + + if (isOLE()) + { + ensureOLEIsInitialized(); + + return mpOLEStorage->maStreams.size(); + } + + mxSeekable->seek(0); + + if (isZip()) + { + ensureZipIsInitialized(); + + return mpZipStorage->maStreams.size(); + } + + return 0; +} + +const char* WPXSvInputStream::subStreamName(const unsigned id) +{ + if ((mnLength == 0) || !mxStream.is() || !mxSeekable.is()) + return nullptr; + + PositionHolder pos(mxSeekable); + mxSeekable->seek(0); + + if (isOLE()) + { + ensureOLEIsInitialized(); + + if (mpOLEStorage->maStreams.size() <= id) + return nullptr; + + return mpOLEStorage->maStreams[id].RVNGname.getStr(); + } + + mxSeekable->seek(0); + + if (isZip()) + { + ensureZipIsInitialized(); + + if (mpZipStorage->maStreams.size() <= id) + return nullptr; + + return mpZipStorage->maStreams[id].aName.getStr(); + } + + return nullptr; +} + +bool WPXSvInputStream::existsSubStream(const char* const name) +{ + if (!name) + return false; + + if ((mnLength == 0) || !mxStream.is() || !mxSeekable.is()) + return false; + + PositionHolder pos(mxSeekable); + mxSeekable->seek(0); + + const OUString aName(OStringToOUString(std::string_view(name), RTL_TEXTENCODING_UTF8)); + + if (isOLE()) + { + ensureOLEIsInitialized(); + return mpOLEStorage->maNameMap.end() != mpOLEStorage->maNameMap.find(aName); + } + + mxSeekable->seek(0); + + if (isZip()) + { + ensureZipIsInitialized(); + return mpZipStorage->maNameMap.end() != mpZipStorage->maNameMap.find(aName); + } + + return false; +} + +librevenge::RVNGInputStream* WPXSvInputStream::getSubStreamByName(const char* const name) +{ + if (!name) + return nullptr; + + if ((mnLength == 0) || !mxStream.is() || !mxSeekable.is()) + return nullptr; + + PositionHolder pos(mxSeekable); + mxSeekable->seek(0); + + const OUString aName(OStringToOUString(std::string_view(name), RTL_TEXTENCODING_UTF8)); + + if (isOLE()) + { + ensureOLEIsInitialized(); + return createWPXStream(mpOLEStorage->getStream(aName)); + } + + mxSeekable->seek(0); + + if (isZip()) + { + ensureZipIsInitialized(); + + try + { + return createWPXStream(mpZipStorage->getStream(aName)); + } + catch (const Exception&) + { + // nothing needed + } + } + + return nullptr; +} + +librevenge::RVNGInputStream* WPXSvInputStream::getSubStreamById(const unsigned id) +{ + if ((mnLength == 0) || !mxStream.is() || !mxSeekable.is()) + return nullptr; + + PositionHolder pos(mxSeekable); + mxSeekable->seek(0); + + if (isOLE()) + { + ensureOLEIsInitialized(); + + if (mpOLEStorage->maStreams.size() <= id) + return nullptr; + + return createWPXStream(mpOLEStorage->getStream(id)); + } + + mxSeekable->seek(0); + + if (isZip()) + { + ensureZipIsInitialized(); + + if (mpZipStorage->maStreams.size() <= id) + return nullptr; + + try + { + return createWPXStream(mpZipStorage->getStream(id)); + } + catch (const Exception&) + { + // nothing needed + } + } + return nullptr; +} + +librevenge::RVNGInputStream* +WPXSvInputStream::createWPXStream(const tools::SvRef<SotStorageStream>& rxStorage) +{ + if (rxStorage.is()) + { + Reference<XInputStream> xContents(new utl::OSeekableInputStreamWrapper(rxStorage.get())); + return new WPXSvInputStream(xContents); + } + return nullptr; +} + +librevenge::RVNGInputStream* +WPXSvInputStream::createWPXStream(const Reference<XInputStream>& rxStream) +{ + if (rxStream.is()) + return new WPXSvInputStream(rxStream); + else + return nullptr; +} + +bool WPXSvInputStream::isOLE() +{ + if (!mbCheckedOLE) + { + assert(0 == mxSeekable->getPosition()); + + std::unique_ptr<SvStream> pStream(utl::UcbStreamHelper::CreateStream(mxStream)); + if (pStream && SotStorage::IsOLEStorage(pStream.get())) + mpOLEStorage.reset(new OLEStorageImpl()); + + mbCheckedOLE = true; + } + + return bool(mpOLEStorage); +} + +bool WPXSvInputStream::isZip() +{ + if (!mbCheckedZip) + { + assert(0 == mxSeekable->getPosition()); + + try + { + const Reference<XComponentContext> xContext(comphelper::getProcessComponentContext(), + UNO_SET_THROW); + const Reference<packages::zip::XZipFileAccess2> xZip( + xContext->getServiceManager()->createInstanceWithArgumentsAndContext( + "com.sun.star.packages.zip.ZipFileAccess", { Any(mxStream) }, xContext), + UNO_QUERY_THROW); + mpZipStorage.reset(new ZipStorageImpl(xZip)); + } + catch (const Exception&) + { + // ignore + } + + mbCheckedZip = true; + } + + return bool(mpZipStorage); +} + +void WPXSvInputStream::ensureOLEIsInitialized() +{ + assert(mpOLEStorage); + + if (!mpOLEStorage->mbInitialized) + mpOLEStorage->initialize(utl::UcbStreamHelper::CreateStream(mxStream)); +} + +void WPXSvInputStream::ensureZipIsInitialized() +{ + assert(mpZipStorage); + + if (!mpZipStorage->mbInitialized) + mpZipStorage->initialize(); +} + +WPXSvInputStream::~WPXSvInputStream() {} + +long WPXSvInputStream::tell() +{ + tools::Long retVal = tellImpl(); + return retVal + static_cast<tools::Long>(mnReadBufferPos); +} + +int WPXSvInputStream::seek(long offset, librevenge::RVNG_SEEK_TYPE seekType) +{ + sal_Int64 tmpOffset = offset; + if (seekType == librevenge::RVNG_SEEK_CUR) + tmpOffset += tell(); + if (seekType == librevenge::RVNG_SEEK_END) + tmpOffset += mnLength; + + int retVal = 0; + if (tmpOffset < 0) + { + tmpOffset = 0; + retVal = -1; + } + if (tmpOffset > mnLength) + { + tmpOffset = mnLength; + retVal = -1; + } + + if (tmpOffset < tellImpl() && o3tl::make_unsigned(tmpOffset) >= o3tl::make_unsigned(tellImpl())) + { + mnReadBufferPos = static_cast<unsigned long>(tmpOffset - tellImpl()); + return retVal; + } + + if (seek(tmpOffset)) + return -1; + return retVal; +} + +bool WPXSvInputStream::isEnd() +{ + if (mnReadBufferPos != 0) + return false; + if ((mnLength == 0) || !mxStream.is() || !mxSeekable.is()) + return true; + return (mxSeekable->getPosition() >= mnLength); +} +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ |