/* -*- 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 #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include 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(aArray), nToTransfer, nBytesTransfer) && nBytesTransfer == nToTransfer) { // add to crc and reduce size nCrc32 = rtl_crc32(nCrc32, static_cast(aArray), static_cast(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(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(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 aTarget(nLength); sal_uInt64 nBaseRead(0); // read rTarget if (osl::File::E_None == rFile->read(static_cast(aTarget.data()), nLength, nBaseRead) && nLength == nBaseRead) { rTarget = OString(aTarget.data(), static_cast(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(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(aArray), nToTransfer, nBytesTransfer) || nBytesTransfer != nToTransfer) { break; } if (osl_File_E_None != osl_writeFile(rTargetHandle, static_cast(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(aArray), nToTransfer, nBytesTransfer) || nBytesTransfer != nToTransfer) { break; } zstream.avail_in = nToTransfer; zstream.next_in = reinterpret_cast(aArray); do { zstream.avail_out = BACKUP_FILE_HELPER_BLOCK_SIZE; zstream.next_out = reinterpret_cast(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(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(aArray), nToTransfer, nBytesTransfer) || nBytesTransfer != nToTransfer) { break; } zstream.avail_in = nToTransfer; zstream.next_in = reinterpret_cast(aArray); do { zstream.avail_out = BACKUP_FILE_HELPER_BLOCK_SIZE; zstream.next_out = reinterpret_cast(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(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(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(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(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(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(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(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(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 lcl_getConfigElement(const uno::Reference& 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(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(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(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: */