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