diff options
Diffstat (limited to '')
-rw-r--r-- | writerperfect/source/common/DirectoryStream.cxx | 221 | ||||
-rw-r--r-- | writerperfect/source/common/DocumentHandler.cxx | 181 | ||||
-rw-r--r-- | writerperfect/source/common/WPFTEncodingDialog.cxx | 122 | ||||
-rw-r--r-- | writerperfect/source/common/WPXSvInputStream.cxx | 960 |
4 files changed, 1484 insertions, 0 deletions
diff --git a/writerperfect/source/common/DirectoryStream.cxx b/writerperfect/source/common/DirectoryStream.cxx new file mode 100644 index 000000000..28c38daa6 --- /dev/null +++ b/writerperfect/source/common/DirectoryStream.cxx @@ -0,0 +1,221 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* writerperfect + * Version: MPL 2.0 / LGPLv2.1+ + * + * 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/. + * + * Major Contributor(s): + * Copyright (C) 2007 Fridrich Strba (fridrich.strba@bluewin.ch) + * + * For minor contributions see the git repository. + * + * Alternatively, the contents of this file may be used under the terms + * of the GNU Lesser General Public License Version 2.1 or later + * (LGPLv2.1+), in which case the provisions of the LGPLv2.1+ are + * applicable instead of those above. + * + * For further information visit http://libwpd.sourceforge.net + */ + +#include <memory> +#include <com/sun/star/container/XChild.hpp> +#include <com/sun/star/io/XInputStream.hpp> +#include <com/sun/star/sdbc/XResultSet.hpp> +#include <com/sun/star/sdbc/XRow.hpp> +#include <com/sun/star/ucb/XContent.hpp> +#include <com/sun/star/ucb/XContentAccess.hpp> +#include <com/sun/star/ucb/XCommandEnvironment.hpp> + +#include <com/sun/star/uno/Reference.hxx> +#include <com/sun/star/uno/Sequence.hxx> + +#include <comphelper/processfactory.hxx> + +#include <rtl/ustring.hxx> + +#include <ucbhelper/content.hxx> + +#include <DirectoryStream.hxx> +#include <WPXSvInputStream.hxx> + +namespace io = com::sun::star::io; +namespace sdbc = com::sun::star::sdbc; +namespace ucb = com::sun::star::ucb; +namespace uno = com::sun::star::uno; + +namespace writerperfect +{ +namespace +{ +uno::Reference<io::XInputStream> findStream(ucbhelper::Content& rContent, std::u16string_view rName) +{ + uno::Reference<io::XInputStream> xInputStream; + + uno::Sequence<OUString> lPropNames{ "Title" }; + try + { + const uno::Reference<sdbc::XResultSet> xResultSet( + rContent.createCursor(lPropNames, ucbhelper::INCLUDE_DOCUMENTS_ONLY)); + if (xResultSet->first()) + { + const uno::Reference<ucb::XContentAccess> xContentAccess(xResultSet, + uno::UNO_QUERY_THROW); + const uno::Reference<sdbc::XRow> xRow(xResultSet, uno::UNO_QUERY_THROW); + do + { + const OUString aTitle(xRow->getString(1)); + if (aTitle == rName) + { + const uno::Reference<ucb::XContent> xSubContent(xContentAccess->queryContent()); + ucbhelper::Content aSubContent(xSubContent, + uno::Reference<ucb::XCommandEnvironment>(), + comphelper::getProcessComponentContext()); + xInputStream = aSubContent.openStream(); + break; + } + } while (xResultSet->next()); + } + } + catch (const uno::RuntimeException&) + { + // ignore + } + catch (const uno::Exception&) + { + // ignore + } + + return xInputStream; +} +} + +struct DirectoryStream::Impl +{ + explicit Impl(const uno::Reference<ucb::XContent>& rxContent); + + uno::Reference<ucb::XContent> xContent; +}; + +DirectoryStream::Impl::Impl(const uno::Reference<ucb::XContent>& rxContent) + : xContent(rxContent) +{ +} + +DirectoryStream::DirectoryStream(const css::uno::Reference<css::ucb::XContent>& xContent) + : m_pImpl(isDirectory(xContent) ? new Impl(xContent) : nullptr) +{ +} + +DirectoryStream::~DirectoryStream() {} + +bool DirectoryStream::isDirectory(const css::uno::Reference<css::ucb::XContent>& xContent) +{ + try + { + if (!xContent.is()) + return false; + + ucbhelper::Content aContent(xContent, uno::Reference<ucb::XCommandEnvironment>(), + comphelper::getProcessComponentContext()); + return aContent.isFolder(); + } + catch (...) + { + return false; + } +} + +std::unique_ptr<DirectoryStream> +DirectoryStream::createForParent(const css::uno::Reference<css::ucb::XContent>& xContent) +{ + try + { + if (!xContent.is()) + return nullptr; + + std::unique_ptr<DirectoryStream> pDir; + + const uno::Reference<css::container::XChild> xChild(xContent, uno::UNO_QUERY); + if (xChild.is()) + { + const uno::Reference<ucb::XContent> xDirContent(xChild->getParent(), uno::UNO_QUERY); + if (xDirContent.is()) + { + pDir = std::make_unique<DirectoryStream>(xDirContent); + if (!pDir->isStructured()) + pDir.reset(); + } + } + + return pDir; + } + catch (...) + { + return nullptr; + } +} + +css::uno::Reference<css::ucb::XContent> DirectoryStream::getContent() const +{ + if (!m_pImpl) + return css::uno::Reference<css::ucb::XContent>(); + return m_pImpl->xContent; +} + +bool DirectoryStream::isStructured() { return m_pImpl != nullptr; } + +unsigned DirectoryStream::subStreamCount() +{ + // TODO: implement me + return 0; +} + +const char* DirectoryStream::subStreamName(unsigned /* id */) +{ + // TODO: implement me + return nullptr; +} + +bool DirectoryStream::existsSubStream(const char* /* name */) +{ + // TODO: implement me + return false; +} + +librevenge::RVNGInputStream* DirectoryStream::getSubStreamByName(const char* const pName) +{ + if (!m_pImpl) + return nullptr; + + ucbhelper::Content aContent(m_pImpl->xContent, uno::Reference<ucb::XCommandEnvironment>(), + comphelper::getProcessComponentContext()); + const uno::Reference<io::XInputStream> xInputStream( + findStream(aContent, OUString::createFromAscii(pName))); + if (xInputStream.is()) + return new WPXSvInputStream(xInputStream); + + return nullptr; +} + +librevenge::RVNGInputStream* DirectoryStream::getSubStreamById(unsigned /* id */) +{ + // TODO: implement me + return nullptr; +} + +const unsigned char* DirectoryStream::read(unsigned long, unsigned long& nNumBytesRead) +{ + nNumBytesRead = 0; + return nullptr; +} + +int DirectoryStream::seek(long, librevenge::RVNG_SEEK_TYPE) { return -1; } + +long DirectoryStream::tell() { return 0; } + +bool DirectoryStream::isEnd() { return true; } +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/writerperfect/source/common/DocumentHandler.cxx b/writerperfect/source/common/DocumentHandler.cxx new file mode 100644 index 000000000..6b5ffe58a --- /dev/null +++ b/writerperfect/source/common/DocumentHandler.cxx @@ -0,0 +1,181 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * 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/. + * + * For further information visit http://libwpd.sourceforge.net + */ + +#include <DocumentHandler.hxx> + +#include <string.h> + +#include <com/sun/star/uno/Reference.hxx> +#include <com/sun/star/xml/sax/XDocumentHandler.hpp> +#include <com/sun/star/xml/sax/XAttributeList.hpp> + +#include <xmloff/attrlist.hxx> +#include <xmloff/xmlimp.hxx> + +namespace writerperfect +{ +const unsigned char librvng_utf8_skip_data[256] + = { 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 3, 3, 3, 3, 3, 3, 3, 3, + 3, 3, 3, 3, 3, 3, 3, 3, 4, 4, 4, 4, 4, 4, 4, 4, 5, 5, 5, 5, 6, 6, 1, 1 }; + +static const char* librvng_utf8_next_char(const char* p) +{ + return p + librvng_utf8_skip_data[*reinterpret_cast<unsigned char const*>(p)]; +} + +static void unescapeXML(const char* s, const unsigned long sz, librevenge::RVNGString& res) +{ + const char* p = s; + const char* const end = p + sz; + while (p != end) + { + const char* const next = librvng_utf8_next_char(p); + if (next > end) + { + // oops, the string is invalid + break; + } + if (p + 4 <= end && p + 1 == next && *p == '&') + { + // look for & , < , > , ' , " + bool escapedChar = false; + switch (*(p + 1)) + { + case 'a': + if (p + 5 <= end && strncmp(p, "&", 5) == 0) + { + res.append('&'); + p += 5; + escapedChar = true; + } + else if (p + 6 <= end && strncmp(p, "'", 6) == 0) + { + res.append('\''); + p += 6; + escapedChar = true; + } + break; + case 'g': + if (strncmp(p, ">", 4) == 0) + { + res.append('>'); + p += 4; + escapedChar = true; + } + break; + case 'l': + if (strncmp(p, "<", 4) == 0) + { + res.append('<'); + p += 4; + escapedChar = true; + } + break; + case 'q': + if (p + 6 <= end && strncmp(p, """, 6) == 0) + { + res.append('"'); + p += 6; + escapedChar = true; + } + break; + default: + break; + } + if (escapedChar) + continue; + } + + while (p != next) + { + res.append(*p); + ++p; + } + p = next; + } +} + +using com::sun::star::uno::Reference; +using com::sun::star::xml::sax::XAttributeList; +using com::sun::star::xml::sax::XDocumentHandler; + +DocumentHandler::DocumentHandler(Reference<XDocumentHandler> const& xHandler) + : mxHandler(xHandler) +{ + if (SvXMLImport* pFastHandler = dynamic_cast<SvXMLImport*>(mxHandler.get())) + mxHandler.set(new SvXMLLegacyToFastDocHandler(pFastHandler)); +} + +void DocumentHandler::startDocument() { mxHandler->startDocument(); } + +void DocumentHandler::endDocument() { mxHandler->endDocument(); } + +void DocumentHandler::startElement(const char* psName, + const librevenge::RVNGPropertyList& xPropList) +{ + rtl::Reference<SvXMLAttributeList> pAttrList = new SvXMLAttributeList(); + librevenge::RVNGPropertyList::Iter i(xPropList); + for (i.rewind(); i.next();) + { + // filter out librevenge elements + if (strncmp(i.key(), "librevenge", 10) != 0) + { + size_t keyLength = strlen(i.key()); + OUString sName(i.key(), keyLength, RTL_TEXTENCODING_UTF8); + OUString sValue(i()->getStr().cstr(), i()->getStr().len(), RTL_TEXTENCODING_UTF8); + + // libodfgen xml-encodes some attribute's value, so check if the value is encoded or not + for (int j = 0; j < 9; ++j) + { + // list of the encoded attributes followed by their lengths + static char const* listEncoded[9] + = { "draw:name", "svg:font-family", "style:condition", + "style:num-prefix", "style:num-suffix", "table:formula", + "text:bullet-char", "text:label", "xlink:href" }; + static size_t const listEncodedLength[9] = { 9, 15, 15, 16, 16, 13, 16, 10, 10 }; + if (keyLength == listEncodedLength[j] + && strncmp(i.key(), listEncoded[j], keyLength) == 0) + { + librevenge::RVNGString decodedValue(""); + unescapeXML(i()->getStr().cstr(), + static_cast<unsigned long>(i()->getStr().len()), decodedValue); + sValue + = OUString(decodedValue.cstr(), decodedValue.len(), RTL_TEXTENCODING_UTF8); + break; + } + } + pAttrList->AddAttribute(sName, sValue); + } + } + + OUString sElementName(psName, strlen(psName), RTL_TEXTENCODING_UTF8); + mxHandler->startElement(sElementName, pAttrList); +} + +void DocumentHandler::endElement(const char* psName) +{ + OUString sElementName(psName, strlen(psName), RTL_TEXTENCODING_UTF8); + mxHandler->endElement(sElementName); +} + +void DocumentHandler::characters(const librevenge::RVNGString& sCharacters) +{ + OUString sCharU16(sCharacters.cstr(), strlen(sCharacters.cstr()), RTL_TEXTENCODING_UTF8); + mxHandler->characters(sCharU16); +} +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/writerperfect/source/common/WPFTEncodingDialog.cxx b/writerperfect/source/common/WPFTEncodingDialog.cxx new file mode 100644 index 000000000..b0c955481 --- /dev/null +++ b/writerperfect/source/common/WPFTEncodingDialog.cxx @@ -0,0 +1,122 @@ +/* -*- 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 <sal/config.h> + +#include <cstddef> +#include <string_view> +#include <utility> + +#include <WPFTEncodingDialog.hxx> + +namespace writerperfect +{ +namespace +{ +std::pair<std::u16string_view, std::u16string_view> const s_encodings[] + = { { u"MacArabic", u"Arabic (Apple Macintosh)" }, + { u"CP864", u"Arabic (DOS/OS2-864)" }, + { u"CP1006", u"Arabic (IBM-1006)" }, + { u"CP1256", u"Arabic (Windows-1256)" }, + { u"CP775", u"Baltic (DOS/OS2-775)" }, + { u"CP1257", u"Baltic (Windows-1257)" }, + { u"MacCeltic", u"Celtic (Apple Macintosh)" }, + { u"MacCyrillic", u"Cyrillic (Apple Macintosh)" }, + { u"CP855", u"Cyrillic (DOS/OS2-855)" }, + { u"CP866", u"Cyrillic (DOS/OS2-866/Russian)" }, + { u"CP1251", u"Cyrillic (Windows-1251)" }, + { u"MacCEurope", u"Eastern Europe (Apple Macintosh)" }, + { u"MacCroatian", u"Eastern Europe (Apple Macintosh/Croatian)" }, + { u"MacRomanian", u"Eastern Europe (Apple Macintosh/Romanian)" }, + { u"CP852", u"Eastern Europe (DOS/OS2-852)" }, + { u"CP1250", u"Eastern Europe (Windows-1250/WinLatin 2)" }, + { u"MacGreek", u"Greek (Apple Macintosh)" }, + { u"CP737", u"Greek (DOS/OS2-737)" }, + { u"CP869", u"Greek (DOS/OS2-869/Greek-2)" }, + { u"CP875", u"Greek (DOS/OS2-875)" }, + { u"CP1253", u"Greek (Windows-1253)" }, + { u"MacHebrew", u"Hebrew (Apple Macintosh)" }, + { u"CP424", u"Hebrew (DOS/OS2-424)" }, + { u"CP856", u"Hebrew (DOS/OS2-856)" }, + { u"CP862", u"Hebrew (DOS/OS2-862)" }, + { u"CP1255", u"Hebrew (Windows-1255)" }, + { u"CP500", u"International (DOS/OS2-500)" }, + { u"CP932", u"Japanese (Windows-932)" }, + { u"MacThai", u"Thai (Apple Macintosh)" }, + { u"CP874", u"Thai (DOS/OS2-874)" }, + { u"CP950", u"Traditional Chinese (Windows-950)" }, + { u"MacTurkish", u"Turkish (Apple Macintosh)" }, + { u"CP857", u"Turkish (DOS/OS2-857)" }, + { u"CP1026", u"Turkish (DOS/OS2-1026)" }, + { u"CP1254", u"Turkish (Windows-1254)" }, + { u"CP1258", u"Vietnamese (Windows-1258)" }, + { u"MacRoman", u"Western Europe (Apple Macintosh)" }, + { u"MacIceland", u"Western Europe (Apple Macintosh/Icelandic)" }, + { u"CP037", u"Western Europe (DOS/OS2-037/US-Canada)" }, + { u"CP437", u"Western Europe (DOS/OS2-437/US)" }, + { u"CP850", u"Western Europe (DOS/OS2-850)" }, + { u"CP860", u"Western Europe (DOS/OS2-860/Portuguese)" }, + { u"CP861", u"Western Europe (DOS/OS2-861/Icelandic)" }, + { u"CP863", u"Western Europe (DOS/OS2-863/French)" }, + { u"CP865", u"Western Europe (DOS/OS2-865/Nordic)" }, + { u"CP1252", u"Western Europe (Windows-1252/WinLatin 1)" } }; + +std::size_t const numEncodings = SAL_N_ELEMENTS(s_encodings); + +void insertEncodings(weld::ComboBox& box) +{ + for (std::size_t i = 0; i < numEncodings; ++i) + box.append(OUString(s_encodings[i].first), OUString(s_encodings[i].second)); +} + +void selectEncoding(weld::ComboBox& box, const OUString& encoding) { box.set_active_id(encoding); } + +OUString getEncoding(const weld::ComboBox& box) { return box.get_active_id(); } +} + +WPFTEncodingDialog::WPFTEncodingDialog(weld::Window* pParent, const OUString& title, + const OUString& encoding) + : GenericDialogController(pParent, "writerperfect/ui/wpftencodingdialog.ui", + "WPFTEncodingDialog") + , m_userHasCancelled(false) + , m_xLbCharset(m_xBuilder->weld_combo_box("comboboxtext")) + , m_xBtnOk(m_xBuilder->weld_button("ok")) + , m_xBtnCancel(m_xBuilder->weld_button("cancel")) +{ + m_xBtnCancel->connect_clicked(LINK(this, WPFTEncodingDialog, CancelHdl)); + + insertEncodings(*m_xLbCharset); + m_xLbCharset->make_sorted(); + selectEncoding(*m_xLbCharset, encoding); + + m_xDialog->set_title(title); +} + +WPFTEncodingDialog::~WPFTEncodingDialog() {} + +OUString WPFTEncodingDialog::GetEncoding() const { return getEncoding(*m_xLbCharset); } + +IMPL_LINK_NOARG(WPFTEncodingDialog, CancelHdl, weld::Button&, void) +{ + m_userHasCancelled = true; + m_xDialog->response(RET_CANCEL); +} +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/writerperfect/source/common/WPXSvInputStream.cxx b/writerperfect/source/common/WPXSvInputStream.cxx new file mode 100644 index 000000000..3364b0509 --- /dev/null +++ b/writerperfect/source/common/WPXSvInputStream.cxx @@ -0,0 +1,960 @@ +/* -*- 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 <sot/storage.hxx> + +#include <tools/stream.hxx> +#include <unotools/streamwrap.hxx> +#include <unotools/ucbstreamhelper.hxx> + +#include <climits> +#include <limits> +#include <memory> +#include <string_view> +#include <unordered_map> +#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; +}; + +PositionHolder::PositionHolder(const Reference<XSeekable>& rxSeekable) + : mxSeekable(rxSeekable) + , mnPosition(rxSeekable->getPosition()) +{ +} + +PositionHolder::~PositionHolder() +{ + try + { + mxSeekable->seek(mnPosition); + } + catch (...) + { + } +} + +} // anonymous namespace + +namespace +{ +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; +} +} + +namespace +{ +OUString concatPath(std::u16string_view lhs, const OUString& rhs) +{ + if (lhs.empty()) + return rhs; + return OUString::Concat(lhs) + "/" + rhs; +} + +struct OLEStreamData +{ + OLEStreamData(const OString& rName, const OString& 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; +}; + +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; +}; + +OLEStreamData::OLEStreamData(const OString& rName, const OString& rvngName) + : name(rName) + , RVNGname(rvngName) +{ +} + +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(const OString& rName); + + Reference<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; +}; + +/** 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 Reference<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<XInputStream> getStream(const OUString& rPath); + Reference<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(const OString& rName) + : aName(rName) +{ +} + +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; +} +} + +class WPXSvInputStreamImpl +{ +public: + explicit WPXSvInputStreamImpl(css::uno::Reference<css::io::XInputStream> const& xStream); + + bool isStructured(); + unsigned subStreamCount(); + const char* subStreamName(unsigned id); + bool existsSubStream(const char* name); + librevenge::RVNGInputStream* getSubStreamByName(const char* name); + librevenge::RVNGInputStream* getSubStreamById(unsigned id); + + const unsigned char* read(unsigned long numBytes, unsigned long& numBytesRead); + int seek(tools::Long offset); + tools::Long tell(); + bool isEnd(); + + void invalidateReadBuffer(); + +private: + bool isOLE(); + void ensureOLEIsInitialized(); + + bool isZip(); + void ensureZipIsInitialized(); + + static librevenge::RVNGInputStream* + createWPXStream(const tools::SvRef<SotStorageStream>& rxStorage); + static librevenge::RVNGInputStream* createWPXStream(const Reference<XInputStream>& rxStream); + +private: + css::uno::Reference<css::io::XInputStream> mxStream; + css::uno::Reference<css::io::XSeekable> mxSeekable; + css::uno::Sequence<sal_Int8> maData; + std::unique_ptr<OLEStorageImpl> mpOLEStorage; + std::unique_ptr<ZipStorageImpl> mpZipStorage; + bool mbCheckedOLE; + bool mbCheckedZip; + +public: + sal_Int64 mnLength; + const unsigned char* mpReadBuffer; + unsigned long mnReadBufferLength; + unsigned long mnReadBufferPos; +}; + +WPXSvInputStreamImpl::WPXSvInputStreamImpl(Reference<XInputStream> const& xStream) + : mxStream(xStream) + , mxSeekable(xStream, UNO_QUERY) + , maData(0) + , mbCheckedOLE(false) + , mbCheckedZip(false) + , mnLength(0) + , mpReadBuffer(nullptr) + , mnReadBufferLength(0) + , mnReadBufferPos(0) +{ + 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* WPXSvInputStreamImpl::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 WPXSvInputStreamImpl::tell() +{ + if ((mnLength == 0) || !mxStream.is() || !mxSeekable.is()) + return -1; + else + { + const sal_Int64 tmpPosition = mxSeekable->getPosition(); + if ((tmpPosition < 0) || (tmpPosition > LONG_MAX)) + return -1; + return static_cast<tools::Long>(tmpPosition); + } +} + +int WPXSvInputStreamImpl::seek(tools::Long offset) +{ + if ((mnLength == 0) || !mxStream.is() || !mxSeekable.is()) + return -1; + + const sal_Int64 tmpPosition = mxSeekable->getPosition(); + if ((tmpPosition < 0) || (tmpPosition > LONG_MAX)) + return -1; + + try + { + mxSeekable->seek(offset); + return 0; + } + catch (...) + { + SAL_WARN("writerperfect", "mxSeekable->seek(offset) threw exception"); + return -1; + } +} + +bool WPXSvInputStreamImpl::isEnd() +{ + if ((mnLength == 0) || !mxStream.is() || !mxSeekable.is()) + return true; + return (mxSeekable->getPosition() >= mnLength); +} + +bool WPXSvInputStreamImpl::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 WPXSvInputStreamImpl::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* WPXSvInputStreamImpl::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 WPXSvInputStreamImpl::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* WPXSvInputStreamImpl::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* WPXSvInputStreamImpl::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; +} + +void WPXSvInputStreamImpl::invalidateReadBuffer() +{ + if (mpReadBuffer) + { + seek(tell() + static_cast<tools::Long>(mnReadBufferPos) + - static_cast<tools::Long>(mnReadBufferLength)); + mpReadBuffer = nullptr; + mnReadBufferPos = 0; + mnReadBufferLength = 0; + } +} + +librevenge::RVNGInputStream* +WPXSvInputStreamImpl::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* +WPXSvInputStreamImpl::createWPXStream(const Reference<XInputStream>& rxStream) +{ + if (rxStream.is()) + return new WPXSvInputStream(rxStream); + else + return nullptr; +} + +bool WPXSvInputStreamImpl::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 WPXSvInputStreamImpl::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 WPXSvInputStreamImpl::ensureOLEIsInitialized() +{ + assert(mpOLEStorage); + + if (!mpOLEStorage->mbInitialized) + mpOLEStorage->initialize(utl::UcbStreamHelper::CreateStream(mxStream)); +} + +void WPXSvInputStreamImpl::ensureZipIsInitialized() +{ + assert(mpZipStorage); + + if (!mpZipStorage->mbInitialized) + mpZipStorage->initialize(); +} + +WPXSvInputStream::WPXSvInputStream(Reference<XInputStream> const& xStream) + : mpImpl(new WPXSvInputStreamImpl(xStream)) +{ +} + +WPXSvInputStream::~WPXSvInputStream() {} + +#define BUFFER_MAX 65536 + +const unsigned char* WPXSvInputStream::read(unsigned long numBytes, unsigned long& numBytesRead) +{ + numBytesRead = 0; + + if (numBytes == 0 || numBytes > std::numeric_limits<unsigned long>::max() / 2) + return nullptr; + + if (mpImpl->mpReadBuffer) + { + if ((mpImpl->mnReadBufferPos + numBytes > mpImpl->mnReadBufferPos) + && (mpImpl->mnReadBufferPos + numBytes <= mpImpl->mnReadBufferLength)) + { + const unsigned char* pTmp = mpImpl->mpReadBuffer + mpImpl->mnReadBufferPos; + mpImpl->mnReadBufferPos += numBytes; + numBytesRead = numBytes; + return pTmp; + } + + mpImpl->invalidateReadBuffer(); + } + + unsigned long curpos = static_cast<unsigned long>(mpImpl->tell()); + if (curpos == static_cast<unsigned long>(-1)) // returned ERROR + return nullptr; + + if ((curpos + numBytes < curpos) /*overflow*/ + || (curpos + numBytes + >= o3tl::make_unsigned(mpImpl->mnLength))) /*reading more than available*/ + { + numBytes = mpImpl->mnLength - curpos; + } + + if (numBytes < BUFFER_MAX) + { + if (BUFFER_MAX < mpImpl->mnLength - curpos) + mpImpl->mnReadBufferLength = BUFFER_MAX; + else /* BUFFER_MAX >= mpImpl->mnLength - curpos */ + mpImpl->mnReadBufferLength = mpImpl->mnLength - curpos; + } + else + mpImpl->mnReadBufferLength = numBytes; + + unsigned long tmpNumBytes(0); + mpImpl->mpReadBuffer = mpImpl->read(mpImpl->mnReadBufferLength, tmpNumBytes); + if (tmpNumBytes != mpImpl->mnReadBufferLength) + mpImpl->mnReadBufferLength = tmpNumBytes; + + mpImpl->mnReadBufferPos = 0; + if (!mpImpl->mnReadBufferLength) + return nullptr; + + if (numBytes <= mpImpl->mnReadBufferLength) + numBytesRead = numBytes; + else + numBytesRead = mpImpl->mnReadBufferLength; + + mpImpl->mnReadBufferPos += numBytesRead; + return mpImpl->mpReadBuffer; +} + +long WPXSvInputStream::tell() +{ + tools::Long retVal = mpImpl->tell(); + return retVal - static_cast<tools::Long>(mpImpl->mnReadBufferLength) + + static_cast<tools::Long>(mpImpl->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 += mpImpl->mnLength; + + int retVal = 0; + if (tmpOffset < 0) + { + tmpOffset = 0; + retVal = -1; + } + if (tmpOffset > mpImpl->mnLength) + { + tmpOffset = mpImpl->mnLength; + retVal = -1; + } + + if (tmpOffset < mpImpl->tell() + && o3tl::make_unsigned(tmpOffset) + >= static_cast<unsigned long>(mpImpl->tell()) - mpImpl->mnReadBufferLength) + { + mpImpl->mnReadBufferPos = static_cast<unsigned long>( + tmpOffset + static_cast<tools::Long>(mpImpl->mnReadBufferLength) - mpImpl->tell()); + return retVal; + } + + mpImpl->invalidateReadBuffer(); + + if (mpImpl->seek(tmpOffset)) + return -1; + return retVal; +} + +bool WPXSvInputStream::isEnd() +{ + return mpImpl->isEnd() && mpImpl->mnReadBufferPos == mpImpl->mnReadBufferLength; +} + +bool WPXSvInputStream::isStructured() +{ + mpImpl->invalidateReadBuffer(); + return mpImpl->isStructured(); +} + +unsigned WPXSvInputStream::subStreamCount() +{ + mpImpl->invalidateReadBuffer(); + return mpImpl->subStreamCount(); +} + +const char* WPXSvInputStream::subStreamName(const unsigned id) +{ + mpImpl->invalidateReadBuffer(); + return mpImpl->subStreamName(id); +} + +bool WPXSvInputStream::existsSubStream(const char* const name) +{ + mpImpl->invalidateReadBuffer(); + return mpImpl->existsSubStream(name); +} + +librevenge::RVNGInputStream* WPXSvInputStream::getSubStreamByName(const char* name) +{ + mpImpl->invalidateReadBuffer(); + return mpImpl->getSubStreamByName(name); +} + +librevenge::RVNGInputStream* WPXSvInputStream::getSubStreamById(const unsigned id) +{ + mpImpl->invalidateReadBuffer(); + return mpImpl->getSubStreamById(id); +} +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ |