/* -*- 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 OUString gaRegPath { u"/registry/com.sun.star.comp.deployment.bundle.PackageRegistryBackend/backenddb.xml"_ustr };
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;
const 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 : 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(u"url"_ustr));
const OUString aAttrRevoked(rElement->getAttribute(u"revoked"_ustr));
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))
{
const 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(u"url"_ustr), RTL_TEXTENCODING_ASCII_US));
const OUString aAttrRevoked(rElement->getAttribute(u"revoked"_ustr));
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(u"revoked"_ustr);
bChanged = true;
}
}
}
for (const auto& disable : rToBeDisabled)
{
if (-1 != aAttrUrl.indexOf(disable.getName()))
{
if (bEnabled)
{
// needs to be disabled
rElement->setAttribute(u"revoked"_ustr, u"true"_ustr);
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;
const 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 constexpr OUString aRegPathFront(u"/uno_packages/cache/registry/com.sun.star.comp.deployment."_ustr);
static constexpr OUString aRegPathBack(u".PackageRegistryBackend/backenddb.xml"_ustr);
// first appearance to check
{
const OUString aUnoPackagReg(OUString::Concat(rUserConfigWorkURL) + aRegPathFront + "bundle" + aRegPathBack);
visitNodesXMLChangeOneCase(
aUnoPackagReg,
u"extension"_ustr,
rToBeEnabled,
rToBeDisabled);
}
// second appearance to check
{
const OUString aUnoPackagReg(OUString::Concat(rUserConfigWorkURL) + aRegPathFront + "configuration" + aRegPathBack);
visitNodesXMLChangeOneCase(
aUnoPackagReg,
u"configuration"_ustr,
rToBeEnabled,
rToBeDisabled);
}
// third appearance to check
{
const OUString aUnoPackagReg(OUString::Concat(rUserConfigWorkURL) + aRegPathFront + "script" + aRegPathBack);
visitNodesXMLChangeOneCase(
aUnoPackagReg,
u"script"_ustr,
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(u"${CONFIGURATION_LAYERS}"_ustr);
rtl::Bootstrap::expandMacros(conf);
static constexpr OUString aTokenUser(u"user:"_ustr);
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 constexpr OUString aSafeMode(u"SafeMode"_ustr);
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(u"SecureUserConfig"_ustr, 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(u"SecureUserConfigNumCopies"_ustr, 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(u"SecureUserConfigMode"_ustr, 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(u"SecureUserConfigExtensions"_ustr, sTokenOut))
{
mbExtensions = sTokenOut.toBoolean();
}
if (mbActive && rtl::Bootstrap::get(u"SecureUserConfigCompress"_ustr, sTokenOut))
{
mbCompress = sTokenOut.toBoolean();
}
}
void BackupFileHelper::setExitWasCalled()
{
mbExitWasCalled = true;
}
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 =
{
u"config"_ustr, // UI config stuff
u"registry"_ustr, // most of the registry stuff
u"psprint"_ustr, // not really needed, can be abandoned
u"store"_ustr, // not really needed, can be abandoned
u"temp"_ustr, // not really needed, can be abandoned
u"pack"_ustr // own backup dir
};
return aDirNames;
}
const std::vector< OUString >& BackupFileHelper::getCustomizationFileNames()
{
static std::vector< OUString > aFileNames =
{
u"registrymodifications.xcu"_ustr // 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(u"item"_ustr);
itemElement->setAttribute(u"oor:path"_ustr, rPath);
uno::Reference< XElement > propElement = xDocument->createElement(u"prop"_ustr);
propElement->setAttribute(u"oor:name"_ustr, rKey);
propElement->setAttribute(u"oor:op"_ustr, u"replace"_ustr); // Replace any other options
uno::Reference< XElement > valueElement = xDocument->createElement(u"value"_ustr);
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;
const 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, u"/org.openoffice.Office.Common/VCL"_ustr,
u"DisableOpenGL"_ustr, u"true"_ustr));
xRootElement->appendChild(lcl_getConfigElement(xDocument, u"/org.openoffice.Office.Common/Misc"_ustr,
u"UseOpenCL"_ustr, u"false"_ustr));
// Do not disable Skia entirely, just force its CPU-based raster mode.
xRootElement->appendChild(lcl_getConfigElement(xDocument, u"/org.openoffice.Office.Common/VCL"_ustr,
u"ForceSkia"_ustr, u"false"_ustr));
xRootElement->appendChild(lcl_getConfigElement(xDocument, u"/org.openoffice.Office.Common/VCL"_ustr,
u"ForceSkiaRaster"_ustr, u"true"_ustr));
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(u"autocorr"_ustr);
// User-Defined AutoText (Edit/AutoText)
maDirs.insert(u"autotext"_ustr);
// User-defined Macros
maDirs.insert(u"basic"_ustr);
// User-adapted toolbars for modules
maDirs.insert(u"config"_ustr);
// Initial and User-defined Databases
maDirs.insert(u"database"_ustr);
// most part of registry files
maDirs.insert(u"registry"_ustr);
// User-Defined Scripts
maDirs.insert(u"Scripts"_ustr);
// Template files
maDirs.insert(u"template"_ustr);
// Custom Dictionaries
maDirs.insert(u"wordbook"_ustr);
// 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(u"SafeMode"_ustr);
// not really needed, can be abandoned
maDirs.erase(u"psprint"_ustr);
// not really needed, can be abandoned
maDirs.erase(u"store"_ustr);
// not really needed, can be abandoned
maDirs.erase(u"temp"_ustr);
// exclude own backup dir to avoid recursion
maDirs.erase(u"pack"_ustr);
break;
}
}
}
}
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
↑ V530 The return value of function 'getInitialBaseURL' is required to be utilized.
↑ V530 The return value of function 'getInitialBaseURL' is required to be utilized.
↑ V1048 The 'bRetval' variable was assigned the same value.