diff options
Diffstat (limited to 'desktop/source/deployment/manager')
-rw-r--r-- | desktop/source/deployment/manager/dp_activepackages.cxx | 217 | ||||
-rw-r--r-- | desktop/source/deployment/manager/dp_activepackages.hxx | 95 | ||||
-rw-r--r-- | desktop/source/deployment/manager/dp_commandenvironments.cxx | 246 | ||||
-rw-r--r-- | desktop/source/deployment/manager/dp_commandenvironments.hxx | 139 | ||||
-rw-r--r-- | desktop/source/deployment/manager/dp_extensionmanager.cxx | 1432 | ||||
-rw-r--r-- | desktop/source/deployment/manager/dp_extensionmanager.hxx | 227 | ||||
-rw-r--r-- | desktop/source/deployment/manager/dp_informationprovider.cxx | 338 | ||||
-rw-r--r-- | desktop/source/deployment/manager/dp_manager.cxx | 1600 | ||||
-rw-r--r-- | desktop/source/deployment/manager/dp_manager.h | 232 | ||||
-rw-r--r-- | desktop/source/deployment/manager/dp_managerfac.cxx | 186 | ||||
-rw-r--r-- | desktop/source/deployment/manager/dp_properties.cxx | 145 | ||||
-rw-r--r-- | desktop/source/deployment/manager/dp_properties.hxx | 58 |
12 files changed, 4915 insertions, 0 deletions
diff --git a/desktop/source/deployment/manager/dp_activepackages.cxx b/desktop/source/deployment/manager/dp_activepackages.cxx new file mode 100644 index 000000000..c1c5f2b28 --- /dev/null +++ b/desktop/source/deployment/manager/dp_activepackages.cxx @@ -0,0 +1,217 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include <config_extensions.h> + +#include <sal/config.h> + +#include <string_view> +#include <utility> + +#include <osl/diagnose.h> +#include <rtl/string.hxx> +#include <rtl/textenc.h> +#include <rtl/ustring.hxx> + +#include <dp_identifier.hxx> +#include "dp_activepackages.hxx" + +// Old format of database entry: +// key: UTF8(filename) +// value: UTF8(tempname ";" mediatype) +// New format of database entry: +// key: 0xFF UTF8(identifier) +// value: UTF8(tempname) 0xFF UTF8(filename) 0xFF UTF8(mediatype) + +#if HAVE_FEATURE_EXTENSIONS + +namespace { + +constexpr const char separator[] = "\xff"; + +OString oldKey(std::u16string_view fileName) { + return OUStringToOString(fileName, RTL_TEXTENCODING_UTF8); +} + +OString newKey(std::u16string_view id) { + return separator + OUStringToOString(id, RTL_TEXTENCODING_UTF8); +} + +::dp_manager::ActivePackages::Data decodeOldData( + OUString const & fileName, OString const & value) +{ + ::dp_manager::ActivePackages::Data d; + sal_Int32 i = value.indexOf(';'); + OSL_ASSERT(i >= 0); + d.temporaryName = OUString(value.getStr(), i, RTL_TEXTENCODING_UTF8); + d.fileName = fileName; + d.mediaType = OUString( + value.getStr() + i + 1, value.getLength() - i - 1, + RTL_TEXTENCODING_UTF8); + return d; +} + +::dp_manager::ActivePackages::Data decodeNewData(OString const & value) { + ::dp_manager::ActivePackages::Data d; + sal_Int32 i1 = value.indexOf(separator); + OSL_ASSERT(i1 >= 0); + d.temporaryName = OUString( + value.getStr(), i1, RTL_TEXTENCODING_UTF8); + sal_Int32 i2 = value.indexOf(separator, i1 + 1); + OSL_ASSERT(i2 >= 0); + d.fileName = OUString( + value.getStr() + i1 + 1, i2 - i1 - 1, RTL_TEXTENCODING_UTF8); + sal_Int32 i3 = value.indexOf(separator, i2 + 1); + + if (i3 < 0) + { + //Before ActivePackages::Data::version was added + d.mediaType = OUString( + value.getStr() + i2 + 1, value.getLength() - i2 - 1, + RTL_TEXTENCODING_UTF8); + } + else + { + sal_Int32 i4 = value.indexOf(separator, i3 + 1); + d.mediaType = OUString( + value.getStr() + i2 + 1, i3 - i2 -1, RTL_TEXTENCODING_UTF8); + d.version = OUString( + value.getStr() + i3 + 1, i4 - i3 - 1, + RTL_TEXTENCODING_UTF8); + d.failedPrerequisites = OUString( + value.getStr() + i4 + 1, value.getLength() - i4 - 1, + RTL_TEXTENCODING_UTF8); + } + return d; +} + +} +#endif + +namespace dp_manager { + +ActivePackages::ActivePackages() {} + +ActivePackages::ActivePackages(OUString const & url) +#if HAVE_FEATURE_EXTENSIONS + : m_map(url) +#endif +{ +#if !HAVE_FEATURE_EXTENSIONS + (void) url; +#endif +} + +ActivePackages::~ActivePackages() {} + +bool ActivePackages::has( + OUString const & id, OUString const & fileName) const +{ + return get(nullptr, id, fileName); +} + +bool ActivePackages::get( + Data * data, OUString const & id, OUString const & fileName) + const +{ +#if HAVE_FEATURE_EXTENSIONS + OString v; + if (m_map.get(&v, newKey(id))) { + if (data != nullptr) { + *data = decodeNewData(v); + } + return true; + } else if (m_map.get(&v, oldKey(fileName))) { + if (data != nullptr) { + *data = decodeOldData(fileName, v); + } + return true; + } else { + return false; + } +#else + (void) data; + (void) id; + (void) fileName; + (void) this; + return false; +#endif +} + +ActivePackages::Entries ActivePackages::getEntries() const { + Entries es; +#if HAVE_FEATURE_EXTENSIONS + ::dp_misc::t_string2string_map m(m_map.getEntries()); + for (auto const& elem : m) + { + if (!elem.first.isEmpty() && elem.first[0] == separator[0]) { + es.emplace_back( + OUString( + elem.first.getStr() + 1, elem.first.getLength() - 1, + RTL_TEXTENCODING_UTF8), + decodeNewData(elem.second)); + } else { + OUString fn( + OStringToOUString(elem.first, RTL_TEXTENCODING_UTF8)); + es.emplace_back( + ::dp_misc::generateLegacyIdentifier(fn), + decodeOldData(fn, elem.second)); + } + } +#else + (void) this; +#endif + return es; +} + +void ActivePackages::put(OUString const & id, Data const & data) { +#if HAVE_FEATURE_EXTENSIONS + OString b = + OUStringToOString(data.temporaryName, RTL_TEXTENCODING_UTF8) + + separator + + OUStringToOString(data.fileName, RTL_TEXTENCODING_UTF8) + + separator + + OUStringToOString(data.mediaType, RTL_TEXTENCODING_UTF8) + + separator + + OUStringToOString(data.version, RTL_TEXTENCODING_UTF8) + + separator + + OUStringToOString(data.failedPrerequisites, RTL_TEXTENCODING_UTF8); + m_map.put(newKey(id), b); +#else + (void) id; + (void) data; + (void) this; +#endif +} + +void ActivePackages::erase( + OUString const & id, OUString const & fileName) +{ +#if HAVE_FEATURE_EXTENSIONS + m_map.erase(newKey(id)) || m_map.erase(oldKey(fileName)); +#else + (void) id; + (void) fileName; + (void) this; +#endif +} + +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/desktop/source/deployment/manager/dp_activepackages.hxx b/desktop/source/deployment/manager/dp_activepackages.hxx new file mode 100644 index 000000000..fae938019 --- /dev/null +++ b/desktop/source/deployment/manager/dp_activepackages.hxx @@ -0,0 +1,95 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#pragma once + +#include <config_extensions.h> + +#include <sal/config.h> +#include <rtl/ustring.hxx> + +#include <utility> +#include <vector> + +#if HAVE_FEATURE_EXTENSIONS +#include <dp_persmap.h> +#endif + + +namespace dp_manager { + +class ActivePackages { +public: + struct Data { + Data(): failedPrerequisites("0") + {} + /* name of the temporary file (shared, user extension) or the name of + the folder of the bundled extension. + It does not contain the trailing '_' of the folder. + UTF-8 encoded + */ + OUString temporaryName; + /* The file name (shared, user) or the folder name (bundled) + If the key is the file name, then file name is not encoded. + If the key is the identifier then the file name is UTF-8 encoded. + */ + OUString fileName; + OUString mediaType; + OUString version; + /* If this string contains the value according to + css::deployment::Prerequisites or "0". That is, if + the value is > 0 then + the call to XPackage::checkPrerequisites failed. + In this case the extension must not be registered. + */ + OUString failedPrerequisites; + }; + + typedef std::vector< std::pair< OUString, Data > > Entries; + + ActivePackages(); + + explicit ActivePackages(OUString const & url); + + ~ActivePackages(); + + bool has(OUString const & id, OUString const & fileName) + const; + + bool get( + Data * data, OUString const & id, + OUString const & fileName) const; + + Entries getEntries() const; + + void put(OUString const & id, Data const & value); + + void erase(OUString const & id, OUString const & fileName); + +private: + ActivePackages(ActivePackages const &) = delete; + ActivePackages& operator =(ActivePackages const &) = delete; +#if HAVE_FEATURE_EXTENSIONS + ::dp_misc::PersistentMap m_map; +#endif +}; + +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/desktop/source/deployment/manager/dp_commandenvironments.cxx b/desktop/source/deployment/manager/dp_commandenvironments.cxx new file mode 100644 index 000000000..0a25e042f --- /dev/null +++ b/desktop/source/deployment/manager/dp_commandenvironments.cxx @@ -0,0 +1,246 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + + +#include <com/sun/star/deployment/VersionException.hpp> +#include <com/sun/star/deployment/LicenseException.hpp> +#include <com/sun/star/deployment/InstallException.hpp> +#include <com/sun/star/deployment/DependencyException.hpp> +#include <com/sun/star/deployment/PlatformException.hpp> +#include <com/sun/star/task/XInteractionApprove.hpp> +#include <com/sun/star/task/XInteractionHandler.hpp> +#include <com/sun/star/ucb/XCommandEnvironment.hpp> +#include <utility> +#include "dp_commandenvironments.hxx" +#include <osl/diagnose.h> + +namespace deployment = com::sun::star::deployment; +namespace task = com::sun::star::task; +namespace ucb = com::sun::star::ucb; +namespace uno = com::sun::star::uno; + +using ::com::sun::star::uno::Reference; + +namespace dp_manager { + +BaseCommandEnv::BaseCommandEnv() +{ +} + +BaseCommandEnv::BaseCommandEnv( + Reference< task::XInteractionHandler> const & handler) + : m_forwardHandler(handler) +{ +} + +BaseCommandEnv::~BaseCommandEnv() +{ +} +// XCommandEnvironment + +Reference<task::XInteractionHandler> BaseCommandEnv::getInteractionHandler() +{ + return this; +} + + +Reference<ucb::XProgressHandler> BaseCommandEnv::getProgressHandler() +{ + return this; +} + +void BaseCommandEnv::handle( + Reference< task::XInteractionRequest> const & /*xRequest*/ ) +{ +} + +void BaseCommandEnv::handle_(bool approve, + Reference< task::XInteractionRequest> const & xRequest ) +{ + if (!approve) + { + //not handled so far -> forwarding + if (m_forwardHandler.is()) + m_forwardHandler->handle(xRequest); + else + return; //cannot handle + } + else + { + // select: + uno::Sequence< Reference< task::XInteractionContinuation > > conts( + xRequest->getContinuations() ); + Reference< task::XInteractionContinuation > const * pConts = + conts.getConstArray(); + sal_Int32 len = conts.getLength(); + for ( sal_Int32 pos = 0; pos < len; ++pos ) + { + if (approve) { + Reference< task::XInteractionApprove > xInteractionApprove( + pConts[ pos ], uno::UNO_QUERY ); + if (xInteractionApprove.is()) { + xInteractionApprove->select(); + // don't query again for ongoing continuations: + approve = false; + } + } + } + } + +} + +// XProgressHandler +void BaseCommandEnv::push( uno::Any const & /*Status*/ ) +{ +} + +void BaseCommandEnv::update( uno::Any const & /*Status */) +{ +} + +void BaseCommandEnv::pop() +{ +} + + +TmpRepositoryCommandEnv::TmpRepositoryCommandEnv() +{ +} + +TmpRepositoryCommandEnv::TmpRepositoryCommandEnv( + css::uno::Reference< css::task::XInteractionHandler> const & handler): + BaseCommandEnv(handler) +{ +} +// XInteractionHandler +void TmpRepositoryCommandEnv::handle( + Reference< task::XInteractionRequest> const & xRequest ) +{ + uno::Any request( xRequest->getRequest() ); + OSL_ASSERT( request.getValueTypeClass() == uno::TypeClass_EXCEPTION ); + + deployment::VersionException verExc; + deployment::LicenseException licExc; + deployment::InstallException instExc; + + bool approve = false; + + if ((request >>= verExc) + || (request >>= licExc) + || (request >>= instExc)) + { + approve = true; + } + + handle_(approve, xRequest); +} + + +LicenseCommandEnv::LicenseCommandEnv( + css::uno::Reference< css::task::XInteractionHandler> const & handler, + bool bSuppressLicense, + OUString repository): + BaseCommandEnv(handler), m_repository(std::move(repository)), + m_bSuppressLicense(bSuppressLicense) +{ +} +// XInteractionHandler +void LicenseCommandEnv::handle( + Reference< task::XInteractionRequest> const & xRequest ) +{ + uno::Any request( xRequest->getRequest() ); + OSL_ASSERT( request.getValueTypeClass() == uno::TypeClass_EXCEPTION ); + + deployment::LicenseException licExc; + + bool approve = false; + + if (request >>= licExc) + { + if (m_bSuppressLicense + || m_repository == "bundled" + || licExc.AcceptBy == "admin") + { + //always approve in bundled case, because we do not support + //showing licenses anyway. + //The "admin" already accepted the license when installing the + // shared extension + approve = true; + } + } + + handle_(approve, xRequest); +} + + +NoLicenseCommandEnv::NoLicenseCommandEnv( + css::uno::Reference< css::task::XInteractionHandler> const & handler): + BaseCommandEnv(handler) +{ +} +// XInteractionHandler +void NoLicenseCommandEnv::handle( + Reference< task::XInteractionRequest> const & xRequest ) +{ + uno::Any request( xRequest->getRequest() ); + OSL_ASSERT( request.getValueTypeClass() == uno::TypeClass_EXCEPTION ); + + deployment::LicenseException licExc; + + bool approve = false; + + if (request >>= licExc) + { + approve = true; + } + handle_(approve, xRequest); +} + +SilentCheckPrerequisitesCommandEnv::SilentCheckPrerequisitesCommandEnv() +{ +} + +void SilentCheckPrerequisitesCommandEnv::handle( + Reference< task::XInteractionRequest> const & xRequest ) +{ + uno::Any request( xRequest->getRequest() ); + OSL_ASSERT( request.getValueTypeClass() == uno::TypeClass_EXCEPTION ); + + deployment::LicenseException licExc; + deployment::PlatformException platformExc; + deployment::DependencyException depExc; + + if (request >>= licExc) + { + handle_(true, xRequest); // approve = true + } + else if ((request >>= platformExc) + || (request >>= depExc)) + { + m_Exception = request; + } + else + { + m_UnknownException = request; + } +} + +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/desktop/source/deployment/manager/dp_commandenvironments.hxx b/desktop/source/deployment/manager/dp_commandenvironments.hxx new file mode 100644 index 000000000..6533d45b4 --- /dev/null +++ b/desktop/source/deployment/manager/dp_commandenvironments.hxx @@ -0,0 +1,139 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#pragma once + +#include <cppuhelper/implbase.hxx> +#include <com/sun/star/task/XInteractionHandler.hpp> +#include <com/sun/star/task/XInteractionRequest.hpp> +#include <com/sun/star/ucb/XProgressHandler.hpp> +#include <com/sun/star/ucb/XCommandEnvironment.hpp> + + +namespace dp_manager { + +/** + This command environment is to be used when an extension is temporarily + stored in the "tmp" repository. It prevents all kind of user interaction. + */ +class BaseCommandEnv + : public ::cppu::WeakImplHelper< css::ucb::XCommandEnvironment, + css::task::XInteractionHandler, + css::ucb::XProgressHandler > +{ + css::uno::Reference< css::task::XInteractionHandler> m_forwardHandler; +protected: + void handle_(bool approve, + css::uno::Reference< css::task::XInteractionRequest> const & xRequest ); +public: + virtual ~BaseCommandEnv() override; + BaseCommandEnv(); + explicit BaseCommandEnv( + css::uno::Reference< css::task::XInteractionHandler> const & handler); + + // XCommandEnvironment + virtual css::uno::Reference<css::task::XInteractionHandler > SAL_CALL + getInteractionHandler() override; + virtual css::uno::Reference<css::ucb::XProgressHandler > + SAL_CALL getProgressHandler() override; + + // XInteractionHandler + virtual void SAL_CALL handle( + css::uno::Reference<css::task::XInteractionRequest > const & xRequest ) override; + + // XProgressHandler + virtual void SAL_CALL push( css::uno::Any const & Status ) override; + virtual void SAL_CALL update( css::uno::Any const & Status ) override; + virtual void SAL_CALL pop() override; +}; + +class TmpRepositoryCommandEnv : public BaseCommandEnv +{ +public: + TmpRepositoryCommandEnv(); + explicit TmpRepositoryCommandEnv(css::uno::Reference< css::task::XInteractionHandler> const & handler); + +// XInteractionHandler + virtual void SAL_CALL handle( + css::uno::Reference<css::task::XInteractionRequest > const & xRequest ) override; + +}; + +/** this class is for use in XPackageManager::synchronize. + + It handles particular license cases. + */ +class LicenseCommandEnv : public BaseCommandEnv +{ +private: + OUString m_repository; + bool m_bSuppressLicense; +public: + LicenseCommandEnv( + css::uno::Reference< css::task::XInteractionHandler> const & handler, + bool bSuppressLicense, + OUString repository); + +// XInteractionHandler + virtual void SAL_CALL handle( + css::uno::Reference<css::task::XInteractionRequest > const & xRequest ) override; + +}; + +/** this class is for use in XPackageManager::checkPrerequisites + + It always prohibits a license interaction + */ +class NoLicenseCommandEnv : public BaseCommandEnv +{ + +public: + explicit NoLicenseCommandEnv(css::uno::Reference< css::task::XInteractionHandler> const & handler); + +// XInteractionHandler + virtual void SAL_CALL handle( + css::uno::Reference<css::task::XInteractionRequest > const & xRequest ) override; + +}; + +/* For use in XExtensionManager::addExtension in the call to + XPackage::checkPrerequisites + It prevents all user interactions. The license is always accepted. + It remembers if there was a platform or a dependency exception in + the member m_bException. if there was any other exception then m_bUnknownException + is set. + + */ +class SilentCheckPrerequisitesCommandEnv : public BaseCommandEnv +{ +public: + SilentCheckPrerequisitesCommandEnv(); + // XInteractionHandler + virtual void SAL_CALL handle( + css::uno::Reference<css::task::XInteractionRequest > const & xRequest ) override; + + // Set to true if a PlatformException or a DependencyException were handled. + css::uno::Any m_Exception; + // Set to true if an unknown exception was handled. + css::uno::Any m_UnknownException; +}; + +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/desktop/source/deployment/manager/dp_extensionmanager.cxx b/desktop/source/deployment/manager/dp_extensionmanager.cxx new file mode 100644 index 000000000..fc7816f6a --- /dev/null +++ b/desktop/source/deployment/manager/dp_extensionmanager.cxx @@ -0,0 +1,1432 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + + +#include <cppuhelper/compbase.hxx> +#include <cppuhelper/supportsservice.hxx> + +#include <cppuhelper/exc_hlp.hxx> +#include <rtl/bootstrap.hxx> +#include <com/sun/star/deployment/DeploymentException.hpp> +#include <com/sun/star/deployment/XExtensionManager.hpp> +#include <com/sun/star/deployment/thePackageManagerFactory.hpp> +#include <com/sun/star/deployment/XPackageManager.hpp> +#include <com/sun/star/deployment/XPackage.hpp> +#include <com/sun/star/deployment/InstallException.hpp> +#include <com/sun/star/deployment/VersionException.hpp> +#include <com/sun/star/lang/IllegalArgumentException.hpp> +#include <com/sun/star/lang/XServiceInfo.hpp> +#include <com/sun/star/beans/Optional.hpp> +#include <com/sun/star/task/XInteractionApprove.hpp> +#include <com/sun/star/beans/Ambiguous.hpp> +#include <com/sun/star/ucb/CommandAbortedException.hpp> +#include <com/sun/star/ucb/CommandFailedException.hpp> +#include <com/sun/star/uno/XComponentContext.hpp> +#include <com/sun/star/io/XInputStream.hpp> +#include <com/sun/star/util/XModifyBroadcaster.hpp> +#include <comphelper/sequence.hxx> +#include <utility> +#include <xmlscript/xml_helper.hxx> +#include <osl/diagnose.h> +#include <dp_interact.h> +#include <dp_misc.h> +#include <dp_ucb.h> +#include <dp_identifier.hxx> +#include <dp_descriptioninfoset.hxx> +#include "dp_extensionmanager.hxx" +#include "dp_commandenvironments.hxx" +#include "dp_properties.hxx" + +#include <vector> +#include <algorithm> +#include <set> +#include <string_view> + +namespace lang = com::sun::star::lang; +namespace task = com::sun::star::task; +namespace ucb = com::sun::star::ucb; +namespace uno = com::sun::star::uno; +namespace beans = com::sun::star::beans; +namespace util = com::sun::star::util; + +using ::com::sun::star::uno::Reference; + +namespace { + +struct CompIdentifiers +{ + bool operator() (std::vector<Reference<css::deployment::XPackage> > const & a, + std::vector<Reference<css::deployment::XPackage> > const & b) + { + return getName(a).compareTo(getName(b)) < 0; + } + + static OUString getName(std::vector<Reference<css::deployment::XPackage> > const & a); +}; + +OUString CompIdentifiers::getName(std::vector<Reference<css::deployment::XPackage> > const & a) +{ + OSL_ASSERT(a.size() == 3); + //get the first non-null reference + Reference<css::deployment::XPackage> extension; + for (auto const& elem : a) + { + if (elem.is()) + { + extension = elem; + break; + } + } + OSL_ASSERT(extension.is()); + return extension->getDisplayName(); +} + +void writeLastModified(OUString & url, Reference<ucb::XCommandEnvironment> const & xCmdEnv, Reference< uno::XComponentContext > const & xContext) +{ + //Write the lastmodified file + try { + ::rtl::Bootstrap::expandMacros(url); + ::ucbhelper::Content ucbStamp(url, xCmdEnv, xContext); + dp_misc::erase_path( url, xCmdEnv ); + OString stamp("1" ); + Reference<css::io::XInputStream> xData( + ::xmlscript::createInputStream( + reinterpret_cast<sal_Int8 const *>(stamp.getStr()), + stamp.getLength() ) ); + ucbStamp.writeStream( xData, true /* replace existing */ ); + } + catch(...) + { + uno::Any exc(::cppu::getCaughtException()); + throw css::deployment::DeploymentException("Failed to update" + url, nullptr, exc); + } +} + +class ExtensionRemoveGuard +{ + css::uno::Reference<css::deployment::XPackage> m_extension; + css::uno::Reference<css::deployment::XPackageManager> m_xPackageManager; + +public: + ExtensionRemoveGuard(){}; + ExtensionRemoveGuard( + css::uno::Reference<css::deployment::XPackage> extension, + css::uno::Reference<css::deployment::XPackageManager> xPackageManager): + m_extension(std::move(extension)), m_xPackageManager(std::move(xPackageManager)) {} + ~ExtensionRemoveGuard(); + + void set(css::uno::Reference<css::deployment::XPackage> const & extension, + css::uno::Reference<css::deployment::XPackageManager> const & xPackageManager) { + m_extension = extension; + m_xPackageManager = xPackageManager; + } +}; + +ExtensionRemoveGuard::~ExtensionRemoveGuard() +{ + try { + OSL_ASSERT(!(m_extension.is() && !m_xPackageManager.is())); + if (m_xPackageManager.is() && m_extension.is()) + m_xPackageManager->removePackage( + dp_misc::getIdentifier(m_extension), OUString(), + css::uno::Reference<css::task::XAbortChannel>(), + css::uno::Reference<css::ucb::XCommandEnvironment>()); + } catch (...) { + OSL_ASSERT(false); + } +} + +} + +namespace dp_manager { + +//ToDo: bundled extension +ExtensionManager::ExtensionManager( Reference< uno::XComponentContext > const& xContext) : + ::cppu::WeakComponentImplHelper< css::deployment::XExtensionManager, css::lang::XServiceInfo >(m_aMutex) + , m_xContext(xContext) +{ + m_xPackageManagerFactory = css::deployment::thePackageManagerFactory::get(m_xContext); + OSL_ASSERT(m_xPackageManagerFactory.is()); + + m_repositoryNames.emplace_back("user"); + m_repositoryNames.emplace_back("shared"); + m_repositoryNames.emplace_back("bundled"); +} + +ExtensionManager::~ExtensionManager() +{ +} + +// XServiceInfo +OUString ExtensionManager::getImplementationName() +{ + return "com.sun.star.comp.deployment.ExtensionManager"; +} + +sal_Bool ExtensionManager::supportsService( const OUString& ServiceName ) +{ + return cppu::supportsService(this, ServiceName); +} + +css::uno::Sequence< OUString > ExtensionManager::getSupportedServiceNames() +{ + // a private one: + return { "com.sun.star.comp.deployment.ExtensionManager" }; +} + +Reference<css::deployment::XPackageManager> ExtensionManager::getUserRepository() +{ + return m_xPackageManagerFactory->getPackageManager("user"); +} +Reference<css::deployment::XPackageManager> ExtensionManager::getSharedRepository() +{ + return m_xPackageManagerFactory->getPackageManager("shared"); +} +Reference<css::deployment::XPackageManager> ExtensionManager::getBundledRepository() +{ + return m_xPackageManagerFactory->getPackageManager("bundled"); +} +Reference<css::deployment::XPackageManager> ExtensionManager::getTmpRepository() +{ + return m_xPackageManagerFactory->getPackageManager("tmp"); +} +Reference<css::deployment::XPackageManager> ExtensionManager::getBakRepository() +{ + return m_xPackageManagerFactory->getPackageManager("bak"); +} + +Reference<task::XAbortChannel> ExtensionManager::createAbortChannel() +{ + return new dp_misc::AbortChannel; +} + +css::uno::Reference<css::deployment::XPackageManager> +ExtensionManager::getPackageManager(std::u16string_view repository) +{ + Reference<css::deployment::XPackageManager> xPackageManager; + if (repository == u"user") + xPackageManager = getUserRepository(); + else if (repository == u"shared") + xPackageManager = getSharedRepository(); + else if (repository == u"bundled") + xPackageManager = getBundledRepository(); + else if (repository == u"tmp") + xPackageManager = getTmpRepository(); + else if (repository == u"bak") + xPackageManager = getBakRepository(); + else + throw lang::IllegalArgumentException( + "No valid repository name provided.", + static_cast<cppu::OWeakObject*>(this), 0); + return xPackageManager; +} + +/* + Enters the XPackage objects into a map. They must be all from the + same repository. The value type of the map is a vector, where each vector + represents an extension with a particular identifier. The first member + represents the user extension, the second the shared extension and the + third the bundled extension. + */ +void ExtensionManager::addExtensionsToMap( + id2extensions & mapExt, + uno::Sequence<Reference<css::deployment::XPackage> > const & seqExt, + std::u16string_view repository) +{ + //Determine the index in the vector where these extensions are to be + //added. + int index = 0; + for (auto const& repositoryName : m_repositoryNames) + { + if (repositoryName == repository) + break; + ++index; + } + + for (const Reference<css::deployment::XPackage>& xExtension : seqExt) + { + OUString id = dp_misc::getIdentifier(xExtension); + id2extensions::iterator ivec = mapExt.find(id); + if (ivec == mapExt.end()) + { + std::vector<Reference<css::deployment::XPackage> > vec(3); + vec[index] = xExtension; + mapExt[id] = vec; + } + else + { + ivec->second[index] = xExtension; + } + } +} + +/* + returns a list containing extensions with the same identifier from + all repositories (user, shared, bundled). If one repository does not + have this extension, then the list contains an empty Reference. The list + is ordered according to the priority of the repositories: + 1. user + 2. shared + 3. bundled + + The number of elements is always three, unless the number of repository + changes. + */ +std::vector<Reference<css::deployment::XPackage> > + ExtensionManager::getExtensionsWithSameId( + OUString const & identifier, OUString const & fileName) + +{ + std::vector<Reference<css::deployment::XPackage> > extensionList; + Reference<css::deployment::XPackageManager> lRepos[] = { + getUserRepository(), getSharedRepository(), getBundledRepository() }; + for (int i(0); i != SAL_N_ELEMENTS(lRepos); ++i) + { + Reference<css::deployment::XPackage> xPackage; + try + { + xPackage = lRepos[i]->getDeployedPackage( + identifier, fileName, Reference<ucb::XCommandEnvironment>()); + } + catch(const lang::IllegalArgumentException &) + { + // thrown if the extension does not exist in this repository + } + extensionList.push_back(xPackage); + } + OSL_ASSERT(extensionList.size() == 3); + return extensionList; +} + +uno::Sequence<Reference<css::deployment::XPackage> > +ExtensionManager::getExtensionsWithSameIdentifier( + OUString const & identifier, + OUString const & fileName, + Reference< ucb::XCommandEnvironment> const & /*xCmdEnv*/ ) +{ + try + { + std::vector<Reference<css::deployment::XPackage> > listExtensions = + getExtensionsWithSameId(identifier, fileName); + bool bHasExtension = false; + + //throw an IllegalArgumentException if there is no extension at all. + for (auto const& extension : listExtensions) + bHasExtension |= extension.is(); + if (!bHasExtension) + throw lang::IllegalArgumentException( + "Could not find extension: " + identifier + ", " + fileName, + static_cast<cppu::OWeakObject*>(this), -1); + + return comphelper::containerToSequence(listExtensions); + } + catch ( const css::deployment::DeploymentException & ) + { + throw; + } + catch ( const ucb::CommandFailedException & ) + { + throw; + } + catch (css::uno::RuntimeException &) + { + throw; + } + catch (...) + { + uno::Any exc = ::cppu::getCaughtException(); + throw css::deployment::DeploymentException( + "Extension Manager: exception during getExtensionsWithSameIdentifier", + static_cast<OWeakObject*>(this), exc); + } +} + +bool ExtensionManager::isUserDisabled( + OUString const & identifier, OUString const & fileName) +{ + std::vector<Reference<css::deployment::XPackage> > listExtensions; + + try { + listExtensions = getExtensionsWithSameId(identifier, fileName); + } catch ( const lang::IllegalArgumentException & ) { + } + OSL_ASSERT(listExtensions.size() == 3); + + return isUserDisabled( ::comphelper::containerToSequence(listExtensions) ); +} + +bool ExtensionManager::isUserDisabled( + uno::Sequence<Reference<css::deployment::XPackage> > const & seqExtSameId) +{ + OSL_ASSERT(seqExtSameId.getLength() == 3); + Reference<css::deployment::XPackage> const & userExtension = seqExtSameId[0]; + if (userExtension.is()) + { + beans::Optional<beans::Ambiguous<sal_Bool> > reg = + userExtension->isRegistered(Reference<task::XAbortChannel>(), + Reference<ucb::XCommandEnvironment>()); + //If the value is ambiguous, then we assume that the extension + //is enabled, but something went wrong during enabling. We do not + //automatically disable user extensions. + if (reg.IsPresent && + ! reg.Value.IsAmbiguous && ! reg.Value.Value) + return true; + } + return false; +} + +/* + This method determines the active extension (XPackage.registerPackage) with a + particular identifier. + + The parameter bUserDisabled determines if the user extension is disabled. + + When the user repository contains an extension with the given identifier and + it is not disabled by the user, then it is always registered. Otherwise an + extension is only registered when there is no registered extension in one of + the repositories with a higher priority. That is, if the extension is from + the shared repository and an active extension with the same identifier is in + the user repository, then the extension is not registered. Similarly a + bundled extension is not registered if there is an active extension with the + same identifier in the shared or user repository. +*/ +void ExtensionManager::activateExtension( + OUString const & identifier, OUString const & fileName, + bool bUserDisabled, + bool bStartup, + Reference<task::XAbortChannel> const & xAbortChannel, + Reference<ucb::XCommandEnvironment> const & xCmdEnv ) +{ + std::vector<Reference<css::deployment::XPackage> > listExtensions; + try { + listExtensions = getExtensionsWithSameId(identifier, fileName); + } catch (const lang::IllegalArgumentException &) { + } + OSL_ASSERT(listExtensions.size() == 3); + + activateExtension( + ::comphelper::containerToSequence(listExtensions), + bUserDisabled, bStartup, xAbortChannel, xCmdEnv); + + fireModified(); +} + +void ExtensionManager::activateExtension( + uno::Sequence<Reference<css::deployment::XPackage> > const & seqExt, + bool bUserDisabled, + bool bStartup, + Reference<task::XAbortChannel> const & xAbortChannel, + Reference<ucb::XCommandEnvironment> const & xCmdEnv ) +{ + bool bActive = false; + sal_Int32 len = seqExt.getLength(); + for (sal_Int32 i = 0; i < len; i++) + { + Reference<css::deployment::XPackage> const & aExt = seqExt[i]; + if (aExt.is()) + { + //get the registration value of the current iteration + beans::Optional<beans::Ambiguous<sal_Bool> > optReg = + aExt->isRegistered(xAbortChannel, xCmdEnv); + //If nothing can be registered then break + if (!optReg.IsPresent) + break; + + //Check if this is a disabled user extension, + if (i == 0 && bUserDisabled) + { + aExt->revokePackage(bStartup, xAbortChannel, xCmdEnv); + continue; + } + + //If we have already determined an active extension then we must + //make sure to unregister all extensions with the same id in + //repositories with a lower priority + if (bActive) + { + aExt->revokePackage(bStartup, xAbortChannel, xCmdEnv); + } + else + { + //This is the first extension in the ordered list, which becomes + //the active extension + bActive = true; + //Register if not already done. + //reregister if the value is ambiguous, which indicates that + //something went wrong during last registration. + aExt->registerPackage(bStartup, xAbortChannel, xCmdEnv); + } + } + } +} + +Reference<css::deployment::XPackage> ExtensionManager::backupExtension( + OUString const & identifier, OUString const & fileName, + Reference<css::deployment::XPackageManager> const & xPackageManager, + Reference<ucb::XCommandEnvironment> const & xCmdEnv ) +{ + Reference<css::deployment::XPackage> xBackup; + Reference<ucb::XCommandEnvironment> tmpCmdEnv( + new TmpRepositoryCommandEnv(xCmdEnv->getInteractionHandler())); + Reference<css::deployment::XPackage> xOldExtension = xPackageManager->getDeployedPackage( + identifier, fileName, tmpCmdEnv); + + if (xOldExtension.is()) + { + xBackup = getTmpRepository()->addPackage( + xOldExtension->getURL(), uno::Sequence<beans::NamedValue>(), + OUString(), Reference<task::XAbortChannel>(), tmpCmdEnv); + + OSL_ENSURE(xBackup.is(), "Failed to backup extension"); + } + return xBackup; +} + +//The supported package types are actually determined by the registry. However +//creating a registry +//(desktop/source/deployment/registry/dp_registry.cxx:PackageRegistryImpl) will +//create all the backends, so that the registry can obtain from them the package +//types. Creating the registry will also set up the registry folder containing +//all the subfolders for the respective backends. +//Because all repositories support the same backends, we can just delegate this +//call to one of the repositories. +uno::Sequence< Reference<css::deployment::XPackageTypeInfo> > +ExtensionManager::getSupportedPackageTypes() +{ + return getUserRepository()->getSupportedPackageTypes(); +} +//Do some necessary checks and user interaction. This function does not +//acquire the extension manager mutex and that mutex must not be acquired +//when this function is called. doChecksForAddExtension does synchronous +//user interactions which may require acquiring the solar mutex. +//Returns true if the extension can be installed. +bool ExtensionManager::doChecksForAddExtension( + Reference<css::deployment::XPackageManager> const & xPackageMgr, + uno::Sequence<beans::NamedValue> const & properties, + css::uno::Reference<css::deployment::XPackage> const & xTmpExtension, + Reference<task::XAbortChannel> const & xAbortChannel, + Reference<ucb::XCommandEnvironment> const & xCmdEnv, + Reference<css::deployment::XPackage> & out_existingExtension ) +{ + try + { + Reference<css::deployment::XPackage> xOldExtension; + const OUString sIdentifier = dp_misc::getIdentifier(xTmpExtension); + const OUString sFileName = xTmpExtension->getName(); + const OUString sDisplayName = xTmpExtension->getDisplayName(); + const OUString sVersion = xTmpExtension->getVersion(); + + try + { + xOldExtension = xPackageMgr->getDeployedPackage( + sIdentifier, sFileName, xCmdEnv); + out_existingExtension = xOldExtension; + } + catch (const lang::IllegalArgumentException &) + { + } + bool bCanInstall = false; + + //This part is not guarded against other threads removing, adding, disabling ... + //etc. the same extension. + //checkInstall is safe because it notifies the user if the extension is not yet + //installed in the same repository. Because addExtension has its own guard + //(m_addMutex), another thread cannot add the extension in the meantime. + //checkUpdate is called if the same extension exists in the same + //repository. The user is asked if they want to replace it. Another + //thread + //could already remove the extension. So asking the user was not + //necessary. No harm is done. The other thread may also ask the user + //if he wants to remove the extension. This depends on the + //XCommandEnvironment which it passes to removeExtension. + if (xOldExtension.is()) + { + //throws a CommandFailedException if the user cancels + //the action. + checkUpdate(sVersion, sDisplayName,xOldExtension, xCmdEnv); + } + else + { + //throws a CommandFailedException if the user cancels + //the action. + checkInstall(sDisplayName, xCmdEnv); + } + //Prevent showing the license if requested. + Reference<ucb::XCommandEnvironment> _xCmdEnv(xCmdEnv); + ExtensionProperties props(std::u16string_view(), properties, Reference<ucb::XCommandEnvironment>(), m_xContext); + + dp_misc::DescriptionInfoset info(dp_misc::getDescriptionInfoset(xTmpExtension->getURL())); + const ::std::optional<dp_misc::SimpleLicenseAttributes> licenseAttributes = + info.getSimpleLicenseAttributes(); + + if (licenseAttributes && licenseAttributes->suppressIfRequired + && props.isSuppressedLicense()) + _xCmdEnv.set(new NoLicenseCommandEnv(xCmdEnv->getInteractionHandler())); + + bCanInstall = xTmpExtension->checkPrerequisites( + xAbortChannel, _xCmdEnv, xOldExtension.is() || props.isExtensionUpdate()) == 0; + + return bCanInstall; + } + catch ( const css::deployment::DeploymentException& ) { + throw; + } catch ( const ucb::CommandFailedException & ) { + throw; + } catch ( const ucb::CommandAbortedException & ) { + throw; + } catch (const lang::IllegalArgumentException &) { + throw; + } catch (const uno::RuntimeException &) { + throw; + } catch (const uno::Exception &) { + uno::Any excOccurred = ::cppu::getCaughtException(); + css::deployment::DeploymentException exc( + "Extension Manager: exception in doChecksForAddExtension", + static_cast<OWeakObject*>(this), excOccurred); + throw exc; + } catch (...) { + throw uno::RuntimeException( + "Extension Manager: unexpected exception in doChecksForAddExtension", + static_cast<OWeakObject*>(this)); + } +} + +// Only add to shared and user repository +Reference<css::deployment::XPackage> ExtensionManager::addExtension( + OUString const & url, uno::Sequence<beans::NamedValue> const & properties, + OUString const & repository, + Reference<task::XAbortChannel> const & xAbortChannel, + Reference<ucb::XCommandEnvironment> const & xCmdEnv ) +{ + Reference<css::deployment::XPackage> xNewExtension; + //Determine the repository to use + Reference<css::deployment::XPackageManager> xPackageManager; + if (repository == "user") + xPackageManager = getUserRepository(); + else if (repository == "shared") + xPackageManager = getSharedRepository(); + else + throw lang::IllegalArgumentException( + "No valid repository name provided.", + static_cast<cppu::OWeakObject*>(this), 0); + //We must make sure that the xTmpExtension is not create twice, because this + //would remove the first one. + ::osl::MutexGuard addGuard(m_addMutex); + + Reference<css::deployment::XPackageManager> xTmpRepository(getTmpRepository()); + // make sure xTmpRepository is alive as long as xTmpExtension is; as + // the "tmp" manager is only held weakly by m_xPackageManagerFactory, it + // could otherwise be disposed early, which would in turn dispose + // xTmpExtension's PackageRegistryBackend behind its back + Reference<css::deployment::XPackage> xTmpExtension( + xTmpRepository->addPackage( + url, uno::Sequence<beans::NamedValue>(), OUString(), xAbortChannel, + new TmpRepositoryCommandEnv())); + if (!xTmpExtension.is()) { + throw css::deployment::DeploymentException( + ("Extension Manager: Failed to create temporary XPackage for url: " + + url), + static_cast<OWeakObject*>(this), uno::Any()); + } + + //Make sure the extension is removed from the tmp repository in case + //of an exception + ExtensionRemoveGuard tmpExtensionRemoveGuard(xTmpExtension, getTmpRepository()); + ExtensionRemoveGuard bakExtensionRemoveGuard; + const OUString sIdentifier = dp_misc::getIdentifier(xTmpExtension); + const OUString sFileName = xTmpExtension->getName(); + Reference<css::deployment::XPackage> xOldExtension; + Reference<css::deployment::XPackage> xExtensionBackup; + + uno::Any excOccurred2; + bool bCanInstall = doChecksForAddExtension( + xPackageManager, + properties, + xTmpExtension, + xAbortChannel, + xCmdEnv, + xOldExtension ); + + { + bool bUserDisabled = false; + // In this guarded section (getMutex) we must not use the argument xCmdEnv + // because it may bring up dialogs (XInteractionHandler::handle) this + // may potentially deadlock. See issue + // http://qa.openoffice.org/issues/show_bug.cgi?id=114933 + // By not providing xCmdEnv the underlying APIs will throw an exception if + // the XInteractionRequest cannot be handled. + ::osl::MutexGuard guard(m_aMutex); + + if (bCanInstall) + { + try + { + bUserDisabled = isUserDisabled(sIdentifier, sFileName); + if (xOldExtension.is()) + { + try + { + xOldExtension->revokePackage( + false, xAbortChannel, Reference<ucb::XCommandEnvironment>()); + //save the old user extension in case the user aborts + xExtensionBackup = getBakRepository()->importExtension( + xOldExtension, Reference<task::XAbortChannel>(), + Reference<ucb::XCommandEnvironment>()); + bakExtensionRemoveGuard.set(xExtensionBackup, getBakRepository()); + } + catch (const lang::DisposedException &) + { + //Another thread might have removed the extension meanwhile + } + } + //check again dependencies but prevent user interaction, + //We can disregard the license, because the user must have already + //accepted it, when we called checkPrerequisites the first time + rtl::Reference<SilentCheckPrerequisitesCommandEnv> pSilentCommandEnv = + new SilentCheckPrerequisitesCommandEnv(); + + sal_Int32 failedPrereq = xTmpExtension->checkPrerequisites( + xAbortChannel, pSilentCommandEnv, true); + if (failedPrereq == 0) + { + xNewExtension = xPackageManager->addPackage( + url, properties, OUString(), xAbortChannel, + Reference<ucb::XCommandEnvironment>()); + //If we add a user extension and there is already one which was + //disabled by a user, then the newly installed one is enabled. If we + //add to another repository then the user extension remains + //disabled. + bool bUserDisabled2 = bUserDisabled; + if (repository == "user") + bUserDisabled2 = false; + + // pass the two values via variables to workaround gcc-4.3.4 specific bug (bnc#655912) + OUString sNewExtensionIdentifier = dp_misc::getIdentifier(xNewExtension); + OUString sNewExtensionFileName = xNewExtension->getName(); + + activateExtension( + sNewExtensionIdentifier, sNewExtensionFileName, + bUserDisabled2, false, xAbortChannel, + Reference<ucb::XCommandEnvironment>()); + + // if reached this section, + // this means that either the licensedialog.ui didn't popup, + // or user accepted the license agreement. otherwise + // no need to call fireModified() because user declined + // the license agreement therefore no change made. + try + { + fireModified(); + + }catch ( const css::deployment::DeploymentException& ) { + throw; + } catch ( const ucb::CommandFailedException & ) { + throw; + } catch ( const ucb::CommandAbortedException & ) { + throw; + } catch (const lang::IllegalArgumentException &) { + throw; + } catch (const uno::RuntimeException &) { + throw; + } catch (const uno::Exception &) { + uno::Any excOccurred = ::cppu::getCaughtException(); + css::deployment::DeploymentException exc( + "Extension Manager: Exception on fireModified() " + "in the scope of 'if (failedPrereq == 0)'", + static_cast<OWeakObject*>(this), excOccurred); + throw exc; + } catch (...) { + throw uno::RuntimeException( + "Extension Manager: RuntimeException on fireModified() " + "in the scope of 'if (failedPrereq == 0)'", + static_cast<OWeakObject*>(this)); + } + } + else + { + if (pSilentCommandEnv->m_Exception.hasValue()) + ::cppu::throwException(pSilentCommandEnv->m_Exception); + else if ( pSilentCommandEnv->m_UnknownException.hasValue()) + ::cppu::throwException(pSilentCommandEnv->m_UnknownException); + else + throw css::deployment::DeploymentException ( + "Extension Manager: exception during addExtension, ckeckPrerequisites failed", + static_cast<OWeakObject*>(this), uno::Any()); + } + } + catch ( const css::deployment::DeploymentException& ) { + excOccurred2 = ::cppu::getCaughtException(); + } catch ( const ucb::CommandFailedException & ) { + excOccurred2 = ::cppu::getCaughtException(); + } catch ( const ucb::CommandAbortedException & ) { + excOccurred2 = ::cppu::getCaughtException(); + } catch (const lang::IllegalArgumentException &) { + excOccurred2 = ::cppu::getCaughtException(); + } catch (const uno::RuntimeException &) { + excOccurred2 = ::cppu::getCaughtException(); + } catch (...) { + excOccurred2 = ::cppu::getCaughtException(); + css::deployment::DeploymentException exc( + "Extension Manager: exception during addExtension, url: " + + url, static_cast<OWeakObject*>(this), excOccurred2); + excOccurred2 <<= exc; + } + } + + if (excOccurred2.hasValue()) + { + //It does not matter what exception is thrown. We try to + //recover the original status. + //If the user aborted installation then a ucb::CommandAbortedException + //is thrown. + //Use a private AbortChannel so the user cannot interrupt. + try + { + if (xExtensionBackup.is()) + { + xPackageManager->importExtension( + xExtensionBackup, Reference<task::XAbortChannel>(), + Reference<ucb::XCommandEnvironment>()); + } + activateExtension( + sIdentifier, sFileName, bUserDisabled, false, + Reference<task::XAbortChannel>(), Reference<ucb::XCommandEnvironment>()); + } + catch (...) + { + } + ::cppu::throwException(excOccurred2); + } + } // leaving the guarded section (getMutex()) + + return xNewExtension; +} + +void ExtensionManager::removeExtension( + OUString const & identifier, OUString const & fileName, + OUString const & repository, + Reference<task::XAbortChannel> const & xAbortChannel, + Reference<ucb::XCommandEnvironment> const & xCmdEnv ) +{ + uno::Any excOccurred1; + Reference<css::deployment::XPackage> xExtensionBackup; + Reference<css::deployment::XPackageManager> xPackageManager; + bool bUserDisabled = false; + ::osl::MutexGuard guard(m_aMutex); + try + { +//Determine the repository to use + if (repository == "user") + xPackageManager = getUserRepository(); + else if (repository == "shared") + xPackageManager = getSharedRepository(); + else + throw lang::IllegalArgumentException( + "No valid repository name provided.", + static_cast<cppu::OWeakObject*>(this), 0); + + bUserDisabled = isUserDisabled(identifier, fileName); + //Backup the extension, in case the user cancels the action + xExtensionBackup = backupExtension( + identifier, fileName, xPackageManager, xCmdEnv); + + //revoke the extension if it is active + Reference<css::deployment::XPackage> xOldExtension = + xPackageManager->getDeployedPackage( + identifier, fileName, xCmdEnv); + xOldExtension->revokePackage(false, xAbortChannel, xCmdEnv); + + xPackageManager->removePackage( + identifier, fileName, xAbortChannel, xCmdEnv); + activateExtension(identifier, fileName, bUserDisabled, false, + xAbortChannel, xCmdEnv); + fireModified(); + } + catch ( const css::deployment::DeploymentException& ) { + excOccurred1 = ::cppu::getCaughtException(); + } catch ( const ucb::CommandFailedException & ) { + excOccurred1 = ::cppu::getCaughtException(); + } catch ( const ucb::CommandAbortedException & ) { + excOccurred1 = ::cppu::getCaughtException(); + } catch (const lang::IllegalArgumentException &) { + excOccurred1 = ::cppu::getCaughtException(); + } catch (const uno::RuntimeException &) { + excOccurred1 = ::cppu::getCaughtException(); + } catch (...) { + excOccurred1 = ::cppu::getCaughtException(); + css::deployment::DeploymentException exc( + "Extension Manager: exception during removeExtension", + static_cast<OWeakObject*>(this), excOccurred1); + excOccurred1 <<= exc; + } + + if (excOccurred1.hasValue()) + { + //User aborted installation, restore the previous situation. + //Use a private AbortChannel so the user cannot interrupt. + try + { + Reference<ucb::XCommandEnvironment> tmpCmdEnv( + new TmpRepositoryCommandEnv(xCmdEnv->getInteractionHandler())); + if (xExtensionBackup.is()) + { + xPackageManager->importExtension( + xExtensionBackup, Reference<task::XAbortChannel>(), + tmpCmdEnv); + activateExtension( + identifier, fileName, bUserDisabled, false, + Reference<task::XAbortChannel>(), + tmpCmdEnv); + + getTmpRepository()->removePackage( + dp_misc::getIdentifier(xExtensionBackup), + xExtensionBackup->getName(), xAbortChannel, xCmdEnv); + fireModified(); + } + } + catch (...) + { + } + ::cppu::throwException(excOccurred1); + } + + if (xExtensionBackup.is()) + getTmpRepository()->removePackage( + dp_misc::getIdentifier(xExtensionBackup), + xExtensionBackup->getName(), xAbortChannel, xCmdEnv); +} + +// Only enable extensions from shared and user repository +void ExtensionManager::enableExtension( + Reference<css::deployment::XPackage> const & extension, + Reference<task::XAbortChannel> const & xAbortChannel, + Reference<ucb::XCommandEnvironment> const & xCmdEnv) +{ + ::osl::MutexGuard guard(m_aMutex); + bool bUserDisabled = false; + uno::Any excOccurred; + try + { + if (!extension.is()) + return; + OUString repository = extension->getRepositoryName(); + if (repository != "user") + throw lang::IllegalArgumentException( + "No valid repository name provided.", + static_cast<cppu::OWeakObject*>(this), 0); + + bUserDisabled = isUserDisabled(dp_misc::getIdentifier(extension), + extension->getName()); + + activateExtension(dp_misc::getIdentifier(extension), + extension->getName(), false, false, + xAbortChannel, xCmdEnv); + } + catch ( const css::deployment::DeploymentException& ) { + excOccurred = ::cppu::getCaughtException(); + } catch ( const ucb::CommandFailedException & ) { + excOccurred = ::cppu::getCaughtException(); + } catch ( const ucb::CommandAbortedException & ) { + excOccurred = ::cppu::getCaughtException(); + } catch (const lang::IllegalArgumentException &) { + excOccurred = ::cppu::getCaughtException(); + } catch (const uno::RuntimeException &) { + excOccurred = ::cppu::getCaughtException(); + } catch (...) { + excOccurred = ::cppu::getCaughtException(); + css::deployment::DeploymentException exc( + "Extension Manager: exception during enableExtension", + static_cast<OWeakObject*>(this), excOccurred); + excOccurred <<= exc; + } + + if (!excOccurred.hasValue()) + return; + + try + { + activateExtension(dp_misc::getIdentifier(extension), + extension->getName(), bUserDisabled, false, + xAbortChannel, xCmdEnv); + } + catch (...) + { + } + ::cppu::throwException(excOccurred); +} + +sal_Int32 ExtensionManager::checkPrerequisitesAndEnable( + Reference<css::deployment::XPackage> const & extension, + Reference<task::XAbortChannel> const & xAbortChannel, + Reference<ucb::XCommandEnvironment> const & xCmdEnv) +{ + try + { + if (!extension.is()) + return 0; + ::osl::MutexGuard guard(m_aMutex); + sal_Int32 ret = 0; + Reference<css::deployment::XPackageManager> mgr = + getPackageManager(extension->getRepositoryName()); + ret = mgr->checkPrerequisites(extension, xAbortChannel, xCmdEnv); + if (ret) + { + //There are some unfulfilled prerequisites, try to revoke + extension->revokePackage(false, xAbortChannel, xCmdEnv); + } + const OUString id(dp_misc::getIdentifier(extension)); + activateExtension(id, extension->getName(), + isUserDisabled(id, extension->getName()), false, + xAbortChannel, xCmdEnv); + return ret; + } + catch ( const css::deployment::DeploymentException& ) { + throw; + } catch ( const ucb::CommandFailedException & ) { + throw; + } catch ( const ucb::CommandAbortedException & ) { + throw; + } catch (const lang::IllegalArgumentException &) { + throw; + } catch (const uno::RuntimeException &) { + throw; + } catch (...) { + uno::Any excOccurred = ::cppu::getCaughtException(); + css::deployment::DeploymentException exc( + "Extension Manager: exception during disableExtension", + static_cast<OWeakObject*>(this), excOccurred); + throw exc; + } +} + +void ExtensionManager::disableExtension( + Reference<css::deployment::XPackage> const & extension, + Reference<task::XAbortChannel> const & xAbortChannel, + Reference<ucb::XCommandEnvironment> const & xCmdEnv ) +{ + ::osl::MutexGuard guard(m_aMutex); + uno::Any excOccurred; + bool bUserDisabled = false; + try + { + if (!extension.is()) + return; + const OUString repository( extension->getRepositoryName()); + if (repository != "user") + throw lang::IllegalArgumentException( + "No valid repository name provided.", + static_cast<cppu::OWeakObject*>(this), 0); + + const OUString id(dp_misc::getIdentifier(extension)); + bUserDisabled = isUserDisabled(id, extension->getName()); + + activateExtension(id, extension->getName(), true, false, + xAbortChannel, xCmdEnv); + } + catch ( const css::deployment::DeploymentException& ) { + excOccurred = ::cppu::getCaughtException(); + } catch ( const ucb::CommandFailedException & ) { + excOccurred = ::cppu::getCaughtException(); + } catch ( const ucb::CommandAbortedException & ) { + excOccurred = ::cppu::getCaughtException(); + } catch (const lang::IllegalArgumentException &) { + excOccurred = ::cppu::getCaughtException(); + } catch (const uno::RuntimeException &) { + excOccurred = ::cppu::getCaughtException(); + } catch (...) { + excOccurred = ::cppu::getCaughtException(); + css::deployment::DeploymentException exc( + "Extension Manager: exception during disableExtension", + static_cast<OWeakObject*>(this), excOccurred); + excOccurred <<= exc; + } + + if (!excOccurred.hasValue()) + return; + + try + { + activateExtension(dp_misc::getIdentifier(extension), + extension->getName(), bUserDisabled, false, + xAbortChannel, xCmdEnv); + } + catch (...) + { + } + ::cppu::throwException(excOccurred); +} + +uno::Sequence< Reference<css::deployment::XPackage> > + ExtensionManager::getDeployedExtensions( + OUString const & repository, + Reference<task::XAbortChannel> const &xAbort, + Reference<ucb::XCommandEnvironment> const & xCmdEnv ) +{ + return getPackageManager(repository)->getDeployedPackages( + xAbort, xCmdEnv); +} + +Reference<css::deployment::XPackage> + ExtensionManager::getDeployedExtension( + OUString const & repository, + OUString const & identifier, + OUString const & filename, + Reference<ucb::XCommandEnvironment> const & xCmdEnv ) +{ + return getPackageManager(repository)->getDeployedPackage( + identifier, filename, xCmdEnv); +} + +uno::Sequence< uno::Sequence<Reference<css::deployment::XPackage> > > + ExtensionManager::getAllExtensions( + Reference<task::XAbortChannel> const & xAbort, + Reference<ucb::XCommandEnvironment> const & xCmdEnv ) +{ + try + { + id2extensions mapExt; + + uno::Sequence<Reference<css::deployment::XPackage> > userExt = + getUserRepository()->getDeployedPackages(xAbort, xCmdEnv); + addExtensionsToMap(mapExt, userExt, u"user"); + uno::Sequence<Reference<css::deployment::XPackage> > sharedExt = + getSharedRepository()->getDeployedPackages(xAbort, xCmdEnv); + addExtensionsToMap(mapExt, sharedExt, u"shared"); + uno::Sequence<Reference<css::deployment::XPackage> > bundledExt = + getBundledRepository()->getDeployedPackages(xAbort, xCmdEnv); + addExtensionsToMap(mapExt, bundledExt, u"bundled"); + + // Create the tmp repository to trigger its clean up (deletion + // of old temporary data.) + getTmpRepository(); + + //copy the values of the map to a vector for sorting + std::vector< std::vector<Reference<css::deployment::XPackage> > > + vecExtensions; + for (auto const& elem : mapExt) + vecExtensions.push_back(elem.second); + + //sort the element according to the identifier + std::sort(vecExtensions.begin(), vecExtensions.end(), CompIdentifiers()); + + sal_Int32 j = 0; + uno::Sequence< uno::Sequence<Reference<css::deployment::XPackage> > > seqSeq(vecExtensions.size()); + auto seqSeqRange = asNonConstRange(seqSeq); + for (auto const& elem : vecExtensions) + { + seqSeqRange[j++] = comphelper::containerToSequence(elem); + } + return seqSeq; + + } catch ( const css::deployment::DeploymentException& ) { + throw; + } catch ( const ucb::CommandFailedException & ) { + throw; + } catch ( const ucb::CommandAbortedException & ) { + throw; + } catch (const lang::IllegalArgumentException &) { + throw; + } catch (const uno::RuntimeException &) { + throw; + } catch (...) { + uno::Any exc = ::cppu::getCaughtException(); + throw css::deployment::DeploymentException( + "Extension Manager: exception during enableExtension", + static_cast<OWeakObject*>(this), exc); + } +} + +// Only to be called from unopkg or soffice bootstrap (with force=true in the +// latter case): +void ExtensionManager::reinstallDeployedExtensions( + sal_Bool force, OUString const & repository, + Reference<task::XAbortChannel> const & xAbortChannel, + Reference<ucb::XCommandEnvironment> const & xCmdEnv ) +{ + try + { + Reference<css::deployment::XPackageManager> + xPackageManager = getPackageManager(repository); + + std::set< OUString > disabledExts; + { + const uno::Sequence< Reference<css::deployment::XPackage> > extensions( + xPackageManager->getDeployedPackages(xAbortChannel, xCmdEnv)); + for ( const Reference<css::deployment::XPackage>& package : extensions ) + { + try + { + beans::Optional< beans::Ambiguous< sal_Bool > > registered( + package->isRegistered(xAbortChannel, xCmdEnv)); + if (registered.IsPresent && + !(registered.Value.IsAmbiguous || + registered.Value.Value)) + { + const OUString id = dp_misc::getIdentifier(package); + OSL_ASSERT(!id.isEmpty()); + disabledExts.insert(id); + } + } + catch (const lang::DisposedException &) + { + } + } + } + + ::osl::MutexGuard guard(m_aMutex); + xPackageManager->reinstallDeployedPackages( + force, xAbortChannel, xCmdEnv); + //We must sync here, otherwise we will get exceptions when extensions + //are removed. + dp_misc::syncRepositories(force, xCmdEnv); + const uno::Sequence< Reference<css::deployment::XPackage> > extensions( + xPackageManager->getDeployedPackages(xAbortChannel, xCmdEnv)); + + for ( const Reference<css::deployment::XPackage>& package : extensions ) + { + try + { + const OUString id = dp_misc::getIdentifier(package); + const OUString fileName = package->getName(); + OSL_ASSERT(!id.isEmpty()); + activateExtension( + id, fileName, disabledExts.find(id) != disabledExts.end(), + true, xAbortChannel, xCmdEnv ); + } + catch (const lang::DisposedException &) + { + } + } + } catch ( const css::deployment::DeploymentException& ) { + throw; + } catch ( const ucb::CommandFailedException & ) { + throw; + } catch ( const ucb::CommandAbortedException & ) { + throw; + } catch (const lang::IllegalArgumentException &) { + throw; + } catch (const uno::RuntimeException &) { + throw; + } catch (...) { + uno::Any exc = ::cppu::getCaughtException(); + throw css::deployment::DeploymentException( + "Extension Manager: exception during enableExtension", + static_cast<OWeakObject*>(this), exc); + } +} + +sal_Bool ExtensionManager::synchronize( + Reference<task::XAbortChannel> const & xAbortChannel, + Reference<ucb::XCommandEnvironment> const & xCmdEnv ) +{ + try + { + ::osl::MutexGuard guard(m_aMutex); + OUString sSynchronizingShared(StrSyncRepository()); + sSynchronizingShared = sSynchronizingShared.replaceAll("%NAME", "shared"); + dp_misc::ProgressLevel progressShared(xCmdEnv, sSynchronizingShared); + bool bModified = getSharedRepository()->synchronize(xAbortChannel, xCmdEnv); + progressShared.update("\n\n"); + + OUString sSynchronizingBundled(StrSyncRepository()); + sSynchronizingBundled = sSynchronizingBundled.replaceAll("%NAME", "bundled"); + dp_misc::ProgressLevel progressBundled(xCmdEnv, sSynchronizingBundled); + bModified |= static_cast<bool>(getBundledRepository()->synchronize(xAbortChannel, xCmdEnv)); + progressBundled.update("\n\n"); + + //Always determine the active extension. + //TODO: Is this still necessary? (It used to be necessary for the + // first-start optimization: The setup created the registration data + // for the bundled extensions (share/prereg/bundled) which was copied to + // the user installation when a user started OOo for the first time + // after running setup. All bundled extensions were registered at that + // moment. However, extensions with the same identifier could be in the + // shared or user repository, in which case the respective bundled + // extensions had to be revoked.) + try + { + const uno::Sequence<uno::Sequence<Reference<css::deployment::XPackage> > > + seqSeqExt = getAllExtensions(xAbortChannel, xCmdEnv); + for (uno::Sequence<Reference<css::deployment::XPackage> > const & seqExt : seqSeqExt) + { + activateExtension(seqExt, isUserDisabled(seqExt), true, + xAbortChannel, xCmdEnv); + } + } + catch (...) + { + //We catch the exception, so we can write the lastmodified file + //so we will no repeat this every time OOo starts. + OSL_FAIL("Extensions Manager: synchronize"); + } + OUString lastSyncBundled("$BUNDLED_EXTENSIONS_USER/lastsynchronized"); + writeLastModified(lastSyncBundled, xCmdEnv, m_xContext); + OUString lastSyncShared("$SHARED_EXTENSIONS_USER/lastsynchronized"); + writeLastModified(lastSyncShared, xCmdEnv, m_xContext); + return bModified; + } catch ( const css::deployment::DeploymentException& ) { + throw; + } catch ( const ucb::CommandFailedException & ) { + throw; + } catch ( const ucb::CommandAbortedException & ) { + throw; + } catch (const lang::IllegalArgumentException &) { + throw; + } catch (const uno::RuntimeException &) { + throw; + } catch (...) { + uno::Any exc = ::cppu::getCaughtException(); + throw css::deployment::DeploymentException( + "Extension Manager: exception in synchronize", + static_cast<OWeakObject*>(this), exc); + } +} + +// Notify the user when a new extension is to be installed. This is only the +// case when one uses the system integration to install an extension (double +// clicking on .oxt file etc.)). The function must only be called if there is no +// extension with the same identifier already deployed. Then the checkUpdate +// function will inform the user that the extension is about to be installed In +// case the user cancels the installation a CommandFailed exception is +// thrown. +void ExtensionManager::checkInstall( + OUString const & displayName, + Reference<ucb::XCommandEnvironment> const & cmdEnv) +{ + uno::Any request( + css::deployment::InstallException( + "Extension " + displayName + + " is about to be installed.", + static_cast<OWeakObject *>(this), displayName)); + bool approve = false, abort = false; + if (! dp_misc::interactContinuation( + request, cppu::UnoType<task::XInteractionApprove>::get(), + cmdEnv, &approve, &abort )) + { + OSL_ASSERT( !approve && !abort ); + throw css::deployment::DeploymentException( + DpResId(RID_STR_ERROR_WHILE_ADDING) + displayName, + static_cast<OWeakObject *>(this), request ); + } + if (abort || !approve) + throw ucb::CommandFailedException( + DpResId(RID_STR_ERROR_WHILE_ADDING) + displayName, + static_cast<OWeakObject *>(this), request ); +} + +/* The function will make the user interaction in case there is an extension +installed with the same id. This function may only be called if there is already +an extension. +*/ +void ExtensionManager::checkUpdate( + OUString const & newVersion, + OUString const & newDisplayName, + Reference<css::deployment::XPackage> const & oldExtension, + Reference<ucb::XCommandEnvironment> const & xCmdEnv ) +{ + // package already deployed, interact --force: + uno::Any request( + (css::deployment::VersionException( + DpResId( + RID_STR_PACKAGE_ALREADY_ADDED ) + newDisplayName, + static_cast<OWeakObject *>(this), newVersion, newDisplayName, + oldExtension ) ) ); + bool replace = false, abort = false; + if (! dp_misc::interactContinuation( + request, cppu::UnoType<task::XInteractionApprove>::get(), + xCmdEnv, &replace, &abort )) { + OSL_ASSERT( !replace && !abort ); + throw css::deployment::DeploymentException( + DpResId( + RID_STR_ERROR_WHILE_ADDING) + newDisplayName, + static_cast<OWeakObject *>(this), request ); + } + if (abort || !replace) + throw ucb::CommandFailedException( + DpResId( + RID_STR_PACKAGE_ALREADY_ADDED) + newDisplayName, + static_cast<OWeakObject *>(this), request ); +} + +uno::Sequence<Reference<css::deployment::XPackage> > SAL_CALL +ExtensionManager::getExtensionsWithUnacceptedLicenses( + OUString const & repository, + Reference<ucb::XCommandEnvironment> const & xCmdEnv) +{ + Reference<css::deployment::XPackageManager> + xPackageManager = getPackageManager(repository); + ::osl::MutexGuard guard(m_aMutex); + return xPackageManager->getExtensionsWithUnacceptedLicenses(xCmdEnv); +} + +sal_Bool ExtensionManager::isReadOnlyRepository(OUString const & repository) +{ + return getPackageManager(repository)->isReadOnly(); +} + + +// XModifyBroadcaster + +void ExtensionManager::addModifyListener( + Reference<util::XModifyListener> const & xListener ) +{ + check(); + rBHelper.addListener( cppu::UnoType<decltype(xListener)>::get(), xListener ); +} + + +void ExtensionManager::removeModifyListener( + Reference<util::XModifyListener> const & xListener ) +{ + check(); + rBHelper.removeListener( cppu::UnoType<decltype(xListener)>::get(), xListener ); +} + +void ExtensionManager::check() +{ + ::osl::MutexGuard guard( m_aMutex ); + if (rBHelper.bInDispose || rBHelper.bDisposed) { + throw lang::DisposedException( + "ExtensionManager instance has already been disposed!", + static_cast<OWeakObject *>(this) ); + } +} + +void ExtensionManager::fireModified() +{ + ::cppu::OInterfaceContainerHelper * pContainer = rBHelper.getContainer( + cppu::UnoType<util::XModifyListener>::get() ); + if (pContainer != nullptr) { + pContainer->forEach<util::XModifyListener>( + [this] (uno::Reference<util::XModifyListener> const& xListener) + { return xListener->modified(lang::EventObject(static_cast<OWeakObject *>(this))); }); + } +} + +} // namespace dp_manager + + +extern "C" SAL_DLLPUBLIC_EXPORT css::uno::XInterface* +com_sun_star_comp_deployment_ExtensionManager_get_implementation( + css::uno::XComponentContext* context, css::uno::Sequence<css::uno::Any> const& ) +{ + return cppu::acquire(new dp_manager::ExtensionManager(context)); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/desktop/source/deployment/manager/dp_extensionmanager.hxx b/desktop/source/deployment/manager/dp_extensionmanager.hxx new file mode 100644 index 000000000..0e08314c3 --- /dev/null +++ b/desktop/source/deployment/manager/dp_extensionmanager.hxx @@ -0,0 +1,227 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#pragma once + +#include <strings.hrc> +#include <dp_shared.hxx> +#include <cppuhelper/basemutex.hxx> +#include <cppuhelper/compbase.hxx> +#include <com/sun/star/deployment/XExtensionManager.hpp> +#include <com/sun/star/deployment/XPackageManager.hpp> +#include <com/sun/star/deployment/XPackageManagerFactory.hpp> +#include <com/sun/star/lang/XServiceInfo.hpp> +#include <com/sun/star/uno/XComponentContext.hpp> +#include <osl/mutex.hxx> +#include <vector> +#include <unordered_map> + +namespace dp_manager { + +typedef std::unordered_map< + OUString, + std::vector<css::uno::Reference<css::deployment::XPackage> > > id2extensions; + +class ExtensionManager : private cppu::BaseMutex, + public ::cppu::WeakComponentImplHelper< css::deployment::XExtensionManager, css::lang::XServiceInfo > +{ +public: + explicit ExtensionManager( css::uno::Reference< css::uno::XComponentContext >const& xContext); + virtual ~ExtensionManager() override; + + void check(); + void fireModified(); + +public: + + // XServiceInfo + virtual OUString SAL_CALL getImplementationName() override; + virtual sal_Bool SAL_CALL supportsService( const OUString& ServiceName ) override; + virtual css::uno::Sequence< OUString > SAL_CALL getSupportedServiceNames() override; + +// XModifyBroadcaster + virtual void SAL_CALL addModifyListener( + css::uno::Reference<css::util::XModifyListener> const & xListener ) override; + virtual void SAL_CALL removeModifyListener( + css::uno::Reference<css::util::XModifyListener> const & xListener ) override; + +//XExtensionManager + virtual css::uno::Sequence< + css::uno::Reference<css::deployment::XPackageTypeInfo> > SAL_CALL + getSupportedPackageTypes() override; + + virtual css::uno::Reference<css::task::XAbortChannel> SAL_CALL + createAbortChannel() override; + + virtual css::uno::Reference<css::deployment::XPackage> SAL_CALL addExtension( + OUString const & url, + css::uno::Sequence<css::beans::NamedValue> const & properties, + OUString const & repository, + css::uno::Reference<css::task::XAbortChannel> const & xAbortChannel, + css::uno::Reference<css::ucb::XCommandEnvironment> const & xCmdEnv ) override; + + virtual void SAL_CALL removeExtension( + OUString const & identifier, + OUString const & filename, + OUString const & repository, + css::uno::Reference<css::task::XAbortChannel> const & xAbortChannel, + css::uno::Reference<css::ucb::XCommandEnvironment> const & xCmdEnv ) override; + + virtual void SAL_CALL enableExtension( + css::uno::Reference<css::deployment::XPackage> const & extension, + css::uno::Reference<css::task::XAbortChannel> const & xAbortChannel, + css::uno::Reference<css::ucb::XCommandEnvironment> const & xCmdEnv ) override; + + virtual void SAL_CALL disableExtension( + css::uno::Reference<css::deployment::XPackage> const & extension, + css::uno::Reference<css::task::XAbortChannel> const & xAbortChannel, + css::uno::Reference<css::ucb::XCommandEnvironment> const & xCmdEnv ) override; + + virtual sal_Int32 SAL_CALL checkPrerequisitesAndEnable( + css::uno::Reference<css::deployment::XPackage> const & extension, + css::uno::Reference<css::task::XAbortChannel> const & xAbortChannel, + css::uno::Reference<css::ucb::XCommandEnvironment> const & xCmdEnv ) override; + + virtual css::uno::Sequence< css::uno::Reference<css::deployment::XPackage> > + SAL_CALL getDeployedExtensions( + OUString const & repository, + css::uno::Reference<css::task::XAbortChannel> const &, + css::uno::Reference<css::ucb::XCommandEnvironment> const & xCmdEnv ) override; + + virtual css::uno::Reference< css::deployment::XPackage> + SAL_CALL getDeployedExtension( + OUString const & repository, + OUString const & identifier, + OUString const & filename, + css::uno::Reference< css::ucb::XCommandEnvironment> const & xCmdEnv ) override; + + virtual css::uno::Sequence<css::uno::Reference<css::deployment::XPackage> > + SAL_CALL getExtensionsWithSameIdentifier( + OUString const & identifier, + OUString const & filename, + css::uno::Reference< css::ucb::XCommandEnvironment> const & xCmdEnv ) override; + + virtual css::uno::Sequence< css::uno::Sequence<css::uno::Reference<css::deployment::XPackage> > > + SAL_CALL getAllExtensions( + css::uno::Reference<css::task::XAbortChannel> const &, + css::uno::Reference<css::ucb::XCommandEnvironment> const & xCmdEnv ) override; + + virtual void SAL_CALL reinstallDeployedExtensions( + sal_Bool force, OUString const & repository, + css::uno::Reference< css::task::XAbortChannel> const & xAbortChannel, + css::uno::Reference< css::ucb::XCommandEnvironment> const & xCmdEnv ) override; + + virtual sal_Bool SAL_CALL synchronize( + css::uno::Reference<css::task::XAbortChannel> const & xAbortChannel, + css::uno::Reference<css::ucb::XCommandEnvironment> const & xCmdEnv ) override; + + virtual css::uno::Sequence<css::uno::Reference<css::deployment::XPackage> > SAL_CALL + getExtensionsWithUnacceptedLicenses( + OUString const & repository, + css::uno::Reference<css::ucb::XCommandEnvironment> const & xCmdEnv) override; + + virtual sal_Bool SAL_CALL isReadOnlyRepository(OUString const & repository) override; + +private: + + static OUString StrSyncRepository() { return DpResId(RID_STR_SYNCHRONIZING_REPOSITORY); } + + css::uno::Reference< css::uno::XComponentContext> m_xContext; + css::uno::Reference<css::deployment::XPackageManagerFactory> m_xPackageManagerFactory; + + //only to be used within addExtension + ::osl::Mutex m_addMutex; + /* contains the names of all repositories (except tmp) in order of there + priority. That is, the first element is "user" followed by "shared" and + then "bundled" + */ + std::vector< OUString > m_repositoryNames; + + css::uno::Reference<css::deployment::XPackageManager> getUserRepository(); + css::uno::Reference<css::deployment::XPackageManager> getSharedRepository(); + css::uno::Reference<css::deployment::XPackageManager> getBundledRepository(); + css::uno::Reference<css::deployment::XPackageManager> getTmpRepository(); + css::uno::Reference<css::deployment::XPackageManager> getBakRepository(); + + bool isUserDisabled(OUString const & identifier, + OUString const & filename); + + static bool isUserDisabled( + css::uno::Sequence<css::uno::Reference<css::deployment::XPackage> > const & seqExtSameId); + + void activateExtension( + OUString const & identifier, + OUString const & fileName, + bool bUserDisabled, bool bStartup, + css::uno::Reference<css::task::XAbortChannel> const & xAbortChannel, + css::uno::Reference<css::ucb::XCommandEnvironment> const & xCmdEnv); + + static void activateExtension( + css::uno::Sequence<css::uno::Reference<css::deployment::XPackage> > const & seqExt, + bool bUserDisabled, bool bStartup, + css::uno::Reference<css::task::XAbortChannel> const & xAbortChannel, + css::uno::Reference<css::ucb::XCommandEnvironment> const & xCmdEnv ); + + std::vector<css::uno::Reference<css::deployment::XPackage> > + getExtensionsWithSameId(OUString const & identifier, + OUString const & fileName); + + css::uno::Reference<css::deployment::XPackage> backupExtension( + OUString const & identifier, OUString const & fileName, + css::uno::Reference<css::deployment::XPackageManager> const & xPackageManager, + css::uno::Reference<css::ucb::XCommandEnvironment> const & xCmdEnv); + + void checkInstall( + OUString const & displayName, + css::uno::Reference<css::ucb::XCommandEnvironment> const & cmdEnv); + + void checkUpdate( + OUString const & newVersion, + OUString const & newDisplayName, + css::uno::Reference<css::deployment::XPackage> const & oldExtension, + css::uno::Reference<css::ucb::XCommandEnvironment> const & xCmdEnv); + + void addExtensionsToMap( + id2extensions & mapExt, + css::uno::Sequence<css::uno::Reference<css::deployment::XPackage> > const & seqExt, + std::u16string_view repository); + + /// @throws css::lang::IllegalArgumentException + /// @throws css::uno::RuntimeException + css::uno::Reference<css::deployment::XPackageManager> + getPackageManager(std::u16string_view repository); + + /// @throws css::deployment::DeploymentException + /// @throws css::ucb::CommandFailedException + /// @throws css::ucb::CommandAbortedException + /// @throws css::lang::IllegalArgumentException + /// @throws css::uno::RuntimeException + bool doChecksForAddExtension( + css::uno::Reference<css::deployment::XPackageManager> const & xPackageMgr, + css::uno::Sequence<css::beans::NamedValue> const & properties, + css::uno::Reference<css::deployment::XPackage> const & xTmpExtension, + css::uno::Reference<css::task::XAbortChannel> const & xAbortChannel, + css::uno::Reference<css::ucb::XCommandEnvironment> const & xCmdEnv, + css::uno::Reference<css::deployment::XPackage> & out_existingExtension ); + +}; + +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/desktop/source/deployment/manager/dp_informationprovider.cxx b/desktop/source/deployment/manager/dp_informationprovider.cxx new file mode 100644 index 000000000..1e7beea34 --- /dev/null +++ b/desktop/source/deployment/manager/dp_informationprovider.cxx @@ -0,0 +1,338 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include <cppuhelper/implbase.hxx> +#include <cppuhelper/supportsservice.hxx> + +#include <com/sun/star/deployment/UpdateInformationProvider.hpp> +#include <com/sun/star/deployment/XPackage.hpp> +#include <com/sun/star/deployment/XPackageInformationProvider.hpp> +#include <com/sun/star/deployment/ExtensionManager.hpp> +#include <com/sun/star/deployment/XUpdateInformationProvider.hpp> +#include <com/sun/star/lang/IllegalArgumentException.hpp> +#include <com/sun/star/lang/XServiceInfo.hpp> +#include <com/sun/star/task/XAbortChannel.hpp> +#include <com/sun/star/ucb/ContentCreationException.hpp> +#include <com/sun/star/uno/XComponentContext.hpp> +#include <com/sun/star/ucb/XCommandEnvironment.hpp> +#include <com/sun/star/xml/dom/XElement.hpp> + +#include <com/sun/star/uno/Reference.hxx> +#include <osl/diagnose.h> +#include <rtl/ustring.hxx> +#include <tools/diagnose_ex.h> +#include <ucbhelper/content.hxx> + +#include <dp_dependencies.hxx> +#include <dp_descriptioninfoset.hxx> +#include <dp_identifier.hxx> +#include <dp_version.hxx> +#include <dp_update.hxx> + +namespace beans = com::sun::star::beans ; +namespace deployment = com::sun::star::deployment ; +namespace lang = com::sun::star::lang ; +namespace task = com::sun::star::task ; +namespace css_ucb = com::sun::star::ucb ; +namespace uno = com::sun::star::uno ; +namespace xml = com::sun::star::xml ; + + +namespace dp_info { + +namespace { + +class PackageInformationProvider : + public ::cppu::WeakImplHelper< deployment::XPackageInformationProvider, lang::XServiceInfo > + +{ + public: + explicit PackageInformationProvider( uno::Reference< uno::XComponentContext >const& xContext); + + // XServiceInfo + virtual OUString SAL_CALL getImplementationName() override; + virtual sal_Bool SAL_CALL supportsService( const OUString& ServiceName ) override; + virtual css::uno::Sequence< OUString > SAL_CALL getSupportedServiceNames() override; + + // XPackageInformationProvider + virtual OUString SAL_CALL getPackageLocation( const OUString& extensionId ) override; + virtual uno::Sequence< uno::Sequence< OUString > > SAL_CALL isUpdateAvailable( const OUString& extensionId ) override; + virtual uno::Sequence< uno::Sequence< OUString > > SAL_CALL getExtensionList() override; + +private: + + uno::Reference< uno::XComponentContext> mxContext; + + OUString getPackageLocation( const OUString& repository, + std::u16string_view _sExtensionId ); + + uno::Reference< deployment::XUpdateInformationProvider > mxUpdateInformation; +}; + +} + +PackageInformationProvider::PackageInformationProvider( uno::Reference< uno::XComponentContext > const& xContext) : + mxContext( xContext ), + mxUpdateInformation( deployment::UpdateInformationProvider::create( xContext ) ) +{ +} + +// XServiceInfo +OUString PackageInformationProvider::getImplementationName() +{ + return "com.sun.star.comp.deployment.PackageInformationProvider"; +} + +sal_Bool PackageInformationProvider::supportsService( const OUString& ServiceName ) +{ + return cppu::supportsService(this, ServiceName); +} + +css::uno::Sequence< OUString > PackageInformationProvider::getSupportedServiceNames() +{ + // a private one: + return { "com.sun.star.comp.deployment.PackageInformationProvider" }; +} + +OUString PackageInformationProvider::getPackageLocation( + const OUString & repository, + std::u16string_view _rExtensionId ) +{ + OUString aLocationURL; + uno::Reference<deployment::XExtensionManager> xManager = + deployment::ExtensionManager::get(mxContext); + + if ( xManager.is() ) + { + const uno::Sequence< uno::Reference< deployment::XPackage > > packages( + xManager->getDeployedExtensions( + repository, + uno::Reference< task::XAbortChannel >(), + uno::Reference< css_ucb::XCommandEnvironment > () ) ); + + for ( int pos = packages.getLength(); pos--; ) + { + try + { + const beans::Optional< OUString > aID = packages[ pos ]->getIdentifier(); + if ( aID.IsPresent && (aID.Value == _rExtensionId ) ) + { + aLocationURL = packages[ pos ]->getURL(); + break; + } + } + catch ( uno::RuntimeException & ) {} + } + } + + return aLocationURL; +} + + +OUString SAL_CALL +PackageInformationProvider::getPackageLocation( const OUString& _sExtensionId ) +{ + OUString aLocationURL = getPackageLocation( "user", _sExtensionId ); + + if ( aLocationURL.isEmpty() ) + { + aLocationURL = getPackageLocation( "shared", _sExtensionId ); + } + if ( aLocationURL.isEmpty() ) + { + aLocationURL = getPackageLocation( "bundled", _sExtensionId ); + } + if ( !aLocationURL.isEmpty() ) + { + try + { + ::ucbhelper::Content aContent( aLocationURL, nullptr, mxContext ); + aLocationURL = aContent.getURL(); + } + catch (const css::ucb::ContentCreationException&) + { + TOOLS_WARN_EXCEPTION("desktop.deployment", "ignoring"); + } + } + return aLocationURL; +} + +uno::Sequence< uno::Sequence< OUString > > SAL_CALL +PackageInformationProvider::isUpdateAvailable( const OUString& _sExtensionId ) +{ + uno::Sequence< uno::Sequence< OUString > > aList; + + uno::Reference<deployment::XExtensionManager> extMgr = + deployment::ExtensionManager::get(mxContext); + + if (!extMgr.is()) + { + OSL_ASSERT(false); + return aList; + } + std::vector<std::pair<uno::Reference<deployment::XPackage>, uno::Any > > errors; + dp_misc::UpdateInfoMap updateInfoMap; + if (!_sExtensionId.isEmpty()) + { + std::vector<uno::Reference<deployment::XPackage> > vecExtensions; + uno::Reference<deployment::XPackage> extension; + try + { + extension = dp_misc::getExtensionWithHighestVersion( + extMgr->getExtensionsWithSameIdentifier( + _sExtensionId, _sExtensionId, uno::Reference<css_ucb::XCommandEnvironment>())); + vecExtensions.push_back(extension); + } + catch (lang::IllegalArgumentException &) + { + OSL_ASSERT(false); + } + updateInfoMap = dp_misc::getOnlineUpdateInfos( + mxContext, extMgr, mxUpdateInformation, &vecExtensions, errors); + } + else + { + updateInfoMap = dp_misc::getOnlineUpdateInfos( + mxContext, extMgr, mxUpdateInformation, nullptr, errors); + } + + int nCount = 0; + for (auto const& updateInfo : updateInfoMap) + { + dp_misc::UpdateInfo const & info = updateInfo.second; + + OUString sOnlineVersion; + if (info.info.is()) + { + // check, if there are unsatisfied dependencies and ignore this online update + dp_misc::DescriptionInfoset infoset(mxContext, info.info); + uno::Sequence< uno::Reference< xml::dom::XElement > > + ds( dp_misc::Dependencies::check( infoset ) ); + if ( ! ds.hasElements() ) + sOnlineVersion = info.version; + } + + OUString sVersionUser; + OUString sVersionShared; + OUString sVersionBundled; + uno::Sequence< uno::Reference< deployment::XPackage> > extensions; + try { + extensions = extMgr->getExtensionsWithSameIdentifier( + dp_misc::getIdentifier(info.extension), info.extension->getName(), + uno::Reference<css_ucb::XCommandEnvironment>()); + } catch (const lang::IllegalArgumentException&) { + TOOLS_WARN_EXCEPTION("desktop.deployment", "ignoring"); + continue; + } + OSL_ASSERT(extensions.getLength() == 3); + if (extensions[0].is() ) + sVersionUser = extensions[0]->getVersion(); + if (extensions[1].is() ) + sVersionShared = extensions[1]->getVersion(); + if (extensions[2].is() ) + sVersionBundled = extensions[2]->getVersion(); + + bool bSharedReadOnly = extMgr->isReadOnlyRepository("shared"); + + dp_misc::UPDATE_SOURCE sourceUser = dp_misc::isUpdateUserExtension( + bSharedReadOnly, sVersionUser, sVersionShared, sVersionBundled, sOnlineVersion); + dp_misc::UPDATE_SOURCE sourceShared = dp_misc::isUpdateSharedExtension( + bSharedReadOnly, sVersionShared, sVersionBundled, sOnlineVersion); + + OUString updateVersionUser; + OUString updateVersionShared; + if (sourceUser != dp_misc::UPDATE_SOURCE_NONE) + updateVersionUser = dp_misc::getHighestVersion( + sVersionShared, sVersionBundled, sOnlineVersion); + if (sourceShared != dp_misc::UPDATE_SOURCE_NONE) + updateVersionShared = dp_misc::getHighestVersion( + OUString(), sVersionBundled, sOnlineVersion); + OUString updateVersion; + if (dp_misc::compareVersions(updateVersionUser, updateVersionShared) == dp_misc::GREATER) + updateVersion = updateVersionUser; + else + updateVersion = updateVersionShared; + if (!updateVersion.isEmpty()) + { + + OUString aNewEntry[2]; + aNewEntry[0] = updateInfo.first; + aNewEntry[1] = updateVersion; + aList.realloc( ++nCount ); + aList.getArray()[ nCount-1 ] = ::uno::Sequence< OUString >( aNewEntry, 2 ); + } + } + return aList; +} + + +uno::Sequence< uno::Sequence< OUString > > SAL_CALL PackageInformationProvider::getExtensionList() +{ + const uno::Reference<deployment::XExtensionManager> mgr = + deployment::ExtensionManager::get(mxContext); + + if (!mgr.is()) + return uno::Sequence< uno::Sequence< OUString > >(); + + const uno::Sequence< uno::Sequence< uno::Reference<deployment::XPackage > > > + allExt = mgr->getAllExtensions( + uno::Reference< task::XAbortChannel >(), + uno::Reference< css_ucb::XCommandEnvironment > () ); + + uno::Sequence< uno::Sequence< OUString > > retList; + + sal_Int32 cAllIds = allExt.getLength(); + retList.realloc(cAllIds); + auto pretList = retList.getArray(); + + for (sal_Int32 i = 0; i < cAllIds; i++) + { + //The inner sequence contains extensions with the same identifier from + //all the different repositories, that is user, share, bundled. + const uno::Sequence< uno::Reference< deployment::XPackage > > & + seqExtension = allExt[i]; + sal_Int32 cExt = seqExtension.getLength(); + OSL_ASSERT(cExt == 3); + for (sal_Int32 j = 0; j < cExt; j++) + { + //ToDo according to the old code the first found extension is used + //even if another one with the same id has a better version. + uno::Reference< deployment::XPackage > const & xExtension( seqExtension[j] ); + if (xExtension.is()) + { + pretList[i] = { dp_misc::getIdentifier(xExtension), xExtension->getVersion() }; + break; + } + } + } + return retList; +} + + +} // namespace dp_info + +extern "C" SAL_DLLPUBLIC_EXPORT css::uno::XInterface* +com_sun_star_comp_deployment_PackageInformationProvider_get_implementation( + css::uno::XComponentContext* context, css::uno::Sequence<css::uno::Any> const& ) +{ + return cppu::acquire(new dp_info::PackageInformationProvider(context)); +} + + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/desktop/source/deployment/manager/dp_manager.cxx b/desktop/source/deployment/manager/dp_manager.cxx new file mode 100644 index 000000000..68765a63f --- /dev/null +++ b/desktop/source/deployment/manager/dp_manager.cxx @@ -0,0 +1,1600 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include <config_features.h> + +#include <dp_interact.h> +#include <dp_misc.h> +#include <dp_registry.hxx> +#include <dp_shared.hxx> +#include <strings.hrc> +#include <dp_ucb.h> +#include <dp_platform.hxx> +#include "dp_manager.h" +#include <dp_identifier.hxx> +#include <rtl/ustrbuf.hxx> +#include <rtl/string.hxx> +#include <rtl/uri.hxx> +#include <rtl/bootstrap.hxx> +#include <sal/log.hxx> +#include <tools/urlobj.hxx> +#include <tools/diagnose_ex.h> +#include <osl/diagnose.h> +#include <osl/file.hxx> +#include <osl/security.hxx> +#include <cppuhelper/exc_hlp.hxx> +#include <comphelper/logging.hxx> +#include <comphelper/sequence.hxx> +#include <utility> +#include <xmlscript/xml_helper.hxx> +#include <svl/inettype.hxx> +#include <com/sun/star/lang/IllegalArgumentException.hpp> +#include <com/sun/star/lang/WrappedTargetRuntimeException.hpp> +#include <com/sun/star/beans/UnknownPropertyException.hpp> +#include <com/sun/star/logging/LogLevel.hpp> +#include <com/sun/star/logging/FileHandler.hpp> +#include <com/sun/star/logging/SimpleTextFormatter.hpp> +#include <com/sun/star/logging/XLogger.hpp> +#include <com/sun/star/util/XUpdatable.hpp> +#include <com/sun/star/sdbc/XResultSet.hpp> +#include <com/sun/star/sdbc/XRow.hpp> +#include <com/sun/star/ucb/CommandAbortedException.hpp> +#include <com/sun/star/ucb/CommandFailedException.hpp> +#include <com/sun/star/ucb/ContentCreationException.hpp> +#include <com/sun/star/ucb/XContentAccess.hpp> +#include <com/sun/star/ucb/NameClash.hpp> +#include <com/sun/star/deployment/DeploymentException.hpp> +#include <com/sun/star/deployment/InvalidRemovedParameterException.hpp> +#include <com/sun/star/deployment/Prerequisites.hpp> +#include <com/sun/star/ucb/UnsupportedCommandException.hpp> +#include <unotools/tempfile.hxx> + +#include <dp_descriptioninfoset.hxx> +#include "dp_commandenvironments.hxx" +#include "dp_properties.hxx" + +#include <vector> +#include <algorithm> + +using namespace ::dp_misc; +using namespace ::com::sun::star; +using namespace ::com::sun::star::uno; +using namespace ::com::sun::star::ucb; +using namespace ::com::sun::star::logging; + + +namespace dp_manager { + +namespace { + +struct MatchTempDir +{ + OUString m_str; + explicit MatchTempDir( OUString str ) : m_str(std::move( str )) {} + bool operator () ( ActivePackages::Entries::value_type const & v ) const { + return v.second.temporaryName.equalsIgnoreAsciiCase( m_str ); + } +}; + +OUString getExtensionFolder(OUString const & parentFolder, + Reference<ucb::XCommandEnvironment> const & xCmdEnv, + Reference<uno::XComponentContext> const & xContext) +{ + ::ucbhelper::Content tempFolder( parentFolder, xCmdEnv, xContext ); + Reference<sdbc::XResultSet> xResultSet( + StrTitle::createCursor (tempFolder, ::ucbhelper::INCLUDE_FOLDERS_ONLY ) ); + + OUString title; + if (xResultSet->next()) + { + title = Reference<sdbc::XRow>( + xResultSet, UNO_QUERY_THROW )->getString(1 /* Title */ ) ; + } + return title; +} +} + +void PackageManagerImpl::initActivationLayer( + Reference<XCommandEnvironment> const & xCmdEnv ) +{ + if (m_activePackages.isEmpty()) + { + OSL_ASSERT( m_registryCache.isEmpty() ); + // documents temp activation: + m_activePackagesDB.reset( new ActivePackages ); + ::ucbhelper::Content ucbContent; + if (create_ucb_content( &ucbContent, m_context, xCmdEnv, + false /* no throw */ )) + { + // scan for all entries in m_packagesDir: + Reference<sdbc::XResultSet> xResultSet( + StrTitle::createCursor (ucbContent, ::ucbhelper::INCLUDE_FOLDERS_AND_DOCUMENTS ) ); + + while (xResultSet->next()) + { + Reference<sdbc::XRow> xRow( xResultSet, UNO_QUERY_THROW ); + OUString title( xRow->getString( 1 /* Title */ ) ); + // xxx todo: remove workaround for tdoc + if ( title == "this_is_a_dummy_stream_just_there_as_a_workaround_for_a_temporary_limitation_of_the_storage_api_implementation" ) + continue; + if ( title == "META-INF" ) + continue; + + ::ucbhelper::Content sourceContent( + Reference<XContentAccess>( + xResultSet, UNO_QUERY_THROW )->queryContent(), + xCmdEnv, m_xComponentContext ); + + OUString mediaType( detectMediaType( sourceContent, + false /* no throw */) ); + if (!mediaType.isEmpty()) + { + ActivePackages::Data dbData; + insertToActivationLayer( + Sequence<css::beans::NamedValue>(),mediaType, sourceContent, + title, &dbData ); + + insertToActivationLayerDB( title, dbData ); + //TODO #i73136#: insertToActivationLayerDB needs id not + // title, but the whole m_activePackages.getLength()==0 + // case (i.e., document-relative deployment) currently + // does not work, anyway. + } + } + } + } + else + { + // user|share: + OSL_ASSERT( !m_activePackages.isEmpty() ); + m_activePackages_expanded = expandUnoRcUrl( m_activePackages ); + m_registrationData_expanded = expandUnoRcUrl(m_registrationData); + if (!m_readOnly) + create_folder( nullptr, m_activePackages_expanded, xCmdEnv); + + OUString dbName; + if (m_context == "user") + dbName = m_activePackages_expanded + ".pmap"; + else + { + // Create the extension data base in the user installation + create_folder( nullptr, m_registrationData_expanded, xCmdEnv); + dbName = m_registrationData_expanded + "/extensions.pmap"; + } + // The data base can always be written because it is always in the user installation + m_activePackagesDB.reset( new ActivePackages( dbName ) ); + + if (! m_readOnly && m_context != "bundled") + { + // clean up activation layer, scan for zombie temp dirs: + ActivePackages::Entries id2temp( m_activePackagesDB->getEntries() ); + + ::ucbhelper::Content tempFolder( m_activePackages_expanded, xCmdEnv, m_xComponentContext ); + Reference<sdbc::XResultSet> xResultSet( + StrTitle::createCursor (tempFolder, + ::ucbhelper::INCLUDE_DOCUMENTS_ONLY ) ); + + // get all temp directories: + std::vector<OUString> tempEntries; + std::vector<OUString> removedEntries; + while (xResultSet->next()) + { + OUString title( + Reference<sdbc::XRow>( + xResultSet, UNO_QUERY_THROW )->getString( + 1 /* Title */ ) ); + if (title.endsWith("removed", &title)) + { + //save the file name without the "removed" part + removedEntries.push_back(::rtl::Uri::encode( + title, rtl_UriCharClassPchar, + rtl_UriEncodeIgnoreEscapes, + RTL_TEXTENCODING_UTF8 ) ); + } + else + { + tempEntries.push_back( ::rtl::Uri::encode( + title, rtl_UriCharClassPchar, + rtl_UriEncodeIgnoreEscapes, + RTL_TEXTENCODING_UTF8 ) ); + } + } + + bool bShared = (m_context == "shared"); + for (const OUString & tempEntry : tempEntries) + { + const MatchTempDir match( tempEntry ); + if (std::none_of( id2temp.begin(), id2temp.end(), match )) + { + const OUString url( + makeURL(m_activePackages_expanded, tempEntry ) ); + + //In case of shared extensions, new entries are regarded as + //added extensions if there is no xxx.tmpremoved file. + if (bShared) + { + if (std::find(removedEntries.begin(), removedEntries.end(), tempEntry) == + removedEntries.end()) + { + continue; + } + else + { + //Make sure only the same user removes the extension, who + //previously unregistered it. This is avoid races if multiple instances + //of OOo are running which all have write access to the shared installation. + //For example, a user removes the extension, but keeps OOo + //running. Parts of the extension may still be loaded and used by OOo. + //Therefore the extension is only deleted the next time the extension manager is + //run after restarting OOo. While OOo is still running, another user starts OOo + //which would deleted the extension files. If the same user starts another + //instance of OOo then the lock file will prevent this. + OUString aUserName; + ::osl::Security aSecurity; + aSecurity.getUserName( aUserName ); + ucbhelper::Content remFileContent( + url + "removed", Reference<XCommandEnvironment>(), m_xComponentContext); + std::vector<sal_Int8> data = dp_misc::readFile(remFileContent); + OString osData(reinterpret_cast<const char*>(data.data()), + data.size()); + OUString sData = OStringToOUString( + osData, RTL_TEXTENCODING_UTF8); + if (sData != aUserName) + continue; + } + } + // temp entry not needed anymore: + erase_path( url + "_", + Reference<XCommandEnvironment>(), + false /* no throw: ignore errors */ ); + erase_path( url, Reference<XCommandEnvironment>(), + false /* no throw: ignore errors */ ); + //delete the xxx.tmpremoved file + erase_path(url + "removed", + Reference<XCommandEnvironment>(), false); + } + } + } + } +} + + +void PackageManagerImpl::initRegistryBackends() +{ + if (!m_registryCache.isEmpty()) + create_folder( nullptr, m_registryCache, + Reference<XCommandEnvironment>(), false); + m_xRegistry.set( ::dp_registry::create( + m_context, m_registryCache, + m_xComponentContext ) ); +} + +namespace { + +osl::FileBase::RC createDirectory(OUString const & url) { + auto e = osl::Directory::create(url); + if (e != osl::FileBase::E_NOENT) { + return e; + } + INetURLObject o(url); + if (!o.removeSegment()) { + return osl::FileBase::E_INVAL; // anything but E_None/E_EXIST + } + e = createDirectory(o.GetMainURL(INetURLObject::DecodeMechanism::NONE)); + if (e != osl::FileBase::E_None && e != osl::FileBase::E_EXIST) { + return e; + } + return osl::Directory::create(url); +} + +bool isMacroURLReadOnly( const OUString &rMacro ) +{ + OUString aDirURL( rMacro ); + ::rtl::Bootstrap::expandMacros( aDirURL ); + + ::osl::FileBase::RC aErr = createDirectory( aDirURL ); + if ( aErr == ::osl::FileBase::E_None ) + return false; // it will be writeable + if ( aErr != ::osl::FileBase::E_EXIST ) + return true; // some serious problem creating it + + bool bError; + sal_uInt64 nWritten = 0; + OUString aFileURL( aDirURL + "/stamp.sys" ); + ::osl::File aFile( aFileURL ); + + bError = aFile.open( osl_File_OpenFlag_Read | + osl_File_OpenFlag_Write | + osl_File_OpenFlag_Create ) != ::osl::FileBase::E_None; + if (!bError) + bError = aFile.write( "1", 1, nWritten ) != ::osl::FileBase::E_None; + if (aFile.close() != ::osl::FileBase::E_None) + bError = true; + if (osl::File::remove( aFileURL ) != ::osl::FileBase::E_None) + bError = true; + + SAL_INFO( + "desktop.deployment", + "local url '" << rMacro << "' -> '" << aFileURL << "' " + << (bError ? "is" : "is not") << " readonly\n"); + return bError; +} + +} + +Reference<deployment::XPackageManager> PackageManagerImpl::create( + Reference<XComponentContext> const & xComponentContext, + OUString const & context ) +{ + rtl::Reference<PackageManagerImpl> that = new PackageManagerImpl( + xComponentContext, context ); + + OUString logFile, stamp; + if ( context == "user" ) { + that->m_activePackages = "vnd.sun.star.expand:$UNO_USER_PACKAGES_CACHE/uno_packages"; + that->m_registrationData = "vnd.sun.star.expand:$UNO_USER_PACKAGES_CACHE"; + that->m_registryCache = "vnd.sun.star.expand:$UNO_USER_PACKAGES_CACHE/registry"; + logFile = "$UNO_USER_PACKAGES_CACHE/log.txt"; + //We use the extension .sys for the file because on Windows Vista a sys + //(as well as exe and dll) file + //will not be written in the VirtualStore. For example if the process has no + //admin right once cannot write to the %programfiles% folder. However, when + //virtualization is used, the file will be written into the VirtualStore and + //it appears as if one could write to %programfiles%. When we test for write + //access to the office/shared folder for shared extensions then this typically + //fails because a normal user typically cannot write to this folder. However, + //using virtualization it appears that he/she can. Then a shared extension can + //be installed but is only visible for the user (because the extension is in + //the virtual store). + stamp = "$UNO_USER_PACKAGES_CACHE"; + } + else if ( context == "shared" ) { + that->m_activePackages = "vnd.sun.star.expand:$UNO_SHARED_PACKAGES_CACHE/uno_packages"; + that->m_registrationData = "vnd.sun.star.expand:$SHARED_EXTENSIONS_USER"; + that->m_registryCache = "vnd.sun.star.expand:$SHARED_EXTENSIONS_USER/registry"; + logFile = "$SHARED_EXTENSIONS_USER/log.txt"; +#if !HAVE_FEATURE_READONLY_INSTALLSET + // The "shared" extensions are read-only when we have a + // read-only installset. + stamp = "$UNO_SHARED_PACKAGES_CACHE"; +#endif + } + else if ( context == "bundled" ) { + that->m_activePackages = "vnd.sun.star.expand:$BUNDLED_EXTENSIONS"; + that->m_registrationData = "vnd.sun.star.expand:$BUNDLED_EXTENSIONS_USER"; + that->m_registryCache = "vnd.sun.star.expand:$BUNDLED_EXTENSIONS_USER/registry"; + logFile = "$BUNDLED_EXTENSIONS_USER/log.txt"; + //No stamp file. We assume that bundled is always readonly. It must not be + //modified from ExtensionManager but only by the installer + } + else if ( context == "tmp" ) { + that->m_activePackages = "vnd.sun.star.expand:$TMP_EXTENSIONS/extensions"; + that->m_registrationData = "vnd.sun.star.expand:$TMP_EXTENSIONS"; + that->m_registryCache = "vnd.sun.star.expand:$TMP_EXTENSIONS/registry"; + stamp = "$TMP_EXTENSIONS"; + } + else if (context == "bak") { + that->m_activePackages = "vnd.sun.star.expand:$BAK_EXTENSIONS/extensions"; + that->m_registrationData = "vnd.sun.star.expand:$BAK_EXTENSIONS"; + that->m_registryCache = "vnd.sun.star.expand:$BAK_EXTENSIONS/registry"; + stamp = "$BAK_EXTENSIONS"; + } + + else if (! context.match("vnd.sun.star.tdoc:/")) { + throw lang::IllegalArgumentException( + "invalid context given: " + context, + Reference<XInterface>(), static_cast<sal_Int16>(-1) ); + } + + Reference<XCommandEnvironment> xCmdEnv; + + try { + // There is no stamp for the bundled folder: + if (!stamp.isEmpty()) + that->m_readOnly = isMacroURLReadOnly( stamp ); + + if (!that->m_readOnly && !logFile.isEmpty()) + { + // Initialize logger which will be used in ProgressLogImpl (created below) + rtl::Bootstrap::expandMacros(logFile); + comphelper::EventLogger logger(xComponentContext, "unopkg"); + const Reference<XLogger> xLogger(logger.getLogger()); + Reference<XLogFormatter> xLogFormatter(SimpleTextFormatter::create(xComponentContext)); + Sequence < beans::NamedValue > aSeq2 { { "Formatter", Any(xLogFormatter) }, {"FileURL", Any(logFile)} }; + Reference<XLogHandler> xFileHandler(css::logging::FileHandler::createWithSettings(xComponentContext, aSeq2)); + xFileHandler->setLevel(LogLevel::WARNING); + xLogger->addLogHandler(xFileHandler); + + that->m_xLogFile.set( + that->m_xComponentContext->getServiceManager() + ->createInstanceWithArgumentsAndContext( + "com.sun.star.comp.deployment.ProgressLog", + Sequence<Any>(), + that->m_xComponentContext ), + UNO_QUERY_THROW ); + xCmdEnv.set( new CmdEnvWrapperImpl( xCmdEnv, that->m_xLogFile ) ); + } + + that->initRegistryBackends(); + that->initActivationLayer( xCmdEnv ); + + return that; + + } + catch (const RuntimeException &) { + throw; + } + catch (const Exception & e) { + Any exc( ::cppu::getCaughtException() ); + throw lang::WrappedTargetRuntimeException( + ("[context=\"" + context + "\"] caught unexpected " + + exc.getValueType().getTypeName() + ": " + e.Message), + Reference<XInterface>(), exc ); + } +} + + +PackageManagerImpl::~PackageManagerImpl() +{ +} + + +void PackageManagerImpl::fireModified() +{ + ::cppu::OInterfaceContainerHelper * pContainer = rBHelper.getContainer( + cppu::UnoType<util::XModifyListener>::get() ); + if (pContainer != nullptr) { + pContainer->forEach<util::XModifyListener>( + [this] (uno::Reference<util::XModifyListener> const& xListener) + { return xListener->modified(lang::EventObject(static_cast<OWeakObject *>(this))); }); + } +} + + +void PackageManagerImpl::disposing() +{ + try { +// // xxx todo: guarding? +// ::osl::MutexGuard guard( getMutex() ); + try_dispose( m_xLogFile ); + m_xLogFile.clear(); + try_dispose( m_xRegistry ); + m_xRegistry.clear(); + m_activePackagesDB.reset(); + m_xComponentContext.clear(); + + t_pm_helper::disposing(); + + } + catch (const RuntimeException &) { + throw; + } + catch (const Exception &) { + Any exc( ::cppu::getCaughtException() ); + throw lang::WrappedTargetRuntimeException( + "caught unexpected exception while disposing...", + static_cast<OWeakObject *>(this), exc ); + } +} + +// XComponent + +void PackageManagerImpl::dispose() +{ + //Do not call check here. We must not throw an exception here if the object + //is being disposed or is already disposed. See com.sun.star.lang.XComponent + WeakComponentImplHelperBase::dispose(); +} + + +void PackageManagerImpl::addEventListener( + Reference<lang::XEventListener> const & xListener ) +{ + //Do not call check here. We must not throw an exception here if the object + //is being disposed or is already disposed. See com.sun.star.lang.XComponent + WeakComponentImplHelperBase::addEventListener( xListener ); +} + + +void PackageManagerImpl::removeEventListener( + Reference<lang::XEventListener> const & xListener ) +{ + //Do not call check here. We must not throw an exception here if the object + //is being disposed or is already disposed. See com.sun.star.lang.XComponent + WeakComponentImplHelperBase::removeEventListener( xListener ); +} + +// XPackageManager + +OUString PackageManagerImpl::getContext() +{ + check(); + return m_context; +} + + +Sequence< Reference<deployment::XPackageTypeInfo> > +PackageManagerImpl::getSupportedPackageTypes() +{ + OSL_ASSERT( m_xRegistry.is() ); + return m_xRegistry->getSupportedPackageTypes(); +} + + +Reference<task::XAbortChannel> PackageManagerImpl::createAbortChannel() +{ + check(); + return new AbortChannel; +} + +// XModifyBroadcaster + +void PackageManagerImpl::addModifyListener( + Reference<util::XModifyListener> const & xListener ) +{ + check(); + rBHelper.addListener( cppu::UnoType<decltype(xListener)>::get(), xListener ); +} + + +void PackageManagerImpl::removeModifyListener( + Reference<util::XModifyListener> const & xListener ) +{ + check(); + rBHelper.removeListener( cppu::UnoType<decltype(xListener)>::get(), xListener ); +} + + +OUString PackageManagerImpl::detectMediaType( + ::ucbhelper::Content const & ucbContent_, bool throw_exc ) +{ + ::ucbhelper::Content ucbContent(ucbContent_); + OUString url( ucbContent.getURL() ); + OUString mediaType; + if (url.match( "vnd.sun.star.tdoc:" ) || url.match( "vnd.sun.star.pkg:" )) + { + try { + ucbContent.getPropertyValue( "MediaType" ) >>= mediaType; + } + catch (const beans::UnknownPropertyException &) { + } + OSL_ENSURE( !mediaType.isEmpty(), "### no media-type?!" ); + } + if (mediaType.isEmpty()) + { + try { + Reference<deployment::XPackage> xPackage( + m_xRegistry->bindPackage( + url, OUString(), false, OUString(), ucbContent.getCommandEnvironment() ) ); + const Reference<deployment::XPackageTypeInfo> xPackageType( + xPackage->getPackageType() ); + OSL_ASSERT( xPackageType.is() ); + if (xPackageType.is()) + mediaType = xPackageType->getMediaType(); + } + catch (const lang::IllegalArgumentException &) { + if (throw_exc) + throw; + css::uno::Any ex( cppu::getCaughtException() ); + SAL_WARN( "desktop", exceptionToString(ex) ); + } + } + return mediaType; +} + + +OUString PackageManagerImpl::insertToActivationLayer( + Sequence<beans::NamedValue> const & properties, + OUString const & mediaType, ::ucbhelper::Content const & sourceContent_, + OUString const & title, ActivePackages::Data * dbData ) +{ + ::ucbhelper::Content sourceContent(sourceContent_); + Reference<XCommandEnvironment> xCmdEnv( + sourceContent.getCommandEnvironment() ); + + OUString baseDir(m_activePackages_expanded); + ::utl::TempFile aTemp(&baseDir, false); + OUString tempEntry = aTemp.GetURL(); + tempEntry = tempEntry.copy(tempEntry.lastIndexOf('/') + 1); + OUString destFolder = makeURL( m_activePackages, tempEntry) + "_"; + + // prepare activation folder: + ::ucbhelper::Content destFolderContent; + create_folder( &destFolderContent, destFolder, xCmdEnv ); + + // copy content into activation temp dir: + if (mediaType.matchIgnoreAsciiCase("application/vnd.sun.star.package-bundle") || + // xxx todo: more sophisticated parsing + mediaType.matchIgnoreAsciiCase("application/vnd.sun.star.legacy-package-bundle")) + { + // inflate content: + OUStringBuffer buf; + if (!sourceContent.isFolder()) + { + buf.append( "vnd.sun.star.zip://" ); + buf.append( ::rtl::Uri::encode( sourceContent.getURL(), + rtl_UriCharClassRegName, + rtl_UriEncodeIgnoreEscapes, + RTL_TEXTENCODING_UTF8 ) ); + } + else + { + //Folder. No need to unzip, just copy + buf.append(sourceContent.getURL()); + } + buf.append( '/' ); + sourceContent = ::ucbhelper::Content( + buf.makeStringAndClear(), xCmdEnv, m_xComponentContext ); + } + destFolderContent.transferContent( + sourceContent, ::ucbhelper::InsertOperation::Copy, + title, NameClash::OVERWRITE ); + + + // write to DB: + //bundled extensions should only be added by the synchronizeAddedExtensions + //functions. Moreover, there is no "temporary folder" for bundled extensions. + OSL_ASSERT(!(m_context == "bundled")); + OUString sFolderUrl = makeURLAppendSysPathSegment(destFolderContent.getURL(), title); + DescriptionInfoset info = + dp_misc::getDescriptionInfoset(sFolderUrl); + dbData->temporaryName = tempEntry; + dbData->fileName = title; + dbData->mediaType = mediaType; + dbData->version = info.getVersion(); + + //No write the properties file next to the extension + ExtensionProperties props(sFolderUrl, properties, xCmdEnv, m_xComponentContext); + props.write(); + return destFolder; +} + + +void PackageManagerImpl::insertToActivationLayerDB( + OUString const & id, ActivePackages::Data const & dbData ) +{ + //access to the database must be guarded. See removePackage + const ::osl::MutexGuard guard( m_aMutex ); + m_activePackagesDB->put( id, dbData ); +} + + +/* The function returns true if there is an extension with the same id already + installed which needs to be uninstalled, before the new extension can be installed. +*/ +bool PackageManagerImpl::isInstalled( + Reference<deployment::XPackage> const & package) +{ + OUString id(dp_misc::getIdentifier(package)); + OUString fn(package->getName()); + bool bInstalled = false; + if (m_activePackagesDB->has( id, fn )) + { + bInstalled = true; + } + return bInstalled; +} + +// XPackageManager + +Reference<deployment::XPackage> PackageManagerImpl::importExtension( + Reference<deployment::XPackage> const & extension, + Reference<task::XAbortChannel> const & xAbortChannel, + Reference<XCommandEnvironment> const & xCmdEnv_ ) +{ + return addPackage(extension->getURL(), Sequence<beans::NamedValue>(), + OUString(), xAbortChannel, xCmdEnv_); +} + +/* The function adds an extension but does not register it!!! + It may not do any user interaction. This is done in XExtensionManager::addExtension +*/ +Reference<deployment::XPackage> PackageManagerImpl::addPackage( + OUString const & url, + css::uno::Sequence<css::beans::NamedValue> const & properties, + OUString const & mediaType_, + Reference<task::XAbortChannel> const & xAbortChannel, + Reference<XCommandEnvironment> const & xCmdEnv_ ) +{ + check(); + if (m_readOnly) + { + OUString message; + if (m_context == "shared") + message = "You need write permissions to install a shared extension!"; + else + message = "You need write permissions to install this extension!"; + throw deployment::DeploymentException( + message, static_cast<OWeakObject *>(this), Any() ); + } + Reference<XCommandEnvironment> xCmdEnv; + if (m_xLogFile.is()) + xCmdEnv.set( new CmdEnvWrapperImpl( xCmdEnv_, m_xLogFile ) ); + else + xCmdEnv.set( xCmdEnv_ ); + + try { + ::ucbhelper::Content sourceContent; + (void)create_ucb_content( &sourceContent, url, xCmdEnv ); // throws exc + const OUString title( StrTitle::getTitle( sourceContent ) ); + const OUString title_enc( ::rtl::Uri::encode( + title, rtl_UriCharClassPchar, + rtl_UriEncodeIgnoreEscapes, + RTL_TEXTENCODING_UTF8 ) ); + OUString destFolder; + + OUString mediaType(mediaType_); + if (mediaType.isEmpty()) + mediaType = detectMediaType( sourceContent ); + + Reference<deployment::XPackage> xPackage; + // copy file: + progressUpdate( + DpResId(RID_STR_COPYING_PACKAGE) + title, xCmdEnv ); + if (m_activePackages.isEmpty()) + { + ::ucbhelper::Content docFolderContent; + create_folder( &docFolderContent, m_context, xCmdEnv ); + // copy into document, first: + docFolderContent.transferContent( + sourceContent, ::ucbhelper::InsertOperation::Copy, + OUString(), + NameClash::ASK /* xxx todo: ASK not needed? */); + // set media-type: + ::ucbhelper::Content docContent( + makeURL( m_context, title_enc ), xCmdEnv, m_xComponentContext ); + //TODO #i73136#: using title instead of id can lead to + // clashes, but the whole m_activePackages.getLength()==0 + // case (i.e., document-relative deployment) currently does + // not work, anyway. + docContent.setPropertyValue("MediaType", Any(mediaType) ); + + // xxx todo: obsolete in the future + try { + docFolderContent.executeCommand( "flush", Any() ); + } + catch (const UnsupportedCommandException &) { + } + } + ActivePackages::Data dbData; + destFolder = insertToActivationLayer( + properties, mediaType, sourceContent, title, &dbData ); + + + // bind activation package: + //Because every shared/user extension will be unpacked in a folder, + //which was created with a unique name we will always have two different + //XPackage objects, even if the second extension is the same. + //Therefore bindPackage does not need a guard here. + xPackage = m_xRegistry->bindPackage( + makeURL( destFolder, title_enc ), mediaType, false, OUString(), xCmdEnv ); + + OSL_ASSERT( xPackage.is() ); + if (xPackage.is()) + { + bool install = false; + try + { + OUString const id = dp_misc::getIdentifier( xPackage ); + + ::osl::MutexGuard g(m_addMutex); + if (isInstalled(xPackage)) + { + //Do not guard the complete function with the getMutex + removePackage(id, xPackage->getName(), xAbortChannel, + xCmdEnv); + } + install = true; + insertToActivationLayerDB(id, dbData); + } + catch (...) + { + deletePackageFromCache( xPackage, destFolder ); + throw; + } + if (!install) + { + deletePackageFromCache( xPackage, destFolder ); + } + //ToDo: We should notify only if the extension is registered + fireModified(); + } + return xPackage; + } + catch (const RuntimeException &) { + throw; + } + catch (const CommandFailedException & exc) { + logIntern( Any(exc) ); + throw; + } + catch (const CommandAbortedException & exc) { + logIntern( Any(exc) ); + throw; + } + catch (const deployment::DeploymentException & exc) { + logIntern( Any(exc) ); + throw; + } + catch (const Exception &) { + Any exc( ::cppu::getCaughtException() ); + logIntern( exc ); + throw deployment::DeploymentException( + DpResId(RID_STR_ERROR_WHILE_ADDING) + url, + static_cast<OWeakObject *>(this), exc ); + } +} +void PackageManagerImpl::deletePackageFromCache( + Reference<deployment::XPackage> const & xPackage, + OUString const & destFolder) +{ + try_dispose( xPackage ); + + //we remove the package from the uno cache + //no service from the package may be loaded at this time!!! + erase_path( destFolder, Reference<XCommandEnvironment>(), + false /* no throw: ignore errors */ ); + //rm last character '_' + OUString url = destFolder.copy(0, destFolder.getLength() - 1); + erase_path( url, Reference<XCommandEnvironment>(), + false /* no throw: ignore errors */ ); + +} + +void PackageManagerImpl::removePackage( + OUString const & id, OUString const & fileName, + Reference<task::XAbortChannel> const & /*xAbortChannel*/, + Reference<XCommandEnvironment> const & xCmdEnv_ ) +{ + check(); + + Reference<XCommandEnvironment> xCmdEnv; + if (m_xLogFile.is()) + xCmdEnv.set( new CmdEnvWrapperImpl( xCmdEnv_, m_xLogFile ) ); + else + xCmdEnv.set( xCmdEnv_ ); + + try { + Reference<deployment::XPackage> xPackage; + { + const ::osl::MutexGuard guard(m_aMutex); + //Check if this extension exist and throw an IllegalArgumentException + //if it does not + //If the files of the extension are already removed, or there is a + //different extension at the same place, for example after updating the + //extension, then the returned object is that which uses the database data. + xPackage = getDeployedPackage_(id, fileName, xCmdEnv ); + + + //Because the extension is only removed the next time the extension + //manager runs after restarting OOo, we need to indicate that a + //shared extension was "deleted". When a user starts OOo, then it + //will check if something changed in the shared repository. Based on + //the flag file it will then recognize, that the extension was + //deleted and can then update the extension database of the shared + //extensions in the user installation. + if ( xPackage.is() && !m_readOnly && !xPackage->isRemoved() && (m_context == "shared")) + { + ActivePackages::Data val; + m_activePackagesDB->get( & val, id, fileName); + OSL_ASSERT(!val.temporaryName.isEmpty()); + OUString url(makeURL(m_activePackages_expanded, + val.temporaryName + "removed")); + ::ucbhelper::Content contentRemoved(url, xCmdEnv, m_xComponentContext); + OUString aUserName; + ::osl::Security aSecurity; + aSecurity.getUserName( aUserName ); + + OString stamp = OUStringToOString(aUserName, RTL_TEXTENCODING_UTF8); + Reference<css::io::XInputStream> xData( + ::xmlscript::createInputStream( + reinterpret_cast<sal_Int8 const *>(stamp.getStr()), + stamp.getLength() ) ); + contentRemoved.writeStream( xData, true /* replace existing */ ); + } + m_activePackagesDB->erase( id, fileName ); // to be removed upon next start + //remove any cached data hold by the backend + m_xRegistry->packageRemoved(xPackage->getURL(), xPackage->getPackageType()->getMediaType()); + } + try_dispose( xPackage ); + + fireModified(); + } + catch (const RuntimeException &) { + throw; + } + catch (const CommandFailedException & exc) { + logIntern( Any(exc) ); + throw; + } + catch (const CommandAbortedException & exc) { + logIntern( Any(exc) ); + throw; + } + catch (const deployment::DeploymentException & exc) { + logIntern( Any(exc) ); + throw; + } + catch (const Exception &) { + Any exc( ::cppu::getCaughtException() ); + logIntern( exc ); + throw deployment::DeploymentException( + DpResId(RID_STR_ERROR_WHILE_REMOVING) + id, + static_cast<OWeakObject *>(this), exc ); + } +} + + +OUString PackageManagerImpl::getDeployPath( ActivePackages::Data const & data ) +{ + OUStringBuffer buf; + buf.append( data.temporaryName ); + //The bundled extensions are not contained in an additional folder + //with a unique name. data.temporaryName contains already the + //UTF8 encoded folder name. See PackageManagerImpl::synchronize + if (m_context != "bundled") + { + buf.append( "_/" ); + buf.append( ::rtl::Uri::encode( data.fileName, rtl_UriCharClassPchar, + rtl_UriEncodeIgnoreEscapes, + RTL_TEXTENCODING_UTF8 ) ); + } + return makeURL( m_activePackages, buf.makeStringAndClear() ); +} + + +Reference<deployment::XPackage> PackageManagerImpl::getDeployedPackage_( + OUString const & id, OUString const & fileName, + Reference<XCommandEnvironment> const & xCmdEnv ) +{ + ActivePackages::Data val; + if (m_activePackagesDB->get( &val, id, fileName )) + { + return getDeployedPackage_( id, val, xCmdEnv ); + } + throw lang::IllegalArgumentException( + DpResId(RID_STR_NO_SUCH_PACKAGE) + id, + static_cast<OWeakObject *>(this), static_cast<sal_Int16>(-1) ); +} + + +Reference<deployment::XPackage> PackageManagerImpl::getDeployedPackage_( + std::u16string_view id, ActivePackages::Data const & data, + Reference<XCommandEnvironment> const & xCmdEnv, bool ignoreAlienPlatforms ) +{ + if (ignoreAlienPlatforms) + { + OUString type, subType; + INetContentTypeParameterList params; + if (INetContentTypes::parse( data.mediaType, type, subType, ¶ms )) + { + auto const iter = params.find(OString("platform")); + if (iter != params.end() && !platform_fits(iter->second.m_sValue)) + throw lang::IllegalArgumentException( + DpResId(RID_STR_NO_SUCH_PACKAGE) + id, + static_cast<OWeakObject *>(this), + static_cast<sal_Int16>(-1) ); + } + } + Reference<deployment::XPackage> xExtension; + try + { + //Ignore extensions where XPackage::checkPrerequisites failed. + //They must not be usable for this user. + if (data.failedPrerequisites == "0") + { + xExtension = m_xRegistry->bindPackage( + getDeployPath( data ), data.mediaType, false, OUString(), xCmdEnv ); + } + } + catch (const deployment::InvalidRemovedParameterException& e) + { + xExtension = e.Extension; + } + return xExtension; +} + + +Sequence< Reference<deployment::XPackage> > +PackageManagerImpl::getDeployedPackages_( + Reference<XCommandEnvironment> const & xCmdEnv ) +{ + std::vector< Reference<deployment::XPackage> > packages; + ActivePackages::Entries id2temp( m_activePackagesDB->getEntries() ); + for (auto const& elem : id2temp) + { + if (elem.second.failedPrerequisites != "0") + continue; + try { + packages.push_back( + getDeployedPackage_( + elem.first, elem.second, xCmdEnv, + true /* xxx todo: think of GUI: + ignore other platforms than the current one */ ) ); + } + catch (const lang::IllegalArgumentException &) { + // ignore + TOOLS_WARN_EXCEPTION( "desktop", "" ); + } + catch (const deployment::DeploymentException&) { + // ignore + TOOLS_WARN_EXCEPTION( "desktop", "" ); + } + } + return comphelper::containerToSequence(packages); +} + + +Reference<deployment::XPackage> PackageManagerImpl::getDeployedPackage( + OUString const & id, OUString const & fileName, + Reference<XCommandEnvironment> const & xCmdEnv_ ) +{ + check(); + Reference<XCommandEnvironment> xCmdEnv; + if (m_xLogFile.is()) + xCmdEnv.set( new CmdEnvWrapperImpl( xCmdEnv_, m_xLogFile ) ); + else + xCmdEnv.set( xCmdEnv_ ); + + try { + const ::osl::MutexGuard guard( m_aMutex ); + return getDeployedPackage_( id, fileName, xCmdEnv ); + } + catch (const RuntimeException &) { + throw; + } + catch (const CommandFailedException & exc) { + logIntern( Any(exc) ); + throw; + } + catch (const deployment::DeploymentException & exc) { + logIntern( Any(exc) ); + throw; + } + catch (const Exception &) { + Any exc( ::cppu::getCaughtException() ); + logIntern( exc ); + throw deployment::DeploymentException( + // ought never occur... + "error while accessing deployed package: " + id, + static_cast<OWeakObject *>(this), exc ); + } +} + + +Sequence< Reference<deployment::XPackage> > +PackageManagerImpl::getDeployedPackages( + Reference<task::XAbortChannel> const &, + Reference<XCommandEnvironment> const & xCmdEnv_ ) +{ + check(); + Reference<XCommandEnvironment> xCmdEnv; + if (m_xLogFile.is()) + xCmdEnv.set( new CmdEnvWrapperImpl( xCmdEnv_, m_xLogFile ) ); + else + xCmdEnv.set( xCmdEnv_ ); + + try { + const ::osl::MutexGuard guard( m_aMutex ); + return getDeployedPackages_( xCmdEnv ); + } + catch (const RuntimeException &) { + throw; + } + catch (const CommandFailedException & exc) { + logIntern( Any(exc) ); + throw; + } + catch (const CommandAbortedException & exc) { + logIntern( Any(exc) ); + throw; + } + catch (const deployment::DeploymentException & exc) { + logIntern( Any(exc) ); + throw; + } + catch (const Exception &) { + Any exc( ::cppu::getCaughtException() ); + logIntern( exc ); + throw deployment::DeploymentException( + // ought never occur... + "error while getting all deployed packages: " + m_context, + static_cast<OWeakObject *>(this), exc ); + } +} + + +//ToDo: the function must not call registerPackage, do this in +//XExtensionManager.reinstallDeployedExtensions +void PackageManagerImpl::reinstallDeployedPackages( + sal_Bool force, Reference<task::XAbortChannel> const & /*xAbortChannel*/, + Reference<XCommandEnvironment> const & xCmdEnv_ ) +{ + check(); + if (!force && office_is_running()) + throw RuntimeException( + "You must close any running Office process before reinstalling packages!", + static_cast<OWeakObject *>(this) ); + + Reference<XCommandEnvironment> xCmdEnv; + if (m_xLogFile.is()) + xCmdEnv.set( new CmdEnvWrapperImpl( xCmdEnv_, m_xLogFile ) ); + else + xCmdEnv.set( xCmdEnv_ ); + + try { + ProgressLevel progress( + xCmdEnv, "Reinstalling all deployed packages..." ); + + try_dispose( m_xRegistry ); + m_xRegistry.clear(); + if (!m_registryCache.isEmpty()) + erase_path( m_registryCache, xCmdEnv ); + initRegistryBackends(); + Reference<util::XUpdatable> xUpdatable( m_xRegistry, UNO_QUERY ); + if (xUpdatable.is()) + xUpdatable->update(); + + //registering is done by the ExtensionManager service. + } + catch (const RuntimeException &) { + throw; + } + catch (const CommandFailedException & exc) { + logIntern( Any(exc) ); + throw; + } + catch (const CommandAbortedException & exc) { + logIntern( Any(exc) ); + throw; + } + catch (const deployment::DeploymentException & exc) { + logIntern( Any(exc) ); + throw; + } + catch (const Exception &) { + Any exc( ::cppu::getCaughtException() ); + logIntern( exc ); + throw deployment::DeploymentException( + "Error while reinstalling all previously deployed packages of context " + m_context, + static_cast<OWeakObject *>(this), exc ); + } +} + + +sal_Bool SAL_CALL PackageManagerImpl::isReadOnly( ) +{ + return m_readOnly; +} +bool PackageManagerImpl::synchronizeRemovedExtensions( + Reference<task::XAbortChannel> const & xAbortChannel, + Reference<css::ucb::XCommandEnvironment> const & xCmdEnv) +{ + + //find all which are in the extension data base but which + //are removed already. + OSL_ASSERT(!(m_context == "user")); + bool bModified = false; + ActivePackages::Entries id2temp( m_activePackagesDB->getEntries() ); + + bool bShared = (m_context == "shared"); + + for (auto const& elem : id2temp) + { + try + { + //Get the URL to the extensions folder, first make the url for the + //shared repository including the temporary name + OUString url = makeURL(m_activePackages, elem.second.temporaryName); + if (bShared) + url = makeURLAppendSysPathSegment( url + "_", elem.second.fileName); + + bool bRemoved = false; + //Check if the URL to the extension is still the same + ::ucbhelper::Content contentExtension; + + if (!create_ucb_content( + &contentExtension, url, + Reference<XCommandEnvironment>(), false)) + { + bRemoved = true; + } + + //The folder is in the extension database, but it can still be deleted. + //look for the xxx.tmpremoved file + //There can also be the case that a different extension was installed + //in a "temp" folder with name that is already used. + if (!bRemoved && bShared) + { + ::ucbhelper::Content contentRemoved; + + if (create_ucb_content( + &contentRemoved, + m_activePackages_expanded + "/" + + elem.second.temporaryName + "removed", + Reference<XCommandEnvironment>(), false)) + { + bRemoved = true; + } + } + + if (!bRemoved) + { + //There may be another extensions at the same place + dp_misc::DescriptionInfoset infoset = + dp_misc::getDescriptionInfoset(url); + OSL_ENSURE(infoset.hasDescription() && infoset.getIdentifier(), + "Extension Manager: bundled and shared extensions " + "must have an identifier and a version"); + if (infoset.hasDescription() && + infoset.getIdentifier() && + ( elem.first != *(infoset.getIdentifier()) + || elem.second.version != infoset.getVersion())) + { + bRemoved = true; + } + + } + if (bRemoved) + { + Reference<deployment::XPackage> xPackage = m_xRegistry->bindPackage( + url, elem.second.mediaType, true, elem.first, xCmdEnv ); + OSL_ASSERT(xPackage.is()); //Even if the files are removed, we must get the object. + xPackage->revokePackage(true, xAbortChannel, xCmdEnv); + removePackage(xPackage->getIdentifier().Value, xPackage->getName(), + xAbortChannel, xCmdEnv); + bModified = true; + } + } + catch( const uno::Exception & ) + { + TOOLS_WARN_EXCEPTION("desktop.deployment", ""); + } + } + return bModified; +} + + +bool PackageManagerImpl::synchronizeAddedExtensions( + Reference<task::XAbortChannel> const & xAbortChannel, + Reference<css::ucb::XCommandEnvironment> const & xCmdEnv) +{ + bool bModified = false; + OSL_ASSERT(!(m_context == "user")); + + ActivePackages::Entries id2temp( m_activePackagesDB->getEntries() ); + //check if the folder exist at all. The shared extension folder + //may not exist for a normal user. + bool bOk=true; + try + { + bOk = create_ucb_content( + nullptr, m_activePackages_expanded, Reference<css::ucb::XCommandEnvironment>(), false); + } + catch (const css::ucb::ContentCreationException&) + { + bOk = false; + } + + if (!bOk) + return bModified; + + ::ucbhelper::Content tempFolder( m_activePackages_expanded, xCmdEnv, m_xComponentContext ); + Reference<sdbc::XResultSet> xResultSet( + StrTitle::createCursor( tempFolder, + ::ucbhelper::INCLUDE_FOLDERS_ONLY ) ); + + while (xResultSet->next()) + { + try + { + OUString title( + Reference<sdbc::XRow>( + xResultSet, UNO_QUERY_THROW )->getString( + 1 /* Title */ ) ); + //The temporary folders of user and shared have an '_' at then end. + //But the name in ActivePackages.temporaryName is saved without. + OUString title2 = title; + bool bShared = (m_context == "shared"); + if (bShared) + { + OSL_ASSERT(title2.endsWith("_")); + title2 = title2.copy(0, title2.getLength() -1); + } + OUString titleEncoded = ::rtl::Uri::encode( + title2, rtl_UriCharClassPchar, + rtl_UriEncodeIgnoreEscapes, + RTL_TEXTENCODING_UTF8); + + //It is sufficient to check for the folder name, because when the administrator + //installed the extension it was already checked if there is one with the + //same identifier. + const MatchTempDir match(titleEncoded); + if (std::none_of( id2temp.begin(), id2temp.end(), match )) + { + + // The folder was not found in the data base, so it must be + // an added extension + OUString url(m_activePackages_expanded + "/" + titleEncoded); + OUString sExtFolder; + if (bShared) //that is, shared + { + //Check if the extension was not "deleted" already which is indicated + //by a xxx.tmpremoved file + ::ucbhelper::Content contentRemoved; + if (create_ucb_content(&contentRemoved, url + "removed", + Reference<XCommandEnvironment>(), false)) + continue; + sExtFolder = getExtensionFolder( + m_activePackages_expanded + "/" + titleEncoded + "_", + xCmdEnv, m_xComponentContext); + url = makeURLAppendSysPathSegment(m_activePackages_expanded, title); + url = makeURLAppendSysPathSegment(url, sExtFolder); + } + Reference<deployment::XPackage> xPackage = m_xRegistry->bindPackage( + url, OUString(), false, OUString(), xCmdEnv ); + if (xPackage.is()) + { + OUString id = dp_misc::getIdentifier( xPackage ); + + //Prepare the database entry + ActivePackages::Data dbData; + + dbData.temporaryName = titleEncoded; + if (bShared) + dbData.fileName = sExtFolder; + else + dbData.fileName = title; + dbData.mediaType = xPackage->getPackageType()->getMediaType(); + dbData.version = xPackage->getVersion(); + SAL_WARN_IF( + dbData.version.isEmpty(), "desktop.deployment", + "bundled/shared extension " << id << " at <" << url + << "> has no explicit version"); + + //We provide a special command environment that will prevent + //showing a license if simple-license/@accept-by = "admin" + //It will also prevent showing the license for bundled extensions + //which is not supported. + OSL_ASSERT(!(m_context == "user")); + + // shall the license be suppressed? + DescriptionInfoset info = + dp_misc::getDescriptionInfoset(url); + ::std::optional<dp_misc::SimpleLicenseAttributes> + attr = info.getSimpleLicenseAttributes(); + ExtensionProperties props(url, xCmdEnv, m_xComponentContext); + bool bNoLicense = false; + if (attr && attr->suppressIfRequired && props.isSuppressedLicense()) + bNoLicense = true; + + Reference<ucb::XCommandEnvironment> licCmdEnv( + new LicenseCommandEnv(xCmdEnv->getInteractionHandler(), + bNoLicense, m_context)); + sal_Int32 failedPrereq = xPackage->checkPrerequisites( + xAbortChannel, licCmdEnv, false); + //Remember that this failed. For example, the user + //could have declined the license. Then the next time the + //extension folder is investigated we do not want to + //try to install the extension again. + dbData.failedPrerequisites = OUString::number(failedPrereq); + insertToActivationLayerDB(id, dbData); + bModified = true; + } + } + } + catch (const uno::Exception &) + { + // Looks like exceptions being caught here is not an uncommon case. + TOOLS_WARN_EXCEPTION("desktop.deployment", ""); + } + } + return bModified; +} + +sal_Bool PackageManagerImpl::synchronize( + Reference<task::XAbortChannel> const & xAbortChannel, + Reference<css::ucb::XCommandEnvironment> const & xCmdEnv) +{ + check(); + bool bModified = false; + if (m_context == "user") + return bModified; + bModified |= + synchronizeRemovedExtensions(xAbortChannel, xCmdEnv); + bModified |= synchronizeAddedExtensions(xAbortChannel, xCmdEnv); + + return bModified; +} + +Sequence< Reference<deployment::XPackage> > PackageManagerImpl::getExtensionsWithUnacceptedLicenses( + Reference<ucb::XCommandEnvironment> const & xCmdEnv) +{ + std::vector<Reference<deployment::XPackage> > vec; + + try + { + const ::osl::MutexGuard guard( m_aMutex ); + // clean up activation layer, scan for zombie temp dirs: + ActivePackages::Entries id2temp( m_activePackagesDB->getEntries() ); + + bool bShared = (m_context == "shared"); + + for (auto const& elem : id2temp) + { + //Get the database entry + ActivePackages::Data const & dbData = elem.second; + sal_Int32 failedPrereq = dbData.failedPrerequisites.toInt32(); + //If the installation failed for other reason then the license then we + //ignore it. + if (failedPrereq ^ deployment::Prerequisites::LICENSE) + continue; + + //Prepare the URL to the extension + OUString url = makeURL(m_activePackages, elem.second.temporaryName); + if (bShared) + url = makeURLAppendSysPathSegment( url + "_", elem.second.fileName); + + Reference<deployment::XPackage> p = m_xRegistry->bindPackage( + url, OUString(), false, OUString(), xCmdEnv ); + + if (p.is()) + vec.push_back(p); + + } + return ::comphelper::containerToSequence(vec); + } + catch (const deployment::DeploymentException &) + { + throw; + } + catch (const RuntimeException&) + { + throw; + } + catch (...) + { + Any exc = ::cppu::getCaughtException(); + deployment::DeploymentException de( + "PackageManagerImpl::getExtensionsWithUnacceptedLicenses", + static_cast<OWeakObject*>(this), exc); + exc <<= de; + ::cppu::throwException(exc); + } + + return ::comphelper::containerToSequence(vec); +} + +sal_Int32 PackageManagerImpl::checkPrerequisites( + css::uno::Reference<css::deployment::XPackage> const & extension, + css::uno::Reference<css::task::XAbortChannel> const & xAbortChannel, + css::uno::Reference<css::ucb::XCommandEnvironment> const & xCmdEnv ) +{ + try + { + if (!extension.is()) + return 0; + if (m_context != extension->getRepositoryName()) + throw lang::IllegalArgumentException( + "PackageManagerImpl::checkPrerequisites: extension is not from this repository.", + nullptr, 0); + + ActivePackages::Data dbData; + OUString id = dp_misc::getIdentifier(extension); + if (!m_activePackagesDB->get( &dbData, id, OUString())) + { + throw lang::IllegalArgumentException( + "PackageManagerImpl::checkPrerequisites: unknown extension", + nullptr, 0); + + } + //If the license was already displayed, then do not show it again + Reference<ucb::XCommandEnvironment> _xCmdEnv = xCmdEnv; + sal_Int32 prereq = dbData.failedPrerequisites.toInt32(); + if ( !(prereq & deployment::Prerequisites::LICENSE)) + _xCmdEnv = new NoLicenseCommandEnv(xCmdEnv->getInteractionHandler()); + + sal_Int32 failedPrereq = extension->checkPrerequisites( + xAbortChannel, _xCmdEnv, false); + dbData.failedPrerequisites = OUString::number(failedPrereq); + insertToActivationLayerDB(id, dbData); + return 0; + } + catch ( const deployment::DeploymentException& ) { + throw; + } catch ( const ucb::CommandFailedException & ) { + throw; + } catch ( const ucb::CommandAbortedException & ) { + throw; + } catch (const lang::IllegalArgumentException &) { + throw; + } catch (const uno::RuntimeException &) { + throw; + } catch (...) { + uno::Any excOccurred = ::cppu::getCaughtException(); + deployment::DeploymentException exc( + "PackageManagerImpl::checkPrerequisites: exception ", + static_cast<OWeakObject*>(this), excOccurred); + throw exc; + } +} + + +PackageManagerImpl::CmdEnvWrapperImpl::~CmdEnvWrapperImpl() +{ +} + + +PackageManagerImpl::CmdEnvWrapperImpl::CmdEnvWrapperImpl( + Reference<XCommandEnvironment> const & xUserCmdEnv, + Reference<XProgressHandler> const & xLogFile ) + : m_xLogFile( xLogFile ) +{ + if (xUserCmdEnv.is()) { + m_xUserProgress.set( xUserCmdEnv->getProgressHandler() ); + m_xUserInteractionHandler.set( xUserCmdEnv->getInteractionHandler() ); + } +} + +// XCommandEnvironment + +Reference<task::XInteractionHandler> +PackageManagerImpl::CmdEnvWrapperImpl::getInteractionHandler() +{ + return m_xUserInteractionHandler; +} + + +Reference<XProgressHandler> +PackageManagerImpl::CmdEnvWrapperImpl::getProgressHandler() +{ + return this; +} + +// XProgressHandler + +void PackageManagerImpl::CmdEnvWrapperImpl::push( Any const & Status ) +{ + if (m_xLogFile.is()) + m_xLogFile->push( Status ); + if (m_xUserProgress.is()) + m_xUserProgress->push( Status ); +} + + +void PackageManagerImpl::CmdEnvWrapperImpl::update( Any const & Status ) +{ + if (m_xLogFile.is()) + m_xLogFile->update( Status ); + if (m_xUserProgress.is()) + m_xUserProgress->update( Status ); +} + + +void PackageManagerImpl::CmdEnvWrapperImpl::pop() +{ + if (m_xLogFile.is()) + m_xLogFile->pop(); + if (m_xUserProgress.is()) + m_xUserProgress->pop(); +} + +} // namespace dp_manager + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/desktop/source/deployment/manager/dp_manager.h b/desktop/source/deployment/manager/dp_manager.h new file mode 100644 index 000000000..a2079da23 --- /dev/null +++ b/desktop/source/deployment/manager/dp_manager.h @@ -0,0 +1,232 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#pragma once + +#include "dp_activepackages.hxx" +#include <cppuhelper/basemutex.hxx> +#include <cppuhelper/compbase.hxx> +#include <cppuhelper/implbase.hxx> +#include <ucbhelper/content.hxx> +#include <com/sun/star/deployment/XPackageRegistry.hpp> +#include <com/sun/star/deployment/XPackageManager.hpp> +#include <memory> +#include <string_view> +#include <utility> + +namespace dp_manager { + +typedef ::cppu::WeakComponentImplHelper< + css::deployment::XPackageManager > t_pm_helper; + + +class PackageManagerImpl final : private cppu::BaseMutex, public t_pm_helper +{ + css::uno::Reference<css::uno::XComponentContext> m_xComponentContext; + OUString m_context; + OUString m_registrationData; + OUString m_registrationData_expanded; + OUString m_registryCache; + bool m_readOnly; + + OUString m_activePackages; + OUString m_activePackages_expanded; + std::unique_ptr< ActivePackages > m_activePackagesDB; + //This mutex is only used for synchronization in addPackage + ::osl::Mutex m_addMutex; + css::uno::Reference<css::ucb::XProgressHandler> m_xLogFile; + inline void logIntern( css::uno::Any const & status ); + void fireModified(); + + css::uno::Reference<css::deployment::XPackageRegistry> m_xRegistry; + + void initRegistryBackends(); + void initActivationLayer( + css::uno::Reference<css::ucb::XCommandEnvironment> const & xCmdEnv ); + OUString detectMediaType( + ::ucbhelper::Content const & ucbContent, bool throw_exc = true ); + OUString insertToActivationLayer( + css::uno::Sequence<css::beans::NamedValue> const & properties, + OUString const & mediaType, + ::ucbhelper::Content const & sourceContent, + OUString const & title, ActivePackages::Data * dbData ); + void insertToActivationLayerDB( + OUString const & id, ActivePackages::Data const & dbData ); + + static void deletePackageFromCache( + css::uno::Reference<css::deployment::XPackage> const & xPackage, + OUString const & destFolder ); + + bool isInstalled( + css::uno::Reference<css::deployment::XPackage> const & package); + + bool synchronizeRemovedExtensions( + css::uno::Reference<css::task::XAbortChannel> const & xAbortChannel, + css::uno::Reference<css::ucb::XCommandEnvironment> const & xCmdEnv); + + bool synchronizeAddedExtensions( + css::uno::Reference<css::task::XAbortChannel> const & xAbortChannel, + css::uno::Reference<css::ucb::XCommandEnvironment> const & xCmdEnv); + + class CmdEnvWrapperImpl + : public ::cppu::WeakImplHelper< css::ucb::XCommandEnvironment, + css::ucb::XProgressHandler > + { + css::uno::Reference<css::ucb::XProgressHandler> m_xLogFile; + css::uno::Reference<css::ucb::XProgressHandler> m_xUserProgress; + css::uno::Reference<css::task::XInteractionHandler> + m_xUserInteractionHandler; + + public: + virtual ~CmdEnvWrapperImpl() override; + CmdEnvWrapperImpl( + css::uno::Reference<css::ucb::XCommandEnvironment> + const & xUserCmdEnv, + css::uno::Reference<css::ucb::XProgressHandler> const & xLogFile ); + + // XCommandEnvironment + virtual css::uno::Reference<css::task::XInteractionHandler> SAL_CALL + getInteractionHandler() override; + virtual css::uno::Reference<css::ucb::XProgressHandler> SAL_CALL + getProgressHandler() override; + + // XProgressHandler + virtual void SAL_CALL push( css::uno::Any const & Status ) override; + virtual void SAL_CALL update( css::uno::Any const & Status ) override; + virtual void SAL_CALL pop() override; + }; + + inline void check(); + virtual void SAL_CALL disposing() override; + + virtual ~PackageManagerImpl() override; + PackageManagerImpl( + css::uno::Reference<css::uno::XComponentContext> xComponentContext, OUString context ) + : t_pm_helper( m_aMutex ), + m_xComponentContext(std::move( xComponentContext )), + m_context(std::move( context )), + m_readOnly( true ) + {} + +public: + static css::uno::Reference<css::deployment::XPackageManager> create( + css::uno::Reference<css::uno::XComponentContext> + const & xComponentContext, OUString const & context ); + + // XComponent + virtual void SAL_CALL dispose() override; + virtual void SAL_CALL addEventListener( + css::uno::Reference<css::lang::XEventListener> const & xListener ) override; + virtual void SAL_CALL removeEventListener( + css::uno::Reference<css::lang::XEventListener> const & xListener ) override; + + // XModifyBroadcaster + virtual void SAL_CALL addModifyListener( + css::uno::Reference<css::util::XModifyListener> const & xListener ) override; + virtual void SAL_CALL removeModifyListener( + css::uno::Reference<css::util::XModifyListener> const & xListener ) override; + + // XPackageManager + virtual OUString SAL_CALL getContext() override; + virtual css::uno::Sequence< + css::uno::Reference<css::deployment::XPackageTypeInfo> > SAL_CALL + getSupportedPackageTypes() override; + + virtual css::uno::Reference<css::task::XAbortChannel> SAL_CALL + createAbortChannel() override; + + virtual css::uno::Reference<css::deployment::XPackage> SAL_CALL addPackage( + OUString const & url, + css::uno::Sequence<css::beans::NamedValue> const & properties, + OUString const & mediaType, + css::uno::Reference<css::task::XAbortChannel> const & xAbortChannel, + css::uno::Reference<css::ucb::XCommandEnvironment> const & xCmdEnv ) override; + + virtual css::uno::Reference<css::deployment::XPackage> SAL_CALL importExtension( + css::uno::Reference<css::deployment::XPackage> const & extension, + css::uno::Reference<css::task::XAbortChannel> const & xAbortChannel, + css::uno::Reference<css::ucb::XCommandEnvironment> const & xCmdEnv ) override; + + virtual void SAL_CALL removePackage( + OUString const & id, OUString const & fileName, + css::uno::Reference<css::task::XAbortChannel> const & xAbortChannel, + css::uno::Reference<css::ucb::XCommandEnvironment> const & xCmdEnv ) override; + + OUString getDeployPath( ActivePackages::Data const & data ); + css::uno::Reference<css::deployment::XPackage> getDeployedPackage_( + OUString const & id, OUString const & fileName, + css::uno::Reference<css::ucb::XCommandEnvironment> const & xCmdEnv ); + css::uno::Reference<css::deployment::XPackage> getDeployedPackage_( + std::u16string_view id, ActivePackages::Data const & data, + css::uno::Reference<css::ucb::XCommandEnvironment> const & xCmdEnv, + bool ignoreAlienPlatforms = false ); + virtual css::uno::Reference<css::deployment::XPackage> SAL_CALL + getDeployedPackage( + OUString const & id, OUString const & fileName, + css::uno::Reference<css::ucb::XCommandEnvironment> const & xCmdEnv ) override; + + css::uno::Sequence< css::uno::Reference<css::deployment::XPackage> > + getDeployedPackages_( + css::uno::Reference<css::ucb::XCommandEnvironment> const & xCmdEnv ); + virtual css::uno::Sequence< css::uno::Reference<css::deployment::XPackage> > + SAL_CALL getDeployedPackages( + css::uno::Reference<css::task::XAbortChannel> const & xAbortChannel, + css::uno::Reference<css::ucb::XCommandEnvironment> const & xCmdEnv ) override; + + virtual void SAL_CALL reinstallDeployedPackages( + sal_Bool force, + css::uno::Reference<css::task::XAbortChannel> const & xAbortChannel, + css::uno::Reference<css::ucb::XCommandEnvironment> const & xCmdEnv ) override; + + virtual ::sal_Bool SAL_CALL isReadOnly( ) override; + + virtual ::sal_Bool SAL_CALL synchronize( + css::uno::Reference<css::task::XAbortChannel> const & xAbortChannel, + css::uno::Reference<css::ucb::XCommandEnvironment> const & xCmdEnv ) override; + + virtual css::uno::Sequence<css::uno::Reference<css::deployment::XPackage> > SAL_CALL + getExtensionsWithUnacceptedLicenses( + css::uno::Reference<css::ucb::XCommandEnvironment> const & xCmdEnv) override; + + virtual sal_Int32 SAL_CALL checkPrerequisites( + css::uno::Reference<css::deployment::XPackage> const & extension, + css::uno::Reference<css::task::XAbortChannel> const & xAbortChannel, + css::uno::Reference<css::ucb::XCommandEnvironment> const & xCmdEnv ) override; + }; + + +inline void PackageManagerImpl::check() +{ + ::osl::MutexGuard guard( m_aMutex ); + if (rBHelper.bInDispose || rBHelper.bDisposed) + throw css::lang::DisposedException( + "PackageManager instance has already been disposed!", + static_cast< ::cppu::OWeakObject * >(this) ); +} + + +inline void PackageManagerImpl::logIntern( css::uno::Any const & status ) +{ + if (m_xLogFile.is()) + m_xLogFile->update( status ); +} + +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/desktop/source/deployment/manager/dp_managerfac.cxx b/desktop/source/deployment/manager/dp_managerfac.cxx new file mode 100644 index 000000000..79e0ea358 --- /dev/null +++ b/desktop/source/deployment/manager/dp_managerfac.cxx @@ -0,0 +1,186 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + + +#include "dp_manager.h" +#include <dp_misc.h> +#include <cppuhelper/compbase.hxx> +#include <cppuhelper/supportsservice.hxx> +#include <com/sun/star/deployment/XPackageManagerFactory.hpp> +#include <com/sun/star/lang/XServiceInfo.hpp> +#include <unordered_map> + +using namespace ::dp_misc; +using namespace ::com::sun::star; +using namespace ::com::sun::star::uno; + +namespace dp_manager::factory { + +typedef ::cppu::WeakComponentImplHelper< + deployment::XPackageManagerFactory, lang::XServiceInfo > t_pmfac_helper; + +namespace { + +class PackageManagerFactoryImpl : private cppu::BaseMutex, public t_pmfac_helper +{ + Reference<XComponentContext> m_xComponentContext; + + Reference<deployment::XPackageManager> m_xUserMgr; + Reference<deployment::XPackageManager> m_xSharedMgr; + Reference<deployment::XPackageManager> m_xBundledMgr; + Reference<deployment::XPackageManager> m_xTmpMgr; + Reference<deployment::XPackageManager> m_xBakMgr; + typedef std::unordered_map< + OUString, WeakReference<deployment::XPackageManager> > t_string2weakref; + t_string2weakref m_managers; + +protected: + inline void check(); + virtual void SAL_CALL disposing() override; + +public: + explicit PackageManagerFactoryImpl( + Reference<XComponentContext> const & xComponentContext ); + + // XServiceInfo + virtual OUString SAL_CALL getImplementationName() override; + virtual sal_Bool SAL_CALL supportsService( const OUString& ServiceName ) override; + virtual css::uno::Sequence< OUString > SAL_CALL getSupportedServiceNames() override; + + // XPackageManagerFactory + virtual Reference<deployment::XPackageManager> SAL_CALL getPackageManager( + OUString const & context ) override; +}; + +} + +PackageManagerFactoryImpl::PackageManagerFactoryImpl( + Reference<XComponentContext> const & xComponentContext ) + : t_pmfac_helper( m_aMutex ), + m_xComponentContext( xComponentContext ) +{ +} + +// XServiceInfo +OUString PackageManagerFactoryImpl::getImplementationName() +{ + return "com.sun.star.comp.deployment.PackageManagerFactory"; +} + +sal_Bool PackageManagerFactoryImpl::supportsService( const OUString& ServiceName ) +{ + return cppu::supportsService(this, ServiceName); +} + +css::uno::Sequence< OUString > PackageManagerFactoryImpl::getSupportedServiceNames() +{ + // a private one: + return { "com.sun.star.comp.deployment.PackageManagerFactory" }; +} + +inline void PackageManagerFactoryImpl::check() +{ + ::osl::MutexGuard guard( m_aMutex ); + if (rBHelper.bInDispose || rBHelper.bDisposed) + { + throw lang::DisposedException( + "PackageManagerFactory instance has already been disposed!", + static_cast<OWeakObject *>(this) ); + } +} + + +void PackageManagerFactoryImpl::disposing() +{ + // dispose all managers: + ::osl::MutexGuard guard( m_aMutex ); + for (auto const& elem : m_managers) + try_dispose( elem.second ); + m_managers = t_string2weakref(); + // the below are already disposed: + m_xUserMgr.clear(); + m_xSharedMgr.clear(); + m_xBundledMgr.clear(); + m_xTmpMgr.clear(); + m_xBakMgr.clear(); +} + +// XPackageManagerFactory + +Reference<deployment::XPackageManager> +PackageManagerFactoryImpl::getPackageManager( OUString const & context ) +{ + Reference< deployment::XPackageManager > xRet; + ::osl::ResettableMutexGuard guard( m_aMutex ); + check(); + t_string2weakref::const_iterator const iFind( m_managers.find( context ) ); + if (iFind != m_managers.end()) { + xRet = iFind->second; + if (xRet.is()) + return xRet; + } + + guard.clear(); + xRet.set( PackageManagerImpl::create( m_xComponentContext, context ) ); + guard.reset(); + std::pair< t_string2weakref::iterator, bool > insertion( + m_managers.emplace( context, xRet ) ); + if (insertion.second) + { + OSL_ASSERT( insertion.first->second.get() == xRet ); + // hold user, shared mgrs for whole process: live deployment + if ( context == "user" ) + m_xUserMgr = xRet; + else if ( context == "shared" ) + m_xSharedMgr = xRet; + else if ( context == "bundled" ) + m_xBundledMgr = xRet; + else if ( context == "tmp" ) + m_xTmpMgr = xRet; + else if ( context == "bak" ) + m_xBakMgr = xRet; + } + else + { + Reference< deployment::XPackageManager > xAlreadyIn( + insertion.first->second ); + if (xAlreadyIn.is()) + { + guard.clear(); + try_dispose( xRet ); + xRet = xAlreadyIn; + } + else + { + insertion.first->second = xRet; + } + } + return xRet; +} + +} // namespace dp_manager::factory + +extern "C" SAL_DLLPUBLIC_EXPORT css::uno::XInterface* +com_sun_star_comp_deployment_PackageManagerFactory_get_implementation( + css::uno::XComponentContext* context, css::uno::Sequence<css::uno::Any> const& ) +{ + return cppu::acquire(new dp_manager::factory::PackageManagerFactoryImpl(context)); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/desktop/source/deployment/manager/dp_properties.cxx b/desktop/source/deployment/manager/dp_properties.cxx new file mode 100644 index 000000000..b0d2245e8 --- /dev/null +++ b/desktop/source/deployment/manager/dp_properties.cxx @@ -0,0 +1,145 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + + +#include <com/sun/star/ucb/XCommandEnvironment.hpp> +#include <com/sun/star/lang/IllegalArgumentException.hpp> +#include <xmlscript/xml_helper.hxx> +#include <ucbhelper/content.hxx> + +#include <dp_ucb.h> +#include "dp_properties.hxx" + +namespace lang = com::sun::star::lang; +namespace ucb = com::sun::star::ucb; +namespace uno = com::sun::star::uno; + + +using ::com::sun::star::uno::Reference; + +constexpr OUStringLiteral PROP_SUPPRESS_LICENSE = u"SUPPRESS_LICENSE"; +constexpr OUStringLiteral PROP_EXTENSION_UPDATE = u"EXTENSION_UPDATE"; + +namespace dp_manager { + +//Reading the file +ExtensionProperties::ExtensionProperties( + std::u16string_view urlExtension, + Reference<ucb::XCommandEnvironment> const & xCmdEnv, + Reference<uno::XComponentContext> const & xContext) : + m_xCmdEnv(xCmdEnv), m_xContext(xContext) +{ + m_propFileUrl = OUString::Concat(urlExtension) + "properties"; + + std::vector< std::pair< OUString, OUString> > props; + if (! dp_misc::create_ucb_content(nullptr, m_propFileUrl, nullptr, false)) + return; + + ::ucbhelper::Content contentProps(m_propFileUrl, m_xCmdEnv, m_xContext); + dp_misc::readProperties(props, contentProps); + + for (auto const& prop : props) + { + if (prop.first == PROP_SUPPRESS_LICENSE) + m_prop_suppress_license = prop.second; + } +} + +//Writing the file +ExtensionProperties::ExtensionProperties( + std::u16string_view urlExtension, + uno::Sequence<css::beans::NamedValue> const & properties, + Reference<ucb::XCommandEnvironment> const & xCmdEnv, + Reference<uno::XComponentContext> const & xContext) : + m_xCmdEnv(xCmdEnv), m_xContext(xContext) +{ + m_propFileUrl = OUString::Concat(urlExtension) + "properties"; + + for (css::beans::NamedValue const & v : properties) + { + if (v.Name == PROP_SUPPRESS_LICENSE) + { + m_prop_suppress_license = getPropertyValue(v); + } + else if (v.Name == PROP_EXTENSION_UPDATE) + { + m_prop_extension_update = getPropertyValue(v); + } + else + { + throw lang::IllegalArgumentException( + "Extension Manager: unknown property", nullptr, -1); + } + } +} + +OUString ExtensionProperties::getPropertyValue(css::beans::NamedValue const & v) +{ + OUString value("0"); + if (! (v.Value >>= value) ) + { + throw lang::IllegalArgumentException( + "Extension Manager: wrong property value", nullptr, -1); + } + return value; +} +void ExtensionProperties::write() +{ + ::ucbhelper::Content contentProps(m_propFileUrl, m_xCmdEnv, m_xContext); + OUString buf; + + if (m_prop_suppress_license) + { + buf = OUString::Concat(PROP_SUPPRESS_LICENSE) + "=" + *m_prop_suppress_license; + } + + OString stamp = OUStringToOString(buf, RTL_TEXTENCODING_UTF8); + Reference<css::io::XInputStream> xData( + ::xmlscript::createInputStream( + reinterpret_cast<sal_Int8 const *>(stamp.getStr()), + stamp.getLength() ) ); + contentProps.writeStream( xData, true /* replace existing */ ); +} + +bool ExtensionProperties::isSuppressedLicense() const +{ + bool ret = false; + if (m_prop_suppress_license) + { + if (*m_prop_suppress_license == "1") + ret = true; + } + return ret; +} + +bool ExtensionProperties::isExtensionUpdate() const +{ + bool ret = false; + if (m_prop_extension_update) + { + if (*m_prop_extension_update == "1") + ret = true; + } + return ret; +} + +} // namespace dp_manager + + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/desktop/source/deployment/manager/dp_properties.hxx b/desktop/source/deployment/manager/dp_properties.hxx new file mode 100644 index 000000000..06139ece3 --- /dev/null +++ b/desktop/source/deployment/manager/dp_properties.hxx @@ -0,0 +1,58 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#pragma once + +#include <com/sun/star/beans/NamedValue.hpp> +#include <com/sun/star/ucb/XCommandEnvironment.hpp> +#include <com/sun/star/uno/XComponentContext.hpp> +#include <optional> +#include <string_view> + +namespace dp_manager +{ +class ExtensionProperties final +{ + OUString m_propFileUrl; + const css::uno::Reference<css::ucb::XCommandEnvironment> m_xCmdEnv; + const css::uno::Reference<css::uno::XComponentContext> m_xContext; + ::std::optional<OUString> m_prop_suppress_license; + ::std::optional<OUString> m_prop_extension_update; + + static OUString getPropertyValue(css::beans::NamedValue const& v); + +public: + ExtensionProperties(std::u16string_view urlExtension, + css::uno::Reference<css::ucb::XCommandEnvironment> const& xCmdEnv, + css::uno::Reference<css::uno::XComponentContext> const& xContext); + + ExtensionProperties(std::u16string_view urlExtension, + css::uno::Sequence<css::beans::NamedValue> const& properties, + css::uno::Reference<css::ucb::XCommandEnvironment> const& xCmdEnv, + css::uno::Reference<css::uno::XComponentContext> const& xContext); + + void write(); + + bool isSuppressedLicense() const; + + bool isExtensionUpdate() const; +}; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ |