diff options
Diffstat (limited to 'desktop/source/deployment/registry')
29 files changed, 9708 insertions, 0 deletions
diff --git a/desktop/source/deployment/registry/component/dp_compbackenddb.cxx b/desktop/source/deployment/registry/component/dp_compbackenddb.cxx new file mode 100644 index 000000000..fd9bb2c61 --- /dev/null +++ b/desktop/source/deployment/registry/component/dp_compbackenddb.cxx @@ -0,0 +1,131 @@ +/* -*- 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/exc_hlp.hxx> +#include <com/sun/star/deployment/DeploymentException.hpp> +#include <com/sun/star/uno/XComponentContext.hpp> + +#include "dp_compbackenddb.hxx" + + +using namespace ::com::sun::star::uno; + +constexpr OUStringLiteral EXTENSION_REG_NS = u"http://openoffice.org/extensionmanager/component-registry/2010"; +constexpr OUStringLiteral NS_PREFIX = u"comp"; +constexpr OUStringLiteral ROOT_ELEMENT_NAME = u"component-backend-db"; +constexpr OUStringLiteral KEY_ELEMENT_NAME = u"component"; + +namespace dp_registry::backend::component { + +ComponentBackendDb::ComponentBackendDb( + Reference<XComponentContext> const & xContext, + OUString const & url):BackendDb(xContext, url) +{ + +} + +OUString ComponentBackendDb::getDbNSName() +{ + return EXTENSION_REG_NS; +} + +OUString ComponentBackendDb::getNSPrefix() +{ + return NS_PREFIX; +} + +OUString ComponentBackendDb::getRootElementName() +{ + return ROOT_ELEMENT_NAME; +} + +OUString ComponentBackendDb::getKeyElementName() +{ + return KEY_ELEMENT_NAME; +} + +void ComponentBackendDb::addEntry(OUString const & url, Data const & data) +{ + try{ + if (!activateEntry(url)) + { + Reference<css::xml::dom::XNode> componentNode = writeKeyElement(url); + writeSimpleElement(u"java-type-library", + OUString::boolean(data.javaTypeLibrary), + componentNode); + + writeSimpleList( + data.implementationNames, + u"implementation-names", + u"name", + componentNode); + + writeVectorOfPair( + data.singletons, + u"singletons", + u"item", + u"key", + u"value", + componentNode); + + save(); + } + } + catch(const css::uno::Exception &) + { + Any exc( ::cppu::getCaughtException() ); + throw css::deployment::DeploymentException( + "Extension Manager: failed to write data entry in backend db: " + + m_urlDb, nullptr, exc); + } +} + +ComponentBackendDb::Data ComponentBackendDb::getEntry(std::u16string_view url) +{ + try + { + ComponentBackendDb::Data retData; + Reference<css::xml::dom::XNode> aNode = getKeyElement(url); + if (aNode.is()) + { + bool bJava = readSimpleElement(u"java-type-library", aNode) == "true"; + retData.javaTypeLibrary = bJava; + + retData.implementationNames = + readList( aNode, u"implementation-names", u"name"); + + retData.singletons = + readVectorOfPair( aNode, u"singletons", u"item", u"key", u"value"); + } + return retData; + } + catch(const css::uno::Exception &) + { + Any exc( ::cppu::getCaughtException() ); + throw css::deployment::DeploymentException( + "Extension Manager: failed to read data entry in backend db: " + + m_urlDb, nullptr, exc); + } +} + + +} // namespace dp_registry::backend::component + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/desktop/source/deployment/registry/component/dp_compbackenddb.hxx b/desktop/source/deployment/registry/component/dp_compbackenddb.hxx new file mode 100644 index 000000000..84153b6fa --- /dev/null +++ b/desktop/source/deployment/registry/component/dp_compbackenddb.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 <rtl/ustring.hxx> +#include <rtl/string.hxx> +#include <vector> +#include <deque> +#include <string_view> + +#include <dp_backenddb.hxx> + +namespace com::sun::star::uno { class XComponentContext; } + +namespace dp_registry::backend::component { + +/* The XML file stores the extensions which are currently registered. + They will be removed when they are revoked. + The format looks like this: + +<?xml version="1.0"?> +<component-backend-db xmlns="http://openoffice.org/extensionmanager/component-registry/2010"> + <component url="vnd.sun.star.expand:$UNO_USER_PACKAGES_CACHE/uno_packages/5CD5.tmp_/leaves1.oxt/extensionoptions.jar"> + <name>FileName</name> + <java-type-library>true</java-type-library> + <implementation-names> + <name>com.sun.star.comp.extensionoptions.OptionsEventHandler$_OptionsEventHandler</name> + ... + </implementation-names> + <singletons> + <item> + <key>com.sun.star.java.theJavaVirtualMachine</key> + <value>com.sun.star.java.JavaVirtualMachine</value> + </item> + ... + </singletons> + </component> + + <component ...> + ... +</component-backend-db> + */ +class ComponentBackendDb: public dp_registry::backend::BackendDb +{ +protected: + virtual OUString getDbNSName() override; + virtual OUString getNSPrefix() override; + virtual OUString getRootElementName() override; + virtual OUString getKeyElementName() override; + +public: + struct Data + { + Data(): javaTypeLibrary(false) {}; + + std::deque< OUString> implementationNames; + std::vector< std::pair< OUString, OUString> >singletons; + // map from singleton names to implementation names + bool javaTypeLibrary; + }; + +public: + + ComponentBackendDb( css::uno::Reference<css::uno::XComponentContext> const & xContext, + OUString const & url); + + void addEntry(OUString const & url, Data const & data); + + Data getEntry(std::u16string_view url); + + +}; + + +} + + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/desktop/source/deployment/registry/component/dp_component.cxx b/desktop/source/deployment/registry/component/dp_component.cxx new file mode 100644 index 000000000..b9aa6518a --- /dev/null +++ b/desktop/source/deployment/registry/component/dp_component.cxx @@ -0,0 +1,1720 @@ +/* -*- 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 <strings.hrc> +#include <dp_misc.h> +#include <dp_shared.hxx> +#include <dp_backend.h> +#include <dp_platform.hxx> +#include <dp_ucb.h> +#include <rtl/string.hxx> +#include <rtl/strbuf.hxx> +#include <rtl/ustrbuf.hxx> +#include <rtl/uri.hxx> +#include <sal/log.hxx> +#include <cppuhelper/exc_hlp.hxx> +#include <cppuhelper/supportsservice.hxx> +#include <ucbhelper/content.hxx> +#include <comphelper/sequence.hxx> +#include <utility> +#include <xmlscript/xml_helper.hxx> +#include <svl/inettype.hxx> +#include <tools/diagnose_ex.h> +#include <o3tl/string_view.hxx> +#include <com/sun/star/beans/NamedValue.hpp> +#include <com/sun/star/deployment/DeploymentException.hpp> +#include <com/sun/star/lang/WrappedTargetRuntimeException.hpp> +#include <com/sun/star/container/XNameContainer.hpp> +#include <com/sun/star/container/XSet.hpp> +#include <com/sun/star/registry/XSimpleRegistry.hpp> +#include <com/sun/star/registry/XImplementationRegistration.hpp> +#include <com/sun/star/loader/XImplementationLoader.hpp> +#include <com/sun/star/io/XInputStream.hpp> +#include <com/sun/star/ucb/NameClash.hpp> +#include <com/sun/star/util/theMacroExpander.hpp> +#include <algorithm> +#include <deque> +#include <memory> +#include <string_view> +#include <unordered_map> +#include <vector> +#include "dp_compbackenddb.hxx" + +using namespace ::dp_misc; +using namespace ::com::sun::star; +using namespace ::com::sun::star::uno; +using namespace ::com::sun::star::ucb; + +namespace dp_registry::backend::component { +namespace { + +/** return a vector of bootstrap variables which have been provided + as command arguments. +*/ +std::vector<OUString> getCmdBootstrapVariables() +{ + std::vector<OUString> ret; + sal_uInt32 count = osl_getCommandArgCount(); + for (sal_uInt32 i = 0; i < count; i++) + { + OUString arg; + osl_getCommandArg(i, &arg.pData); + if (arg.startsWith("-env:")) + ret.push_back(arg); + } + return ret; +} + +bool jarManifestHeaderPresent( + OUString const & url, OUString const & name, + Reference<XCommandEnvironment> const & xCmdEnv ) +{ + OUString buf = "vnd.sun.star.zip://" + + ::rtl::Uri::encode( + url, rtl_UriCharClassRegName, rtl_UriEncodeIgnoreEscapes, + RTL_TEXTENCODING_UTF8 ) + + "/META-INF/MANIFEST.MF"; + ::ucbhelper::Content manifestContent; + OUString line; + return + create_ucb_content( + &manifestContent, buf, xCmdEnv, + false /* no throw */ ) + && readLine( &line, name, manifestContent, RTL_TEXTENCODING_ASCII_US ); +} + + +class BackendImpl : public ::dp_registry::backend::PackageRegistryBackend +{ + class ComponentPackageImpl : public ::dp_registry::backend::Package + { + BackendImpl * getMyBackend() const; + + const OUString m_loader; + + enum class Reg { Uninit, Void, Registered, NotRegistered, MaybeRegistered }; + Reg m_registered; + + void getComponentInfo( + ComponentBackendDb::Data * data, + std::vector< css::uno::Reference< css::uno::XInterface > > * + factories, + Reference<XComponentContext> const & xContext ); + + void componentLiveInsertion( + ComponentBackendDb::Data const & data, + std::vector< css::uno::Reference< css::uno::XInterface > > const & + factories); + + void componentLiveRemoval(ComponentBackendDb::Data const & data); + + // Package + virtual beans::Optional< beans::Ambiguous<sal_Bool> > isRegistered_( + ::osl::ResettableMutexGuard & guard, + ::rtl::Reference<AbortChannel> const & abortChannel, + Reference<XCommandEnvironment> const & xCmdEnv ) override; + virtual void processPackage_( + ::osl::ResettableMutexGuard & guard, + bool registerPackage, + bool startup, + ::rtl::Reference<AbortChannel> const & abortChannel, + Reference<XCommandEnvironment> const & xCmdEnv ) override; + + Reference<registry::XSimpleRegistry> getRDB() const; + + public: + ComponentPackageImpl( + ::rtl::Reference<PackageRegistryBackend> const & myBackend, + OUString const & url, OUString const & name, + Reference<deployment::XPackageTypeInfo> const & xPackageType, + OUString loader, bool bRemoved, + OUString const & identifier); + }; + friend class ComponentPackageImpl; + + class ComponentsPackageImpl : public ::dp_registry::backend::Package + { + BackendImpl * getMyBackend() const; + + // Package + virtual beans::Optional< beans::Ambiguous<sal_Bool> > isRegistered_( + ::osl::ResettableMutexGuard & guard, + ::rtl::Reference<AbortChannel> const & abortChannel, + Reference<XCommandEnvironment> const & xCmdEnv ) override; + virtual void processPackage_( + ::osl::ResettableMutexGuard & guard, + bool registerPackage, + bool startup, + ::rtl::Reference<AbortChannel> const & abortChannel, + Reference<XCommandEnvironment> const & xCmdEnv ) override; + public: + ComponentsPackageImpl( + ::rtl::Reference<PackageRegistryBackend> const & myBackend, + OUString const & url, OUString const & name, + Reference<deployment::XPackageTypeInfo> const & xPackageType, + bool bRemoved, OUString const & identifier); + }; + friend class ComponentsPackageImpl; + + class TypelibraryPackageImpl : public ::dp_registry::backend::Package + { + BackendImpl * getMyBackend() const; + + const bool m_jarFile; + + // Package + virtual beans::Optional< beans::Ambiguous<sal_Bool> > isRegistered_( + ::osl::ResettableMutexGuard & guard, + ::rtl::Reference<AbortChannel> const & abortChannel, + Reference<XCommandEnvironment> const & xCmdEnv ) override; + virtual void processPackage_( + ::osl::ResettableMutexGuard & guard, + bool registerPackage, + bool startup, + ::rtl::Reference<AbortChannel> const & abortChannel, + Reference<XCommandEnvironment> const & xCmdEnv ) override; + + public: + TypelibraryPackageImpl( + ::rtl::Reference<PackageRegistryBackend> const & myBackend, + OUString const & url, OUString const & name, + Reference<deployment::XPackageTypeInfo> const & xPackageType, + bool jarFile, bool bRemoved, + OUString const & identifier); + }; + friend class TypelibraryPackageImpl; + + /** Serves for unregistering packages that were registered on a + different platform. This can happen if one has remotely mounted + /home, for example. + */ + class OtherPlatformPackageImpl : public ::dp_registry::backend::Package + { + public: + OtherPlatformPackageImpl( + ::rtl::Reference<PackageRegistryBackend> const & myBackend, + OUString const & url, OUString const & name, + Reference<deployment::XPackageTypeInfo> const & xPackageType, + bool bRemoved, OUString const & identifier, OUString platform); + + private: + BackendImpl * getMyBackend() const; + + Reference<registry::XSimpleRegistry> impl_openRDB() const; + Reference<XInterface> impl_createInstance(OUString const& rService) const; + + // Package + virtual beans::Optional< beans::Ambiguous<sal_Bool> > isRegistered_( + ::osl::ResettableMutexGuard & guard, + ::rtl::Reference<AbortChannel> const & abortChannel, + Reference<XCommandEnvironment> const & xCmdEnv ) override; + virtual void processPackage_( + ::osl::ResettableMutexGuard & guard, + bool registerPackage, + bool startup, + ::rtl::Reference<AbortChannel> const & abortChannel, + Reference<XCommandEnvironment> const & xCmdEnv ) override; + + private: + OUString const m_aPlatform; + }; + friend class OtherPlatformPackageImpl; + + std::deque<OUString> m_jar_typelibs; + std::deque<OUString> m_rdb_typelibs; + std::deque<OUString> m_components; + + enum RcItem { RCITEM_JAR_TYPELIB, RCITEM_RDB_TYPELIB, RCITEM_COMPONENTS }; + + std::deque<OUString> & getRcItemList( RcItem kind ) { + switch (kind) + { + case RCITEM_JAR_TYPELIB: + return m_jar_typelibs; + case RCITEM_RDB_TYPELIB: + return m_rdb_typelibs; + default: // case RCITEM_COMPONENTS + return m_components; + } + } + + bool m_unorc_inited; + bool m_unorc_modified; + bool bSwitchedRdbFiles; + + typedef std::unordered_map< OUString, Reference<XInterface> > t_string2object; + t_string2object m_backendObjects; + + // PackageRegistryBackend + virtual Reference<deployment::XPackage> bindPackage_( + OUString const & url, OUString const & mediaType, + bool bRemoved, OUString const & identifier, + Reference<XCommandEnvironment> const & xCmdEnv ) override; + + virtual void SAL_CALL disposing() override; + + const Reference<deployment::XPackageTypeInfo> m_xDynComponentTypeInfo; + const Reference<deployment::XPackageTypeInfo> m_xJavaComponentTypeInfo; + const Reference<deployment::XPackageTypeInfo> m_xPythonComponentTypeInfo; + const Reference<deployment::XPackageTypeInfo> m_xComponentsTypeInfo; + const Reference<deployment::XPackageTypeInfo> m_xRDBTypelibTypeInfo; + const Reference<deployment::XPackageTypeInfo> m_xJavaTypelibTypeInfo; + Sequence< Reference<deployment::XPackageTypeInfo> > m_typeInfos; + + OUString m_commonRDB; + OUString m_nativeRDB; + + //URLs of the original rdbs (before any switching): + OUString m_commonRDB_orig; + OUString m_nativeRDB_orig; + + std::unique_ptr<ComponentBackendDb> m_backendDb; + + void addDataToDb(OUString const & url, ComponentBackendDb::Data const & data); + ComponentBackendDb::Data readDataFromDb(std::u16string_view url); + void revokeEntryFromDb(std::u16string_view url); + + Reference<registry::XSimpleRegistry> m_xCommonRDB; + Reference<registry::XSimpleRegistry> m_xNativeRDB; + + void unorc_verify_init( Reference<XCommandEnvironment> const & xCmdEnv ); + void unorc_flush( Reference<XCommandEnvironment> const & xCmdEnv ); + + Reference<XInterface> getObject( OUString const & id ); + Reference<XInterface> insertObject( + OUString const & id, Reference<XInterface> const & xObject ); + void releaseObject( OUString const & id ); + + void addToUnoRc( RcItem kind, OUString const & url, + Reference<XCommandEnvironment> const & xCmdEnv ); + void removeFromUnoRc( RcItem kind, OUString const & url, + Reference<XCommandEnvironment> const & xCmdEnv ); + bool hasInUnoRc( RcItem kind, OUString const & url ); + + css::uno::Reference< css::uno::XComponentContext > getRootContext() const; + +public: + BackendImpl( Sequence<Any> const & args, + 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; + + // XPackageRegistry + virtual Sequence< Reference<deployment::XPackageTypeInfo> > SAL_CALL + getSupportedPackageTypes() override; + + virtual void SAL_CALL packageRemoved(OUString const & url, OUString const & mediaType) override; + + using PackageRegistryBackend::disposing; + + //Will be called from ComponentPackageImpl + void initServiceRdbFiles(); +}; + + +BackendImpl::ComponentPackageImpl::ComponentPackageImpl( + ::rtl::Reference<PackageRegistryBackend> const & myBackend, + OUString const & url, OUString const & name, + Reference<deployment::XPackageTypeInfo> const & xPackageType, + OUString loader, bool bRemoved, + OUString const & identifier) + : Package( myBackend, url, name, name /* display-name */, + xPackageType, bRemoved, identifier), + m_loader(std::move( loader )), + m_registered( Reg::Uninit ) +{} + +Reference<registry::XSimpleRegistry> +BackendImpl::ComponentPackageImpl::getRDB() const +{ + BackendImpl * that = getMyBackend(); + + //Late "initialization" of the services rdb files + //This is to prevent problems when running several + //instances of OOo with root rights in parallel. This + //would otherwise cause problems when copying the rdbs. + //See http://qa.openoffice.org/issues/show_bug.cgi?id=99257 + { + const ::osl::MutexGuard guard( m_aMutex ); + if (!that->bSwitchedRdbFiles) + { + that->bSwitchedRdbFiles = true; + that->initServiceRdbFiles(); + } + } + if ( m_loader == "com.sun.star.loader.SharedLibrary" ) + return that->m_xNativeRDB; + else + return that->m_xCommonRDB; +} + +BackendImpl * BackendImpl::ComponentPackageImpl::getMyBackend() const +{ + BackendImpl * pBackend = static_cast<BackendImpl *>(m_myBackend.get()); + if (nullptr == pBackend) + { + //Throws a DisposedException + check(); + //We should never get here... + throw RuntimeException( + "Failed to get the BackendImpl", + static_cast<OWeakObject*>(const_cast<ComponentPackageImpl *>(this))); + } + return pBackend; +} + + +void BackendImpl::disposing() +{ + try { + m_backendObjects = t_string2object(); + if (m_xNativeRDB.is()) { + m_xNativeRDB->close(); + m_xNativeRDB.clear(); + } + if (m_xCommonRDB.is()) { + m_xCommonRDB->close(); + m_xCommonRDB.clear(); + } + unorc_flush( Reference<XCommandEnvironment>() ); + + PackageRegistryBackend::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 ); + } +} + + +void BackendImpl::initServiceRdbFiles() +{ + const Reference<XCommandEnvironment> xCmdEnv; + + ::ucbhelper::Content cacheDir( getCachePath(), xCmdEnv, m_xComponentContext ); + ::ucbhelper::Content oldRDB; + // switch common rdb: + if (!m_commonRDB_orig.isEmpty()) + { + (void)create_ucb_content( + &oldRDB, makeURL( getCachePath(), m_commonRDB_orig), + xCmdEnv, false /* no throw */ ); + } + m_commonRDB = m_commonRDB_orig == "common.rdb" ? std::u16string_view(u"common_.rdb") : std::u16string_view(u"common.rdb"); + if (oldRDB.get().is()) + { + cacheDir.transferContent( + oldRDB, ::ucbhelper::InsertOperation::Copy, + m_commonRDB, NameClash::OVERWRITE ); + oldRDB = ::ucbhelper::Content(); + } + // switch native rdb: + if (!m_nativeRDB_orig.isEmpty()) + { + (void)create_ucb_content( + &oldRDB, makeURL(getCachePath(), m_nativeRDB_orig), + xCmdEnv, false /* no throw */ ); + } + const OUString plt_rdb( getPlatformString() + ".rdb" ); + const OUString plt_rdb_( getPlatformString() + "_.rdb" ); + m_nativeRDB = (m_nativeRDB_orig == plt_rdb ) ? plt_rdb_ : plt_rdb; + if (oldRDB.get().is()) + { + cacheDir.transferContent( + oldRDB, ::ucbhelper::InsertOperation::Copy, + m_nativeRDB, NameClash::OVERWRITE ); + } + + // UNO is bootstrapped, flush for next process start: + m_unorc_modified = true; + unorc_flush( Reference<XCommandEnvironment>() ); + + + // common rdb for java, native rdb for shared lib components + if (!m_commonRDB.isEmpty()) { + m_xCommonRDB.set( + m_xComponentContext->getServiceManager() + ->createInstanceWithContext( + "com.sun.star.registry.SimpleRegistry", + m_xComponentContext ), UNO_QUERY_THROW ); + m_xCommonRDB->open( + makeURL( expandUnoRcUrl(getCachePath()), m_commonRDB ), + false, true); + } + if (!m_nativeRDB.isEmpty()) { + m_xNativeRDB.set( + m_xComponentContext->getServiceManager() + ->createInstanceWithContext( + "com.sun.star.registry.SimpleRegistry", + m_xComponentContext ), UNO_QUERY_THROW ); + m_xNativeRDB->open( + makeURL( expandUnoRcUrl(getCachePath()), m_nativeRDB ), + false, true); + } +} + +BackendImpl::BackendImpl( + Sequence<Any> const & args, + Reference<XComponentContext> const & xComponentContext ) + : PackageRegistryBackend( args, xComponentContext ), + m_unorc_inited( false ), + m_unorc_modified( false ), + bSwitchedRdbFiles(false), + m_xDynComponentTypeInfo( new Package::TypeInfo( + "application/vnd.sun.star.uno-component;type=native;platform=" + + getPlatformString(), + "*" SAL_DLLEXTENSION, + DpResId(RID_STR_DYN_COMPONENT) + ) ), + m_xJavaComponentTypeInfo( new Package::TypeInfo( + "application/vnd.sun.star.uno-component;type=Java", + "*.jar", + DpResId(RID_STR_JAVA_COMPONENT) + ) ), + m_xPythonComponentTypeInfo( new Package::TypeInfo( + "application/vnd.sun.star.uno-component;type=Python", + "*.py", + DpResId( + RID_STR_PYTHON_COMPONENT) + ) ), + m_xComponentsTypeInfo( new Package::TypeInfo( + "application/vnd.sun.star.uno-components", + "*.components", + DpResId(RID_STR_COMPONENTS) + ) ), + m_xRDBTypelibTypeInfo( new Package::TypeInfo( + "application/vnd.sun.star.uno-typelibrary;type=RDB", + "*.rdb", + DpResId(RID_STR_RDB_TYPELIB) + ) ), + m_xJavaTypelibTypeInfo( new Package::TypeInfo( + "application/vnd.sun.star.uno-typelibrary;type=Java", + "*.jar", + DpResId(RID_STR_JAVA_TYPELIB) + ) ), + m_typeInfos{ m_xDynComponentTypeInfo, m_xJavaComponentTypeInfo, m_xPythonComponentTypeInfo, + m_xComponentsTypeInfo, m_xRDBTypelibTypeInfo, m_xJavaTypelibTypeInfo } +{ + const Reference<XCommandEnvironment> xCmdEnv; + + if (transientMode()) + { + // in-mem rdbs: + // common rdb for java, native rdb for shared lib components + m_xCommonRDB.set( + xComponentContext->getServiceManager()->createInstanceWithContext( + "com.sun.star.registry.SimpleRegistry", + xComponentContext ), UNO_QUERY_THROW ); + m_xCommonRDB->open( OUString() /* in-mem */, + false /* ! read-only */, true /* create */ ); + m_xNativeRDB.set( + xComponentContext->getServiceManager()->createInstanceWithContext( + "com.sun.star.registry.SimpleRegistry", + xComponentContext ), UNO_QUERY_THROW ); + m_xNativeRDB->open( OUString() /* in-mem */, + false /* ! read-only */, true /* create */ ); + } + else + { + unorc_verify_init( xCmdEnv ); + OUString dbFile = makeURL(getCachePath(), "backenddb.xml"); + m_backendDb.reset( + new ComponentBackendDb(getComponentContext(), dbFile)); + } +} + +// XServiceInfo +OUString BackendImpl::getImplementationName() +{ + return "com.sun.star.comp.deployment.component.PackageRegistryBackend"; +} + +sal_Bool BackendImpl::supportsService( const OUString& ServiceName ) +{ + return cppu::supportsService(this, ServiceName); +} + +css::uno::Sequence< OUString > BackendImpl::getSupportedServiceNames() +{ + return { BACKEND_SERVICE_NAME }; +} + +void BackendImpl::addDataToDb( + OUString const & url, ComponentBackendDb::Data const & data) +{ + if (m_backendDb) + m_backendDb->addEntry(url, data); +} + +ComponentBackendDb::Data BackendImpl::readDataFromDb(std::u16string_view url) +{ + ComponentBackendDb::Data data; + if (m_backendDb) + data = m_backendDb->getEntry(url); + return data; +} + +void BackendImpl::revokeEntryFromDb(std::u16string_view url) +{ + if (m_backendDb) + m_backendDb->revokeEntry(url); +} + +// XPackageRegistry + +Sequence< Reference<deployment::XPackageTypeInfo> > +BackendImpl::getSupportedPackageTypes() +{ + return m_typeInfos; +} + +void BackendImpl::packageRemoved(OUString const & url, OUString const & /*mediaType*/) +{ + if (m_backendDb) + m_backendDb->removeEntry(url); +} + +// PackageRegistryBackend + +Reference<deployment::XPackage> BackendImpl::bindPackage_( + OUString const & url, OUString const & mediaType_, + bool bRemoved, OUString const & identifier, + Reference<XCommandEnvironment> const & xCmdEnv ) +{ + OUString mediaType(mediaType_); + if ( mediaType.isEmpty() || mediaType == "application/vnd.sun.star.uno-component" || mediaType == "application/vnd.sun.star.uno-typelibrary" ) + { + // detect exact media-type: + ::ucbhelper::Content ucbContent; + if (create_ucb_content( &ucbContent, url, xCmdEnv )) { + const OUString title( StrTitle::getTitle( ucbContent ) ); + if (title.endsWithIgnoreAsciiCase(SAL_DLLEXTENSION)) + { + mediaType = "application/vnd.sun.star.uno-component;type=native;platform=" + + getPlatformString(); + } + else if (title.endsWithIgnoreAsciiCase(".jar")) + { + if (jarManifestHeaderPresent( + url, "RegistrationClassName", xCmdEnv )) + mediaType = "application/vnd.sun.star.uno-component;type=Java"; + if (mediaType.isEmpty()) + mediaType = "application/vnd.sun.star.uno-typelibrary;type=Java"; + } + else if (title.endsWithIgnoreAsciiCase(".py")) + mediaType = "application/vnd.sun.star.uno-component;type=Python"; + else if (title.endsWithIgnoreAsciiCase(".rdb")) + mediaType = "application/vnd.sun.star.uno-typelibrary;type=RDB"; + } + if (mediaType.isEmpty()) + throw lang::IllegalArgumentException( + StrCannotDetectMediaType() + url, + static_cast<OWeakObject *>(this), static_cast<sal_Int16>(-1) ); + } + + OUString type, subType; + INetContentTypeParameterList params; + if (INetContentTypes::parse( mediaType, type, subType, ¶ms )) + { + if (type.equalsIgnoreAsciiCase("application")) + { + OUString name; + if (!bRemoved) + { + ::ucbhelper::Content ucbContent( url, xCmdEnv, m_xComponentContext ); + name = StrTitle::getTitle( ucbContent ); + } + + if (subType.equalsIgnoreAsciiCase("vnd.sun.star.uno-component")) + { + // xxx todo: probe and evaluate component xml description + + auto const iter = params.find(OString("platform")); + bool bPlatformFits(iter == params.end()); + OUString aPlatform; + if (!bPlatformFits) // platform is specified, we have to check + { + aPlatform = iter->second.m_sValue; + bPlatformFits = platform_fits(aPlatform); + } + // If the package is being removed, do not care whether + // platform fits. We won't be using it anyway. + if (bPlatformFits || bRemoved) { + auto const iterType = params.find(OString("type")); + if (iterType != params.end()) + { + OUString const & value = iterType->second.m_sValue; + if (value.equalsIgnoreAsciiCase("native")) { + if (bPlatformFits) + return new BackendImpl::ComponentPackageImpl( + this, url, name, m_xDynComponentTypeInfo, + "com.sun.star.loader.SharedLibrary", + bRemoved, identifier); + else + return new BackendImpl::OtherPlatformPackageImpl( + this, url, name, m_xDynComponentTypeInfo, + bRemoved, identifier, aPlatform); + } + if (value.equalsIgnoreAsciiCase("Java")) { + return new BackendImpl::ComponentPackageImpl( + this, url, name, m_xJavaComponentTypeInfo, + "com.sun.star.loader.Java2", + bRemoved, identifier); + } + if (value.equalsIgnoreAsciiCase("Python")) { + return new BackendImpl::ComponentPackageImpl( + this, url, name, m_xPythonComponentTypeInfo, + "com.sun.star.loader.Python", + bRemoved, identifier); + } + } + } + } + else if (subType.equalsIgnoreAsciiCase("vnd.sun.star.uno-components")) + { + auto const iter = params.find(OString("platform")); + if (iter == params.end() || platform_fits(iter->second.m_sValue)) { + return new BackendImpl::ComponentsPackageImpl( + this, url, name, m_xComponentsTypeInfo, bRemoved, + identifier); + } + } + else if (subType.equalsIgnoreAsciiCase( "vnd.sun.star.uno-typelibrary")) + { + auto const iter = params.find(OString("type")); + if (iter != params.end()) { + OUString const & value = iter->second.m_sValue; + if (value.equalsIgnoreAsciiCase("RDB")) + { + return new BackendImpl::TypelibraryPackageImpl( + this, url, name, m_xRDBTypelibTypeInfo, + false /* rdb */, bRemoved, identifier); + } + if (value.equalsIgnoreAsciiCase("Java")) { + return new BackendImpl::TypelibraryPackageImpl( + this, url, name, m_xJavaTypelibTypeInfo, + true /* jar */, bRemoved, identifier); + } + } + } + } + } + throw lang::IllegalArgumentException( + StrUnsupportedMediaType() + mediaType, + static_cast<OWeakObject *>(this), + static_cast<sal_Int16>(-1) ); +} + + +void BackendImpl::unorc_verify_init( + Reference<XCommandEnvironment> const & xCmdEnv ) +{ + if (transientMode()) + return; + const ::osl::MutexGuard guard( m_aMutex ); + if ( m_unorc_inited) + return; + + // common rc: + ::ucbhelper::Content ucb_content; + if (create_ucb_content( + &ucb_content, + makeURL( getCachePath(), "unorc" ), + xCmdEnv, false /* no throw */ )) + { + OUString line; + if (readLine( &line, "UNO_JAVA_CLASSPATH=", ucb_content, + RTL_TEXTENCODING_UTF8 )) + { + sal_Int32 index = sizeof ("UNO_JAVA_CLASSPATH=") - 1; + do { + OUString token( o3tl::trim(o3tl::getToken(line, 0, ' ', index )) ); + if (!token.isEmpty()) + { + if (create_ucb_content( + nullptr, expandUnoRcTerm(token), xCmdEnv, + false /* no throw */ )) + { + //The jar file may not exist anymore if a shared or bundled + //extension was removed, but it can still be in the unorc + //After running XExtensionManager::synchronize, the unorc is + //cleaned up + m_jar_typelibs.push_back( token ); + } + } + } + while (index >= 0); + } + if (readLine( &line, "UNO_TYPES=", ucb_content, + RTL_TEXTENCODING_UTF8 )) { + sal_Int32 index = sizeof ("UNO_TYPES=") - 1; + do { + OUString token( o3tl::trim(o3tl::getToken(line, 0, ' ', index )) ); + if (!token.isEmpty()) + { + if (token[ 0 ] == '?') + token = token.copy( 1 ); + if (create_ucb_content( + nullptr, expandUnoRcTerm(token), xCmdEnv, + false /* no throw */ )) + { + //The RDB file may not exist anymore if a shared or bundled + //extension was removed, but it can still be in the unorc. + //After running XExtensionManager::synchronize, the unorc is + //cleaned up + m_rdb_typelibs.push_back( token ); + } + } + } + while (index >= 0); + } + if (readLine( &line, "UNO_SERVICES=", ucb_content, + RTL_TEXTENCODING_UTF8 )) + { + // The UNO_SERVICES line always has the BNF form + // "UNO_SERVICES=" + // ("?$ORIGIN/" <common-rdb>)? -- first + // "${$ORIGIN/${_OS}_${_ARCH}rc:UNO_SERVICES}"? -- second + // ("?" ("BUNDLED_EXTENSIONS" | -- third + // "UNO_SHARED_PACKAGES_CACHE" | "UNO_USER_PACKAGES_CACHE") + // ...)* + // so can unambiguously be split into its three parts: + int state = 1; + for (sal_Int32 i = RTL_CONSTASCII_LENGTH("UNO_SERVICES="); + i >= 0;) + { + OUString token(line.getToken(0, ' ', i)); + if (!token.isEmpty()) + { + if (state == 1 && token.match("?$ORIGIN/")) + { + m_commonRDB_orig = token.copy( + RTL_CONSTASCII_LENGTH("?$ORIGIN/")); + state = 2; + } + else if ( state <= 2 && token == "${$ORIGIN/${_OS}_${_ARCH}rc:UNO_SERVICES}" ) + { + state = 3; + } + else + { + if (token[0] == '?') + { + token = token.copy(1); + } + m_components.push_back(token); + state = 3; + } + } + } + } + + // native rc: + if (create_ucb_content( + &ucb_content, + makeURL( getCachePath(), getPlatformString() + "rc"), + xCmdEnv, false /* no throw */ )) { + if (readLine( &line, "UNO_SERVICES=", ucb_content, + RTL_TEXTENCODING_UTF8 )) { + m_nativeRDB_orig = line.copy( + sizeof ("UNO_SERVICES=?$ORIGIN/") - 1 ); + } + } + } + m_unorc_modified = false; + m_unorc_inited = true; +} + + +void BackendImpl::unorc_flush( Reference<XCommandEnvironment> const & xCmdEnv ) +{ + if (transientMode()) + return; + if (!m_unorc_inited || !m_unorc_modified) + return; + + OStringBuffer buf; + + buf.append("ORIGIN="); + OUString sOrigin = dp_misc::makeRcTerm(m_cachePath); + OString osOrigin = OUStringToOString(sOrigin, RTL_TEXTENCODING_UTF8); + buf.append(osOrigin); + buf.append(LF); + + if (! m_jar_typelibs.empty()) + { + auto iPos( m_jar_typelibs.cbegin() ); + auto const iEnd( m_jar_typelibs.cend() ); + buf.append( "UNO_JAVA_CLASSPATH=" ); + while (iPos != iEnd) { + // encoded ASCII file-urls: + const OString item( + OUStringToOString( *iPos, RTL_TEXTENCODING_ASCII_US ) ); + buf.append( item ); + ++iPos; + if (iPos != iEnd) + buf.append( ' ' ); + } + buf.append(LF); + } + if (! m_rdb_typelibs.empty()) + { + auto iPos( m_rdb_typelibs.cbegin() ); + auto const iEnd( m_rdb_typelibs.cend() ); + buf.append( "UNO_TYPES=" ); + while (iPos != iEnd) { + buf.append( '?' ); + // encoded ASCII file-urls: + const OString item( + OUStringToOString( *iPos, RTL_TEXTENCODING_ASCII_US ) ); + buf.append( item ); + ++iPos; + if (iPos != iEnd) + buf.append( ' ' ); + } + buf.append(LF); + } + + // If we duplicated the common or native rdb then we must use those urls + //otherwise we use those of the original files. That is, m_commonRDB_orig + //and m_nativeRDB_orig; + OUString sCommonRDB(m_commonRDB.isEmpty() ? m_commonRDB_orig : m_commonRDB ); + OUString sNativeRDB(m_nativeRDB.isEmpty() ? m_nativeRDB_orig : m_nativeRDB ); + + if (!sCommonRDB.isEmpty() || !sNativeRDB.isEmpty() || + !m_components.empty()) + { + buf.append( "UNO_SERVICES=" ); + bool space = false; + if (!sCommonRDB.isEmpty()) + { + buf.append( "?$ORIGIN/" ); + buf.append( OUStringToOString( + sCommonRDB, RTL_TEXTENCODING_ASCII_US ) ); + space = true; + } + if (!sNativeRDB.isEmpty()) + { + if (space) + { + buf.append(' '); + } + buf.append( "${$ORIGIN/${_OS}_${_ARCH}rc:UNO_SERVICES}" ); + space = true; + + // write native rc: + OString buf2 = + "ORIGIN=" + + osOrigin + + OStringChar(LF) + + "UNO_SERVICES=?$ORIGIN/" + + OUStringToOString( sNativeRDB, RTL_TEXTENCODING_ASCII_US ) + + OStringChar(LF); + + const Reference<io::XInputStream> xData( + ::xmlscript::createInputStream( + reinterpret_cast<sal_Int8 const *>(buf2.getStr()), + buf2.getLength() ) ); + ::ucbhelper::Content ucb_content( + makeURL( getCachePath(), getPlatformString() + "rc" ), + xCmdEnv, m_xComponentContext ); + ucb_content.writeStream( xData, true /* replace existing */ ); + } + for (auto const& component : m_components) + { + if (space) + { + buf.append(' '); + } + buf.append('?'); + buf.append(OUStringToOString(component, RTL_TEXTENCODING_UTF8)); + space = true; + } + buf.append(LF); + } + + // write unorc: + const Reference<io::XInputStream> xData( + ::xmlscript::createInputStream( + reinterpret_cast<sal_Int8 const *>(buf.getStr()), + buf.getLength() ) ); + ::ucbhelper::Content ucb_content( + makeURL( getCachePath(), "unorc" ), xCmdEnv, m_xComponentContext ); + ucb_content.writeStream( xData, true /* replace existing */ ); + + m_unorc_modified = false; +} + + +void BackendImpl::addToUnoRc( RcItem kind, OUString const & url_, + Reference<XCommandEnvironment> const & xCmdEnv ) +{ + const OUString rcterm( dp_misc::makeRcTerm(url_) ); + const ::osl::MutexGuard guard( m_aMutex ); + unorc_verify_init( xCmdEnv ); + std::deque<OUString> & rSet = getRcItemList(kind); + if (std::find( rSet.begin(), rSet.end(), rcterm ) == rSet.end()) { + rSet.push_front( rcterm ); // prepend to list, thus overriding + // write immediately: + m_unorc_modified = true; + unorc_flush( xCmdEnv ); + } +} + + +void BackendImpl::removeFromUnoRc( + RcItem kind, OUString const & url_, + Reference<XCommandEnvironment> const & xCmdEnv ) +{ + const OUString rcterm( dp_misc::makeRcTerm(url_) ); + const ::osl::MutexGuard guard( m_aMutex ); + unorc_verify_init( xCmdEnv ); + std::deque<OUString> & aRcItemList = getRcItemList(kind); + aRcItemList.erase(std::remove(aRcItemList.begin(), aRcItemList.end(), rcterm), aRcItemList.end()); + // write immediately: + m_unorc_modified = true; + unorc_flush( xCmdEnv ); +} + + +bool BackendImpl::hasInUnoRc( + RcItem kind, OUString const & url_ ) +{ + const OUString rcterm( dp_misc::makeRcTerm(url_) ); + const ::osl::MutexGuard guard( m_aMutex ); + std::deque<OUString> const & rSet = getRcItemList(kind); + return std::find( rSet.begin(), rSet.end(), rcterm ) != rSet.end(); +} + +css::uno::Reference< css::uno::XComponentContext > BackendImpl::getRootContext() + const +{ + css::uno::Reference< css::uno::XComponentContext > rootContext( + getComponentContext()->getValueByName("_root"), + css::uno::UNO_QUERY); + return rootContext.is() ? rootContext : getComponentContext(); +} + + +void BackendImpl::releaseObject( OUString const & id ) +{ + const ::osl::MutexGuard guard( m_aMutex ); + m_backendObjects.erase( id ); +} + + +Reference<XInterface> BackendImpl::getObject( OUString const & id ) +{ + const ::osl::MutexGuard guard( m_aMutex ); + const t_string2object::const_iterator iFind( m_backendObjects.find( id ) ); + if (iFind == m_backendObjects.end()) + return Reference<XInterface>(); + else + return iFind->second; +} + + +Reference<XInterface> BackendImpl::insertObject( + OUString const & id, Reference<XInterface> const & xObject ) +{ + const ::osl::MutexGuard guard( m_aMutex ); + const std::pair<t_string2object::iterator, bool> insertion( + m_backendObjects.emplace( id, xObject ) ); + return insertion.first->second; +} + + +Reference<XComponentContext> raise_uno_process( + Reference<XComponentContext> const & xContext, + ::rtl::Reference<AbortChannel> const & abortChannel ) +{ + OSL_ASSERT( xContext.is() ); + + OUString url( util::theMacroExpander::get(xContext)->expandMacros( "$URE_BIN_DIR/uno" ) ); + + const OUString connectStr = "uno:pipe,name=" + generateRandomPipeId() + ";urp;uno.ComponentContext"; + + // raise core UNO process to register/run a component, + // javavm service uses unorc next to executable to retrieve deployed + // jar typelibs + + std::vector<OUString> args{ +#if OSL_DEBUG_LEVEL == 0 + "--quiet", +#endif + "--singleaccept", + "-u", + connectStr, + // don't inherit from unorc: + "-env:INIFILENAME=" }; + + //now add the bootstrap variables which were supplied on the command line + std::vector<OUString> bootvars = getCmdBootstrapVariables(); + args.insert(args.end(), bootvars.begin(), bootvars.end()); + + oslProcess hProcess; + try { + hProcess = raiseProcess( + url, comphelper::containerToSequence(args) ); + } + catch (...) { + OUStringBuffer sMsg = "error starting process: " + url; + for(const auto& arg : args) + sMsg.append(" " + arg); + throw uno::RuntimeException(sMsg.makeStringAndClear()); + } + try { + return Reference<XComponentContext>( + resolveUnoURL( connectStr, xContext, abortChannel.get() ), + UNO_QUERY_THROW ); + } + catch (...) { + // try to terminate process: + if ( osl_terminateProcess( hProcess ) != osl_Process_E_None ) + { + OSL_ASSERT( false ); + } + throw; + } +} + +void extractComponentData( + css::uno::Reference< css::uno::XComponentContext > const & context, + css::uno::Reference< css::registry::XRegistryKey > const & registry, + ComponentBackendDb::Data * data, + std::vector< css::uno::Reference< css::uno::XInterface > > * factories, + css::uno::Reference< css::loader::XImplementationLoader > const & + componentLoader, + OUString const & componentUrl) +{ + OSL_ASSERT( + context.is() && registry.is() && data != nullptr && componentLoader.is()); + OUString registryName(registry->getKeyName()); + sal_Int32 prefix = registryName.getLength(); + if (!registryName.endsWith("/")) { + prefix += RTL_CONSTASCII_LENGTH("/"); + } + const css::uno::Sequence< css::uno::Reference< css::registry::XRegistryKey > > + keys(registry->openKeys()); + css::uno::Reference< css::lang::XMultiComponentFactory > smgr( + context->getServiceManager(), css::uno::UNO_SET_THROW); + for (css::uno::Reference< css::registry::XRegistryKey > const & key : keys) { + OUString name(key->getKeyName().copy(prefix)); + data->implementationNames.push_back(name); + css::uno::Reference< css::registry::XRegistryKey > singletons( + key->openKey("UNO/SINGLETONS")); + if (singletons.is()) { + sal_Int32 prefix2 = key->getKeyName().getLength() + + RTL_CONSTASCII_LENGTH("/UNO/SINGLETONS/"); + const css::uno::Sequence< + css::uno::Reference< css::registry::XRegistryKey > > + singletonKeys(singletons->openKeys()); + for (css::uno::Reference< css::registry::XRegistryKey > const & singletonKey : singletonKeys) { + data->singletons.emplace_back( + singletonKey->getKeyName().copy(prefix2), name); + } + } + if (factories != nullptr) { + factories->push_back( + componentLoader->activate( + name, OUString(), componentUrl, key)); + } + } +} + +void BackendImpl::ComponentPackageImpl::getComponentInfo( + ComponentBackendDb::Data * data, + std::vector< css::uno::Reference< css::uno::XInterface > > * factories, + Reference<XComponentContext> const & xContext ) +{ + const Reference<loader::XImplementationLoader> xLoader( + xContext->getServiceManager()->createInstanceWithContext( + m_loader, xContext ), UNO_QUERY ); + if (! xLoader.is()) + { + throw css::deployment::DeploymentException( + "cannot instantiate loader " + m_loader, + static_cast< OWeakObject * >(this), Any()); + } + + // HACK: highly dependent on stoc/source/servicemanager + // and stoc/source/implreg implementation which rely on the same + // services.rdb format! + // .../UNO/LOCATION and .../UNO/ACTIVATOR appear not to be written by + // writeRegistryInfo, however, but are known, fixed values here, so + // can be passed into extractComponentData + OUString url(getURL()); + const Reference<registry::XSimpleRegistry> xMemReg( + xContext->getServiceManager()->createInstanceWithContext( + "com.sun.star.registry.SimpleRegistry", xContext ), + UNO_QUERY_THROW ); + xMemReg->open( OUString() /* in mem */, false, true ); + xLoader->writeRegistryInfo( xMemReg->getRootKey(), OUString(), url ); + extractComponentData( + xContext, xMemReg->getRootKey(), data, factories, xLoader, url); +} + +void BackendImpl::ComponentPackageImpl::componentLiveInsertion( + ComponentBackendDb::Data const & data, + std::vector< css::uno::Reference< css::uno::XInterface > > const & + factories) +{ + css::uno::Reference< css::uno::XComponentContext > rootContext( + getMyBackend()->getRootContext()); + css::uno::Reference< css::container::XSet > set( + rootContext->getServiceManager(), css::uno::UNO_QUERY_THROW); + std::vector< css::uno::Reference< css::uno::XInterface > >::const_iterator + factory(factories.begin()); + for (auto const& implementationName : data.implementationNames) + { + try { + set->insert(css::uno::Any(*factory++)); + } catch (const container::ElementExistException &) { + SAL_WARN("desktop.deployment", "implementation already registered " << implementationName); + } + } + if (data.singletons.empty()) return; + + css::uno::Reference< css::container::XNameContainer > cont( + rootContext, css::uno::UNO_QUERY_THROW); + for (auto const& singleton : data.singletons) + { + OUString name("/singletons/" + singleton.first); + //TODO: Update should be atomic: + try { + cont->removeByName( name + "/arguments"); + } catch (const container::NoSuchElementException &) {} + try { + cont->insertByName( name + "/service", css::uno::Any(singleton.second)); + } catch (const container::ElementExistException &) { + cont->replaceByName( name + "/service", css::uno::Any(singleton.second)); + } + try { + cont->insertByName(name, css::uno::Any()); + } catch (const container::ElementExistException &) { + SAL_WARN("desktop.deployment", "singleton already registered " << singleton.first); + cont->replaceByName(name, css::uno::Any()); + } + } +} + +void BackendImpl::ComponentPackageImpl::componentLiveRemoval( + ComponentBackendDb::Data const & data) +{ + css::uno::Reference< css::uno::XComponentContext > rootContext( + getMyBackend()->getRootContext()); + css::uno::Reference< css::container::XSet > set( + rootContext->getServiceManager(), css::uno::UNO_QUERY_THROW); + for (auto const& implementationName : data.implementationNames) + { + try { + set->remove(css::uno::Any(implementationName)); + } catch (const css::container::NoSuchElementException &) { + // ignore if factory has not been live deployed + } + } + if (data.singletons.empty()) + return; + + css::uno::Reference< css::container::XNameContainer > cont( + rootContext, css::uno::UNO_QUERY_THROW); + for (auto const& singleton : data.singletons) + { + OUString name("/singletons/" + singleton.first); + //TODO: Removal should be atomic: + try { + cont->removeByName(name); + } catch (const container::NoSuchElementException &) {} + try { + cont->removeByName( name + "/service" ); + } catch (const container::NoSuchElementException &) {} + try { + cont->removeByName( name + "/arguments" ); + } catch (const container::NoSuchElementException &) {} + } +} + +// Package + +//We could use here BackendImpl::hasActiveEntry. However, this check is just as well. +//And it also shows the problem if another extension has overwritten an implementation +//entry, because it contains the same service implementation +beans::Optional< beans::Ambiguous<sal_Bool> > +BackendImpl::ComponentPackageImpl::isRegistered_( + ::osl::ResettableMutexGuard &, + ::rtl::Reference<AbortChannel> const & abortChannel, + Reference<XCommandEnvironment> const & ) +{ + if (m_registered == Reg::Uninit) + { + m_registered = Reg::NotRegistered; + const Reference<registry::XSimpleRegistry> xRDB( getRDB() ); + if (xRDB.is()) + { + bool bAmbiguousComponentName = false; + // lookup rdb for location URL: + const Reference<registry::XRegistryKey> xRootKey( + xRDB->getRootKey() ); + const Reference<registry::XRegistryKey> xImplKey( + xRootKey->openKey( "IMPLEMENTATIONS" ) ); + Sequence<OUString> implNames; + if (xImplKey.is() && xImplKey->isValid()) + implNames = xImplKey->getKeyNames(); + OUString const * pImplNames = implNames.getConstArray(); + sal_Int32 pos = implNames.getLength(); + for ( ; pos--; ) + { + checkAborted( abortChannel ); + const OUString key( + pImplNames[ pos ] + "/UNO/LOCATION" ); + const Reference<registry::XRegistryKey> xKey( + xRootKey->openKey(key) ); + if (xKey.is() && xKey->isValid()) + { + const OUString location( xKey->getAsciiValue() ); + if (location.equalsIgnoreAsciiCase( getURL() )) + { + break; + } + else + { + //try to match only the file name + OUString thisUrl(getURL()); + std::u16string_view thisFileName(thisUrl.subView(thisUrl.lastIndexOf('/'))); + + std::u16string_view locationFileName(location.subView(location.lastIndexOf('/'))); + if (o3tl::equalsIgnoreAsciiCase(locationFileName, thisFileName)) + bAmbiguousComponentName = true; + } + } + } + if (pos >= 0) + m_registered = Reg::Registered; + else if (bAmbiguousComponentName) + m_registered = Reg::MaybeRegistered; + } + } + + //Different extensions can use the same service implementations. Then the extensions + //which was installed last will overwrite the one from the other extension. That is + //the registry will contain the path (the location) of the library or jar of the + //second extension. In this case isRegistered called for the lib of the first extension + //would return "not registered". That would mean that during uninstallation + //XPackage::registerPackage is not called, because it just was not registered. This is, + //however, necessary for jar files. Registering and unregistering update + //uno_packages/cache/registry/com.sun.star.comp.deployment.component.PackageRegistryBackend/unorc + //Therefore, we will return always "is ambiguous" if the path of this component cannot + //be found in the registry and if there is another path and both have the same file name (but + //the rest of the path is different). + //If the caller cannot precisely determine that this package was registered, then it must + //call registerPackage. + bool bAmbiguous = m_registered == Reg::Void // Reg::Void == we are in the progress of unregistration + || m_registered == Reg::MaybeRegistered; + return beans::Optional< beans::Ambiguous<sal_Bool> >( + true /* IsPresent */, + beans::Ambiguous<sal_Bool>( + m_registered == Reg::Registered, bAmbiguous) ); +} + + +void BackendImpl::ComponentPackageImpl::processPackage_( + ::osl::ResettableMutexGuard &, + bool doRegisterPackage, + bool startup, + ::rtl::Reference<AbortChannel> const & abortChannel, + Reference<XCommandEnvironment> const & xCmdEnv ) +{ + BackendImpl * that = getMyBackend(); + OUString url(getURL()); + if (doRegisterPackage) { + ComponentBackendDb::Data data; + css::uno::Reference< css::uno::XComponentContext > context; + if (startup) { + context = that->getComponentContext(); + } else { + context.set(that->getObject(url), css::uno::UNO_QUERY); + if (!context.is()) { + context.set( + that->insertObject( + url, + raise_uno_process( + that->getComponentContext(), abortChannel)), + css::uno::UNO_QUERY_THROW); + } + } + css::uno::Reference< css::registry::XImplementationRegistration> impreg( + context->getServiceManager()->createInstanceWithContext( + "com.sun.star.registry.ImplementationRegistration", + context), + css::uno::UNO_QUERY_THROW); + css::uno::Reference< css::registry::XSimpleRegistry > rdb(getRDB()); + impreg->registerImplementation(m_loader, url, rdb); + // Only write to unorc after successful registration; it may fail if + // there is no suitable java + if (m_loader == "com.sun.star.loader.Java2" && !jarManifestHeaderPresent(url, "UNO-Type-Path", xCmdEnv)) + { + that->addToUnoRc(RCITEM_JAR_TYPELIB, url, xCmdEnv); + data.javaTypeLibrary = true; + } + std::vector< css::uno::Reference< css::uno::XInterface > > factories; + getComponentInfo(&data, startup ? nullptr : &factories, context); + if (!startup) { + try { + componentLiveInsertion(data, factories); + } catch (css::uno::Exception &) { + TOOLS_INFO_EXCEPTION("desktop.deployment", "caught"); + try { + impreg->revokeImplementation(url, rdb); + } catch (css::uno::RuntimeException &) { + TOOLS_WARN_EXCEPTION("desktop.deployment", "ignored"); + } + throw; + } + } + m_registered = Reg::Registered; + that->addDataToDb(url, data); + } else { // revoke + m_registered = Reg::Void; + ComponentBackendDb::Data data(that->readDataFromDb(url)); + css::uno::Reference< css::uno::XComponentContext > context( + that->getObject(url), css::uno::UNO_QUERY); + bool remoteContext = context.is(); + if (!remoteContext) { + context = that->getComponentContext(); + } + if (!startup) { + componentLiveRemoval(data); + } + css::uno::Reference< css::registry::XImplementationRegistration >( + context->getServiceManager()->createInstanceWithContext( + "com.sun.star.registry.ImplementationRegistration", + context), + css::uno::UNO_QUERY_THROW)->revokeImplementation(url, getRDB()); + if (data.javaTypeLibrary) { + that->removeFromUnoRc(RCITEM_JAR_TYPELIB, url, xCmdEnv); + } + if (remoteContext) { + that->releaseObject(url); + } + m_registered = Reg::NotRegistered; + getMyBackend()->revokeEntryFromDb(url); + } +} + +BackendImpl::TypelibraryPackageImpl::TypelibraryPackageImpl( + ::rtl::Reference<PackageRegistryBackend> const & myBackend, + OUString const & url, OUString const & name, + Reference<deployment::XPackageTypeInfo> const & xPackageType, + bool jarFile, bool bRemoved, OUString const & identifier) + : Package( myBackend, url, name, name /* display-name */, + xPackageType, bRemoved, identifier), + m_jarFile( jarFile ) +{ +} + +// Package +BackendImpl * BackendImpl::TypelibraryPackageImpl::getMyBackend() const +{ + BackendImpl * pBackend = static_cast<BackendImpl *>(m_myBackend.get()); + if (nullptr == pBackend) + { + //May throw a DisposedException + check(); + //We should never get here... + throw RuntimeException( "Failed to get the BackendImpl", + static_cast<OWeakObject*>(const_cast<TypelibraryPackageImpl *>(this))); + } + return pBackend; +} + +beans::Optional< beans::Ambiguous<sal_Bool> > +BackendImpl::TypelibraryPackageImpl::isRegistered_( + ::osl::ResettableMutexGuard &, + ::rtl::Reference<AbortChannel> const &, + Reference<XCommandEnvironment> const & ) +{ + BackendImpl * that = getMyBackend(); + return beans::Optional< beans::Ambiguous<sal_Bool> >( + true /* IsPresent */, + beans::Ambiguous<sal_Bool>( + that->hasInUnoRc( + m_jarFile ? RCITEM_JAR_TYPELIB : RCITEM_RDB_TYPELIB, getURL() ), + false /* IsAmbiguous */ ) ); +} + + +void BackendImpl::TypelibraryPackageImpl::processPackage_( + ::osl::ResettableMutexGuard &, + bool doRegisterPackage, + bool /*startup*/, + ::rtl::Reference<AbortChannel> const &, + Reference<XCommandEnvironment> const & xCmdEnv ) +{ + BackendImpl * that = getMyBackend(); + const OUString url( getURL() ); + + if (doRegisterPackage) + { + // live insertion: + if (m_jarFile) { + // xxx todo add to classpath at runtime: ??? + //SB: It is probably not worth it to add the live inserted type + // library JAR to the UnoClassLoader in the soffice process. Any + // live inserted component JAR that might reference this type + // library JAR runs in its own uno process, so there is probably no + // Java code in the soffice process that would see any UNO types + // introduced by this type library JAR. + } + else // RDB: + { + css::uno::Reference< css::container::XSet >( + that->getComponentContext()->getValueByName( + "/singletons" + "/com.sun.star.reflection.theTypeDescriptionManager"), + css::uno::UNO_QUERY_THROW)->insert( + css::uno::Any(expandUnoRcUrl(url))); + } + + that->addToUnoRc( m_jarFile ? RCITEM_JAR_TYPELIB : RCITEM_RDB_TYPELIB, + url, xCmdEnv ); + } + else // revokePackage() + { + that->removeFromUnoRc( + m_jarFile ? RCITEM_JAR_TYPELIB : RCITEM_RDB_TYPELIB, url, xCmdEnv ); + + // revoking types at runtime, possible, sensible? + if (!m_jarFile) { + css::uno::Reference< css::container::XSet >( + that->getComponentContext()->getValueByName( + "/singletons" + "/com.sun.star.reflection.theTypeDescriptionManager"), + css::uno::UNO_QUERY_THROW)->remove( + css::uno::Any(expandUnoRcUrl(url))); + } + } +} + +BackendImpl::OtherPlatformPackageImpl::OtherPlatformPackageImpl( + ::rtl::Reference<PackageRegistryBackend> const & myBackend, + OUString const & url, OUString const & name, + Reference<deployment::XPackageTypeInfo> const & xPackageType, + bool bRemoved, OUString const & identifier, OUString platform) + : Package(myBackend, url, name, name, xPackageType, bRemoved, identifier) + , m_aPlatform(std::move(platform)) +{ + OSL_PRECOND(bRemoved, "this class can only be used for removing packages!"); +} + +BackendImpl * +BackendImpl::OtherPlatformPackageImpl::getMyBackend() const +{ + BackendImpl * pBackend = static_cast<BackendImpl *>(m_myBackend.get()); + if (nullptr == pBackend) + { + //Throws a DisposedException + check(); + //We should never get here... + throw RuntimeException("Failed to get the BackendImpl", + static_cast<OWeakObject*>(const_cast<OtherPlatformPackageImpl*>(this))); + } + return pBackend; +} + +Reference<registry::XSimpleRegistry> +BackendImpl::OtherPlatformPackageImpl::impl_openRDB() const +{ + OUString const aRDB(m_aPlatform + ".rdb"); + OUString const aRDBPath(makeURL(getMyBackend()->getCachePath(), aRDB)); + + Reference<registry::XSimpleRegistry> xRegistry; + + try + { + xRegistry.set( + impl_createInstance("com.sun.star.registry.SimpleRegistry"), + UNO_QUERY) + ; + if (xRegistry.is()) + xRegistry->open(expandUnoRcUrl(aRDBPath), false, false); + } + catch (registry::InvalidRegistryException const&) + { + // If the registry does not exist, we do not need to bother at all + xRegistry.set(nullptr); + } + + SAL_WARN_IF( !xRegistry.is(), "desktop.deployment", "could not create registry for the package's platform"); + return xRegistry; +} + +Reference<XInterface> +BackendImpl::OtherPlatformPackageImpl::impl_createInstance(OUString const& rService) +const +{ + Reference<XComponentContext> const xContext(getMyBackend()->getComponentContext()); + OSL_ASSERT(xContext.is()); + Reference<XInterface> xService; + if (xContext.is()) + xService.set(xContext->getServiceManager()->createInstanceWithContext(rService, xContext)); + return xService; +} + +beans::Optional<beans::Ambiguous<sal_Bool> > +BackendImpl::OtherPlatformPackageImpl::isRegistered_( + ::osl::ResettableMutexGuard& /* guard */, + ::rtl::Reference<AbortChannel> const& /* abortChannel */, + Reference<XCommandEnvironment> const& /* xCmdEnv */ ) +{ + return beans::Optional<beans::Ambiguous<sal_Bool> >(true, + beans::Ambiguous<sal_Bool>(true, false)); +} + +void +BackendImpl::OtherPlatformPackageImpl::processPackage_( + ::osl::ResettableMutexGuard& /* guard */, + bool bRegisterPackage, + bool /* bStartup */, + ::rtl::Reference<AbortChannel> const& /* abortChannel */, + Reference<XCommandEnvironment> const& /* xCmdEnv */) +{ + OSL_PRECOND(!bRegisterPackage, "this class can only be used for removing packages!"); + + OUString const aURL(getURL()); + + Reference<registry::XSimpleRegistry> const xServicesRDB(impl_openRDB()); + Reference<registry::XImplementationRegistration> const xImplReg( + impl_createInstance("com.sun.star.registry.ImplementationRegistration"), + UNO_QUERY) + ; + if (xImplReg.is() && xServicesRDB.is()) + xImplReg->revokeImplementation(aURL, xServicesRDB); + if (xServicesRDB.is()) + xServicesRDB->close(); + + getMyBackend()->revokeEntryFromDb(aURL); +} + +BackendImpl * BackendImpl::ComponentsPackageImpl::getMyBackend() const +{ + BackendImpl * pBackend = static_cast<BackendImpl *>(m_myBackend.get()); + if (nullptr == pBackend) + { + //Throws a DisposedException + check(); + //We should never get here... + throw RuntimeException("Failed to get the BackendImpl", + static_cast<OWeakObject*>(const_cast<ComponentsPackageImpl *>(this))); + } + return pBackend; +} + +beans::Optional< beans::Ambiguous<sal_Bool> > +BackendImpl::ComponentsPackageImpl::isRegistered_( + ::osl::ResettableMutexGuard &, + ::rtl::Reference<AbortChannel> const &, + Reference<XCommandEnvironment> const & ) +{ + return beans::Optional< beans::Ambiguous<sal_Bool> >( + true, + beans::Ambiguous<sal_Bool>( + getMyBackend()->hasInUnoRc(RCITEM_COMPONENTS, getURL()), false)); +} + +void BackendImpl::ComponentsPackageImpl::processPackage_( + ::osl::ResettableMutexGuard &, + bool doRegisterPackage, + bool startup, + ::rtl::Reference<AbortChannel> const & abortChannel, + Reference<XCommandEnvironment> const & xCmdEnv ) +{ + BackendImpl * that = getMyBackend(); + OUString url(getURL()); + if (doRegisterPackage) { + if (!startup) { + css::uno::Reference< css::uno::XComponentContext > context( + that->getObject(url), css::uno::UNO_QUERY); + if (!context.is()) { + context.set( + that->insertObject( + url, + raise_uno_process( + that->getComponentContext(), abortChannel)), + css::uno::UNO_QUERY_THROW); + } + // This relies on the root component context's service manager + // supporting the extended XSet semantics: + css::uno::Sequence< css::beans::NamedValue > args + { + { "uri", css::uno::Any(expandUnoRcUrl(url)) }, + { "component-context", css::uno::Any(context) } + }; + css::uno::Reference< css::container::XSet > smgr( + that->getRootContext()->getServiceManager(), + css::uno::UNO_QUERY_THROW); + smgr->insert(css::uno::Any(args)); + } + that->addToUnoRc(RCITEM_COMPONENTS, url, xCmdEnv); + } else { // revoke + that->removeFromUnoRc(RCITEM_COMPONENTS, url, xCmdEnv); + if (!startup) { + // This relies on the root component context's service manager + // supporting the extended XSet semantics: + css::uno::Sequence< css::beans::NamedValue > args { { "uri", css::uno::Any(expandUnoRcUrl(url)) } }; + css::uno::Reference< css::container::XSet > smgr( + that->getRootContext()->getServiceManager(), + css::uno::UNO_QUERY_THROW); + smgr->remove(css::uno::Any(args)); + } + that->releaseObject(url); + that->revokeEntryFromDb(url); // in case it got added with old code + } +} + +BackendImpl::ComponentsPackageImpl::ComponentsPackageImpl( + ::rtl::Reference<PackageRegistryBackend> const & myBackend, + OUString const & url, OUString const & name, + Reference<deployment::XPackageTypeInfo> const & xPackageType, + bool bRemoved, OUString const & identifier) + : Package( myBackend, url, name, name /* display-name */, + xPackageType, bRemoved, identifier) +{} + +} // anon namespace + +} // namespace dp_registry + + +extern "C" SAL_DLLPUBLIC_EXPORT css::uno::XInterface* +com_sun_star_comp_deployment_component_PackageRegistryBackend_get_implementation( + css::uno::XComponentContext* context, css::uno::Sequence<css::uno::Any> const& args) +{ + return cppu::acquire(new dp_registry::backend::component::BackendImpl(args, context)); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/desktop/source/deployment/registry/configuration/dp_configuration.cxx b/desktop/source/deployment/registry/configuration/dp_configuration.cxx new file mode 100644 index 000000000..62d5ab88f --- /dev/null +++ b/desktop/source/deployment/registry/configuration/dp_configuration.cxx @@ -0,0 +1,818 @@ +/* -*- 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 . + */ + +//TODO: Large parts of this file were copied from dp_component.cxx; those parts +// should be consolidated. + +#include <config_extensions.h> + +#include <dp_backend.h> +#if HAVE_FEATURE_EXTENSIONS +#include <dp_persmap.h> +#endif +#include <dp_misc.h> +#include <dp_ucb.h> +#include <rtl/string.hxx> +#include <rtl/strbuf.hxx> +#include <rtl/ustrbuf.hxx> +#include <cppuhelper/exc_hlp.hxx> +#include <cppuhelper/supportsservice.hxx> +#include <ucbhelper/content.hxx> +#include <unotools/ucbhelper.hxx> +#include <xmlscript/xml_helper.hxx> +#include <comphelper/lok.hxx> +#include <svl/inettype.hxx> +#include <o3tl/string_view.hxx> +#include <com/sun/star/configuration/Update.hpp> +#include <com/sun/star/lang/IllegalArgumentException.hpp> +#include <com/sun/star/lang/WrappedTargetRuntimeException.hpp> +#include <deque> +#include <memory> +#include <string_view> +#include <utility> + +#include "dp_configurationbackenddb.hxx" + +using namespace ::dp_misc; +using namespace ::com::sun::star; +using namespace ::com::sun::star::uno; +using namespace ::com::sun::star::ucb; + +namespace dp_registry::backend::configuration { +namespace { + +class BackendImpl : public ::dp_registry::backend::PackageRegistryBackend +{ + class PackageImpl : public ::dp_registry::backend::Package + { + BackendImpl * getMyBackend() const ; + + const bool m_isSchema; + + // Package + virtual beans::Optional< beans::Ambiguous<sal_Bool> > isRegistered_( + ::osl::ResettableMutexGuard & guard, + ::rtl::Reference<AbortChannel> const & abortChannel, + Reference<XCommandEnvironment> const & xCmdEnv ) override; + virtual void processPackage_( + ::osl::ResettableMutexGuard & guard, + bool registerPackage, + bool startup, + ::rtl::Reference<AbortChannel> const & abortChannel, + Reference<XCommandEnvironment> const & xCmdEnv ) override; + + public: + PackageImpl( + ::rtl::Reference<PackageRegistryBackend> const & myBackend, + OUString const & url, OUString const & name, + Reference<deployment::XPackageTypeInfo> const & xPackageType, + bool isSchema, bool bRemoved, OUString const & identifier) + : Package( myBackend, url, name, name /* display-name */, + xPackageType, bRemoved, identifier), + m_isSchema( isSchema ) + {} + }; + friend class PackageImpl; + + std::deque<OUString> m_xcs_files; + std::deque<OUString> m_xcu_files; + std::deque<OUString> & getFiles( bool xcs ) { + return xcs ? m_xcs_files : m_xcu_files; + } + + bool m_configmgrini_inited; + bool m_configmgrini_modified; + std::unique_ptr<ConfigurationBackendDb> m_backendDb; + + // PackageRegistryBackend + virtual Reference<deployment::XPackage> bindPackage_( + OUString const & url, OUString const & mediaType, bool bRemoved, + OUString const & identifier, + Reference<XCommandEnvironment> const & xCmdEnv ) override; + +#if HAVE_FEATURE_EXTENSIONS + // for backwards compatibility - nil if no (compatible) back-compat db present + std::unique_ptr<PersistentMap> m_registeredPackages; +#endif + + virtual void SAL_CALL disposing() override; + + const Reference<deployment::XPackageTypeInfo> m_xConfDataTypeInfo; + const Reference<deployment::XPackageTypeInfo> m_xConfSchemaTypeInfo; + Sequence< Reference<deployment::XPackageTypeInfo> > m_typeInfos; + + void configmgrini_verify_init( + Reference<XCommandEnvironment> const & xCmdEnv ); + void configmgrini_flush( Reference<XCommandEnvironment> const & xCmdEnv ); + + /* The parameter isURL is false in the case of adding the conf:ini-entry + value from the backend db. This entry already contains the path as it + is used in the configmgr.ini. + */ + void addToConfigmgrIni( bool isSchema, bool isURL, OUString const & url, + Reference<XCommandEnvironment> const & xCmdEnv ); +#if HAVE_FEATURE_EXTENSIONS + bool removeFromConfigmgrIni( bool isSchema, OUString const & url, + Reference<XCommandEnvironment> const & xCmdEnv ); +#endif + void addDataToDb(OUString const & url, ConfigurationBackendDb::Data const & data); + ::std::optional<ConfigurationBackendDb::Data> readDataFromDb(std::u16string_view url); + void revokeEntryFromDb(std::u16string_view url); + bool hasActiveEntry(std::u16string_view url); + bool activateEntry(std::u16string_view url); + +public: + BackendImpl( Sequence<Any> const & args, + 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; + + // XPackageRegistry + virtual Sequence< Reference<deployment::XPackageTypeInfo> > SAL_CALL + getSupportedPackageTypes() override; + virtual void SAL_CALL packageRemoved(OUString const & url, OUString const & mediaType) override; + + using PackageRegistryBackend::disposing; +}; + + +void BackendImpl::disposing() +{ + try { + configmgrini_flush( Reference<XCommandEnvironment>() ); + + PackageRegistryBackend::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 ); + } +} + + +BackendImpl::BackendImpl( + Sequence<Any> const & args, + Reference<XComponentContext> const & xComponentContext ) + : PackageRegistryBackend( args, xComponentContext ), + m_configmgrini_inited( false ), + m_configmgrini_modified( false ), + m_xConfDataTypeInfo( new Package::TypeInfo( + "application/vnd.sun.star.configuration-data", + "*.xcu", + DpResId(RID_STR_CONF_DATA) + ) ), + m_xConfSchemaTypeInfo( new Package::TypeInfo( + "application/vnd.sun.star.configuration-schema", + "*.xcs", + DpResId(RID_STR_CONF_SCHEMA) + ) ), + m_typeInfos{ m_xConfDataTypeInfo, m_xConfSchemaTypeInfo } +{ + const Reference<XCommandEnvironment> xCmdEnv; + + if (transientMode()) + { + // TODO + } + else + { + OUString dbFile = makeURL(getCachePath(), "backenddb.xml"); + m_backendDb.reset( + new ConfigurationBackendDb(getComponentContext(), dbFile)); + //clean up data folders which are no longer used. + //This must not be done in the same process where the help files + //are still registers. Only after revoking and restarting OOo the folders + //can be removed. This works now, because the extension manager is a singleton + //and the backends are only create once per process. + std::vector<OUString> folders = m_backendDb->getAllDataUrls(); + deleteUnusedFolders(folders); + + configmgrini_verify_init( xCmdEnv ); + +#if HAVE_FEATURE_EXTENSIONS + std::unique_ptr<PersistentMap> pMap; + OUString aCompatURL( makeURL( getCachePath(), "registered_packages.pmap" ) ); + + // Don't create it if it doesn't exist already + if ( ::utl::UCBContentHelper::Exists( expandUnoRcUrl( aCompatURL ) ) ) + { + try + { + pMap.reset( new PersistentMap( aCompatURL ) ); + } + catch (const Exception &e) + { + OUString aStr = "Exception loading legacy package database: '" + + e.Message + + "' - ignoring file, please remove it.\n"; + dp_misc::writeConsole( aStr ); + } + } + m_registeredPackages = std::move(pMap); +#endif + } +} + +// XServiceInfo +OUString BackendImpl::getImplementationName() +{ + return "com.sun.star.comp.deployment.configuration.PackageRegistryBackend"; +} + +sal_Bool BackendImpl::supportsService( const OUString& ServiceName ) +{ + return cppu::supportsService(this, ServiceName); +} + +css::uno::Sequence< OUString > BackendImpl::getSupportedServiceNames() +{ + return { BACKEND_SERVICE_NAME }; +} + +void BackendImpl::addDataToDb( + OUString const & url, ConfigurationBackendDb::Data const & data) +{ + if (m_backendDb) + m_backendDb->addEntry(url, data); +} + +::std::optional<ConfigurationBackendDb::Data> BackendImpl::readDataFromDb( + std::u16string_view url) +{ + ::std::optional<ConfigurationBackendDb::Data> data; + if (m_backendDb) + data = m_backendDb->getEntry(url); + return data; +} + +void BackendImpl::revokeEntryFromDb(std::u16string_view url) +{ + if (m_backendDb) + m_backendDb->revokeEntry(url); +} + +bool BackendImpl::hasActiveEntry(std::u16string_view url) +{ + if (m_backendDb) + return m_backendDb->hasActiveEntry(url); + return false; +} + +bool BackendImpl::activateEntry(std::u16string_view url) +{ + if (m_backendDb) + return m_backendDb->activateEntry(url); + return false; +} + + +// XPackageRegistry + +Sequence< Reference<deployment::XPackageTypeInfo> > +BackendImpl::getSupportedPackageTypes() +{ + return m_typeInfos; +} +void BackendImpl::packageRemoved(OUString const & url, OUString const & /*mediaType*/) +{ + if (m_backendDb) + m_backendDb->removeEntry(url); +} + +// PackageRegistryBackend + +Reference<deployment::XPackage> BackendImpl::bindPackage_( + OUString const & url, OUString const & mediaType_, + bool bRemoved, OUString const & identifier, + Reference<XCommandEnvironment> const & xCmdEnv ) +{ + OUString mediaType( mediaType_ ); + if (mediaType.isEmpty()) + { + // detect media-type: + ::ucbhelper::Content ucbContent; + if (create_ucb_content( &ucbContent, url, xCmdEnv )) + { + const OUString title( StrTitle::getTitle( ucbContent ) ); + if (title.endsWithIgnoreAsciiCase( ".xcu" )) { + mediaType = "application/vnd.sun.star.configuration-data"; + } + if (title.endsWithIgnoreAsciiCase( ".xcs" )) { + mediaType = "application/vnd.sun.star.configuration-schema"; + } + } + if (mediaType.isEmpty()) + throw lang::IllegalArgumentException( + StrCannotDetectMediaType() + url, + static_cast<OWeakObject *>(this), static_cast<sal_Int16>(-1) ); + } + + OUString type, subType; + INetContentTypeParameterList params; + if (INetContentTypes::parse( mediaType, type, subType, ¶ms )) + { + if (type.equalsIgnoreAsciiCase("application")) + { + OUString name; + if (!bRemoved) + { + ::ucbhelper::Content ucbContent( url, xCmdEnv, m_xComponentContext ); + name = StrTitle::getTitle( ucbContent ); + } + + if (subType.equalsIgnoreAsciiCase( "vnd.sun.star.configuration-data")) + { + return new PackageImpl( + this, url, name, m_xConfDataTypeInfo, false /* data file */, + bRemoved, identifier); + } + else if (subType.equalsIgnoreAsciiCase( "vnd.sun.star.configuration-schema")) { + return new PackageImpl( + this, url, name, m_xConfSchemaTypeInfo, true /* schema file */, + bRemoved, identifier); + } + } + } + throw lang::IllegalArgumentException( + StrUnsupportedMediaType() + mediaType, + static_cast<OWeakObject *>(this), + static_cast<sal_Int16>(-1) ); +} + + +void BackendImpl::configmgrini_verify_init( + Reference<XCommandEnvironment> const & xCmdEnv ) +{ + if (transientMode()) + return; + const ::osl::MutexGuard guard( m_aMutex ); + if ( m_configmgrini_inited) + return; + + // common rc: + ::ucbhelper::Content ucb_content; + if (create_ucb_content( + &ucb_content, + makeURL( getCachePath(), "configmgr.ini" ), + xCmdEnv, false /* no throw */ )) + { + OUString line; + if (readLine( &line, "SCHEMA=", ucb_content, + RTL_TEXTENCODING_UTF8 )) + { + sal_Int32 index = RTL_CONSTASCII_LENGTH("SCHEMA="); + do { + OUString token( o3tl::trim(o3tl::getToken(line, 0, ' ', index )) ); + if (!token.isEmpty()) { + //The file may not exist anymore if a shared or bundled + //extension was removed, but it can still be in the configmgrini. + //After running XExtensionManager::synchronize, the configmgrini is + //cleaned up + m_xcs_files.push_back( token ); + } + } + while (index >= 0); + } + if (readLine( &line, "DATA=", ucb_content, + RTL_TEXTENCODING_UTF8 )) { + sal_Int32 index = RTL_CONSTASCII_LENGTH("DATA="); + do { + std::u16string_view token( o3tl::trim(o3tl::getToken(line, 0, ' ', index )) ); + if (!token.empty()) + { + if (token[ 0 ] == '?') + token = token.substr( 1 ); + //The file may not exist anymore if a shared or bundled + //extension was removed, but it can still be in the configmgrini. + //After running XExtensionManager::synchronize, the configmgrini is + //cleaned up + m_xcu_files.push_back( OUString(token) ); + } + } + while (index >= 0); + } + } + m_configmgrini_modified = false; + m_configmgrini_inited = true; +} + + +void BackendImpl::configmgrini_flush( + Reference<XCommandEnvironment> const & xCmdEnv ) +{ + if (transientMode()) + return; + if (!m_configmgrini_inited || !m_configmgrini_modified) + return; + + OStringBuffer buf; + if (! m_xcs_files.empty()) + { + auto iPos( m_xcs_files.cbegin() ); + auto const iEnd( m_xcs_files.cend() ); + buf.append( "SCHEMA=" ); + while (iPos != iEnd) { + // encoded ASCII file-urls: + const OString item( + OUStringToOString( *iPos, RTL_TEXTENCODING_ASCII_US ) ); + buf.append( item ); + ++iPos; + if (iPos != iEnd) + buf.append( ' ' ); + } + buf.append(LF); + } + if (! m_xcu_files.empty()) + { + auto iPos( m_xcu_files.cbegin() ); + auto const iEnd( m_xcu_files.cend() ); + buf.append( "DATA=" ); + while (iPos != iEnd) { + // encoded ASCII file-urls: + const OString item( + OUStringToOString( *iPos, RTL_TEXTENCODING_ASCII_US ) ); + buf.append( item ); + ++iPos; + if (iPos != iEnd) + buf.append( ' ' ); + } + buf.append(LF); + } + + // write configmgr.ini: + const Reference<io::XInputStream> xData( + ::xmlscript::createInputStream( + reinterpret_cast<sal_Int8 const *>(buf.getStr()), + buf.getLength() ) ); + ::ucbhelper::Content ucb_content( + makeURL( getCachePath(), "configmgr.ini" ), xCmdEnv, m_xComponentContext ); + ucb_content.writeStream( xData, true /* replace existing */ ); + + m_configmgrini_modified = false; +} + + +void BackendImpl::addToConfigmgrIni( bool isSchema, bool isURL, OUString const & url_, + Reference<XCommandEnvironment> const & xCmdEnv ) +{ + const OUString rcterm( isURL ? dp_misc::makeRcTerm(url_) : url_ ); + const ::osl::MutexGuard guard( m_aMutex ); + configmgrini_verify_init( xCmdEnv ); + std::deque<OUString> & rSet = getFiles(isSchema); + if (std::find( rSet.begin(), rSet.end(), rcterm ) == rSet.end()) { + rSet.push_front( rcterm ); // prepend to list, thus overriding + // write immediately: + m_configmgrini_modified = true; + configmgrini_flush( xCmdEnv ); + } +} + +#if HAVE_FEATURE_EXTENSIONS +bool BackendImpl::removeFromConfigmgrIni( + bool isSchema, OUString const & url_, + Reference<XCommandEnvironment> const & xCmdEnv ) +{ + const OUString rcterm( dp_misc::makeRcTerm(url_) ); + const ::osl::MutexGuard guard( m_aMutex ); + configmgrini_verify_init( xCmdEnv ); + std::deque<OUString> & rSet = getFiles(isSchema); + auto i(std::find(rSet.begin(), rSet.end(), rcterm)); + if (i == rSet.end() && !isSchema) + { + //in case the xcu contained %origin% then the configmr.ini contains the + //url to the file in the user installation (e.g. $BUNDLED_EXTENSIONS_USER) + //However, m_url (getURL()) contains the URL for the file in the actual + //extension installation. + ::std::optional<ConfigurationBackendDb::Data> data = readDataFromDb(url_); + if (data) + i = std::find(rSet.begin(), rSet.end(), data->iniEntry); + } + if (i == rSet.end()) { + return false; + } + rSet.erase(i); + // write immediately: + m_configmgrini_modified = true; + configmgrini_flush( xCmdEnv ); + return true; +} +#endif + +// Package + + +BackendImpl * BackendImpl::PackageImpl::getMyBackend() const +{ + BackendImpl * pBackend = static_cast<BackendImpl *>(m_myBackend.get()); + if (nullptr == pBackend) + { + //May throw a DisposedException + check(); + //We should never get here... + throw RuntimeException( + "Failed to get the BackendImpl", + static_cast<OWeakObject*>(const_cast<PackageImpl *>(this))); + } + return pBackend; +} + +beans::Optional< beans::Ambiguous<sal_Bool> > +BackendImpl::PackageImpl::isRegistered_( + ::osl::ResettableMutexGuard &, + ::rtl::Reference<AbortChannel> const &, + Reference<XCommandEnvironment> const & ) +{ + BackendImpl * that = getMyBackend(); + + bool bReg = false; + if (that->hasActiveEntry(getURL())) + bReg = true; + +#if HAVE_FEATURE_EXTENSIONS + const OUString url(getURL()); + if (!bReg && that->m_registeredPackages) + { + // fallback for user extension registered in berkeley DB + bReg = that->m_registeredPackages->has( + OUStringToOString( url, RTL_TEXTENCODING_UTF8 )); + } +#endif + return beans::Optional< beans::Ambiguous<sal_Bool> >( + true, beans::Ambiguous<sal_Bool>( bReg, false ) ); +} + + +OUString encodeForXml( OUString const & text ) +{ + // encode conforming xml: + sal_Int32 len = text.getLength(); + OUStringBuffer buf; + for ( sal_Int32 pos = 0; pos < len; ++pos ) + { + sal_Unicode c = text[ pos ]; + switch (c) { + case '<': + buf.append( "<" ); + break; + case '>': + buf.append( ">" ); + break; + case '&': + buf.append( "&" ); + break; + case '\'': + buf.append( "'" ); + break; + case '\"': + buf.append( """ ); + break; + default: + buf.append( c ); + break; + } + } + return buf.makeStringAndClear(); +} + + +OUString replaceOrigin( + OUString const & url, std::u16string_view destFolder, Reference< XCommandEnvironment > const & xCmdEnv, Reference< XComponentContext > const & xContext, bool & out_replaced) +{ + // looking for %origin%: + ::ucbhelper::Content ucb_content( url, xCmdEnv, xContext ); + std::vector<sal_Int8> bytes( readFile( ucb_content ) ); + std::vector<sal_Int8> filtered( bytes.size() * 2 ); + bool use_filtered = false; + OString origin; + char const * pBytes = reinterpret_cast<char const *>( + bytes.data()); + std::size_t nBytes = bytes.size(); + size_t write_pos = 0; + while (nBytes > 0) + { + sal_Int32 index = rtl_str_indexOfChar_WithLength( pBytes, nBytes, '%' ); + if (index < 0) { + if (! use_filtered) // opt + break; + index = nBytes; + } + + if ((write_pos + index) > filtered.size()) + filtered.resize( (filtered.size() + index) * 2 ); + memcpy( filtered.data() + write_pos, pBytes, index ); + write_pos += index; + pBytes += index; + nBytes -= index; + if (nBytes == 0) + break; + + // consume %: + ++pBytes; + --nBytes; + char const * pAdd = "%"; + sal_Int32 nAdd = 1; + if (nBytes > 1 && pBytes[ 0 ] == '%') + { + // %% => % + ++pBytes; + --nBytes; + use_filtered = true; + } + else if (rtl_str_shortenedCompare_WithLength( + pBytes, nBytes, + "origin%", + RTL_CONSTASCII_LENGTH("origin%"), + RTL_CONSTASCII_LENGTH("origin%")) == 0) + { + if (origin.isEmpty()) { + // encode only once + origin = OUStringToOString( + encodeForXml( url.copy( 0, url.lastIndexOf( '/' ) ) ), + // xxx todo: encode always for UTF-8? => lookup doc-header? + RTL_TEXTENCODING_UTF8 ); + } + pAdd = origin.getStr(); + nAdd = origin.getLength(); + pBytes += RTL_CONSTASCII_LENGTH("origin%"); + nBytes -= RTL_CONSTASCII_LENGTH("origin%"); + use_filtered = true; + } + if ((write_pos + nAdd) > filtered.size()) + filtered.resize( (filtered.size() + nAdd) * 2 ); + memcpy( filtered.data() + write_pos, pAdd, nAdd ); + write_pos += nAdd; + } + if (!use_filtered) + return url; + if (write_pos < filtered.size()) + filtered.resize( write_pos ); + OUString newUrl(url); + if (!destFolder.empty()) + { + //get the file name of the xcu and add it to the url of the temporary folder + sal_Int32 i = url.lastIndexOf('/'); + newUrl = OUString::Concat(destFolder) + url.subView(i); + } + + ucbhelper::Content(newUrl, xCmdEnv, xContext).writeStream( + xmlscript::createInputStream(std::move(filtered)), true); + out_replaced = true; + return newUrl; +} + + +void BackendImpl::PackageImpl::processPackage_( + ::osl::ResettableMutexGuard &, + bool doRegisterPackage, + bool startup, + ::rtl::Reference<AbortChannel> const &, + Reference<XCommandEnvironment> const & xCmdEnv ) +{ + BackendImpl * that = getMyBackend(); + OUString url( getURL() ); + + if (doRegisterPackage) + { + if (getMyBackend()->activateEntry(getURL())) + { + ::std::optional<ConfigurationBackendDb::Data> data = that->readDataFromDb(url); + OSL_ASSERT(data); + that->addToConfigmgrIni( m_isSchema, false, data->iniEntry, xCmdEnv ); + } + else + { + ConfigurationBackendDb::Data data; + if (!m_isSchema) + { + const OUString sModFolder = that->createFolder(xCmdEnv); + bool out_replaced = false; + url = replaceOrigin(url, sModFolder, xCmdEnv, that->getComponentContext(), out_replaced); + if (out_replaced) + data.dataUrl = sModFolder; + else + deleteTempFolder(sModFolder); + } + //No need for live-deployment for bundled extension, because OOo + //restarts after installation + if ((that->m_eContext != Context::Bundled && !startup) + || comphelper::LibreOfficeKit::isActive()) + { + if (m_isSchema) + { + css::configuration::Update::get( + that->m_xComponentContext)->insertExtensionXcsFile( + that->m_eContext == Context::Shared, expandUnoRcUrl(url)); + } + else + { + css::configuration::Update::get( + that->m_xComponentContext)->insertExtensionXcuFile( + that->m_eContext == Context::Shared, expandUnoRcUrl(url)); + } + } + that->addToConfigmgrIni( m_isSchema, true, url, xCmdEnv ); + data.iniEntry = dp_misc::makeRcTerm(url); + that->addDataToDb(getURL(), data); + } + } + else // revoke + { +#if HAVE_FEATURE_EXTENSIONS + if (!that->removeFromConfigmgrIni(m_isSchema, url, xCmdEnv) && + that->m_registeredPackages) { + // Obsolete package database handling - should be removed for LibreOffice 4.0 + t_string2string_map entries( + that->m_registeredPackages->getEntries()); + for (auto const& entry : entries) + { + //If the xcu file was installed before the configmgr was changed + //to use the configmgr.ini, one needed to rebuild to whole directory + //structure containing the xcu, xcs files from all extensions. Now, + //we just add all other xcu/xcs files to the configmgr.ini instead of + //rebuilding the directory structure. + OUString url2( + OStringToOUString(entry.first, RTL_TEXTENCODING_UTF8)); + if (url2 != url) { + bool schema = entry.second.equalsIgnoreAsciiCase( + "vnd.sun.star.configuration-schema"); + OUString url_replaced(url2); + ConfigurationBackendDb::Data data; + if (!schema) + { + const OUString sModFolder = that->createFolder(xCmdEnv); + bool out_replaced = false; + url_replaced = replaceOrigin( + url2, sModFolder, xCmdEnv, that->getComponentContext(), out_replaced); + if (out_replaced) + data.dataUrl = sModFolder; + else + deleteTempFolder(sModFolder); + } + that->addToConfigmgrIni(schema, true, url_replaced, xCmdEnv); + data.iniEntry = dp_misc::makeRcTerm(url_replaced); + that->addDataToDb(url2, data); + } + that->m_registeredPackages->erase(entry.first); + } + try + { + ::ucbhelper::Content( + makeURL( that->getCachePath(), "registry" ), + xCmdEnv, that->getComponentContext() ).executeCommand( + "delete", Any( true /* delete physically */ ) ); + } + catch(const Exception&) + { + OSL_ASSERT(false); + } + } +#endif + ::std::optional<ConfigurationBackendDb::Data> data = that->readDataFromDb(url); + //If an xcu file was life deployed then always a data entry is written. + //If the xcu file was already in the configmr.ini then there is also + //a data entry + if (!m_isSchema && data) + { + css::configuration::Update::get( + that->m_xComponentContext)->removeExtensionXcuFile(expandUnoRcTerm(data->iniEntry)); + } + that->revokeEntryFromDb(url); + } +} + +} // anon namespace + +} // namespace dp_registry + +extern "C" SAL_DLLPUBLIC_EXPORT css::uno::XInterface* +com_sun_star_comp_deployment_configuration_PackageRegistryBackend_get_implementation( + css::uno::XComponentContext* context, css::uno::Sequence<css::uno::Any> const& args) +{ + return cppu::acquire(new dp_registry::backend::configuration::BackendImpl(args, context)); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/desktop/source/deployment/registry/configuration/dp_configurationbackenddb.cxx b/desktop/source/deployment/registry/configuration/dp_configurationbackenddb.cxx new file mode 100644 index 000000000..afdc8112f --- /dev/null +++ b/desktop/source/deployment/registry/configuration/dp_configurationbackenddb.cxx @@ -0,0 +1,161 @@ +/* -*- 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/exc_hlp.hxx> +#include <com/sun/star/deployment/DeploymentException.hpp> +#include <com/sun/star/uno/XComponentContext.hpp> +#include <com/sun/star/xml/xpath/XXPathAPI.hpp> + +#include "dp_configurationbackenddb.hxx" + + +using namespace ::com::sun::star::uno; + +constexpr OUStringLiteral EXTENSION_REG_NS = u"http://openoffice.org/extensionmanager/configuration-registry/2010"; +constexpr OUStringLiteral NS_PREFIX = u"conf"; +constexpr OUStringLiteral ROOT_ELEMENT_NAME = u"configuration-backend-db"; +constexpr OUStringLiteral KEY_ELEMENT_NAME = u"configuration"; + +namespace dp_registry::backend::configuration { + +ConfigurationBackendDb::ConfigurationBackendDb( + Reference<XComponentContext> const & xContext, + OUString const & url):BackendDb(xContext, url) +{ + +} + +OUString ConfigurationBackendDb::getDbNSName() +{ + return EXTENSION_REG_NS; +} + +OUString ConfigurationBackendDb::getNSPrefix() +{ + return NS_PREFIX; +} + +OUString ConfigurationBackendDb::getRootElementName() +{ + return ROOT_ELEMENT_NAME; +} + +OUString ConfigurationBackendDb::getKeyElementName() +{ + return KEY_ELEMENT_NAME; +} + + +void ConfigurationBackendDb::addEntry(OUString const & url, Data const & data) +{ + try{ + if (!activateEntry(url)) + { + Reference<css::xml::dom::XNode> helpNode + = writeKeyElement(url); + + writeSimpleElement(u"data-url", data.dataUrl, helpNode); + writeSimpleElement(u"ini-entry", data.iniEntry, helpNode); + save(); + } + } + catch ( const css::deployment::DeploymentException& ) + { + throw; + } + catch(const css::uno::Exception &) + { + Any exc( ::cppu::getCaughtException() ); + throw css::deployment::DeploymentException( + "Extension Manager: failed to write data entry in configuration backend db: " + + m_urlDb, nullptr, exc); + } +} + + +::std::optional<ConfigurationBackendDb::Data> +ConfigurationBackendDb::getEntry(std::u16string_view url) +{ + try + { + ConfigurationBackendDb::Data retData; + Reference<css::xml::dom::XNode> aNode = getKeyElement(url); + if (aNode.is()) + { + retData.dataUrl = readSimpleElement(u"data-url", aNode); + retData.iniEntry = readSimpleElement(u"ini-entry", aNode); + } + else + { + return ::std::optional<Data>(); + } + return ::std::optional<Data>(retData); + } + catch ( const css::deployment::DeploymentException& ) + { + throw; + } + catch(const css::uno::Exception &) + { + Any exc( ::cppu::getCaughtException() ); + throw css::deployment::DeploymentException( + "Extension Manager: failed to read data entry in configuration backend db: " + + m_urlDb, nullptr, exc); + } +} + +std::vector<OUString> ConfigurationBackendDb::getAllDataUrls() +{ + try + { + std::vector<OUString> listRet; + Reference<css::xml::dom::XDocument> doc = getDocument(); + Reference<css::xml::dom::XNode> root = doc->getFirstChild(); + + Reference<css::xml::xpath::XXPathAPI> xpathApi = getXPathAPI(); + const OUString sPrefix = getNSPrefix(); + OUString sExpression( + sPrefix + ":configuration/" + sPrefix + ":data-url/text()"); + Reference<css::xml::dom::XNodeList> nodes = + xpathApi->selectNodeList(root, sExpression); + if (nodes.is()) + { + sal_Int32 length = nodes->getLength(); + for (sal_Int32 i = 0; i < length; i++) + listRet.push_back(nodes->item(i)->getNodeValue()); + } + return listRet; + } + catch ( const css::deployment::DeploymentException& ) + { + throw; + } + catch(const css::uno::Exception &) + { + Any exc( ::cppu::getCaughtException() ); + throw css::deployment::DeploymentException( + "Extension Manager: failed to read data entry in configuration backend db: " + + m_urlDb, nullptr, exc); + } +} + +} // namespace dp_registry::backend::configuration + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/desktop/source/deployment/registry/configuration/dp_configurationbackenddb.hxx b/desktop/source/deployment/registry/configuration/dp_configurationbackenddb.hxx new file mode 100644 index 000000000..bd48aab7b --- /dev/null +++ b/desktop/source/deployment/registry/configuration/dp_configurationbackenddb.hxx @@ -0,0 +1,74 @@ +/* -*- 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 <rtl/ustring.hxx> +#include <rtl/string.hxx> +#include <vector> +#include <optional> +#include <string_view> + +#include <dp_backenddb.hxx> + +namespace com::sun::star::uno +{ +class XComponentContext; +} + +namespace dp_registry::backend::configuration +{ +/* The XML file stores the extensions which are currently registered. + They will be removed when they are revoked. + */ +class ConfigurationBackendDb : public dp_registry::backend::BackendDb +{ +protected: + virtual OUString getDbNSName() override; + + virtual OUString getNSPrefix() override; + + virtual OUString getRootElementName() override; + + virtual OUString getKeyElementName() override; + +public: + struct Data + { + /* the URL to the folder containing the xcu or xcs files which contained + %origin% + */ + OUString dataUrl; + /* the URL of the xcu or xcs file which is written in to the configmgr.ini + */ + OUString iniEntry; + }; + +public: + ConfigurationBackendDb(css::uno::Reference<css::uno::XComponentContext> const& xContext, + OUString const& url); + + void addEntry(OUString const& url, Data const& data); + + ::std::optional<Data> getEntry(std::u16string_view url); + std::vector<OUString> getAllDataUrls(); +}; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/desktop/source/deployment/registry/dp_backend.cxx b/desktop/source/deployment/registry/dp_backend.cxx new file mode 100644 index 000000000..2a681fe87 --- /dev/null +++ b/desktop/source/deployment/registry/dp_backend.cxx @@ -0,0 +1,771 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include <sal/config.h> + +#include <cassert> + +#include <dp_backend.h> +#include <dp_misc.h> +#include <dp_ucb.h> +#include <rtl/ustring.hxx> +#include <rtl/bootstrap.hxx> +#include <sal/log.hxx> +#include <cppuhelper/exc_hlp.hxx> +#include <comphelper/unwrapargs.hxx> +#include <ucbhelper/content.hxx> +#include <com/sun/star/lang/WrappedTargetRuntimeException.hpp> +#include <com/sun/star/deployment/DeploymentException.hpp> +#include <com/sun/star/deployment/ExtensionRemovedException.hpp> +#include <com/sun/star/deployment/InvalidRemovedParameterException.hpp> +#include <com/sun/star/ucb/ContentCreationException.hpp> +#include <com/sun/star/ucb/CommandAbortedException.hpp> +#include <com/sun/star/ucb/CommandFailedException.hpp> +#include <com/sun/star/ucb/InteractiveAugmentedIOException.hpp> +#include <com/sun/star/ucb/IOErrorCode.hpp> +#include <com/sun/star/beans/StringPair.hpp> +#include <com/sun/star/sdbc/XResultSet.hpp> +#include <com/sun/star/sdbc/XRow.hpp> +#include <tools/diagnose_ex.h> +#include <unotools/tempfile.hxx> +#include <optional> +#include <utility> + +using namespace ::dp_misc; +using namespace ::com::sun::star; +using namespace ::com::sun::star::uno; +using namespace ::com::sun::star::ucb; + +namespace dp_registry::backend { + + +PackageRegistryBackend::~PackageRegistryBackend() +{ +} + + +void PackageRegistryBackend::disposing( lang::EventObject const & event ) +{ + Reference<deployment::XPackage> xPackage( + event.Source, UNO_QUERY_THROW ); + OUString url( xPackage->getURL() ); + ::osl::MutexGuard guard( m_aMutex ); + if ( m_bound.erase( url ) != 1 ) + { + SAL_WARN("desktop.deployment", "erase(" << url << ") != 1"); + } +} + + +PackageRegistryBackend::PackageRegistryBackend( + Sequence<Any> const & args, + Reference<XComponentContext> const & xContext ) + : t_BackendBase( m_aMutex ), + m_xComponentContext( xContext ), + m_eContext( Context::Unknown ) +{ + assert(xContext.is()); + std::optional<OUString> cachePath; + std::optional<bool> readOnly; + comphelper::unwrapArgs( args, m_context, cachePath, readOnly ); + if (cachePath) + m_cachePath = *cachePath; + + if ( m_context == "user" ) + m_eContext = Context::User; + else if ( m_context == "shared" ) + m_eContext = Context::Shared; + else if ( m_context == "bundled" ) + m_eContext = Context::Bundled; + else if ( m_context == "tmp" ) + m_eContext = Context::Tmp; + else if (m_context.matchIgnoreAsciiCase("vnd.sun.star.tdoc:/")) + m_eContext = Context::Document; + else + m_eContext = Context::Unknown; +} + + +void PackageRegistryBackend::check() +{ + ::osl::MutexGuard guard( m_aMutex ); + if (rBHelper.bInDispose || rBHelper.bDisposed) { + throw lang::DisposedException( + "PackageRegistryBackend instance has already been disposed!", + static_cast<OWeakObject *>(this) ); + } +} + + +void PackageRegistryBackend::disposing() +{ + try { + for (auto const& elem : m_bound) + elem.second->removeEventListener(this); + m_bound.clear(); + m_xComponentContext.clear(); + WeakComponentImplHelperBase::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 ); + } +} + +// XPackageRegistry + +Reference<deployment::XPackage> PackageRegistryBackend::bindPackage( + OUString const & url, OUString const & mediaType, sal_Bool bRemoved, + OUString const & identifier, Reference<XCommandEnvironment> const & xCmdEnv ) +{ + ::osl::ResettableMutexGuard guard( m_aMutex ); + check(); + + t_string2ref::const_iterator const iFind( m_bound.find( url ) ); + if (iFind != m_bound.end()) + { + Reference<deployment::XPackage> xPackage( iFind->second ); + if (xPackage.is()) + { + if (!mediaType.isEmpty() && + mediaType != xPackage->getPackageType()->getMediaType()) + throw lang::IllegalArgumentException + ("XPackageRegistry::bindPackage: media type does not match", + static_cast<OWeakObject*>(this), 1); + if (xPackage->isRemoved() != bRemoved) + throw deployment::InvalidRemovedParameterException( + "XPackageRegistry::bindPackage: bRemoved parameter does not match", + static_cast<OWeakObject*>(this), xPackage->isRemoved(), xPackage); + return xPackage; + } + } + + guard.clear(); + + Reference<deployment::XPackage> xNewPackage; + try { + xNewPackage = bindPackage_( url, mediaType, bRemoved, + identifier, xCmdEnv ); + } + catch (const RuntimeException &) { + throw; + } + catch (const CommandFailedException &) { + throw; + } + catch (const deployment::DeploymentException &) { + throw; + } + catch (const Exception &) { + Any exc( ::cppu::getCaughtException() ); + throw deployment::DeploymentException( + "Error binding package: " + url, + static_cast<OWeakObject *>(this), exc ); + } + + guard.reset(); + + std::pair< t_string2ref::iterator, bool > insertion( + m_bound.emplace( url, xNewPackage ) ); + if (insertion.second) + { // first insertion + SAL_WARN_IF( + Reference<XInterface>(insertion.first->second) != xNewPackage, + "desktop.deployment", "mismatch"); + } + else + { // found existing entry + Reference<deployment::XPackage> xPackage( insertion.first->second ); + if (xPackage.is()) + return xPackage; + insertion.first->second = xNewPackage; + } + + guard.clear(); + xNewPackage->addEventListener( this ); // listen for disposing events + return xNewPackage; +} + +OUString PackageRegistryBackend::createFolder( + Reference<ucb::XCommandEnvironment> const & xCmdEnv) +{ + const OUString sDataFolder = makeURL(getCachePath(), OUString()); + //make sure the folder exist + ucbhelper::Content dataContent; + ::dp_misc::create_folder(&dataContent, sDataFolder, xCmdEnv); + + const OUString baseDir(sDataFolder); + ::utl::TempFile aTemp(&baseDir, true); + const OUString& url = aTemp.GetURL(); + return sDataFolder + url.subView(url.lastIndexOf('/')); +} + +//folderURL can have the extension .tmp or .tmp_ +//Before OOo 3.4 the created a tmp file with osl_createTempFile and +//then created a Folder with a same name and a trailing '_' +//If the folderURL has no '_' then there is no corresponding tmp file. +void PackageRegistryBackend::deleteTempFolder( + OUString const & folderUrl) +{ + if (!folderUrl.isEmpty()) + { + erase_path( folderUrl, Reference<XCommandEnvironment>(), + false /* no throw: ignore errors */ ); + + if (folderUrl.endsWith("_")) + { + const OUString tempFile = folderUrl.copy(0, folderUrl.getLength() - 1); + erase_path( tempFile, Reference<XCommandEnvironment>(), + false /* no throw: ignore errors */ ); + } + } +} + +//usedFolders can contain folder names which have the extension .tmp or .tmp_ +//Before OOo 3.4 we created a tmp file with osl_createTempFile and +//then created a Folder with a same name and a trailing '_' +//If the folderURL has no '_' then there is no corresponding tmp file. +void PackageRegistryBackend::deleteUnusedFolders( + std::vector< OUString> const & usedFolders) +{ + try + { + const OUString sDataFolder = makeURL(getCachePath(), OUString()); + ::ucbhelper::Content tempFolder( + sDataFolder, Reference<ucb::XCommandEnvironment>(), m_xComponentContext); + + Reference<sdbc::XResultSet> xResultSet( + StrTitle::createCursor( tempFolder, ::ucbhelper::INCLUDE_FOLDERS_ONLY ) ); + + // get all temp directories: + std::vector<OUString> tempEntries; + + while (xResultSet->next()) + { + OUString title( + Reference<sdbc::XRow>( + xResultSet, UNO_QUERY_THROW )->getString( + 1 /* Title */ ) ); + + if (title.endsWith(".tmp")) + tempEntries.push_back( + makeURLAppendSysPathSegment(sDataFolder, title)); + } + + for (const OUString & tempEntrie : tempEntries) + { + if (std::find( usedFolders.begin(), usedFolders.end(), tempEntrie ) == + usedFolders.end()) + { + deleteTempFolder(tempEntrie); + } + } + } + catch (const ucb::InteractiveAugmentedIOException& e) + { + //In case the folder containing all the data folder does not + //exist yet, we ignore the exception + if (e.Code != ucb::IOErrorCode_NOT_EXISTING) + throw; + } + +} + + +Package::~Package() +{ +} + + +Package::Package( ::rtl::Reference<PackageRegistryBackend> myBackend, + OUString url, + OUString aName, + OUString displayName, + Reference<deployment::XPackageTypeInfo> const & xPackageType, + bool bRemoved, + OUString identifier) + : t_PackageBase( m_aMutex ), + m_myBackend(std::move( myBackend )), + m_url(std::move( url )), + m_name(std::move( aName )), + m_displayName(std::move( displayName )), + m_xPackageType( xPackageType ), + m_bRemoved(bRemoved), + m_identifier(std::move(identifier)) +{ + if (m_bRemoved) + { + //We use the last segment of the URL + SAL_WARN_IF( + !m_name.isEmpty(), "desktop.deployment", "non-empty m_name"); + OUString name = m_url; + rtl::Bootstrap::expandMacros(name); + sal_Int32 index = name.lastIndexOf('/'); + if (index != -1 && index < name.getLength()) + m_name = name.copy(index + 1); + } +} + + +void Package::disposing() +{ + m_myBackend.clear(); + WeakComponentImplHelperBase::disposing(); +} + + +void Package::check() const +{ + ::osl::MutexGuard guard( m_aMutex ); + if (rBHelper.bInDispose || rBHelper.bDisposed) { + throw lang::DisposedException( + "Package instance has already been disposed!", + static_cast<OWeakObject *>(const_cast<Package *>(this))); + } +} + +// XComponent + +void Package::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 Package::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 Package::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 ); +} + +// XModifyBroadcaster + +void Package::addModifyListener( + Reference<util::XModifyListener> const & xListener ) +{ + check(); + rBHelper.addListener( cppu::UnoType<decltype(xListener)>::get(), xListener ); +} + + +void Package::removeModifyListener( + Reference<util::XModifyListener> const & xListener ) +{ + check(); + rBHelper.removeListener( cppu::UnoType<decltype(xListener)>::get(), xListener ); +} + + +void Package::checkAborted( + ::rtl::Reference<AbortChannel> const & abortChannel ) +{ + if (abortChannel.is() && abortChannel->isAborted()) { + throw CommandAbortedException( + "abort!", static_cast<OWeakObject *>(this) ); + } +} + +// XPackage + +Reference<task::XAbortChannel> Package::createAbortChannel() +{ + check(); + return new AbortChannel; +} + + +sal_Bool Package::isBundle() +{ + return false; // default +} + + +::sal_Int32 Package::checkPrerequisites( + const css::uno::Reference< css::task::XAbortChannel >&, + const css::uno::Reference< css::ucb::XCommandEnvironment >&, + sal_Bool) +{ + if (m_bRemoved) + throw deployment::ExtensionRemovedException(); + return 0; +} + + +sal_Bool Package::checkDependencies( + const css::uno::Reference< css::ucb::XCommandEnvironment >& ) +{ + if (m_bRemoved) + throw deployment::ExtensionRemovedException(); + return true; +} + + +Sequence< Reference<deployment::XPackage> > Package::getBundle( + Reference<task::XAbortChannel> const &, + Reference<XCommandEnvironment> const & ) +{ + return Sequence< Reference<deployment::XPackage> >(); +} + + +OUString Package::getName() +{ + return m_name; +} + +beans::Optional<OUString> Package::getIdentifier() +{ + if (m_bRemoved) + return beans::Optional<OUString>(true, m_identifier); + + return beans::Optional<OUString>(); +} + + +OUString Package::getVersion() +{ + if (m_bRemoved) + throw deployment::ExtensionRemovedException(); + return OUString(); +} + + +OUString Package::getURL() +{ + return m_url; +} + + +OUString Package::getDisplayName() +{ + if (m_bRemoved) + throw deployment::ExtensionRemovedException(); + return m_displayName; +} + + +OUString Package::getDescription() +{ + if (m_bRemoved) + throw deployment::ExtensionRemovedException(); + return OUString(); +} + + +OUString Package::getLicenseText() +{ + if (m_bRemoved) + throw deployment::ExtensionRemovedException(); + return OUString(); +} + + +Sequence<OUString> Package::getUpdateInformationURLs() +{ + if (m_bRemoved) + throw deployment::ExtensionRemovedException(); + return Sequence<OUString>(); +} + + +css::beans::StringPair Package::getPublisherInfo() +{ + if (m_bRemoved) + throw deployment::ExtensionRemovedException(); + css::beans::StringPair aEmptyPair; + return aEmptyPair; +} + + +uno::Reference< css::graphic::XGraphic > Package::getIcon( sal_Bool /*bHighContrast*/ ) +{ + if (m_bRemoved) + throw deployment::ExtensionRemovedException(); + + uno::Reference< css::graphic::XGraphic > aEmpty; + return aEmpty; +} + + +Reference<deployment::XPackageTypeInfo> Package::getPackageType() +{ + return m_xPackageType; +} + +void Package::exportTo( + OUString const & destFolderURL, OUString const & newTitle, + sal_Int32 nameClashAction, Reference<XCommandEnvironment> const & xCmdEnv ) +{ + if (m_bRemoved) + throw deployment::ExtensionRemovedException(); + + ::ucbhelper::Content destFolder( destFolderURL, xCmdEnv, getMyBackend()->getComponentContext() ); + ::ucbhelper::Content sourceContent( getURL(), xCmdEnv, getMyBackend()->getComponentContext() ); + bool bOk=true; + try + { + destFolder.transferContent( + sourceContent, ::ucbhelper::InsertOperation::Copy, + newTitle, nameClashAction); + } + catch (const css::ucb::ContentCreationException&) + { + bOk = false; + } + + if (!bOk) + throw RuntimeException( "UCB transferContent() failed!", nullptr ); +} + +void Package::fireModified() +{ + ::cppu::OInterfaceContainerHelper * container = rBHelper.getContainer( + cppu::UnoType<util::XModifyListener>::get() ); + if (container == nullptr) + return; + + const Sequence< Reference<XInterface> > elements( + container->getElements() ); + lang::EventObject evt( static_cast<OWeakObject *>(this) ); + for ( const Reference<XInterface>& x : elements ) + { + Reference<util::XModifyListener> xListener( x, UNO_QUERY ); + if (xListener.is()) + xListener->modified( evt ); + } +} + +// XPackage + +beans::Optional< beans::Ambiguous<sal_Bool> > Package::isRegistered( + Reference<task::XAbortChannel> const & xAbortChannel, + Reference<XCommandEnvironment> const & xCmdEnv ) +{ + try { + ::osl::ResettableMutexGuard guard( m_aMutex ); + return isRegistered_( guard, + AbortChannel::get(xAbortChannel), + xCmdEnv ); + } + catch (const RuntimeException &) { + throw; + } + catch (const CommandFailedException &) { + throw; + } + catch (const CommandAbortedException &) { + throw; + } + catch (const deployment::DeploymentException &) { + throw; + } + catch (const Exception & e) { + Any exc( ::cppu::getCaughtException() ); + throw deployment::DeploymentException( + "unexpected " + exc.getValueTypeName() + ": " + e.Message, + static_cast<OWeakObject *>(this), exc ); + } +} + + +void Package::processPackage_impl( + bool doRegisterPackage, + bool startup, + Reference<task::XAbortChannel> const & xAbortChannel, + Reference<XCommandEnvironment> const & xCmdEnv ) +{ + check(); + bool action = false; + + try { + try { + ::osl::ResettableMutexGuard guard( m_aMutex ); + beans::Optional< beans::Ambiguous<sal_Bool> > option( + isRegistered_( guard, AbortChannel::get(xAbortChannel), + xCmdEnv ) ); + action = (option.IsPresent && + (option.Value.IsAmbiguous || + (doRegisterPackage ? !option.Value.Value + : option.Value.Value))); + if (action) { + + OUString displayName = isRemoved() ? getName() : getDisplayName(); + ProgressLevel progress( + xCmdEnv, + (doRegisterPackage + ? PackageRegistryBackend::StrRegisteringPackage() + : PackageRegistryBackend::StrRevokingPackage()) + + displayName ); + processPackage_( guard, + doRegisterPackage, + startup, + AbortChannel::get(xAbortChannel), + xCmdEnv ); + } + } + catch (lang::IllegalArgumentException &) { + Any e(cppu::getCaughtException()); + throw deployment::DeploymentException( + ((doRegisterPackage + ? DpResId(RID_STR_ERROR_WHILE_REGISTERING) + : DpResId(RID_STR_ERROR_WHILE_REVOKING)) + + getDisplayName()), + static_cast< OWeakObject * >(this), e); + } + catch (const RuntimeException &) { + TOOLS_WARN_EXCEPTION("desktop.deployment", "unexpected"); + throw; + } + catch (const CommandFailedException &) { + throw; + } + catch (const CommandAbortedException &) { + throw; + } + catch (const deployment::DeploymentException &) { + throw; + } + catch (const Exception & e) { + Any exc( ::cppu::getCaughtException() ); + throw deployment::DeploymentException( + (doRegisterPackage + ? DpResId(RID_STR_ERROR_WHILE_REGISTERING) + : DpResId(RID_STR_ERROR_WHILE_REVOKING)) + + getDisplayName() + ": " + exc.getValueType().getTypeName() + " \"" + e.Message + + "\"", + static_cast<OWeakObject *>(this), exc ); + } + } + catch (...) { + if (action) + fireModified(); + throw; + } + if (action) + fireModified(); +} + + +void Package::registerPackage( + sal_Bool startup, + Reference<task::XAbortChannel> const & xAbortChannel, + Reference<XCommandEnvironment> const & xCmdEnv ) +{ + if (m_bRemoved) + throw deployment::ExtensionRemovedException(); + processPackage_impl( true /* register */, startup, xAbortChannel, xCmdEnv ); +} + + +void Package::revokePackage( + sal_Bool startup, + Reference<task::XAbortChannel> const & xAbortChannel, + Reference<XCommandEnvironment> const & xCmdEnv ) +{ + processPackage_impl( false /* revoke */, startup, xAbortChannel, xCmdEnv ); + +} + +PackageRegistryBackend * Package::getMyBackend() const +{ + PackageRegistryBackend * pBackend = m_myBackend.get(); + if (nullptr == pBackend) + { + //May throw a DisposedException + check(); + //We should never get here... + throw RuntimeException( + "Failed to get the BackendImpl", + static_cast<OWeakObject*>(const_cast<Package *>(this))); + } + return pBackend; +} + +OUString Package::getRepositoryName() +{ + PackageRegistryBackend * backEnd = getMyBackend(); + return backEnd->getContext(); +} + +beans::Optional< OUString > Package::getRegistrationDataURL() +{ + if (m_bRemoved) + throw deployment::ExtensionRemovedException(); + return beans::Optional<OUString>(); +} + +sal_Bool Package::isRemoved() +{ + return m_bRemoved; +} + +Package::TypeInfo::~TypeInfo() +{ +} + +// XPackageTypeInfo + +OUString Package::TypeInfo::getMediaType() +{ + return m_mediaType; +} + + +OUString Package::TypeInfo::getDescription() +{ + return getShortDescription(); +} + + +OUString Package::TypeInfo::getShortDescription() +{ + return m_shortDescr; +} + +OUString Package::TypeInfo::getFileFilter() +{ + return m_fileFilter; +} + +Any Package::TypeInfo::getIcon( sal_Bool /*highContrast*/, sal_Bool /*smallIcon*/ ) +{ + return Any(); +} + +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/desktop/source/deployment/registry/dp_backenddb.cxx b/desktop/source/deployment/registry/dp_backenddb.cxx new file mode 100644 index 000000000..28effd95c --- /dev/null +++ b/desktop/source/deployment/registry/dp_backenddb.cxx @@ -0,0 +1,655 @@ +/* -*- 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/exc_hlp.hxx> +#include <osl/file.hxx> +#include <com/sun/star/deployment/DeploymentException.hpp> +#include <com/sun/star/uno/XComponentContext.hpp> +#include <com/sun/star/xml/dom/DocumentBuilder.hpp> +#include <com/sun/star/xml/xpath/XPathAPI.hpp> +#include <com/sun/star/io/XActiveDataSource.hpp> +#include <com/sun/star/io/XActiveDataControl.hpp> +#include <dp_misc.h> +#include <ucbhelper/content.hxx> +#include <xmlscript/xml_helper.hxx> +#include <dp_backenddb.hxx> + + +using namespace ::com::sun::star::uno; + + +namespace dp_registry::backend { + +BackendDb::BackendDb( + Reference<css::uno::XComponentContext> const & xContext, + OUString const & url): + m_xContext(xContext) +{ + m_urlDb = dp_misc::expandUnoRcUrl(url); +} + +void BackendDb::save() +{ + const Reference<css::io::XActiveDataSource> xDataSource(m_doc,css::uno::UNO_QUERY_THROW); + std::vector<sal_Int8> bytes; + xDataSource->setOutputStream(::xmlscript::createOutputStream(&bytes)); + const Reference<css::io::XActiveDataControl> xDataControl(m_doc,css::uno::UNO_QUERY_THROW); + xDataControl->start(); + + const Reference<css::io::XInputStream> xData( + ::xmlscript::createInputStream(std::move(bytes))); + ::ucbhelper::Content ucbDb(m_urlDb, nullptr, m_xContext); + ucbDb.writeStream(xData, true /*replace existing*/); +} + +css::uno::Reference<css::xml::dom::XDocument> const & BackendDb::getDocument() +{ + if (!m_doc.is()) + { + const Reference<css::xml::dom::XDocumentBuilder> xDocBuilder( + css::xml::dom::DocumentBuilder::create(m_xContext) ); + + ::osl::DirectoryItem item; + ::osl::File::RC err = ::osl::DirectoryItem::get(m_urlDb, item); + if (err == ::osl::File::E_None) + { + ::ucbhelper::Content descContent( + m_urlDb, css::uno::Reference<css::ucb::XCommandEnvironment>(), + m_xContext); + Reference<css::io::XInputStream> xIn = descContent.openStream(); + m_doc = xDocBuilder->parse(xIn); + } + else if (err == ::osl::File::E_NOENT) + { + //Create a new document and insert some basic stuff + m_doc = xDocBuilder->newDocument(); + const Reference<css::xml::dom::XElement> rootNode = + m_doc->createElementNS(getDbNSName(), getNSPrefix() + + ":" + getRootElementName()); + + m_doc->appendChild(Reference<css::xml::dom::XNode>( + rootNode, UNO_QUERY_THROW)); + save(); + } + else + throw css::uno::RuntimeException( + "Extension manager could not access database file:" + + m_urlDb, nullptr); + + if (!m_doc.is()) + throw css::uno::RuntimeException( + "Extension manager could not get root node of data base file: " + + m_urlDb, nullptr); + } + + return m_doc; +} + +Reference<css::xml::xpath::XXPathAPI> const & BackendDb::getXPathAPI() +{ + if (!m_xpathApi.is()) + { + m_xpathApi = css::xml::xpath::XPathAPI::create( m_xContext ); + + m_xpathApi->registerNS( getNSPrefix(), getDbNSName() ); + } + + return m_xpathApi; +} + +void BackendDb::removeElement(OUString const & sXPathExpression) +{ + try + { + const Reference<css::xml::dom::XDocument> doc = getDocument(); + const Reference<css::xml::dom::XNode> root = doc->getFirstChild(); + const Reference<css::xml::xpath::XXPathAPI> xpathApi = getXPathAPI(); + //find the extension element that is to be removed + const Reference<css::xml::dom::XNode> aNode = + xpathApi->selectSingleNode(root, sXPathExpression); + + if (aNode.is()) + { + root->removeChild(aNode); + save(); + } + +#if OSL_DEBUG_LEVEL > 0 + //There must not be any other entry with the same url + const Reference<css::xml::dom::XNode> nextNode = + xpathApi->selectSingleNode(root, sXPathExpression); + OSL_ASSERT(! nextNode.is()); +#endif + } + catch(const css::uno::Exception &) + { + Any exc( ::cppu::getCaughtException() ); + throw css::deployment::DeploymentException( + "Extension Manager: failed to write data entry in backend db: " + + m_urlDb, nullptr, exc); + } +} + +void BackendDb::removeEntry(std::u16string_view url) +{ + const OUString sKeyElement = getKeyElementName(); + const OUString sPrefix = getNSPrefix(); + OUString sExpression = + sPrefix + + ":" + + sKeyElement + + "[@url = \"" + + url + + "\"]"; + + removeElement(sExpression); +} + +void BackendDb::revokeEntry(std::u16string_view url) +{ + try + { + Reference<css::xml::dom::XElement> entry(getKeyElement(url), UNO_QUERY); + if (entry.is()) + { + entry->setAttribute("revoked", "true"); + save(); + } + } + catch(const css::uno::Exception &) + { + Any exc( ::cppu::getCaughtException() ); + throw css::deployment::DeploymentException( + "Extension Manager: failed to revoke data entry in backend db: " + + m_urlDb, nullptr, exc); + } +} + +bool BackendDb::activateEntry(std::u16string_view url) +{ + try + { + bool ret = false; + Reference<css::xml::dom::XElement> entry(getKeyElement(url), UNO_QUERY); + if (entry.is()) + { + //no attribute "active" means it is active, that is, registered. + entry->removeAttribute("revoked"); + save(); + ret = true; + } + return ret; + } + catch(const css::uno::Exception &) + { + Any exc( ::cppu::getCaughtException() ); + throw css::deployment::DeploymentException( + "Extension Manager: failed to revoke data entry in backend db: " + + m_urlDb, nullptr, exc); + } +} + +bool BackendDb::hasActiveEntry(std::u16string_view url) +{ + try + { + bool ret = false; + Reference<css::xml::dom::XElement> entry(getKeyElement(url), UNO_QUERY); + if (entry.is()) + { + OUString sActive = entry->getAttribute("revoked"); + if (!(sActive == "true")) + ret = true; + } + return ret; + + } + catch(const css::uno::Exception &) + { + Any exc( ::cppu::getCaughtException() ); + throw css::deployment::DeploymentException( + "Extension Manager: failed to determine an active entry in backend db: " + + m_urlDb, nullptr, exc); + } +} + +Reference<css::xml::dom::XNode> BackendDb::getKeyElement( + std::u16string_view url) +{ + try + { + const OUString sPrefix = getNSPrefix(); + const OUString sKeyElement = getKeyElementName(); + OUString sExpression = + sPrefix + + ":" + + sKeyElement + + "[@url = \"" + + url + + "\"]"; + + const Reference<css::xml::dom::XDocument> doc = getDocument(); + const Reference<css::xml::dom::XNode> root = doc->getFirstChild(); + const Reference<css::xml::xpath::XXPathAPI> xpathApi = getXPathAPI(); + return xpathApi->selectSingleNode(root, sExpression); + } + catch(const css::uno::Exception &) + { + Any exc( ::cppu::getCaughtException() ); + throw css::deployment::DeploymentException( + "Extension Manager: failed to read key element in backend db: " + + m_urlDb, nullptr, exc); + } +} + +//Only writes the data if there is at least one entry +void BackendDb::writeVectorOfPair( + std::vector< std::pair< OUString, OUString > > const & vecPairs, + std::u16string_view sVectorTagName, + std::u16string_view sPairTagName, + std::u16string_view sFirstTagName, + std::u16string_view sSecondTagName, + css::uno::Reference<css::xml::dom::XNode> const & xParent) +{ + try{ + if (vecPairs.empty()) + return; + const OUString sNameSpace = getDbNSName(); + OSL_ASSERT(!sNameSpace.isEmpty()); + const OUString sPrefix(getNSPrefix() + ":"); + const Reference<css::xml::dom::XDocument> doc = getDocument(); + + const Reference<css::xml::dom::XElement> vectorNode( + doc->createElementNS(sNameSpace, sPrefix + sVectorTagName)); + + xParent->appendChild( + Reference<css::xml::dom::XNode>( + vectorNode, css::uno::UNO_QUERY_THROW)); + for (auto const& vecPair : vecPairs) + { + const Reference<css::xml::dom::XElement> pairNode( + doc->createElementNS(sNameSpace, sPrefix + sPairTagName)); + + vectorNode->appendChild( + Reference<css::xml::dom::XNode>( + pairNode, css::uno::UNO_QUERY_THROW)); + + const Reference<css::xml::dom::XElement> firstNode( + doc->createElementNS(sNameSpace, sPrefix + sFirstTagName)); + + pairNode->appendChild( + Reference<css::xml::dom::XNode>( + firstNode, css::uno::UNO_QUERY_THROW)); + + const Reference<css::xml::dom::XText> firstTextNode( + doc->createTextNode( vecPair.first)); + + firstNode->appendChild( + Reference<css::xml::dom::XNode>( + firstTextNode, css::uno::UNO_QUERY_THROW)); + + const Reference<css::xml::dom::XElement> secondNode( + doc->createElementNS(sNameSpace, sPrefix + sSecondTagName)); + + pairNode->appendChild( + Reference<css::xml::dom::XNode>( + secondNode, css::uno::UNO_QUERY_THROW)); + + const Reference<css::xml::dom::XText> secondTextNode( + doc->createTextNode( vecPair.second)); + + secondNode->appendChild( + Reference<css::xml::dom::XNode>( + secondTextNode, css::uno::UNO_QUERY_THROW)); + } + } + catch(const css::uno::Exception &) + { + Any exc( ::cppu::getCaughtException() ); + throw css::deployment::DeploymentException( + "Extension Manager: failed to write data entry in backend db: " + + m_urlDb, nullptr, exc); + } +} + +std::vector< std::pair< OUString, OUString > > +BackendDb::readVectorOfPair( + Reference<css::xml::dom::XNode> const & parent, + std::u16string_view sListTagName, + std::u16string_view sPairTagName, + std::u16string_view sFirstTagName, + std::u16string_view sSecondTagName) +{ + try + { + OSL_ASSERT(parent.is()); + const OUString sPrefix(getNSPrefix() + ":"); + const Reference<css::xml::xpath::XXPathAPI> xpathApi = getXPathAPI(); + const OUString sExprPairs( + sPrefix + sListTagName + "/" + sPrefix + sPairTagName); + const Reference<css::xml::dom::XNodeList> listPairs = + xpathApi->selectNodeList(parent, sExprPairs); + + std::vector< std::pair< OUString, OUString > > retVector; + sal_Int32 length = listPairs->getLength(); + for (sal_Int32 i = 0; i < length; i++) + { + const Reference<css::xml::dom::XNode> aPair = listPairs->item(i); + const OUString sExprFirst(sPrefix + sFirstTagName + "/text()"); + const Reference<css::xml::dom::XNode> first = + xpathApi->selectSingleNode(aPair, sExprFirst); + + const OUString sExprSecond(sPrefix + sSecondTagName + "/text()"); + const Reference<css::xml::dom::XNode> second = + xpathApi->selectSingleNode(aPair, sExprSecond); + OSL_ASSERT(first.is() && second.is()); + + retVector.emplace_back( + first->getNodeValue(), second->getNodeValue()); + } + return retVector; + } + catch(const css::uno::Exception &) + { + Any exc( ::cppu::getCaughtException() ); + throw css::deployment::DeploymentException( + "Extension Manager: failed to read data entry in backend db: " + + m_urlDb, nullptr, exc); + } +} + +//Only writes the data if there is at least one entry +void BackendDb::writeSimpleList( + std::deque< OUString> const & list, + std::u16string_view sListTagName, + std::u16string_view sMemberTagName, + Reference<css::xml::dom::XNode> const & xParent) +{ + try + { + if (list.empty()) + return; + const OUString sNameSpace = getDbNSName(); + const OUString sPrefix(getNSPrefix() + ":"); + const Reference<css::xml::dom::XDocument> doc = getDocument(); + + const Reference<css::xml::dom::XElement> listNode( + doc->createElementNS(sNameSpace, sPrefix + sListTagName)); + + xParent->appendChild( + Reference<css::xml::dom::XNode>( + listNode, css::uno::UNO_QUERY_THROW)); + + for (auto const& elem : list) + { + const Reference<css::xml::dom::XNode> memberNode( + doc->createElementNS(sNameSpace, sPrefix + sMemberTagName), css::uno::UNO_QUERY_THROW); + + listNode->appendChild(memberNode); + + const Reference<css::xml::dom::XNode> textNode( + doc->createTextNode(elem), css::uno::UNO_QUERY_THROW); + + memberNode->appendChild(textNode); + } + } + catch(const css::uno::Exception &) + { + Any exc( ::cppu::getCaughtException() ); + throw css::deployment::DeploymentException( + "Extension Manager: failed to write data entry in backend db: " + + m_urlDb, nullptr, exc); + } +} + +//Writes only the element if is has a value. +//The prefix is automatically added to the element name +void BackendDb::writeSimpleElement( + std::u16string_view sElementName, OUString const & value, + Reference<css::xml::dom::XNode> const & xParent) +{ + try + { + if (value.isEmpty()) + return; + const OUString sPrefix = getNSPrefix(); + const Reference<css::xml::dom::XDocument> doc = getDocument(); + const OUString sNameSpace = getDbNSName(); + const Reference<css::xml::dom::XNode> dataNode( + doc->createElementNS(sNameSpace, sPrefix + ":" + sElementName), + UNO_QUERY_THROW); + xParent->appendChild(dataNode); + + const Reference<css::xml::dom::XNode> dataValue( + doc->createTextNode(value), UNO_QUERY_THROW); + dataNode->appendChild(dataValue); + } + catch(const css::uno::Exception &) + { + Any exc( ::cppu::getCaughtException() ); + throw css::deployment::DeploymentException( + "Extension Manager: failed to write data entry(writeSimpleElement) in backend db: " + + m_urlDb, nullptr, exc); + } + +} + +/// The key elements have a url attribute and are always children of the root element. +Reference<css::xml::dom::XNode> BackendDb::writeKeyElement( + OUString const & url) +{ + try + { + const OUString sNameSpace = getDbNSName(); + const OUString sPrefix = getNSPrefix(); + const OUString sElementName = getKeyElementName(); + const Reference<css::xml::dom::XDocument> doc = getDocument(); + const Reference<css::xml::dom::XNode> root = doc->getFirstChild(); + + //Check if there are an entry with the same url. This can be the case if the + //status of an XPackage is ambiguous. In this case a call to activateExtension + //(dp_extensionmanager.cxx), will register the package again. See also + //Package::processPackage_impl in dp_backend.cxx. + //A package can become + //invalid after its successful registration, for example if a second extension with + //the same service is installed. + const OUString sExpression( + sPrefix + ":" + sElementName + "[@url = \"" + url + "\"]"); + const Reference<css::xml::dom::XNode> existingNode = + getXPathAPI()->selectSingleNode(root, sExpression); + if (existingNode.is()) + { + OSL_ASSERT(false); + //replace the existing entry. + removeEntry(url); + } + + const Reference<css::xml::dom::XElement> keyElement( + doc->createElementNS(sNameSpace, sPrefix + ":" + sElementName)); + + keyElement->setAttribute("url", url); + + const Reference<css::xml::dom::XNode> keyNode( + keyElement, UNO_QUERY_THROW); + root->appendChild(keyNode); + return keyNode; + } + catch(const css::uno::Exception &) + { + Any exc( ::cppu::getCaughtException() ); + throw css::deployment::DeploymentException( + "Extension Manager: failed to write key element in backend db: " + + m_urlDb, nullptr, exc); + } +} + +OUString BackendDb::readSimpleElement( + std::u16string_view sElementName, Reference<css::xml::dom::XNode> const & xParent) +{ + try + { + const OUString sPrefix = getNSPrefix(); + const OUString sExpr(sPrefix + ":" + sElementName + "/text()"); + const Reference<css::xml::xpath::XXPathAPI> xpathApi = getXPathAPI(); + const Reference<css::xml::dom::XNode> val = + xpathApi->selectSingleNode(xParent, sExpr); + if (val.is()) + return val->getNodeValue(); + return OUString(); + } + catch(const css::uno::Exception &) + { + Any exc( ::cppu::getCaughtException() ); + throw css::deployment::DeploymentException( + "Extension Manager: failed to read data (readSimpleElement) in backend db: " + + m_urlDb, nullptr, exc); + } +} + + +std::deque< OUString> BackendDb::readList( + Reference<css::xml::dom::XNode> const & parent, + std::u16string_view sListTagName, + std::u16string_view sMemberTagName) +{ + try + { + OSL_ASSERT(parent.is()); + const OUString sPrefix(getNSPrefix() + ":"); + const Reference<css::xml::xpath::XXPathAPI> xpathApi = getXPathAPI(); + const OUString sExprList( + sPrefix + sListTagName + "/" + sPrefix + sMemberTagName + "/text()"); + const Reference<css::xml::dom::XNodeList> list = + xpathApi->selectNodeList(parent, sExprList); + + std::deque<OUString > retList; + sal_Int32 length = list->getLength(); + for (sal_Int32 i = 0; i < length; i++) + { + const Reference<css::xml::dom::XNode> member = list->item(i); + retList.push_back(member->getNodeValue()); + } + return retList; + } + catch(const css::uno::Exception &) + { + Any exc( ::cppu::getCaughtException() ); + throw css::deployment::DeploymentException( + "Extension Manager: failed to read data entry in backend db: " + + m_urlDb, nullptr, exc); + } +} + +std::vector<OUString> BackendDb::getOneChildFromAllEntries( + std::u16string_view name) +{ + try + { + std::vector<OUString> listRet; + Reference<css::xml::dom::XDocument> doc = getDocument(); + Reference<css::xml::dom::XNode> root = doc->getFirstChild(); + + Reference<css::xml::xpath::XXPathAPI> xpathApi = getXPathAPI(); + const OUString sPrefix = getNSPrefix(); + const OUString sKeyElement = getKeyElementName(); + OUString sNodeSelectExpr = + sPrefix + + ":" + + sKeyElement + + "/" + + sPrefix + + ":" + + name + + "/text()"; + + Reference<css::xml::dom::XNodeList> nodes = + xpathApi->selectNodeList(root, sNodeSelectExpr); + if (nodes.is()) + { + sal_Int32 length = nodes->getLength(); + for (sal_Int32 i = 0; i < length; i++) + listRet.push_back(nodes->item(i)->getNodeValue()); + } + return listRet; + } + catch ( const css::deployment::DeploymentException& ) + { + throw; + } + catch(const css::uno::Exception &) + { + Any exc( ::cppu::getCaughtException() ); + throw css::deployment::DeploymentException( + "Extension Manager: failed to read data entry in backend db: " + + m_urlDb, nullptr, exc); + } +} + + +RegisteredDb::RegisteredDb( + Reference<XComponentContext> const & xContext, + OUString const & url):BackendDb(xContext, url) +{ +} + +void RegisteredDb::addEntry(OUString const & url) +{ + try{ + if (!activateEntry(url)) + { + const OUString sNameSpace = getDbNSName(); + const OUString sPrefix = getNSPrefix(); + const OUString sEntry = getKeyElementName(); + + Reference<css::xml::dom::XDocument> doc = getDocument(); + Reference<css::xml::dom::XNode> root = doc->getFirstChild(); + +#if OSL_DEBUG_LEVEL > 0 + //There must not be yet an entry with the same url + OUString sExpression( + sPrefix + ":" + sEntry + "[@url = \"" + url + "\"]"); + Reference<css::xml::dom::XNode> _extensionNode = + getXPathAPI()->selectSingleNode(root, sExpression); + OSL_ASSERT(! _extensionNode.is()); +#endif + Reference<css::xml::dom::XElement> helpElement( + doc->createElementNS(sNameSpace, sPrefix + ":" + sEntry)); + + helpElement->setAttribute("url", url); + + Reference<css::xml::dom::XNode> helpNode( + helpElement, UNO_QUERY_THROW); + root->appendChild(helpNode); + + save(); + } + } + catch(const css::uno::Exception &) + { + Any exc( ::cppu::getCaughtException() ); + throw css::deployment::DeploymentException( + "Extension Manager: failed to write data entry in backend db: " + + m_urlDb, nullptr, exc); + } +} + +} // namespace dp_registry + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/desktop/source/deployment/registry/dp_registry.cxx b/desktop/source/deployment/registry/dp_registry.cxx new file mode 100644 index 000000000..961afe7bb --- /dev/null +++ b/desktop/source/deployment/registry/dp_registry.cxx @@ -0,0 +1,528 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include <sal/config.h> +#include <sal/log.hxx> + +#include <dp_shared.hxx> +#include <dp_package.hxx> +#include <strings.hrc> +#include <dp_registry.hxx> +#include <dp_misc.h> +#include <dp_ucb.h> +#include <osl/diagnose.h> +#include <rtl/ustrbuf.hxx> +#include <rtl/uri.hxx> +#include <cppuhelper/basemutex.hxx> +#include <cppuhelper/compbase.hxx> +#include <comphelper/sequence.hxx> +#include <ucbhelper/content.hxx> +#include <o3tl/string_view.hxx> +#include <com/sun/star/ucb/ContentCreationException.hpp> +#include <com/sun/star/uno/DeploymentException.hpp> +#include <com/sun/star/lang/DisposedException.hpp> +#include <com/sun/star/lang/IllegalArgumentException.hpp> +#include <com/sun/star/lang/XServiceInfo.hpp> +#include <com/sun/star/lang/XSingleComponentFactory.hpp> +#include <com/sun/star/lang/XSingleServiceFactory.hpp> +#include <com/sun/star/util/XUpdatable.hpp> +#include <com/sun/star/container/XContentEnumerationAccess.hpp> +#include <com/sun/star/deployment/XPackageTypeInfo.hpp> +#include <com/sun/star/deployment/XPackageRegistry.hpp> +#include <set> +#include <string_view> +#include <unordered_map> +#include <unordered_set> + +using namespace ::dp_misc; +using namespace ::com::sun::star; +using namespace ::com::sun::star::uno; +using namespace ::com::sun::star::ucb; + + +namespace dp_registry { + +namespace { + +typedef ::cppu::WeakComponentImplHelper< + deployment::XPackageRegistry, util::XUpdatable > t_helper; + + +class PackageRegistryImpl : private cppu::BaseMutex, public t_helper +{ + struct ci_string_hash { + std::size_t operator () ( OUString const & str ) const { + return str.toAsciiLowerCase().hashCode(); + } + }; + struct ci_string_equals { + bool operator () ( std::u16string_view str1, std::u16string_view str2 ) const{ + return o3tl::equalsIgnoreAsciiCase( str1, str2 ); + } + }; + typedef std::unordered_map< + OUString, Reference<deployment::XPackageRegistry>, + ci_string_hash, ci_string_equals > t_string2registry; + typedef std::unordered_map< + OUString, OUString, + ci_string_hash, ci_string_equals > t_string2string; + typedef std::set< + Reference<deployment::XPackageRegistry> > t_registryset; + + t_string2registry m_mediaType2backend; + t_string2string m_filter2mediaType; + t_registryset m_ambiguousBackends; + t_registryset m_allBackends; + std::vector< Reference<deployment::XPackageTypeInfo> > m_typesInfos; + + void insertBackend( + Reference<deployment::XPackageRegistry> const & xBackend ); + +protected: + void check(); + virtual void SAL_CALL disposing() override; + + virtual ~PackageRegistryImpl() override; + PackageRegistryImpl() : t_helper( m_aMutex ) {} + + +public: + static Reference<deployment::XPackageRegistry> create( + OUString const & context, + OUString const & cachePath, + Reference<XComponentContext> const & xComponentContext ); + + // XUpdatable + virtual void SAL_CALL update() override; + + // XPackageRegistry + virtual Reference<deployment::XPackage> SAL_CALL bindPackage( + OUString const & url, OUString const & mediaType, sal_Bool bRemoved, + OUString const & identifier, Reference<XCommandEnvironment> const & xCmdEnv ) override; + virtual Sequence< Reference<deployment::XPackageTypeInfo> > SAL_CALL + getSupportedPackageTypes() override; + virtual void SAL_CALL packageRemoved(OUString const & url, OUString const & mediaType) override; + +}; + + +void PackageRegistryImpl::check() +{ + ::osl::MutexGuard guard( m_aMutex ); + if (rBHelper.bInDispose || rBHelper.bDisposed) { + throw lang::DisposedException( + "PackageRegistry instance has already been disposed!", + static_cast<OWeakObject *>(this) ); + } +} + + +void PackageRegistryImpl::disposing() +{ + // dispose all backends: + for (auto const& backend : m_allBackends) + { + try_dispose(backend); + } + m_mediaType2backend = t_string2registry(); + m_ambiguousBackends = t_registryset(); + m_allBackends = t_registryset(); + + t_helper::disposing(); +} + + +PackageRegistryImpl::~PackageRegistryImpl() +{ +} + + +OUString normalizeMediaType( std::u16string_view mediaType ) +{ + OUStringBuffer buf; + sal_Int32 index = 0; + for (;;) { + buf.append( o3tl::trim(o3tl::getToken(mediaType, 0, '/', index )) ); + if (index < 0) + break; + buf.append( '/' ); + } + return buf.makeStringAndClear(); +} + + +void PackageRegistryImpl::packageRemoved( + OUString const & url, OUString const & mediaType) +{ + const t_string2registry::const_iterator i = + m_mediaType2backend.find(mediaType); + + if (i != m_mediaType2backend.end()) + { + i->second->packageRemoved(url, mediaType); + } +} + +void PackageRegistryImpl::insertBackend( + Reference<deployment::XPackageRegistry> const & xBackend ) +{ + m_allBackends.insert( xBackend ); + std::unordered_set<OUString> ambiguousFilters; + + const Sequence< Reference<deployment::XPackageTypeInfo> > packageTypes( + xBackend->getSupportedPackageTypes() ); + for ( Reference<deployment::XPackageTypeInfo> const & xPackageType : packageTypes ) + { + m_typesInfos.push_back( xPackageType ); + + const OUString mediaType( normalizeMediaType( + xPackageType->getMediaType() ) ); + std::pair<t_string2registry::iterator, bool> a_insertion( + m_mediaType2backend.emplace( mediaType, xBackend ) ); + if (a_insertion.second) + { + // add parameterless media-type, too: + sal_Int32 semi = mediaType.indexOf( ';' ); + if (semi >= 0) { + m_mediaType2backend.emplace( mediaType.copy( 0, semi ), xBackend ); + } + const OUString fileFilter( xPackageType->getFileFilter() ); + //The package backend shall also be called to determine the mediatype + //(XPackageRegistry.bindPackage) when the URL points to a directory. + const bool bExtension = (mediaType == "application/vnd.sun.star.package-bundle"); + if (fileFilter.isEmpty() || fileFilter == "*.*" || fileFilter == "*" || bExtension) + { + m_ambiguousBackends.insert( xBackend ); + } + else + { + sal_Int32 nIndex = 0; + do { + OUString token( fileFilter.getToken( 0, ';', nIndex ) ); + if (token.match( "*." )) + token = token.copy( 1 ); + if (token.isEmpty()) + continue; + // mark any further wildcards ambig: + bool ambig = (token.indexOf('*') >= 0 || + token.indexOf('?') >= 0); + if (! ambig) { + std::pair<t_string2string::iterator, bool> ins( + m_filter2mediaType.emplace( + token, mediaType ) ); + ambig = !ins.second; + if (ambig) { + // filter has already been in: add previously + // added backend to ambig set + const t_string2registry::const_iterator iFind( + m_mediaType2backend.find( + /* media-type of pr. added backend */ + ins.first->second ) ); + OSL_ASSERT( + iFind != m_mediaType2backend.end() ); + if (iFind != m_mediaType2backend.end()) + m_ambiguousBackends.insert( iFind->second ); + } + } + if (ambig) { + m_ambiguousBackends.insert( xBackend ); + // mark filter to be removed later from filters map: + ambiguousFilters.insert( token ); + } + } + while (nIndex >= 0); + } + } +#if OSL_DEBUG_LEVEL > 0 + else + { + SAL_WARN( "desktop", "more than one PackageRegistryBackend for media-type=\"" + << mediaType + << "\" => " + << Reference<lang::XServiceInfo>( + xBackend, UNO_QUERY_THROW )->getImplementationName() + << "\"!" ); + } +#endif + } + + // cut out ambiguous filters: + for (auto const& ambiguousFilter : ambiguousFilters) + { + m_filter2mediaType.erase(ambiguousFilter); + } +} + + +Reference<deployment::XPackageRegistry> PackageRegistryImpl::create( + OUString const & context, + OUString const & cachePath, + Reference<XComponentContext> const & xComponentContext ) +{ + rtl::Reference<PackageRegistryImpl> that = new PackageRegistryImpl; + + // auto-detect all registered package registries: + Reference<container::XEnumeration> xEnum( + Reference<container::XContentEnumerationAccess>( + xComponentContext->getServiceManager(), + UNO_QUERY_THROW )->createContentEnumeration( + "com.sun.star.deployment.PackageRegistryBackend" ) ); + if (xEnum.is()) + { + while (xEnum->hasMoreElements()) + { + Any element( xEnum->nextElement() ); + Sequence<Any> registryArgs(cachePath.isEmpty() ? 1 : 3 ); + auto pregistryArgs = registryArgs.getArray(); + pregistryArgs[ 0 ] <<= context; + if (!cachePath.isEmpty()) + { + Reference<lang::XServiceInfo> xServiceInfo( + element, UNO_QUERY_THROW ); + OUString registryCachePath( + makeURL( cachePath, + ::rtl::Uri::encode( + xServiceInfo->getImplementationName(), + rtl_UriCharClassPchar, + rtl_UriEncodeIgnoreEscapes, + RTL_TEXTENCODING_UTF8 ) ) ); + pregistryArgs[ 1 ] <<= registryCachePath; + pregistryArgs[ 2 ] <<= false; // readOnly; + create_folder( nullptr, registryCachePath, + Reference<XCommandEnvironment>() ); + } + + Reference<deployment::XPackageRegistry> xBackend; + Reference<lang::XSingleComponentFactory> xFac( element, UNO_QUERY ); + if (xFac.is()) { + xBackend.set( + xFac->createInstanceWithArgumentsAndContext( + registryArgs, xComponentContext ), UNO_QUERY ); + } + else { + Reference<lang::XSingleServiceFactory> xSingleServiceFac( + element, UNO_QUERY_THROW ); + xBackend.set( + xSingleServiceFac->createInstanceWithArguments( + registryArgs ), UNO_QUERY ); + } + if (! xBackend.is()) { + throw DeploymentException( + "cannot instantiate PackageRegistryBackend service: " + + Reference<lang::XServiceInfo>( + element, UNO_QUERY_THROW )->getImplementationName(), + static_cast<OWeakObject *>(that.get()) ); + } + + that->insertBackend( xBackend ); + } + } + + // Insert bundle back-end. + // Always register as last, because we want to add extensions also as folders + // and as a default we accept every folder, which was not recognized by the other + // backends. + Reference<deployment::XPackageRegistry> extensionBackend = + ::dp_registry::backend::bundle::create( + that, context, cachePath, xComponentContext); + that->insertBackend(extensionBackend); + + Reference<lang::XServiceInfo> xServiceInfo( + extensionBackend, UNO_QUERY_THROW ); + + OSL_ASSERT(xServiceInfo.is()); + OUString registryCachePath( + makeURL( cachePath, + ::rtl::Uri::encode( + xServiceInfo->getImplementationName(), + rtl_UriCharClassPchar, + rtl_UriEncodeIgnoreEscapes, + RTL_TEXTENCODING_UTF8 ) ) ); + create_folder( nullptr, registryCachePath, Reference<XCommandEnvironment>()); + + +#if OSL_DEBUG_LEVEL > 0 + // dump tables: + { + t_registryset allBackends; + dp_misc::TRACE("> [dp_registry.cxx] media-type detection:\n\n" ); + for (auto const& elem : that->m_filter2mediaType) + { + const Reference<deployment::XPackageRegistry> xBackend( + that->m_mediaType2backend.find( elem.second )->second ); + allBackends.insert( xBackend ); + dp_misc::TRACE( + "extension \"" + elem.first + "\" maps to media-type \"" + elem.second + + "\" maps to backend " + + Reference<lang::XServiceInfo>( + xBackend, UNO_QUERY_THROW ) + ->getImplementationName() + + "\n"); + } + dp_misc::TRACE( "> [dp_registry.cxx] ambiguous backends:\n\n" ); + for (auto const& ambiguousBackend : that->m_ambiguousBackends) + { + OUStringBuffer buf; + buf.append( + Reference<lang::XServiceInfo>( + ambiguousBackend, UNO_QUERY_THROW )->getImplementationName() ); + buf.append( ": " ); + const Sequence< Reference<deployment::XPackageTypeInfo> > types( + ambiguousBackend->getSupportedPackageTypes() ); + for ( sal_Int32 pos = 0; pos < types.getLength(); ++pos ) { + Reference<deployment::XPackageTypeInfo> const & xInfo = + types[ pos ]; + buf.append( xInfo->getMediaType() ); + const OUString filter( xInfo->getFileFilter() ); + if (!filter.isEmpty()) { + buf.append( " (" ); + buf.append( filter ); + buf.append( ")" ); + } + if (pos < (types.getLength() - 1)) + buf.append( ", " ); + } + dp_misc::TRACE(buf + "\n\n"); + } + allBackends.insert( that->m_ambiguousBackends.begin(), + that->m_ambiguousBackends.end() ); + OSL_ASSERT( allBackends == that->m_allBackends ); + } +#endif + + return that; +} + +// XUpdatable: broadcast to backends + +void PackageRegistryImpl::update() +{ + check(); + for (auto const& backend : m_allBackends) + { + const Reference<util::XUpdatable> xUpdatable(backend, UNO_QUERY); + if (xUpdatable.is()) + xUpdatable->update(); + } +} + +// XPackageRegistry + +Reference<deployment::XPackage> PackageRegistryImpl::bindPackage( + OUString const & url, OUString const & mediaType_, sal_Bool bRemoved, + OUString const & identifier, Reference<XCommandEnvironment> const & xCmdEnv ) +{ + check(); + OUString mediaType(mediaType_); + if (mediaType.isEmpty()) + { + ::ucbhelper::Content ucbContent; + bool bOk=true; + + try + { + bOk = create_ucb_content( + &ucbContent, url, xCmdEnv, false /* no throw */ ) + && !ucbContent.isFolder(); + } + catch (const css::ucb::ContentCreationException&) + { + bOk = false; + } + + if (bOk) + { + OUString title( StrTitle::getTitle( ucbContent ) ); + for (;;) + { + const t_string2string::const_iterator iFind( + m_filter2mediaType.find(title) ); + if (iFind != m_filter2mediaType.end()) { + mediaType = iFind->second; + break; + } + sal_Int32 point = title.indexOf( '.', 1 /* consume . */ ); + if (point < 0) + break; + title = title.copy(point); + } + } + } + if (mediaType.isEmpty()) + { + // try ambiguous backends: + for (auto const& ambiguousBackend : m_ambiguousBackends) + { + try { + return ambiguousBackend->bindPackage( url, mediaType, bRemoved, + identifier, xCmdEnv ); + } + catch (const lang::IllegalArgumentException &) { + } + } + throw lang::IllegalArgumentException( + DpResId(RID_STR_CANNOT_DETECT_MEDIA_TYPE) + url, + static_cast<OWeakObject *>(this), static_cast<sal_Int16>(-1) ); + } + else + { + // get backend by media-type: + t_string2registry::const_iterator iFind( + m_mediaType2backend.find( normalizeMediaType(mediaType) ) ); + if (iFind == m_mediaType2backend.end()) { + // xxx todo: more sophisticated media-type argument parsing... + sal_Int32 q = mediaType.indexOf( ';' ); + if (q >= 0) { + iFind = m_mediaType2backend.find( + normalizeMediaType( + // cut parameters: + mediaType.subView( 0, q ) ) ); + } + } + if (iFind == m_mediaType2backend.end()) { + throw lang::IllegalArgumentException( + DpResId(RID_STR_UNSUPPORTED_MEDIA_TYPE) + mediaType, + static_cast<OWeakObject *>(this), static_cast<sal_Int16>(-1) ); + } + return iFind->second->bindPackage( url, mediaType, bRemoved, + identifier, xCmdEnv ); + } +} + + +Sequence< Reference<deployment::XPackageTypeInfo> > +PackageRegistryImpl::getSupportedPackageTypes() +{ + return comphelper::containerToSequence(m_typesInfos); +} +} // anon namespace + + +Reference<deployment::XPackageRegistry> create( + OUString const & context, + OUString const & cachePath, + Reference<XComponentContext> const & xComponentContext ) +{ + return PackageRegistryImpl::create( + context, cachePath, xComponentContext ); +} + +} // namespace dp_registry + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/desktop/source/deployment/registry/executable/dp_executable.cxx b/desktop/source/deployment/registry/executable/dp_executable.cxx new file mode 100644 index 000000000..40b253587 --- /dev/null +++ b/desktop/source/deployment/registry/executable/dp_executable.cxx @@ -0,0 +1,336 @@ +/* -*- 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 <memory> +#include <string_view> + +#include <dp_misc.h> +#include <dp_backend.h> +#include <dp_ucb.h> +#include <dp_interact.h> +#include <com/sun/star/lang/IllegalArgumentException.hpp> +#include <osl/file.hxx> +#include <ucbhelper/content.hxx> +#include <svl/inettype.hxx> +#include "dp_executablebackenddb.hxx" +#include <cppuhelper/supportsservice.hxx> + +using namespace ::com::sun::star; +using namespace ::com::sun::star::uno; +using namespace ::com::sun::star::ucb; +using namespace dp_misc; + +namespace dp_registry::backend::executable { +namespace { + +class BackendImpl : public ::dp_registry::backend::PackageRegistryBackend +{ + class ExecutablePackageImpl : public ::dp_registry::backend::Package + { + BackendImpl * getMyBackend() const; + + // Package + virtual beans::Optional< beans::Ambiguous<sal_Bool> > isRegistered_( + ::osl::ResettableMutexGuard & guard, + ::rtl::Reference<dp_misc::AbortChannel> const & abortChannel, + Reference<XCommandEnvironment> const & xCmdEnv ) override; + virtual void processPackage_( + ::osl::ResettableMutexGuard & guard, + bool registerPackage, + bool startup, + ::rtl::Reference<dp_misc::AbortChannel> const & abortChannel, + Reference<XCommandEnvironment> const & xCmdEnv ) override; + + bool getFileAttributes(sal_uInt64& out_Attributes); + bool isUrlTargetInExtension() const; + + public: + ExecutablePackageImpl( + ::rtl::Reference<PackageRegistryBackend> const & myBackend, + OUString const & url, OUString const & name, + Reference<deployment::XPackageTypeInfo> const & xPackageType, + bool bRemoved, OUString const & identifier) + : Package( myBackend, url, name, name /* display-name */, + xPackageType, bRemoved, identifier) + {} + }; + friend class ExecutablePackageImpl; + + // PackageRegistryBackend + virtual Reference<deployment::XPackage> bindPackage_( + OUString const & url, OUString const & mediaType, bool bRemoved, + OUString const & identifier, Reference<XCommandEnvironment> const & xCmdEnv ) override; + + void addDataToDb(OUString const & url); + bool hasActiveEntry(std::u16string_view url); + void revokeEntryFromDb(std::u16string_view url); + + Reference<deployment::XPackageTypeInfo> m_xExecutableTypeInfo; + std::unique_ptr<ExecutableBackendDb> m_backendDb; +public: + BackendImpl( Sequence<Any> const & args, + 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; + + // XPackageRegistry + virtual Sequence< Reference<deployment::XPackageTypeInfo> > SAL_CALL + getSupportedPackageTypes() override; + virtual void SAL_CALL packageRemoved(OUString const & url, OUString const & mediaType) override; + +}; + + +BackendImpl::BackendImpl( + Sequence<Any> const & args, + Reference<XComponentContext> const & xComponentContext ) + : PackageRegistryBackend( args, xComponentContext ), + m_xExecutableTypeInfo(new Package::TypeInfo( + "application/vnd.sun.star.executable", + "", "Executable" ) ) +{ + if (!transientMode()) + { + OUString dbFile = makeURL(getCachePath(), "backenddb.xml"); + m_backendDb.reset( + new ExecutableBackendDb(getComponentContext(), dbFile)); + } +} + +// XServiceInfo +OUString BackendImpl::getImplementationName() +{ + return "com.sun.star.comp.deployment.executable.PackageRegistryBackend"; +} + +sal_Bool BackendImpl::supportsService( const OUString& ServiceName ) +{ + return cppu::supportsService(this, ServiceName); +} + +css::uno::Sequence< OUString > BackendImpl::getSupportedServiceNames() +{ + return { BACKEND_SERVICE_NAME }; +} + +void BackendImpl::addDataToDb(OUString const & url) +{ + if (m_backendDb) + m_backendDb->addEntry(url); +} + +void BackendImpl::revokeEntryFromDb(std::u16string_view url) +{ + if (m_backendDb) + m_backendDb->revokeEntry(url); +} + +bool BackendImpl::hasActiveEntry(std::u16string_view url) +{ + if (m_backendDb) + return m_backendDb->hasActiveEntry(url); + return false; +} + + +// XPackageRegistry +Sequence< Reference<deployment::XPackageTypeInfo> > +BackendImpl::getSupportedPackageTypes() +{ + return Sequence<Reference<deployment::XPackageTypeInfo> >( + & m_xExecutableTypeInfo, 1); +} + +void BackendImpl::packageRemoved(OUString const & url, OUString const & /*mediaType*/) +{ + if (m_backendDb) + m_backendDb->removeEntry(url); +} + +// PackageRegistryBackend +Reference<deployment::XPackage> BackendImpl::bindPackage_( + OUString const & url, OUString const & mediaType, bool bRemoved, + OUString const & identifier, Reference<XCommandEnvironment> const & xCmdEnv ) +{ + if (mediaType.isEmpty()) + { + throw lang::IllegalArgumentException( + StrCannotDetectMediaType() + url, + static_cast<OWeakObject *>(this), static_cast<sal_Int16>(-1) ); + } + + OUString type, subType; + INetContentTypeParameterList params; + if (INetContentTypes::parse( mediaType, type, subType, ¶ms )) + { + if (type.equalsIgnoreAsciiCase("application")) + { + OUString name; + if (!bRemoved) + { + ::ucbhelper::Content ucbContent( + url, xCmdEnv, getComponentContext() ); + name = StrTitle::getTitle( ucbContent ); + } + if (subType.equalsIgnoreAsciiCase("vnd.sun.star.executable")) + { + return new BackendImpl::ExecutablePackageImpl( + this, url, name, m_xExecutableTypeInfo, bRemoved, + identifier); + } + } + } + return Reference<deployment::XPackage>(); +} + + +// Package +BackendImpl * BackendImpl::ExecutablePackageImpl::getMyBackend() const +{ + BackendImpl * pBackend = static_cast<BackendImpl *>(m_myBackend.get()); + if (nullptr == pBackend) + { + //May throw a DisposedException + check(); + //We should never get here... + throw RuntimeException( "Failed to get the BackendImpl", + static_cast<OWeakObject*>(const_cast<ExecutablePackageImpl *>(this))); + } + return pBackend; +} + +beans::Optional< beans::Ambiguous<sal_Bool> > +BackendImpl::ExecutablePackageImpl::isRegistered_( + ::osl::ResettableMutexGuard &, + ::rtl::Reference<dp_misc::AbortChannel> const &, + Reference<XCommandEnvironment> const & ) +{ + bool registered = getMyBackend()->hasActiveEntry(getURL()); + return beans::Optional< beans::Ambiguous<sal_Bool> >( + true /* IsPresent */, + beans::Ambiguous<sal_Bool>( + registered, false /* IsAmbiguous */ ) ); +} + +void BackendImpl::ExecutablePackageImpl::processPackage_( + ::osl::ResettableMutexGuard &, + bool doRegisterPackage, + bool /*startup*/, + ::rtl::Reference<dp_misc::AbortChannel> const & abortChannel, + Reference<XCommandEnvironment> const & /*xCmdEnv*/ ) +{ + checkAborted(abortChannel); + if (doRegisterPackage) + { + if (!isUrlTargetInExtension()) + { + OSL_ASSERT(false); + return; + } + sal_uInt64 attributes = 0; + //Setting the executable attribute does not affect executables on Windows + if (getFileAttributes(attributes)) + { + if(getMyBackend()->m_context == "user") + attributes |= osl_File_Attribute_OwnExe; + else if (getMyBackend()->m_context == "shared") + attributes |= (osl_File_Attribute_OwnExe | osl_File_Attribute_GrpExe + | osl_File_Attribute_OthExe); + else if (getMyBackend()->m_context != "bundled") + //Bundled extensions are required to be in the properly + //installed. That is an executable must have the right flags + OSL_ASSERT(false); + + //This won't have effect on Windows + osl::File::setAttributes( + dp_misc::expandUnoRcUrl(m_url), attributes); + } + getMyBackend()->addDataToDb(getURL()); + } + else + { + getMyBackend()->revokeEntryFromDb(getURL()); + } +} + +//We currently cannot check if this XPackage represents a content of a particular extension +//But we can check if we are within $UNO_USER_PACKAGES_CACHE etc. +//Done for security reasons. For example an extension manifest could contain a path to +//an executable outside the extension. +bool BackendImpl::ExecutablePackageImpl::isUrlTargetInExtension() const +{ + bool bSuccess = false; + OUString sExtensionDir; + if(getMyBackend()->m_context == "user") + sExtensionDir = dp_misc::expandUnoRcTerm("$UNO_USER_PACKAGES_CACHE"); + else if (getMyBackend()->m_context == "shared") + sExtensionDir = dp_misc::expandUnoRcTerm("$UNO_SHARED_PACKAGES_CACHE"); + else if (getMyBackend()->m_context == "bundled") + sExtensionDir = dp_misc::expandUnoRcTerm("$BUNDLED_EXTENSIONS"); + else + OSL_ASSERT(false); + //remove file ellipses + if (osl::File::E_None == osl::File::getAbsoluteFileURL(OUString(), sExtensionDir, sExtensionDir)) + { + OUString sFile; + if (osl::File::E_None == osl::File::getAbsoluteFileURL( + OUString(), dp_misc::expandUnoRcUrl(m_url), sFile)) + { + if (sFile.match(sExtensionDir)) + bSuccess = true; + } + } + return bSuccess; +} + +bool BackendImpl::ExecutablePackageImpl::getFileAttributes(sal_uInt64& out_Attributes) +{ + bool bSuccess = false; + const OUString url(dp_misc::expandUnoRcUrl(m_url)); + osl::DirectoryItem item; + if (osl::FileBase::E_None == osl::DirectoryItem::get(url, item)) + { + osl::FileStatus aStatus(osl_FileStatus_Mask_Attributes); + if( osl::FileBase::E_None == item.getFileStatus(aStatus)) + { + out_Attributes = aStatus.getAttributes(); + bSuccess = true; + } + } + return bSuccess; +} + + +} // anon namespace + + +} // namespace dp_registry::backend::executable + +extern "C" SAL_DLLPUBLIC_EXPORT css::uno::XInterface* +com_sun_star_comp_deployment_executable_PackageRegistryBackend_get_implementation( + css::uno::XComponentContext* context, css::uno::Sequence<css::uno::Any> const& args) +{ + return cppu::acquire(new dp_registry::backend::executable::BackendImpl(args, context)); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/desktop/source/deployment/registry/executable/dp_executablebackenddb.cxx b/desktop/source/deployment/registry/executable/dp_executablebackenddb.cxx new file mode 100644 index 000000000..29cbf85b3 --- /dev/null +++ b/desktop/source/deployment/registry/executable/dp_executablebackenddb.cxx @@ -0,0 +1,64 @@ +/* -*- 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/uno/XComponentContext.hpp> +#include "dp_executablebackenddb.hxx" + + +using namespace ::com::sun::star::uno; + +constexpr OUStringLiteral EXTENSION_REG_NS = u"http://openoffice.org/extensionmanager/executable-registry/2010"; +constexpr OUStringLiteral NS_PREFIX = u"exe"; +constexpr OUStringLiteral ROOT_ELEMENT_NAME = u"executable-backend-db"; +constexpr OUStringLiteral ENTRY_NAME = u"executable"; + +namespace dp_registry::backend::executable { + +ExecutableBackendDb::ExecutableBackendDb( + Reference<XComponentContext> const & xContext, + OUString const & url):RegisteredDb(xContext, url) +{ + +} + +OUString ExecutableBackendDb::getDbNSName() +{ + return EXTENSION_REG_NS; +} + +OUString ExecutableBackendDb::getNSPrefix() +{ + return NS_PREFIX; +} + +OUString ExecutableBackendDb::getRootElementName() +{ + return ROOT_ELEMENT_NAME; +} + +OUString ExecutableBackendDb::getKeyElementName() +{ + return ENTRY_NAME; +} + + +} // namespace dp_registry::backend::executable + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/desktop/source/deployment/registry/executable/dp_executablebackenddb.hxx b/desktop/source/deployment/registry/executable/dp_executablebackenddb.hxx new file mode 100644 index 000000000..0561a8c54 --- /dev/null +++ b/desktop/source/deployment/registry/executable/dp_executablebackenddb.hxx @@ -0,0 +1,55 @@ +/* -*- 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 <rtl/ustring.hxx> +#include <dp_backenddb.hxx> + +namespace com::sun::star::uno +{ +class XComponentContext; +} + +namespace dp_registry::backend::executable +{ +/* The XML file stores the extensions which are currently registered. + They will be removed when they are revoked. + The format looks like this: + +<?xml version="1.0"?> + */ +class ExecutableBackendDb : public dp_registry::backend::RegisteredDb +{ +protected: + virtual OUString getDbNSName() override; + + virtual OUString getNSPrefix() override; + + virtual OUString getRootElementName() override; + + virtual OUString getKeyElementName() override; + +public: + ExecutableBackendDb(css::uno::Reference<css::uno::XComponentContext> const& xContext, + OUString const& url); +}; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/desktop/source/deployment/registry/help/README.md b/desktop/source/deployment/registry/help/README.md new file mode 100644 index 000000000..24ea19518 --- /dev/null +++ b/desktop/source/deployment/registry/help/README.md @@ -0,0 +1 @@ +Support for help integrated in extensions. Also see /README.help.md. diff --git a/desktop/source/deployment/registry/help/dp_help.cxx b/desktop/source/deployment/registry/help/dp_help.cxx new file mode 100644 index 000000000..9e1a75fb9 --- /dev/null +++ b/desktop/source/deployment/registry/help/dp_help.cxx @@ -0,0 +1,621 @@ +/* -*- 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 <memory> +#include <config_features.h> + +#include <strings.hrc> +#include <dp_backend.h> +#include <dp_misc.h> +#include "dp_helpbackenddb.hxx" +#include <dp_ucb.h> +#include <rtl/uri.hxx> +#include <osl/file.hxx> +#include <ucbhelper/content.hxx> +#include <svl/inettype.hxx> +#include <unotools/pathoptions.hxx> +#include <cppuhelper/supportsservice.hxx> +#include <o3tl/string_view.hxx> + +#if HAVE_FEATURE_XMLHELP +#include <helpcompiler/compilehelp.hxx> +#include <helpcompiler/HelpIndexer.hxx> +#endif +#include <com/sun/star/deployment/DeploymentException.hpp> +#include <com/sun/star/deployment/ExtensionRemovedException.hpp> +#include <com/sun/star/ucb/SimpleFileAccess.hpp> +#include <com/sun/star/util/XMacroExpander.hpp> +#include <optional> +#include <string_view> + +using namespace ::dp_misc; +using namespace ::com::sun::star; +using namespace ::com::sun::star::uno; +using namespace ::com::sun::star::ucb; + +namespace dp_registry::backend::help { +namespace { + + +class BackendImpl : public ::dp_registry::backend::PackageRegistryBackend +{ + class PackageImpl : public ::dp_registry::backend::Package + { + BackendImpl * getMyBackend() const; + + // Package + virtual beans::Optional< beans::Ambiguous<sal_Bool> > isRegistered_( + ::osl::ResettableMutexGuard & guard, + ::rtl::Reference<AbortChannel> const & abortChannel, + Reference<XCommandEnvironment> const & xCmdEnv ) override; + virtual void processPackage_( + ::osl::ResettableMutexGuard & guard, + bool registerPackage, + bool startup, + ::rtl::Reference<AbortChannel> const & abortChannel, + Reference<XCommandEnvironment> const & xCmdEnv ) override; + + + public: + PackageImpl( + ::rtl::Reference<PackageRegistryBackend> const & myBackend, + OUString const & url, OUString const & name, + Reference<deployment::XPackageTypeInfo> const & xPackageType, + bool bRemoved, OUString const & identifier); + + bool extensionContainsCompiledHelp(); + + //XPackage + virtual css::beans::Optional< OUString > SAL_CALL getRegistrationDataURL() override; + }; + friend class PackageImpl; + + // PackageRegistryBackend + virtual Reference<deployment::XPackage> bindPackage_( + OUString const & url, OUString const & mediaType, + bool bRemoved, OUString const & identifier, + Reference<XCommandEnvironment> const & xCmdEnv ) override; + + void implProcessHelp( PackageImpl * package, bool doRegisterPackage, + Reference<ucb::XCommandEnvironment> const & xCmdEnv); + void implCollectXhpFiles( const OUString& aDir, + std::vector< OUString >& o_rXhpFileVector ); + + ::std::optional<HelpBackendDb::Data> readDataFromDb(std::u16string_view url); + bool hasActiveEntry(std::u16string_view url); + bool activateEntry(std::u16string_view url); + + Reference< ucb::XSimpleFileAccess3 > const & getFileAccess(); + Reference< ucb::XSimpleFileAccess3 > m_xSFA; + + const Reference<deployment::XPackageTypeInfo> m_xHelpTypeInfo; + Sequence< Reference<deployment::XPackageTypeInfo> > m_typeInfos; + std::unique_ptr<HelpBackendDb> m_backendDb; + +public: + BackendImpl( Sequence<Any> const & args, + 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; + + // XPackageRegistry + virtual Sequence< Reference<deployment::XPackageTypeInfo> > SAL_CALL + getSupportedPackageTypes() override; + virtual void SAL_CALL packageRemoved(OUString const & url, OUString const & mediaType) override; + +}; + + +BackendImpl::BackendImpl( + Sequence<Any> const & args, + Reference<XComponentContext> const & xComponentContext ) + : PackageRegistryBackend( args, xComponentContext ), + m_xHelpTypeInfo( new Package::TypeInfo("application/vnd.sun.star.help", + OUString(), + DpResId(RID_STR_HELP) + ) ), + m_typeInfos{ m_xHelpTypeInfo } +{ + if (transientMode()) + return; + + OUString dbFile = makeURL(getCachePath(), "backenddb.xml"); + m_backendDb.reset( + new HelpBackendDb(getComponentContext(), dbFile)); + + //clean up data folders which are no longer used. + //This must not be done in the same process where the help files + //are still registers. Only after revoking and restarting OOo the folders + //can be removed. This works now, because the extension manager is a singleton + //and the backends are only create once per process. + std::vector<OUString> folders = m_backendDb->getAllDataUrls(); + deleteUnusedFolders(folders); +} + +// XServiceInfo +OUString BackendImpl::getImplementationName() +{ + return "com.sun.star.comp.deployment.help.PackageRegistryBackend"; +} + +sal_Bool BackendImpl::supportsService( const OUString& ServiceName ) +{ + return cppu::supportsService(this, ServiceName); +} + +css::uno::Sequence< OUString > BackendImpl::getSupportedServiceNames() +{ + return { BACKEND_SERVICE_NAME }; +} + +// XPackageRegistry + +Sequence< Reference<deployment::XPackageTypeInfo> > +BackendImpl::getSupportedPackageTypes() +{ + return m_typeInfos; +} + +void BackendImpl::packageRemoved(OUString const & url, OUString const & /*mediaType*/) +{ + if (m_backendDb) + m_backendDb->removeEntry(url); +} + +// PackageRegistryBackend + +Reference<deployment::XPackage> BackendImpl::bindPackage_( + OUString const & url, OUString const & mediaType_, + bool bRemoved, OUString const & identifier, + Reference<XCommandEnvironment> const & xCmdEnv ) +{ + // we don't support auto detection: + if (mediaType_.isEmpty()) + throw lang::IllegalArgumentException( + StrCannotDetectMediaType() + url, + static_cast<OWeakObject *>(this), static_cast<sal_Int16>(-1) ); + + OUString type, subType; + INetContentTypeParameterList params; + if (INetContentTypes::parse( mediaType_, type, subType, ¶ms )) + { + if (type.equalsIgnoreAsciiCase("application")) + { + OUString name; + if (!bRemoved) + { + ::ucbhelper::Content ucbContent( + url, xCmdEnv, getComponentContext() ); + name = StrTitle::getTitle( ucbContent ); + } + + if (subType.equalsIgnoreAsciiCase( "vnd.sun.star.help")) + { + return new PackageImpl( + this, url, name, m_xHelpTypeInfo, bRemoved, + identifier); + } + } + } + throw lang::IllegalArgumentException( + StrUnsupportedMediaType() + mediaType_, + static_cast<OWeakObject *>(this), + static_cast<sal_Int16>(-1) ); +} + +::std::optional<HelpBackendDb::Data> BackendImpl::readDataFromDb( + std::u16string_view url) +{ + ::std::optional<HelpBackendDb::Data> data; + if (m_backendDb) + data = m_backendDb->getEntry(url); + return data; +} + +bool BackendImpl::hasActiveEntry(std::u16string_view url) +{ + if (m_backendDb) + return m_backendDb->hasActiveEntry(url); + return false; +} + +bool BackendImpl::activateEntry(std::u16string_view url) +{ + if (m_backendDb) + return m_backendDb->activateEntry(url); + return false; +} + + +BackendImpl::PackageImpl::PackageImpl( + ::rtl::Reference<PackageRegistryBackend> const & myBackend, + OUString const & url, OUString const & name, + Reference<deployment::XPackageTypeInfo> const & xPackageType, + bool bRemoved, OUString const & identifier) + : Package( myBackend, url, name, name, xPackageType, bRemoved, + identifier) +{ +} + +// Package +BackendImpl * BackendImpl::PackageImpl::getMyBackend() const +{ + BackendImpl * pBackend = static_cast<BackendImpl *>(m_myBackend.get()); + if (nullptr == pBackend) + { + //May throw a DisposedException + check(); + //We should never get here... + throw RuntimeException("Failed to get the BackendImpl", + static_cast<OWeakObject*>(const_cast<PackageImpl *>(this))); + } + return pBackend; +} + +bool BackendImpl::PackageImpl::extensionContainsCompiledHelp() +{ + bool bCompiled = true; + OUString aExpandedHelpURL = dp_misc::expandUnoRcUrl(getURL()); + + ::osl::Directory helpFolder(aExpandedHelpURL); + if ( helpFolder.open() == ::osl::File::E_None) + { + //iterate over the contents of the help folder + //We assume that all folders within the help folder contain language specific + //help files. If just one of them does not contain compiled help then this + //function returns false. + ::osl::DirectoryItem item; + ::osl::File::RC errorNext = ::osl::File::E_None; + while ((errorNext = helpFolder.getNextItem(item)) == ::osl::File::E_None) + { + //No find the language folders + ::osl::FileStatus stat(osl_FileStatus_Mask_Type | osl_FileStatus_Mask_FileName |osl_FileStatus_Mask_FileURL); + if (item.getFileStatus(stat) == ::osl::File::E_None) + { + if (stat.getFileType() != ::osl::FileStatus::Directory) + continue; + + //look if there is the folder help.idxl in the language folder + OUString compUrl(stat.getFileURL() + "/help.idxl"); + ::osl::Directory compiledFolder(compUrl); + if (compiledFolder.open() != ::osl::File::E_None) + { + bCompiled = false; + break; + } + } + else + { + //Error + OSL_ASSERT(false); + bCompiled = false; + break; + } + } + if (errorNext != ::osl::File::E_NOENT + && errorNext != ::osl::File::E_None) + { + //Error + OSL_ASSERT(false); + bCompiled = false; + } + } + return bCompiled; +} + + +beans::Optional< beans::Ambiguous<sal_Bool> > +BackendImpl::PackageImpl::isRegistered_( + ::osl::ResettableMutexGuard &, + ::rtl::Reference<AbortChannel> const &, + Reference<XCommandEnvironment> const & ) +{ + BackendImpl * that = getMyBackend(); + + bool bReg = false; + if (that->hasActiveEntry(getURL())) + bReg = true; + + return beans::Optional< beans::Ambiguous<sal_Bool> >( true, beans::Ambiguous<sal_Bool>( bReg, false ) ); +} + + +void BackendImpl::PackageImpl::processPackage_( + ::osl::ResettableMutexGuard &, + bool doRegisterPackage, + bool /* startup */, + ::rtl::Reference<AbortChannel> const &, + Reference<XCommandEnvironment> const & xCmdEnv ) +{ + BackendImpl* that = getMyBackend(); + that->implProcessHelp( this, doRegisterPackage, xCmdEnv); +} + +beans::Optional< OUString > BackendImpl::PackageImpl::getRegistrationDataURL() +{ + if (m_bRemoved) + throw deployment::ExtensionRemovedException(); + + ::std::optional<HelpBackendDb::Data> data = + getMyBackend()->readDataFromDb(getURL()); + + if (data && getMyBackend()->hasActiveEntry(getURL())) + return beans::Optional<OUString>(true, data->dataUrl); + + return beans::Optional<OUString>(true, OUString()); +} + +void BackendImpl::implProcessHelp( + PackageImpl * package, bool doRegisterPackage, + Reference<ucb::XCommandEnvironment> const & xCmdEnv) +{ + Reference< deployment::XPackage > xPackage(package); + OSL_ASSERT(xPackage.is()); + if (doRegisterPackage) + { + //revive already processed help if possible + if ( !activateEntry(xPackage->getURL())) + { + HelpBackendDb::Data data; + data.dataUrl = xPackage->getURL(); + if (!package->extensionContainsCompiledHelp()) + { +#if HAVE_FEATURE_XMLHELP + const OUString sHelpFolder = createFolder(xCmdEnv); + data.dataUrl = sHelpFolder; + + Reference< ucb::XSimpleFileAccess3 > xSFA = getFileAccess(); + OUString aHelpURL = xPackage->getURL(); + OUString aExpandedHelpURL = dp_misc::expandUnoRcUrl( aHelpURL ); + if( !xSFA->isFolder( aExpandedHelpURL ) ) + { + OUString aErrStr = DpResId( RID_STR_HELPPROCESSING_GENERAL_ERROR ) + + "No help folder"; + OWeakObject* oWeakThis = this; + throw deployment::DeploymentException( OUString(), oWeakThis, + Any( uno::Exception( aErrStr, oWeakThis ) ) ); + } + + // Scan languages + Sequence< OUString > aLanguageFolderSeq = xSFA->getFolderContents( aExpandedHelpURL, true ); + sal_Int32 nLangCount = aLanguageFolderSeq.getLength(); + const OUString* pSeq = aLanguageFolderSeq.getConstArray(); + for( sal_Int32 iLang = 0 ; iLang < nLangCount ; ++iLang ) + { + OUString aLangURL = pSeq[iLang]; + if( xSFA->isFolder( aLangURL ) ) + { + std::vector< OUString > aXhpFileVector; + + // calculate jar file URL + sal_Int32 indexStartSegment = aLangURL.lastIndexOf('/'); + // for example "/en" + OUString langFolderURLSegment( + aLangURL.copy( + indexStartSegment + 1, aLangURL.getLength() - indexStartSegment - 1)); + + //create the folder in the "temporary folder" + ::ucbhelper::Content langFolderContent; + const OUString langFolderDest = makeURL(sHelpFolder, langFolderURLSegment); + const OUString langFolderDestExpanded = ::dp_misc::expandUnoRcUrl(langFolderDest); + ::dp_misc::create_folder( + &langFolderContent, + langFolderDest, xCmdEnv); + + static const OUStringLiteral aHelpStr(u"help"); + + OUString aJarFile( + makeURL(sHelpFolder, langFolderURLSegment + "/" + aHelpStr + ".jar")); + aJarFile = ::dp_misc::expandUnoRcUrl(aJarFile); + + OUString aEncodedJarFilePath = rtl::Uri::encode( + aJarFile, rtl_UriCharClassPchar, + rtl_UriEncodeIgnoreEscapes, + RTL_TEXTENCODING_UTF8 ); + OUString aDestBasePath = "vnd.sun.star.zip://" + + aEncodedJarFilePath + "/" ; + + sal_Int32 nLenLangFolderURL = aLangURL.getLength() + 1; + + Sequence< OUString > aSubLangSeq = xSFA->getFolderContents( aLangURL, true ); + sal_Int32 nSubLangCount = aSubLangSeq.getLength(); + const OUString* pSubLangSeq = aSubLangSeq.getConstArray(); + for( sal_Int32 iSubLang = 0 ; iSubLang < nSubLangCount ; ++iSubLang ) + { + OUString aSubFolderURL = pSubLangSeq[iSubLang]; + if( !xSFA->isFolder( aSubFolderURL ) ) + continue; + + implCollectXhpFiles( aSubFolderURL, aXhpFileVector ); + + // Copy to package (later: move?) + std::u16string_view aPureFolderName = aSubFolderURL.subView( nLenLangFolderURL ); + OUString aDestPath = aDestBasePath + aPureFolderName; + xSFA->copy( aSubFolderURL, aDestPath ); + } + + // Call compiler + sal_Int32 nXhpFileCount = aXhpFileVector.size(); + std::unique_ptr<OUString[]> pXhpFiles(new OUString[nXhpFileCount]); + for( sal_Int32 iXhp = 0 ; iXhp < nXhpFileCount ; ++iXhp ) + { + OUString aXhpFile = aXhpFileVector[iXhp]; + OUString aXhpRelFile = aXhpFile.copy( nLenLangFolderURL ); + pXhpFiles[iXhp] = aXhpRelFile; + } + + OUString aOfficeHelpPath( SvtPathOptions().GetHelpPath() ); + OUString aOfficeHelpPathFileURL; + ::osl::File::getFileURLFromSystemPath( aOfficeHelpPath, aOfficeHelpPathFileURL ); + + HelpProcessingErrorInfo aErrorInfo; + bool bSuccess = compileExtensionHelp( + aOfficeHelpPathFileURL, aHelpStr, aLangURL, + nXhpFileCount, pXhpFiles.get(), + langFolderDestExpanded, aErrorInfo ); + + pXhpFiles.reset(); + + if( bSuccess ) + { + OUString aLang; + sal_Int32 nLastSlash = aLangURL.lastIndexOf( '/' ); + if( nLastSlash != -1 ) + aLang = aLangURL.copy( nLastSlash + 1 ); + else + aLang = "en"; + + HelpIndexer aIndexer(aLang, "help", langFolderDestExpanded, langFolderDestExpanded); + aIndexer.indexDocuments(); + } + + if( !bSuccess ) + { + TranslateId pErrStrId; + switch( aErrorInfo.m_eErrorClass ) + { + case HelpProcessingErrorClass::General: pErrStrId = RID_STR_HELPPROCESSING_GENERAL_ERROR; break; + case HelpProcessingErrorClass::XmlParsing: pErrStrId = RID_STR_HELPPROCESSING_XMLPARSING_ERROR; break; + default: ; + }; + + OUString aErrStr; + if (pErrStrId) + { + aErrStr = DpResId(pErrStrId); + + // Remove CR/LF + OUString aErrMsg( aErrorInfo.m_aErrorMsg ); + sal_Unicode const nCR = 13, nLF = 10; + sal_Int32 nSearchCR = aErrMsg.indexOf( nCR ); + sal_Int32 nSearchLF = aErrMsg.indexOf( nLF ); + if( nSearchCR != -1 || nSearchLF != -1 ) + { + sal_Int32 nCopy; + if( nSearchCR == -1 ) + nCopy = nSearchLF; + else if( nSearchLF == -1 ) + nCopy = nSearchCR; + else + nCopy = ( nSearchCR < nSearchLF ) ? nSearchCR : nSearchLF; + + aErrMsg = aErrMsg.copy( 0, nCopy ); + } + aErrStr += aErrMsg; + if (pErrStrId != RID_STR_HELPPROCESSING_XMLPARSING_ERROR && !aErrorInfo.m_aXMLParsingFile.isEmpty() ) + { + aErrStr += " in "; + + OUString aDecodedFile = rtl::Uri::decode( aErrorInfo.m_aXMLParsingFile, + rtl_UriDecodeWithCharset, RTL_TEXTENCODING_UTF8 ); + aErrStr += aDecodedFile; + if( aErrorInfo.m_nXMLParsingLine != -1 ) + { + aErrStr += ", line " + + OUString::number( aErrorInfo.m_nXMLParsingLine ); + } + } + } + + OWeakObject* oWeakThis = this; + throw deployment::DeploymentException( OUString(), oWeakThis, + Any( uno::Exception( aErrStr, oWeakThis ) ) ); + } + } + } +#else + (void) xCmdEnv; +#endif + } + // Writing the data entry replaces writing the flag file. If we got to this + // point the registration was successful. + if (m_backendDb) + m_backendDb->addEntry(xPackage->getURL(), data); + } + } //if (doRegisterPackage) + else + { + if (m_backendDb) + m_backendDb->revokeEntry(xPackage->getURL()); + } +} + +void BackendImpl::implCollectXhpFiles( const OUString& aDir, + std::vector< OUString >& o_rXhpFileVector ) +{ + Reference< ucb::XSimpleFileAccess3 > xSFA = getFileAccess(); + + // Scan xhp files recursively + Sequence< OUString > aSeq = xSFA->getFolderContents( aDir, true ); + sal_Int32 nCount = aSeq.getLength(); + const OUString* pSeq = aSeq.getConstArray(); + for( sal_Int32 i = 0 ; i < nCount ; ++i ) + { + OUString aURL = pSeq[i]; + if( xSFA->isFolder( aURL ) ) + { + implCollectXhpFiles( aURL, o_rXhpFileVector ); + } + else + { + sal_Int32 nLastDot = aURL.lastIndexOf( '.' ); + if( nLastDot != -1 ) + { + std::u16string_view aExt = aURL.subView( nLastDot + 1 ); + if( o3tl::equalsIgnoreAsciiCase( aExt, u"xhp" ) ) + o_rXhpFileVector.push_back( aURL ); + } + } + } +} + +Reference< ucb::XSimpleFileAccess3 > const & BackendImpl::getFileAccess() +{ + if( !m_xSFA.is() ) + { + Reference<XComponentContext> const & xContext = getComponentContext(); + if( xContext.is() ) + { + m_xSFA = ucb::SimpleFileAccess::create(xContext); + } + if( !m_xSFA.is() ) + { + throw RuntimeException( + "dp_registry::backend::help::BackendImpl::getFileAccess(), " + "could not instantiate SimpleFileAccess." ); + } + } + return m_xSFA; +} + +} // anon namespace + +} // namespace dp_registry + +extern "C" SAL_DLLPUBLIC_EXPORT css::uno::XInterface* +com_sun_star_comp_deployment_help_PackageRegistryBackend_get_implementation( + css::uno::XComponentContext* context, css::uno::Sequence<css::uno::Any> const& args) +{ + return cppu::acquire(new dp_registry::backend::help::BackendImpl(args, context)); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/desktop/source/deployment/registry/help/dp_helpbackenddb.cxx b/desktop/source/deployment/registry/help/dp_helpbackenddb.cxx new file mode 100644 index 000000000..19bb3c3ce --- /dev/null +++ b/desktop/source/deployment/registry/help/dp_helpbackenddb.cxx @@ -0,0 +1,126 @@ +/* -*- 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/exc_hlp.hxx> +#include <com/sun/star/deployment/DeploymentException.hpp> +#include <com/sun/star/uno/XComponentContext.hpp> + +#include "dp_helpbackenddb.hxx" + + +using namespace ::com::sun::star::uno; + +constexpr OUStringLiteral EXTENSION_REG_NS = u"http://openoffice.org/extensionmanager/help-registry/2010"; +constexpr OUStringLiteral NS_PREFIX = u"help"; +constexpr OUStringLiteral ROOT_ELEMENT_NAME = u"help-backend-db"; +constexpr OUStringLiteral KEY_ELEMENT_NAME = u"help"; + +namespace dp_registry::backend::help { + +HelpBackendDb::HelpBackendDb( + Reference<XComponentContext> const & xContext, + OUString const & url):BackendDb(xContext, url) +{ + +} + +OUString HelpBackendDb::getDbNSName() +{ + return EXTENSION_REG_NS; +} + +OUString HelpBackendDb::getNSPrefix() +{ + return NS_PREFIX; +} + +OUString HelpBackendDb::getRootElementName() +{ + return ROOT_ELEMENT_NAME; +} + +OUString HelpBackendDb::getKeyElementName() +{ + return KEY_ELEMENT_NAME; +} + + +void HelpBackendDb::addEntry(OUString const & url, Data const & data) +{ + try{ + if (!activateEntry(url)) + { + Reference<css::xml::dom::XNode> helpNode + = writeKeyElement(url); + + writeSimpleElement(u"data-url", data.dataUrl, helpNode); + save(); + } + } + catch ( const css::deployment::DeploymentException& ) + { + throw; + } + catch(const css::uno::Exception &) + { + Any exc( ::cppu::getCaughtException() ); + throw css::deployment::DeploymentException( + "Extension Manager: failed to write data entry in help backend db: " + m_urlDb, nullptr, exc); + } +} + + +::std::optional<HelpBackendDb::Data> +HelpBackendDb::getEntry(std::u16string_view url) +{ + try + { + HelpBackendDb::Data retData; + Reference<css::xml::dom::XNode> aNode = getKeyElement(url); + if (aNode.is()) + { + retData.dataUrl = readSimpleElement(u"data-url", aNode); + } + else + { + return ::std::optional<Data>(); + } + return ::std::optional<Data>(retData); + } + catch ( const css::deployment::DeploymentException& ) + { + throw; + } + catch(const css::uno::Exception &) + { + Any exc( ::cppu::getCaughtException() ); + throw css::deployment::DeploymentException( + "Extension Manager: failed to read data entry in help backend db: " + m_urlDb, nullptr, exc); + } +} + +std::vector<OUString> HelpBackendDb::getAllDataUrls() +{ + return getOneChildFromAllEntries(u"data-url"); +} + +} // namespace dp_registry::backend::help + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/desktop/source/deployment/registry/help/dp_helpbackenddb.hxx b/desktop/source/deployment/registry/help/dp_helpbackenddb.hxx new file mode 100644 index 000000000..a46bd8663 --- /dev/null +++ b/desktop/source/deployment/registry/help/dp_helpbackenddb.hxx @@ -0,0 +1,70 @@ +/* -*- 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 <rtl/ustring.hxx> +#include <optional> +#include <string_view> + +#include <dp_backenddb.hxx> + +namespace com::sun::star::uno +{ +class XComponentContext; +} + +namespace dp_registry::backend::help +{ +/* The XML file stores the extensions which are currently registered. + They will be removed when they are revoked. + */ +class HelpBackendDb : public dp_registry::backend::BackendDb +{ +protected: + virtual OUString getDbNSName() override; + + virtual OUString getNSPrefix() override; + + virtual OUString getRootElementName() override; + + virtual OUString getKeyElementName() override; + +public: + struct Data + { + /* the URL to the folder containing the compiled help files, etc. + */ + OUString dataUrl; + }; + +public: + HelpBackendDb(css::uno::Reference<css::uno::XComponentContext> const& xContext, + OUString const& url); + + void addEntry(OUString const& url, Data const& data); + + ::std::optional<Data> getEntry(std::u16string_view url); + //must also return the data urls for entries with @active="false". That is, + //those are currently revoked. + std::vector<OUString> getAllDataUrls(); +}; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/desktop/source/deployment/registry/inc/dp_backend.h b/desktop/source/deployment/registry/inc/dp_backend.h new file mode 100644 index 000000000..0fe39db45 --- /dev/null +++ b/desktop/source/deployment/registry/inc/dp_backend.h @@ -0,0 +1,287 @@ +/* -*- 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_shared.hxx> +#include <dp_interact.h> +#include <rtl/ref.hxx> +#include <cppuhelper/basemutex.hxx> +#include <cppuhelper/implbase.hxx> +#include <cppuhelper/compbase.hxx> +#include <com/sun/star/lang/XEventListener.hpp> +#include <com/sun/star/lang/XServiceInfo.hpp> +#include <com/sun/star/deployment/XPackageRegistry.hpp> +#include <com/sun/star/uno/XComponentContext.hpp> +#include <unordered_map> +#include <strings.hrc> +#include <utility> + +namespace dp_registry::backend +{ + +class PackageRegistryBackend; + +inline constexpr OUStringLiteral BACKEND_SERVICE_NAME = u"com.sun.star.deployment.PackageRegistryBackend"; + +typedef ::cppu::WeakComponentImplHelper< + css::deployment::XPackage > t_PackageBase; + + +class Package : protected cppu::BaseMutex, public t_PackageBase +{ + PackageRegistryBackend * getMyBackend() const; + void processPackage_impl( + bool registerPackage, + bool startup, + css::uno::Reference<css::task::XAbortChannel> const & xAbortChannel, + css::uno::Reference<css::ucb::XCommandEnvironment> const & xCmdEnv ); + +protected: + ::rtl::Reference<PackageRegistryBackend> m_myBackend; + const OUString m_url; + OUString m_name; + OUString m_displayName; + const css::uno::Reference<css::deployment::XPackageTypeInfo> m_xPackageType; + const bool m_bRemoved; + //Only set if m_bRemoved = true; + const OUString m_identifier; + + void check() const; + void fireModified(); + virtual void SAL_CALL disposing() override; + + void checkAborted( + ::rtl::Reference< ::dp_misc::AbortChannel > const & abortChannel ); + + // @@@ to be implemented by specific backend: + virtual css::beans::Optional< css::beans::Ambiguous<sal_Bool> > + isRegistered_( + ::osl::ResettableMutexGuard & guard, + ::rtl::Reference< ::dp_misc::AbortChannel > const & abortChannel, + css::uno::Reference<css::ucb::XCommandEnvironment> const & xCmdEnv ) + = 0; + virtual void processPackage_( + ::osl::ResettableMutexGuard & guard, + bool registerPackage, + bool startup, + ::rtl::Reference< ::dp_misc::AbortChannel > const & abortChannel, + css::uno::Reference<css::ucb::XCommandEnvironment> const & xCmdEnv ) + = 0; + + virtual ~Package() override; + Package( ::rtl::Reference<PackageRegistryBackend> myBackend, + OUString url, + OUString name, + OUString displayName, + css::uno::Reference<css::deployment::XPackageTypeInfo> const & xPackageType, + bool bRemoved, + OUString identifier); + +public: + + class TypeInfo : + public ::cppu::WeakImplHelper<css::deployment::XPackageTypeInfo> + { + const OUString m_mediaType; + const OUString m_fileFilter; + const OUString m_shortDescr; + public: + virtual ~TypeInfo() override; + TypeInfo( OUString mediaType, + OUString fileFilter, + OUString shortDescr ) + : m_mediaType(std::move(mediaType)), m_fileFilter(std::move(fileFilter)), + m_shortDescr(std::move(shortDescr)) + {} + // XPackageTypeInfo + virtual OUString SAL_CALL getMediaType() override; + virtual OUString SAL_CALL getDescription() override; + virtual OUString SAL_CALL getShortDescription() override; + virtual OUString SAL_CALL getFileFilter() override; + virtual css::uno::Any SAL_CALL getIcon( sal_Bool highContrast, + sal_Bool smallIcon ) override; + }; + + // 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; + + // XPackage + virtual css::uno::Reference<css::task::XAbortChannel> SAL_CALL + createAbortChannel() override; + virtual css::beans::Optional< css::beans::Ambiguous<sal_Bool> > + SAL_CALL isRegistered( + css::uno::Reference<css::task::XAbortChannel> const & xAbortChannel, + css::uno::Reference<css::ucb::XCommandEnvironment> const & xCmdEnv ) override; + + virtual ::sal_Int32 SAL_CALL checkPrerequisites( + const css::uno::Reference< css::task::XAbortChannel >& xAbortChannel, + const css::uno::Reference< css::ucb::XCommandEnvironment >& xCmdEnv, + sal_Bool noLicenseChecking) override; + + virtual ::sal_Bool SAL_CALL checkDependencies( + const css::uno::Reference< css::ucb::XCommandEnvironment >& xCmdEnv ) override; + + virtual void SAL_CALL registerPackage( + sal_Bool startup, + css::uno::Reference<css::task::XAbortChannel> const & xAbortChannel, + css::uno::Reference<css::ucb::XCommandEnvironment> const & xCmdEnv ) override; + virtual void SAL_CALL revokePackage( + sal_Bool startup, + css::uno::Reference<css::task::XAbortChannel> const & xAbortChannel, + css::uno::Reference<css::ucb::XCommandEnvironment> const & xCmdEnv ) override; + virtual sal_Bool SAL_CALL isBundle() override; + virtual css::uno::Sequence< css::uno::Reference<css::deployment::XPackage> > + SAL_CALL getBundle( + css::uno::Reference<css::task::XAbortChannel> const & xAbortChannel, + css::uno::Reference<css::ucb::XCommandEnvironment> const & xCmdEnv ) override; + virtual OUString SAL_CALL getName() override; + virtual css::beans::Optional< OUString > SAL_CALL getIdentifier() override; + virtual OUString SAL_CALL getVersion() override; + virtual OUString SAL_CALL getURL() override; + virtual OUString SAL_CALL getDisplayName() override; + virtual OUString SAL_CALL getDescription() override; + virtual OUString SAL_CALL getLicenseText() override; + virtual css::uno::Sequence< OUString > SAL_CALL + getUpdateInformationURLs() override; + virtual css::beans::StringPair SAL_CALL getPublisherInfo() override; + virtual css::uno::Reference< css::graphic::XGraphic > SAL_CALL + getIcon( sal_Bool bHighContrast ) override; + virtual css::uno::Reference<css::deployment::XPackageTypeInfo> SAL_CALL + getPackageType() override; + virtual void SAL_CALL exportTo( + OUString const & destFolderURL, + OUString const & newTitle, + sal_Int32 nameClashAction, + css::uno::Reference<css::ucb::XCommandEnvironment> const & xCmdEnv ) override; + virtual OUString SAL_CALL getRepositoryName() override; + virtual css::beans::Optional< OUString > SAL_CALL getRegistrationDataURL() override; + virtual sal_Bool SAL_CALL isRemoved() override; + +}; + +typedef ::cppu::WeakComponentImplHelper< + css::lang::XEventListener, + css::deployment::XPackageRegistry, + css::lang::XServiceInfo > t_BackendBase; + + +class PackageRegistryBackend + : protected cppu::BaseMutex, public t_BackendBase +{ + //The map held originally WeakReferences. The map entries are removed in the disposing + //function, which is called when the XPackages are destructed or they are + //explicitly disposed. The latter happens, for example, when an extension is + //removed (see dp_manager.cxx). However, because of how the help systems work, now + // XPackageManager::getDeployedPackages is called often. This results in a lot + //of bindPackage calls which are costly. Therefore we keep hard references in + //the map now. + typedef std::unordered_map< + OUString, css::uno::Reference<css::deployment::XPackage> > t_string2ref; + t_string2ref m_bound; + +protected: + OUString m_cachePath; + css::uno::Reference<css::uno::XComponentContext> m_xComponentContext; + + OUString m_context; + // currently only for library containers: + enum class Context { + Unknown, User, Shared, Bundled, Tmp, Document + } m_eContext; + + static OUString StrCannotDetectMediaType() { return DpResId(RID_STR_CANNOT_DETECT_MEDIA_TYPE); } + static OUString StrUnsupportedMediaType() { return DpResId(RID_STR_UNSUPPORTED_MEDIA_TYPE); } + + // @@@ to be implemented by specific backend: + virtual css::uno::Reference<css::deployment::XPackage> bindPackage_( + OUString const & url, OUString const & mediaType, + bool bRemoved, OUString const & identifier, + css::uno::Reference<css::ucb::XCommandEnvironment> const & xCmdEnv ) + = 0; + + void check(); + virtual void SAL_CALL disposing() override; + + virtual ~PackageRegistryBackend() override; + PackageRegistryBackend( + css::uno::Sequence<css::uno::Any> const & args, + css::uno::Reference<css::uno::XComponentContext> const & xContext ); + + /* creates a folder with a unique name. + If url is empty then it is created in the backend folder, otherwise + at a location relative to that folder specified by url. + */ + OUString createFolder( + css::uno::Reference<css::ucb::XCommandEnvironment> const & xCmdEnv); + /* deletes folders and files. + + All folder all files which end with ".tmp" or ".tmp_" and which are + not used are deleted. + */ + void deleteUnusedFolders( + std::vector< OUString> const & usedFolders); + /* deletes one folder with a "temporary" name and the corresponding + tmp file, which was used to derive the folder name. + */ + static void deleteTempFolder( + OUString const & folderUrl); + +public: + static OUString StrRegisteringPackage() { return DpResId(RID_STR_REGISTERING_PACKAGE); } + static OUString StrRevokingPackage() { return DpResId(RID_STR_REVOKING_PACKAGE); } + + css::uno::Reference<css::uno::XComponentContext> const & + getComponentContext() const { return m_xComponentContext; } + + OUString const & getCachePath() const { return m_cachePath; } + bool transientMode() const { return m_cachePath.isEmpty(); } + + const OUString& getContext() const {return m_context; } + + // XEventListener + virtual void SAL_CALL disposing( css::lang::EventObject const & evt ) override; + + // XPackageRegistry + virtual css::uno::Reference<css::deployment::XPackage> SAL_CALL bindPackage( + OUString const & url, OUString const & mediaType, + sal_Bool bRemoved, OUString const & identifier, + css::uno::Reference<css::ucb::XCommandEnvironment> const & xCmdEnv ) override; + +// virtual void SAL_CALL packageRemoved( +// OUString const & url, OUString const & mediaType) +// throw (css::deployment::DeploymentException, +// css::uno::RuntimeException); + +}; + +} + + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/desktop/source/deployment/registry/inc/dp_backenddb.hxx b/desktop/source/deployment/registry/inc/dp_backenddb.hxx new file mode 100644 index 000000000..785201466 --- /dev/null +++ b/desktop/source/deployment/registry/inc/dp_backenddb.hxx @@ -0,0 +1,165 @@ +/* -*- 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/uno/Reference.hxx> +#include <rtl/ustring.hxx> +#include <deque> +#include <string_view> +#include <vector> + +namespace com::sun::star { + namespace uno { + class XComponentContext; + } + namespace xml::dom { + class XDocument; + class XNode; + } + namespace xml::xpath { + class XXPathAPI; + } +} + +namespace dp_registry::backend { + +class BackendDb +{ +private: + + css::uno::Reference<css::xml::dom::XDocument> m_doc; + css::uno::Reference<css::xml::xpath::XXPathAPI> m_xpathApi; + + BackendDb(BackendDb const &) = delete; + BackendDb & operator = (BackendDb const &) = delete; + +protected: + const css::uno::Reference<css::uno::XComponentContext> m_xContext; + OUString m_urlDb; + +protected: + + /* caller must make sure that only one thread accesses the function + */ + css::uno::Reference<css::xml::dom::XDocument> const & getDocument(); + + /* the namespace prefix is "reg" (without quotes) + */ + css::uno::Reference<css::xml::xpath::XXPathAPI> const & getXPathAPI(); + void save(); + void removeElement(OUString const & sXPathExpression); + + css::uno::Reference<css::xml::dom::XNode> getKeyElement( + std::u16string_view url); + + void writeSimpleList( + std::deque< OUString> const & list, + std::u16string_view sListTagName, + std::u16string_view sMemberTagName, + css::uno::Reference<css::xml::dom::XNode> const & xParent); + + void writeVectorOfPair( + std::vector< std::pair< OUString, OUString > > const & vecPairs, + std::u16string_view sVectorTagName, + std::u16string_view sPairTagName, + std::u16string_view sFirstTagName, + std::u16string_view sSecondTagName, + css::uno::Reference<css::xml::dom::XNode> const & xParent); + + void writeSimpleElement( + std::u16string_view sElementName, OUString const & value, + css::uno::Reference<css::xml::dom::XNode> const & xParent); + + css::uno::Reference<css::xml::dom::XNode> writeKeyElement( + OUString const & url); + + OUString readSimpleElement( + std::u16string_view sElementName, + css::uno::Reference<css::xml::dom::XNode> const & xParent); + + std::vector< std::pair< OUString, OUString > > + readVectorOfPair( + css::uno::Reference<css::xml::dom::XNode> const & parent, + std::u16string_view sListTagName, + std::u16string_view sPairTagName, + std::u16string_view sFirstTagName, + std::u16string_view sSecondTagName); + + std::deque< OUString> readList( + css::uno::Reference<css::xml::dom::XNode> const & parent, + std::u16string_view sListTagName, + std::u16string_view sMemberTagName); + + /* returns the values of one particularly child element of all key elements. + */ + std::vector< OUString> getOneChildFromAllEntries( + std::u16string_view sElementName); + + + /* returns the namespace which is to be written as xmlns attribute + into the root element. + */ + virtual OUString getDbNSName()=0; + /* return the namespace prefix which is to be registered with the XPath API. + + The prefix can then be used in XPath expressions. + */ + virtual OUString getNSPrefix()=0; + /* returns the name of the root element without any namespace prefix. + */ + virtual OUString getRootElementName()=0; + /* returns the name of xml element for each entry + */ + virtual OUString getKeyElementName()=0; + +public: + BackendDb(css::uno::Reference<css::uno::XComponentContext> const & xContext, + OUString const & url); + virtual ~BackendDb() {}; + + void removeEntry(std::u16string_view url); + + /* This is called to write the "revoked" attribute to the entry. + This is done when XPackage::revokePackage is called. + */ + void revokeEntry(std::u16string_view url); + + /* returns false if the entry does not exist yet. + */ + bool activateEntry(std::u16string_view url); + + bool hasActiveEntry(std::u16string_view url); + +}; + +class RegisteredDb: public BackendDb +{ + +public: + RegisteredDb( css::uno::Reference<css::uno::XComponentContext> const & xContext, + OUString const & url); + + + void addEntry(OUString const & url); +}; + +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/desktop/source/deployment/registry/package/dp_extbackenddb.cxx b/desktop/source/deployment/registry/package/dp_extbackenddb.cxx new file mode 100644 index 000000000..8e656c5d7 --- /dev/null +++ b/desktop/source/deployment/registry/package/dp_extbackenddb.cxx @@ -0,0 +1,111 @@ +/* -*- 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/exc_hlp.hxx> +#include <com/sun/star/deployment/DeploymentException.hpp> +#include <com/sun/star/uno/XComponentContext.hpp> + +#include "dp_extbackenddb.hxx" + + +using namespace ::com::sun::star::uno; + +constexpr OUStringLiteral EXTENSION_REG_NS = u"http://openoffice.org/extensionmanager/extension-registry/2010"; +constexpr OUStringLiteral NS_PREFIX = u"ext"; +constexpr OUStringLiteral ROOT_ELEMENT_NAME = u"extension-backend-db"; +constexpr OUStringLiteral KEY_ELEMENT_NAME = u"extension"; + +namespace dp_registry::backend::bundle { + +ExtensionBackendDb::ExtensionBackendDb( + Reference<XComponentContext> const & xContext, + OUString const & url):BackendDb(xContext, url) +{ + +} + +OUString ExtensionBackendDb::getDbNSName() +{ + return EXTENSION_REG_NS; +} + +OUString ExtensionBackendDb::getNSPrefix() +{ + return NS_PREFIX; +} + +OUString ExtensionBackendDb::getRootElementName() +{ + return ROOT_ELEMENT_NAME; +} + +OUString ExtensionBackendDb::getKeyElementName() +{ + return KEY_ELEMENT_NAME; +} + +void ExtensionBackendDb::addEntry(OUString const & url, Data const & data) +{ + try{ + //reactive revoked entry if possible. + if (!activateEntry(url)) + { + Reference<css::xml::dom::XNode> extensionNodeNode = writeKeyElement(url); + writeVectorOfPair( data.items, u"extension-items", u"item", + u"url", u"media-type", extensionNodeNode); + save(); + } + } + catch(const css::uno::Exception &) + { + Any exc( ::cppu::getCaughtException() ); + throw css::deployment::DeploymentException( + "Extension Manager: failed to write data entry in backend db: " + + m_urlDb, nullptr, exc); + } +} + +ExtensionBackendDb::Data ExtensionBackendDb::getEntry(std::u16string_view url) +{ + try + { + ExtensionBackendDb::Data retData; + Reference<css::xml::dom::XNode> aNode = getKeyElement(url); + + if (aNode.is()) + { + retData.items = + readVectorOfPair( aNode, u"extension-items", u"item", + u"url", u"media-type"); + } + return retData; + } + catch(const css::uno::Exception &) + { + Any exc( ::cppu::getCaughtException() ); + throw css::deployment::DeploymentException( + "Extension Manager: failed to read data entry in backend db: " + + m_urlDb, nullptr, exc); + } +} + +} // namespace dp_registry::backend::bundle + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/desktop/source/deployment/registry/package/dp_extbackenddb.hxx b/desktop/source/deployment/registry/package/dp_extbackenddb.hxx new file mode 100644 index 000000000..fb736e6e2 --- /dev/null +++ b/desktop/source/deployment/registry/package/dp_extbackenddb.hxx @@ -0,0 +1,69 @@ +/* -*- 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 <sal/config.h> + +#include <string_view> +#include <utility> +#include <vector> + +#include <rtl/ustring.hxx> + +#include <dp_backenddb.hxx> + +namespace com::sun::star::uno +{ +class XComponentContext; +} + +namespace dp_registry::backend::bundle +{ +/* The XML file stores the extensions which are currently registered. + They will be removed when they are revoked. + */ +class ExtensionBackendDb : public dp_registry::backend::BackendDb +{ +protected: + virtual OUString getDbNSName() override; + virtual OUString getNSPrefix() override; + virtual OUString getRootElementName() override; + virtual OUString getKeyElementName() override; + +public: + struct Data + { + /* every element consists of a pair of the url to the item (jar,rdb, etc) + and the media type + */ + std::vector<std::pair<OUString, OUString>> items; + }; + +public: + ExtensionBackendDb(css::uno::Reference<css::uno::XComponentContext> const& xContext, + OUString const& url); + + void addEntry(OUString const& url, Data const& data); + + Data getEntry(std::u16string_view url); +}; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/desktop/source/deployment/registry/package/dp_package.cxx b/desktop/source/deployment/registry/package/dp_package.cxx new file mode 100644 index 000000000..36b761b54 --- /dev/null +++ b/desktop/source/deployment/registry/package/dp_package.cxx @@ -0,0 +1,1594 @@ +/* -*- 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 <strings.hrc> +#include <dp_package.hxx> +#include <dp_backend.h> +#include <dp_misc.h> +#include <dp_ucb.h> +#include <dp_interact.h> +#include <dp_dependencies.hxx> +#include <dp_platform.hxx> +#include <dp_descriptioninfoset.hxx> +#include <dp_identifier.hxx> +#include <dp_resource.h> +#include <rtl/uri.hxx> +#include <rtl/ustrbuf.hxx> +#include <sal/log.hxx> +#include <o3tl/string_view.hxx> +#include <cppuhelper/exc_hlp.hxx> +#include <cppuhelper/implbase.hxx> +#include <cppuhelper/supportsservice.hxx> +#include <ucbhelper/content.hxx> +#include <svl/inettype.hxx> +#include <comphelper/propertyvalue.hxx> +#include <comphelper/sequence.hxx> +#include <com/sun/star/lang/WrappedTargetException.hpp> +#include <com/sun/star/graphic/XGraphic.hpp> +#include <com/sun/star/graphic/GraphicProvider.hpp> +#include <com/sun/star/graphic/XGraphicProvider.hpp> +#include <com/sun/star/io/Pipe.hpp> +#include <com/sun/star/io/XOutputStream.hpp> +#include <com/sun/star/io/XInputStream.hpp> +#include <com/sun/star/task/InteractionClassification.hpp> +#include <com/sun/star/task/XInteractionApprove.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/XInteractionReplaceExistingData.hpp> +#include <com/sun/star/ucb/NameClashResolveRequest.hpp> +#include <com/sun/star/ucb/XContentAccess.hpp> +#include <com/sun/star/ucb/NameClash.hpp> +#include <com/sun/star/ucb/UnsupportedCommandException.hpp> +#include <com/sun/star/sdbc/XResultSet.hpp> +#include <com/sun/star/sdbc/XRow.hpp> +#include <com/sun/star/packages/manifest/ManifestReader.hpp> +#include <com/sun/star/packages/manifest/ManifestWriter.hpp> +#include <com/sun/star/deployment/DependencyException.hpp> +#include <com/sun/star/deployment/DeploymentException.hpp> +#include <com/sun/star/deployment/ExtensionRemovedException.hpp> +#include <com/sun/star/deployment/LicenseException.hpp> +#include <com/sun/star/deployment/PlatformException.hpp> +#include <com/sun/star/deployment/Prerequisites.hpp> +#include <optional> +#include <tools/diagnose_ex.h> + +#include <algorithm> +#include <memory> +#include <string_view> +#include <utility> +#include <vector> + +#include "dp_extbackenddb.hxx" +using namespace ::dp_misc; +using namespace ::com::sun::star; +using namespace ::com::sun::star::uno; + + +namespace dp_registry::backend::bundle { +namespace { + +typedef cppu::ImplInheritanceHelper<PackageRegistryBackend> ImplBaseT; + + +class BackendImpl : public ImplBaseT +{ + class PackageImpl : public ::dp_registry::backend::Package + { + BackendImpl * getMyBackend() const; + /** contains the old tooltip description for the Extension Manager GUI in OOo v.2.x + We keep it for backward compatibility. + */ + OUString m_oldDescription; + OUString m_url_expanded; + const bool m_legacyBundle; + Sequence< Reference<deployment::XPackage> > m_bundle; + Sequence< Reference<deployment::XPackage> > * m_pBundle; + + ExtensionBackendDb::Data m_dbData; + + Reference<deployment::XPackage> bindBundleItem( + OUString const & url, OUString const & mediaType, + bool bRemoved, //that is, using data base information + OUString const & identifier, + Reference<ucb::XCommandEnvironment> const & xCmdEnv, + bool notifyDetectionError = true ); + + typedef std::vector< Reference<deployment::XPackage> > t_packagevec; + void scanBundle( + t_packagevec & bundle, + ::rtl::Reference<AbortChannel> const & abortChannel, + Reference<ucb::XCommandEnvironment> const & xCmdEnv ); + void scanLegacyBundle( + t_packagevec & bundle, + OUString const & url, + ::rtl::Reference<AbortChannel> const & abortChannel, + Reference<ucb::XCommandEnvironment> const & xCmdEnv, + bool skip_registration = false ); + std::vector<Reference<deployment::XPackage> > getPackagesFromDb( + Reference<ucb::XCommandEnvironment> const & xCmdEnv); + bool checkPlatform( + Reference<ucb::XCommandEnvironment > const & environment); + + bool checkDependencies( + Reference<ucb::XCommandEnvironment > const & + environment, + DescriptionInfoset const & description); + // throws css::uno::RuntimeException, + // css::deployment::DeploymentException + + /// @throws deployment::DeploymentException + /// @throws ucb::CommandFailedException + /// @throws ucb::CommandAbortedException + /// @throws RuntimeException + bool checkLicense( + Reference< ucb::XCommandEnvironment > const & xCmdEnv, + DescriptionInfoset const & description, bool bNoLicenseChecking); + // @throws DeploymentException + OUString getTextFromURL( + const Reference< ucb::XCommandEnvironment >& xCmdEnv, + const OUString& licenseUrl); + + DescriptionInfoset getDescriptionInfoset() const; + + // Package + virtual beans::Optional< beans::Ambiguous<sal_Bool> > isRegistered_( + ::osl::ResettableMutexGuard & guard, + ::rtl::Reference<AbortChannel> const & abortChannel, + Reference<ucb::XCommandEnvironment> const & xCmdEnv ) override; + virtual void processPackage_( + ::osl::ResettableMutexGuard & guard, + bool registerPackage, + bool startup, + ::rtl::Reference<AbortChannel> const & abortChannel, + Reference<ucb::XCommandEnvironment> const & xCmdEnv ) override; + + virtual void SAL_CALL disposing() override; + + + public: + PackageImpl( + ::rtl::Reference<PackageRegistryBackend> const & myBackend, + OUString const & url, + OUString const & name, + Reference<deployment::XPackageTypeInfo> const & xPackageType, + bool legacyBundle, + bool bRemoved, + OUString const & identifier); + + // XPackage + virtual sal_Bool SAL_CALL isBundle() override; + + virtual Sequence< Reference<deployment::XPackage> > SAL_CALL getBundle( + Reference<task::XAbortChannel> const & xAbortChannel, + Reference<ucb::XCommandEnvironment> const & xCmdEnv ) override; + virtual OUString SAL_CALL getDescription() override; + + virtual OUString SAL_CALL getLicenseText() override; + + virtual void SAL_CALL exportTo( + OUString const & destFolderURL, OUString const & newTitle, + sal_Int32 nameClashAction, + Reference<ucb::XCommandEnvironment> const & xCmdEnv ) override; + + virtual ::sal_Int32 SAL_CALL checkPrerequisites( + const Reference< task::XAbortChannel >& xAbortChannel, + const Reference< ucb::XCommandEnvironment >& xCmdEnv, + sal_Bool noLicenseChecking) override; + + virtual sal_Bool SAL_CALL checkDependencies( + const Reference< ucb::XCommandEnvironment >& xCmdEnv ) override; + + virtual beans::Optional<OUString> SAL_CALL getIdentifier() override; + + virtual OUString SAL_CALL getVersion() override; + + virtual Sequence<OUString> SAL_CALL getUpdateInformationURLs() override; + + virtual beans::StringPair SAL_CALL getPublisherInfo() override; + + virtual OUString SAL_CALL getDisplayName() override; + + virtual Reference< graphic::XGraphic > SAL_CALL + getIcon( sal_Bool bHighContrast ) override; + }; + friend class PackageImpl; + + Reference<deployment::XPackageRegistry> m_xRootRegistry; + const Reference<deployment::XPackageTypeInfo> m_xBundleTypeInfo; + const Reference<deployment::XPackageTypeInfo> m_xLegacyBundleTypeInfo; + Sequence< Reference<deployment::XPackageTypeInfo> > m_typeInfos; + + std::unique_ptr<ExtensionBackendDb> m_backendDb; + + void addDataToDb(OUString const & url, ExtensionBackendDb::Data const & data); + ExtensionBackendDb::Data readDataFromDb(std::u16string_view url); + void revokeEntryFromDb(std::u16string_view url); + + // PackageRegistryBackend + virtual Reference<deployment::XPackage> bindPackage_( + OUString const & url, OUString const & mediaType, + bool bRemoved, OUString const & identifier, + Reference<ucb::XCommandEnvironment> const & xCmdEnv ) override; + + virtual void SAL_CALL disposing() override; + +public: + BackendImpl( + Sequence<Any> const & args, + Reference<XComponentContext> const & xComponentContext, + Reference<deployment::XPackageRegistry> const & xRootRegistry ); + + // XServiceInfo + virtual OUString SAL_CALL getImplementationName() override; + virtual sal_Bool SAL_CALL supportsService( OUString const& name ) override; + virtual Sequence<OUString> SAL_CALL getSupportedServiceNames() override; + + // XPackageRegistry + virtual Sequence< Reference<deployment::XPackageTypeInfo> > SAL_CALL + getSupportedPackageTypes() override; + virtual void SAL_CALL packageRemoved(OUString const & url, OUString const & mediaType) override; + + using ImplBaseT::disposing; +}; + +//Used to find a XPackage with a particular URL +class XPackage_eq +{ + OUString m_URL; +public: + explicit XPackage_eq(OUString s) : m_URL(std::move(s)) {} + bool operator() (const Reference<deployment::XPackage> & p) const + { + return m_URL == p->getURL(); + } +}; + + +BackendImpl::BackendImpl( + Sequence<Any> const & args, + Reference<XComponentContext> const & xComponentContext, + Reference<deployment::XPackageRegistry> const & xRootRegistry ) + : ImplBaseT( args, xComponentContext ), + m_xRootRegistry( xRootRegistry ), + m_xBundleTypeInfo( new Package::TypeInfo( + "application/vnd.sun.star.package-bundle", + "*.oxt;*.uno.pkg", + DpResId(RID_STR_PACKAGE_BUNDLE) + ) ), + m_xLegacyBundleTypeInfo( new Package::TypeInfo( + "application/vnd.sun.star.legacy-package-bundle", + "*.zip", + m_xBundleTypeInfo->getShortDescription() + ) ), + m_typeInfos{ m_xBundleTypeInfo, m_xLegacyBundleTypeInfo } +{ + if (!transientMode()) + { + OUString dbFile = makeURL(getCachePath(), getImplementationName()); + dbFile = makeURL(dbFile, "backenddb.xml"); + m_backendDb.reset( + new ExtensionBackendDb(getComponentContext(), dbFile)); + } +} + + +void BackendImpl::disposing() +{ + m_xRootRegistry.clear(); + PackageRegistryBackend::disposing(); +} + +// XServiceInfo +OUString BackendImpl::getImplementationName() +{ + return "com.sun.star.comp.deployment.bundle.PackageRegistryBackend"; +} + +sal_Bool BackendImpl::supportsService(OUString const & ServiceName) +{ + return cppu::supportsService(this, ServiceName); +} + +Sequence<OUString> BackendImpl::getSupportedServiceNames() +{ + return { OUString(BACKEND_SERVICE_NAME) }; +} + +// XPackageRegistry + +Sequence< Reference<deployment::XPackageTypeInfo> > +BackendImpl::getSupportedPackageTypes() +{ + return m_typeInfos; +} + +void BackendImpl::packageRemoved(OUString const & url, OUString const & /*mediaType*/) +{ + //Notify the backend responsible for processing the different media + //types that this extension was removed. + ExtensionBackendDb::Data data = readDataFromDb(url); + for (auto const& item : data.items) + { + m_xRootRegistry->packageRemoved(item.first, item.second); + } + + if (m_backendDb) + m_backendDb->removeEntry(url); +} + + +// PackageRegistryBackend + +Reference<deployment::XPackage> BackendImpl::bindPackage_( + OUString const & url, OUString const & mediaType_, + bool bRemoved, OUString const & identifier, + Reference<ucb::XCommandEnvironment> const & xCmdEnv ) +{ + OUString mediaType( mediaType_ ); + if (mediaType.isEmpty()) + { + // detect media-type: + ::ucbhelper::Content ucbContent; + if (create_ucb_content( &ucbContent, url, xCmdEnv )) + { + if (ucbContent.isFolder()) + { + //Every .oxt, uno.pkg file must contain a META-INF folder + ::ucbhelper::Content metaInfContent; + if (create_ucb_content( + &metaInfContent, makeURL( url, "META-INF" ), + xCmdEnv, false /* no throw */ )) + { + mediaType = "application/vnd.sun.star.package-bundle"; + } + //No support of legacy bundles, because every folder could be one. + } + else + { + const OUString title( StrTitle::getTitle( ucbContent ) ); + if (title.endsWithIgnoreAsciiCase(".oxt") || + title.endsWithIgnoreAsciiCase(".uno.pkg")) + mediaType = "application/vnd.sun.star.package-bundle"; + else if (title.endsWithIgnoreAsciiCase(".zip")) + mediaType = "application/vnd.sun.star.legacy-package-bundle"; + } + } + if (mediaType.isEmpty()) + throw lang::IllegalArgumentException( + StrCannotDetectMediaType() + url, + static_cast<OWeakObject *>(this), static_cast<sal_Int16>(-1) ); + } + + OUString type, subType; + INetContentTypeParameterList params; + if (INetContentTypes::parse( mediaType, type, subType, ¶ms )) + { + if (type.equalsIgnoreAsciiCase("application")) + { + + //In case a XPackage is created for a removed extension, we cannot + //obtain the name + OUString name; + if (!bRemoved) + { + ::ucbhelper::Content ucbContent( + url, xCmdEnv, getComponentContext() ); + name = StrTitle::getTitle( ucbContent ); + } + if (subType.equalsIgnoreAsciiCase("vnd.sun.star.package-bundle")) + { + return new PackageImpl( + this, url, name, m_xBundleTypeInfo, false, bRemoved, + identifier); + } + else if (subType.equalsIgnoreAsciiCase( "vnd.sun.star.legacy-package-bundle")) + { + return new PackageImpl( + this, url, name, m_xLegacyBundleTypeInfo, true, bRemoved, + identifier); + } + } + } + throw lang::IllegalArgumentException( + StrUnsupportedMediaType() + mediaType, + static_cast<OWeakObject *>(this), + static_cast<sal_Int16>(-1) ); +} + +void BackendImpl::addDataToDb( + OUString const & url, ExtensionBackendDb::Data const & data) +{ + if (m_backendDb) + m_backendDb->addEntry(url, data); +} + +ExtensionBackendDb::Data BackendImpl::readDataFromDb( + std::u16string_view url) +{ + ExtensionBackendDb::Data data; + if (m_backendDb) + data = m_backendDb->getEntry(url); + return data; +} + +void BackendImpl::revokeEntryFromDb(std::u16string_view url) +{ + if (m_backendDb) + m_backendDb->revokeEntry(url); +} + + +BackendImpl::PackageImpl::PackageImpl( + ::rtl::Reference<PackageRegistryBackend> const & myBackend, + OUString const & url, + OUString const & name, + Reference<deployment::XPackageTypeInfo> const & xPackageType, + bool legacyBundle, bool bRemoved, OUString const & identifier) + : Package( myBackend, url, name, name /* display-name */, + xPackageType, bRemoved, identifier), + m_url_expanded( expandUnoRcUrl( url ) ), + m_legacyBundle( legacyBundle ), + m_pBundle( nullptr ) +{ + if (bRemoved) + m_dbData = getMyBackend()->readDataFromDb(url); +} + +BackendImpl * BackendImpl::PackageImpl::getMyBackend() const +{ + BackendImpl * pBackend = static_cast<BackendImpl *>(m_myBackend.get()); + if (nullptr == pBackend) + { + //May throw a DisposedException + check(); + //We should never get here... + throw RuntimeException("Failed to get the BackendImpl", + static_cast<OWeakObject*>(const_cast<PackageImpl *>(this))); + } + return pBackend; +} + +void BackendImpl::PackageImpl::disposing() +{ + sal_Int32 len = m_bundle.getLength(); + Reference<deployment::XPackage> const * p = m_bundle.getConstArray(); + for ( sal_Int32 pos = 0; pos < len; ++pos ) + try_dispose( p[ pos ] ); + m_bundle.realloc( 0 ); + + Package::disposing(); +} + +// Package + +beans::Optional< beans::Ambiguous<sal_Bool> > +BackendImpl::PackageImpl::isRegistered_( + ::osl::ResettableMutexGuard &, + ::rtl::Reference<AbortChannel> const & abortChannel, + Reference<ucb::XCommandEnvironment> const & xCmdEnv ) +{ + //In case the object was created for a removed extension (m_bRemoved = true) + //but the extension is not registered, then bundle will be empty. Then + //the return value will be Optional<...>.IsPresent= false. Although this is + //not true, this does not matter. Then registerPackage or revokePackage + //would never be called for the items. But since the extension is removed + //and not registered anyway, this does not matter. + const Sequence< Reference<deployment::XPackage> > bundle( + getBundle( abortChannel, xCmdEnv ) ); + + bool reg = false; + bool present = false; + bool ambig = false; + for ( sal_Int32 pos = bundle.getLength(); pos--; ) + { + Reference<deployment::XPackage> const & xPackage = bundle[ pos ]; + Reference<task::XAbortChannel> xSubAbortChannel( + xPackage->createAbortChannel() ); + AbortChannel::Chain chain( abortChannel, xSubAbortChannel ); + beans::Optional< beans::Ambiguous<sal_Bool> > option( + xPackage->isRegistered( xSubAbortChannel, xCmdEnv ) ); + + //present = true if at least one bundle item has this value. + //reg = true if all bundle items have an option value (option.IsPresent == 1) + //and all have value of true (option.Value.Value == true) + //If not, then the bundle has the status of not registered and ambiguous. + if (option.IsPresent) + { + beans::Ambiguous<sal_Bool> const & status = option.Value; + if (present) + { + //we never come here in the first iteration + if (reg != bool(status.Value)) { + + ambig = true; + reg = false; + break; + } + } + else + { + //we always come here in the first iteration + reg = status.Value; + present = true; + } + } + } + return beans::Optional< beans::Ambiguous<sal_Bool> >( + present, beans::Ambiguous<sal_Bool>(reg, ambig) ); +} + +OUString BackendImpl::PackageImpl::getTextFromURL( + const css::uno::Reference< css::ucb::XCommandEnvironment >& xCmdEnv, + const OUString& licenseUrl) +{ + try + { + ::ucbhelper::Content descContent( + licenseUrl, xCmdEnv, getMyBackend()->getComponentContext()); + std::vector<sal_Int8> seq = dp_misc::readFile(descContent); + return OUString( reinterpret_cast<char const *>( + seq.data()), seq.size(), RTL_TEXTENCODING_UTF8); + } + catch (const css::uno::Exception&) + { + Any exc( ::cppu::getCaughtException() ); + throw css::deployment::DeploymentException( + "Could not read file " + licenseUrl, nullptr, exc); + } + +} + +DescriptionInfoset BackendImpl::PackageImpl::getDescriptionInfoset() const +{ + return dp_misc::getDescriptionInfoset(m_url_expanded); +} + +bool BackendImpl::PackageImpl::checkPlatform( + css::uno::Reference< css::ucb::XCommandEnvironment > const & environment) +{ + bool ret = false; + DescriptionInfoset info(getDescriptionInfoset()); + Sequence<OUString> platforms(info.getSupportedPlatforms()); + if (hasValidPlatform(platforms)) + { + ret = true; + } + else + { + ret = false; + OUString msg( + "unsupported platform"); + Any e( + css::deployment::PlatformException( + msg, static_cast<OWeakObject *>(this), this)); + if (!interactContinuation( + e, cppu::UnoType< css::task::XInteractionApprove >::get(), + environment, nullptr, nullptr)) + { + throw css::deployment::DeploymentException( + msg, static_cast<OWeakObject *>(this), e); + } + } + return ret; +} + + +bool BackendImpl::PackageImpl::checkDependencies( + css::uno::Reference< css::ucb::XCommandEnvironment > const & environment, + DescriptionInfoset const & description) +{ + css::uno::Sequence< css::uno::Reference< css::xml::dom::XElement > > + unsatisfied(dp_misc::Dependencies::check(description)); + + if (!unsatisfied.hasElements()) { + return true; + } else { + OUString msg( + "unsatisfied dependencies"); + Any e( + css::deployment::DependencyException( + msg, static_cast<OWeakObject *>(this), unsatisfied)); + if (!interactContinuation( + e, cppu::UnoType< css::task::XInteractionApprove >::get(), + environment, nullptr, nullptr)) + { + throw css::deployment::DeploymentException( + msg, static_cast<OWeakObject *>(this), e); + } + return false; + } +} + +bool BackendImpl::PackageImpl::checkLicense( + css::uno::Reference< css::ucb::XCommandEnvironment > const & xCmdEnv, + DescriptionInfoset const & info, bool alreadyInstalled) +{ + try + { + ::std::optional<SimpleLicenseAttributes> simplLicAttr + = info.getSimpleLicenseAttributes(); + if (! simplLicAttr) + return true; + OUString sLic = info.getLocalizedLicenseURL(); + //If we do not get a localized licence then there is an error in the description.xml + //This should be handled by using a validating parser. Therefore we assume that no + //license is available. + if (sLic.isEmpty()) + throw css::deployment::DeploymentException( + "Could not obtain path to license. Possible error in description.xml", nullptr, Any()); + OUString sHref = m_url_expanded + "/" + sLic; + OUString sLicense = getTextFromURL(xCmdEnv, sHref); + ////determine who has to agree to the license + //check correct value for attribute + if ( simplLicAttr->acceptBy != "user" && simplLicAttr->acceptBy != "admin") + throw css::deployment::DeploymentException( + "Could not obtain attribute simple-license@accept-by or it has no valid value", nullptr, Any()); + + + //Only use interaction if there is no version of this extension already installed + //and the suppress-on-update flag is not set for the new extension + // alreadyInstalled | bSuppressOnUpdate | show license + + // 0 | 0 | 1 + // 0 | 1 | 1 + // 1 | 0 | 1 + // 1 | 1 | 0 + + if ( !(alreadyInstalled && simplLicAttr->suppressOnUpdate)) + { + css::deployment::LicenseException licExc( + OUString(), nullptr, getDisplayName(), sLicense, + simplLicAttr->acceptBy); + bool approve = false; + bool abort = false; + if (! interactContinuation( + Any(licExc), cppu::UnoType<task::XInteractionApprove>::get(), xCmdEnv, &approve, &abort )) + throw css::deployment::DeploymentException( + "Could not interact with user.", nullptr, Any()); + + return approve; + } + return true; + } catch (const css::ucb::CommandFailedException&) { + throw; + } catch (const css::ucb::CommandAbortedException&) { + throw; + } catch (const css::deployment::DeploymentException&) { + throw; + } catch (const css::uno::RuntimeException&) { + throw; + } catch (const css::uno::Exception&) { + Any anyExc = cppu::getCaughtException(); + throw css::deployment::DeploymentException("Unexpected exception", nullptr, anyExc); + } +} + +::sal_Int32 BackendImpl::PackageImpl::checkPrerequisites( + const css::uno::Reference< css::task::XAbortChannel >&, + const css::uno::Reference< css::ucb::XCommandEnvironment >& xCmdEnv, + sal_Bool alreadyInstalled) +{ + if (m_bRemoved) + throw deployment::ExtensionRemovedException(); + DescriptionInfoset info = getDescriptionInfoset(); + if (!info.hasDescription()) + return 0; + + //always return LICENSE as long as the user did not accept the license + //so that XExtensionManager::checkPrerequisitesAndEnable will again + //check the license + if (!checkPlatform(xCmdEnv)) + return deployment::Prerequisites::PLATFORM | + deployment::Prerequisites::LICENSE; + else if(!checkDependencies(xCmdEnv, info)) + return deployment::Prerequisites::DEPENDENCIES | + deployment::Prerequisites::LICENSE; + else if(!checkLicense(xCmdEnv, info, alreadyInstalled)) + return deployment::Prerequisites::LICENSE; + else + return 0; +} + +sal_Bool BackendImpl::PackageImpl::checkDependencies( + const css::uno::Reference< css::ucb::XCommandEnvironment >& xCmdEnv ) +{ + if (m_bRemoved) + throw deployment::ExtensionRemovedException(); + DescriptionInfoset info = getDescriptionInfoset(); + if (!info.hasDescription()) + return true; + + return checkDependencies(xCmdEnv, info); +} + +beans::Optional<OUString> BackendImpl::PackageImpl::getIdentifier() +{ + OUString identifier; + if (m_bRemoved) + identifier = m_identifier; + else + identifier = dp_misc::generateIdentifier( + getDescriptionInfoset().getIdentifier(), m_name); + + return beans::Optional<OUString>( + true, identifier); +} + +OUString BackendImpl::PackageImpl::getVersion() +{ + if (m_bRemoved) + throw deployment::ExtensionRemovedException(); + return getDescriptionInfoset().getVersion(); +} + +Sequence<OUString> BackendImpl::PackageImpl::getUpdateInformationURLs() +{ + if (m_bRemoved) + throw deployment::ExtensionRemovedException(); + return getDescriptionInfoset().getUpdateInformationUrls(); +} + +beans::StringPair BackendImpl::PackageImpl::getPublisherInfo() +{ + if (m_bRemoved) + throw deployment::ExtensionRemovedException(); + std::pair< OUString, OUString > aInfo = getDescriptionInfoset().getLocalizedPublisherNameAndURL(); + beans::StringPair aStrPair( aInfo.first, aInfo.second ); + return aStrPair; +} + + +uno::Reference< graphic::XGraphic > BackendImpl::PackageImpl::getIcon( sal_Bool bHighContrast ) +{ + if (m_bRemoved) + throw deployment::ExtensionRemovedException(); + + uno::Reference< graphic::XGraphic > xGraphic; + + OUString aIconURL = getDescriptionInfoset().getIconURL( bHighContrast ); + if ( !aIconURL.isEmpty() ) + { + OUString aFullIconURL = m_url_expanded + "/" + aIconURL; + + uno::Reference< XComponentContext > xContext( getMyBackend()->getComponentContext() ); + uno::Reference< graphic::XGraphicProvider > xGraphProvider( graphic::GraphicProvider::create(xContext) ); + + uno::Sequence< beans::PropertyValue > aMediaProps{ comphelper::makePropertyValue( + "URL", aFullIconURL) }; + xGraphic = xGraphProvider->queryGraphic( aMediaProps ); + } + + return xGraphic; +} + + +void BackendImpl::PackageImpl::processPackage_( + ::osl::ResettableMutexGuard &, + bool doRegisterPackage, + bool startup, + ::rtl::Reference<AbortChannel> const & abortChannel, + Reference<ucb::XCommandEnvironment> const & xCmdEnv ) +{ + const Sequence< Reference<deployment::XPackage> > bundle( + getBundle( abortChannel, xCmdEnv ) ); + + if (doRegisterPackage) + { + ExtensionBackendDb::Data data; + const sal_Int32 len = bundle.getLength(); + for ( sal_Int32 pos = 0; pos < len; ++pos ) + { + checkAborted(abortChannel); + Reference<deployment::XPackage> const & xPackage = bundle[ pos ]; + Reference<task::XAbortChannel> xSubAbortChannel( + xPackage->createAbortChannel() ); + AbortChannel::Chain chain( abortChannel, xSubAbortChannel ); + try { + xPackage->registerPackage( startup, xSubAbortChannel, xCmdEnv ); + } + catch (const Exception &) + { + //We even try a rollback if the user cancelled the action (CommandAbortedException) + //in order to prevent invalid database entries. + Any exc( ::cppu::getCaughtException() ); + // try to handle exception, notify: + bool approve = false, abort = false; + if (! interactContinuation( + Any( lang::WrappedTargetException( + "bundle item registration error!", + static_cast<OWeakObject *>(this), exc ) ), + cppu::UnoType<task::XInteractionApprove>::get(), xCmdEnv, + &approve, &abort )) { + OSL_ASSERT( !approve && !abort ); + if (m_legacyBundle) // default for legacy packages: ignore + continue; + // no selection at all, so rethrow; + // no C++ rethrow after getCaughtException(), + // see cppuhelper/exc_hlp.hxx: + ::cppu::throwException(exc); + } + if (approve && !abort) // ignore error, just continue + continue; + + { + ProgressLevel progress( xCmdEnv, "rollback..." ); + // try rollback + for ( ; pos--; ) + { + try { + bundle[ pos ]->revokePackage( + startup, xSubAbortChannel, xCmdEnv ); + } + catch (const Exception &) + { + TOOLS_WARN_EXCEPTION( "desktop", "" ); + // ignore any errors of rollback + } + } + progress.update( "rollback finished." ); + } + + deployment::DeploymentException dpExc; + if (exc >>= dpExc) { + throw ucb::CommandFailedException( + dpExc.Message, dpExc.Context, dpExc.Cause ); + } + else { + // rethrow CommandFailedException + ::cppu::throwException(exc); + } + } + data.items.emplace_back(xPackage->getURL(), + xPackage->getPackageType()->getMediaType()); + } + getMyBackend()->addDataToDb(getURL(), data); + } + else + { + // revoke in reverse order: + for ( sal_Int32 pos = bundle.getLength(); pos--; ) + { + checkAborted(abortChannel); + Reference<deployment::XPackage> const & xPackage = bundle[ pos ]; + Reference<task::XAbortChannel> xSubAbortChannel( + xPackage->createAbortChannel() ); + AbortChannel::Chain chain( abortChannel, xSubAbortChannel ); + try { + bundle[ pos ]->revokePackage( + startup, xSubAbortChannel, xCmdEnv ); + } + catch (const RuntimeException &) { + throw; + } + catch (const ucb::CommandAbortedException &) { + throw; + } + catch (const Exception &) { + // CommandFailedException, DeploymentException: + Any exc( ::cppu::getCaughtException() ); + // try to handle exception, notify: + bool approve = false, abort = false; + if (! interactContinuation( + Any( lang::WrappedTargetException( + "bundle item revocation error!", + static_cast<OWeakObject *>(this), exc ) ), + cppu::UnoType<task::XInteractionApprove>::get(), xCmdEnv, + &approve, &abort )) { + OSL_ASSERT( !approve && !abort ); + if (m_legacyBundle) // default for legacy packages: ignore + continue; + // no selection at all, so rethrow + // no C++ rethrow after getCaughtException(), + // see cppuhelper/exc_hlp.hxx: + ::cppu::throwException(exc); + } + // ignore errors when revoking, although abort may have been + // selected + } + } + getMyBackend()->revokeEntryFromDb(getURL()); + } +} + + +OUString BackendImpl::PackageImpl::getDescription() +{ + if (m_bRemoved) + throw deployment::ExtensionRemovedException(); + + const OUString sRelativeURL(getDescriptionInfoset().getLocalizedDescriptionURL()); + OUString sDescription; + if (!sRelativeURL.isEmpty()) + { + OUString sURL = m_url_expanded + "/" + sRelativeURL; + + try + { + sDescription = getTextFromURL( css::uno::Reference< css::ucb::XCommandEnvironment >(), sURL ); + } + catch ( const css::deployment::DeploymentException& ) + { + TOOLS_WARN_EXCEPTION( "desktop", "" ); + } + } + + if (!sDescription.isEmpty()) + return sDescription; + return m_oldDescription; +} + + +OUString BackendImpl::PackageImpl::getLicenseText() +{ + if (m_bRemoved) + throw deployment::ExtensionRemovedException(); + + OUString sLicense; + DescriptionInfoset aInfo = getDescriptionInfoset(); + + ::std::optional< SimpleLicenseAttributes > aSimplLicAttr = aInfo.getSimpleLicenseAttributes(); + if ( aSimplLicAttr ) + { + OUString aLicenseURL = aInfo.getLocalizedLicenseURL(); + + if ( !aLicenseURL.isEmpty() ) + { + OUString aFullURL = m_url_expanded + "/" + aLicenseURL; + sLicense = getTextFromURL( Reference< ucb::XCommandEnvironment >(), aFullURL); + } + } + + return sLicense; +} + + +void BackendImpl::PackageImpl::exportTo( + OUString const & destFolderURL, OUString const & newTitle, + sal_Int32 nameClashAction, Reference<ucb::XCommandEnvironment> const & xCmdEnv ) +{ + if (m_bRemoved) + throw deployment::ExtensionRemovedException(); + + ::ucbhelper::Content sourceContent( + m_url_expanded, xCmdEnv, getMyBackend()->getComponentContext() ); + OUString title(newTitle); + if (title.isEmpty()) + sourceContent.getPropertyValue( "Title" ) >>= title; + OUString destURL( makeURL( destFolderURL, ::rtl::Uri::encode( + title, rtl_UriCharClassPchar, + rtl_UriEncodeIgnoreEscapes, + RTL_TEXTENCODING_UTF8 ) ) ); + + if (nameClashAction == ucb::NameClash::ASK) + { + if (create_ucb_content( + nullptr, destURL, xCmdEnv, false /* no throw */ )) { + bool replace = false, abort = false; + if (! interactContinuation( + Any( ucb::NameClashResolveRequest( + "file already exists: " + title, + static_cast<OWeakObject *>(this), + task::InteractionClassification_QUERY, + destFolderURL, title, OUString() ) ), + cppu::UnoType<ucb::XInteractionReplaceExistingData>::get(), xCmdEnv, + &replace, &abort ) || !replace) { + return; + } + } + } + else if (nameClashAction != ucb::NameClash::OVERWRITE) { + throw ucb::CommandFailedException("unsupported nameClashAction!", + static_cast<OWeakObject *>(this), Any() ); + } + erase_path( destURL, xCmdEnv ); + + OUString destFolder = + "vnd.sun.star.zip://" + + ::rtl::Uri::encode( destURL, + rtl_UriCharClassRegName, + rtl_UriEncodeIgnoreEscapes, + RTL_TEXTENCODING_UTF8 ) + + "/"; + + ::ucbhelper::Content destFolderContent( + destFolder, xCmdEnv, getMyBackend()->getComponentContext() ); + { + // transfer every item of folder into zip: + Reference<sdbc::XResultSet> xResultSet( + sourceContent.createCursor( Sequence<OUString>() ) ); + ProgressLevel progress( xCmdEnv, OUString() ); + while (xResultSet->next()) + { + ::ucbhelper::Content subContent( + Reference<ucb::XContentAccess>( + xResultSet, UNO_QUERY_THROW )->queryContent(), + xCmdEnv, getMyBackend()->getComponentContext() ); + destFolderContent.transferContent( + subContent, ::ucbhelper::InsertOperation::Copy, + OUString(), ucb::NameClash::OVERWRITE ); + progress.update( Any() ); // animating progress bar + } + } + + // assure META-INF folder: + ::ucbhelper::Content metainfFolderContent; + create_folder( &metainfFolderContent, + makeURL( destFolderContent.getURL(), "META-INF" ), + xCmdEnv ); + + if (m_legacyBundle) + { + // easy to migrate legacy bundles to new format: + // just export them once using a .oxt name! + // set detected media-types of any bundle item: + + // collect all manifest entries: + Sequence< Reference<deployment::XPackage> > bundle; + try { + bundle = getBundle( Reference<task::XAbortChannel>(), xCmdEnv ); + } + // xxx todo: think about exception specs: + catch (const deployment::DeploymentException &) { + TOOLS_WARN_EXCEPTION( "desktop", "" ); + } + catch (const lang::IllegalArgumentException &) { + TOOLS_WARN_EXCEPTION( "desktop", "" ); + } + + std::vector< Sequence<beans::PropertyValue> > manifest; + manifest.reserve( bundle.getLength() ); + sal_Int32 baseURLlen = m_url_expanded.getLength(); + Reference<deployment::XPackage> const *pbundle = bundle.getConstArray(); + static const OUStringLiteral strMediaType( u"MediaType" ); + static const OUStringLiteral strFullPath( u"FullPath" ); + static const OUStringLiteral strIsFolder( u"IsFolder" ); + for ( sal_Int32 pos = bundle.getLength(); pos--; ) + { + Reference<deployment::XPackage> const & xPackage = pbundle[ pos ]; + OUString url_( expandUnoRcUrl( xPackage->getURL() ) ); + OSL_ASSERT( url_.getLength() >= baseURLlen ); + OUString fullPath; + if (url_.getLength() > baseURLlen) + fullPath = url_.copy( baseURLlen + 1 ); + ::ucbhelper::Content ucbContent( + url_, xCmdEnv, getMyBackend()->getComponentContext() ); + if (ucbContent.getPropertyValue(strIsFolder).get<bool>()) + fullPath += "/"; + Sequence<beans::PropertyValue> attribs( 2 ); + beans::PropertyValue * pattribs = attribs.getArray(); + pattribs[ 0 ].Name = strFullPath; + pattribs[ 0 ].Value <<= fullPath; + pattribs[ 1 ].Name = strMediaType; + const Reference<deployment::XPackageTypeInfo> xPackageType( + xPackage->getPackageType() ); + OUString mediaType; + OSL_ASSERT( xPackageType.is() ); + if (xPackageType.is()) + mediaType = xPackageType->getMediaType(); + else + mediaType = "unknown"; + pattribs[ 1 ].Value <<= mediaType; + manifest.push_back( attribs ); + } + + // write into pipe: + Reference<XComponentContext> xContext( + getMyBackend()->getComponentContext() ); + Reference<packages::manifest::XManifestWriter> xManifestWriter = + packages::manifest::ManifestWriter::create( xContext ); + Reference<io::XOutputStream> xPipe( io::Pipe::create(xContext), UNO_QUERY_THROW ); + xManifestWriter->writeManifestSequence( + xPipe, comphelper::containerToSequence(manifest) ); + + // write buffered pipe data to content: + ::ucbhelper::Content manifestContent( + makeURL( metainfFolderContent.getURL(), "manifest.xml" ), + xCmdEnv, getMyBackend()->getComponentContext() ); + manifestContent.writeStream( + Reference<io::XInputStream>( xPipe, UNO_QUERY_THROW ), + true /* replace existing */ ); + } + else + { + bool bSuccess = false; + try + { + // overwrite manifest.xml: + ::ucbhelper::Content manifestContent; + if ( ! create_ucb_content( + &manifestContent, + makeURL( m_url_expanded, "META-INF/manifest.xml" ), + xCmdEnv, false ) ) + { + OSL_FAIL( "### missing META-INF/manifest.xml file!" ); + return; + } + + metainfFolderContent.transferContent( + manifestContent, ::ucbhelper::InsertOperation::Copy, + OUString(), ucb::NameClash::OVERWRITE ); + bSuccess = true; + } + catch (const css::ucb::ContentCreationException &) + { + TOOLS_WARN_EXCEPTION("desktop.deployment", "exception on overwriting manifest"); + } + + if (!bSuccess) + throw RuntimeException( "UCB transferContent() failed!", + static_cast<OWeakObject *>(this) ); + } + + // xxx todo: maybe obsolete in the future + try { + destFolderContent.executeCommand( "flush", Any() ); + } + catch (const ucb::UnsupportedCommandException &) { + } +} + + +sal_Bool BackendImpl::PackageImpl::isBundle() +{ + return true; +} + + +Sequence< Reference<deployment::XPackage> > BackendImpl::PackageImpl::getBundle( + Reference<task::XAbortChannel> const & xAbortChannel, + Reference<ucb::XCommandEnvironment> const & xCmdEnv ) +{ + Sequence< Reference<deployment::XPackage> > * pBundle = m_pBundle; + if (pBundle == nullptr) + { + t_packagevec bundle; + if (m_bRemoved) + { + bundle = getPackagesFromDb(xCmdEnv); + } + else + { + try { + if (m_legacyBundle) + { + // .zip legacy packages allow script.xlb, dialog.xlb in bundle + // root folder: + OUString mediaType; + // probe for script.xlb: + if (create_ucb_content( + nullptr, makeURL( m_url_expanded, "script.xlb" ), + xCmdEnv, false /* no throw */ )) { + mediaType = "application/vnd.sun.star.basic-library"; + } + // probe for dialog.xlb: + else if (create_ucb_content( + nullptr, makeURL( m_url_expanded, "dialog.xlb" ), + xCmdEnv, false /* no throw */ )) + mediaType = "application/vnd.sun.star.dialog-library"; + + if (!mediaType.isEmpty()) { + const Reference<deployment::XPackage> xPackage( + bindBundleItem( getURL(), mediaType, false, OUString(), + xCmdEnv ) ); + if (xPackage.is()) + bundle.push_back( xPackage ); + // continue scanning: + } + scanLegacyBundle( bundle, getURL(), + AbortChannel::get(xAbortChannel), xCmdEnv ); + } + else + { + // .oxt: + scanBundle( bundle, AbortChannel::get(xAbortChannel), xCmdEnv ); + } + + } + catch (const RuntimeException &) { + throw; + } + catch (const ucb::CommandFailedException &) { + throw; + } + catch (const ucb::CommandAbortedException &) { + throw; + } + catch (const deployment::DeploymentException &) { + throw; + } + catch (const Exception &) { + Any exc( ::cppu::getCaughtException() ); + throw deployment::DeploymentException( + "error scanning bundle: " + getURL(), + static_cast<OWeakObject *>(this), exc ); + } + } + + // sort: schema before config data, typelibs before components: + Sequence< Reference<deployment::XPackage> > ret( bundle.size() ); + Reference<deployment::XPackage> * pret = ret.getArray(); + sal_Int32 lower_end = 0; + sal_Int32 upper_end = ret.getLength(); + for (auto const& elem : bundle) + { + const Reference<deployment::XPackageTypeInfo> xPackageType( + elem->getPackageType() ); + OSL_ASSERT( xPackageType.is() ); + if (xPackageType.is()) + { + const OUString mediaType( xPackageType->getMediaType() ); + OUString type, subType; + INetContentTypeParameterList params; + if (INetContentTypes::parse( mediaType, type, subType, ¶ms ) && + type.equalsIgnoreAsciiCase("application") && + (subType.equalsIgnoreAsciiCase( "vnd.sun.star.uno-component") || + subType.equalsIgnoreAsciiCase( "vnd.sun.star.configuration-data"))) + { + --upper_end; + pret[ upper_end ] = elem; + continue; + } + } + pret[ lower_end ] = elem; + ++lower_end; + } + OSL_ASSERT( lower_end == upper_end ); + + const ::osl::MutexGuard guard( m_aMutex ); + pBundle = m_pBundle; + if (pBundle == nullptr) { + m_bundle = ret; + pBundle = &m_bundle; + OSL_DOUBLE_CHECKED_LOCKING_MEMORY_BARRIER(); + m_pBundle = pBundle; + } + } + else { + OSL_DOUBLE_CHECKED_LOCKING_MEMORY_BARRIER(); + } + return *pBundle; +} + +bool isBundle_( std::u16string_view mediaType ) +{ + // xxx todo: additional parsing? + return !mediaType.empty() && + (o3tl::matchIgnoreAsciiCase( mediaType, u"application/vnd.sun.star.package-bundle") || + o3tl::matchIgnoreAsciiCase( mediaType, u"application/vnd.sun.star.legacy-package-bundle")); +} + + +Reference<deployment::XPackage> BackendImpl::PackageImpl::bindBundleItem( + OUString const & url, OUString const & mediaType, + bool bRemoved, OUString const & identifier, + Reference<ucb::XCommandEnvironment> const & xCmdEnv, + bool notifyDetectionError ) +{ + // ignore any nested bundles: + if (isBundle_(mediaType)) + return Reference<deployment::XPackage>(); + + Reference<deployment::XPackage>xPackage; + try { + try { + xPackage.set( getMyBackend()->m_xRootRegistry->bindPackage( + url, mediaType, bRemoved, identifier, xCmdEnv ) ); + OSL_ASSERT( xPackage.is() ); + } catch (css::lang::IllegalArgumentException & e) { + css::uno::Any exc(cppu::getCaughtException()); + throw css::lang::WrappedTargetException( + "wrapped: " + e.Message, e.Context, exc); + } + } + catch (const RuntimeException &) { + throw; + } + catch (const ucb::CommandFailedException &) { + // ignore already handled error + } + catch (const Exception &) { + const Any exc( ::cppu::getCaughtException() ); + if (notifyDetectionError || + !exc.isExtractableTo( cppu::UnoType<lang::IllegalArgumentException>::get()) ) + { + (void)interactContinuation( + Any( lang::WrappedTargetException("bundle item error!", + static_cast<OWeakObject *>(this), exc ) ), + cppu::UnoType<task::XInteractionApprove>::get(), xCmdEnv, nullptr, nullptr ); + } + } + + if (xPackage.is()) { + const Reference<deployment::XPackageTypeInfo> xPackageType( + xPackage->getPackageType() ); + OSL_ASSERT( xPackageType.is() ); + // ignore any nested bundles: + if (xPackageType.is() && isBundle_( xPackageType->getMediaType() )) + xPackage.clear(); + } + return xPackage; +} + + +void BackendImpl::PackageImpl::scanBundle( + t_packagevec & bundle, + ::rtl::Reference<AbortChannel> const & abortChannel, + Reference<ucb::XCommandEnvironment> const & xCmdEnv ) +{ + OSL_ASSERT( !m_legacyBundle ); + + OUString mfUrl( makeURL( m_url_expanded, "META-INF/manifest.xml" ) ); + ::ucbhelper::Content manifestContent; + if (! create_ucb_content( + &manifestContent, mfUrl, xCmdEnv, false /* no throw */ )) + { + SAL_WARN( + "desktop.deployment", + "cannot create UCB Content for <" << mfUrl << ">" ); + return; + } + + + const LanguageTag& officeLocale = getOfficeLanguageTag(); + const std::vector< OUString > officeFallbacks( officeLocale.getFallbackStrings( true)); + const size_t nPenaltyMax = std::numeric_limits<size_t>::max(); + size_t descrPenalty = nPenaltyMax; + OUString descrFile; + + const Reference<XComponentContext> xContext( + getMyBackend()->getComponentContext() ); + Reference<packages::manifest::XManifestReader> xManifestReader = + packages::manifest::ManifestReader::create( xContext ); + const Sequence< Sequence<beans::PropertyValue> > manifestSeq( + xManifestReader->readManifestSequence( manifestContent.openStream() ) ); + const OUString packageRootURL( getURL() ); + for ( sal_Int32 pos = manifestSeq.getLength(); pos--; ) + { + OUString fullPath, mediaType; + Sequence<beans::PropertyValue> const & attribs = manifestSeq[ pos ]; + for ( sal_Int32 i = attribs.getLength(); i--; ) + { + if (!(fullPath.isEmpty() || mediaType.isEmpty())) + break; + if ( attribs[i].Name == "FullPath" ) + attribs[i].Value >>= fullPath; + else if ( attribs[i].Name == "MediaType" ) + attribs[i].Value >>= mediaType; + } + + if ( fullPath.isEmpty() || mediaType.isEmpty() || mediaType == "text/xml" )// opt: exclude common text/xml + continue; + + OUString type, subType; + INetContentTypeParameterList params; + if (! INetContentTypes::parse( mediaType, type, subType, ¶ms )) + continue; + + { + auto const iter = params.find("platform"); + if (iter != params.end() && !platform_fits(iter->second.m_sValue)) + continue; + } + const OUString url( makeURL( packageRootURL, fullPath ) ); + + // check for bundle description: + if (type.equalsIgnoreAsciiCase("application") && + subType.equalsIgnoreAsciiCase( "vnd.sun.star.package-bundle-description")) + { + // check locale: + auto const iter = params.find("locale"); + if (iter == params.end()) + { + if (descrFile.isEmpty()) + descrFile = url; + } + else { + // match best locale: + LanguageTag descrTag(iter->second.m_sValue); + if (officeLocale.getLanguage() == descrTag.getLanguage()) + { + size_t nPenalty = nPenaltyMax; + const std::vector< OUString > descrFallbacks( descrTag.getFallbackStrings( true)); + for (size_t o=0; o < officeFallbacks.size() && nPenalty == nPenaltyMax; ++o) + { + for (size_t d=0; d < descrFallbacks.size() && nPenalty == nPenaltyMax; ++d) + { + if (officeFallbacks[o] == descrFallbacks[d]) + { + // The last fallbacks are always language-only + // fallbacks, so we _will_ have _some_ match if + // we ever entered the overall if() condition. + nPenalty = o * 1000 + d; + if (descrPenalty > nPenalty) + { + descrPenalty = nPenalty; + descrFile = url; + } + } + } + } + } + // TODO: we could break here if descrPenalty==0 for an exact + // match of officeLocale, but the previous code didn't; are + // there side effects? + } + continue; + } + + checkAborted( abortChannel ); + + //We make sure that we only create one XPackage for a particular URL. + //Sometime programmers insert the same URL several times in the manifest + //which may lead to DisposedExceptions. + if (std::none_of(bundle.begin(), bundle.end(), XPackage_eq(url))) + { + const Reference<deployment::XPackage> xPackage( + bindBundleItem( url, mediaType, false, OUString(), xCmdEnv ) ); + if (xPackage.is()) + bundle.push_back( xPackage ); + } + else + { + SAL_WARN("desktop.deployment", "manifest.xml contains a duplicate entry (from " << url << ")"); + } + } + + if (descrFile.isEmpty()) + return; + + ::ucbhelper::Content descrFileContent; + if (!create_ucb_content( &descrFileContent, descrFile, + xCmdEnv, false /* no throw */ )) + return; + + // patch description: + std::vector<sal_Int8> bytes( readFile( descrFileContent ) ); + OUStringBuffer buf; + if ( !bytes.empty() ) + { + buf.append( OUString( reinterpret_cast<char const *>( + bytes.data() ), + bytes.size(), RTL_TEXTENCODING_UTF8 ) ); + } + else + { + buf.append( Package::getDescription() ); + } + m_oldDescription = buf.makeStringAndClear(); +} + + +void BackendImpl::PackageImpl::scanLegacyBundle( + t_packagevec & bundle, + OUString const & url, + ::rtl::Reference<AbortChannel> const & abortChannel, + Reference<ucb::XCommandEnvironment> const & xCmdEnv, + bool skip_registration ) +{ + ::ucbhelper::Content ucbContent( + url, xCmdEnv, getMyBackend()->getComponentContext() ); + + // check for platform paths: + const OUString title( StrTitle::getTitle( ucbContent ) ); + if (title.endsWithIgnoreAsciiCase( ".plt" ) && + !platform_fits( title.subView( 0, title.getLength() - 4 ) )) { + return; + } + if (title.endsWithIgnoreAsciiCase("skip_registration") ) + skip_registration = true; + + Sequence<OUString> ar { OUString("Title"), OUString("IsFolder") }; + Reference<sdbc::XResultSet> xResultSet( ucbContent.createCursor( ar ) ); + while (xResultSet->next()) + { + checkAborted( abortChannel ); + + const Reference<sdbc::XRow> xRow( xResultSet, UNO_QUERY_THROW ); + const OUString title_enc( ::rtl::Uri::encode( + xRow->getString( 1 /* Title */ ), + rtl_UriCharClassPchar, + rtl_UriEncodeIgnoreEscapes, + RTL_TEXTENCODING_UTF8 ) ); + const OUString path( makeURL( url, title_enc ) ); + + OUString mediaType; + const Reference<deployment::XPackage> xPackage( + bindBundleItem( path, OUString() /* detect */, false, OUString(), + xCmdEnv, false /* ignore detection errors */ ) ); + if (xPackage.is()) { + const Reference<deployment::XPackageTypeInfo> xPackageType( + xPackage->getPackageType() ); + OSL_ASSERT( xPackageType.is() ); + if (xPackageType.is()) + mediaType = xPackageType->getMediaType(); + + if (skip_registration && + // xxx todo: additional parsing? + mediaType.matchIgnoreAsciiCase("application/vnd.sun.star.uno-component")) + continue; + + bundle.push_back( xPackage ); + } + + if (mediaType.isEmpty() || + // script.xlb, dialog.xlb can be met everywhere: + mediaType.matchIgnoreAsciiCase("application/vnd.sun.star.basic-library") || + mediaType.matchIgnoreAsciiCase("application/vnd.sun.star.dialog-library")) + { + if (xRow->getBoolean( 2 /* IsFolder */ )) { // recurse into folder: + scanLegacyBundle( + bundle, path, abortChannel, xCmdEnv, skip_registration ); + } + } + } +} + +OUString BackendImpl::PackageImpl::getDisplayName() +{ + if (m_bRemoved) + throw deployment::ExtensionRemovedException(); + + OUString sName = getDescriptionInfoset().getLocalizedDisplayName(); + if (sName.isEmpty()) + return m_displayName; + else + return sName; +} + +std::vector<Reference<deployment::XPackage> > +BackendImpl::PackageImpl::getPackagesFromDb( + Reference<ucb::XCommandEnvironment> const & xCmdEnv) +{ + std::vector<Reference<deployment::XPackage> > retVector; + + for (auto const& item : m_dbData.items) + { + Reference<deployment::XPackage> xExtension = + bindBundleItem(item.first, item.second, true, m_identifier, xCmdEnv); + OSL_ASSERT(xExtension.is()); + if (xExtension.is()) + retVector.push_back(xExtension); + } + + return retVector; +} + +} // anon namespace + + +Reference<deployment::XPackageRegistry> create( + Reference<deployment::XPackageRegistry> const & xRootRegistry, + OUString const & context, OUString const & cachePath, + Reference<XComponentContext> const & xComponentContext ) +{ + Sequence<Any> args(cachePath.isEmpty() ? 1 : 3 ); + auto pArgs = args.getArray(); + pArgs[ 0 ] <<= context; + if (!cachePath.isEmpty()) { + pArgs[ 1 ] <<= cachePath; + pArgs[ 2 ] <<= false; // readOnly + } + return new BackendImpl( args, xComponentContext, xRootRegistry ); +} + +} // namespace dp_registry + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/desktop/source/deployment/registry/script/dp_lib_container.cxx b/desktop/source/deployment/registry/script/dp_lib_container.cxx new file mode 100644 index 000000000..3a6f30253 --- /dev/null +++ b/desktop/source/deployment/registry/script/dp_lib_container.cxx @@ -0,0 +1,64 @@ +/* -*- 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/uno/XComponentContext.hpp> +#include <com/sun/star/ucb/XCommandEnvironment.hpp> + +#include <strings.hrc> +#include <dp_resource.h> +#include <dp_shared.hxx> +#include <dp_xml.h> +#include "dp_lib_container.h" + +#include <rtl/ustring.hxx> +#include <ucbhelper/content.hxx> +#include <xmlscript/xmllib_imexp.hxx> + + +using namespace ::dp_misc; +using namespace ::com::sun::star; +using namespace ::com::sun::star::uno; +using namespace ::com::sun::star::ucb; + +namespace dp_registry::backend::script { + +namespace { + OUString StrCannotDetermineLibName() { return DpResId(RID_STR_CANNOT_DETERMINE_LIBNAME); } +} + +OUString LibraryContainer::get_libname( + OUString const & url, + Reference<XCommandEnvironment> const & xCmdEnv, + Reference<XComponentContext> const & xContext ) +{ + ::xmlscript::LibDescriptor import; + ::ucbhelper::Content ucb_content( url, xCmdEnv, xContext ); + xml_parse( ::xmlscript::importLibrary( import ), ucb_content, xContext ); + + if (import.aName.isEmpty()) { + throw Exception( StrCannotDetermineLibName(), + Reference<XInterface>() ); + } + return import.aName; +} + +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/desktop/source/deployment/registry/script/dp_lib_container.h b/desktop/source/deployment/registry/script/dp_lib_container.h new file mode 100644 index 000000000..fbbedf866 --- /dev/null +++ b/desktop/source/deployment/registry/script/dp_lib_container.h @@ -0,0 +1,48 @@ +/* -*- 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/uno/Reference.hxx> + +namespace com::sun::star { + namespace uno { + class XComponentContext; + } + namespace ucb { + class XCommandEnvironment; + } +} + + +namespace dp_registry::backend::script { + + +class LibraryContainer +{ +public: + static OUString get_libname( + OUString const & url, + css::uno::Reference<css::ucb::XCommandEnvironment> const & xCmdEnv, + css::uno::Reference<css::uno::XComponentContext> const & xContext ); +}; + +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/desktop/source/deployment/registry/script/dp_script.cxx b/desktop/source/deployment/registry/script/dp_script.cxx new file mode 100644 index 000000000..47e41a364 --- /dev/null +++ b/desktop/source/deployment/registry/script/dp_script.cxx @@ -0,0 +1,480 @@ +/* -*- 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 <strings.hrc> +#include "dp_lib_container.h" +#include <dp_backend.h> +#include <dp_misc.h> +#include <dp_ucb.h> +#include <ucbhelper/content.hxx> +#include <cppuhelper/implbase.hxx> +#include <svl/inettype.hxx> +#include <com/sun/star/util/XUpdatable.hpp> +#include <com/sun/star/script/XLibraryContainer3.hpp> +#include <memory> +#include <string_view> + +#include "dp_scriptbackenddb.hxx" +#include <cppuhelper/supportsservice.hxx> + +using namespace ::dp_misc; +using namespace ::com::sun::star; +using namespace ::com::sun::star::uno; +using namespace ::com::sun::star::ucb; + +namespace dp_registry::backend::script { +namespace { + +typedef ::cppu::ImplInheritanceHelper< + ::dp_registry::backend::PackageRegistryBackend, util::XUpdatable > t_helper; + +class BackendImpl : public t_helper +{ + class PackageImpl : public ::dp_registry::backend::Package + { + BackendImpl * getMyBackend() const; + + const OUString m_scriptURL; + const OUString m_dialogURL; + OUString m_dialogName; + + // Package + virtual beans::Optional< beans::Ambiguous<sal_Bool> > isRegistered_( + ::osl::ResettableMutexGuard & guard, + ::rtl::Reference<AbortChannel> const & abortChannel, + Reference<XCommandEnvironment> const & xCmdEnv ) override; + virtual void processPackage_( + ::osl::ResettableMutexGuard & guard, + bool registerPackage, + bool startup, + ::rtl::Reference<AbortChannel> const & abortChannel, + Reference<XCommandEnvironment> const & xCmdEnv ) override; + + public: + PackageImpl( + ::rtl::Reference<BackendImpl> const & myBackend, + OUString const & url, + Reference<XCommandEnvironment> const &xCmdEnv, + OUString const & scriptURL, OUString const & dialogURL, + bool bRemoved, OUString const & identifier); + }; + friend class PackageImpl; + + // PackageRegistryBackend + virtual Reference<deployment::XPackage> bindPackage_( + OUString const & url, OUString const & mediaType, + bool bRemoved, OUString const & identifier, + Reference<XCommandEnvironment> const & xCmdEnv ) override; + + void addDataToDb(OUString const & url); + bool hasActiveEntry(std::u16string_view url); + void revokeEntryFromDb(std::u16string_view url); + + const Reference<deployment::XPackageTypeInfo> m_xBasicLibTypeInfo; + const Reference<deployment::XPackageTypeInfo> m_xDialogLibTypeInfo; + Sequence< Reference<deployment::XPackageTypeInfo> > m_typeInfos; + std::unique_ptr<ScriptBackendDb> m_backendDb; +public: + BackendImpl( Sequence<Any> const & args, + 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; + + // XUpdatable + virtual void SAL_CALL update() override; + + // XPackageRegistry + virtual Sequence< Reference<deployment::XPackageTypeInfo> > SAL_CALL + getSupportedPackageTypes() override; + virtual void SAL_CALL packageRemoved(OUString const & url, OUString const & mediaType) override; + +}; + + +BackendImpl::PackageImpl::PackageImpl( + ::rtl::Reference<BackendImpl> const & myBackend, + OUString const & url, + Reference<XCommandEnvironment> const &xCmdEnv, + OUString const & scriptURL, OUString const & dialogURL, bool bRemoved, + OUString const & identifier) + : Package( myBackend, url, + OUString(), OUString(), // will be late-initialized + !scriptURL.isEmpty() ? myBackend->m_xBasicLibTypeInfo + : myBackend->m_xDialogLibTypeInfo, bRemoved, identifier), + m_scriptURL( scriptURL ), + m_dialogURL( dialogURL ) +{ + // name, displayName: + if (!dialogURL.isEmpty()) { + m_dialogName = LibraryContainer::get_libname( + dialogURL, xCmdEnv, myBackend->getComponentContext() ); + } + if (!scriptURL.isEmpty()) { + assert(m_name.pData); + m_name = LibraryContainer::get_libname( + scriptURL, xCmdEnv, myBackend->getComponentContext() ); + } + else + m_name = m_dialogName; + m_displayName = m_name; +} + + +BackendImpl::BackendImpl( + Sequence<Any> const & args, + Reference<XComponentContext> const & xComponentContext ) + : t_helper( args, xComponentContext ), + m_xBasicLibTypeInfo( new Package::TypeInfo( + "application/vnd.sun.star.basic-library", + OUString() /* no file filter */, + DpResId(RID_STR_BASIC_LIB) + ) ), + m_xDialogLibTypeInfo( new Package::TypeInfo( + "application/vnd.sun.star.dialog-library", + OUString() /* no file filter */, + DpResId(RID_STR_DIALOG_LIB) + ) ), + m_typeInfos{ m_xBasicLibTypeInfo, m_xDialogLibTypeInfo } +{ + OSL_ASSERT( ! transientMode() ); + + if (!transientMode()) + { + OUString dbFile = makeURL(getCachePath(), "backenddb.xml"); + m_backendDb.reset( + new ScriptBackendDb(getComponentContext(), dbFile)); + } + +} + +// XServiceInfo +OUString BackendImpl::getImplementationName() +{ + return "com.sun.star.comp.deployment.script.PackageRegistryBackend"; +} + +sal_Bool BackendImpl::supportsService( const OUString& ServiceName ) +{ + return cppu::supportsService(this, ServiceName); +} + +css::uno::Sequence< OUString > BackendImpl::getSupportedServiceNames() +{ + return { BACKEND_SERVICE_NAME }; +} + +void BackendImpl::addDataToDb(OUString const & url) +{ + if (m_backendDb) + m_backendDb->addEntry(url); +} + +bool BackendImpl::hasActiveEntry(std::u16string_view url) +{ + if (m_backendDb) + return m_backendDb->hasActiveEntry(url); + return false; +} + +// XUpdatable + +void BackendImpl::update() +{ + // Nothing to do here after fixing i70283!? +} + +// XPackageRegistry + +Sequence< Reference<deployment::XPackageTypeInfo> > +BackendImpl::getSupportedPackageTypes() +{ + return m_typeInfos; +} +void BackendImpl::revokeEntryFromDb(std::u16string_view url) +{ + if (m_backendDb) + m_backendDb->revokeEntry(url); +} + +void BackendImpl::packageRemoved(OUString const & url, OUString const & /*mediaType*/) +{ + if (m_backendDb) + m_backendDb->removeEntry(url); +} + +// PackageRegistryBackend + +Reference<deployment::XPackage> BackendImpl::bindPackage_( + OUString const & url, OUString const & mediaType_, + bool bRemoved, OUString const & identifier, + Reference<XCommandEnvironment> const & xCmdEnv ) +{ + OUString mediaType( mediaType_ ); + if (mediaType.isEmpty()) + { + // detect media-type: + ::ucbhelper::Content ucbContent; + if (create_ucb_content( &ucbContent, url, xCmdEnv ) && + ucbContent.isFolder()) + { + // probe for script.xlb: + if (create_ucb_content( + nullptr, makeURL( url, "script.xlb" ), + xCmdEnv, false /* no throw */ )) + mediaType = "application/vnd.sun.star.basic-library"; + // probe for dialog.xlb: + else if (create_ucb_content( + nullptr, makeURL( url, "dialog.xlb" ), + xCmdEnv, false /* no throw */ )) + mediaType = "application/vnd.sun.star.dialog-library"; + } + if (mediaType.isEmpty()) + throw lang::IllegalArgumentException( + StrCannotDetectMediaType() + url, + static_cast<OWeakObject *>(this), static_cast<sal_Int16>(-1) ); + } + + OUString type, subType; + INetContentTypeParameterList params; + if (INetContentTypes::parse( mediaType, type, subType, ¶ms )) + { + if (type.equalsIgnoreAsciiCase("application")) + { + OUString dialogURL( makeURL( url, "dialog.xlb" ) ); + if (! create_ucb_content( + nullptr, dialogURL, xCmdEnv, false /* no throw */ )) { + dialogURL.clear(); + } + + if (subType.equalsIgnoreAsciiCase("vnd.sun.star.basic-library")) + { + OUString scriptURL( makeURL( url, "script.xlb")); + if (! create_ucb_content( + nullptr, scriptURL, xCmdEnv, false /* no throw */ )) { + scriptURL.clear(); + } + + return new PackageImpl( + this, url, xCmdEnv, scriptURL, + dialogURL, bRemoved, identifier); + } + else if (subType.equalsIgnoreAsciiCase( + "vnd.sun.star.dialog-library")) { + return new PackageImpl( + this, url, xCmdEnv, + OUString() /* no script lib */, + dialogURL, + bRemoved, identifier); + } + } + } + throw lang::IllegalArgumentException( + StrUnsupportedMediaType() + mediaType, + static_cast<OWeakObject *>(this), + static_cast<sal_Int16>(-1) ); +} + + +// Package +BackendImpl * BackendImpl::PackageImpl::getMyBackend() const +{ + BackendImpl * pBackend = static_cast<BackendImpl *>(m_myBackend.get()); + if (nullptr == pBackend) + { + //May throw a DisposedException + check(); + //We should never get here... + throw RuntimeException( + "Failed to get the BackendImpl", + static_cast<OWeakObject*>(const_cast<PackageImpl *>(this))); + } + return pBackend; +} + +beans::Optional< beans::Ambiguous<sal_Bool> > +BackendImpl::PackageImpl::isRegistered_( + ::osl::ResettableMutexGuard & /* guard */, + ::rtl::Reference<AbortChannel> const & /* abortChannel */, + Reference<XCommandEnvironment> const & /* xCmdEnv */ ) +{ + BackendImpl * that = getMyBackend(); + Reference< deployment::XPackage > xThisPackage( this ); + + bool registered = that->hasActiveEntry(getURL()); + return beans::Optional< beans::Ambiguous<sal_Bool> >( + true /* IsPresent */, + beans::Ambiguous<sal_Bool>( registered, false /* IsAmbiguous */ ) ); +} + +void +lcl_maybeRemoveScript( + bool const bExists, + OUString const& rName, + std::u16string_view rScriptURL, + Reference<css::script::XLibraryContainer3> const& xScriptLibs) +{ + if (bExists && xScriptLibs.is() && xScriptLibs->hasByName(rName)) + { + const OUString sScriptUrl = xScriptLibs->getOriginalLibraryLinkURL(rName); + if (sScriptUrl == rScriptURL) + xScriptLibs->removeLibrary(rName); + } +} + +bool +lcl_maybeAddScript( + bool const bExists, + OUString const& rName, + OUString const& rScriptURL, + Reference<css::script::XLibraryContainer3> const& xScriptLibs) +{ + if (!bExists || !xScriptLibs) + return false; + + bool bCanAdd = true; + if (xScriptLibs->hasByName(rName)) + { + const OUString sOriginalUrl = xScriptLibs->getOriginalLibraryLinkURL(rName); + //We assume here that library names in extensions are unique, which may not be the case + //ToDo: If the script exist in another extension, then both extensions must have the + //same id + if (sOriginalUrl.match("vnd.sun.star.expand:$UNO_USER_PACKAGES_CACHE") + || sOriginalUrl.match("vnd.sun.star.expand:$UNO_SHARED_PACKAGES_CACHE") + || sOriginalUrl.match("vnd.sun.star.expand:$BUNDLED_EXTENSIONS") + || sOriginalUrl.match("$(INST)/share/basic/Access2Base/")) + { + xScriptLibs->removeLibrary(rName); + bCanAdd = true; + } + else + { + bCanAdd = false; + } + } + + if (bCanAdd) + { + xScriptLibs->createLibraryLink(rName, rScriptURL, false); + return xScriptLibs->hasByName(rName); + } + + return false; +} + +void BackendImpl::PackageImpl::processPackage_( + ::osl::ResettableMutexGuard & /* guard */, + bool doRegisterPackage, + bool startup, + ::rtl::Reference<AbortChannel> const & /* abortChannel */, + Reference<XCommandEnvironment> const & /* xCmdEnv */ ) +{ + BackendImpl * that = getMyBackend(); + + Reference< deployment::XPackage > xThisPackage( this ); + Reference<XComponentContext> const & xComponentContext = that->getComponentContext(); + + bool bScript = !m_scriptURL.isEmpty(); + Reference<css::script::XLibraryContainer3> xScriptLibs; + + bool bDialog = !m_dialogURL.isEmpty(); + Reference<css::script::XLibraryContainer3> xDialogLibs; + + bool bRunning = !startup && office_is_running(); + if( bRunning ) + { + if( bScript ) + { + xScriptLibs.set( + xComponentContext->getServiceManager()->createInstanceWithContext( + "com.sun.star.script.ApplicationScriptLibraryContainer", + xComponentContext ), UNO_QUERY_THROW ); + } + + if( bDialog ) + { + xDialogLibs.set( + xComponentContext->getServiceManager()->createInstanceWithContext( + "com.sun.star.script.ApplicationDialogLibraryContainer", + xComponentContext ), UNO_QUERY_THROW ); + } + } + bool bRegistered = getMyBackend()->hasActiveEntry(getURL()); + if( !doRegisterPackage ) + { + //We cannot just call removeLibrary(name) because this could remove a + //script which was added by an extension in a different repository. For + //example, extension foo is contained in the bundled repository and then + //the user adds it to the user repository. The extension manager will + //then register the new script and revoke the script from the bundled + //extension. removeLibrary(name) would now remove the script from the + //user repository. That is, the script of the newly added user extension does + //not work anymore. Therefore we must check if the currently active + //script comes in fact from the currently processed extension. + + if (bRegistered) + { + //we also prevent and live deployment at startup + if (!isRemoved() && !startup) + { + lcl_maybeRemoveScript(bScript, m_name, m_scriptURL, xScriptLibs); + lcl_maybeRemoveScript(bDialog, m_dialogName, m_dialogURL, xDialogLibs); + } + getMyBackend()->revokeEntryFromDb(getURL()); + return; + } + } + if (bRegistered) + return; // Already registered + + // Update LibraryContainer + bool bScriptSuccess = false; + bool bDialogSuccess = false; + if (!startup) + { + //If there is a bundled extension, and the user installs the same extension + //then the script from the bundled extension must be removed. If this does not work + //then live deployment does not work for scripts. + bScriptSuccess = lcl_maybeAddScript(bScript, m_name, m_scriptURL, xScriptLibs); + bDialogSuccess = lcl_maybeAddScript(bDialog, m_dialogName, m_dialogURL, xDialogLibs); + } + bool bSuccess = bScript || bDialog; // Something must have happened + if( bRunning ) + if( (bScript && !bScriptSuccess) || (bDialog && !bDialogSuccess) ) + bSuccess = false; + + if (bSuccess) + getMyBackend()->addDataToDb(getURL()); +} + +} // anon namespace + +} // namespace dp_registry::backend::script + +extern "C" SAL_DLLPUBLIC_EXPORT css::uno::XInterface* +com_sun_star_comp_deployment_script_PackageRegistryBackend_get_implementation( + css::uno::XComponentContext* context, css::uno::Sequence<css::uno::Any> const& args) +{ + return cppu::acquire(new dp_registry::backend::script::BackendImpl(args, context)); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/desktop/source/deployment/registry/script/dp_scriptbackenddb.cxx b/desktop/source/deployment/registry/script/dp_scriptbackenddb.cxx new file mode 100644 index 000000000..476f43953 --- /dev/null +++ b/desktop/source/deployment/registry/script/dp_scriptbackenddb.cxx @@ -0,0 +1,64 @@ +/* -*- 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/uno/XComponentContext.hpp> +#include "dp_scriptbackenddb.hxx" + + +using namespace ::com::sun::star::uno; + +constexpr OUStringLiteral EXTENSION_REG_NS = u"http://openoffice.org/extensionmanager/script-registry/2010"; +constexpr OUStringLiteral NS_PREFIX = u"script"; +constexpr OUStringLiteral ROOT_ELEMENT_NAME = u"script-backend-db"; +constexpr OUStringLiteral KEY_ELEMENT_NAME = u"script"; + +namespace dp_registry::backend::script { + +ScriptBackendDb::ScriptBackendDb( + Reference<XComponentContext> const & xContext, + OUString const & url):RegisteredDb(xContext, url) +{ + +} + +OUString ScriptBackendDb::getDbNSName() +{ + return EXTENSION_REG_NS; +} + +OUString ScriptBackendDb::getNSPrefix() +{ + return NS_PREFIX; +} + +OUString ScriptBackendDb::getRootElementName() +{ + return ROOT_ELEMENT_NAME; +} + +OUString ScriptBackendDb::getKeyElementName() +{ + return KEY_ELEMENT_NAME; +} + + +} // namespace dp_registry::backend::script + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/desktop/source/deployment/registry/script/dp_scriptbackenddb.hxx b/desktop/source/deployment/registry/script/dp_scriptbackenddb.hxx new file mode 100644 index 000000000..21d4b1f6b --- /dev/null +++ b/desktop/source/deployment/registry/script/dp_scriptbackenddb.hxx @@ -0,0 +1,55 @@ +/* -*- 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 <rtl/ustring.hxx> +#include <dp_backenddb.hxx> +#include <optional> + +namespace com::sun::star::uno { class XComponentContext; } + +namespace dp_registry::backend::script { + +/* The XML file stores the extensions which are currently registered. + They will be removed when they are revoked. + */ +class ScriptBackendDb: public dp_registry::backend::RegisteredDb +{ +protected: + virtual OUString getDbNSName() override; + + virtual OUString getNSPrefix() override; + + virtual OUString getRootElementName() override; + + virtual OUString getKeyElementName() override; + + +public: + + ScriptBackendDb( css::uno::Reference<css::uno::XComponentContext> const & xContext, + OUString const & url); +}; + + +} + + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/desktop/source/deployment/registry/sfwk/dp_parceldesc.cxx b/desktop/source/deployment/registry/sfwk/dp_parceldesc.cxx new file mode 100644 index 000000000..530924a07 --- /dev/null +++ b/desktop/source/deployment/registry/sfwk/dp_parceldesc.cxx @@ -0,0 +1,103 @@ +/* -*- 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_misc.h> +#include "dp_parceldesc.hxx" + + +using namespace ::com::sun::star; +using namespace ::com::sun::star::uno; + + +namespace dp_registry::backend::sfwk +{ + + +// XDocumentHandler +void SAL_CALL +ParcelDescDocHandler::startDocument() +{ + m_bIsParsed = false; +} + +void SAL_CALL +ParcelDescDocHandler::endDocument() +{ + m_bIsParsed = true; +} + +void SAL_CALL +ParcelDescDocHandler::characters( const OUString & ) +{ +} + +void SAL_CALL +ParcelDescDocHandler::ignorableWhitespace( const OUString & ) +{ +} + +void SAL_CALL +ParcelDescDocHandler::processingInstruction( + const OUString &, const OUString & ) +{ +} + +void SAL_CALL +ParcelDescDocHandler::setDocumentLocator( + const Reference< xml::sax::XLocator >& ) +{ +} + +void SAL_CALL +ParcelDescDocHandler::startElement( const OUString& aName, + const Reference< xml::sax::XAttributeList > & xAttribs ) +{ + + dp_misc::TRACE("ParcelDescDocHandler::startElement() for " + + aName + "\n"); + if ( !skipIndex ) + { + if ( aName == "parcel" ) + { + m_sLang = xAttribs->getValueByName( "language" ); + } + ++skipIndex; + } + else + { + dp_misc::TRACE("ParcelDescDocHandler::startElement() skipping for " + + aName + "\n"); + } + +} + +void SAL_CALL ParcelDescDocHandler::endElement( const OUString & aName ) +{ + if ( skipIndex ) + { + --skipIndex; + dp_misc::TRACE("ParcelDescDocHandler::endElement() skipping for " + + aName + "\n"); + } +} + + +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/desktop/source/deployment/registry/sfwk/dp_parceldesc.hxx b/desktop/source/deployment/registry/sfwk/dp_parceldesc.hxx new file mode 100644 index 000000000..6b5bde8bd --- /dev/null +++ b/desktop/source/deployment/registry/sfwk/dp_parceldesc.hxx @@ -0,0 +1,64 @@ +/* -*- 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/xml/sax/XAttributeList.hpp> +#include <com/sun/star/xml/sax/XDocumentHandler.hpp> + +namespace dp_registry::backend::sfwk +{ + +class ParcelDescDocHandler : public ::cppu::WeakImplHelper< css::xml::sax::XDocumentHandler > +{ +private: + bool m_bIsParsed; + OUString m_sLang; + sal_Int32 skipIndex; +public: + ParcelDescDocHandler():m_bIsParsed( false ), skipIndex( 0 ){} + const OUString& getParcelLanguage() const { return m_sLang; } + bool isParsed() const { return m_bIsParsed; } + // XDocumentHandler + virtual void SAL_CALL startDocument() override; + + virtual void SAL_CALL endDocument() override; + + virtual void SAL_CALL startElement( const OUString& aName, + const css::uno::Reference< css::xml::sax::XAttributeList > & xAttribs ) override; + + virtual void SAL_CALL endElement( const OUString & aName ) override; + + virtual void SAL_CALL characters( const OUString & aChars ) override; + + virtual void SAL_CALL ignorableWhitespace( const OUString & aWhitespaces ) override; + + virtual void SAL_CALL processingInstruction( + const OUString & aTarget, const OUString & aData ) override; + + virtual void SAL_CALL setDocumentLocator( + const css::uno::Reference< css::xml::sax::XLocator >& xLocator ) override; +}; +} + + + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/desktop/source/deployment/registry/sfwk/dp_sfwk.cxx b/desktop/source/deployment/registry/sfwk/dp_sfwk.cxx new file mode 100644 index 000000000..b617d7fa4 --- /dev/null +++ b/desktop/source/deployment/registry/sfwk/dp_sfwk.cxx @@ -0,0 +1,378 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include <sal/config.h> + +#include <strings.hrc> +#include <dp_backend.h> +#include <dp_misc.h> +#include <dp_ucb.h> +#include "dp_parceldesc.hxx" +#include <rtl/uri.hxx> +#include <ucbhelper/content.hxx> +#include <svl/inettype.hxx> +#include <com/sun/star/container/XNameContainer.hpp> +#include <com/sun/star/script/provider/theMasterScriptProviderFactory.hpp> +#include <com/sun/star/xml/sax/Parser.hpp> +#include <cppuhelper/supportsservice.hxx> +#include <utility> + + +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::script; + + +namespace dp_registry::backend::sfwk +{ + +namespace { + +class BackendImpl : public ::dp_registry::backend::PackageRegistryBackend +{ + class PackageImpl : public ::dp_registry::backend::Package + { + BackendImpl * getMyBackend() const; + + Reference< container::XNameContainer > m_xNameCntrPkgHandler; + OUString m_descr; + + void initPackageHandler(); + + // Package + virtual beans::Optional< beans::Ambiguous<sal_Bool> > isRegistered_( + ::osl::ResettableMutexGuard & guard, + ::rtl::Reference<AbortChannel> const & abortChannel, + Reference<XCommandEnvironment> const & xCmdEnv ) override; + virtual void processPackage_( + ::osl::ResettableMutexGuard & guard, + bool registerPackage, + bool startup, + ::rtl::Reference<AbortChannel> const & abortChannel, + Reference<XCommandEnvironment> const & xCmdEnv ) override; + + public: + PackageImpl( + ::rtl::Reference<BackendImpl> const & myBackend, + OUString const & url, OUString libType, bool bRemoved, + OUString const & identifier); + // XPackage + virtual OUString SAL_CALL getDescription() override; + virtual OUString SAL_CALL getLicenseText() override; + }; + friend class PackageImpl; + + // PackageRegistryBackend + virtual Reference<deployment::XPackage> bindPackage_( + OUString const & url, OUString const & mediaType, + bool bRemoved, OUString const & identifier, + Reference<XCommandEnvironment> const & xCmdEnv ) override; + + const Reference<deployment::XPackageTypeInfo> m_xTypeInfo; + + +public: + BackendImpl( + Sequence<Any> const & args, + 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; + + // XPackageRegistry + virtual Sequence< Reference<deployment::XPackageTypeInfo> > SAL_CALL + getSupportedPackageTypes() override; + virtual void SAL_CALL packageRemoved(OUString const & url, OUString const & mediaType) override; +}; + +} + +BackendImpl * BackendImpl::PackageImpl::getMyBackend() const +{ + BackendImpl * pBackend = static_cast<BackendImpl *>(m_myBackend.get()); + if (nullptr == pBackend) + { + //May throw a DisposedException + check(); + //We should never get here... + throw RuntimeException("Failed to get the BackendImpl", + static_cast<OWeakObject*>(const_cast<PackageImpl *>(this))); + } + return pBackend; +} + +OUString BackendImpl::PackageImpl::getDescription() +{ + if (m_descr.isEmpty()) + return Package::getDescription(); + else + return m_descr; +} + +OUString BackendImpl::PackageImpl::getLicenseText() +{ + return Package::getDescription(); +} + +BackendImpl::PackageImpl::PackageImpl( + ::rtl::Reference<BackendImpl> const & myBackend, + OUString const & url, OUString libType, bool bRemoved, + OUString const & identifier) + : Package( myBackend, url, OUString(), OUString(), + myBackend->m_xTypeInfo, bRemoved, identifier), + m_descr(std::move(libType)) +{ + initPackageHandler(); + + sal_Int32 segmEnd = url.getLength(); + if ( url.endsWith("/") ) + --segmEnd; + sal_Int32 segmStart = url.lastIndexOf( '/', segmEnd ) + 1; + if (segmStart < 0) + segmStart = 0; + // name and display name default the same: + m_displayName = ::rtl::Uri::decode( + url.copy( segmStart, segmEnd - segmStart ), + rtl_UriDecodeWithCharset, RTL_TEXTENCODING_UTF8 ); + m_name = m_displayName; + + dp_misc::TRACE("PackageImpl displayName is " + m_displayName); +} + + +BackendImpl::BackendImpl( + Sequence<Any> const & args, + Reference<XComponentContext> const & xComponentContext ) + : PackageRegistryBackend( args, xComponentContext ), + m_xTypeInfo( new Package::TypeInfo( + "application/vnd.sun.star.framework-script", + OUString() /* no file filter */, + "Scripting Framework Script Library" + ) ) +{ +} + + +// XServiceInfo +OUString BackendImpl::getImplementationName() +{ + return "com.sun.star.comp.deployment.sfwk.PackageRegistryBackend"; +} + +sal_Bool BackendImpl::supportsService( const OUString& ServiceName ) +{ + return cppu::supportsService(this, ServiceName); +} + +css::uno::Sequence< OUString > BackendImpl::getSupportedServiceNames() +{ + return { BACKEND_SERVICE_NAME }; +} + +// XPackageRegistry + +Sequence< Reference<deployment::XPackageTypeInfo> > +BackendImpl::getSupportedPackageTypes() +{ + return Sequence< Reference<deployment::XPackageTypeInfo> >(&m_xTypeInfo, 1); +} + +void BackendImpl::packageRemoved(OUString const & /*url*/, OUString const & /*mediaType*/) +{ +} + +// PackageRegistryBackend + +Reference<deployment::XPackage> BackendImpl::bindPackage_( + OUString const & url, OUString const & mediaType_, bool bRemoved, + OUString const & identifier, Reference<XCommandEnvironment> const & xCmdEnv ) +{ + OUString mediaType( mediaType_ ); + if (mediaType.isEmpty()) + { + // detect media-type: + ::ucbhelper::Content ucbContent; + if (create_ucb_content( &ucbContent, url, xCmdEnv ) && + ucbContent.isFolder()) + { + // probe for parcel-descriptor.xml: + if (create_ucb_content( + nullptr, makeURL( url, "parcel-descriptor.xml" ), + xCmdEnv, false /* no throw */ )) + { + mediaType = "application/vnd.sun.star.framework-script"; + } + } + if (mediaType.isEmpty()) + throw lang::IllegalArgumentException( + StrCannotDetectMediaType() + url, + static_cast<OWeakObject *>(this), static_cast<sal_Int16>(-1) ); + } + + OUString type, subType; + INetContentTypeParameterList params; + if (INetContentTypes::parse( mediaType, type, subType, ¶ms )) + { + if (type.equalsIgnoreAsciiCase("application")) + { + if (subType.equalsIgnoreAsciiCase("vnd.sun.star.framework-script")) + { + OUString lang = "Script"; + OUString sParcelDescURL = makeURL( + url, "parcel-descriptor.xml" ); + + ::ucbhelper::Content ucb_content; + + if (create_ucb_content( &ucb_content, sParcelDescURL, + xCmdEnv, false /* no throw */ )) + { + rtl::Reference<ParcelDescDocHandler> pHandler = + new ParcelDescDocHandler(); + + Reference<XComponentContext> + xContext( getComponentContext() ); + + Reference< xml::sax::XParser > xParser = xml::sax::Parser::create(xContext); + + xParser->setDocumentHandler( pHandler ); + xml::sax::InputSource source; + source.aInputStream = ucb_content.openStream(); + source.sSystemId = ucb_content.getURL(); + xParser->parseStream( source ); + + if ( pHandler->isParsed() ) + { + lang = pHandler->getParcelLanguage(); + } + } + + OUString sfwkLibType = DpResId( RID_STR_SFWK_LIB ); + // replace %MACRONAME placeholder with language name + OUString MACRONAME( "%MACROLANG" ); + sal_Int32 startOfReplace = sfwkLibType.indexOf( MACRONAME ); + sal_Int32 charsToReplace = MACRONAME.getLength(); + sfwkLibType = sfwkLibType.replaceAt( startOfReplace, charsToReplace, lang ); + dp_misc::TRACE("******************************\n"); + dp_misc::TRACE(" BackEnd detected lang = " + lang + "\n"); + dp_misc::TRACE(" for url " + sParcelDescURL + "\n"); + dp_misc::TRACE("******************************\n"); + return new PackageImpl( this, url, sfwkLibType, bRemoved, identifier); + } + } + } + throw lang::IllegalArgumentException( + StrUnsupportedMediaType() + mediaType, + static_cast<OWeakObject *>(this), + static_cast<sal_Int16>(-1) ); +} + + +void BackendImpl::PackageImpl:: initPackageHandler() +{ + if (m_xNameCntrPkgHandler.is()) + return; + + BackendImpl * that = getMyBackend(); + Any aContext; + + if ( that->m_eContext == Context::User ) + { + aContext <<= OUString("user"); + } + else if ( that->m_eContext == Context::Shared ) + { + aContext <<= OUString("share"); + } + else if ( that->m_eContext == Context::Bundled ) + { + aContext <<= OUString("bundled"); + } + else + { + OSL_ASSERT( false ); + // NOT supported at the moment // TODO + } + + Reference< provider::XScriptProviderFactory > xFac = + provider::theMasterScriptProviderFactory::get( that->getComponentContext() ); + + Reference< container::XNameContainer > xName( xFac->createScriptProvider( aContext ), UNO_QUERY ); + if ( xName.is() ) + { + m_xNameCntrPkgHandler.set( xName ); + } + // TODO what happens if above fails?? +} + +// Package + +beans::Optional< beans::Ambiguous<sal_Bool> > +BackendImpl::PackageImpl::isRegistered_( + ::osl::ResettableMutexGuard &, + ::rtl::Reference<AbortChannel> const &, + Reference<XCommandEnvironment> const & ) +{ + return beans::Optional< beans::Ambiguous<sal_Bool> >( + true /* IsPresent */, + beans::Ambiguous<sal_Bool>( + m_xNameCntrPkgHandler.is() && m_xNameCntrPkgHandler->hasByName( + m_url ), + false /* IsAmbiguous */ ) ); +} + + +void BackendImpl::PackageImpl::processPackage_( + ::osl::ResettableMutexGuard &, + bool doRegisterPackage, + bool /* startup */, + ::rtl::Reference<AbortChannel> const &, + Reference<XCommandEnvironment> const & ) +{ + if ( !m_xNameCntrPkgHandler.is() ) + { + dp_misc::TRACE("no package handler!!!!\n"); + throw RuntimeException( "No package Handler " ); + } + + if (doRegisterPackage) + { + // will throw if it fails + m_xNameCntrPkgHandler->insertByName( m_url, Any( Reference< XPackage >(this) ) ); + + } + else // revokePackage() + { + m_xNameCntrPkgHandler->removeByName( m_url ); + } +} + +} // namespace dp_registry::backend::sfwk + +extern "C" SAL_DLLPUBLIC_EXPORT css::uno::XInterface* +com_sun_star_comp_deployment_sfwk_PackageRegistryBackend_get_implementation( + css::uno::XComponentContext* context, css::uno::Sequence<css::uno::Any> const& args) +{ + return cppu::acquire(new dp_registry::backend::sfwk::BackendImpl(args, context)); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ |