/************************************************************** * * 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 * * Unless required by applicable law or agreed to in writing, * software distributed under the License is distributed on an * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * KIND, either express or implied. See the License for the * specific language governing permissions and limitations * under the License. * *************************************************************/ // MARKER(update_precomp.py): autogen include statement, do not remove #include "precompiled_desktop.hxx" //TODO: Large parts of this file were copied from dp_component.cxx; those parts // should be consolidated. #include "dp_configuration.hrc" #include "dp_backend.h" #include "dp_persmap.h" #include "dp_ucb.h" #include "rtl/string.hxx" #include "rtl/ustrbuf.hxx" #include "rtl/uri.hxx" #include "rtl/memory.h" #include "osl/file.hxx" #include "cppuhelper/exc_hlp.hxx" #include "ucbhelper/content.hxx" #include "comphelper/anytostring.hxx" #include "comphelper/servicedecl.hxx" #include "xmlscript/xml_helper.hxx" #include "svl/inettype.hxx" #include "com/sun/star/configuration/Update.hpp" #include "com/sun/star/ucb/NameClash.hpp" #include "com/sun/star/io/XActiveDataSink.hpp" #include "com/sun/star/lang/WrappedTargetRuntimeException.hpp" #include "com/sun/star/util/XRefreshable.hpp" #include #include #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; using ::rtl::OUString; namespace dp_registry { namespace backend { namespace configuration { namespace { typedef ::std::list t_stringlist; //============================================================================== 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 > isRegistered_( ::osl::ResettableMutexGuard & guard, ::rtl::Reference const & abortChannel, Reference const & xCmdEnv ); virtual void processPackage_( ::osl::ResettableMutexGuard & guard, bool registerPackage, bool startup, ::rtl::Reference const & abortChannel, Reference const & xCmdEnv ); public: inline PackageImpl( ::rtl::Reference const & myBackend, OUString const & url, OUString const & name, Reference 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; t_stringlist m_xcs_files; t_stringlist m_xcu_files; t_stringlist & getFiles( bool xcs ) { return xcs ? m_xcs_files : m_xcu_files; } bool m_configmgrini_inited; bool m_configmgrini_modified; std::auto_ptr m_backendDb; // PackageRegistryBackend virtual Reference bindPackage_( OUString const & url, OUString const & mediaType, sal_Bool bRemoved, OUString const & identifier, Reference const & xCmdEnv ); ::std::auto_ptr m_registeredPackages; // for backwards compatibility virtual void SAL_CALL disposing(); const Reference m_xConfDataTypeInfo; const Reference m_xConfSchemaTypeInfo; Sequence< Reference > m_typeInfos; void configmgrini_verify_init( Reference const & xCmdEnv ); void configmgrini_flush( Reference const & xCmdEnv ); /* The paramter 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. */ bool addToConfigmgrIni( bool isSchema, bool isURL, OUString const & url, Reference const & xCmdEnv ); bool removeFromConfigmgrIni( bool isSchema, OUString const & url, Reference const & xCmdEnv ); void addDataToDb(OUString const & url, ConfigurationBackendDb::Data const & data); ::boost::optional readDataFromDb(OUString const & url); void revokeEntryFromDb(OUString const & url); ::std::list getAllIniEntries(); bool hasActiveEntry(OUString const & url); bool activateEntry(OUString const & url); public: BackendImpl( Sequence const & args, Reference const & xComponentContext ); // XPackageRegistry virtual Sequence< Reference > SAL_CALL getSupportedPackageTypes() throw (RuntimeException); virtual void SAL_CALL packageRemoved(OUString const & url, OUString const & mediaType) throw (deployment::DeploymentException, uno::RuntimeException); using PackageRegistryBackend::disposing; }; //______________________________________________________________________________ void BackendImpl::disposing() { try { configmgrini_flush( Reference() ); PackageRegistryBackend::disposing(); } catch (RuntimeException &) { throw; } catch (Exception &) { Any exc( ::cppu::getCaughtException() ); throw lang::WrappedTargetRuntimeException( OUSTR("caught unexpected exception while disposing..."), static_cast(this), exc ); } } //______________________________________________________________________________ BackendImpl::BackendImpl( Sequence const & args, Reference const & xComponentContext ) : PackageRegistryBackend( args, xComponentContext ), m_configmgrini_inited( false ), m_configmgrini_modified( false ), m_xConfDataTypeInfo( new Package::TypeInfo( OUSTR("application/" "vnd.sun.star.configuration-data"), OUSTR("*.xcu"), getResourceString(RID_STR_CONF_DATA), RID_IMG_CONF_XML, RID_IMG_CONF_XML_HC ) ), m_xConfSchemaTypeInfo( new Package::TypeInfo( OUSTR("application/" "vnd.sun.star.configuration-schema"), OUSTR("*.xcs"), getResourceString(RID_STR_CONF_SCHEMA), RID_IMG_CONF_XML, RID_IMG_CONF_XML_HC ) ), m_typeInfos( 2 ) { m_typeInfos[ 0 ] = m_xConfDataTypeInfo; m_typeInfos[ 1 ] = m_xConfSchemaTypeInfo; const Reference xCmdEnv; if (transientMode()) { //TODO } else { OUString dbFile = makeURL(getCachePath(), OUSTR("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::list folders = m_backendDb->getAllDataUrls(); deleteUnusedFolders(OUString(), folders); configmgrini_verify_init( xCmdEnv ); m_registeredPackages.reset( new PersistentMap( makeURL( getCachePath(), OUSTR("registered_packages.pmap") ), false ) ); } } void BackendImpl::addDataToDb( OUString const & url, ConfigurationBackendDb::Data const & data) { if (m_backendDb.get()) m_backendDb->addEntry(url, data); } ::boost::optional BackendImpl::readDataFromDb( OUString const & url) { ::boost::optional data; if (m_backendDb.get()) data = m_backendDb->getEntry(url); return data; } void BackendImpl::revokeEntryFromDb(OUString const & url) { if (m_backendDb.get()) m_backendDb->revokeEntry(url); } ::std::list BackendImpl::getAllIniEntries() { if (m_backendDb.get()) return m_backendDb->getAllIniEntries(); else return ::std::list(); } bool BackendImpl::hasActiveEntry(OUString const & url) { if (m_backendDb.get()) return m_backendDb->hasActiveEntry(url); return false; } bool BackendImpl::activateEntry(OUString const & url) { if (m_backendDb.get()) return m_backendDb->activateEntry(url); return false; } // XPackageRegistry //______________________________________________________________________________ Sequence< Reference > BackendImpl::getSupportedPackageTypes() throw (RuntimeException) { return m_typeInfos; } void BackendImpl::packageRemoved(OUString const & url, OUString const & /*mediaType*/) throw (deployment::DeploymentException, uno::RuntimeException) { if (m_backendDb.get()) m_backendDb->removeEntry(url); } // PackageRegistryBackend //______________________________________________________________________________ Reference BackendImpl::bindPackage_( OUString const & url, OUString const & mediaType_, sal_Bool bRemoved, OUString const & identifier, Reference const & xCmdEnv ) { OUString mediaType( mediaType_ ); if (mediaType.getLength() == 0) { // detect media-type: ::ucbhelper::Content ucbContent; if (create_ucb_content( &ucbContent, url, xCmdEnv )) { const OUString title( ucbContent.getPropertyValue( StrTitle::get() ).get() ); if (title.endsWithIgnoreAsciiCaseAsciiL( RTL_CONSTASCII_STRINGPARAM(".xcu") )) { mediaType = OUSTR("application/" "vnd.sun.star.configuration-data"); } if (title.endsWithIgnoreAsciiCaseAsciiL( RTL_CONSTASCII_STRINGPARAM(".xcs") )) { mediaType = OUSTR("application/" "vnd.sun.star.configuration-schema"); } } if (mediaType.getLength() == 0) throw lang::IllegalArgumentException( StrCannotDetectMediaType::get() + url, static_cast(this), static_cast(-1) ); } String type, subType; INetContentTypeParameterList params; if (INetContentTypes::parse( mediaType, type, subType, ¶ms )) { if (type.EqualsIgnoreCaseAscii("application")) { OUString name; if (!bRemoved) { ::ucbhelper::Content ucbContent( url, xCmdEnv ); name = ucbContent.getPropertyValue( StrTitle::get() ).get(); } ::ucbhelper::Content ucbContent( url, xCmdEnv ); if (subType.EqualsIgnoreCaseAscii( "vnd.sun.star.configuration-data")) { return new PackageImpl( this, url, name, m_xConfDataTypeInfo, false /* data file */, bRemoved, identifier); } else if (subType.EqualsIgnoreCaseAscii( "vnd.sun.star.configuration-schema")) { return new PackageImpl( this, url, name, m_xConfSchemaTypeInfo, true /* schema file */, bRemoved, identifier); } } } throw lang::IllegalArgumentException( StrUnsupportedMediaType::get() + mediaType, static_cast(this), static_cast(-1) ); } //############################################################################## //______________________________________________________________________________ void BackendImpl::configmgrini_verify_init( Reference const & xCmdEnv ) { if (transientMode()) return; const ::osl::MutexGuard guard( getMutex() ); if (! m_configmgrini_inited) { // common rc: ::ucbhelper::Content ucb_content; if (create_ucb_content( &ucb_content, makeURL( getCachePath(), OUSTR("configmgr.ini") ), xCmdEnv, false /* no throw */ )) { OUString line; if (readLine( &line, OUSTR("SCHEMA="), ucb_content, RTL_TEXTENCODING_UTF8 )) { sal_Int32 index = sizeof ("SCHEMA=") - 1; do { OUString token( line.getToken( 0, ' ', index ).trim() ); if (token.getLength() > 0) { //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, OUSTR("DATA="), ucb_content, RTL_TEXTENCODING_UTF8 )) { sal_Int32 index = sizeof ("DATA=") - 1; do { OUString token( line.getToken( 0, ' ', index ).trim() ); if (token.getLength() > 0) { if (token[ 0 ] == '?') token = token.copy( 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( token ); } } while (index >= 0); } } m_configmgrini_modified = false; m_configmgrini_inited = true; } } //______________________________________________________________________________ void BackendImpl::configmgrini_flush( Reference const & xCmdEnv ) { if (transientMode()) return; if (!m_configmgrini_inited || !m_configmgrini_modified) return; ::rtl::OStringBuffer buf; if (! m_xcs_files.empty()) { t_stringlist::const_iterator iPos( m_xcs_files.begin() ); t_stringlist::const_iterator const iEnd( m_xcs_files.end() ); buf.append( RTL_CONSTASCII_STRINGPARAM("SCHEMA=") ); while (iPos != iEnd) { // encoded ASCII file-urls: const ::rtl::OString item( ::rtl::OUStringToOString( *iPos, RTL_TEXTENCODING_ASCII_US ) ); buf.append( item ); ++iPos; if (iPos != iEnd) buf.append( ' ' ); } buf.append(LF); } if (! m_xcu_files.empty()) { t_stringlist::const_iterator iPos( m_xcu_files.begin() ); t_stringlist::const_iterator const iEnd( m_xcu_files.end() ); buf.append( RTL_CONSTASCII_STRINGPARAM("DATA=") ); while (iPos != iEnd) { // encoded ASCII file-urls: const ::rtl::OString item( ::rtl::OUStringToOString( *iPos, RTL_TEXTENCODING_ASCII_US ) ); buf.append( item ); ++iPos; if (iPos != iEnd) buf.append( ' ' ); } buf.append(LF); } // write configmgr.ini: const Reference xData( ::xmlscript::createInputStream( ::rtl::ByteSequence( reinterpret_cast(buf.getStr()), buf.getLength() ) ) ); ::ucbhelper::Content ucb_content( makeURL( getCachePath(), OUSTR("configmgr.ini") ), xCmdEnv ); ucb_content.writeStream( xData, true /* replace existing */ ); m_configmgrini_modified = false; } //______________________________________________________________________________ bool BackendImpl::addToConfigmgrIni( bool isSchema, bool isURL, OUString const & url_, Reference const & xCmdEnv ) { const OUString rcterm( isURL ? dp_misc::makeRcTerm(url_) : url_ ); const ::osl::MutexGuard guard( getMutex() ); configmgrini_verify_init( xCmdEnv ); t_stringlist & 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 ); return true; } else return false; } //______________________________________________________________________________ bool BackendImpl::removeFromConfigmgrIni( bool isSchema, OUString const & url_, Reference const & xCmdEnv ) { const OUString rcterm( dp_misc::makeRcTerm(url_) ); const ::osl::MutexGuard guard( getMutex() ); configmgrini_verify_init( xCmdEnv ); t_stringlist & rSet = getFiles(isSchema); t_stringlist::iterator 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 installatation. ::boost::optional 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; } //############################################################################## // Package //______________________________________________________________________________ BackendImpl * BackendImpl::PackageImpl::getMyBackend() const { BackendImpl * pBackend = static_cast(m_myBackend.get()); if (NULL == pBackend) { //May throw a DisposedException check(); //We should never get here... throw RuntimeException( OUSTR("Failed to get the BackendImpl"), static_cast(const_cast(this))); } return pBackend; } beans::Optional< beans::Ambiguous > BackendImpl::PackageImpl::isRegistered_( ::osl::ResettableMutexGuard &, ::rtl::Reference const &, Reference const & ) { BackendImpl * that = getMyBackend(); const rtl::OUString url(getURL()); bool bReg = false; if (that->hasActiveEntry(getURL())) bReg = true; if (!bReg) //fallback for user extension registered in berkeley DB bReg = that->m_registeredPackages->has( rtl::OUStringToOString( url, RTL_TEXTENCODING_UTF8 )); return beans::Optional< beans::Ambiguous >( true, beans::Ambiguous( bReg, false ) ); } //------------------------------------------------------------------------------ OUString encodeForXml( OUString const & text ) { // encode conforming xml: sal_Int32 len = text.getLength(); ::rtl::OUStringBuffer buf; for ( sal_Int32 pos = 0; pos < len; ++pos ) { sal_Unicode c = text[ pos ]; switch (c) { case '<': buf.appendAscii( RTL_CONSTASCII_STRINGPARAM("<") ); break; case '>': buf.appendAscii( RTL_CONSTASCII_STRINGPARAM(">") ); break; case '&': buf.appendAscii( RTL_CONSTASCII_STRINGPARAM("&") ); break; case '\'': buf.appendAscii( RTL_CONSTASCII_STRINGPARAM("'") ); break; case '\"': buf.appendAscii( RTL_CONSTASCII_STRINGPARAM(""") ); break; default: buf.append( c ); break; } } return buf.makeStringAndClear(); } //______________________________________________________________________________ OUString replaceOrigin( OUString const & url, OUString const & destFolder, Reference< XCommandEnvironment > const & xCmdEnv, bool & out_replaced) { // looking for %origin%: ::ucbhelper::Content ucb_content( url, xCmdEnv ); ::rtl::ByteSequence bytes( readFile( ucb_content ) ); ::rtl::ByteSequence filtered( bytes.getLength() * 2, ::rtl::BYTESEQ_NODEFAULT ); bool use_filtered = false; ::rtl::OString origin; sal_Char const * pBytes = reinterpret_cast( bytes.getConstArray()); sal_Size nBytes = bytes.getLength(); sal_Int32 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.getLength()) filtered.realloc( (filtered.getLength() + index) * 2 ); rtl_copyMemory( filtered.getArray() + write_pos, pBytes, index ); write_pos += index; pBytes += index; nBytes -= index; if (nBytes == 0) break; // consume %: ++pBytes; --nBytes; sal_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, RTL_CONSTASCII_STRINGPARAM("origin%"), sizeof ("origin%") - 1 ) == 0) { if (origin.getLength() == 0) { // encode only once origin = ::rtl::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 += (sizeof ("origin%") - 1); nBytes -= (sizeof ("origin%") - 1); use_filtered = true; } if ((write_pos + nAdd) > filtered.getLength()) filtered.realloc( (filtered.getLength() + nAdd) * 2 ); rtl_copyMemory( filtered.getArray() + write_pos, pAdd, nAdd ); write_pos += nAdd; } if (!use_filtered) return url; if (write_pos < filtered.getLength()) filtered.realloc( write_pos ); rtl::OUString newUrl(url); if (destFolder.getLength()) { //get the file name of the xcu and add it to the url of the temporary folder sal_Int32 i = url.lastIndexOf('/'); newUrl = destFolder + url.copy(i); } ucbhelper::Content(newUrl, xCmdEnv).writeStream( xmlscript::createInputStream(filtered), true); out_replaced = true; return newUrl; } //______________________________________________________________________________ void BackendImpl::PackageImpl::processPackage_( ::osl::ResettableMutexGuard &, bool doRegisterPackage, bool startup, ::rtl::Reference const &, Reference const & xCmdEnv ) { BackendImpl * that = getMyBackend(); OUString url( getURL() ); if (doRegisterPackage) { if (getMyBackend()->activateEntry(getURL())) { ::boost::optional 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(OUString(), xCmdEnv); bool out_replaced = false; url = replaceOrigin(url, sModFolder, xCmdEnv, 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 && that->m_eContext != CONTEXT_BUNDLED_PREREG && !startup) { if (m_isSchema) { com::sun::star::configuration::Update::get( that->m_xComponentContext)->insertExtensionXcsFile( that->m_eContext == CONTEXT_SHARED, expandUnoRcUrl(url)); } else { com::sun::star::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 (!that->removeFromConfigmgrIni(m_isSchema, url, xCmdEnv)) { t_string2string_map entries( that->m_registeredPackages->getEntries()); for (t_string2string_map::iterator i(entries.begin()); i != entries.end(); ++i) { //If the xcu file was installed before the configmgr was chaned //to use the configmgr.ini, one needed to rebuild to whole directory //structur 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. rtl::OUString url2( rtl::OStringToOUString(i->first, RTL_TEXTENCODING_UTF8)); if (url2 != url) { bool schema = i->second.equalsIgnoreAsciiCase( "vnd.sun.star.configuration-schema"); OUString url_replaced(url2); ConfigurationBackendDb::Data data; if (!schema) { const OUString sModFolder = that->createFolder(OUString(), xCmdEnv); bool out_replaced = false; url_replaced = replaceOrigin( url2, sModFolder, xCmdEnv, 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(i->first); } try { ::ucbhelper::Content( makeURL( that->getCachePath(), OUSTR("registry") ), xCmdEnv ).executeCommand( OUSTR("delete"), Any( true /* delete physically */ ) ); } catch(Exception&) { OSL_ASSERT(0); } } ::boost::optional 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) { com::sun::star::configuration::Update::get( that->m_xComponentContext)->removeExtensionXcuFile(expandUnoRcTerm(data->iniEntry)); } that->revokeEntryFromDb(url); } } } // anon namespace namespace sdecl = comphelper::service_decl; sdecl::class_ > serviceBI; extern sdecl::ServiceDecl const serviceDecl( serviceBI, "com.sun.star.comp.deployment.configuration.PackageRegistryBackend", BACKEND_SERVICE_NAME ); } // namespace configuration } // namespace backend } // namespace dp_registry