1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2 /*
3  * This file is part of the LibreOffice project.
4  *
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/.
8  *
9  * This file incorporates work covered by the following license notice:
10  *
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 .
18  */
19 
20 #include <memory>
21 #include <config_feature_desktop.h>
22 
23 #include <strings.hrc>
24 #include <dp_backend.h>
25 #include "dp_helpbackenddb.hxx"
26 #include <dp_ucb.h>
27 #include <rtl/uri.hxx>
28 #include <osl/file.hxx>
29 #include <ucbhelper/content.hxx>
30 #include <svl/inettype.hxx>
31 #include <unotools/pathoptions.hxx>
32 #include <cppuhelper/supportsservice.hxx>
33 
34 #if HAVE_FEATURE_DESKTOP
35 #include <helpcompiler/compilehelp.hxx>
36 #include <helpcompiler/HelpIndexer.hxx>
37 #endif
38 #include <com/sun/star/deployment/DeploymentException.hpp>
39 #include <com/sun/star/deployment/ExtensionRemovedException.hpp>
40 #include <com/sun/star/ucb/SimpleFileAccess.hpp>
41 #include <com/sun/star/util/XMacroExpander.hpp>
42 #include <optional>
43 #include <string_view>
44 
45 using namespace ::dp_misc;
46 using namespace ::com::sun::star;
47 using namespace ::com::sun::star::uno;
48 using namespace ::com::sun::star::ucb;
49 
50 namespace dp_registry::backend::help {
51 namespace {
52 
53 
54 class BackendImpl : public ::dp_registry::backend::PackageRegistryBackend
55 {
56     class PackageImpl : public ::dp_registry::backend::Package
57     {
58         BackendImpl * getMyBackend() const;
59 
60         // Package
61         virtual beans::Optional< beans::Ambiguous<sal_Bool> > isRegistered_(
62             ::osl::ResettableMutexGuard & guard,
63             ::rtl::Reference<AbortChannel> const & abortChannel,
64             Reference<XCommandEnvironment> const & xCmdEnv ) override;
65         virtual void processPackage_(
66             ::osl::ResettableMutexGuard & guard,
67             bool registerPackage,
68             bool startup,
69             ::rtl::Reference<AbortChannel> const & abortChannel,
70             Reference<XCommandEnvironment> const & xCmdEnv ) override;
71 
72 
73     public:
74         PackageImpl(
75             ::rtl::Reference<PackageRegistryBackend> const & myBackend,
76             OUString const & url, OUString const & name,
77             Reference<deployment::XPackageTypeInfo> const & xPackageType,
78             bool bRemoved, OUString const & identifier);
79 
80         bool extensionContainsCompiledHelp();
81 
82         //XPackage
83         virtual css::beans::Optional< OUString > SAL_CALL getRegistrationDataURL() override;
84     };
85     friend class PackageImpl;
86 
87     // PackageRegistryBackend
88     virtual Reference<deployment::XPackage> bindPackage_(
89         OUString const & url, OUString const & mediaType,
90         bool bRemoved, OUString const & identifier,
91         Reference<XCommandEnvironment> const & xCmdEnv ) override;
92 
93     void implProcessHelp( PackageImpl * package, bool doRegisterPackage,
94                           Reference<ucb::XCommandEnvironment> const & xCmdEnv);
95     void implCollectXhpFiles( const OUString& aDir,
96         std::vector< OUString >& o_rXhpFileVector );
97 
98     ::std::optional<HelpBackendDb::Data> readDataFromDb(std::u16string_view url);
99     bool hasActiveEntry(std::u16string_view url);
100     bool activateEntry(std::u16string_view url);
101 
102     Reference< ucb::XSimpleFileAccess3 > const & getFileAccess();
103     Reference< ucb::XSimpleFileAccess3 > m_xSFA;
104 
105     const Reference<deployment::XPackageTypeInfo> m_xHelpTypeInfo;
106     Sequence< Reference<deployment::XPackageTypeInfo> > m_typeInfos;
107     std::unique_ptr<HelpBackendDb> m_backendDb;
108 
109 public:
110     BackendImpl( Sequence<Any> const & args,
111                  Reference<XComponentContext> const & xComponentContext );
112 
113     // XServiceInfo
114     virtual OUString SAL_CALL getImplementationName() override;
115     virtual sal_Bool SAL_CALL supportsService( const OUString& ServiceName ) override;
116     virtual css::uno::Sequence< OUString > SAL_CALL getSupportedServiceNames() override;
117 
118     // XPackageRegistry
119     virtual Sequence< Reference<deployment::XPackageTypeInfo> > SAL_CALL
120         getSupportedPackageTypes() override;
121     virtual void SAL_CALL packageRemoved(OUString const & url, OUString const & mediaType) override;
122 
123 };
124 
125 
BackendImpl(Sequence<Any> const & args,Reference<XComponentContext> const & xComponentContext)126 BackendImpl::BackendImpl(
127     Sequence<Any> const & args,
128     Reference<XComponentContext> const & xComponentContext )
129     : PackageRegistryBackend( args, xComponentContext ),
130       m_xHelpTypeInfo( new Package::TypeInfo("application/vnd.sun.star.help",
131                                OUString(),
132                                DpResId(RID_STR_HELP)
133                                ) ),
134       m_typeInfos( 1 )
135 {
136     m_typeInfos[ 0 ] = m_xHelpTypeInfo;
137     if (transientMode())
138         return;
139 
140     OUString dbFile = makeURL(getCachePath(), "backenddb.xml");
141     m_backendDb.reset(
142         new HelpBackendDb(getComponentContext(), dbFile));
143 
144     //clean up data folders which are no longer used.
145     //This must not be done in the same process where the help files
146     //are still registers. Only after revoking and restarting OOo the folders
147     //can be removed. This works now, because the extension manager is a singleton
148     //and the backends are only create once per process.
149     std::vector<OUString> folders = m_backendDb->getAllDataUrls();
150     deleteUnusedFolders(folders);
151 }
152 
153 // XServiceInfo
getImplementationName()154 OUString BackendImpl::getImplementationName()
155 {
156     return "com.sun.star.comp.deployment.help.PackageRegistryBackend";
157 }
158 
supportsService(const OUString & ServiceName)159 sal_Bool BackendImpl::supportsService( const OUString& ServiceName )
160 {
161     return cppu::supportsService(this, ServiceName);
162 }
163 
getSupportedServiceNames()164 css::uno::Sequence< OUString > BackendImpl::getSupportedServiceNames()
165 {
166     return { BACKEND_SERVICE_NAME };
167 }
168 
169 // XPackageRegistry
170 
171 Sequence< Reference<deployment::XPackageTypeInfo> >
getSupportedPackageTypes()172 BackendImpl::getSupportedPackageTypes()
173 {
174     return m_typeInfos;
175 }
176 
packageRemoved(OUString const & url,OUString const &)177 void BackendImpl::packageRemoved(OUString const & url, OUString const & /*mediaType*/)
178 {
179     if (m_backendDb)
180         m_backendDb->removeEntry(url);
181 }
182 
183 // PackageRegistryBackend
184 
bindPackage_(OUString const & url,OUString const & mediaType_,bool bRemoved,OUString const & identifier,Reference<XCommandEnvironment> const & xCmdEnv)185 Reference<deployment::XPackage> BackendImpl::bindPackage_(
186     OUString const & url, OUString const & mediaType_,
187     bool bRemoved, OUString const & identifier,
188     Reference<XCommandEnvironment> const & xCmdEnv )
189 {
190     // we don't support auto detection:
191     if (mediaType_.isEmpty())
192         throw lang::IllegalArgumentException(
193             StrCannotDetectMediaType() + url,
194             static_cast<OWeakObject *>(this), static_cast<sal_Int16>(-1) );
195 
196     OUString type, subType;
197     INetContentTypeParameterList params;
198     if (INetContentTypes::parse( mediaType_, type, subType, &params ))
199     {
200         if (type.equalsIgnoreAsciiCase("application"))
201         {
202             OUString name;
203             if (!bRemoved)
204             {
205                 ::ucbhelper::Content ucbContent(
206                     url, xCmdEnv, getComponentContext() );
207                 name = StrTitle::getTitle( ucbContent );
208             }
209 
210             if (subType.equalsIgnoreAsciiCase( "vnd.sun.star.help"))
211             {
212                 return new PackageImpl(
213                     this, url, name, m_xHelpTypeInfo, bRemoved,
214                     identifier);
215             }
216         }
217     }
218     throw lang::IllegalArgumentException(
219         StrUnsupportedMediaType() + mediaType_,
220         static_cast<OWeakObject *>(this),
221         static_cast<sal_Int16>(-1) );
222 }
223 
readDataFromDb(std::u16string_view url)224 ::std::optional<HelpBackendDb::Data> BackendImpl::readDataFromDb(
225     std::u16string_view url)
226 {
227     ::std::optional<HelpBackendDb::Data> data;
228     if (m_backendDb)
229         data = m_backendDb->getEntry(url);
230     return data;
231 }
232 
hasActiveEntry(std::u16string_view url)233 bool BackendImpl::hasActiveEntry(std::u16string_view url)
234 {
235     if (m_backendDb)
236         return m_backendDb->hasActiveEntry(url);
237     return false;
238 }
239 
activateEntry(std::u16string_view url)240 bool BackendImpl::activateEntry(std::u16string_view url)
241 {
242     if (m_backendDb)
243         return m_backendDb->activateEntry(url);
244     return false;
245 }
246 
247 
PackageImpl(::rtl::Reference<PackageRegistryBackend> const & myBackend,OUString const & url,OUString const & name,Reference<deployment::XPackageTypeInfo> const & xPackageType,bool bRemoved,OUString const & identifier)248 BackendImpl::PackageImpl::PackageImpl(
249     ::rtl::Reference<PackageRegistryBackend> const & myBackend,
250     OUString const & url, OUString const & name,
251     Reference<deployment::XPackageTypeInfo> const & xPackageType,
252     bool bRemoved, OUString const & identifier)
253     : Package( myBackend, url, name, name, xPackageType, bRemoved,
254                identifier)
255 {
256 }
257 
258 // Package
getMyBackend() const259 BackendImpl * BackendImpl::PackageImpl::getMyBackend() const
260 {
261     BackendImpl * pBackend = static_cast<BackendImpl *>(m_myBackend.get());
262     if (nullptr == pBackend)
263     {
264         //May throw a DisposedException
265         check();
266         //We should never get here...
267         throw RuntimeException("Failed to get the BackendImpl",
268             static_cast<OWeakObject*>(const_cast<PackageImpl *>(this)));
269     }
270     return pBackend;
271 }
272 
extensionContainsCompiledHelp()273 bool BackendImpl::PackageImpl::extensionContainsCompiledHelp()
274 {
275     bool bCompiled = true;
276     OUString aExpandedHelpURL = dp_misc::expandUnoRcUrl(getURL());
277 
278     ::osl::Directory helpFolder(aExpandedHelpURL);
279     if ( helpFolder.open() == ::osl::File::E_None)
280     {
281         //iterate over the contents of the help folder
282         //We assume that all folders within the help folder contain language specific
283         //help files. If just one of them does not contain compiled help then this
284         //function returns false.
285         ::osl::DirectoryItem item;
286         ::osl::File::RC errorNext = ::osl::File::E_None;
287         while ((errorNext = helpFolder.getNextItem(item)) == ::osl::File::E_None)
288         {
289             //No find the language folders
290             ::osl::FileStatus stat(osl_FileStatus_Mask_Type | osl_FileStatus_Mask_FileName |osl_FileStatus_Mask_FileURL);
291             if (item.getFileStatus(stat) == ::osl::File::E_None)
292             {
293                 if (stat.getFileType() != ::osl::FileStatus::Directory)
294                     continue;
295 
296                 //look if there is the folder help.idxl in the language folder
297                 OUString compUrl(stat.getFileURL() + "/help.idxl");
298                 ::osl::Directory compiledFolder(compUrl);
299                 if (compiledFolder.open() != ::osl::File::E_None)
300                 {
301                     bCompiled = false;
302                     break;
303                 }
304             }
305             else
306             {
307                 //Error
308                 OSL_ASSERT(false);
309                 bCompiled = false;
310                 break;
311             }
312         }
313         if (errorNext != ::osl::File::E_NOENT
314             && errorNext != ::osl::File::E_None)
315         {
316             //Error
317             OSL_ASSERT(false);
318             bCompiled = false;
319         }
320     }
321     return bCompiled;
322 }
323 
324 
325 beans::Optional< beans::Ambiguous<sal_Bool> >
isRegistered_(::osl::ResettableMutexGuard &,::rtl::Reference<AbortChannel> const &,Reference<XCommandEnvironment> const &)326 BackendImpl::PackageImpl::isRegistered_(
327     ::osl::ResettableMutexGuard &,
328     ::rtl::Reference<AbortChannel> const &,
329     Reference<XCommandEnvironment> const & )
330 {
331     BackendImpl * that = getMyBackend();
332 
333     bool bReg = false;
334     if (that->hasActiveEntry(getURL()))
335         bReg = true;
336 
337     return beans::Optional< beans::Ambiguous<sal_Bool> >( true, beans::Ambiguous<sal_Bool>( bReg, false ) );
338 }
339 
340 
processPackage_(::osl::ResettableMutexGuard &,bool doRegisterPackage,bool,::rtl::Reference<AbortChannel> const &,Reference<XCommandEnvironment> const & xCmdEnv)341 void BackendImpl::PackageImpl::processPackage_(
342     ::osl::ResettableMutexGuard &,
343     bool doRegisterPackage,
344     bool /* startup */,
345     ::rtl::Reference<AbortChannel> const &,
346     Reference<XCommandEnvironment> const & xCmdEnv )
347 {
348     BackendImpl* that = getMyBackend();
349     that->implProcessHelp( this, doRegisterPackage, xCmdEnv);
350 }
351 
getRegistrationDataURL()352 beans::Optional< OUString > BackendImpl::PackageImpl::getRegistrationDataURL()
353 {
354     if (m_bRemoved)
355         throw deployment::ExtensionRemovedException();
356 
357     ::std::optional<HelpBackendDb::Data> data =
358           getMyBackend()->readDataFromDb(getURL());
359 
360     if (data && getMyBackend()->hasActiveEntry(getURL()))
361         return beans::Optional<OUString>(true, data->dataUrl);
362 
363     return beans::Optional<OUString>(true, OUString());
364 }
365 
implProcessHelp(PackageImpl * package,bool doRegisterPackage,Reference<ucb::XCommandEnvironment> const & xCmdEnv)366 void BackendImpl::implProcessHelp(
367     PackageImpl * package, bool doRegisterPackage,
368     Reference<ucb::XCommandEnvironment> const & xCmdEnv)
369 {
370     Reference< deployment::XPackage > xPackage(package);
371     OSL_ASSERT(xPackage.is());
372     if (doRegisterPackage)
373     {
374         //revive already processed help if possible
375         if ( !activateEntry(xPackage->getURL()))
376         {
377             HelpBackendDb::Data data;
378             data.dataUrl = xPackage->getURL();
379             if (!package->extensionContainsCompiledHelp())
380             {
381 #if HAVE_FEATURE_DESKTOP
382                 const OUString sHelpFolder = createFolder(xCmdEnv);
383                 data.dataUrl = sHelpFolder;
384 
385                 Reference< ucb::XSimpleFileAccess3 > xSFA = getFileAccess();
386                 OUString aHelpURL = xPackage->getURL();
387                 OUString aExpandedHelpURL = dp_misc::expandUnoRcUrl( aHelpURL );
388                 if( !xSFA->isFolder( aExpandedHelpURL ) )
389                 {
390                     OUString aErrStr = DpResId( RID_STR_HELPPROCESSING_GENERAL_ERROR ) +
391                         "No help folder";
392                     OWeakObject* oWeakThis = static_cast<OWeakObject *>(this);
393                     throw deployment::DeploymentException( OUString(), oWeakThis,
394                                                            makeAny( uno::Exception( aErrStr, oWeakThis ) ) );
395                 }
396 
397                 // Scan languages
398                 Sequence< OUString > aLanguageFolderSeq = xSFA->getFolderContents( aExpandedHelpURL, true );
399                 sal_Int32 nLangCount = aLanguageFolderSeq.getLength();
400                 const OUString* pSeq = aLanguageFolderSeq.getConstArray();
401                 for( sal_Int32 iLang = 0 ; iLang < nLangCount ; ++iLang )
402                 {
403                     OUString aLangURL = pSeq[iLang];
404                     if( xSFA->isFolder( aLangURL ) )
405                     {
406                         std::vector< OUString > aXhpFileVector;
407 
408                         // calculate jar file URL
409                         sal_Int32 indexStartSegment = aLangURL.lastIndexOf('/');
410                         // for example "/en"
411                         OUString langFolderURLSegment(
412                             aLangURL.copy(
413                                 indexStartSegment + 1, aLangURL.getLength() - indexStartSegment - 1));
414 
415                         //create the folder in the "temporary folder"
416                         ::ucbhelper::Content langFolderContent;
417                         const OUString langFolderDest = makeURL(sHelpFolder, langFolderURLSegment);
418                         const OUString langFolderDestExpanded = ::dp_misc::expandUnoRcUrl(langFolderDest);
419                         ::dp_misc::create_folder(
420                             &langFolderContent,
421                             langFolderDest, xCmdEnv);
422 
423                         static const OUStringLiteral aHelpStr(u"help");
424 
425                         OUString aJarFile(
426                             makeURL(sHelpFolder, langFolderURLSegment + "/" + aHelpStr + ".jar"));
427                         aJarFile = ::dp_misc::expandUnoRcUrl(aJarFile);
428 
429                         OUString aEncodedJarFilePath = rtl::Uri::encode(
430                             aJarFile, rtl_UriCharClassPchar,
431                             rtl_UriEncodeIgnoreEscapes,
432                             RTL_TEXTENCODING_UTF8 );
433                         OUString aDestBasePath = "vnd.sun.star.zip://" +
434                             aEncodedJarFilePath + "/" ;
435 
436                         sal_Int32 nLenLangFolderURL = aLangURL.getLength() + 1;
437 
438                         Sequence< OUString > aSubLangSeq = xSFA->getFolderContents( aLangURL, true );
439                         sal_Int32 nSubLangCount = aSubLangSeq.getLength();
440                         const OUString* pSubLangSeq = aSubLangSeq.getConstArray();
441                         for( sal_Int32 iSubLang = 0 ; iSubLang < nSubLangCount ; ++iSubLang )
442                         {
443                             OUString aSubFolderURL = pSubLangSeq[iSubLang];
444                             if( !xSFA->isFolder( aSubFolderURL ) )
445                                 continue;
446 
447                             implCollectXhpFiles( aSubFolderURL, aXhpFileVector );
448 
449                             // Copy to package (later: move?)
450                             OUString aDestPath = aDestBasePath;
451                             OUString aPureFolderName = aSubFolderURL.copy( nLenLangFolderURL );
452                             aDestPath += aPureFolderName;
453                             xSFA->copy( aSubFolderURL, aDestPath );
454                         }
455 
456                         // Call compiler
457                         sal_Int32 nXhpFileCount = aXhpFileVector.size();
458                         std::unique_ptr<OUString[]> pXhpFiles(new OUString[nXhpFileCount]);
459                         for( sal_Int32 iXhp = 0 ; iXhp < nXhpFileCount ; ++iXhp )
460                         {
461                             OUString aXhpFile = aXhpFileVector[iXhp];
462                             OUString aXhpRelFile = aXhpFile.copy( nLenLangFolderURL );
463                             pXhpFiles[iXhp] = aXhpRelFile;
464                         }
465 
466                         OUString aOfficeHelpPath( SvtPathOptions().GetHelpPath() );
467                         OUString aOfficeHelpPathFileURL;
468                         ::osl::File::getFileURLFromSystemPath( aOfficeHelpPath, aOfficeHelpPathFileURL );
469 
470                         HelpProcessingErrorInfo aErrorInfo;
471                         bool bSuccess = compileExtensionHelp(
472                             aOfficeHelpPathFileURL, aHelpStr, aLangURL,
473                             nXhpFileCount, pXhpFiles.get(),
474                             langFolderDestExpanded, aErrorInfo );
475 
476                         pXhpFiles.reset();
477 
478                         if( bSuccess )
479                         {
480                             OUString aLang;
481                             sal_Int32 nLastSlash = aLangURL.lastIndexOf( '/' );
482                             if( nLastSlash != -1 )
483                                 aLang = aLangURL.copy( nLastSlash + 1 );
484                             else
485                                 aLang = "en";
486 
487                             HelpIndexer aIndexer(aLang, "help", langFolderDestExpanded, langFolderDestExpanded);
488                             aIndexer.indexDocuments();
489                         }
490 
491                         if( !bSuccess )
492                         {
493                             const char* pErrStrId = nullptr;
494                             switch( aErrorInfo.m_eErrorClass )
495                             {
496                             case HelpProcessingErrorClass::General:      pErrStrId = RID_STR_HELPPROCESSING_GENERAL_ERROR; break;
497                             case HelpProcessingErrorClass::XmlParsing:   pErrStrId = RID_STR_HELPPROCESSING_XMLPARSING_ERROR; break;
498                             default: ;
499                             };
500 
501                             OUString aErrStr;
502                             if (pErrStrId)
503                             {
504                                 aErrStr = DpResId(pErrStrId);
505 
506                                 // Remove CR/LF
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 )
512                                 {
513                                     sal_Int32 nCopy;
514                                     if( nSearchCR == -1 )
515                                         nCopy = nSearchLF;
516                                     else if( nSearchLF == -1 )
517                                         nCopy = nSearchCR;
518                                     else
519                                         nCopy = ( nSearchCR < nSearchLF ) ? nSearchCR : nSearchLF;
520 
521                                     aErrMsg = aErrMsg.copy( 0, nCopy );
522                                 }
523                                 aErrStr += aErrMsg;
524                                 if (!strcmp(pErrStrId, RID_STR_HELPPROCESSING_XMLPARSING_ERROR) && !aErrorInfo.m_aXMLParsingFile.isEmpty() )
525                                 {
526                                     aErrStr += " in ";
527 
528                                     OUString aDecodedFile = rtl::Uri::decode( aErrorInfo.m_aXMLParsingFile,
529                                                                                    rtl_UriDecodeWithCharset, RTL_TEXTENCODING_UTF8 );
530                                     aErrStr += aDecodedFile;
531                                     if( aErrorInfo.m_nXMLParsingLine != -1 )
532                                     {
533                                         aErrStr += ", line " +
534                                             OUString::number( aErrorInfo.m_nXMLParsingLine );
535                                     }
536                                 }
537                             }
538 
539                             OWeakObject* oWeakThis = static_cast<OWeakObject *>(this);
540                             throw deployment::DeploymentException( OUString(), oWeakThis,
541                                                                    makeAny( uno::Exception( aErrStr, oWeakThis ) ) );
542                         }
543                     }
544                 }
545 #else
546                 (void) xCmdEnv;
547 #endif
548             }
549             // Writing the data entry replaces writing the flag file. If we got to this
550             // point the registration was successful.
551             if (m_backendDb)
552                 m_backendDb->addEntry(xPackage->getURL(), data);
553         }
554     } //if (doRegisterPackage)
555     else
556     {
557         if (m_backendDb)
558             m_backendDb->revokeEntry(xPackage->getURL());
559     }
560 }
561 
implCollectXhpFiles(const OUString & aDir,std::vector<OUString> & o_rXhpFileVector)562 void BackendImpl::implCollectXhpFiles( const OUString& aDir,
563     std::vector< OUString >& o_rXhpFileVector )
564 {
565     Reference< ucb::XSimpleFileAccess3 > xSFA = getFileAccess();
566 
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 )
572     {
573         OUString aURL = pSeq[i];
574         if( xSFA->isFolder( aURL ) )
575         {
576             implCollectXhpFiles( aURL, o_rXhpFileVector );
577         }
578         else
579         {
580             sal_Int32 nLastDot = aURL.lastIndexOf( '.' );
581             if( nLastDot != -1 )
582             {
583                 OUString aExt = aURL.copy( nLastDot + 1 );
584                 if( aExt.equalsIgnoreAsciiCase( "xhp" ) )
585                     o_rXhpFileVector.push_back( aURL );
586             }
587         }
588     }
589 }
590 
getFileAccess()591 Reference< ucb::XSimpleFileAccess3 > const & BackendImpl::getFileAccess()
592 {
593     if( !m_xSFA.is() )
594     {
595         Reference<XComponentContext> const & xContext = getComponentContext();
596         if( xContext.is() )
597         {
598             m_xSFA = ucb::SimpleFileAccess::create(xContext);
599         }
600         if( !m_xSFA.is() )
601         {
602             throw RuntimeException(
603                 "dp_registry::backend::help::BackendImpl::getFileAccess(), "
604                 "could not instantiate SimpleFileAccess."  );
605         }
606     }
607     return m_xSFA;
608 }
609 
610 } // anon namespace
611 
612 } // namespace dp_registry
613 
614 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)615 com_sun_star_comp_deployment_help_PackageRegistryBackend_get_implementation(
616     css::uno::XComponentContext* context, css::uno::Sequence<css::uno::Any> const& args)
617 {
618     return cppu::acquire(new dp_registry::backend::help::BackendImpl(args, context));
619 }
620 
621 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */
622