diff options
Diffstat (limited to 'comphelper/source/misc/backupfilehelper.cxx')
-rw-r--r-- | comphelper/source/misc/backupfilehelper.cxx | 2504 |
1 files changed, 2504 insertions, 0 deletions
diff --git a/comphelper/source/misc/backupfilehelper.cxx b/comphelper/source/misc/backupfilehelper.cxx new file mode 100644 index 000000000..e9b173a47 --- /dev/null +++ b/comphelper/source/misc/backupfilehelper.cxx @@ -0,0 +1,2504 @@ +/* -*- 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 <sal/config.h> +#include <rtl/ustring.hxx> +#include <rtl/bootstrap.hxx> +#include <sal/log.hxx> +#include <osl/file.hxx> +#include <comphelper/backupfilehelper.hxx> +#include <comphelper/DirectoryHelper.hxx> +#include <rtl/crc.h> +#include <algorithm> +#include <deque> +#include <memory> +#include <string_view> +#include <utility> +#include <vector> +#include <zlib.h> + +#include <comphelper/processfactory.hxx> +#include <com/sun/star/lang/WrappedTargetRuntimeException.hpp> +#include <com/sun/star/ucb/CommandAbortedException.hpp> +#include <com/sun/star/ucb/CommandFailedException.hpp> +#include <com/sun/star/uno/Sequence.hxx> +#include <com/sun/star/uno/Reference.hxx> +#include <com/sun/star/deployment/DeploymentException.hpp> +#include <com/sun/star/deployment/ExtensionManager.hpp> +#include <com/sun/star/xml/dom/XDocumentBuilder.hpp> +#include <com/sun/star/xml/dom/DocumentBuilder.hpp> +#include <com/sun/star/xml/dom/XElement.hpp> +#include <com/sun/star/xml/dom/XNodeList.hpp> +#include <com/sun/star/xml/dom/XText.hpp> +#include <com/sun/star/xml/sax/XSAXSerializable.hpp> +#include <com/sun/star/xml/sax/Writer.hpp> +#include <com/sun/star/xml/sax/XWriter.hpp> +#include <com/sun/star/io/XStream.hpp> +#include <com/sun/star/io/TempFile.hpp> +#include <com/sun/star/io/XOutputStream.hpp> +#include <com/sun/star/beans/XPropertySet.hpp> +#include <cppuhelper/exc_hlp.hxx> + +using namespace comphelper; +using namespace css; +using namespace css::xml::dom; + +const sal_uInt32 BACKUP_FILE_HELPER_BLOCK_SIZE = 16384; + +namespace +{ + typedef std::shared_ptr< osl::File > FileSharedPtr; + + sal_uInt32 createCrc32(FileSharedPtr const & rCandidate, sal_uInt32 nOffset) + { + sal_uInt32 nCrc32(0); + + if (rCandidate && osl::File::E_None == rCandidate->open(osl_File_OpenFlag_Read)) + { + sal_uInt8 aArray[BACKUP_FILE_HELPER_BLOCK_SIZE]; + sal_uInt64 nBytesTransfer(0); + sal_uInt64 nSize(0); + + rCandidate->getSize(nSize); + + // set offset in source file - should be zero due to crc32 should + // only be needed to be created for new entries, gets loaded with old + // ones + if (osl::File::E_None == rCandidate->setPos(osl_Pos_Absolut, sal_Int64(nOffset))) + { + while (nSize != 0) + { + const sal_uInt64 nToTransfer(std::min(nSize, sal_uInt64(BACKUP_FILE_HELPER_BLOCK_SIZE))); + + if (osl::File::E_None == rCandidate->read(static_cast<void*>(aArray), nToTransfer, nBytesTransfer) && nBytesTransfer == nToTransfer) + { + // add to crc and reduce size + nCrc32 = rtl_crc32(nCrc32, static_cast<void*>(aArray), static_cast<sal_uInt32>(nBytesTransfer)); + nSize -= nToTransfer; + } + else + { + // error - reset to zero again + nSize = nCrc32 = 0; + } + } + } + + rCandidate->close(); + } + + return nCrc32; + } + + bool read_sal_uInt32(FileSharedPtr const & rFile, sal_uInt32& rTarget) + { + sal_uInt8 aArray[4]; + sal_uInt64 nBaseRead(0); + + // read rTarget + if (osl::File::E_None == rFile->read(static_cast<void*>(aArray), 4, nBaseRead) && 4 == nBaseRead) + { + rTarget = (sal_uInt32(aArray[0]) << 24) + (sal_uInt32(aArray[1]) << 16) + (sal_uInt32(aArray[2]) << 8) + sal_uInt32(aArray[3]); + return true; + } + + return false; + } + + bool write_sal_uInt32(oslFileHandle& rHandle, sal_uInt32 nSource) + { + sal_uInt8 aArray[4]; + sal_uInt64 nBaseWritten(0); + + // write nSource + aArray[0] = sal_uInt8((nSource & 0xff000000) >> 24); + aArray[1] = sal_uInt8((nSource & 0x00ff0000) >> 16); + aArray[2] = sal_uInt8((nSource & 0x0000ff00) >> 8); + aArray[3] = sal_uInt8(nSource & 0x000000ff); + + return osl_File_E_None == osl_writeFile(rHandle, static_cast<const void*>(aArray), 4, &nBaseWritten) && 4 == nBaseWritten; + } + + bool read_OString(FileSharedPtr const & rFile, OString& rTarget) + { + sal_uInt32 nLength(0); + + if (!read_sal_uInt32(rFile, nLength)) + { + return false; + } + + sal_uInt64 nPos; + if (osl::File::E_None != rFile->getPos(nPos)) + return false; + + sal_uInt64 nSize; + if (osl::File::E_None != rFile->getSize(nSize)) + return false; + + const auto nRemainingSize = nSize - nPos; + if (nLength > nRemainingSize) + return false; + + std::vector<char> aTarget(nLength); + sal_uInt64 nBaseRead(0); + + // read rTarget + if (osl::File::E_None == rFile->read(static_cast<void*>(aTarget.data()), nLength, nBaseRead) && nLength == nBaseRead) + { + rTarget = OString(aTarget.data(), static_cast<sal_Int32>(nBaseRead)); + return true; + } + + return false; + } + + bool write_OString(oslFileHandle& rHandle, const OString& rSource) + { + const sal_uInt32 nLength(rSource.getLength()); + + if (!write_sal_uInt32(rHandle, nLength)) + { + return false; + } + + sal_uInt64 nBaseWritten(0); + + return osl_File_E_None == osl_writeFile(rHandle, static_cast<const void*>(rSource.getStr()), nLength, &nBaseWritten) && nLength == nBaseWritten; + } + + OUString createFileURL( + std::u16string_view rURL, std::u16string_view rName, std::u16string_view rExt) + { + OUString aRetval; + + if (!rURL.empty() && !rName.empty()) + { + aRetval = OUString::Concat(rURL) + "/" + rName; + + if (!rExt.empty()) + { + aRetval += OUString::Concat(".") + rExt; + } + } + + return aRetval; + } + + OUString createPackURL(std::u16string_view rURL, std::u16string_view rName) + { + OUString aRetval; + + if (!rURL.empty() && !rName.empty()) + { + aRetval = OUString::Concat(rURL) + "/" + rName + ".pack"; + } + + return aRetval; + } +} + +namespace +{ + enum PackageRepository { USER, SHARED, BUNDLED }; + + class ExtensionInfoEntry + { + private: + OString maName; // extension name + PackageRepository maRepository; // user|shared|bundled + bool mbEnabled; // state + + public: + ExtensionInfoEntry() + : maRepository(USER), + mbEnabled(false) + { + } + + ExtensionInfoEntry(OString aName, bool bEnabled) + : maName(std::move(aName)), + maRepository(USER), + mbEnabled(bEnabled) + { + } + + ExtensionInfoEntry(const uno::Reference< deployment::XPackage >& rxPackage) + : maName(OUStringToOString(rxPackage->getName(), RTL_TEXTENCODING_ASCII_US)), + maRepository(USER), + mbEnabled(false) + { + // check maRepository + const OString aRepName(OUStringToOString(rxPackage->getRepositoryName(), RTL_TEXTENCODING_ASCII_US)); + + if (aRepName == "shared") + { + maRepository = SHARED; + } + else if (aRepName == "bundled") + { + maRepository = BUNDLED; + } + + // check mbEnabled + const beans::Optional< beans::Ambiguous< sal_Bool > > option( + rxPackage->isRegistered(uno::Reference< task::XAbortChannel >(), + uno::Reference< ucb::XCommandEnvironment >())); + + if (option.IsPresent) + { + ::beans::Ambiguous< sal_Bool > const& reg = option.Value; + + if (!reg.IsAmbiguous) + { + mbEnabled = reg.Value; + } + } + } + + bool isSameExtension(const ExtensionInfoEntry& rComp) const + { + return (maRepository == rComp.maRepository && maName == rComp.maName); + } + + bool operator<(const ExtensionInfoEntry& rComp) const + { + if (maRepository == rComp.maRepository) + { + if (maName == rComp.maName) + { + return mbEnabled < rComp.mbEnabled; + } + else + { + return 0 > maName.compareTo(rComp.maName); + } + } + else + { + return maRepository < rComp.maRepository; + } + } + + bool read_entry(FileSharedPtr const & rFile) + { + // read maName + if (!read_OString(rFile, maName)) + { + return false; + } + + // read maRepository + sal_uInt32 nState(0); + + if (read_sal_uInt32(rFile, nState)) + { + maRepository = static_cast< PackageRepository >(nState); + } + else + { + return false; + } + + // read mbEnabled + if (read_sal_uInt32(rFile, nState)) + { + mbEnabled = static_cast< bool >(nState); + } + else + { + return false; + } + + return true; + } + + bool write_entry(oslFileHandle& rHandle) const + { + // write maName; + if (!write_OString(rHandle, maName)) + { + return false; + } + + // write maRepository + sal_uInt32 nState(maRepository); + + if (!write_sal_uInt32(rHandle, nState)) + { + return false; + } + + // write mbEnabled + nState = static_cast< sal_uInt32 >(mbEnabled); + + return write_sal_uInt32(rHandle, nState); + } + + const OString& getName() const + { + return maName; + } + + bool isEnabled() const + { + return mbEnabled; + } + }; + + typedef std::vector< ExtensionInfoEntry > ExtensionInfoEntryVector; + + constexpr OUStringLiteral gaRegPath { u"/registry/com.sun.star.comp.deployment.bundle.PackageRegistryBackend/backenddb.xml" }; + + class ExtensionInfo + { + private: + ExtensionInfoEntryVector maEntries; + + public: + ExtensionInfo() + { + } + + const ExtensionInfoEntryVector& getExtensionInfoEntryVector() const + { + return maEntries; + } + + void reset() + { + // clear all data + maEntries.clear(); + } + + void createUsingXExtensionManager() + { + // clear all data + reset(); + + // create content from current extension configuration + uno::Sequence< uno::Sequence< uno::Reference< deployment::XPackage > > > xAllPackages; + uno::Reference< uno::XComponentContext > xContext = ::comphelper::getProcessComponentContext(); + uno::Reference< deployment::XExtensionManager > m_xExtensionManager = deployment::ExtensionManager::get(xContext); + + try + { + xAllPackages = m_xExtensionManager->getAllExtensions(uno::Reference< task::XAbortChannel >(), + uno::Reference< ucb::XCommandEnvironment >()); + } + catch (const deployment::DeploymentException &) + { + return; + } + catch (const ucb::CommandFailedException &) + { + return; + } + catch (const ucb::CommandAbortedException &) + { + return; + } + catch (const lang::IllegalArgumentException & e) + { + css::uno::Any anyEx = cppu::getCaughtException(); + throw css::lang::WrappedTargetRuntimeException( e.Message, + e.Context, anyEx ); + } + + for (const uno::Sequence< uno::Reference< deployment::XPackage > > & xPackageList : std::as_const(xAllPackages)) + { + for (const uno::Reference< deployment::XPackage > & xPackage : xPackageList) + { + if (xPackage.is()) + { + maEntries.emplace_back(xPackage); + } + } + } + + if (!maEntries.empty()) + { + // sort the list + std::sort(maEntries.begin(), maEntries.end()); + } + } + + private: + void visitNodesXMLRead(const uno::Reference< xml::dom::XElement >& rElement) + { + if (!rElement.is()) + return; + + const OUString aTagName(rElement->getTagName()); + + if (aTagName == "extension") + { + OUString aAttrUrl(rElement->getAttribute("url")); + const OUString aAttrRevoked(rElement->getAttribute("revoked")); + + if (!aAttrUrl.isEmpty()) + { + const sal_Int32 nIndex(aAttrUrl.lastIndexOf('/')); + + if (nIndex > 0 && aAttrUrl.getLength() > nIndex + 1) + { + aAttrUrl = aAttrUrl.copy(nIndex + 1); + } + + const bool bEnabled(aAttrRevoked.isEmpty() || !aAttrRevoked.toBoolean()); + maEntries.emplace_back( + OUStringToOString(aAttrUrl, RTL_TEXTENCODING_ASCII_US), + bEnabled); + } + } + else + { + uno::Reference< xml::dom::XNodeList > aList = rElement->getChildNodes(); + + if (aList.is()) + { + const sal_Int32 nLength(aList->getLength()); + + for (sal_Int32 a(0); a < nLength; a++) + { + const uno::Reference< xml::dom::XElement > aChild(aList->item(a), uno::UNO_QUERY); + + if (aChild.is()) + { + visitNodesXMLRead(aChild); + } + } + } + } + } + + public: + void createUserExtensionRegistryEntriesFromXML(std::u16string_view rUserConfigWorkURL) + { + const OUString aPath( + OUString::Concat(rUserConfigWorkURL) + "/uno_packages/cache" + gaRegPath); + createExtensionRegistryEntriesFromXML(aPath); + } + + void createSharedExtensionRegistryEntriesFromXML(std::u16string_view rUserConfigWorkURL) + { + const OUString aPath( + OUString::Concat(rUserConfigWorkURL) + "/extensions/shared" + gaRegPath); + createExtensionRegistryEntriesFromXML(aPath); + } + + void createBundledExtensionRegistryEntriesFromXML(std::u16string_view rUserConfigWorkURL) + { + const OUString aPath( + OUString::Concat(rUserConfigWorkURL) + "/extensions/bundled" + gaRegPath); + createExtensionRegistryEntriesFromXML(aPath); + } + + + void createExtensionRegistryEntriesFromXML(const OUString& aPath) + { + if (DirectoryHelper::fileExists(aPath)) + { + uno::Reference< uno::XComponentContext > xContext = ::comphelper::getProcessComponentContext(); + uno::Reference< xml::dom::XDocumentBuilder > xBuilder(xml::dom::DocumentBuilder::create(xContext)); + uno::Reference< xml::dom::XDocument > aDocument = xBuilder->parseURI(aPath); + + if (aDocument.is()) + { + visitNodesXMLRead(aDocument->getDocumentElement()); + } + } + + if (!maEntries.empty()) + { + // sort the list + std::sort(maEntries.begin(), maEntries.end()); + } + } + + private: + static bool visitNodesXMLChange( + const OUString& rTagToSearch, + const uno::Reference< xml::dom::XElement >& rElement, + const ExtensionInfoEntryVector& rToBeEnabled, + const ExtensionInfoEntryVector& rToBeDisabled) + { + bool bChanged(false); + + if (rElement.is()) + { + const OUString aTagName(rElement->getTagName()); + + if (aTagName == rTagToSearch) + { + const OString aAttrUrl(OUStringToOString(rElement->getAttribute("url"), RTL_TEXTENCODING_ASCII_US)); + const OUString aAttrRevoked(rElement->getAttribute("revoked")); + const bool bEnabled(aAttrRevoked.isEmpty() || !aAttrRevoked.toBoolean()); + + if (!aAttrUrl.isEmpty()) + { + for (const auto& enable : rToBeEnabled) + { + if (-1 != aAttrUrl.indexOf(enable.getName())) + { + if (!bEnabled) + { + // needs to be enabled + rElement->removeAttribute("revoked"); + bChanged = true; + } + } + } + + for (const auto& disable : rToBeDisabled) + { + if (-1 != aAttrUrl.indexOf(disable.getName())) + { + if (bEnabled) + { + // needs to be disabled + rElement->setAttribute("revoked", "true"); + bChanged = true; + } + } + } + } + } + else + { + uno::Reference< xml::dom::XNodeList > aList = rElement->getChildNodes(); + + if (aList.is()) + { + const sal_Int32 nLength(aList->getLength()); + + for (sal_Int32 a(0); a < nLength; a++) + { + const uno::Reference< xml::dom::XElement > aChild(aList->item(a), uno::UNO_QUERY); + + if (aChild.is()) + { + bChanged |= visitNodesXMLChange( + rTagToSearch, + aChild, + rToBeEnabled, + rToBeDisabled); + } + } + } + } + } + + return bChanged; + } + + static void visitNodesXMLChangeOneCase( + const OUString& rUnoPackagReg, + const OUString& rTagToSearch, + const ExtensionInfoEntryVector& rToBeEnabled, + const ExtensionInfoEntryVector& rToBeDisabled) + { + if (!DirectoryHelper::fileExists(rUnoPackagReg)) + return; + + uno::Reference< uno::XComponentContext > xContext = ::comphelper::getProcessComponentContext(); + uno::Reference< xml::dom::XDocumentBuilder > xBuilder = xml::dom::DocumentBuilder::create(xContext); + uno::Reference< xml::dom::XDocument > aDocument = xBuilder->parseURI(rUnoPackagReg); + + if (!aDocument.is()) + return; + + if (!visitNodesXMLChange( + rTagToSearch, + aDocument->getDocumentElement(), + rToBeEnabled, + rToBeDisabled)) + return; + + // did change - write back + uno::Reference< xml::sax::XSAXSerializable > xSerializer(aDocument, uno::UNO_QUERY); + + if (!xSerializer.is()) + return; + + // create a SAXWriter + uno::Reference< xml::sax::XWriter > const xSaxWriter = xml::sax::Writer::create(xContext); + uno::Reference< io::XTempFile > xTempFile = io::TempFile::create(xContext); + uno::Reference< io::XOutputStream > xOutStrm = xTempFile->getOutputStream(); + + // set output stream and do the serialization + xSaxWriter->setOutputStream(xOutStrm); + xSerializer->serialize(xSaxWriter, uno::Sequence< beans::StringPair >()); + + // get URL from temp file + OUString aTempURL = xTempFile->getUri(); + + // copy back file + if (aTempURL.isEmpty() || !DirectoryHelper::fileExists(aTempURL)) + return; + + if (DirectoryHelper::fileExists(rUnoPackagReg)) + { + osl::File::remove(rUnoPackagReg); + } + +#if OSL_DEBUG_LEVEL > 1 + SAL_WARN_IF(osl::FileBase::E_None != osl::File::move(aTempURL, rUnoPackagReg), "comphelper.backupfilehelper", "could not copy back modified Extension configuration file"); +#else + osl::File::move(aTempURL, rUnoPackagReg); +#endif + } + + public: + static void changeEnableDisableStateInXML( + std::u16string_view rUserConfigWorkURL, + const ExtensionInfoEntryVector& rToBeEnabled, + const ExtensionInfoEntryVector& rToBeDisabled) + { + static const OUStringLiteral aRegPathFront(u"/uno_packages/cache/registry/com.sun.star.comp.deployment."); + static const OUStringLiteral aRegPathBack(u".PackageRegistryBackend/backenddb.xml"); + // first appearance to check + { + const OUString aUnoPackagReg(OUString::Concat(rUserConfigWorkURL) + aRegPathFront + "bundle" + aRegPathBack); + + visitNodesXMLChangeOneCase( + aUnoPackagReg, + "extension", + rToBeEnabled, + rToBeDisabled); + } + + // second appearance to check + { + const OUString aUnoPackagReg(OUString::Concat(rUserConfigWorkURL) + aRegPathFront + "configuration" + aRegPathBack); + + visitNodesXMLChangeOneCase( + aUnoPackagReg, + "configuration", + rToBeEnabled, + rToBeDisabled); + } + + // third appearance to check + { + const OUString aUnoPackagReg(OUString::Concat(rUserConfigWorkURL) + aRegPathFront + "script" + aRegPathBack); + + visitNodesXMLChangeOneCase( + aUnoPackagReg, + "script", + rToBeEnabled, + rToBeDisabled); + } + } + + bool read_entries(FileSharedPtr const & rFile) + { + // read NumExtensionEntries + sal_uInt32 nExtEntries(0); + + if (!read_sal_uInt32(rFile, nExtEntries)) + { + return false; + } + + // coverity#1373663 Untrusted loop bound, check file size + // isn't utterly broken + sal_uInt64 nFileSize(0); + rFile->getSize(nFileSize); + if (nFileSize < nExtEntries) + return false; + + for (sal_uInt32 a(0); a < nExtEntries; a++) + { + ExtensionInfoEntry aNewEntry; + + if (aNewEntry.read_entry(rFile)) + { + maEntries.push_back(aNewEntry); + } + else + { + return false; + } + } + + return true; + } + + bool write_entries(oslFileHandle& rHandle) const + { + const sal_uInt32 nExtEntries(maEntries.size()); + + if (!write_sal_uInt32(rHandle, nExtEntries)) + { + return false; + } + + for (const auto& a : maEntries) + { + if (!a.write_entry(rHandle)) + { + return false; + } + } + + return true; + } + + bool createTempFile(OUString& rTempFileName) + { + oslFileHandle aHandle; + bool bRetval(false); + + // create current configuration + if (maEntries.empty()) + { + createUsingXExtensionManager(); + } + + // open target temp file and write current configuration to it - it exists until deleted + if (osl::File::E_None == osl::FileBase::createTempFile(nullptr, &aHandle, &rTempFileName)) + { + bRetval = write_entries(aHandle); + + // close temp file - it exists until deleted + osl_closeFile(aHandle); + } + + return bRetval; + } + + bool areThereEnabledExtensions() const + { + for (const auto& a : maEntries) + { + if (a.isEnabled()) + { + return true; + } + } + + return false; + } + }; +} + +namespace +{ + class PackedFileEntry + { + private: + sal_uInt32 mnFullFileSize; // size in bytes of unpacked original file + sal_uInt32 mnPackFileSize; // size in bytes in file backup package (smaller if compressed, same if not) + sal_uInt32 mnOffset; // offset in File (zero identifies new file) + sal_uInt32 mnCrc32; // checksum + FileSharedPtr maFile; // file where to find the data (at offset) + bool const mbDoCompress; // flag if this file is scheduled to be compressed when written + + bool copy_content_straight(oslFileHandle& rTargetHandle) + { + if (!maFile || osl::File::E_None != maFile->open(osl_File_OpenFlag_Read)) + return false; + + sal_uInt8 aArray[BACKUP_FILE_HELPER_BLOCK_SIZE]; + sal_uInt64 nBytesTransfer(0); + sal_uInt64 nSize(getPackFileSize()); + + // set offset in source file - when this is zero, a new file is to be added + if (osl::File::E_None == maFile->setPos(osl_Pos_Absolut, sal_Int64(getOffset()))) + { + while (nSize != 0) + { + const sal_uInt64 nToTransfer(std::min(nSize, sal_uInt64(BACKUP_FILE_HELPER_BLOCK_SIZE))); + + if (osl::File::E_None != maFile->read(static_cast<void*>(aArray), nToTransfer, nBytesTransfer) || nBytesTransfer != nToTransfer) + { + break; + } + + if (osl_File_E_None != osl_writeFile(rTargetHandle, static_cast<const void*>(aArray), nToTransfer, &nBytesTransfer) || nBytesTransfer != nToTransfer) + { + break; + } + + nSize -= nToTransfer; + } + } + + maFile->close(); + return (0 == nSize); + } + + bool copy_content_compress(oslFileHandle& rTargetHandle) + { + if (!maFile || osl::File::E_None != maFile->open(osl_File_OpenFlag_Read)) + return false; + + sal_uInt8 aArray[BACKUP_FILE_HELPER_BLOCK_SIZE]; + sal_uInt8 aBuffer[BACKUP_FILE_HELPER_BLOCK_SIZE]; + sal_uInt64 nBytesTransfer(0); + sal_uInt64 nSize(getPackFileSize()); + z_stream zstream; + memset(&zstream, 0, sizeof(zstream)); + + if (Z_OK == deflateInit(&zstream, Z_BEST_COMPRESSION)) + { + // set offset in source file - when this is zero, a new file is to be added + if (osl::File::E_None == maFile->setPos(osl_Pos_Absolut, sal_Int64(getOffset()))) + { + bool bOkay(true); + + while (bOkay && nSize != 0) + { + const sal_uInt64 nToTransfer(std::min(nSize, sal_uInt64(BACKUP_FILE_HELPER_BLOCK_SIZE))); + + if (osl::File::E_None != maFile->read(static_cast<void*>(aArray), nToTransfer, nBytesTransfer) || nBytesTransfer != nToTransfer) + { + break; + } + + zstream.avail_in = nToTransfer; + zstream.next_in = reinterpret_cast<unsigned char*>(aArray); + + do { + zstream.avail_out = BACKUP_FILE_HELPER_BLOCK_SIZE; + zstream.next_out = reinterpret_cast<unsigned char*>(aBuffer); +#if !defined Z_PREFIX + const sal_Int64 nRetval(deflate(&zstream, nSize == nToTransfer ? Z_FINISH : Z_NO_FLUSH)); +#else + const sal_Int64 nRetval(z_deflate(&zstream, nSize == nToTransfer ? Z_FINISH : Z_NO_FLUSH)); +#endif + if (Z_STREAM_ERROR == nRetval) + { + bOkay = false; + } + else + { + const sal_uInt64 nAvailable(BACKUP_FILE_HELPER_BLOCK_SIZE - zstream.avail_out); + + if (osl_File_E_None != osl_writeFile(rTargetHandle, static_cast<const void*>(aBuffer), nAvailable, &nBytesTransfer) || nBytesTransfer != nAvailable) + { + bOkay = false; + } + } + } while (bOkay && 0 == zstream.avail_out); + + if (!bOkay) + { + break; + } + + nSize -= nToTransfer; + } + +#if !defined Z_PREFIX + deflateEnd(&zstream); +#else + z_deflateEnd(&zstream); +#endif + } + } + + maFile->close(); + + // get compressed size and add to entry + if (mnFullFileSize == mnPackFileSize && mnFullFileSize == zstream.total_in) + { + mnPackFileSize = zstream.total_out; + } + + return (0 == nSize); + } + + bool copy_content_uncompress(oslFileHandle& rTargetHandle) + { + if (!maFile || osl::File::E_None != maFile->open(osl_File_OpenFlag_Read)) + return false; + + sal_uInt8 aArray[BACKUP_FILE_HELPER_BLOCK_SIZE]; + sal_uInt8 aBuffer[BACKUP_FILE_HELPER_BLOCK_SIZE]; + sal_uInt64 nBytesTransfer(0); + sal_uInt64 nSize(getPackFileSize()); + z_stream zstream; + memset(&zstream, 0, sizeof(zstream)); + + if (Z_OK == inflateInit(&zstream)) + { + // set offset in source file - when this is zero, a new file is to be added + if (osl::File::E_None == maFile->setPos(osl_Pos_Absolut, sal_Int64(getOffset()))) + { + bool bOkay(true); + + while (bOkay && nSize != 0) + { + const sal_uInt64 nToTransfer(std::min(nSize, sal_uInt64(BACKUP_FILE_HELPER_BLOCK_SIZE))); + + if (osl::File::E_None != maFile->read(static_cast<void*>(aArray), nToTransfer, nBytesTransfer) || nBytesTransfer != nToTransfer) + { + break; + } + + zstream.avail_in = nToTransfer; + zstream.next_in = reinterpret_cast<unsigned char*>(aArray); + + do { + zstream.avail_out = BACKUP_FILE_HELPER_BLOCK_SIZE; + zstream.next_out = reinterpret_cast<unsigned char*>(aBuffer); +#if !defined Z_PREFIX + const sal_Int64 nRetval(inflate(&zstream, Z_NO_FLUSH)); +#else + const sal_Int64 nRetval(z_inflate(&zstream, Z_NO_FLUSH)); +#endif + if (Z_STREAM_ERROR == nRetval) + { + bOkay = false; + } + else + { + const sal_uInt64 nAvailable(BACKUP_FILE_HELPER_BLOCK_SIZE - zstream.avail_out); + + if (osl_File_E_None != osl_writeFile(rTargetHandle, static_cast<const void*>(aBuffer), nAvailable, &nBytesTransfer) || nBytesTransfer != nAvailable) + { + bOkay = false; + } + } + } while (bOkay && 0 == zstream.avail_out); + + if (!bOkay) + { + break; + } + + nSize -= nToTransfer; + } + +#if !defined Z_PREFIX + deflateEnd(&zstream); +#else + z_deflateEnd(&zstream); +#endif + } + } + + maFile->close(); + return (0 == nSize); + } + + + public: + // create new, uncompressed entry + PackedFileEntry( + sal_uInt32 nFullFileSize, + sal_uInt32 nCrc32, + FileSharedPtr xFile, + bool bDoCompress) + : mnFullFileSize(nFullFileSize), + mnPackFileSize(nFullFileSize), + mnOffset(0), + mnCrc32(nCrc32), + maFile(std::move(xFile)), + mbDoCompress(bDoCompress) + { + } + + // create entry to be loaded as header (read_header) + PackedFileEntry() + : mnFullFileSize(0), + mnPackFileSize(0), + mnOffset(0), + mnCrc32(0), + mbDoCompress(false) + { + } + + sal_uInt32 getFullFileSize() const + { + return mnFullFileSize; + } + + sal_uInt32 getPackFileSize() const + { + return mnPackFileSize; + } + + sal_uInt32 getOffset() const + { + return mnOffset; + } + + void setOffset(sal_uInt32 nOffset) + { + mnOffset = nOffset; + } + + static sal_uInt32 getEntrySize() + { + return 12; + } + + sal_uInt32 getCrc32() const + { + return mnCrc32; + } + + bool read_header(FileSharedPtr const & rFile) + { + if (!rFile) + { + return false; + } + + maFile = rFile; + + // read and compute full file size + if (!read_sal_uInt32(rFile, mnFullFileSize)) + { + return false; + } + + // read and compute entry crc32 + if (!read_sal_uInt32(rFile, mnCrc32)) + { + return false; + } + + // read and compute packed size + if (!read_sal_uInt32(rFile, mnPackFileSize)) + { + return false; + } + + return true; + } + + bool write_header(oslFileHandle& rHandle) const + { + // write full file size + if (!write_sal_uInt32(rHandle, mnFullFileSize)) + { + return false; + } + + // write crc32 + if (!write_sal_uInt32(rHandle, mnCrc32)) + { + return false; + } + + // write packed file size + if (!write_sal_uInt32(rHandle, mnPackFileSize)) + { + return false; + } + + return true; + } + + bool copy_content(oslFileHandle& rTargetHandle, bool bUncompress) + { + if (bUncompress) + { + if (getFullFileSize() == getPackFileSize()) + { + // not compressed, just copy + return copy_content_straight(rTargetHandle); + } + else + { + // compressed, need to uncompress on copy + return copy_content_uncompress(rTargetHandle); + } + } + else if (0 == getOffset()) + { + if (mbDoCompress) + { + // compressed wanted, need to compress on copy + return copy_content_compress(rTargetHandle); + } + else + { + // not compressed, straight copy + return copy_content_straight(rTargetHandle); + } + } + else + { + return copy_content_straight(rTargetHandle); + } + } + }; +} + +namespace +{ + class PackedFile + { + private: + const OUString maURL; + std::deque< PackedFileEntry > + maPackedFileEntryVector; + bool mbChanged; + + public: + PackedFile(const OUString& rURL) + : maURL(rURL), + mbChanged(false) + { + FileSharedPtr aSourceFile = std::make_shared<osl::File>(rURL); + + if (osl::File::E_None == aSourceFile->open(osl_File_OpenFlag_Read)) + { + sal_uInt64 nBaseLen(0); + aSourceFile->getSize(nBaseLen); + + // we need at least File_ID and num entries -> 8byte + if (8 < nBaseLen) + { + sal_uInt8 aArray[4]; + sal_uInt64 nBaseRead(0); + + // read and check File_ID + if (osl::File::E_None == aSourceFile->read(static_cast< void* >(aArray), 4, nBaseRead) && 4 == nBaseRead) + { + if ('P' == aArray[0] && 'A' == aArray[1] && 'C' == aArray[2] && 'K' == aArray[3]) + { + // read and compute num entries in this file + if (osl::File::E_None == aSourceFile->read(static_cast<void*>(aArray), 4, nBaseRead) && 4 == nBaseRead) + { + sal_uInt32 nEntries((sal_uInt32(aArray[0]) << 24) + (sal_uInt32(aArray[1]) << 16) + (sal_uInt32(aArray[2]) << 8) + sal_uInt32(aArray[3])); + + // if there are entries (and less than max), read them + if (nEntries >= 1 && nEntries <= 10) + { + for (sal_uInt32 a(0); a < nEntries; a++) + { + // create new entry, read header (size, crc and PackedSize), + // set offset and source file + PackedFileEntry aEntry; + + if (aEntry.read_header(aSourceFile)) + { + // add to local data + maPackedFileEntryVector.push_back(aEntry); + } + else + { + // error + nEntries = 0; + } + } + + if (0 == nEntries) + { + // on read error clear local data + maPackedFileEntryVector.clear(); + } + else + { + // calculate and set offsets to file binary content + sal_uInt32 nHeaderSize(8); + + nHeaderSize += maPackedFileEntryVector.size() * PackedFileEntry::getEntrySize(); + + sal_uInt32 nOffset(nHeaderSize); + + for (auto& b : maPackedFileEntryVector) + { + b.setOffset(nOffset); + nOffset += b.getPackFileSize(); + } + } + } + } + } + } + } + + aSourceFile->close(); + } + + if (maPackedFileEntryVector.empty()) + { + // on error or no data get rid of pack file + osl::File::remove(maURL); + } + } + + void flush() + { + bool bRetval(true); + + if (maPackedFileEntryVector.empty()) + { + // get rid of (now?) empty pack file + osl::File::remove(maURL); + } + else if (mbChanged) + { + // need to create a new pack file, do this in a temp file to which data + // will be copied from local file (so keep it here until this is done) + oslFileHandle aHandle = nullptr; + OUString aTempURL; + + // open target temp file - it exists until deleted + if (osl::File::E_None == osl::FileBase::createTempFile(nullptr, &aHandle, &aTempURL)) + { + sal_uInt8 aArray[4]; + sal_uInt64 nBaseWritten(0); + + aArray[0] = 'P'; + aArray[1] = 'A'; + aArray[2] = 'C'; + aArray[3] = 'K'; + + // write File_ID + if (osl_File_E_None == osl_writeFile(aHandle, static_cast<const void*>(aArray), 4, &nBaseWritten) && 4 == nBaseWritten) + { + const sal_uInt32 nSize(maPackedFileEntryVector.size()); + + // write number of entries + if (write_sal_uInt32(aHandle, nSize)) + { + // write placeholder for headers. Due to the fact that + // PackFileSize for newly added files gets set during + // writing the content entry, write headers after content + // is written. To do so, write placeholders here + sal_uInt32 nWriteSize(0); + + nWriteSize += maPackedFileEntryVector.size() * PackedFileEntry::getEntrySize(); + + aArray[0] = aArray[1] = aArray[2] = aArray[3] = 0; + + for (sal_uInt32 a(0); bRetval && a < nWriteSize; a++) + { + if (osl_File_E_None != osl_writeFile(aHandle, static_cast<const void*>(aArray), 1, &nBaseWritten) || 1 != nBaseWritten) + { + bRetval = false; + } + } + + if (bRetval) + { + // write contents - this may adapt PackFileSize for new + // files + for (auto& candidate : maPackedFileEntryVector) + { + if (!candidate.copy_content(aHandle, false)) + { + bRetval = false; + break; + } + } + } + + if (bRetval) + { + // seek back to header start (at position 8) + if (osl_File_E_None != osl_setFilePos(aHandle, osl_Pos_Absolut, sal_Int64(8))) + { + bRetval = false; + } + } + + if (bRetval) + { + // write headers + for (const auto& candidate : maPackedFileEntryVector) + { + if (!candidate.write_header(aHandle)) + { + // error + bRetval = false; + break; + } + } + } + } + } + } + + // close temp file (in all cases) - it exists until deleted + osl_closeFile(aHandle); + + if (bRetval) + { + // copy over existing file by first deleting original + // and moving the temp file to old original + osl::File::remove(maURL); + osl::File::move(aTempURL, maURL); + } + + // delete temp file (in all cases - it may be moved already) + osl::File::remove(aTempURL); + } + } + + bool tryPush(FileSharedPtr const & rFileCandidate, bool bCompress) + { + sal_uInt64 nFileSize(0); + + if (rFileCandidate && osl::File::E_None == rFileCandidate->open(osl_File_OpenFlag_Read)) + { + rFileCandidate->getSize(nFileSize); + rFileCandidate->close(); + } + + if (0 == nFileSize) + { + // empty file offered + return false; + } + + bool bNeedToAdd(false); + sal_uInt32 nCrc32(0); + + if (maPackedFileEntryVector.empty()) + { + // no backup yet, add as 1st backup + bNeedToAdd = true; + } + else + { + // already backups there, check if different from last entry + const PackedFileEntry& aLastEntry = maPackedFileEntryVector.back(); + + // check if file is different + if (aLastEntry.getFullFileSize() != static_cast<sal_uInt32>(nFileSize)) + { + // different size, different file + bNeedToAdd = true; + } + else + { + // same size, check crc32 + nCrc32 = createCrc32(rFileCandidate, 0); + + if (nCrc32 != aLastEntry.getCrc32()) + { + // different crc, different file + bNeedToAdd = true; + } + } + } + + if (bNeedToAdd) + { + // create crc32 if not yet done + if (0 == nCrc32) + { + nCrc32 = createCrc32(rFileCandidate, 0); + } + + // create a file entry for a new file. Offset is set automatically + // to 0 to mark the entry as new file entry + maPackedFileEntryVector.emplace_back( + static_cast< sal_uInt32 >(nFileSize), + nCrc32, + rFileCandidate, + bCompress); + + mbChanged = true; + } + + return bNeedToAdd; + } + + bool tryPop(oslFileHandle& rHandle) + { + if (maPackedFileEntryVector.empty()) + return false; + + // already backups there, check if different from last entry + PackedFileEntry& aLastEntry = maPackedFileEntryVector.back(); + + // here the uncompress flag has to be determined, true + // means to add the file compressed, false means to add it + // uncompressed + bool bRetval = aLastEntry.copy_content(rHandle, true); + + if (bRetval) + { + maPackedFileEntryVector.pop_back(); + mbChanged = true; + } + + return bRetval; + } + + void tryReduceToNumBackups(sal_uInt16 nNumBackups) + { + while (maPackedFileEntryVector.size() > nNumBackups) + { + maPackedFileEntryVector.pop_front(); + mbChanged = true; + } + } + + bool empty() const + { + return maPackedFileEntryVector.empty(); + } + }; +} + +namespace comphelper +{ + sal_uInt16 BackupFileHelper::mnMaxAllowedBackups = 10; + bool BackupFileHelper::mbExitWasCalled = false; + bool BackupFileHelper::mbSafeModeDirExists = false; + OUString BackupFileHelper::maInitialBaseURL; + OUString BackupFileHelper::maUserConfigBaseURL; + OUString BackupFileHelper::maUserConfigWorkURL; + OUString BackupFileHelper::maRegModName; + OUString BackupFileHelper::maExt; + + const OUString& BackupFileHelper::getInitialBaseURL() + { + if (maInitialBaseURL.isEmpty()) + { + // try to access user layer configuration file URL, the one that + // points to registrymodifications.xcu + OUString conf("${CONFIGURATION_LAYERS}"); + rtl::Bootstrap::expandMacros(conf); + static const OUStringLiteral aTokenUser(u"user:"); + sal_Int32 nStart(conf.indexOf(aTokenUser)); + + if (-1 != nStart) + { + nStart += aTokenUser.getLength(); + sal_Int32 nEnd(conf.indexOf(' ', nStart)); + + if (-1 == nEnd) + { + nEnd = conf.getLength(); + } + + maInitialBaseURL = conf.copy(nStart, nEnd - nStart); + (void)maInitialBaseURL.startsWith("!", &maInitialBaseURL); + } + + if (!maInitialBaseURL.isEmpty()) + { + // split URL at extension and at last path separator + maUserConfigBaseURL = DirectoryHelper::splitAtLastToken( + DirectoryHelper::splitAtLastToken(maInitialBaseURL, '.', maExt), '/', + maRegModName); + } + + if (!maUserConfigBaseURL.isEmpty()) + { + // check if SafeModeDir exists + mbSafeModeDirExists = DirectoryHelper::dirExists(maUserConfigBaseURL + "/" + getSafeModeName()); + } + + maUserConfigWorkURL = maUserConfigBaseURL; + + if (mbSafeModeDirExists) + { + // adapt work URL to do all repair op's in the correct directory + maUserConfigWorkURL += "/" + getSafeModeName(); + } + } + + return maInitialBaseURL; + } + + const OUString& BackupFileHelper::getSafeModeName() + { + static const OUString aSafeMode("SafeMode"); + + return aSafeMode; + } + + BackupFileHelper::BackupFileHelper() + : mnNumBackups(2), + mnMode(1), + mbActive(false), + mbExtensions(true), + mbCompress(true) + { + OUString sTokenOut; + + // read configuration item 'SecureUserConfig' -> bool on/off + if (rtl::Bootstrap::get("SecureUserConfig", sTokenOut)) + { + mbActive = sTokenOut.toBoolean(); + } + + if (mbActive) + { + // ensure existence + getInitialBaseURL(); + + // if not found, we are out of business (maExt may be empty) + mbActive = !maInitialBaseURL.isEmpty() && !maUserConfigBaseURL.isEmpty() && !maRegModName.isEmpty(); + } + + if (mbActive && rtl::Bootstrap::get("SecureUserConfigNumCopies", sTokenOut)) + { + const sal_uInt16 nConfigNumCopies(static_cast<sal_uInt16>(sTokenOut.toUInt32())); + + // limit to range [1..mnMaxAllowedBackups] + mnNumBackups = std::clamp(mnNumBackups, nConfigNumCopies, mnMaxAllowedBackups); + } + + if (mbActive && rtl::Bootstrap::get("SecureUserConfigMode", sTokenOut)) + { + const sal_uInt16 nMode(static_cast<sal_uInt16>(sTokenOut.toUInt32())); + + // limit to range [0..2] + mnMode = std::min(nMode, sal_uInt16(2)); + } + + if (mbActive && rtl::Bootstrap::get("SecureUserConfigExtensions", sTokenOut)) + { + mbExtensions = sTokenOut.toBoolean(); + } + + if (mbActive && rtl::Bootstrap::get("SecureUserConfigCompress", sTokenOut)) + { + mbCompress = sTokenOut.toBoolean(); + } + } + + void BackupFileHelper::setExitWasCalled() + { + mbExitWasCalled = true; + } + + bool BackupFileHelper::getExitWasCalled() + { + return mbExitWasCalled; + } + + void BackupFileHelper::reactOnSafeMode(bool bSafeMode) + { + // ensure existence of needed paths + getInitialBaseURL(); + + if (maUserConfigBaseURL.isEmpty()) + return; + + if (bSafeMode) + { + if (!mbSafeModeDirExists) + { + std::set< OUString > aExcludeList; + + // do not move SafeMode directory itself + aExcludeList.insert(getSafeModeName()); + + // init SafeMode by creating the 'SafeMode' directory and moving + // all stuff there. All repairs will happen there. Both Dirs have to exist. + // extend maUserConfigWorkURL as needed + maUserConfigWorkURL = maUserConfigBaseURL + "/" + getSafeModeName(); + + osl::Directory::createPath(maUserConfigWorkURL); + DirectoryHelper::moveDirContent(maUserConfigBaseURL, maUserConfigWorkURL, aExcludeList); + + // switch local flag, maUserConfigWorkURL is already reset + mbSafeModeDirExists = true; + } + } + else + { + if (mbSafeModeDirExists) + { + // SafeMode has ended, return to normal mode by moving all content + // from 'SafeMode' directory back to UserDirectory and deleting it. + // Both Dirs have to exist + std::set< OUString > aExcludeList; + + DirectoryHelper::moveDirContent(maUserConfigWorkURL, maUserConfigBaseURL, aExcludeList); + osl::Directory::remove(maUserConfigWorkURL); + + // switch local flag and reset maUserConfigWorkURL + mbSafeModeDirExists = false; + maUserConfigWorkURL = maUserConfigBaseURL; + } + } + } + + void BackupFileHelper::tryPush() + { + // no push when SafeModeDir exists, it may be Office's exit after SafeMode + // where SafeMode flag is already deleted, but SafeModeDir cleanup is not + // done yet (is done at next startup) + if (!mbActive || mbSafeModeDirExists) + return; + + const OUString aPackURL(getPackURL()); + + // ensure dir and file vectors + fillDirFileInfo(); + + // process all files in question recursively + if (!maDirs.empty() || !maFiles.empty()) + { + tryPush_Files( + maDirs, + maFiles, + maUserConfigWorkURL, + aPackURL); + } + } + + void BackupFileHelper::tryPushExtensionInfo() + { + // no push when SafeModeDir exists, it may be Office's exit after SafeMode + // where SafeMode flag is already deleted, but SafeModeDir cleanup is not + // done yet (is done at next startup) + if (mbActive && mbExtensions && !mbSafeModeDirExists) + { + const OUString aPackURL(getPackURL()); + + tryPush_extensionInfo(aPackURL); + } + } + + bool BackupFileHelper::isPopPossible() + { + bool bPopPossible(false); + + if (mbActive) + { + const OUString aPackURL(getPackURL()); + + // ensure dir and file vectors + fillDirFileInfo(); + + // process all files in question recursively + if (!maDirs.empty() || !maFiles.empty()) + { + bPopPossible = isPopPossible_files( + maDirs, + maFiles, + maUserConfigWorkURL, + aPackURL); + } + } + + return bPopPossible; + } + + void BackupFileHelper::tryPop() + { + if (!mbActive) + return; + + bool bDidPop(false); + const OUString aPackURL(getPackURL()); + + // ensure dir and file vectors + fillDirFileInfo(); + + // process all files in question recursively + if (!maDirs.empty() || !maFiles.empty()) + { + bDidPop = tryPop_files( + maDirs, + maFiles, + maUserConfigWorkURL, + aPackURL); + } + + if (bDidPop) + { + // try removal of evtl. empty directory + osl::Directory::remove(aPackURL); + } + } + + bool BackupFileHelper::isPopPossibleExtensionInfo() const + { + bool bPopPossible(false); + + if (mbActive && mbExtensions) + { + const OUString aPackURL(getPackURL()); + + bPopPossible = isPopPossible_extensionInfo(aPackURL); + } + + return bPopPossible; + } + + void BackupFileHelper::tryPopExtensionInfo() + { + if (!(mbActive && mbExtensions)) + return; + + bool bDidPop(false); + const OUString aPackURL(getPackURL()); + + bDidPop = tryPop_extensionInfo(aPackURL); + + if (bDidPop) + { + // try removal of evtl. empty directory + osl::Directory::remove(aPackURL); + } + } + + bool BackupFileHelper::isTryDisableAllExtensionsPossible() + { + // check if there are still enabled extension which can be disabled, + // but as we are now in SafeMode, use XML infos for this since the + // extensions are not loaded from XExtensionManager + class ExtensionInfo aExtensionInfo; + + aExtensionInfo.createUserExtensionRegistryEntriesFromXML(maUserConfigWorkURL); + + return aExtensionInfo.areThereEnabledExtensions(); + } + + void BackupFileHelper::tryDisableAllExtensions() + { + // disable all still enabled extensions, + // but as we are now in SafeMode, use XML infos for this since the + // extensions are not loaded from XExtensionManager + ExtensionInfo aCurrentExtensionInfo; + const ExtensionInfoEntryVector aToBeEnabled{}; + ExtensionInfoEntryVector aToBeDisabled; + + aCurrentExtensionInfo.createUserExtensionRegistryEntriesFromXML(maUserConfigWorkURL); + + const ExtensionInfoEntryVector& rCurrentVector = aCurrentExtensionInfo.getExtensionInfoEntryVector(); + + for (const auto& rCurrentInfo : rCurrentVector) + { + if (rCurrentInfo.isEnabled()) + { + aToBeDisabled.push_back(rCurrentInfo); + } + } + + ExtensionInfo::changeEnableDisableStateInXML(maUserConfigWorkURL, aToBeEnabled, aToBeDisabled); + } + + bool BackupFileHelper::isTryDeinstallUserExtensionsPossible() + { + // check if there are User Extensions installed. + class ExtensionInfo aExtensionInfo; + + aExtensionInfo.createUserExtensionRegistryEntriesFromXML(maUserConfigWorkURL); + + return !aExtensionInfo.getExtensionInfoEntryVector().empty(); + } + + void BackupFileHelper::tryDeinstallUserExtensions() + { + // delete User Extension installs + DirectoryHelper::deleteDirRecursively(maUserConfigWorkURL + "/uno_packages"); + } + + bool BackupFileHelper::isTryResetSharedExtensionsPossible() + { + // check if there are shared Extensions installed + class ExtensionInfo aExtensionInfo; + + aExtensionInfo.createSharedExtensionRegistryEntriesFromXML(maUserConfigWorkURL); + + return !aExtensionInfo.getExtensionInfoEntryVector().empty(); + } + + void BackupFileHelper::tryResetSharedExtensions() + { + // reset shared extension info + DirectoryHelper::deleteDirRecursively(maUserConfigWorkURL + "/extensions/shared"); + } + + bool BackupFileHelper::isTryResetBundledExtensionsPossible() + { + // check if there are shared Extensions installed + class ExtensionInfo aExtensionInfo; + + aExtensionInfo.createBundledExtensionRegistryEntriesFromXML(maUserConfigWorkURL); + + return !aExtensionInfo.getExtensionInfoEntryVector().empty(); + } + + void BackupFileHelper::tryResetBundledExtensions() + { + // reset shared extension info + DirectoryHelper::deleteDirRecursively(maUserConfigWorkURL + "/extensions/bundled"); + } + + const std::vector< OUString >& BackupFileHelper::getCustomizationDirNames() + { + static std::vector< OUString > aDirNames = + { + "config", // UI config stuff + "registry", // most of the registry stuff + "psprint", // not really needed, can be abandoned + "store", // not really needed, can be abandoned + "temp", // not really needed, can be abandoned + "pack" // own backup dir + }; + + return aDirNames; + } + + const std::vector< OUString >& BackupFileHelper::getCustomizationFileNames() + { + static std::vector< OUString > aFileNames = + { + "registrymodifications.xcu" // personal registry stuff + }; + + return aFileNames; + } + + namespace { + uno::Reference<XElement> lcl_getConfigElement(const uno::Reference<XDocument>& xDocument, const OUString& rPath, + const OUString& rKey, const OUString& rValue) + { + uno::Reference< XElement > itemElement = xDocument->createElement("item"); + itemElement->setAttribute("oor:path", rPath); + + uno::Reference< XElement > propElement = xDocument->createElement("prop"); + propElement->setAttribute("oor:name", rKey); + propElement->setAttribute("oor:op", "replace"); // Replace any other options + + uno::Reference< XElement > valueElement = xDocument->createElement("value"); + uno::Reference< XText > textElement = xDocument->createTextNode(rValue); + + valueElement->appendChild(textElement); + propElement->appendChild(valueElement); + itemElement->appendChild(propElement); + + return itemElement; + } + } + + void BackupFileHelper::tryDisableHWAcceleration() + { + const OUString aRegistryModifications(maUserConfigWorkURL + "/registrymodifications.xcu"); + if (!DirectoryHelper::fileExists(aRegistryModifications)) + return; + + uno::Reference< uno::XComponentContext > xContext = ::comphelper::getProcessComponentContext(); + uno::Reference< XDocumentBuilder > xBuilder = DocumentBuilder::create(xContext); + uno::Reference< XDocument > xDocument = xBuilder->parseURI(aRegistryModifications); + uno::Reference< XElement > xRootElement = xDocument->getDocumentElement(); + + xRootElement->appendChild(lcl_getConfigElement(xDocument, "/org.openoffice.Office.Common/VCL", + "DisableOpenGL", "true")); + xRootElement->appendChild(lcl_getConfigElement(xDocument, "/org.openoffice.Office.Common/Misc", + "UseOpenCL", "false")); + // Do not disable Skia entirely, just force its CPU-based raster mode. + xRootElement->appendChild(lcl_getConfigElement(xDocument, "/org.openoffice.Office.Common/VCL", + "ForceSkia", "false")); + xRootElement->appendChild(lcl_getConfigElement(xDocument, "/org.openoffice.Office.Common/VCL", + "ForceSkiaRaster", "true")); + + OUString aTempURL; + { + // use the scope to make sure that the temp file gets properly closed before move + + // write back + uno::Reference< xml::sax::XSAXSerializable > xSerializer(xDocument, uno::UNO_QUERY); + + if (!xSerializer.is()) + return; + + // create a SAXWriter + uno::Reference< xml::sax::XWriter > const xSaxWriter = xml::sax::Writer::create(xContext); + uno::Reference< io::XTempFile > xTempFile = io::TempFile::create(xContext); + xTempFile->setRemoveFile(false); // avoid removal of tempfile when leaving the scope + uno::Reference< io::XOutputStream > xOutStrm = xTempFile->getOutputStream(); + + // set output stream and do the serialization + xSaxWriter->setOutputStream(xOutStrm); + xSerializer->serialize(xSaxWriter, uno::Sequence< beans::StringPair >()); + + // get URL from temp file + aTempURL = xTempFile->getUri(); + } + + // copy back file + if (aTempURL.isEmpty() || !DirectoryHelper::fileExists(aTempURL)) + return; + + if (DirectoryHelper::fileExists(aRegistryModifications)) + { + osl::File::remove(aRegistryModifications); + } + + int result = osl::File::move(aTempURL, aRegistryModifications); + SAL_WARN_IF(result != osl::FileBase::E_None, "comphelper.backupfilehelper", "could not copy back modified Extension configuration file"); + } + + bool BackupFileHelper::isTryResetCustomizationsPossible() + { + // return true if not all of the customization selection dirs or files are deleted + const std::vector< OUString >& rDirs = getCustomizationDirNames(); + + for (const auto& a : rDirs) + { + if (DirectoryHelper::dirExists(maUserConfigWorkURL + "/" + a)) + { + return true; + } + } + + const std::vector< OUString >& rFiles = getCustomizationFileNames(); + + for (const auto& b : rFiles) + { + if (DirectoryHelper::fileExists(maUserConfigWorkURL + "/" + b)) + { + return true; + } + } + + return false; + } + + void BackupFileHelper::tryResetCustomizations() + { + // delete all of the customization selection dirs + const std::vector< OUString >& rDirs = getCustomizationDirNames(); + + for (const auto& a : rDirs) + { + DirectoryHelper::deleteDirRecursively(maUserConfigWorkURL + "/" + a); + } + + const std::vector< OUString >& rFiles = getCustomizationFileNames(); + + for (const auto& b : rFiles) + { + osl::File::remove(maUserConfigWorkURL + "/" + b); + } + } + + void BackupFileHelper::tryResetUserProfile() + { + // completely delete the current UserProfile + DirectoryHelper::deleteDirRecursively(maUserConfigWorkURL); + } + + const OUString& BackupFileHelper::getUserProfileURL() + { + return maUserConfigBaseURL; + } + + const OUString& BackupFileHelper::getUserProfileWorkURL() + { + return maUserConfigWorkURL; + } + + /////////////////// helpers /////////////////////// + + OUString BackupFileHelper::getPackURL() + { + return OUString(maUserConfigWorkURL + "/pack"); + } + + /////////////////// file push helpers /////////////////////// + + bool BackupFileHelper::tryPush_Files( + const std::set< OUString >& rDirs, + const std::set< std::pair< OUString, OUString > >& rFiles, + std::u16string_view rSourceURL, // source dir without trailing '/' + const OUString& rTargetURL // target dir without trailing '/' + ) + { + bool bDidPush(false); + osl::Directory::createPath(rTargetURL); + + // process files + for (const auto& file : rFiles) + { + bDidPush |= tryPush_file( + rSourceURL, + rTargetURL, + file.first, + file.second); + } + + // process dirs + for (const auto& dir : rDirs) + { + OUString aNewSourceURL(OUString::Concat(rSourceURL) + "/" + dir); + OUString aNewTargetURL(rTargetURL + "/" + dir); + std::set< OUString > aNewDirs; + std::set< std::pair< OUString, OUString > > aNewFiles; + + DirectoryHelper::scanDirsAndFiles( + aNewSourceURL, + aNewDirs, + aNewFiles); + + if (!aNewDirs.empty() || !aNewFiles.empty()) + { + bDidPush |= tryPush_Files( + aNewDirs, + aNewFiles, + aNewSourceURL, + aNewTargetURL); + } + } + + if (!bDidPush) + { + // try removal of evtl. empty directory + osl::Directory::remove(rTargetURL); + } + + return bDidPush; + } + + bool BackupFileHelper::tryPush_file( + std::u16string_view rSourceURL, // source dir without trailing '/' + std::u16string_view rTargetURL, // target dir without trailing '/' + std::u16string_view rName, // filename + std::u16string_view rExt // extension (or empty) + ) + { + const OUString aFileURL(createFileURL(rSourceURL, rName, rExt)); + + if (DirectoryHelper::fileExists(aFileURL)) + { + const OUString aPackURL(createPackURL(rTargetURL, rName)); + PackedFile aPackedFile(aPackURL); + FileSharedPtr aBaseFile = std::make_shared<osl::File>(aFileURL); + + if (aPackedFile.tryPush(aBaseFile, mbCompress)) + { + // reduce to allowed number and flush + aPackedFile.tryReduceToNumBackups(mnNumBackups); + aPackedFile.flush(); + + return true; + } + } + + return false; + } + + /////////////////// file pop possibilities helper /////////////////////// + + bool BackupFileHelper::isPopPossible_files( + const std::set< OUString >& rDirs, + const std::set< std::pair< OUString, OUString > >& rFiles, + std::u16string_view rSourceURL, // source dir without trailing '/' + std::u16string_view rTargetURL // target dir without trailing '/' + ) + { + bool bPopPossible(false); + + // process files + for (const auto& file : rFiles) + { + bPopPossible |= isPopPossible_file( + rSourceURL, + rTargetURL, + file.first, + file.second); + } + + // process dirs + for (const auto& dir : rDirs) + { + OUString aNewSourceURL(OUString::Concat(rSourceURL) + "/" + dir); + OUString aNewTargetURL(OUString::Concat(rTargetURL) + "/" + dir); + std::set< OUString > aNewDirs; + std::set< std::pair< OUString, OUString > > aNewFiles; + + DirectoryHelper::scanDirsAndFiles( + aNewSourceURL, + aNewDirs, + aNewFiles); + + if (!aNewDirs.empty() || !aNewFiles.empty()) + { + bPopPossible |= isPopPossible_files( + aNewDirs, + aNewFiles, + aNewSourceURL, + aNewTargetURL); + } + } + + return bPopPossible; + } + + bool BackupFileHelper::isPopPossible_file( + std::u16string_view rSourceURL, // source dir without trailing '/' + std::u16string_view rTargetURL, // target dir without trailing '/' + std::u16string_view rName, // filename + std::u16string_view rExt // extension (or empty) + ) + { + const OUString aFileURL(createFileURL(rSourceURL, rName, rExt)); + + if (DirectoryHelper::fileExists(aFileURL)) + { + const OUString aPackURL(createPackURL(rTargetURL, rName)); + PackedFile aPackedFile(aPackURL); + + return !aPackedFile.empty(); + } + + return false; + } + + /////////////////// file pop helpers /////////////////////// + + bool BackupFileHelper::tryPop_files( + const std::set< OUString >& rDirs, + const std::set< std::pair< OUString, OUString > >& rFiles, + std::u16string_view rSourceURL, // source dir without trailing '/' + const OUString& rTargetURL // target dir without trailing '/' + ) + { + bool bDidPop(false); + + // process files + for (const auto& file : rFiles) + { + bDidPop |= tryPop_file( + rSourceURL, + rTargetURL, + file.first, + file.second); + } + + // process dirs + for (const auto& dir : rDirs) + { + OUString aNewSourceURL(OUString::Concat(rSourceURL) + "/" + dir); + OUString aNewTargetURL(rTargetURL + "/" + dir); + std::set< OUString > aNewDirs; + std::set< std::pair< OUString, OUString > > aNewFiles; + + DirectoryHelper::scanDirsAndFiles( + aNewSourceURL, + aNewDirs, + aNewFiles); + + if (!aNewDirs.empty() || !aNewFiles.empty()) + { + bDidPop |= tryPop_files( + aNewDirs, + aNewFiles, + aNewSourceURL, + aNewTargetURL); + } + } + + if (bDidPop) + { + // try removal of evtl. empty directory + osl::Directory::remove(rTargetURL); + } + + return bDidPop; + } + + bool BackupFileHelper::tryPop_file( + std::u16string_view rSourceURL, // source dir without trailing '/' + std::u16string_view rTargetURL, // target dir without trailing '/' + std::u16string_view rName, // filename + std::u16string_view rExt // extension (or empty) + ) + { + const OUString aFileURL(createFileURL(rSourceURL, rName, rExt)); + + if (!DirectoryHelper::fileExists(aFileURL)) + return false; + + // try Pop for base file + const OUString aPackURL(createPackURL(rTargetURL, rName)); + PackedFile aPackedFile(aPackURL); + + if (aPackedFile.empty()) + return false; + + oslFileHandle aHandle; + OUString aTempURL; + + // open target temp file - it exists until deleted + if (osl::File::E_None != osl::FileBase::createTempFile(nullptr, &aHandle, &aTempURL)) + return false; + + bool bRetval(aPackedFile.tryPop(aHandle)); + + // close temp file (in all cases) - it exists until deleted + osl_closeFile(aHandle); + + if (bRetval) + { + // copy over existing file by first deleting original + // and moving the temp file to old original + osl::File::remove(aFileURL); + osl::File::move(aTempURL, aFileURL); + + // reduce to allowed number and flush + aPackedFile.tryReduceToNumBackups(mnNumBackups); + aPackedFile.flush(); + } + + // delete temp file (in all cases - it may be moved already) + osl::File::remove(aTempURL); + + return bRetval; + } + + /////////////////// ExtensionInfo helpers /////////////////////// + + bool BackupFileHelper::tryPush_extensionInfo( + std::u16string_view rTargetURL // target dir without trailing '/' + ) + { + ExtensionInfo aExtensionInfo; + OUString aTempURL; + bool bRetval(false); + + // create current configuration and write to temp file - it exists until deleted + if (aExtensionInfo.createTempFile(aTempURL)) + { + const OUString aPackURL(createPackURL(rTargetURL, u"ExtensionInfo")); + PackedFile aPackedFile(aPackURL); + FileSharedPtr aBaseFile = std::make_shared<osl::File>(aTempURL); + + if (aPackedFile.tryPush(aBaseFile, mbCompress)) + { + // reduce to allowed number and flush + aPackedFile.tryReduceToNumBackups(mnNumBackups); + aPackedFile.flush(); + bRetval = true; + } + } + + // delete temp file (in all cases) + osl::File::remove(aTempURL); + return bRetval; + } + + bool BackupFileHelper::isPopPossible_extensionInfo( + std::u16string_view rTargetURL // target dir without trailing '/' + ) + { + // extensionInfo always exists internally, no test needed + const OUString aPackURL(createPackURL(rTargetURL, u"ExtensionInfo")); + PackedFile aPackedFile(aPackURL); + + return !aPackedFile.empty(); + } + + bool BackupFileHelper::tryPop_extensionInfo( + std::u16string_view rTargetURL // target dir without trailing '/' + ) + { + // extensionInfo always exists internally, no test needed + const OUString aPackURL(createPackURL(rTargetURL, u"ExtensionInfo")); + PackedFile aPackedFile(aPackURL); + + if (aPackedFile.empty()) + return false; + + oslFileHandle aHandle; + OUString aTempURL; + + // open target temp file - it exists until deleted + if (osl::File::E_None != osl::FileBase::createTempFile(nullptr, &aHandle, &aTempURL)) + return false; + + bool bRetval(aPackedFile.tryPop(aHandle)); + + // close temp file (in all cases) - it exists until deleted + osl_closeFile(aHandle); + + if (bRetval) + { + // last config is in temp file, load it to ExtensionInfo + ExtensionInfo aLoadedExtensionInfo; + FileSharedPtr aBaseFile = std::make_shared<osl::File>(aTempURL); + + if (osl::File::E_None == aBaseFile->open(osl_File_OpenFlag_Read)) + { + if (aLoadedExtensionInfo.read_entries(aBaseFile)) + { + // get current extension info, but from XML config files + ExtensionInfo aCurrentExtensionInfo; + + aCurrentExtensionInfo.createUserExtensionRegistryEntriesFromXML(maUserConfigWorkURL); + + // now we have loaded last_working (aLoadedExtensionInfo) and + // current (aCurrentExtensionInfo) ExtensionInfo and may react on + // differences by de/activating these as needed + const ExtensionInfoEntryVector& aUserEntries = aCurrentExtensionInfo.getExtensionInfoEntryVector(); + const ExtensionInfoEntryVector& rLoadedVector = aLoadedExtensionInfo.getExtensionInfoEntryVector(); + ExtensionInfoEntryVector aToBeDisabled; + ExtensionInfoEntryVector aToBeEnabled; + + for (const auto& rCurrentInfo : aUserEntries) + { + const ExtensionInfoEntry* pLoadedInfo = nullptr; + + for (const auto& rLoadedInfo : rLoadedVector) + { + if (rCurrentInfo.isSameExtension(rLoadedInfo)) + { + pLoadedInfo = &rLoadedInfo; + break; + } + } + + if (nullptr != pLoadedInfo) + { + // loaded info contains information about the Extension rCurrentInfo + const bool bCurrentEnabled(rCurrentInfo.isEnabled()); + const bool bLoadedEnabled(pLoadedInfo->isEnabled()); + + if (bCurrentEnabled && !bLoadedEnabled) + { + aToBeDisabled.push_back(rCurrentInfo); + } + else if (!bCurrentEnabled && bLoadedEnabled) + { + aToBeEnabled.push_back(rCurrentInfo); + } + } + else + { + // There is no loaded info about the Extension rCurrentInfo. + // It needs to be disabled + if (rCurrentInfo.isEnabled()) + { + aToBeDisabled.push_back(rCurrentInfo); + } + } + } + + if (!aToBeDisabled.empty() || !aToBeEnabled.empty()) + { + ExtensionInfo::changeEnableDisableStateInXML(maUserConfigWorkURL, aToBeEnabled, aToBeDisabled); + } + + bRetval = true; + } + } + + // reduce to allowed number and flush + aPackedFile.tryReduceToNumBackups(mnNumBackups); + aPackedFile.flush(); + } + + // delete temp file (in all cases - it may be moved already) + osl::File::remove(aTempURL); + + return bRetval; + } + + /////////////////// FileDirInfo helpers /////////////////////// + + void BackupFileHelper::fillDirFileInfo() + { + if (!maDirs.empty() || !maFiles.empty()) + { + // already done + return; + } + + // Information about the configuration and the role/purpose of directories in + // the UserConfiguration is taken from: https://wiki.documentfoundation.org/UserProfile + + // fill dir and file info list to work with dependent on work mode + switch (mnMode) + { + case 0: + { + // simple mode: add just registrymodifications + // (the orig file in maInitialBaseURL) + maFiles.insert(std::pair< OUString, OUString >(maRegModName, maExt)); + break; + } + case 1: + { + // defined mode: Add a selection of dirs containing User-Defined and thus + // valuable configuration information. + // This is clearly discussable in every single point and may be adapted/corrected + // over time. Main focus is to secure User-Defined/adapted values + + // add registrymodifications (the orig file in maInitialBaseURL) + maFiles.insert(std::pair< OUString, OUString >(maRegModName, maExt)); + + // User-defined substitution table (Tools/AutoCorrect) + maDirs.insert("autocorr"); + + // User-Defined AutoText (Edit/AutoText) + maDirs.insert("autotext"); + + // User-defined Macros + maDirs.insert("basic"); + + // User-adapted toolbars for modules + maDirs.insert("config"); + + // Initial and User-defined Databases + maDirs.insert("database"); + + // most part of registry files + maDirs.insert("registry"); + + // User-Defined Scripts + maDirs.insert("Scripts"); + + // Template files + maDirs.insert("template"); + + // Custom Dictionaries + maDirs.insert("wordbook"); + + // Questionable - where and how is Extension stuff held and how + // does this interact with enabled/disabled states which are extra handled? + // Keep out of business until deeper evaluated + // + // maDirs.insert("extensions"); + // maDirs.insert("uno-packages"); + break; + } + case 2: + { + // whole directory. To do so, scan directory and exclude some dirs + // from which we know they do not need to be secured explicitly. This + // should already include registrymodifications, too. + DirectoryHelper::scanDirsAndFiles( + maUserConfigWorkURL, + maDirs, + maFiles); + + // should not exist, but for the case an error occurred and it got + // copied somehow, avoid further recursive copying/saving + maDirs.erase("SafeMode"); + + // not really needed, can be abandoned + maDirs.erase("psprint"); + + // not really needed, can be abandoned + maDirs.erase("store"); + + // not really needed, can be abandoned + maDirs.erase("temp"); + + // exclude own backup dir to avoid recursion + maDirs.erase("pack"); + + break; + } + } + } +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ |