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