1 /**************************************************************
2  *
3  * Licensed to the Apache Software Foundation (ASF) under one
4  * or more contributor license agreements.  See the NOTICE file
5  * distributed with this work for additional information
6  * regarding copyright ownership.  The ASF licenses this file
7  * to you under the Apache License, Version 2.0 (the
8  * "License"); you may not use this file except in compliance
9  * with the License.  You may obtain a copy of the License at
10  *
11  *   http://www.apache.org/licenses/LICENSE-2.0
12  *
13  * Unless required by applicable law or agreed to in writing,
14  * software distributed under the License is distributed on an
15  * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
16  * KIND, either express or implied.  See the License for the
17  * specific language governing permissions and limitations
18  * under the License.
19  *
20  *************************************************************/
21 
22 
23 
24 // MARKER(update_precomp.py): autogen include statement, do not remove
25 #include "precompiled_desktop.hxx"
26 
27 #include "oo3extensionmigration.hxx"
28 #include <rtl/instance.hxx>
29 #include <osl/file.hxx>
30 #include <osl/thread.h>
31 #include <tools/urlobj.hxx>
32 #include <unotools/bootstrap.hxx>
33 #include <unotools/ucbstreamhelper.hxx>
34 #include <unotools/textsearch.hxx>
35 #include <comphelper/sequence.hxx>
36 #include <comphelper/processfactory.hxx>
37 #include <ucbhelper/content.hxx>
38 
39 #include <com/sun/star/task/XInteractionApprove.hpp>
40 #include <com/sun/star/task/XInteractionAbort.hpp>
41 #include <com/sun/star/ucb/XCommandInfo.hpp>
42 #include <com/sun/star/ucb/TransferInfo.hpp>
43 #include <com/sun/star/ucb/NameClash.hpp>
44 #include <com/sun/star/ucb/XCommandEnvironment.hpp>
45 #include <com/sun/star/xml/xpath/XXPathAPI.hpp>
46 #include <com/sun/star/beans/NamedValue.hpp>
47 #include <com/sun/star/deployment/ExtensionManager.hpp>
48 
49 #include <com/sun/star/deployment/VersionException.hpp>
50 #include <dp_gui_handleversionexception.hxx>
51 #include <com/sun/star/deployment/DeploymentException.hpp>
52 
53 using namespace ::com::sun::star;
54 using namespace ::com::sun::star::uno;
55 
56 namespace migration
57 {
58 
59 static ::rtl::OUString sExtensionSubDir = ::rtl::OUString( RTL_CONSTASCII_USTRINGPARAM( "/user/uno_packages/" ) );
60 static ::rtl::OUString sSubDirName = ::rtl::OUString( RTL_CONSTASCII_USTRINGPARAM( "cache" ) );
61 static ::rtl::OUString sConfigDir = ::rtl::OUString( RTL_CONSTASCII_USTRINGPARAM( "/user/registry/data" ) );
62 static ::rtl::OUString sOrgDir = ::rtl::OUString( RTL_CONSTASCII_USTRINGPARAM( "/user/registry/data/org" ) );
63 static ::rtl::OUString sExcludeDir1 = ::rtl::OUString( RTL_CONSTASCII_USTRINGPARAM( "/user/registry/data/org" ) );
64 static ::rtl::OUString sExcludeDir2 = ::rtl::OUString( RTL_CONSTASCII_USTRINGPARAM( "/user/registry/data/org/openoffice" ) );
65 static ::rtl::OUString sDescriptionXmlFile = ::rtl::OUString( RTL_CONSTASCII_USTRINGPARAM( "/description.xml" ) );
66 static ::rtl::OUString sExtensionRootSubDirName = ::rtl::OUString( RTL_CONSTASCII_USTRINGPARAM( "/uno_packages" ) );
67 
68 static ::rtl::OUString sConfigurationDataType = ::rtl::OUString( RTL_CONSTASCII_USTRINGPARAM("application/vnd.sun.star.configuration-data"));
69 static ::rtl::OUString sConfigurationSchemaType = ::rtl::OUString( RTL_CONSTASCII_USTRINGPARAM("application/vnd.sun.star.configuration-schema"));
70 
71 // =============================================================================
72 // component operations
73 // =============================================================================
74 
75 ::rtl::OUString OO3ExtensionMigration_getImplementationName()
76 {
77     static ::rtl::OUString* pImplName = 0;
78     if ( !pImplName )
79     {
80         ::osl::MutexGuard aGuard( ::osl::Mutex::getGlobalMutex() );
81         if ( !pImplName )
82 	    {
83             static ::rtl::OUString aImplName( RTL_CONSTASCII_USTRINGPARAM( "com.sun.star.comp.desktop.migration.OOo3Extensions" ) );
84 		    pImplName = &aImplName;
85 	    }
86     }
87     return *pImplName;
88 }
89 
90 // -----------------------------------------------------------------------------
91 
92 Sequence< ::rtl::OUString > OO3ExtensionMigration_getSupportedServiceNames()
93 {
94     static Sequence< ::rtl::OUString >* pNames = 0;
95     if ( !pNames )
96     {
97         ::osl::MutexGuard aGuard( ::osl::Mutex::getGlobalMutex() );
98 	    if ( !pNames )
99 	    {
100             static Sequence< ::rtl::OUString > aNames(1);
101             aNames.getArray()[0] = ::rtl::OUString( RTL_CONSTASCII_USTRINGPARAM( "com.sun.star.migration.Extensions" ) );
102             pNames = &aNames;
103 	    }
104     }
105     return *pNames;
106 }
107 
108 // =============================================================================
109 // ExtensionMigration
110 // =============================================================================
111 
112 OO3ExtensionMigration::OO3ExtensionMigration(Reference< XComponentContext > const & ctx) :
113 m_ctx(ctx)
114 {
115 }
116 
117 // -----------------------------------------------------------------------------
118 
119 OO3ExtensionMigration::~OO3ExtensionMigration()
120 {
121 }
122 
123 ::osl::FileBase::RC OO3ExtensionMigration::checkAndCreateDirectory( INetURLObject& rDirURL )
124 {
125     ::osl::FileBase::RC aResult = ::osl::Directory::create( rDirURL.GetMainURL( INetURLObject::DECODE_TO_IURI ) );
126     if ( aResult == ::osl::FileBase::E_NOENT )
127     {
128         INetURLObject aBaseURL( rDirURL );
129         aBaseURL.removeSegment();
130         checkAndCreateDirectory( aBaseURL );
131         return ::osl::Directory::create( rDirURL.GetMainURL( INetURLObject::DECODE_TO_IURI ) );
132     }
133     else
134     {
135         return aResult;
136     }
137 }
138 
139 void OO3ExtensionMigration::scanUserExtensions( const ::rtl::OUString& sSourceDir, TStringVector& aMigrateExtensions )
140 {
141     osl::Directory    aScanRootDir( sSourceDir );
142     osl::FileStatus   fs(FileStatusMask_Type | FileStatusMask_FileURL);
143     osl::FileBase::RC nRetCode = aScanRootDir.open();
144     if ( nRetCode == osl::Directory::E_None )
145     {
146         sal_uInt32    nHint( 0 );
147         osl::DirectoryItem aItem;
148         while ( aScanRootDir.getNextItem( aItem, nHint ) == osl::Directory::E_None )
149         {
150             if (( aItem.getFileStatus(fs) == osl::FileBase::E_None ) &&
151                 ( fs.getFileType() == osl::FileStatus::Directory   ))
152             {
153                 //Check next folder as the "real" extension folder is below a temp folder!
154                 ::rtl::OUString sExtensionFolderURL = fs.getFileURL();
155 
156                 osl::DirectoryItem aExtDirItem;
157                 osl::Directory     aExtensionRootDir( sExtensionFolderURL );
158 
159                 nRetCode = aExtensionRootDir.open();
160                 if (( nRetCode == osl::Directory::E_None ) &&
161                     ( aExtensionRootDir.getNextItem( aExtDirItem, nHint ) == osl::Directory::E_None ))
162                 {
163                     bool bFileStatus = aExtDirItem.getFileStatus(fs) == osl::FileBase::E_None;
164                     bool bIsDir      = fs.getFileType() == osl::FileStatus::Directory;
165 
166                     if ( bFileStatus && bIsDir )
167                     {
168                         sExtensionFolderURL = fs.getFileURL();
169                         ScanResult eResult = scanExtensionFolder( sExtensionFolderURL );
170                         if ( eResult == SCANRESULT_MIGRATE_EXTENSION )
171                             aMigrateExtensions.push_back( sExtensionFolderURL );
172                     }
173                 }
174             }
175         }
176     }
177 }
178 
179 OO3ExtensionMigration::ScanResult OO3ExtensionMigration::scanExtensionFolder( const ::rtl::OUString& sExtFolder )
180 {
181     ScanResult     aResult = SCANRESULT_NOTFOUND;
182     osl::Directory aDir(sExtFolder);
183 
184     // get sub dirs
185     if (aDir.open() == osl::FileBase::E_None)
186     {
187         // work through directory contents...
188         osl::DirectoryItem item;
189         osl::FileStatus fs(FileStatusMask_Type | FileStatusMask_FileURL);
190         TStringVector aDirectories;
191         while ((aDir.getNextItem(item) == osl::FileBase::E_None ) &&
192                ( aResult == SCANRESULT_NOTFOUND ))
193         {
194             if (item.getFileStatus(fs) == osl::FileBase::E_None)
195             {
196                 ::rtl::OUString aDirEntryURL;
197                 if (fs.getFileType() == osl::FileStatus::Directory)
198                     aDirectories.push_back( fs.getFileURL() );
199                 else
200                 {
201                     aDirEntryURL = fs.getFileURL();
202                     if ( aDirEntryURL.indexOf( sDescriptionXmlFile ) > 0 )
203                         aResult = scanDescriptionXml( aDirEntryURL ) ? SCANRESULT_MIGRATE_EXTENSION : SCANRESULT_DONTMIGRATE_EXTENSION;
204                 }
205             }
206         }
207 
208         TStringVector::const_iterator pIter = aDirectories.begin();
209         while ( pIter != aDirectories.end() && aResult == SCANRESULT_NOTFOUND )
210         {
211             aResult = scanExtensionFolder( *pIter );
212             ++pIter;
213         }
214     }
215     return aResult;
216 }
217 
218 bool OO3ExtensionMigration::scanDescriptionXml( const ::rtl::OUString& sDescriptionXmlURL )
219 {
220     if ( !m_xDocBuilder.is() )
221     {
222         m_xDocBuilder = uno::Reference< xml::dom::XDocumentBuilder >(
223             m_ctx->getServiceManager()->createInstanceWithContext(
224                 ::rtl::OUString( RTL_CONSTASCII_USTRINGPARAM("com.sun.star.xml.dom.DocumentBuilder")),
225                 m_ctx ), uno::UNO_QUERY );
226     }
227 
228     if ( !m_xSimpleFileAccess.is() )
229     {
230         m_xSimpleFileAccess = uno::Reference< ucb::XSimpleFileAccess >(
231             m_ctx->getServiceManager()->createInstanceWithContext(
232                 ::rtl::OUString( RTL_CONSTASCII_USTRINGPARAM("com.sun.star.ucb.SimpleFileAccess")),
233                 m_ctx ), uno::UNO_QUERY );
234     }
235 
236     ::rtl::OUString aExtIdentifier;
237     if ( m_xDocBuilder.is() && m_xSimpleFileAccess.is() )
238     {
239         try
240         {
241             uno::Reference< io::XInputStream > xIn =
242                 m_xSimpleFileAccess->openFileRead( sDescriptionXmlURL );
243 
244             if ( xIn.is() )
245             {
246                 uno::Reference< xml::dom::XDocument > xDoc = m_xDocBuilder->parse( xIn );
247                 if ( xDoc.is() )
248                 {
249                     uno::Reference< xml::dom::XElement > xRoot = xDoc->getDocumentElement();
250                     if ( xRoot.is() &&
251                          xRoot->getTagName().equals(::rtl::OUString( RTL_CONSTASCII_USTRINGPARAM("description"))) )
252                     {
253                         uno::Reference< xml::xpath::XXPathAPI > xPath(
254                             m_ctx->getServiceManager()->createInstanceWithContext(
255                                 ::rtl::OUString( RTL_CONSTASCII_USTRINGPARAM( "com.sun.star.xml.xpath.XPathAPI")),
256                                 m_ctx),
257                             uno::UNO_QUERY);
258 
259                         xPath->registerNS(
260                             ::rtl::OUString(RTL_CONSTASCII_USTRINGPARAM("desc")),
261                             xRoot->getNamespaceURI());
262                         xPath->registerNS(
263                             ::rtl::OUString(RTL_CONSTASCII_USTRINGPARAM("xlink")),
264                             ::rtl::OUString(RTL_CONSTASCII_USTRINGPARAM("http://www.w3.org/1999/xlink")));
265 
266                         try
267                         {
268                             uno::Reference< xml::dom::XNode > xRootNode( xRoot, uno::UNO_QUERY );
269                             uno::Reference< xml::dom::XNode > xNode(
270                                 xPath->selectSingleNode(
271                                     xRootNode,
272                                     ::rtl::OUString(RTL_CONSTASCII_USTRINGPARAM("desc:identifier/@value")) ));
273                             if ( xNode.is() )
274                                 aExtIdentifier = xNode->getNodeValue();
275                         }
276                         catch ( xml::xpath::XPathException& )
277                         {
278                         }
279                         catch ( xml::dom::DOMException& )
280                         {
281                         }
282                     }
283                 }
284             }
285 
286             if ( aExtIdentifier.getLength() > 0 )
287             {
288                 // scan extension identifier and try to match with our black list entries
289                 for ( sal_uInt32 i = 0; i < m_aBlackList.size(); i++ )
290                 {
291                     utl::SearchParam param(m_aBlackList[i], utl::SearchParam::SRCH_REGEXP);
292                     utl::TextSearch  ts(param, LANGUAGE_DONTKNOW);
293 
294                     xub_StrLen start = 0;
295                     xub_StrLen end   = static_cast<sal_uInt16>(aExtIdentifier.getLength());
296                     if (ts.SearchFrwrd(aExtIdentifier, &start, &end))
297                         return false;
298                 }
299             }
300         }
301         catch ( ucb::CommandAbortedException& )
302         {
303         }
304         catch ( uno::RuntimeException& )
305         {
306         }
307 
308         if ( aExtIdentifier.getLength() == 0 )
309         {
310             // Fallback:
311             // Try to use the folder name to match our black list
312             // as some extensions don't provide an identifier in the
313             // description.xml!
314             for ( sal_uInt32 i = 0; i < m_aBlackList.size(); i++ )
315             {
316                 utl::SearchParam param(m_aBlackList[i], utl::SearchParam::SRCH_REGEXP);
317                 utl::TextSearch  ts(param, LANGUAGE_DONTKNOW);
318 
319                 xub_StrLen start = 0;
320                 xub_StrLen end   = static_cast<sal_uInt16>(sDescriptionXmlURL.getLength());
321                 if (ts.SearchFrwrd(sDescriptionXmlURL, &start, &end))
322                     return false;
323             }
324         }
325     }
326 
327     return true;
328 }
329 
330 bool OO3ExtensionMigration::migrateExtension( const ::rtl::OUString& sSourceDir )
331 {
332     if ( !m_xExtensionManager.is() )
333     {
334         try
335         {
336             m_xExtensionManager = deployment::ExtensionManager::get( m_ctx );
337         }
338         catch ( ucb::CommandFailedException & ){}
339         catch ( uno::RuntimeException & ) {}
340     }
341 
342     if ( m_xExtensionManager.is() )
343     {
344         try
345         {
346             TmpRepositoryCommandEnv* pCmdEnv = new TmpRepositoryCommandEnv();
347 
348             uno::Reference< ucb::XCommandEnvironment > xCmdEnv(
349                 static_cast< cppu::OWeakObject* >( pCmdEnv ), uno::UNO_QUERY );
350             uno::Reference< task::XAbortChannel > xAbortChannel;
351             uno::Reference< deployment::XPackage > xPackage =
352                 m_xExtensionManager->addExtension(
353                     sSourceDir, uno::Sequence<beans::NamedValue>(),
354                     ::rtl::OUString(RTL_CONSTASCII_USTRINGPARAM("user")), xAbortChannel, xCmdEnv );
355 
356             if ( xPackage.is() )
357                 return true;
358         }
359         catch ( ucb::CommandFailedException& )
360         {
361         }
362         catch ( ucb::CommandAbortedException& )
363         {
364         }
365         catch ( lang::IllegalArgumentException& )
366         {
367         }
368         catch ( deployment::DeploymentException& )
369         {
370         }
371         catch ( uno::RuntimeException & )
372         {
373         }
374     }
375 
376     return false;
377 }
378 
379 
380 // -----------------------------------------------------------------------------
381 // XServiceInfo
382 // -----------------------------------------------------------------------------
383 
384 ::rtl::OUString OO3ExtensionMigration::getImplementationName() throw (RuntimeException)
385 {
386     return OO3ExtensionMigration_getImplementationName();
387 }
388 
389 // -----------------------------------------------------------------------------
390 
391 sal_Bool OO3ExtensionMigration::supportsService( const ::rtl::OUString& rServiceName ) throw (RuntimeException)
392 {
393     Sequence< ::rtl::OUString > aNames( getSupportedServiceNames() );
394     const ::rtl::OUString* pNames = aNames.getConstArray();
395     const ::rtl::OUString* pEnd = pNames + aNames.getLength();
396     for ( ; pNames != pEnd && !pNames->equals( rServiceName ); ++pNames )
397 	    ;
398 
399     return pNames != pEnd;
400 }
401 
402 // -----------------------------------------------------------------------------
403 
404 Sequence< ::rtl::OUString > OO3ExtensionMigration::getSupportedServiceNames() throw (RuntimeException)
405 {
406     return OO3ExtensionMigration_getSupportedServiceNames();
407 }
408 
409 // -----------------------------------------------------------------------------
410 // XInitialization
411 // -----------------------------------------------------------------------------
412 
413 void OO3ExtensionMigration::initialize( const Sequence< Any >& aArguments ) throw (Exception, RuntimeException)
414 {
415     ::osl::MutexGuard aGuard( m_aMutex );
416 
417     const Any* pIter = aArguments.getConstArray();
418     const Any* pEnd = pIter + aArguments.getLength();
419     for ( ; pIter != pEnd ; ++pIter )
420     {
421         beans::NamedValue aValue;
422         *pIter >>= aValue;
423         if ( aValue.Name.equalsAscii( "UserData" ) )
424         {
425             if ( !(aValue.Value >>= m_sSourceDir) )
426             {
427                 OSL_ENSURE( false, "ExtensionMigration::initialize: argument UserData has wrong type!" );
428             }
429         }
430         else if ( aValue.Name.equalsAscii( "ExtensionBlackList" ) )
431         {
432             Sequence< ::rtl::OUString > aBlackList;
433             if ( (aValue.Value >>= aBlackList ) && ( aBlackList.getLength() > 0 ))
434             {
435                 m_aBlackList.resize( aBlackList.getLength() );
436                 ::comphelper::sequenceToArray< ::rtl::OUString >( &m_aBlackList[0], aBlackList );
437             }
438         }
439     }
440 }
441 
442 // -----------------------------------------------------------------------------
443 
444 TStringVectorPtr getContent( const ::rtl::OUString& rBaseURL )
445 {
446     TStringVectorPtr aResult( new TStringVector );
447     ::osl::Directory aDir( rBaseURL);
448     if ( aDir.open() == ::osl::FileBase::E_None )
449     {
450         // iterate over directory content
451         TStringVector aSubDirs;
452         ::osl::DirectoryItem aItem;
453         while ( aDir.getNextItem( aItem ) == ::osl::FileBase::E_None )
454         {
455             ::osl::FileStatus aFileStatus( FileStatusMask_Type | FileStatusMask_FileURL );
456             if ( aItem.getFileStatus( aFileStatus ) == ::osl::FileBase::E_None )
457                 aResult->push_back( aFileStatus.getFileURL() );
458         }
459     }
460 
461     return aResult;
462 }
463 
464 Any OO3ExtensionMigration::execute( const Sequence< beans::NamedValue >& )
465     throw (lang::IllegalArgumentException, Exception, RuntimeException)
466 {
467     ::osl::MutexGuard aGuard( m_aMutex );
468 
469 	::utl::Bootstrap::PathStatus aStatus = ::utl::Bootstrap::locateUserInstallation( m_sTargetDir );
470 	if ( aStatus == ::utl::Bootstrap::PATH_EXISTS )
471 	{
472 		// copy all extensions
473 		::rtl::OUString sSourceDir( m_sSourceDir );
474         sSourceDir += sExtensionSubDir;
475 		sSourceDir += sSubDirName;
476         sSourceDir += sExtensionRootSubDirName;
477         TStringVector aExtensionToMigrate;
478         scanUserExtensions( sSourceDir, aExtensionToMigrate );
479         if ( aExtensionToMigrate.size() > 0 )
480         {
481             TStringVector::iterator pIter = aExtensionToMigrate.begin();
482             while ( pIter != aExtensionToMigrate.end() )
483             {
484                 migrateExtension( *pIter );
485                 ++pIter;
486             }
487         }
488 	}
489 
490     return Any();
491 }
492 
493 // -----------------------------------------------------------------------------
494 // TmpRepositoryCommandEnv
495 // -----------------------------------------------------------------------------
496 
497 TmpRepositoryCommandEnv::TmpRepositoryCommandEnv()
498 {
499 }
500 
501 TmpRepositoryCommandEnv::~TmpRepositoryCommandEnv()
502 {
503 }
504 // XCommandEnvironment
505 //______________________________________________________________________________
506 uno::Reference< task::XInteractionHandler > TmpRepositoryCommandEnv::getInteractionHandler()
507 throw ( uno::RuntimeException )
508 {
509     return this;
510 }
511 
512 //______________________________________________________________________________
513 uno::Reference< ucb::XProgressHandler > TmpRepositoryCommandEnv::getProgressHandler()
514 throw ( uno::RuntimeException )
515 {
516     return this;
517 }
518 
519 // XInteractionHandler
520 void TmpRepositoryCommandEnv::handle(
521     uno::Reference< task::XInteractionRequest> const & xRequest )
522     throw ( uno::RuntimeException )
523 {
524     uno::Any request( xRequest->getRequest() );
525     OSL_ASSERT( request.getValueTypeClass() == uno::TypeClass_EXCEPTION );
526 
527     bool approve = true;
528     bool abort   = false;
529 
530     deployment::VersionException verExc;
531     if ( xRequest->getRequest() >>= verExc )
532     {
533         // user interaction, if an extension is already been installed.
534         approve = handleVersionException( verExc );
535         abort = !approve;
536     }
537 
538     // select:
539     uno::Sequence< Reference< task::XInteractionContinuation > > conts(
540         xRequest->getContinuations() );
541     Reference< task::XInteractionContinuation > const * pConts =
542         conts.getConstArray();
543     sal_Int32 len = conts.getLength();
544     for ( sal_Int32 pos = 0; pos < len; ++pos )
545     {
546         if (approve) {
547             uno::Reference< task::XInteractionApprove > xInteractionApprove(
548                 pConts[ pos ], uno::UNO_QUERY );
549             if (xInteractionApprove.is()) {
550                 xInteractionApprove->select();
551                 // don't query again for ongoing continuations:
552                 approve = false;
553             }
554         }
555         else if (abort) {
556             uno::Reference< task::XInteractionAbort > xInteractionAbort(
557                 pConts[ pos ], uno::UNO_QUERY );
558             if (xInteractionAbort.is()) {
559                 xInteractionAbort->select();
560                 // don't query again for ongoing continuations:
561                 abort = false;
562             }
563         }
564 	}
565 }
566 
567 // XProgressHandler
568 void TmpRepositoryCommandEnv::push( uno::Any const & /*Status*/ )
569 throw (uno::RuntimeException)
570 {
571 }
572 
573 
574 void TmpRepositoryCommandEnv::update( uno::Any const & /*Status */)
575 throw (uno::RuntimeException)
576 {
577 }
578 
579 void TmpRepositoryCommandEnv::pop() throw (uno::RuntimeException)
580 {
581 }
582 
583 // =============================================================================
584 // component operations
585 // =============================================================================
586 
587 Reference< XInterface > SAL_CALL OO3ExtensionMigration_create(
588     Reference< XComponentContext > const & ctx )
589     SAL_THROW( () )
590 {
591     return static_cast< lang::XTypeProvider * >( new OO3ExtensionMigration(
592         ctx) );
593 }
594 
595 // -----------------------------------------------------------------------------
596 
597 }	// namespace migration
598