1 /*************************************************************************
2  *
3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4  *
5  * Copyright 2000, 2010 Oracle and/or its affiliates.
6  *
7  * OpenOffice.org - a multi-platform office productivity suite
8  *
9  * This file is part of OpenOffice.org.
10  *
11  * OpenOffice.org is free software: you can redistribute it and/or modify
12  * it under the terms of the GNU Lesser General Public License version 3
13  * only, as published by the Free Software Foundation.
14  *
15  * OpenOffice.org is distributed in the hope that it will be useful,
16  * but WITHOUT ANY WARRANTY; without even the implied warranty of
17  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
18  * GNU Lesser General Public License version 3 for more details
19  * (a copy is included in the LICENSE file that accompanied this code).
20  *
21  * You should have received a copy of the GNU Lesser General Public License
22  * version 3 along with OpenOffice.org.  If not, see
23  * <http://www.openoffice.org/license.html>
24  * for a copy of the LGPLv3 License.
25  *
26  ************************************************************************/
27 
28 // MARKER(update_precomp.py): autogen include statement, do not remove
29 #include "precompiled_desktop.hxx"
30 
31 #include "dp_registry.hrc"
32 #include "dp_misc.h"
33 #include "dp_resource.h"
34 #include "dp_interact.h"
35 #include "dp_ucb.h"
36 #include "osl/diagnose.h"
37 #include "rtl/ustrbuf.hxx"
38 #include "rtl/uri.hxx"
39 #include "cppuhelper/compbase2.hxx"
40 #include "cppuhelper/exc_hlp.hxx"
41 #include "comphelper/sequence.hxx"
42 #include "ucbhelper/content.hxx"
43 #include "com/sun/star/uno/DeploymentException.hpp"
44 #include "com/sun/star/lang/DisposedException.hpp"
45 #include "com/sun/star/lang/WrappedTargetRuntimeException.hpp"
46 #include "com/sun/star/lang/XServiceInfo.hpp"
47 #include "com/sun/star/lang/XSingleComponentFactory.hpp"
48 #include "com/sun/star/lang/XSingleServiceFactory.hpp"
49 #include "com/sun/star/util/XUpdatable.hpp"
50 #include "com/sun/star/container/XContentEnumerationAccess.hpp"
51 #include "com/sun/star/deployment/PackageRegistryBackend.hpp"
52 #include <hash_map>
53 #include <set>
54 #include <hash_set>
55 #include <memory>
56 
57 using namespace ::dp_misc;
58 using namespace ::com::sun::star;
59 using namespace ::com::sun::star::uno;
60 using namespace ::com::sun::star::ucb;
61 using ::rtl::OUString;
62 
63 
64 namespace dp_registry {
65 
66 namespace backend {
67 namespace bundle {
68 Reference<deployment::XPackageRegistry> create(
69     Reference<deployment::XPackageRegistry> const & xRootRegistry,
70     OUString const & context, OUString const & cachePath, bool readOnly,
71     Reference<XComponentContext> const & xComponentContext );
72 }
73 }
74 
75 namespace {
76 
77 typedef ::cppu::WeakComponentImplHelper2<
78     deployment::XPackageRegistry, util::XUpdatable > t_helper;
79 
80 //==============================================================================
81 class PackageRegistryImpl : private MutexHolder, public t_helper
82 {
83     struct ci_string_hash {
84         ::std::size_t operator () ( OUString const & str ) const {
85             return str.toAsciiLowerCase().hashCode();
86         }
87     };
88     struct ci_string_equals {
89         bool operator () ( OUString const & str1, OUString const & str2 ) const{
90             return str1.equalsIgnoreAsciiCase( str2 );
91         }
92     };
93     typedef ::std::hash_map<
94         OUString, Reference<deployment::XPackageRegistry>,
95         ci_string_hash, ci_string_equals > t_string2registry;
96     typedef ::std::hash_map<
97         OUString, OUString,
98         ci_string_hash, ci_string_equals > t_string2string;
99     typedef ::std::set<
100         Reference<deployment::XPackageRegistry> > t_registryset;
101 
102     t_string2registry m_mediaType2backend;
103     t_string2string m_filter2mediaType;
104     t_registryset m_ambiguousBackends;
105     t_registryset m_allBackends;
106     ::std::vector< Reference<deployment::XPackageTypeInfo> > m_typesInfos;
107 
108     void insertBackend(
109         Reference<deployment::XPackageRegistry> const & xBackend );
110 
111 protected:
112     inline void check();
113     virtual void SAL_CALL disposing();
114 
115     virtual ~PackageRegistryImpl();
116     PackageRegistryImpl() : t_helper( getMutex() ) {}
117 
118 
119 public:
120     static Reference<deployment::XPackageRegistry> create(
121         OUString const & context,
122         OUString const & cachePath, bool readOnly,
123         Reference<XComponentContext> const & xComponentContext );
124 
125     // XUpdatable
126     virtual void SAL_CALL update() throw (RuntimeException);
127 
128     // XPackageRegistry
129     virtual Reference<deployment::XPackage> SAL_CALL bindPackage(
130         OUString const & url, OUString const & mediaType, sal_Bool bRemoved,
131         OUString const & identifier, Reference<XCommandEnvironment> const & xCmdEnv )
132         throw (deployment::DeploymentException,
133                deployment::InvalidRemovedParameterException,
134                CommandFailedException,
135                lang::IllegalArgumentException, RuntimeException);
136     virtual Sequence< Reference<deployment::XPackageTypeInfo> > SAL_CALL
137     getSupportedPackageTypes() throw (RuntimeException);
138     virtual void SAL_CALL packageRemoved(OUString const & url, OUString const & mediaType)
139                 throw (deployment::DeploymentException,
140                 RuntimeException);
141 
142 };
143 
144 //______________________________________________________________________________
145 inline void PackageRegistryImpl::check()
146 {
147     ::osl::MutexGuard guard( getMutex() );
148     if (rBHelper.bInDispose || rBHelper.bDisposed) {
149         throw lang::DisposedException(
150             OUSTR("PackageRegistry instance has already been disposed!"),
151             static_cast<OWeakObject *>(this) );
152     }
153 }
154 
155 //______________________________________________________________________________
156 void PackageRegistryImpl::disposing()
157 {
158     // dispose all backends:
159     t_registryset::const_iterator iPos( m_allBackends.begin() );
160     t_registryset::const_iterator const iEnd( m_allBackends.end() );
161     for ( ; iPos != iEnd; ++iPos ) {
162         try_dispose( *iPos );
163     }
164     m_mediaType2backend = t_string2registry();
165     m_ambiguousBackends = t_registryset();
166     m_allBackends = t_registryset();
167 
168     t_helper::disposing();
169 }
170 
171 //______________________________________________________________________________
172 PackageRegistryImpl::~PackageRegistryImpl()
173 {
174 }
175 
176 //______________________________________________________________________________
177 OUString normalizeMediaType( OUString const & mediaType )
178 {
179     ::rtl::OUStringBuffer buf;
180     sal_Int32 index = 0;
181     for (;;) {
182         buf.append( mediaType.getToken( 0, '/', index ).trim() );
183         if (index < 0)
184             break;
185         buf.append( static_cast< sal_Unicode >('/') );
186     }
187     return buf.makeStringAndClear();
188 }
189 
190 //______________________________________________________________________________
191 
192 void PackageRegistryImpl::packageRemoved(
193     ::rtl::OUString const & url, ::rtl::OUString const & mediaType)
194     throw (css::deployment::DeploymentException,
195            css::uno::RuntimeException)
196 {
197     const t_string2registry::const_iterator i =
198         m_mediaType2backend.find(mediaType);
199 
200     if (i != m_mediaType2backend.end())
201     {
202         i->second->packageRemoved(url, mediaType);
203     }
204 }
205 
206 void PackageRegistryImpl::insertBackend(
207     Reference<deployment::XPackageRegistry> const & xBackend )
208 {
209     m_allBackends.insert( xBackend );
210     typedef ::std::hash_set<OUString, ::rtl::OUStringHash> t_stringset;
211     t_stringset ambiguousFilters;
212 
213     const Sequence< Reference<deployment::XPackageTypeInfo> > packageTypes(
214         xBackend->getSupportedPackageTypes() );
215     for ( sal_Int32 pos = 0; pos < packageTypes.getLength(); ++pos )
216     {
217         Reference<deployment::XPackageTypeInfo> const & xPackageType =
218             packageTypes[ pos ];
219         m_typesInfos.push_back( xPackageType );
220 
221         const OUString mediaType( normalizeMediaType(
222                                       xPackageType->getMediaType() ) );
223         ::std::pair<t_string2registry::iterator, bool> mb_insertion(
224             m_mediaType2backend.insert( t_string2registry::value_type(
225                                             mediaType, xBackend ) ) );
226         if (mb_insertion.second)
227         {
228             // add parameterless media-type, too:
229             sal_Int32 semi = mediaType.indexOf( ';' );
230             if (semi >= 0) {
231                 m_mediaType2backend.insert(
232                     t_string2registry::value_type(
233                         mediaType.copy( 0, semi ), xBackend ) );
234             }
235             const OUString fileFilter( xPackageType->getFileFilter() );
236             //The package backend shall also be called to determine the mediatype
237             //(XPackageRegistry.bindPackage) when the URL points to a directory.
238             const bool bExtension = mediaType.equals(OUSTR("application/vnd.sun.star.package-bundle"));
239             if (fileFilter.getLength() == 0 ||
240                 fileFilter.equalsAsciiL( RTL_CONSTASCII_STRINGPARAM("*.*") ) ||
241                 fileFilter.equalsAsciiL( RTL_CONSTASCII_STRINGPARAM("*") ) ||
242                 bExtension)
243             {
244                 m_ambiguousBackends.insert( xBackend );
245             }
246             else
247             {
248                 sal_Int32 nIndex = 0;
249                 do {
250                     OUString token( fileFilter.getToken( 0, ';', nIndex ) );
251                     if (token.matchAsciiL( RTL_CONSTASCII_STRINGPARAM("*.") ))
252                         token = token.copy( 1 );
253                     if (token.getLength() == 0)
254                         continue;
255                     // mark any further wildcards ambig:
256                     bool ambig = (token.indexOf('*') >= 0 ||
257                                   token.indexOf('?') >= 0);
258                     if (! ambig) {
259                         ::std::pair<t_string2string::iterator, bool> ins(
260                             m_filter2mediaType.insert(
261                                 t_string2string::value_type(
262                                     token, mediaType ) ) );
263                         ambig = !ins.second;
264                         if (ambig) {
265                             // filter has already been in: add previously
266                             // added backend to ambig set
267                             const t_string2registry::const_iterator iFind(
268                                 m_mediaType2backend.find(
269                                     /* media-type of pr. added backend */
270                                     ins.first->second ) );
271                             OSL_ASSERT(
272                                 iFind != m_mediaType2backend.end() );
273                             if (iFind != m_mediaType2backend.end())
274                                 m_ambiguousBackends.insert( iFind->second );
275                         }
276                     }
277                     if (ambig) {
278                         m_ambiguousBackends.insert( xBackend );
279                         // mark filter to be removed later from filters map:
280                         ambiguousFilters.insert( token );
281                     }
282                 }
283                 while (nIndex >= 0);
284             }
285         }
286 #if OSL_DEBUG_LEVEL > 0
287         else {
288             ::rtl::OUStringBuffer buf;
289             buf.appendAscii(
290                 RTL_CONSTASCII_STRINGPARAM(
291                     "more than one PackageRegistryBackend for "
292                     "media-type=\"") );
293             buf.append( mediaType );
294             buf.appendAscii( RTL_CONSTASCII_STRINGPARAM("\" => ") );
295             buf.append( Reference<lang::XServiceInfo>(
296                             xBackend, UNO_QUERY_THROW )->
297                         getImplementationName() );
298             buf.appendAscii( RTL_CONSTASCII_STRINGPARAM("\"!") );
299             OSL_ENSURE( 0, ::rtl::OUStringToOString(
300                             buf.makeStringAndClear(),
301                             RTL_TEXTENCODING_UTF8 ) );
302         }
303 #endif
304     }
305 
306     // cut out ambiguous filters:
307     t_stringset::const_iterator iPos( ambiguousFilters.begin() );
308     const t_stringset::const_iterator iEnd( ambiguousFilters.end() );
309     for ( ; iPos != iEnd; ++iPos ) {
310         m_filter2mediaType.erase( *iPos );
311     }
312 }
313 
314 //______________________________________________________________________________
315 Reference<deployment::XPackageRegistry> PackageRegistryImpl::create(
316     OUString const & context,
317     OUString const & cachePath, bool readOnly,
318     Reference<XComponentContext> const & xComponentContext )
319 {
320     PackageRegistryImpl * that = new PackageRegistryImpl;
321     Reference<deployment::XPackageRegistry> xRet(that);
322 
323     // auto-detect all registered package registries:
324     Reference<container::XEnumeration> xEnum(
325         Reference<container::XContentEnumerationAccess>(
326             xComponentContext->getServiceManager(),
327             UNO_QUERY_THROW )->createContentEnumeration(
328                 OUSTR("com.sun.star.deployment.PackageRegistryBackend") ) );
329     if (xEnum.is())
330     {
331         while (xEnum->hasMoreElements())
332         {
333             Any element( xEnum->nextElement() );
334             Sequence<Any> registryArgs(
335                 cachePath.getLength() == 0 ? 1 : 3 );
336             registryArgs[ 0 ] <<= context;
337             if (cachePath.getLength() > 0)
338             {
339                 Reference<lang::XServiceInfo> xServiceInfo(
340                     element, UNO_QUERY_THROW );
341                 OUString registryCachePath(
342                     makeURL( cachePath,
343                              ::rtl::Uri::encode(
344                                  xServiceInfo->getImplementationName(),
345                                  rtl_UriCharClassPchar,
346                                  rtl_UriEncodeIgnoreEscapes,
347                                  RTL_TEXTENCODING_UTF8 ) ) );
348                 registryArgs[ 1 ] <<= registryCachePath;
349                 registryArgs[ 2 ] <<= readOnly;
350                 if (! readOnly)
351                     create_folder( 0, registryCachePath,
352                                    Reference<XCommandEnvironment>() );
353             }
354 
355             Reference<deployment::XPackageRegistry> xBackend;
356             Reference<lang::XSingleComponentFactory> xFac( element, UNO_QUERY );
357             if (xFac.is()) {
358                 xBackend.set(
359                     xFac->createInstanceWithArgumentsAndContext(
360                         registryArgs, xComponentContext ), UNO_QUERY );
361             }
362             else {
363                 Reference<lang::XSingleServiceFactory> xSingleServiceFac(
364                     element, UNO_QUERY_THROW );
365                 xBackend.set(
366                     xSingleServiceFac->createInstanceWithArguments(
367                         registryArgs ), UNO_QUERY );
368             }
369             if (! xBackend.is()) {
370                 throw DeploymentException(
371                     OUSTR("cannot instantiate PackageRegistryBackend service: ")
372                     + Reference<lang::XServiceInfo>(
373                         element, UNO_QUERY_THROW )->getImplementationName(),
374                     static_cast<OWeakObject *>(that) );
375             }
376 
377             that->insertBackend( xBackend );
378         }
379     }
380 
381     // Insert bundle back-end.
382     // Always register as last, because we want to add extensions also as folders
383     // and as a default we accept every folder, which was not recognized by the other
384     // backends.
385     Reference<deployment::XPackageRegistry> extensionBackend =
386         ::dp_registry::backend::bundle::create(
387             that, context, cachePath, readOnly, xComponentContext);
388     that->insertBackend(extensionBackend);
389 
390     Reference<lang::XServiceInfo> xServiceInfo(
391         extensionBackend, UNO_QUERY_THROW );
392 
393     OSL_ASSERT(xServiceInfo.is());
394     OUString registryCachePath(
395         makeURL( cachePath,
396                  ::rtl::Uri::encode(
397                      xServiceInfo->getImplementationName(),
398                      rtl_UriCharClassPchar,
399                      rtl_UriEncodeIgnoreEscapes,
400                      RTL_TEXTENCODING_UTF8 ) ) );
401     create_folder( 0, registryCachePath, Reference<XCommandEnvironment>());
402 
403 
404 #if OSL_DEBUG_LEVEL > 1
405     // dump tables:
406     {
407         t_registryset allBackends;
408         dp_misc::TRACE("> [dp_registry.cxx] media-type detection:\n\n" );
409         for ( t_string2string::const_iterator iPos(
410                   that->m_filter2mediaType.begin() );
411               iPos != that->m_filter2mediaType.end(); ++iPos )
412         {
413             ::rtl::OUStringBuffer buf;
414             buf.appendAscii( RTL_CONSTASCII_STRINGPARAM("extension \"") );
415             buf.append( iPos->first );
416             buf.appendAscii( RTL_CONSTASCII_STRINGPARAM(
417                                  "\" maps to media-type \"") );
418             buf.append( iPos->second );
419             buf.appendAscii( RTL_CONSTASCII_STRINGPARAM(
420                                  "\" maps to backend ") );
421             const Reference<deployment::XPackageRegistry> xBackend(
422                 that->m_mediaType2backend.find( iPos->second )->second );
423             allBackends.insert( xBackend );
424             buf.append( Reference<lang::XServiceInfo>(
425                             xBackend, UNO_QUERY_THROW )
426                         ->getImplementationName() );
427             dp_misc::writeConsole( buf.makeStringAndClear() + OUSTR("\n"));
428         }
429         dp_misc::TRACE( "> [dp_registry.cxx] ambiguous backends:\n\n" );
430         for ( t_registryset::const_iterator iPos(
431                   that->m_ambiguousBackends.begin() );
432               iPos != that->m_ambiguousBackends.end(); ++iPos )
433         {
434             ::rtl::OUStringBuffer buf;
435             buf.append(
436                 Reference<lang::XServiceInfo>(
437                     *iPos, UNO_QUERY_THROW )->getImplementationName() );
438             buf.appendAscii( RTL_CONSTASCII_STRINGPARAM(": ") );
439             const Sequence< Reference<deployment::XPackageTypeInfo> > types(
440                 (*iPos)->getSupportedPackageTypes() );
441             for ( sal_Int32 pos = 0; pos < types.getLength(); ++pos ) {
442                 Reference<deployment::XPackageTypeInfo> const & xInfo =
443                     types[ pos ];
444                 buf.append( xInfo->getMediaType() );
445                 const OUString filter( xInfo->getFileFilter() );
446                 if (filter.getLength() > 0) {
447                     buf.appendAscii( RTL_CONSTASCII_STRINGPARAM(" (") );
448                     buf.append( filter );
449                     buf.appendAscii( RTL_CONSTASCII_STRINGPARAM(")") );
450                 }
451                 if (pos < (types.getLength() - 1))
452                     buf.appendAscii( RTL_CONSTASCII_STRINGPARAM(", ") );
453             }
454             dp_misc::TRACE(buf.makeStringAndClear() + OUSTR("\n\n"));
455         }
456         allBackends.insert( that->m_ambiguousBackends.begin(),
457                             that->m_ambiguousBackends.end() );
458         OSL_ASSERT( allBackends == that->m_allBackends );
459     }
460 #endif
461 
462     return xRet;
463 }
464 
465 // XUpdatable: broadcast to backends
466 //______________________________________________________________________________
467 void PackageRegistryImpl::update() throw (RuntimeException)
468 {
469     check();
470     t_registryset::const_iterator iPos( m_allBackends.begin() );
471     const t_registryset::const_iterator iEnd( m_allBackends.end() );
472     for ( ; iPos != iEnd; ++iPos ) {
473         const Reference<util::XUpdatable> xUpdatable( *iPos, UNO_QUERY );
474         if (xUpdatable.is())
475             xUpdatable->update();
476     }
477 }
478 
479 // XPackageRegistry
480 //______________________________________________________________________________
481 Reference<deployment::XPackage> PackageRegistryImpl::bindPackage(
482     OUString const & url, OUString const & mediaType_, sal_Bool bRemoved,
483     OUString const & identifier, Reference<XCommandEnvironment> const & xCmdEnv )
484     throw (deployment::DeploymentException, deployment::InvalidRemovedParameterException,
485            CommandFailedException,
486            lang::IllegalArgumentException, RuntimeException)
487 {
488     check();
489     OUString mediaType(mediaType_);
490     if (mediaType.getLength() == 0)
491     {
492         ::ucbhelper::Content ucbContent;
493         if (create_ucb_content(
494                 &ucbContent, url, xCmdEnv, false /* no throw */ )
495                 && !ucbContent.isFolder())
496         {
497             OUString title( ucbContent.getPropertyValue(
498                                 StrTitle::get() ).get<OUString>() );
499             for (;;)
500             {
501                 const t_string2string::const_iterator iFind(
502                     m_filter2mediaType.find(title) );
503                 if (iFind != m_filter2mediaType.end()) {
504                     mediaType = iFind->second;
505                     break;
506                 }
507                 sal_Int32 point = title.indexOf( '.', 1 /* consume . */ );
508                 if (point < 0)
509                     break;
510                 title = title.copy(point);
511             }
512         }
513     }
514     if (mediaType.getLength() == 0)
515     {
516         // try ambiguous backends:
517         t_registryset::const_iterator iPos( m_ambiguousBackends.begin() );
518         const t_registryset::const_iterator iEnd( m_ambiguousBackends.end() );
519         for ( ; iPos != iEnd; ++iPos )
520         {
521             try {
522                 return (*iPos)->bindPackage( url, mediaType, bRemoved,
523                     identifier, xCmdEnv );
524             }
525             catch (lang::IllegalArgumentException &) {
526             }
527         }
528         throw lang::IllegalArgumentException(
529             getResourceString(RID_STR_CANNOT_DETECT_MEDIA_TYPE) + url,
530             static_cast<OWeakObject *>(this), static_cast<sal_Int16>(-1) );
531     }
532     else
533     {
534         // get backend by media-type:
535         t_string2registry::const_iterator iFind(
536             m_mediaType2backend.find( normalizeMediaType(mediaType) ) );
537         if (iFind == m_mediaType2backend.end()) {
538             // xxx todo: more sophisticated media-type argument parsing...
539             sal_Int32 q = mediaType.indexOf( ';' );
540             if (q >= 0) {
541                 iFind = m_mediaType2backend.find(
542                     normalizeMediaType(
543                         // cut parameters:
544                         mediaType.copy( 0, q ) ) );
545             }
546         }
547         if (iFind == m_mediaType2backend.end()) {
548             throw lang::IllegalArgumentException(
549                 getResourceString(RID_STR_UNSUPPORTED_MEDIA_TYPE) + mediaType,
550                 static_cast<OWeakObject *>(this), static_cast<sal_Int16>(-1) );
551         }
552         return iFind->second->bindPackage( url, mediaType, bRemoved,
553             identifier, xCmdEnv );
554     }
555 }
556 
557 //______________________________________________________________________________
558 Sequence< Reference<deployment::XPackageTypeInfo> >
559 PackageRegistryImpl::getSupportedPackageTypes() throw (RuntimeException)
560 {
561     return comphelper::containerToSequence(m_typesInfos);
562 }
563 } // anon namespace
564 
565 //==============================================================================
566 Reference<deployment::XPackageRegistry> SAL_CALL create(
567     OUString const & context,
568     OUString const & cachePath, bool readOnly,
569     Reference<XComponentContext> const & xComponentContext )
570 {
571     return PackageRegistryImpl::create(
572         context, cachePath, readOnly, xComponentContext );
573 }
574 
575 } // namespace dp_registry
576 
577