1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
3 * This file is part of the LibreOffice project.
5 * This Source Code Form is subject to the terms of the Mozilla Public
6 * License, v. 2.0. If a copy of the MPL was not distributed with this
7 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
9 * This file incorporates work covered by the following license notice:
11 * Licensed to the Apache Software Foundation (ASF) under one or more
12 * contributor license agreements. See the NOTICE file distributed
13 * with this work for additional information regarding copyright
14 * ownership. The ASF licenses this file to you under the Apache
15 * License, Version 2.0 (the "License"); you may not use this file
16 * except in compliance with the License. You may obtain a copy of
17 * the License at http://www.apache.org/licenses/LICENSE-2.0 .
21 #include <config_features.h>
23 #include <strings.hrc>
24 #include <dp_backend.h>
26 #include "dp_helpbackenddb.hxx"
28 #include <rtl/uri.hxx>
29 #include <osl/file.hxx>
30 #include <ucbhelper/content.hxx>
31 #include <svl/inettype.hxx>
32 #include <unotools/pathoptions.hxx>
33 #include <cppuhelper/supportsservice.hxx>
34 #include <o3tl/string_view.hxx>
36 #if HAVE_FEATURE_XMLHELP
37 #include <helpcompiler/compilehelp.hxx>
38 #include <helpcompiler/HelpIndexer.hxx>
40 #include <com/sun/star/deployment/DeploymentException.hpp>
41 #include <com/sun/star/deployment/ExtensionRemovedException.hpp>
42 #include <com/sun/star/ucb/SimpleFileAccess.hpp>
43 #include <com/sun/star/util/XMacroExpander.hpp>
45 #include <string_view>
47 using namespace ::dp_misc
;
48 using namespace ::com::sun::star
;
49 using namespace ::com::sun::star::uno
;
50 using namespace ::com::sun::star::ucb
;
52 namespace dp_registry::backend::help
{
56 class BackendImpl
: public ::dp_registry::backend::PackageRegistryBackend
58 class PackageImpl
: public ::dp_registry::backend::Package
60 BackendImpl
* getMyBackend() const;
63 virtual beans::Optional
< beans::Ambiguous
<sal_Bool
> > isRegistered_(
64 ::osl::ResettableMutexGuard
& guard
,
65 ::rtl::Reference
<AbortChannel
> const & abortChannel
,
66 Reference
<XCommandEnvironment
> const & xCmdEnv
) override
;
67 virtual void processPackage_(
68 ::osl::ResettableMutexGuard
& guard
,
71 ::rtl::Reference
<AbortChannel
> const & abortChannel
,
72 Reference
<XCommandEnvironment
> const & xCmdEnv
) override
;
77 ::rtl::Reference
<PackageRegistryBackend
> const & myBackend
,
78 OUString
const & url
, OUString
const & name
,
79 Reference
<deployment::XPackageTypeInfo
> const & xPackageType
,
80 bool bRemoved
, OUString
const & identifier
);
82 bool extensionContainsCompiledHelp();
85 virtual css::beans::Optional
< OUString
> SAL_CALL
getRegistrationDataURL() override
;
87 friend class PackageImpl
;
89 // PackageRegistryBackend
90 virtual Reference
<deployment::XPackage
> bindPackage_(
91 OUString
const & url
, OUString
const & mediaType
,
92 bool bRemoved
, OUString
const & identifier
,
93 Reference
<XCommandEnvironment
> const & xCmdEnv
) override
;
95 void implProcessHelp( PackageImpl
* package
, bool doRegisterPackage
,
96 Reference
<ucb::XCommandEnvironment
> const & xCmdEnv
);
97 void implCollectXhpFiles( const OUString
& aDir
,
98 std::vector
< OUString
>& o_rXhpFileVector
);
100 ::std::optional
<HelpBackendDb::Data
> readDataFromDb(std::u16string_view url
);
101 bool hasActiveEntry(std::u16string_view url
);
102 bool activateEntry(std::u16string_view url
);
104 Reference
< ucb::XSimpleFileAccess3
> const & getFileAccess();
105 Reference
< ucb::XSimpleFileAccess3
> m_xSFA
;
107 const Reference
<deployment::XPackageTypeInfo
> m_xHelpTypeInfo
;
108 Sequence
< Reference
<deployment::XPackageTypeInfo
> > m_typeInfos
;
109 std::unique_ptr
<HelpBackendDb
> m_backendDb
;
112 BackendImpl( Sequence
<Any
> const & args
,
113 Reference
<XComponentContext
> const & xComponentContext
);
116 virtual OUString SAL_CALL
getImplementationName() override
;
117 virtual sal_Bool SAL_CALL
supportsService( const OUString
& ServiceName
) override
;
118 virtual css::uno::Sequence
< OUString
> SAL_CALL
getSupportedServiceNames() override
;
121 virtual Sequence
< Reference
<deployment::XPackageTypeInfo
> > SAL_CALL
122 getSupportedPackageTypes() override
;
123 virtual void SAL_CALL
packageRemoved(OUString
const & url
, OUString
const & mediaType
) override
;
128 BackendImpl::BackendImpl(
129 Sequence
<Any
> const & args
,
130 Reference
<XComponentContext
> const & xComponentContext
)
131 : PackageRegistryBackend( args
, xComponentContext
),
132 m_xHelpTypeInfo( new Package::TypeInfo("application/vnd.sun.star.help",
134 DpResId(RID_STR_HELP
)
136 m_typeInfos
{ m_xHelpTypeInfo
}
141 OUString dbFile
= makeURL(getCachePath(), "backenddb.xml");
143 new HelpBackendDb(getComponentContext(), dbFile
));
145 //clean up data folders which are no longer used.
146 //This must not be done in the same process where the help files
147 //are still registers. Only after revoking and restarting OOo the folders
148 //can be removed. This works now, because the extension manager is a singleton
149 //and the backends are only create once per process.
150 std::vector
<OUString
> folders
= m_backendDb
->getAllDataUrls();
151 deleteUnusedFolders(folders
);
155 OUString
BackendImpl::getImplementationName()
157 return "com.sun.star.comp.deployment.help.PackageRegistryBackend";
160 sal_Bool
BackendImpl::supportsService( const OUString
& ServiceName
)
162 return cppu::supportsService(this, ServiceName
);
165 css::uno::Sequence
< OUString
> BackendImpl::getSupportedServiceNames()
167 return { BACKEND_SERVICE_NAME
};
172 Sequence
< Reference
<deployment::XPackageTypeInfo
> >
173 BackendImpl::getSupportedPackageTypes()
178 void BackendImpl::packageRemoved(OUString
const & url
, OUString
const & /*mediaType*/)
181 m_backendDb
->removeEntry(url
);
184 // PackageRegistryBackend
186 Reference
<deployment::XPackage
> BackendImpl::bindPackage_(
187 OUString
const & url
, OUString
const & mediaType_
,
188 bool bRemoved
, OUString
const & identifier
,
189 Reference
<XCommandEnvironment
> const & xCmdEnv
)
191 // we don't support auto detection:
192 if (mediaType_
.isEmpty())
193 throw lang::IllegalArgumentException(
194 StrCannotDetectMediaType() + url
,
195 static_cast<OWeakObject
*>(this), static_cast<sal_Int16
>(-1) );
197 OUString type
, subType
;
198 INetContentTypeParameterList params
;
199 if (INetContentTypes::parse( mediaType_
, type
, subType
, ¶ms
))
201 if (type
.equalsIgnoreAsciiCase("application"))
206 ::ucbhelper::Content
ucbContent(
207 url
, xCmdEnv
, getComponentContext() );
208 name
= StrTitle::getTitle( ucbContent
);
211 if (subType
.equalsIgnoreAsciiCase( "vnd.sun.star.help"))
213 return new PackageImpl(
214 this, url
, name
, m_xHelpTypeInfo
, bRemoved
,
219 throw lang::IllegalArgumentException(
220 StrUnsupportedMediaType() + mediaType_
,
221 static_cast<OWeakObject
*>(this),
222 static_cast<sal_Int16
>(-1) );
225 ::std::optional
<HelpBackendDb::Data
> BackendImpl::readDataFromDb(
226 std::u16string_view url
)
228 ::std::optional
<HelpBackendDb::Data
> data
;
230 data
= m_backendDb
->getEntry(url
);
234 bool BackendImpl::hasActiveEntry(std::u16string_view url
)
237 return m_backendDb
->hasActiveEntry(url
);
241 bool BackendImpl::activateEntry(std::u16string_view url
)
244 return m_backendDb
->activateEntry(url
);
249 BackendImpl::PackageImpl::PackageImpl(
250 ::rtl::Reference
<PackageRegistryBackend
> const & myBackend
,
251 OUString
const & url
, OUString
const & name
,
252 Reference
<deployment::XPackageTypeInfo
> const & xPackageType
,
253 bool bRemoved
, OUString
const & identifier
)
254 : Package( myBackend
, url
, name
, name
, xPackageType
, bRemoved
,
260 BackendImpl
* BackendImpl::PackageImpl::getMyBackend() const
262 BackendImpl
* pBackend
= static_cast<BackendImpl
*>(m_myBackend
.get());
263 if (nullptr == pBackend
)
265 //May throw a DisposedException
267 //We should never get here...
268 throw RuntimeException("Failed to get the BackendImpl",
269 static_cast<OWeakObject
*>(const_cast<PackageImpl
*>(this)));
274 bool BackendImpl::PackageImpl::extensionContainsCompiledHelp()
276 bool bCompiled
= true;
277 OUString aExpandedHelpURL
= dp_misc::expandUnoRcUrl(getURL());
279 ::osl::Directory
helpFolder(aExpandedHelpURL
);
280 if ( helpFolder
.open() == ::osl::File::E_None
)
282 //iterate over the contents of the help folder
283 //We assume that all folders within the help folder contain language specific
284 //help files. If just one of them does not contain compiled help then this
285 //function returns false.
286 ::osl::DirectoryItem item
;
287 ::osl::File::RC errorNext
= ::osl::File::E_None
;
288 while ((errorNext
= helpFolder
.getNextItem(item
)) == ::osl::File::E_None
)
290 //No find the language folders
291 ::osl::FileStatus
stat(osl_FileStatus_Mask_Type
| osl_FileStatus_Mask_FileName
|osl_FileStatus_Mask_FileURL
);
292 if (item
.getFileStatus(stat
) == ::osl::File::E_None
)
294 if (stat
.getFileType() != ::osl::FileStatus::Directory
)
297 //look if there is the folder help.idxl in the language folder
298 OUString
compUrl(stat
.getFileURL() + "/help.idxl");
299 ::osl::Directory
compiledFolder(compUrl
);
300 if (compiledFolder
.open() != ::osl::File::E_None
)
314 if (errorNext
!= ::osl::File::E_NOENT
315 && errorNext
!= ::osl::File::E_None
)
326 beans::Optional
< beans::Ambiguous
<sal_Bool
> >
327 BackendImpl::PackageImpl::isRegistered_(
328 ::osl::ResettableMutexGuard
&,
329 ::rtl::Reference
<AbortChannel
> const &,
330 Reference
<XCommandEnvironment
> const & )
332 BackendImpl
* that
= getMyBackend();
335 if (that
->hasActiveEntry(getURL()))
338 return beans::Optional
< beans::Ambiguous
<sal_Bool
> >( true, beans::Ambiguous
<sal_Bool
>( bReg
, false ) );
342 void BackendImpl::PackageImpl::processPackage_(
343 ::osl::ResettableMutexGuard
&,
344 bool doRegisterPackage
,
346 ::rtl::Reference
<AbortChannel
> const &,
347 Reference
<XCommandEnvironment
> const & xCmdEnv
)
349 BackendImpl
* that
= getMyBackend();
350 that
->implProcessHelp( this, doRegisterPackage
, xCmdEnv
);
353 beans::Optional
< OUString
> BackendImpl::PackageImpl::getRegistrationDataURL()
356 throw deployment::ExtensionRemovedException();
358 ::std::optional
<HelpBackendDb::Data
> data
=
359 getMyBackend()->readDataFromDb(getURL());
361 if (data
&& getMyBackend()->hasActiveEntry(getURL()))
362 return beans::Optional
<OUString
>(true, data
->dataUrl
);
364 return beans::Optional
<OUString
>(true, OUString());
367 void BackendImpl::implProcessHelp(
368 PackageImpl
* package
, bool doRegisterPackage
,
369 Reference
<ucb::XCommandEnvironment
> const & xCmdEnv
)
371 Reference
< deployment::XPackage
> xPackage(package
);
372 OSL_ASSERT(xPackage
.is());
373 if (doRegisterPackage
)
375 //revive already processed help if possible
376 if ( !activateEntry(xPackage
->getURL()))
378 HelpBackendDb::Data data
;
379 data
.dataUrl
= xPackage
->getURL();
380 if (!package
->extensionContainsCompiledHelp())
382 #if HAVE_FEATURE_XMLHELP
383 const OUString sHelpFolder
= createFolder(xCmdEnv
);
384 data
.dataUrl
= sHelpFolder
;
386 Reference
< ucb::XSimpleFileAccess3
> xSFA
= getFileAccess();
387 OUString aHelpURL
= xPackage
->getURL();
388 OUString aExpandedHelpURL
= dp_misc::expandUnoRcUrl( aHelpURL
);
389 if( !xSFA
->isFolder( aExpandedHelpURL
) )
391 OUString aErrStr
= DpResId( RID_STR_HELPPROCESSING_GENERAL_ERROR
) +
393 OWeakObject
* oWeakThis
= this;
394 throw deployment::DeploymentException( OUString(), oWeakThis
,
395 Any( uno::Exception( aErrStr
, oWeakThis
) ) );
399 Sequence
< OUString
> aLanguageFolderSeq
= xSFA
->getFolderContents( aExpandedHelpURL
, true );
400 sal_Int32 nLangCount
= aLanguageFolderSeq
.getLength();
401 const OUString
* pSeq
= aLanguageFolderSeq
.getConstArray();
402 for( sal_Int32 iLang
= 0 ; iLang
< nLangCount
; ++iLang
)
404 OUString aLangURL
= pSeq
[iLang
];
405 if( xSFA
->isFolder( aLangURL
) )
407 std::vector
< OUString
> aXhpFileVector
;
409 // calculate jar file URL
410 sal_Int32 indexStartSegment
= aLangURL
.lastIndexOf('/');
412 OUString
langFolderURLSegment(
414 indexStartSegment
+ 1, aLangURL
.getLength() - indexStartSegment
- 1));
416 //create the folder in the "temporary folder"
417 ::ucbhelper::Content langFolderContent
;
418 const OUString langFolderDest
= makeURL(sHelpFolder
, langFolderURLSegment
);
419 const OUString langFolderDestExpanded
= ::dp_misc::expandUnoRcUrl(langFolderDest
);
420 ::dp_misc::create_folder(
422 langFolderDest
, xCmdEnv
);
424 static const OUStringLiteral
aHelpStr(u
"help");
427 makeURL(sHelpFolder
, langFolderURLSegment
+ "/" + aHelpStr
+ ".jar"));
428 aJarFile
= ::dp_misc::expandUnoRcUrl(aJarFile
);
430 OUString aEncodedJarFilePath
= rtl::Uri::encode(
431 aJarFile
, rtl_UriCharClassPchar
,
432 rtl_UriEncodeIgnoreEscapes
,
433 RTL_TEXTENCODING_UTF8
);
434 OUString aDestBasePath
= "vnd.sun.star.zip://" +
435 aEncodedJarFilePath
+ "/" ;
437 sal_Int32 nLenLangFolderURL
= aLangURL
.getLength() + 1;
439 Sequence
< OUString
> aSubLangSeq
= xSFA
->getFolderContents( aLangURL
, true );
440 sal_Int32 nSubLangCount
= aSubLangSeq
.getLength();
441 const OUString
* pSubLangSeq
= aSubLangSeq
.getConstArray();
442 for( sal_Int32 iSubLang
= 0 ; iSubLang
< nSubLangCount
; ++iSubLang
)
444 OUString aSubFolderURL
= pSubLangSeq
[iSubLang
];
445 if( !xSFA
->isFolder( aSubFolderURL
) )
448 implCollectXhpFiles( aSubFolderURL
, aXhpFileVector
);
450 // Copy to package (later: move?)
451 std::u16string_view aPureFolderName
= aSubFolderURL
.subView( nLenLangFolderURL
);
452 OUString aDestPath
= aDestBasePath
+ aPureFolderName
;
453 xSFA
->copy( aSubFolderURL
, aDestPath
);
457 sal_Int32 nXhpFileCount
= aXhpFileVector
.size();
458 std::unique_ptr
<OUString
[]> pXhpFiles(new OUString
[nXhpFileCount
]);
459 for( sal_Int32 iXhp
= 0 ; iXhp
< nXhpFileCount
; ++iXhp
)
461 OUString aXhpFile
= aXhpFileVector
[iXhp
];
462 OUString aXhpRelFile
= aXhpFile
.copy( nLenLangFolderURL
);
463 pXhpFiles
[iXhp
] = aXhpRelFile
;
466 OUString
aOfficeHelpPath( SvtPathOptions().GetHelpPath() );
467 OUString aOfficeHelpPathFileURL
;
468 ::osl::File::getFileURLFromSystemPath( aOfficeHelpPath
, aOfficeHelpPathFileURL
);
470 HelpProcessingErrorInfo aErrorInfo
;
471 bool bSuccess
= compileExtensionHelp(
472 aOfficeHelpPathFileURL
, aHelpStr
, aLangURL
,
473 nXhpFileCount
, pXhpFiles
.get(),
474 langFolderDestExpanded
, aErrorInfo
);
481 sal_Int32 nLastSlash
= aLangURL
.lastIndexOf( '/' );
482 if( nLastSlash
!= -1 )
483 aLang
= aLangURL
.copy( nLastSlash
+ 1 );
487 HelpIndexer
aIndexer(aLang
, "help", langFolderDestExpanded
, langFolderDestExpanded
);
488 aIndexer
.indexDocuments();
493 TranslateId pErrStrId
;
494 switch( aErrorInfo
.m_eErrorClass
)
496 case HelpProcessingErrorClass::General
: pErrStrId
= RID_STR_HELPPROCESSING_GENERAL_ERROR
; break;
497 case HelpProcessingErrorClass::XmlParsing
: pErrStrId
= RID_STR_HELPPROCESSING_XMLPARSING_ERROR
; break;
504 aErrStr
= DpResId(pErrStrId
);
507 OUString
aErrMsg( aErrorInfo
.m_aErrorMsg
);
508 sal_Unicode
const nCR
= 13, nLF
= 10;
509 sal_Int32 nSearchCR
= aErrMsg
.indexOf( nCR
);
510 sal_Int32 nSearchLF
= aErrMsg
.indexOf( nLF
);
511 if( nSearchCR
!= -1 || nSearchLF
!= -1 )
514 if( nSearchCR
== -1 )
516 else if( nSearchLF
== -1 )
519 nCopy
= ( nSearchCR
< nSearchLF
) ? nSearchCR
: nSearchLF
;
521 aErrMsg
= aErrMsg
.copy( 0, nCopy
);
524 if (pErrStrId
!= RID_STR_HELPPROCESSING_XMLPARSING_ERROR
&& !aErrorInfo
.m_aXMLParsingFile
.isEmpty() )
528 OUString aDecodedFile
= rtl::Uri::decode( aErrorInfo
.m_aXMLParsingFile
,
529 rtl_UriDecodeWithCharset
, RTL_TEXTENCODING_UTF8
);
530 aErrStr
+= aDecodedFile
;
531 if( aErrorInfo
.m_nXMLParsingLine
!= -1 )
533 aErrStr
+= ", line " +
534 OUString::number( aErrorInfo
.m_nXMLParsingLine
);
539 OWeakObject
* oWeakThis
= this;
540 throw deployment::DeploymentException( OUString(), oWeakThis
,
541 Any( uno::Exception( aErrStr
, oWeakThis
) ) );
549 // Writing the data entry replaces writing the flag file. If we got to this
550 // point the registration was successful.
552 m_backendDb
->addEntry(xPackage
->getURL(), data
);
554 } //if (doRegisterPackage)
558 m_backendDb
->revokeEntry(xPackage
->getURL());
562 void BackendImpl::implCollectXhpFiles( const OUString
& aDir
,
563 std::vector
< OUString
>& o_rXhpFileVector
)
565 Reference
< ucb::XSimpleFileAccess3
> xSFA
= getFileAccess();
567 // Scan xhp files recursively
568 Sequence
< OUString
> aSeq
= xSFA
->getFolderContents( aDir
, true );
569 sal_Int32 nCount
= aSeq
.getLength();
570 const OUString
* pSeq
= aSeq
.getConstArray();
571 for( sal_Int32 i
= 0 ; i
< nCount
; ++i
)
573 OUString aURL
= pSeq
[i
];
574 if( xSFA
->isFolder( aURL
) )
576 implCollectXhpFiles( aURL
, o_rXhpFileVector
);
580 sal_Int32 nLastDot
= aURL
.lastIndexOf( '.' );
583 std::u16string_view aExt
= aURL
.subView( nLastDot
+ 1 );
584 if( o3tl::equalsIgnoreAsciiCase( aExt
, u
"xhp" ) )
585 o_rXhpFileVector
.push_back( aURL
);
591 Reference
< ucb::XSimpleFileAccess3
> const & BackendImpl::getFileAccess()
595 Reference
<XComponentContext
> const & xContext
= getComponentContext();
598 m_xSFA
= ucb::SimpleFileAccess::create(xContext
);
602 throw RuntimeException(
603 "dp_registry::backend::help::BackendImpl::getFileAccess(), "
604 "could not instantiate SimpleFileAccess." );
612 } // namespace dp_registry
614 extern "C" SAL_DLLPUBLIC_EXPORT
css::uno::XInterface
*
615 com_sun_star_comp_deployment_help_PackageRegistryBackend_get_implementation(
616 css::uno::XComponentContext
* context
, css::uno::Sequence
<css::uno::Any
> const& args
)
618 return cppu::acquire(new dp_registry::backend::help::BackendImpl(args
, context
));
621 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */