/* -*- 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 #include #include #include #include #include "dp_helpbackenddb.hxx" #include #include #include #include #include #include #include #include #if HAVE_FEATURE_XMLHELP #include #include #endif #include #include #include #include #include #include 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 > isRegistered_( ::osl::ResettableMutexGuard & guard, ::rtl::Reference const & abortChannel, Reference const & xCmdEnv ) override; virtual void processPackage_( ::osl::ResettableMutexGuard & guard, bool registerPackage, bool startup, ::rtl::Reference const & abortChannel, Reference const & xCmdEnv ) override; public: PackageImpl( ::rtl::Reference const & myBackend, OUString const & url, OUString const & name, Reference 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 bindPackage_( OUString const & url, OUString const & mediaType, bool bRemoved, OUString const & identifier, Reference const & xCmdEnv ) override; void implProcessHelp( PackageImpl * package, bool doRegisterPackage, Reference const & xCmdEnv); void implCollectXhpFiles( const OUString& aDir, std::vector< OUString >& o_rXhpFileVector ); ::std::optional 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 m_xHelpTypeInfo; Sequence< Reference > m_typeInfos; std::unique_ptr m_backendDb; public: BackendImpl( Sequence const & args, Reference 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 > SAL_CALL getSupportedPackageTypes() override; virtual void SAL_CALL packageRemoved(OUString const & url, OUString const & mediaType) override; }; BackendImpl::BackendImpl( Sequence const & args, Reference 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 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 > BackendImpl::getSupportedPackageTypes() { return m_typeInfos; } void BackendImpl::packageRemoved(OUString const & url, OUString const & /*mediaType*/) { if (m_backendDb) m_backendDb->removeEntry(url); } // PackageRegistryBackend Reference BackendImpl::bindPackage_( OUString const & url, OUString const & mediaType_, bool bRemoved, OUString const & identifier, Reference const & xCmdEnv ) { // we don't support auto detection: if (mediaType_.isEmpty()) throw lang::IllegalArgumentException( StrCannotDetectMediaType() + url, static_cast(this), static_cast(-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(this), static_cast(-1) ); } ::std::optional BackendImpl::readDataFromDb( std::u16string_view url) { ::std::optional 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 const & myBackend, OUString const & url, OUString const & name, Reference 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(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(const_cast(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 > BackendImpl::PackageImpl::isRegistered_( ::osl::ResettableMutexGuard &, ::rtl::Reference const &, Reference const & ) { BackendImpl * that = getMyBackend(); bool bReg = false; if (that->hasActiveEntry(getURL())) bReg = true; return beans::Optional< beans::Ambiguous >( true, beans::Ambiguous( bReg, false ) ); } void BackendImpl::PackageImpl::processPackage_( ::osl::ResettableMutexGuard &, bool doRegisterPackage, bool /* startup */, ::rtl::Reference const &, Reference const & xCmdEnv ) { BackendImpl* that = getMyBackend(); that->implProcessHelp( this, doRegisterPackage, xCmdEnv); } beans::Optional< OUString > BackendImpl::PackageImpl::getRegistrationDataURL() { if (m_bRemoved) throw deployment::ExtensionRemovedException(); ::std::optional data = getMyBackend()->readDataFromDb(getURL()); if (data && getMyBackend()->hasActiveEntry(getURL())) return beans::Optional(true, data->dataUrl); return beans::Optional(true, OUString()); } void BackendImpl::implProcessHelp( PackageImpl * package, bool doRegisterPackage, Reference 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 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 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 const& args) { return cppu::acquire(new dp_registry::backend::help::BackendImpl(args, context)); } /* vim:set shiftwidth=4 softtabstop=4 expandtab: */