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