1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2 /*
3  * This file is part of the LibreOffice project.
4  *
5  * This Source Code Form is subject to the terms of the Mozilla Public
6  * License, v. 2.0. If a copy of the MPL was not distributed with this
7  * file, You can obtain one at http://mozilla.org/MPL/2.0/.
8  */
9 
10 #include <cstdio>
11 #include <string_view>
12 
13 #include <com/sun/star/beans/IllegalTypeException.hpp>
14 #include <com/sun/star/beans/PropertyAttribute.hpp>
15 #include <com/sun/star/beans/PropertyValue.hpp>
16 #include <com/sun/star/beans/XPropertySetInfo.hpp>
17 #include <com/sun/star/document/CmisProperty.hpp>
18 #include <com/sun/star/io/XActiveDataSink.hpp>
19 #include <com/sun/star/io/XActiveDataStreamer.hpp>
20 #include <com/sun/star/lang/IllegalAccessException.hpp>
21 #include <com/sun/star/lang/IllegalArgumentException.hpp>
22 #include <com/sun/star/lang/WrappedTargetRuntimeException.hpp>
23 #include <com/sun/star/task/InteractionClassification.hpp>
24 #include <com/sun/star/ucb/ContentInfo.hpp>
25 #include <com/sun/star/ucb/ContentInfoAttribute.hpp>
26 #include <com/sun/star/ucb/InsertCommandArgument2.hpp>
27 #include <com/sun/star/ucb/InteractiveBadTransferURLException.hpp>
28 #include <com/sun/star/ucb/InteractiveAugmentedIOException.hpp>
29 #include <com/sun/star/ucb/MissingInputStreamException.hpp>
30 #include <com/sun/star/ucb/OpenMode.hpp>
31 #include <com/sun/star/ucb/UnsupportedCommandException.hpp>
32 #include <com/sun/star/ucb/UnsupportedDataSinkException.hpp>
33 #include <com/sun/star/ucb/UnsupportedOpenModeException.hpp>
34 #include <com/sun/star/ucb/XCommandInfo.hpp>
35 #include <com/sun/star/ucb/XDynamicResultSet.hpp>
36 #ifndef SYSTEM_CURL
37 #include <com/sun/star/xml/crypto/XDigestContext.hpp>
38 #include <com/sun/star/xml/crypto/DigestID.hpp>
39 #include <com/sun/star/xml/crypto/NSSInitializer.hpp>
40 #endif
41 
42 #include <comphelper/processfactory.hxx>
43 #include <comphelper/sequence.hxx>
44 #include <cppuhelper/exc_hlp.hxx>
45 #include <cppuhelper/queryinterface.hxx>
46 #include <config_oauth2.h>
47 #include <o3tl/runtimetooustring.hxx>
48 #include <sal/log.hxx>
49 #include <tools/urlobj.hxx>
50 #include <tools/long.hxx>
51 #include <ucbhelper/cancelcommandexecution.hxx>
52 #include <ucbhelper/content.hxx>
53 #include <ucbhelper/contentidentifier.hxx>
54 #include <ucbhelper/propertyvalueset.hxx>
55 #include <ucbhelper/proxydecider.hxx>
56 #include <ucbhelper/macros.hxx>
57 #include <sax/tools/converter.hxx>
58 
59 #include "auth_provider.hxx"
60 #include "certvalidation_handler.hxx"
61 #include "cmis_content.hxx"
62 #include "cmis_provider.hxx"
63 #include "cmis_resultset.hxx"
64 #include "cmis_strings.hxx"
65 #include "std_inputstream.hxx"
66 #include "std_outputstream.hxx"
67 
68 #define OUSTR_TO_STDSTR(s) string( OUStringToOString( s, RTL_TEXTENCODING_UTF8 ).getStr() )
69 #define STD_TO_OUSTR( str ) OUString( str.c_str(), str.length( ), RTL_TEXTENCODING_UTF8 )
70 
71 using namespace com::sun::star;
72 using namespace std;
73 
74 namespace
75 {
lcl_boostToUnoTime(const boost::posix_time::ptime & boostTime)76     util::DateTime lcl_boostToUnoTime(const boost::posix_time::ptime& boostTime)
77     {
78         util::DateTime unoTime;
79         unoTime.Year = boostTime.date().year();
80         unoTime.Month = boostTime.date().month();
81         unoTime.Day = boostTime.date().day();
82         unoTime.Hours = boostTime.time_of_day().hours();
83         unoTime.Minutes = boostTime.time_of_day().minutes();
84         unoTime.Seconds = boostTime.time_of_day().seconds();
85 
86         // TODO FIXME maybe we should compile with BOOST_DATE_TIME_POSIX_TIME_STD_CONFIG
87         //            to actually get nanosecond precision in boostTime?
88         // use this way rather than total_nanos to avoid overflows with 32-bit long
89         const tools::Long ticks = boostTime.time_of_day().fractional_seconds();
90         tools::Long nanoSeconds = ticks * ( 1000000000 / boost::posix_time::time_duration::ticks_per_second());
91 
92         unoTime.NanoSeconds = nanoSeconds;
93 
94         return unoTime;
95     }
96 
lcl_cmisPropertyToUno(const libcmis::PropertyPtr & pProperty)97     uno::Any lcl_cmisPropertyToUno( const libcmis::PropertyPtr& pProperty )
98     {
99         uno::Any aValue;
100         switch ( pProperty->getPropertyType( )->getType( ) )
101         {
102             default:
103             case libcmis::PropertyType::String:
104                 {
105                     vector< string > aCmisStrings = pProperty->getStrings( );
106                     uno::Sequence< OUString > aStrings( aCmisStrings.size( ) );
107                     OUString* aStringsArr = aStrings.getArray( );
108                     sal_Int32 i = 0;
109                     for ( const auto& rCmisStr : aCmisStrings )
110                     {
111                         aStringsArr[i++] = STD_TO_OUSTR( rCmisStr );
112                     }
113                     aValue <<= aStrings;
114                 }
115                 break;
116             case libcmis::PropertyType::Integer:
117                 {
118                     vector< long > aCmisLongs = pProperty->getLongs( );
119                     uno::Sequence< sal_Int64 > aLongs( aCmisLongs.size( ) );
120                     sal_Int64* aLongsArr = aLongs.getArray( );
121                     sal_Int32 i = 0;
122                     for ( const auto& rCmisLong : aCmisLongs )
123                     {
124                         aLongsArr[i++] = rCmisLong;
125                     }
126                     aValue <<= aLongs;
127                 }
128                 break;
129             case libcmis::PropertyType::Decimal:
130                 {
131                     vector< double > aCmisDoubles = pProperty->getDoubles( );
132                     uno::Sequence< double > aDoubles = comphelper::containerToSequence(aCmisDoubles);
133                     aValue <<= aDoubles;
134                 }
135                 break;
136             case libcmis::PropertyType::Bool:
137                 {
138                     vector< bool > aCmisBools = pProperty->getBools( );
139                     uno::Sequence< sal_Bool > aBools( aCmisBools.size( ) );
140                     sal_Bool* aBoolsArr = aBools.getArray( );
141                     sal_Int32 i = 0;
142                     for ( bool bCmisBool : aCmisBools )
143                     {
144                         aBoolsArr[i++] = bCmisBool;
145                     }
146                     aValue <<= aBools;
147                 }
148                 break;
149             case libcmis::PropertyType::DateTime:
150                 {
151                     vector< boost::posix_time::ptime > aCmisTimes = pProperty->getDateTimes( );
152                     uno::Sequence< util::DateTime > aTimes( aCmisTimes.size( ) );
153                     util::DateTime* aTimesArr = aTimes.getArray( );
154                     sal_Int32 i = 0;
155                     for ( const auto& rCmisTime : aCmisTimes )
156                     {
157                         aTimesArr[i++] = lcl_boostToUnoTime( rCmisTime );
158                     }
159                     aValue <<= aTimes;
160                 }
161                 break;
162         }
163         return aValue;
164     }
165 
lcl_unoToCmisProperty(const document::CmisProperty & prop)166     libcmis::PropertyPtr lcl_unoToCmisProperty(const document::CmisProperty& prop )
167     {
168         libcmis::PropertyTypePtr propertyType( new libcmis::PropertyType( ) );
169 
170         OUString id = prop.Id;
171         OUString name = prop.Name;
172         bool bUpdatable = prop.Updatable;
173         bool bRequired = prop.Required;
174         bool bMultiValued = prop.MultiValued;
175         bool bOpenChoice = prop.OpenChoice;
176         uno::Any value = prop.Value;
177         std::vector< std::string > values;
178 
179         libcmis::PropertyType::Type type = libcmis::PropertyType::String;
180         if ( prop.Type == CMIS_TYPE_STRING )
181         {
182             uno::Sequence< OUString > seqValue;
183             value >>= seqValue;
184             std::transform(seqValue.begin(), seqValue.end(), std::back_inserter(values),
185                 [](const OUString& rValue) -> std::string { return OUSTR_TO_STDSTR( rValue ); });
186             type = libcmis::PropertyType::String;
187         }
188         else if ( prop.Type == CMIS_TYPE_BOOL )
189         {
190             uno::Sequence< sal_Bool > seqValue;
191             value >>= seqValue;
192             std::transform(seqValue.begin(), seqValue.end(), std::back_inserter(values),
193                 [](const bool nValue) -> std::string { return OUSTR_TO_STDSTR( OUString::boolean( nValue ) ); });
194             type = libcmis::PropertyType::Bool;
195         }
196         else if ( prop.Type == CMIS_TYPE_INTEGER )
197         {
198             uno::Sequence< sal_Int64 > seqValue;
199             value >>= seqValue;
200             std::transform(seqValue.begin(), seqValue.end(), std::back_inserter(values),
201                 [](const sal_Int64 nValue) -> std::string { return OUSTR_TO_STDSTR( OUString::number( nValue ) ); });
202             type = libcmis::PropertyType::Integer;
203         }
204         else if ( prop.Type == CMIS_TYPE_DECIMAL )
205         {
206             uno::Sequence< double > seqValue;
207             value >>= seqValue;
208             std::transform(seqValue.begin(), seqValue.end(), std::back_inserter(values),
209                 [](const double fValue) -> std::string { return OUSTR_TO_STDSTR( OUString::number( fValue ) ); });
210             type = libcmis::PropertyType::Decimal;
211         }
212         else if ( prop.Type == CMIS_TYPE_DATETIME )
213         {
214             uno::Sequence< util::DateTime > seqValue;
215             value >>= seqValue;
216             std::transform(seqValue.begin(), seqValue.end(), std::back_inserter(values),
217                 [](const util::DateTime& rValue) -> std::string {
218                     OUStringBuffer aBuffer;
219                     ::sax::Converter::convertDateTime( aBuffer, rValue, nullptr );
220                     return OUSTR_TO_STDSTR( aBuffer.makeStringAndClear( ) );
221                 });
222             type = libcmis::PropertyType::DateTime;
223         }
224 
225         propertyType->setId( OUSTR_TO_STDSTR( id ));
226         propertyType->setDisplayName( OUSTR_TO_STDSTR( name ) );
227         propertyType->setUpdatable( bUpdatable );
228         propertyType->setRequired( bRequired );
229         propertyType->setMultiValued( bMultiValued );
230         propertyType->setOpenChoice( bOpenChoice );
231         propertyType->setType( type );
232 
233         libcmis::PropertyPtr property( new libcmis::Property( propertyType, values ) );
234 
235         return property;
236     }
237 
generateErrorArguments(const cmis::URL & rURL)238     uno::Sequence< uno::Any > generateErrorArguments( const cmis::URL & rURL )
239     {
240         uno::Sequence< uno::Any > aArguments(3);
241 
242         size_t i = 0;
243         aArguments[i++] <<= beans::PropertyValue(
244             "Binding URL",
245             - 1,
246             uno::makeAny( rURL.getBindingUrl() ),
247             beans::PropertyState_DIRECT_VALUE );
248 
249         aArguments[i++] <<= beans::PropertyValue(
250             "Username",
251             -1,
252             uno::makeAny( rURL.getUsername() ),
253             beans::PropertyState_DIRECT_VALUE );
254 
255         aArguments[i++] <<= beans::PropertyValue(
256             "Repository Id",
257             -1,
258             uno::makeAny( rURL.getRepositoryId() ),
259             beans::PropertyState_DIRECT_VALUE );
260 
261         return aArguments;
262     }
263 }
264 
265 namespace cmis
266 {
Content(const uno::Reference<uno::XComponentContext> & rxContext,ContentProvider * pProvider,const uno::Reference<ucb::XContentIdentifier> & Identifier,libcmis::ObjectPtr const & pObject)267     Content::Content( const uno::Reference< uno::XComponentContext >& rxContext,
268         ContentProvider *pProvider, const uno::Reference< ucb::XContentIdentifier >& Identifier,
269         libcmis::ObjectPtr const & pObject )
270         : ContentImplHelper( rxContext, pProvider, Identifier ),
271         m_pProvider( pProvider ),
272         m_pSession( nullptr ),
273         m_pObject( pObject ),
274         m_sURL( Identifier->getContentIdentifier( ) ),
275         m_aURL( Identifier->getContentIdentifier( ) ),
276         m_bTransient( false ),
277         m_bIsFolder( false )
278     {
279         SAL_INFO( "ucb.ucp.cmis", "Content::Content() " << m_sURL );
280 
281         m_sObjectPath = m_aURL.getObjectPath( );
282         m_sObjectId = m_aURL.getObjectId( );
283     }
284 
Content(const uno::Reference<uno::XComponentContext> & rxContext,ContentProvider * pProvider,const uno::Reference<ucb::XContentIdentifier> & Identifier,bool bIsFolder)285     Content::Content( const uno::Reference< uno::XComponentContext >& rxContext, ContentProvider *pProvider,
286         const uno::Reference< ucb::XContentIdentifier >& Identifier,
287         bool bIsFolder )
288         : ContentImplHelper( rxContext, pProvider, Identifier ),
289         m_pProvider( pProvider ),
290         m_pSession( nullptr ),
291         m_sURL( Identifier->getContentIdentifier( ) ),
292         m_aURL( Identifier->getContentIdentifier( ) ),
293         m_bTransient( true ),
294         m_bIsFolder( bIsFolder )
295     {
296         SAL_INFO( "ucb.ucp.cmis", "Content::Content() " << m_sURL );
297 
298         m_sObjectPath = m_aURL.getObjectPath( );
299         m_sObjectId = m_aURL.getObjectId( );
300     }
301 
~Content()302     Content::~Content()
303     {
304     }
305 
getSession(const uno::Reference<ucb::XCommandEnvironment> & xEnv)306     libcmis::Session* Content::getSession( const uno::Reference< ucb::XCommandEnvironment >& xEnv )
307     {
308         // Set the proxy if needed. We are doing that all times as the proxy data shouldn't be cached.
309         ucbhelper::InternetProxyDecider aProxyDecider( m_xContext );
310         INetURLObject aBindingUrl( m_aURL.getBindingUrl( ) );
311         const ucbhelper::InternetProxyServer& rProxy = aProxyDecider.getProxy(
312                 INetURLObject::GetScheme( aBindingUrl.GetProtocol( ) ), aBindingUrl.GetHost(), aBindingUrl.GetPort() );
313         OUString sProxy = rProxy.aName;
314         if ( rProxy.nPort > 0 )
315             sProxy += ":" + OUString::number( rProxy.nPort );
316         libcmis::SessionFactory::setProxySettings( OUSTR_TO_STDSTR( sProxy ), string(), string(), string() );
317 
318         // Look for a cached session, key is binding url + repo id
319         OUString sSessionId = m_aURL.getBindingUrl( ) + m_aURL.getRepositoryId( );
320         if ( nullptr == m_pSession )
321             m_pSession = m_pProvider->getSession( sSessionId, m_aURL.getUsername( ) );
322 
323         if ( nullptr == m_pSession )
324         {
325 #ifndef SYSTEM_CURL
326             // Initialize NSS library to make sure libcmis (and curl) can access CACERTs using NSS
327             // when using internal libcurl.
328             uno::Reference< css::xml::crypto::XNSSInitializer >
329                 xNSSInitializer = css::xml::crypto::NSSInitializer::create( m_xContext );
330 
331             uno::Reference< css::xml::crypto::XDigestContext > xDigestContext(
332                     xNSSInitializer->getDigestContext( css::xml::crypto::DigestID::SHA256,
333                                                               uno::Sequence< beans::NamedValue >() ),
334                                                               uno::UNO_SET_THROW );
335 #endif
336 
337             // Set the SSL Validation handler
338             libcmis::CertValidationHandlerPtr certHandler(
339                     new CertValidationHandler( xEnv, m_xContext, aBindingUrl.GetHost( ) ) );
340             libcmis::SessionFactory::setCertificateValidationHandler( certHandler );
341 
342             // Get the auth credentials
343             AuthProvider aAuthProvider(xEnv, m_xIdentifier->getContentIdentifier(), m_aURL.getBindingUrl());
344             AuthProvider::setXEnv( xEnv );
345 
346             string rUsername = OUSTR_TO_STDSTR( m_aURL.getUsername( ) );
347             string rPassword = OUSTR_TO_STDSTR( m_aURL.getPassword( ) );
348 
349             bool bSkipInitialPWAuth = false;
350             if (m_aURL.getBindingUrl() == ONEDRIVE_BASE_URL
351                 || m_aURL.getBindingUrl() == GDRIVE_BASE_URL)
352             {
353                 // skip the initial username and pw-auth prompt, the only supported method is the
354                 // auth-code-fallback one (login with your browser, copy code into the dialog)
355                 // TODO: if LO were to listen on localhost for the request, it would be much nicer
356                 // user experience
357                 bSkipInitialPWAuth = true;
358                 rPassword = aAuthProvider.getRefreshToken(rUsername);
359             }
360 
361             bool bIsDone = false;
362 
363             while ( !bIsDone )
364             {
365                 if (bSkipInitialPWAuth || aAuthProvider.authenticationQuery(rUsername, rPassword))
366                 {
367                     // Initiate a CMIS session and register it as we found nothing
368                     libcmis::OAuth2DataPtr oauth2Data;
369                     if ( m_aURL.getBindingUrl( ) == GDRIVE_BASE_URL )
370                     {
371                         // reset the skip, so user gets a chance to cancel
372                         bSkipInitialPWAuth = false;
373                         libcmis::SessionFactory::setOAuth2AuthCodeProvider(AuthProvider::copyWebAuthCodeFallback);
374                         oauth2Data.reset( new libcmis::OAuth2Data(
375                             GDRIVE_AUTH_URL, GDRIVE_TOKEN_URL,
376                             GDRIVE_SCOPE, GDRIVE_REDIRECT_URI,
377                             GDRIVE_CLIENT_ID, GDRIVE_CLIENT_SECRET ) );
378                     }
379                     if ( m_aURL.getBindingUrl().startsWith( ALFRESCO_CLOUD_BASE_URL ) )
380                         oauth2Data.reset( new libcmis::OAuth2Data(
381                             ALFRESCO_CLOUD_AUTH_URL, ALFRESCO_CLOUD_TOKEN_URL,
382                             ALFRESCO_CLOUD_SCOPE, ALFRESCO_CLOUD_REDIRECT_URI,
383                             ALFRESCO_CLOUD_CLIENT_ID, ALFRESCO_CLOUD_CLIENT_SECRET ) );
384                     if ( m_aURL.getBindingUrl( ) == ONEDRIVE_BASE_URL )
385                     {
386                         // reset the skip, so user gets a chance to cancel
387                         bSkipInitialPWAuth = false;
388                         libcmis::SessionFactory::setOAuth2AuthCodeProvider(AuthProvider::copyWebAuthCodeFallback);
389                         oauth2Data.reset( new libcmis::OAuth2Data(
390                             ONEDRIVE_AUTH_URL, ONEDRIVE_TOKEN_URL,
391                             ONEDRIVE_SCOPE, ONEDRIVE_REDIRECT_URI,
392                             ONEDRIVE_CLIENT_ID, ONEDRIVE_CLIENT_SECRET ) );
393                     }
394                     try
395                     {
396                         m_pSession = libcmis::SessionFactory::createSession(
397                             OUSTR_TO_STDSTR( m_aURL.getBindingUrl( ) ),
398                             rUsername, rPassword, OUSTR_TO_STDSTR( m_aURL.getRepositoryId( ) ), false, oauth2Data );
399 
400                         if ( m_pSession == nullptr )
401                         {
402                             // Fail: session was not created
403                             ucbhelper::cancelCommandExecution(
404                                 ucb::IOErrorCode_INVALID_DEVICE,
405                                 generateErrorArguments(m_aURL),
406                                 xEnv);
407                         }
408                         else if ( m_pSession->getRepository() == nullptr )
409                         {
410                             // Fail: no repository or repository is invalid
411                             ucbhelper::cancelCommandExecution(
412                                 ucb::IOErrorCode_INVALID_DEVICE,
413                                 generateErrorArguments(m_aURL),
414                                 xEnv,
415                                 "error accessing a repository");
416                         }
417                         else
418                         {
419                             m_pProvider->registerSession(sSessionId, m_aURL.getUsername( ), m_pSession);
420                             if (m_aURL.getBindingUrl() == ONEDRIVE_BASE_URL
421                                 || m_aURL.getBindingUrl() == GDRIVE_BASE_URL)
422                             {
423                                 aAuthProvider.storeRefreshToken(rUsername, rPassword,
424                                                                 m_pSession->getRefreshToken());
425                             }
426                         }
427 
428                         bIsDone = true;
429                     }
430                     catch( const libcmis::Exception & e )
431                     {
432                         if ( e.getType() != "permissionDenied" )
433                         {
434                             SAL_INFO("ucb.ucp.cmis", "Unexpected libcmis exception: " << e.what());
435                             throw;
436                         }
437                     }
438                 }
439                 else
440                 {
441                     // Silently fail as the user cancelled the authentication
442                     ucbhelper::cancelCommandExecution(
443                                         ucb::IOErrorCode_ABORT,
444                                         uno::Sequence< uno::Any >( 0 ),
445                                         xEnv );
446                     throw uno::RuntimeException( );
447                 }
448             }
449         }
450         return m_pSession;
451     }
452 
getObjectType(const uno::Reference<ucb::XCommandEnvironment> & xEnv)453     libcmis::ObjectTypePtr const & Content::getObjectType( const uno::Reference< ucb::XCommandEnvironment >& xEnv )
454     {
455         if ( nullptr == m_pObjectType.get( ) && m_bTransient )
456         {
457             string typeId = m_bIsFolder ? "cmis:folder" : "cmis:document";
458             // The type to create needs to be fetched from the possible children types
459             // defined in the parent folder. Then, we'll pick up the first one we find matching
460             // cmis:folder or cmis:document (depending what we need to create).
461             // The easy case will work in most cases, but not on some servers (like Lotus Live)
462             libcmis::Folder* pParent = nullptr;
463             bool bTypeRestricted = false;
464             try
465             {
466                 pParent = dynamic_cast< libcmis::Folder* >( getObject( xEnv ).get( ) );
467             }
468             catch ( const libcmis::Exception& )
469             {
470             }
471 
472             if ( pParent )
473             {
474                 map< string, libcmis::PropertyPtr >& aProperties = pParent->getProperties( );
475                 map< string, libcmis::PropertyPtr >::iterator it = aProperties.find( "cmis:allowedChildObjectTypeIds" );
476                 if ( it != aProperties.end( ) )
477                 {
478                     libcmis::PropertyPtr pProperty = it->second;
479                     if ( pProperty )
480                     {
481                         vector< string > typesIds = pProperty->getStrings( );
482                         for ( const auto& rType : typesIds )
483                         {
484                             bTypeRestricted = true;
485                             libcmis::ObjectTypePtr type = getSession( xEnv )->getType( rType );
486 
487                             // FIXME Improve performances by adding getBaseTypeId( ) method to libcmis
488                             if ( type->getBaseType( )->getId( ) == typeId )
489                             {
490                                 m_pObjectType = type;
491                                 break;
492                             }
493                         }
494                     }
495                 }
496             }
497 
498             if ( !bTypeRestricted )
499                 m_pObjectType = getSession( xEnv )->getType( typeId );
500         }
501         return m_pObjectType;
502     }
503 
504 
getObject(const uno::Reference<ucb::XCommandEnvironment> & xEnv)505     libcmis::ObjectPtr const & Content::getObject( const uno::Reference< ucb::XCommandEnvironment >& xEnv )
506     {
507         // can't get the session for some reason
508         // the recent file opening at start up is an example.
509         try
510         {
511             if ( !getSession( xEnv ) )
512                 return m_pObject;
513         }
514         catch ( uno::RuntimeException& )
515         {
516             return m_pObject;
517         }
518         if ( !m_pObject.get() )
519         {
520             if ( !m_sObjectId.isEmpty( ) )
521             {
522                 try
523                 {
524                     m_pObject = getSession( xEnv )->getObject( OUSTR_TO_STDSTR( m_sObjectId ) );
525                 }
526                 catch ( const libcmis::Exception& )
527                 {
528                     SAL_INFO( "ucb.ucp.cmis", "object: " << OUSTR_TO_STDSTR(m_sObjectId));
529                     throw libcmis::Exception( "Object not found" );
530                 }
531             }
532             else if (!(m_sObjectPath.isEmpty() || m_sObjectPath == "/"))
533             {
534                 try
535                 {
536                     m_pObject = getSession( xEnv )->getObjectByPath( OUSTR_TO_STDSTR( m_sObjectPath ) );
537                 }
538                 catch ( const libcmis::Exception& )
539                 {
540                     // In some cases, getting the object from the path doesn't work,
541                     // but getting the parent from its path and the get the child in the list is OK.
542                     // It's weird, but needed to handle case where the path isn't the folders/files
543                     // names separated by '/' (as in Lotus Live)
544                     INetURLObject aParentUrl( m_sURL );
545                     string sName = OUSTR_TO_STDSTR( aParentUrl.getName( INetURLObject::LAST_SEGMENT, true, INetURLObject::DecodeMechanism::WithCharset ) );
546                     aParentUrl.removeSegment( );
547                     OUString sParentUrl = aParentUrl.GetMainURL( INetURLObject::DecodeMechanism::NONE );
548                     // Avoid infinite recursion if sParentUrl == m_sURL
549                     if (sParentUrl != m_sURL)
550                     {
551                         rtl::Reference<Content> xParent(new Content(m_xContext, m_pProvider, new ucbhelper::ContentIdentifier(sParentUrl)));
552                         libcmis::FolderPtr pParentFolder = boost::dynamic_pointer_cast< libcmis::Folder >(xParent->getObject(xEnv));
553                         if (pParentFolder)
554                         {
555                             vector< libcmis::ObjectPtr > children = pParentFolder->getChildren();
556                             auto it = std::find_if(children.begin(), children.end(),
557                                 [&sName](const libcmis::ObjectPtr& rChild) { return rChild->getName() == sName; });
558                             if (it != children.end())
559                                 m_pObject = *it;
560                         }
561                     }
562 
563                     if ( !m_pObject )
564                         throw libcmis::Exception( "Object not found" );
565                 }
566             }
567             else
568             {
569                 m_pObject = getSession( xEnv )->getRootFolder( );
570                 m_sObjectPath = "/";
571                 m_sObjectId = OUString( );
572             }
573         }
574 
575         return m_pObject;
576     }
577 
isFolder(const uno::Reference<ucb::XCommandEnvironment> & xEnv)578     bool Content::isFolder(const uno::Reference< ucb::XCommandEnvironment >& xEnv )
579     {
580         bool bIsFolder = false;
581         try
582         {
583             libcmis::ObjectPtr obj = getObject( xEnv );
584             if ( obj )
585                 bIsFolder = obj->getBaseType( ) == "cmis:folder";
586         }
587         catch ( const libcmis::Exception& e )
588         {
589             SAL_INFO( "ucb.ucp.cmis", "Unexpected libcmis exception: " << e.what( ) );
590 
591             ucbhelper::cancelCommandExecution(
592                             ucb::IOErrorCode_GENERAL,
593                             uno::Sequence< uno::Any >( 0 ),
594                             xEnv,
595                             OUString::createFromAscii( e.what( ) ) );
596 
597         }
598         return bIsFolder;
599     }
600 
getBadArgExcept()601     uno::Any Content::getBadArgExcept()
602     {
603         return uno::makeAny( lang::IllegalArgumentException(
604             "Wrong argument type!",
605             static_cast< cppu::OWeakObject * >( this ), -1) );
606     }
607 
updateProperties(const uno::Any & iCmisProps,const uno::Reference<ucb::XCommandEnvironment> & xEnv)608     libcmis::ObjectPtr Content::updateProperties(
609          const uno::Any& iCmisProps,
610          const uno::Reference< ucb::XCommandEnvironment >& xEnv )
611     {
612         // Convert iCmisProps to Cmis Properties;
613         uno::Sequence< document::CmisProperty > aPropsSeq;
614         iCmisProps >>= aPropsSeq;
615         map< string, libcmis::PropertyPtr > aProperties;
616 
617         for ( const auto& rProp : std::as_const(aPropsSeq) )
618         {
619             std::string id = OUSTR_TO_STDSTR( rProp.Id );
620             libcmis::PropertyPtr prop = lcl_unoToCmisProperty( rProp );
621             aProperties.insert( std::pair<string, libcmis::PropertyPtr>( id, prop ) );
622         }
623         libcmis::ObjectPtr updateObj;
624         try
625         {
626             updateObj = getObject( xEnv )->updateProperties( aProperties );
627         }
628         catch ( const libcmis::Exception& e )
629         {
630             SAL_INFO( "ucb.ucp.cmis", "Unexpected libcmis exception: "<< e.what( ) );
631         }
632 
633         return updateObj;
634     }
635 
getPropertyValues(const uno::Sequence<beans::Property> & rProperties,const uno::Reference<ucb::XCommandEnvironment> & xEnv)636     uno::Reference< sdbc::XRow > Content::getPropertyValues(
637             const uno::Sequence< beans::Property >& rProperties,
638             const uno::Reference< ucb::XCommandEnvironment >& xEnv )
639     {
640         rtl::Reference< ::ucbhelper::PropertyValueSet > xRow = new ::ucbhelper::PropertyValueSet( m_xContext );
641 
642         for( const beans::Property& rProp : rProperties )
643         {
644             try
645             {
646                 if ( rProp.Name == "IsDocument" )
647                 {
648                     try
649                     {
650                         libcmis::ObjectPtr obj = getObject( xEnv );
651                         if ( obj )
652                             xRow->appendBoolean( rProp, obj->getBaseType( ) == "cmis:document" );
653                     }
654                     catch ( const libcmis::Exception& )
655                     {
656                         if ( m_pObjectType.get( ) )
657                             xRow->appendBoolean( rProp, getObjectType( xEnv )->getBaseType()->getId( ) == "cmis:document" );
658                         else
659                             xRow->appendVoid( rProp );
660                     }
661                 }
662                 else if ( rProp.Name == "IsFolder" )
663                 {
664                     try
665                     {
666                         libcmis::ObjectPtr obj = getObject( xEnv );
667                         if ( obj )
668                             xRow->appendBoolean( rProp, obj->getBaseType( ) == "cmis:folder" );
669                         else
670                             xRow->appendBoolean( rProp, false );
671                     }
672                     catch ( const libcmis::Exception& )
673                     {
674                         if ( m_pObjectType.get( ) )
675                             xRow->appendBoolean( rProp, getObjectType( xEnv )->getBaseType()->getId( ) == "cmis:folder" );
676                         else
677                             xRow->appendVoid( rProp );
678                     }
679                 }
680                 else if ( rProp.Name == "Title" )
681                 {
682                     OUString sTitle;
683                     try
684                     {
685                         sTitle = STD_TO_OUSTR( getObject( xEnv )->getName() );
686                     }
687                     catch ( const libcmis::Exception& )
688                     {
689                         if ( !m_pObjectProps.empty() )
690                         {
691                             map< string, libcmis::PropertyPtr >::iterator it = m_pObjectProps.find( "cmis:name" );
692                             if ( it != m_pObjectProps.end( ) )
693                             {
694                                 vector< string > values = it->second->getStrings( );
695                                 if ( !values.empty() )
696                                     sTitle = STD_TO_OUSTR( values.front( ) );
697                             }
698                         }
699                     }
700 
701                     // Nothing worked... get it from the path
702                     if ( sTitle.isEmpty( ) )
703                     {
704                         OUString sPath = m_sObjectPath;
705 
706                         // Get rid of the trailing slash problem
707                         if ( sPath.endsWith("/") )
708                             sPath = sPath.copy( 0, sPath.getLength() - 1 );
709 
710                         // Get the last segment
711                         sal_Int32 nPos = sPath.lastIndexOf( '/' );
712                         if ( nPos >= 0 )
713                             sTitle = sPath.copy( nPos + 1 );
714                     }
715 
716                     if ( !sTitle.isEmpty( ) )
717                         xRow->appendString( rProp, sTitle );
718                     else
719                         xRow->appendVoid( rProp );
720                 }
721                 else if ( rProp.Name == "ObjectId" )
722                 {
723                     OUString sId;
724                     try
725                     {
726                         sId = STD_TO_OUSTR( getObject( xEnv )->getId() );
727                     }
728                     catch ( const libcmis::Exception& )
729                     {
730                         if ( !m_pObjectProps.empty() )
731                         {
732                             map< string, libcmis::PropertyPtr >::iterator it = m_pObjectProps.find( "cmis:objectId" );
733                             if ( it != m_pObjectProps.end( ) )
734                             {
735                                 vector< string > values = it->second->getStrings( );
736                                 if ( !values.empty() )
737                                     sId = STD_TO_OUSTR( values.front( ) );
738                             }
739                         }
740                     }
741 
742                     if ( !sId.isEmpty( ) )
743                         xRow->appendString( rProp, sId );
744                     else
745                         xRow->appendVoid( rProp );
746                 }
747                 else if ( rProp.Name == "TitleOnServer" )
748                 {
749                     xRow->appendString( rProp, m_sObjectPath);
750                 }
751                 else if ( rProp.Name == "IsReadOnly" )
752                 {
753                     boost::shared_ptr< libcmis::AllowableActions > allowableActions = getObject( xEnv )->getAllowableActions( );
754                     bool bReadOnly = false;
755                     if ( !allowableActions->isAllowed( libcmis::ObjectAction::SetContentStream ) &&
756                          !allowableActions->isAllowed( libcmis::ObjectAction::CheckIn ) )
757                         bReadOnly = true;
758 
759                     xRow->appendBoolean( rProp, bReadOnly );
760                 }
761                 else if ( rProp.Name == "DateCreated" )
762                 {
763                     util::DateTime aTime = lcl_boostToUnoTime( getObject( xEnv )->getCreationDate( ) );
764                     xRow->appendTimestamp( rProp, aTime );
765                 }
766                 else if ( rProp.Name == "DateModified" )
767                 {
768                     util::DateTime aTime = lcl_boostToUnoTime( getObject( xEnv )->getLastModificationDate( ) );
769                     xRow->appendTimestamp( rProp, aTime );
770                 }
771                 else if ( rProp.Name == "Size" )
772                 {
773                     try
774                     {
775                         libcmis::Document* document = dynamic_cast< libcmis::Document* >( getObject( xEnv ).get( ) );
776                         if ( nullptr != document )
777                             xRow->appendLong( rProp, document->getContentLength() );
778                         else
779                             xRow->appendVoid( rProp );
780                     }
781                     catch ( const libcmis::Exception& )
782                     {
783                         xRow->appendVoid( rProp );
784                     }
785                 }
786                 else if ( rProp.Name == "CreatableContentsInfo" )
787                 {
788                     xRow->appendObject( rProp, uno::makeAny( queryCreatableContentsInfo( xEnv ) ) );
789                 }
790                 else if ( rProp.Name == "MediaType" )
791                 {
792                     try
793                     {
794                         libcmis::Document* document = dynamic_cast< libcmis::Document* >( getObject( xEnv ).get( ) );
795                         if ( nullptr != document )
796                             xRow->appendString( rProp, STD_TO_OUSTR( document->getContentType() ) );
797                         else
798                             xRow->appendVoid( rProp );
799                     }
800                     catch ( const libcmis::Exception& )
801                     {
802                         xRow->appendVoid( rProp );
803                     }
804                 }
805                 else if ( rProp.Name == "IsVolume" )
806                 {
807                     xRow->appendBoolean( rProp, false );
808                 }
809                 else if ( rProp.Name == "IsRemote" )
810                 {
811                     xRow->appendBoolean( rProp, false );
812                 }
813                 else if ( rProp.Name == "IsRemoveable" )
814                 {
815                     xRow->appendBoolean( rProp, false );
816                 }
817                 else if ( rProp.Name == "IsFloppy" )
818                 {
819                     xRow->appendBoolean( rProp, false );
820                 }
821                 else if ( rProp.Name == "IsCompactDisc" )
822                 {
823                     xRow->appendBoolean( rProp, false );
824                 }
825                 else if ( rProp.Name == "IsHidden" )
826                 {
827                     xRow->appendBoolean( rProp, false );
828                 }
829                 else if ( rProp.Name == "TargetURL" )
830                 {
831                     xRow->appendString( rProp, "" );
832                 }
833                 else if ( rProp.Name == "BaseURI" )
834                 {
835                     xRow->appendString( rProp, m_aURL.getBindingUrl( ) );
836                 }
837                 else if ( rProp.Name == "CmisProperties" )
838                 {
839                     try
840                     {
841                         libcmis::ObjectPtr object = getObject( xEnv );
842                         map< string, libcmis::PropertyPtr >& aProperties = object->getProperties( );
843                         uno::Sequence< document::CmisProperty > aCmisProperties( aProperties.size( ) );
844                         document::CmisProperty* pCmisProps = aCmisProperties.getArray( );
845                         sal_Int32 i = 0;
846                         for ( const auto& [sId, rProperty] : aProperties )
847                         {
848                             string sDisplayName = rProperty->getPropertyType()->getDisplayName( );
849                             bool bUpdatable = rProperty->getPropertyType()->isUpdatable( );
850                             bool bRequired = rProperty->getPropertyType()->isRequired( );
851                             bool bMultiValued = rProperty->getPropertyType()->isMultiValued();
852                             bool bOpenChoice = rProperty->getPropertyType()->isOpenChoice();
853 
854                             pCmisProps[i].Id = STD_TO_OUSTR( sId );
855                             pCmisProps[i].Name = STD_TO_OUSTR( sDisplayName );
856                             pCmisProps[i].Updatable = bUpdatable;
857                             pCmisProps[i].Required = bRequired;
858                             pCmisProps[i].MultiValued = bMultiValued;
859                             pCmisProps[i].OpenChoice = bOpenChoice;
860                             pCmisProps[i].Value = lcl_cmisPropertyToUno( rProperty );
861                             switch ( rProperty->getPropertyType( )->getType( ) )
862                             {
863                                 default:
864                                 case libcmis::PropertyType::String:
865                                     pCmisProps[i].Type = CMIS_TYPE_STRING;
866                                 break;
867                                 case libcmis::PropertyType::Integer:
868                                     pCmisProps[i].Type = CMIS_TYPE_INTEGER;
869                                 break;
870                                 case libcmis::PropertyType::Decimal:
871                                     pCmisProps[i].Type = CMIS_TYPE_DECIMAL;
872                                 break;
873                                 case libcmis::PropertyType::Bool:
874                                     pCmisProps[i].Type = CMIS_TYPE_BOOL;
875                                 break;
876                                 case libcmis::PropertyType::DateTime:
877                                     pCmisProps[i].Type = CMIS_TYPE_DATETIME;
878                                 break;
879                             }
880                             ++i;
881                         }
882                         xRow->appendObject( rProp.Name, uno::makeAny( aCmisProperties ) );
883                     }
884                     catch ( const libcmis::Exception& )
885                     {
886                         xRow->appendVoid( rProp );
887                     }
888                 }
889                 else if ( rProp.Name == "IsVersionable" )
890                 {
891                     try
892                     {
893                         libcmis::ObjectPtr object = getObject( xEnv );
894                         bool bIsVersionable = object->getTypeDescription( )->isVersionable( );
895                         xRow->appendBoolean( rProp, bIsVersionable );
896                     }
897                     catch ( const libcmis::Exception& )
898                     {
899                         xRow->appendVoid( rProp );
900                     }
901                 }
902                 else if ( rProp.Name == "CanCheckOut" )
903                 {
904                     try
905                     {
906                         libcmis::ObjectPtr pObject = getObject( xEnv );
907                         libcmis::AllowableActionsPtr aAllowables = pObject->getAllowableActions( );
908                         bool bAllowed = false;
909                         if ( aAllowables )
910                         {
911                             bAllowed = aAllowables->isAllowed( libcmis::ObjectAction::CheckOut );
912                         }
913                         xRow->appendBoolean( rProp, bAllowed );
914                     }
915                     catch ( const libcmis::Exception& )
916                     {
917                         xRow->appendVoid( rProp );
918                     }
919                 }
920                 else if ( rProp.Name == "CanCancelCheckOut" )
921                 {
922                     try
923                     {
924                         libcmis::ObjectPtr pObject = getObject( xEnv );
925                         libcmis::AllowableActionsPtr aAllowables = pObject->getAllowableActions( );
926                         bool bAllowed = false;
927                         if ( aAllowables )
928                         {
929                             bAllowed = aAllowables->isAllowed( libcmis::ObjectAction::CancelCheckOut );
930                         }
931                         xRow->appendBoolean( rProp, bAllowed );
932                     }
933                     catch ( const libcmis::Exception& )
934                     {
935                         xRow->appendVoid( rProp );
936                     }
937                 }
938                 else if ( rProp.Name == "CanCheckIn" )
939                 {
940                     try
941                     {
942                         libcmis::ObjectPtr pObject = getObject( xEnv );
943                         libcmis::AllowableActionsPtr aAllowables = pObject->getAllowableActions( );
944                         bool bAllowed = false;
945                         if ( aAllowables )
946                         {
947                             bAllowed = aAllowables->isAllowed( libcmis::ObjectAction::CheckIn );
948                         }
949                         xRow->appendBoolean( rProp, bAllowed );
950                     }
951                     catch ( const libcmis::Exception& )
952                     {
953                         xRow->appendVoid( rProp );
954                     }
955                 }
956                 else
957                     SAL_INFO( "ucb.ucp.cmis", "Looking for unsupported property " << rProp.Name );
958             }
959             catch (const libcmis::Exception&)
960             {
961                 xRow->appendVoid( rProp );
962             }
963         }
964 
965         return xRow;
966     }
967 
open(const ucb::OpenCommandArgument2 & rOpenCommand,const uno::Reference<ucb::XCommandEnvironment> & xEnv)968     uno::Any Content::open(const ucb::OpenCommandArgument2 & rOpenCommand,
969         const uno::Reference< ucb::XCommandEnvironment > & xEnv )
970     {
971         bool bIsFolder = isFolder( xEnv );
972 
973         // Handle the case of the non-existing file
974         if ( !getObject( xEnv ) )
975         {
976             uno::Sequence< uno::Any > aArgs( 1 );
977             aArgs[ 0 ] <<= m_xIdentifier->getContentIdentifier();
978             uno::Any aErr = uno::makeAny(
979                 ucb::InteractiveAugmentedIOException(OUString(), static_cast< cppu::OWeakObject * >( this ),
980                     task::InteractionClassification_ERROR,
981                     bIsFolder ? ucb::IOErrorCode_NOT_EXISTING_PATH : ucb::IOErrorCode_NOT_EXISTING, aArgs)
982             );
983 
984             ucbhelper::cancelCommandExecution(aErr, xEnv);
985         }
986 
987         uno::Any aRet;
988 
989         bool bOpenFolder = (
990             ( rOpenCommand.Mode == ucb::OpenMode::ALL ) ||
991             ( rOpenCommand.Mode == ucb::OpenMode::FOLDERS ) ||
992             ( rOpenCommand.Mode == ucb::OpenMode::DOCUMENTS )
993          );
994 
995         if ( bOpenFolder && bIsFolder )
996         {
997             uno::Reference< ucb::XDynamicResultSet > xSet
998                 = new DynamicResultSet(m_xContext, this, rOpenCommand, xEnv );
999             aRet <<= xSet;
1000         }
1001         else if ( rOpenCommand.Sink.is() )
1002         {
1003             if (
1004                 ( rOpenCommand.Mode == ucb::OpenMode::DOCUMENT_SHARE_DENY_NONE ) ||
1005                 ( rOpenCommand.Mode == ucb::OpenMode::DOCUMENT_SHARE_DENY_WRITE )
1006                )
1007             {
1008                 ucbhelper::cancelCommandExecution(
1009                     uno::makeAny ( ucb::UnsupportedOpenModeException
1010                         ( OUString(), static_cast< cppu::OWeakObject * >( this ),
1011                           sal_Int16( rOpenCommand.Mode ) ) ),
1012                         xEnv );
1013             }
1014 
1015             if ( !feedSink( rOpenCommand.Sink, xEnv ) )
1016             {
1017                 // Note: rOpenCommand.Sink may contain an XStream
1018                 //       implementation. Support for this type of
1019                 //       sink is optional...
1020                 SAL_INFO( "ucb.ucp.cmis", "Failed to copy data to sink" );
1021 
1022                 ucbhelper::cancelCommandExecution(
1023                     uno::makeAny (ucb::UnsupportedDataSinkException
1024                         ( OUString(), static_cast< cppu::OWeakObject * >( this ),
1025                           rOpenCommand.Sink ) ),
1026                         xEnv );
1027             }
1028         }
1029         else
1030             SAL_INFO( "ucb.ucp.cmis", "Open falling through ..." );
1031 
1032         return aRet;
1033     }
1034 
checkIn(const ucb::CheckinArgument & rArg,const uno::Reference<ucb::XCommandEnvironment> & xEnv)1035     OUString Content::checkIn( const ucb::CheckinArgument& rArg,
1036         const uno::Reference< ucb::XCommandEnvironment > & xEnv )
1037     {
1038         ucbhelper::Content aSourceContent( rArg.SourceURL, xEnv, comphelper::getProcessComponentContext( ) );
1039         uno::Reference< io::XInputStream > xIn = aSourceContent.openStream( );
1040 
1041         libcmis::ObjectPtr object;
1042         try
1043         {
1044             object = getObject( xEnv );
1045         }
1046         catch ( const libcmis::Exception& e )
1047         {
1048             SAL_INFO( "ucb.ucp.cmis", "Unexpected libcmis exception: " << e.what( ) );
1049             ucbhelper::cancelCommandExecution(
1050                                 ucb::IOErrorCode_GENERAL,
1051                                 uno::Sequence< uno::Any >( 0 ),
1052                                 xEnv,
1053                                 OUString::createFromAscii( e.what() ) );
1054         }
1055 
1056         libcmis::Document* pPwc = dynamic_cast< libcmis::Document* >( object.get( ) );
1057         if ( !pPwc )
1058         {
1059             ucbhelper::cancelCommandExecution(
1060                                 ucb::IOErrorCode_GENERAL,
1061                                 uno::Sequence< uno::Any >( 0 ),
1062                                 xEnv,
1063                                 "Checkin only supported by documents" );
1064         }
1065 
1066         boost::shared_ptr< ostream > pOut( new ostringstream ( ios_base::binary | ios_base::in | ios_base::out ) );
1067         uno::Reference < io::XOutputStream > xOutput = new StdOutputStream( pOut );
1068         copyData( xIn, xOutput );
1069 
1070         map< string, libcmis::PropertyPtr > newProperties;
1071         libcmis::DocumentPtr pDoc;
1072 
1073         try
1074         {
1075             pDoc = pPwc->checkIn( rArg.MajorVersion, OUSTR_TO_STDSTR( rArg.VersionComment ), newProperties,
1076                                   pOut, OUSTR_TO_STDSTR( rArg.MimeType ), OUSTR_TO_STDSTR( rArg.NewTitle ) );
1077         }
1078         catch ( const libcmis::Exception& e )
1079         {
1080             SAL_INFO( "ucb.ucp.cmis", "Unexpected libcmis exception: " << e.what( ) );
1081             ucbhelper::cancelCommandExecution(
1082                                 ucb::IOErrorCode_GENERAL,
1083                                 uno::Sequence< uno::Any >( 0 ),
1084                                 xEnv,
1085                                 OUString::createFromAscii( e.what() ) );
1086         }
1087 
1088         // Get the URL and send it back as a result
1089         URL aCmisUrl( m_sURL );
1090         vector< string > aPaths = pDoc->getPaths( );
1091         if ( !aPaths.empty() )
1092         {
1093             string sPath = aPaths.front( );
1094             aCmisUrl.setObjectPath( STD_TO_OUSTR( sPath ) );
1095         }
1096         else
1097         {
1098             // We may have unfiled document depending on the server, those
1099             // won't have any path, use their ID instead
1100             string sId = pDoc->getId( );
1101             aCmisUrl.setObjectId( STD_TO_OUSTR( sId ) );
1102         }
1103         return aCmisUrl.asString( );
1104     }
1105 
checkOut(const uno::Reference<ucb::XCommandEnvironment> & xEnv)1106     OUString Content::checkOut( const uno::Reference< ucb::XCommandEnvironment > & xEnv )
1107     {
1108         OUString aRet;
1109         try
1110         {
1111             // Checkout the document if possible
1112             libcmis::DocumentPtr pDoc = boost::dynamic_pointer_cast< libcmis::Document >( getObject( xEnv ) );
1113             if ( pDoc.get( ) == nullptr )
1114             {
1115                 ucbhelper::cancelCommandExecution(
1116                                     ucb::IOErrorCode_GENERAL,
1117                                     uno::Sequence< uno::Any >( 0 ),
1118                                     xEnv,
1119                                     "Checkout only supported by documents" );
1120             }
1121             libcmis::DocumentPtr pPwc = pDoc->checkOut( );
1122 
1123             // Compute the URL of the Private Working Copy (PWC)
1124             URL aCmisUrl( m_sURL );
1125             vector< string > aPaths = pPwc->getPaths( );
1126             if ( !aPaths.empty() )
1127             {
1128                 string sPath = aPaths.front( );
1129                 aCmisUrl.setObjectPath( STD_TO_OUSTR( sPath ) );
1130             }
1131             else
1132             {
1133                 // We may have unfiled PWC depending on the server, those
1134                 // won't have any path, use their ID instead
1135                 string sId = pPwc->getId( );
1136                 aCmisUrl.setObjectId( STD_TO_OUSTR( sId ) );
1137             }
1138             aRet = aCmisUrl.asString( );
1139         }
1140         catch ( const libcmis::Exception& e )
1141         {
1142             SAL_INFO( "ucb.ucp.cmis", "Unexpected libcmis exception: " << e.what( ) );
1143             ucbhelper::cancelCommandExecution(
1144                                 ucb::IOErrorCode_GENERAL,
1145                                 uno::Sequence< uno::Any >( 0 ),
1146                                 xEnv,
1147                                 o3tl::runtimeToOUString(e.what()));
1148         }
1149         return aRet;
1150     }
1151 
cancelCheckOut(const uno::Reference<ucb::XCommandEnvironment> & xEnv)1152     OUString Content::cancelCheckOut( const uno::Reference< ucb::XCommandEnvironment > & xEnv )
1153     {
1154         OUString aRet;
1155         try
1156         {
1157             libcmis::DocumentPtr pPwc = boost::dynamic_pointer_cast< libcmis::Document >( getObject( xEnv ) );
1158             if ( pPwc.get( ) == nullptr )
1159             {
1160                 ucbhelper::cancelCommandExecution(
1161                                     ucb::IOErrorCode_GENERAL,
1162                                     uno::Sequence< uno::Any >( 0 ),
1163                                     xEnv,
1164                                     "CancelCheckout only supported by documents" );
1165             }
1166             pPwc->cancelCheckout( );
1167 
1168             // Get the Original document (latest version)
1169             vector< libcmis::DocumentPtr > aVersions = pPwc->getAllVersions( );
1170             for ( const auto& rVersion : aVersions )
1171             {
1172                 libcmis::DocumentPtr pVersion = rVersion;
1173                 map< string, libcmis::PropertyPtr > aProps = pVersion->getProperties( );
1174                 bool bIsLatestVersion = false;
1175                 map< string, libcmis::PropertyPtr >::iterator propIt = aProps.find( string( "cmis:isLatestVersion" ) );
1176                 if ( propIt != aProps.end( ) && !propIt->second->getBools( ).empty( ) )
1177                 {
1178                     bIsLatestVersion = propIt->second->getBools( ).front( );
1179                 }
1180 
1181                 if ( bIsLatestVersion )
1182                 {
1183                     // Compute the URL of the Document
1184                     URL aCmisUrl( m_sURL );
1185                     vector< string > aPaths = pVersion->getPaths( );
1186                     if ( !aPaths.empty() )
1187                     {
1188                         string sPath = aPaths.front( );
1189                         aCmisUrl.setObjectPath( STD_TO_OUSTR( sPath ) );
1190                     }
1191                     else
1192                     {
1193                         // We may have unfiled doc depending on the server, those
1194                         // won't have any path, use their ID instead
1195                         string sId = pVersion->getId( );
1196                         aCmisUrl.setObjectId( STD_TO_OUSTR( sId ) );
1197                     }
1198                     aRet = aCmisUrl.asString( );
1199                     break;
1200                 }
1201             }
1202         }
1203         catch ( const libcmis::Exception& e )
1204         {
1205             SAL_INFO( "ucb.ucp.cmis", "Unexpected libcmis exception: " << e.what( ) );
1206             ucbhelper::cancelCommandExecution(
1207                                 ucb::IOErrorCode_GENERAL,
1208                                 uno::Sequence< uno::Any >( 0 ),
1209                                 xEnv,
1210                                 o3tl::runtimeToOUString(e.what()));
1211         }
1212         return aRet;
1213     }
1214 
getAllVersions(const uno::Reference<ucb::XCommandEnvironment> & xEnv)1215     uno::Sequence< document::CmisVersion> Content::getAllVersions( const uno::Reference< ucb::XCommandEnvironment > & xEnv )
1216     {
1217         try
1218         {
1219             // get the document
1220             libcmis::DocumentPtr pDoc = boost::dynamic_pointer_cast< libcmis::Document >( getObject( xEnv ) );
1221             if ( pDoc.get( ) == nullptr )
1222             {
1223                 ucbhelper::cancelCommandExecution(
1224                                     ucb::IOErrorCode_GENERAL,
1225                                     uno::Sequence< uno::Any >( 0 ),
1226                                     xEnv,
1227                                     "Can not get the document" );
1228             }
1229             vector< libcmis::DocumentPtr > aCmisVersions = pDoc->getAllVersions( );
1230             uno::Sequence< document::CmisVersion > aVersions( aCmisVersions.size( ) );
1231             int i = 0;
1232             for ( const auto& rVersion : aCmisVersions )
1233             {
1234                 libcmis::DocumentPtr pVersion = rVersion;
1235                 aVersions[i].Id = STD_TO_OUSTR( pVersion->getId( ) );
1236                 aVersions[i].Author = STD_TO_OUSTR( pVersion->getCreatedBy( ) );
1237                 aVersions[i].TimeStamp = lcl_boostToUnoTime( pVersion->getLastModificationDate( ) );
1238                 aVersions[i].Comment = STD_TO_OUSTR( pVersion->getStringProperty("cmis:checkinComment") );
1239                 ++i;
1240             }
1241             return aVersions;
1242         }
1243         catch ( const libcmis::Exception& e )
1244         {
1245             SAL_INFO( "ucb.ucp.cmis", "Unexpected libcmis exception: " << e.what( ) );
1246             ucbhelper::cancelCommandExecution(
1247                     ucb::IOErrorCode_GENERAL,
1248                     uno::Sequence< uno::Any >( 0 ),
1249                     xEnv,
1250                     o3tl::runtimeToOUString(e.what()));
1251         }
1252         return uno::Sequence< document::CmisVersion > ( );
1253     }
1254 
transfer(const ucb::TransferInfo & rTransferInfo,const uno::Reference<ucb::XCommandEnvironment> & xEnv)1255     void Content::transfer( const ucb::TransferInfo& rTransferInfo,
1256         const uno::Reference< ucb::XCommandEnvironment > & xEnv )
1257     {
1258         // If the source isn't on the same CMIS repository, then simply copy
1259         INetURLObject aSourceUrl( rTransferInfo.SourceURL );
1260         if ( aSourceUrl.GetProtocol() != INetProtocol::Cmis )
1261         {
1262             OUString sSrcBindingUrl = URL( rTransferInfo.SourceURL ).getBindingUrl( );
1263             if ( sSrcBindingUrl != m_aURL.getBindingUrl( ) )
1264             {
1265                 ucbhelper::cancelCommandExecution(
1266                     uno::makeAny(
1267                         ucb::InteractiveBadTransferURLException(
1268                             "Unsupported URL scheme!",
1269                             static_cast< cppu::OWeakObject * >( this ) ) ),
1270                     xEnv );
1271             }
1272         }
1273 
1274         SAL_INFO( "ucb.ucp.cmis", "TODO - Content::transfer()" );
1275     }
1276 
insert(const uno::Reference<io::XInputStream> & xInputStream,bool bReplaceExisting,std::u16string_view rMimeType,const uno::Reference<ucb::XCommandEnvironment> & xEnv)1277     void Content::insert( const uno::Reference< io::XInputStream > & xInputStream,
1278         bool bReplaceExisting, std::u16string_view rMimeType,
1279         const uno::Reference< ucb::XCommandEnvironment >& xEnv )
1280     {
1281         if ( !xInputStream.is() )
1282         {
1283             ucbhelper::cancelCommandExecution( uno::makeAny
1284                 ( ucb::MissingInputStreamException
1285                   ( OUString(), static_cast< cppu::OWeakObject * >( this ) ) ),
1286                 xEnv );
1287         }
1288 
1289         // For transient content, the URL is the one of the parent
1290         if ( !m_bTransient )
1291             return;
1292 
1293         OUString sNewPath;
1294 
1295         // Try to get the object from the server if there is any
1296         libcmis::FolderPtr pFolder;
1297         try
1298         {
1299             pFolder = boost::dynamic_pointer_cast< libcmis::Folder >( getObject( xEnv ) );
1300         }
1301         catch ( const libcmis::Exception& )
1302         {
1303         }
1304 
1305         if ( pFolder == nullptr )
1306             return;
1307 
1308         libcmis::ObjectPtr object;
1309         map< string, libcmis::PropertyPtr >::iterator it = m_pObjectProps.find( "cmis:name" );
1310         if ( it == m_pObjectProps.end( ) )
1311         {
1312             ucbhelper::cancelCommandExecution( uno::makeAny
1313                 ( uno::RuntimeException( "Missing name property",
1314                     static_cast< cppu::OWeakObject * >( this ) ) ),
1315                 xEnv );
1316         }
1317         string newName = it->second->getStrings( ).front( );
1318         string newPath = OUSTR_TO_STDSTR( m_sObjectPath );
1319         if ( !newPath.empty( ) && newPath[ newPath.size( ) - 1 ] != '/' )
1320             newPath += "/";
1321         newPath += newName;
1322         try
1323         {
1324             if ( !m_sObjectId.isEmpty( ) )
1325                 object = getSession( xEnv )->getObject( OUSTR_TO_STDSTR( m_sObjectId) );
1326             else
1327                 object = getSession( xEnv )->getObjectByPath( newPath );
1328             sNewPath = STD_TO_OUSTR( newPath );
1329         }
1330         catch ( const libcmis::Exception& )
1331         {
1332             // Nothing matched the path
1333         }
1334 
1335         if ( nullptr != object.get( ) )
1336         {
1337             // Are the base type matching?
1338             if ( object->getBaseType( ) != m_pObjectType->getBaseType( )->getId() )
1339             {
1340                 ucbhelper::cancelCommandExecution( uno::makeAny
1341                     ( uno::RuntimeException( "Can't change a folder into a document and vice-versa.",
1342                         static_cast< cppu::OWeakObject * >( this ) ) ),
1343                     xEnv );
1344             }
1345 
1346             // Update the existing object if it's a document
1347             libcmis::Document* document = dynamic_cast< libcmis::Document* >( object.get( ) );
1348             if ( nullptr != document )
1349             {
1350                 boost::shared_ptr< ostream > pOut( new ostringstream ( ios_base::binary | ios_base::in | ios_base::out ) );
1351                 uno::Reference < io::XOutputStream > xOutput = new StdOutputStream( pOut );
1352                 copyData( xInputStream, xOutput );
1353                 try
1354                 {
1355                     document->setContentStream( pOut, OUSTR_TO_STDSTR( rMimeType ), string( ), bReplaceExisting );
1356                 }
1357                 catch ( const libcmis::Exception& )
1358                 {
1359                     ucbhelper::cancelCommandExecution( uno::makeAny
1360                         ( uno::RuntimeException( "Error when setting document content",
1361                             static_cast< cppu::OWeakObject * >( this ) ) ),
1362                         xEnv );
1363                 }
1364             }
1365         }
1366         else
1367         {
1368             // We need to create a brand new object... either folder or document
1369             bool bIsFolder = getObjectType( xEnv )->getBaseType( )->getId( ) == "cmis:folder";
1370             setCmisProperty( "cmis:objectTypeId", getObjectType( xEnv )->getId( ), xEnv );
1371 
1372             if ( bIsFolder )
1373             {
1374                 try
1375                 {
1376                     pFolder->createFolder( m_pObjectProps );
1377                     sNewPath = STD_TO_OUSTR( newPath );
1378                 }
1379                 catch ( const libcmis::Exception& )
1380                 {
1381                     ucbhelper::cancelCommandExecution( uno::makeAny
1382                         ( uno::RuntimeException( "Error when creating folder",
1383                             static_cast< cppu::OWeakObject * >( this ) ) ),
1384                         xEnv );
1385                 }
1386             }
1387             else
1388             {
1389                 boost::shared_ptr< ostream > pOut( new ostringstream ( ios_base::binary | ios_base::in | ios_base::out ) );
1390                 uno::Reference < io::XOutputStream > xOutput = new StdOutputStream( pOut );
1391                 copyData( xInputStream, xOutput );
1392                 try
1393                 {
1394                     pFolder->createDocument( m_pObjectProps, pOut, OUSTR_TO_STDSTR( rMimeType ), string() );
1395                     sNewPath = STD_TO_OUSTR( newPath );
1396                 }
1397                 catch ( const libcmis::Exception& )
1398                 {
1399                     ucbhelper::cancelCommandExecution( uno::makeAny
1400                         ( uno::RuntimeException( "Error when creating document",
1401                             static_cast< cppu::OWeakObject * >( this ) ) ),
1402                         xEnv );
1403                 }
1404             }
1405         }
1406 
1407         if ( sNewPath.isEmpty( ) && m_sObjectId.isEmpty( ) )
1408             return;
1409 
1410         // Update the current content: it's no longer transient
1411         m_sObjectPath = sNewPath;
1412         URL aUrl( m_sURL );
1413         aUrl.setObjectPath( m_sObjectPath );
1414         aUrl.setObjectId( m_sObjectId );
1415         m_sURL = aUrl.asString( );
1416         m_pObject.reset( );
1417         m_pObjectType.reset( );
1418         m_pObjectProps.clear( );
1419         m_bTransient = false;
1420         inserted();
1421     }
1422 
1423     const int TRANSFER_BUFFER_SIZE = 65536;
1424 
copyData(const uno::Reference<io::XInputStream> & xIn,const uno::Reference<io::XOutputStream> & xOut)1425     void Content::copyData(
1426         const uno::Reference< io::XInputStream >& xIn,
1427         const uno::Reference< io::XOutputStream >& xOut )
1428     {
1429         uno::Sequence< sal_Int8 > theData( TRANSFER_BUFFER_SIZE );
1430 
1431         while ( xIn->readBytes( theData, TRANSFER_BUFFER_SIZE ) > 0 )
1432             xOut->writeBytes( theData );
1433 
1434         xOut->closeOutput();
1435     }
1436 
setPropertyValues(const uno::Sequence<beans::PropertyValue> & rValues,const uno::Reference<ucb::XCommandEnvironment> & xEnv)1437     uno::Sequence< uno::Any > Content::setPropertyValues(
1438             const uno::Sequence< beans::PropertyValue >& rValues,
1439             const uno::Reference< ucb::XCommandEnvironment >& xEnv )
1440     {
1441         try
1442         {
1443             // Get the already set properties if possible
1444             if ( !m_bTransient && getObject( xEnv ).get( ) )
1445             {
1446                 m_pObjectProps.clear( );
1447                 m_pObjectType = getObject( xEnv )->getTypeDescription();
1448             }
1449         }
1450         catch ( const libcmis::Exception& e )
1451         {
1452             SAL_INFO( "ucb.ucp.cmis", "Unexpected libcmis exception: " << e.what( ) );
1453             ucbhelper::cancelCommandExecution(
1454                                 ucb::IOErrorCode_GENERAL,
1455                                 uno::Sequence< uno::Any >( 0 ),
1456                                 xEnv,
1457                                 o3tl::runtimeToOUString(e.what()));
1458         }
1459 
1460         sal_Int32 nCount = rValues.getLength();
1461         uno::Sequence< uno::Any > aRet( nCount );
1462 
1463         bool bChanged = false;
1464         const beans::PropertyValue* pValues = rValues.getConstArray();
1465         for ( sal_Int32 n = 0; n < nCount; ++n )
1466         {
1467             const beans::PropertyValue& rValue = pValues[ n ];
1468             if ( rValue.Name == "ContentType" ||
1469                  rValue.Name == "MediaType" ||
1470                  rValue.Name == "IsDocument" ||
1471                  rValue.Name == "IsFolder" ||
1472                  rValue.Name == "Size" ||
1473                  rValue.Name == "CreatableContentsInfo" )
1474             {
1475                 lang::IllegalAccessException e ( "Property is read-only!",
1476                        static_cast< cppu::OWeakObject* >( this ) );
1477                 aRet[ n ] <<= e;
1478             }
1479             else if ( rValue.Name == "Title" )
1480             {
1481                 OUString aNewTitle;
1482                 if (!( rValue.Value >>= aNewTitle ))
1483                 {
1484                     aRet[ n ] <<= beans::IllegalTypeException
1485                         ( "Property value has wrong type!",
1486                           static_cast< cppu::OWeakObject * >( this ) );
1487                     continue;
1488                 }
1489 
1490                 if ( aNewTitle.isEmpty() )
1491                 {
1492                     aRet[ n ] <<= lang::IllegalArgumentException
1493                         ( "Empty title not allowed!",
1494                           static_cast< cppu::OWeakObject * >( this ), -1 );
1495                     continue;
1496 
1497                 }
1498 
1499                 setCmisProperty( "cmis:name", OUSTR_TO_STDSTR( aNewTitle ), xEnv );
1500                 bChanged = true;
1501             }
1502             else
1503             {
1504                 SAL_INFO( "ucb.ucp.cmis", "Couldn't set property: " << rValue.Name );
1505                 lang::IllegalAccessException e ( "Property is read-only!",
1506                        static_cast< cppu::OWeakObject* >( this ) );
1507                 aRet[ n ] <<= e;
1508             }
1509         }
1510 
1511         try
1512         {
1513             if ( !m_bTransient && bChanged )
1514             {
1515                 getObject( xEnv )->updateProperties( m_pObjectProps );
1516             }
1517         }
1518         catch ( const libcmis::Exception& e )
1519         {
1520             SAL_INFO( "ucb.ucp.cmis", "Unexpected libcmis exception: " << e.what( ) );
1521             ucbhelper::cancelCommandExecution(
1522                                 ucb::IOErrorCode_GENERAL,
1523                                 uno::Sequence< uno::Any >( 0 ),
1524                                 xEnv,
1525                                 o3tl::runtimeToOUString(e.what()));
1526         }
1527 
1528         return aRet;
1529     }
1530 
feedSink(const uno::Reference<uno::XInterface> & xSink,const uno::Reference<ucb::XCommandEnvironment> & xEnv)1531     bool Content::feedSink( const uno::Reference< uno::XInterface>& xSink,
1532         const uno::Reference< ucb::XCommandEnvironment >& xEnv )
1533     {
1534         if ( !xSink.is() )
1535             return false;
1536 
1537         uno::Reference< io::XOutputStream > xOut(xSink, uno::UNO_QUERY );
1538         uno::Reference< io::XActiveDataSink > xDataSink(xSink, uno::UNO_QUERY );
1539         uno::Reference< io::XActiveDataStreamer > xDataStreamer( xSink, uno::UNO_QUERY );
1540 
1541         if ( !xOut.is() && !xDataSink.is() && ( !xDataStreamer.is() || !xDataStreamer->getStream().is() ) )
1542             return false;
1543 
1544         if ( xDataStreamer.is() && !xOut.is() )
1545             xOut = xDataStreamer->getStream()->getOutputStream();
1546 
1547         try
1548         {
1549             libcmis::Document* document = dynamic_cast< libcmis::Document* >( getObject( xEnv ).get() );
1550 
1551             if (!document)
1552                 return false;
1553 
1554             boost::shared_ptr< istream > aIn = document->getContentStream( );
1555 
1556             uno::Reference< io::XInputStream > xIn = new StdInputStream( aIn );
1557             if( !xIn.is( ) )
1558                 return false;
1559 
1560             if ( xDataSink.is() )
1561                 xDataSink->setInputStream( xIn );
1562             else if ( xOut.is() )
1563                 copyData( xIn, xOut );
1564         }
1565         catch ( const libcmis::Exception& e )
1566         {
1567             SAL_INFO( "ucb.ucp.cmis", "Unexpected libcmis exception: " << e.what( ) );
1568             ucbhelper::cancelCommandExecution(
1569                                 ucb::IOErrorCode_GENERAL,
1570                                 uno::Sequence< uno::Any >( 0 ),
1571                                 xEnv,
1572                                 o3tl::runtimeToOUString(e.what()));
1573         }
1574 
1575         return true;
1576     }
1577 
getProperties(const uno::Reference<ucb::XCommandEnvironment> &)1578     uno::Sequence< beans::Property > Content::getProperties(
1579             const uno::Reference< ucb::XCommandEnvironment > & )
1580     {
1581         static const beans::Property aGenericProperties[] =
1582         {
1583             beans::Property( "IsDocument",
1584                 -1, cppu::UnoType<bool>::get(),
1585                 beans::PropertyAttribute::BOUND | beans::PropertyAttribute::READONLY ),
1586             beans::Property( "IsFolder",
1587                 -1, cppu::UnoType<bool>::get(),
1588                 beans::PropertyAttribute::BOUND | beans::PropertyAttribute::READONLY ),
1589             beans::Property( "Title",
1590                 -1, cppu::UnoType<OUString>::get(),
1591                 beans::PropertyAttribute::BOUND ),
1592             beans::Property( "ObjectId",
1593                 -1, cppu::UnoType<OUString>::get(),
1594                 beans::PropertyAttribute::BOUND ),
1595             beans::Property( "TitleOnServer",
1596                 -1, cppu::UnoType<OUString>::get(),
1597                 beans::PropertyAttribute::BOUND ),
1598             beans::Property( "IsReadOnly",
1599                 -1, cppu::UnoType<bool>::get(),
1600                 beans::PropertyAttribute::BOUND | beans::PropertyAttribute::READONLY ),
1601             beans::Property( "DateCreated",
1602                 -1, cppu::UnoType<util::DateTime>::get(),
1603                 beans::PropertyAttribute::BOUND | beans::PropertyAttribute::READONLY ),
1604             beans::Property( "DateModified",
1605                 -1, cppu::UnoType<util::DateTime>::get(),
1606                 beans::PropertyAttribute::BOUND | beans::PropertyAttribute::READONLY ),
1607             beans::Property( "Size",
1608                 -1, cppu::UnoType<sal_Int64>::get(),
1609                 beans::PropertyAttribute::BOUND | beans::PropertyAttribute::READONLY ),
1610             beans::Property( "CreatableContentsInfo",
1611                 -1, cppu::UnoType<uno::Sequence< ucb::ContentInfo >>::get(),
1612                 beans::PropertyAttribute::BOUND | beans::PropertyAttribute::READONLY ),
1613             beans::Property( "MediaType",
1614                 -1, cppu::UnoType<OUString>::get(),
1615                 beans::PropertyAttribute::BOUND ),
1616             beans::Property( "CmisProperties",
1617                 -1, cppu::UnoType<uno::Sequence< document::CmisProperty>>::get(),
1618                 beans::PropertyAttribute::BOUND ),
1619             beans::Property( "IsVersionable",
1620                 -1, cppu::UnoType<bool>::get(),
1621                 beans::PropertyAttribute::BOUND | beans::PropertyAttribute::READONLY ),
1622             beans::Property( "CanCheckOut",
1623                 -1, cppu::UnoType<bool>::get(),
1624                 beans::PropertyAttribute::BOUND | beans::PropertyAttribute::READONLY ),
1625             beans::Property( "CanCancelCheckOut",
1626                 -1, cppu::UnoType<bool>::get(),
1627                 beans::PropertyAttribute::BOUND | beans::PropertyAttribute::READONLY ),
1628             beans::Property( "CanCheckIn",
1629                 -1, cppu::UnoType<bool>::get(),
1630                 beans::PropertyAttribute::BOUND | beans::PropertyAttribute::READONLY ),
1631         };
1632 
1633         const int nProps = SAL_N_ELEMENTS(aGenericProperties);
1634         return uno::Sequence< beans::Property > ( aGenericProperties, nProps );
1635     }
1636 
getCommands(const uno::Reference<ucb::XCommandEnvironment> & xEnv)1637     uno::Sequence< ucb::CommandInfo > Content::getCommands(
1638             const uno::Reference< ucb::XCommandEnvironment > & xEnv )
1639     {
1640         static const ucb::CommandInfo aCommandInfoTable[] =
1641         {
1642             // Required commands
1643             ucb::CommandInfo
1644             ( "getCommandInfo",
1645               -1, cppu::UnoType<void>::get() ),
1646             ucb::CommandInfo
1647             ( "getPropertySetInfo",
1648               -1, cppu::UnoType<void>::get() ),
1649             ucb::CommandInfo
1650             ( "getPropertyValues",
1651               -1, cppu::UnoType<uno::Sequence< beans::Property >>::get() ),
1652             ucb::CommandInfo
1653             ( "setPropertyValues",
1654               -1, cppu::UnoType<uno::Sequence< beans::PropertyValue >>::get() ),
1655 
1656             // Optional standard commands
1657             ucb::CommandInfo
1658             ( "delete",
1659               -1, cppu::UnoType<bool>::get() ),
1660             ucb::CommandInfo
1661             ( "insert",
1662               -1, cppu::UnoType<ucb::InsertCommandArgument2>::get() ),
1663             ucb::CommandInfo
1664             ( "open",
1665               -1, cppu::UnoType<ucb::OpenCommandArgument2>::get() ),
1666 
1667             // Mandatory CMIS-only commands
1668             ucb::CommandInfo ( "checkout", -1, cppu::UnoType<void>::get() ),
1669             ucb::CommandInfo ( "cancelCheckout", -1, cppu::UnoType<void>::get() ),
1670             ucb::CommandInfo ( "checkIn", -1,
1671                     cppu::UnoType<ucb::TransferInfo>::get() ),
1672             ucb::CommandInfo ( "updateProperties", -1, cppu::UnoType<void>::get() ),
1673             ucb::CommandInfo
1674             ( "getAllVersions",
1675               -1, cppu::UnoType<uno::Sequence< document::CmisVersion >>::get() ),
1676 
1677 
1678             // Folder Only, omitted if not a folder
1679             ucb::CommandInfo
1680             ( "transfer",
1681               -1, cppu::UnoType<ucb::TransferInfo>::get() ),
1682             ucb::CommandInfo
1683             ( "createNewContent",
1684               -1, cppu::UnoType<ucb::ContentInfo>::get() )
1685         };
1686 
1687         const int nProps = SAL_N_ELEMENTS( aCommandInfoTable );
1688         return uno::Sequence< ucb::CommandInfo >(aCommandInfoTable, isFolder( xEnv ) ? nProps : nProps - 2);
1689     }
1690 
getParentURL()1691     OUString Content::getParentURL( )
1692     {
1693         SAL_INFO( "ucb.ucp.cmis", "Content::getParentURL()" );
1694         OUString parentUrl = "/";
1695         if ( m_sObjectPath == "/" )
1696             return parentUrl;
1697         else
1698         {
1699             INetURLObject aUrl( m_sURL );
1700             if ( aUrl.getSegmentCount( ) > 0 )
1701             {
1702                 URL aCmisUrl( m_sURL );
1703                 aUrl.removeSegment( );
1704                 aCmisUrl.setObjectPath( aUrl.GetURLPath( INetURLObject::DecodeMechanism::WithCharset ) );
1705                 parentUrl = aCmisUrl.asString( );
1706             }
1707         }
1708         return parentUrl;
1709     }
1710 
1711     XTYPEPROVIDER_COMMON_IMPL( Content );
1712 
acquire()1713     void SAL_CALL Content::acquire() noexcept
1714     {
1715         ContentImplHelper::acquire();
1716     }
1717 
release()1718     void SAL_CALL Content::release() noexcept
1719     {
1720         ContentImplHelper::release();
1721     }
1722 
queryInterface(const uno::Type & rType)1723     uno::Any SAL_CALL Content::queryInterface( const uno::Type & rType )
1724     {
1725         uno::Any aRet = cppu::queryInterface( rType, static_cast< ucb::XContentCreator * >( this ) );
1726         return aRet.hasValue() ? aRet : ContentImplHelper::queryInterface(rType);
1727     }
1728 
getImplementationName()1729     OUString SAL_CALL Content::getImplementationName()
1730     {
1731        return "com.sun.star.comp.CmisContent";
1732     }
1733 
getSupportedServiceNames()1734     uno::Sequence< OUString > SAL_CALL Content::getSupportedServiceNames()
1735     {
1736            uno::Sequence<OUString> aSNS { "com.sun.star.ucb.CmisContent" };
1737            return aSNS;
1738     }
1739 
getContentType()1740     OUString SAL_CALL Content::getContentType()
1741     {
1742         OUString sRet;
1743         try
1744         {
1745             sRet = isFolder( uno::Reference< ucb::XCommandEnvironment >() )
1746                 ? std::u16string_view(u"" CMIS_FOLDER_TYPE)
1747                 : std::u16string_view(u"" CMIS_FILE_TYPE);
1748         }
1749         catch (const uno::RuntimeException&)
1750         {
1751             throw;
1752         }
1753         catch (const uno::Exception& e)
1754         {
1755             uno::Any a(cppu::getCaughtException());
1756             throw lang::WrappedTargetRuntimeException(
1757                 "wrapped Exception " + e.Message,
1758                 uno::Reference<uno::XInterface>(), a);
1759         }
1760         return sRet;
1761     }
1762 
execute(const ucb::Command & aCommand,sal_Int32,const uno::Reference<ucb::XCommandEnvironment> & xEnv)1763     uno::Any SAL_CALL Content::execute(
1764         const ucb::Command& aCommand,
1765         sal_Int32 /*CommandId*/,
1766         const uno::Reference< ucb::XCommandEnvironment >& xEnv )
1767     {
1768         SAL_INFO( "ucb.ucp.cmis", "Content::execute( ) - " << aCommand.Name );
1769         uno::Any aRet;
1770 
1771         if ( aCommand.Name == "getPropertyValues" )
1772         {
1773             uno::Sequence< beans::Property > Properties;
1774             if ( !( aCommand.Argument >>= Properties ) )
1775                 ucbhelper::cancelCommandExecution ( getBadArgExcept (), xEnv );
1776             aRet <<= getPropertyValues( Properties, xEnv );
1777         }
1778         else if ( aCommand.Name == "getPropertySetInfo" )
1779             aRet <<= getPropertySetInfo( xEnv, false );
1780         else if ( aCommand.Name == "getCommandInfo" )
1781             aRet <<= getCommandInfo( xEnv, false );
1782         else if ( aCommand.Name == "open" )
1783         {
1784             ucb::OpenCommandArgument2 aOpenCommand;
1785             if ( !( aCommand.Argument >>= aOpenCommand ) )
1786                 ucbhelper::cancelCommandExecution ( getBadArgExcept (), xEnv );
1787             aRet = open( aOpenCommand, xEnv );
1788         }
1789         else if ( aCommand.Name == "transfer" )
1790         {
1791             ucb::TransferInfo transferArgs;
1792             if ( !( aCommand.Argument >>= transferArgs ) )
1793                 ucbhelper::cancelCommandExecution ( getBadArgExcept (), xEnv );
1794             transfer( transferArgs, xEnv );
1795         }
1796         else if ( aCommand.Name == "setPropertyValues" )
1797         {
1798             uno::Sequence< beans::PropertyValue > aProperties;
1799             if ( !( aCommand.Argument >>= aProperties ) || !aProperties.hasElements() )
1800                 ucbhelper::cancelCommandExecution ( getBadArgExcept (), xEnv );
1801             aRet <<= setPropertyValues( aProperties, xEnv );
1802         }
1803         else if (aCommand.Name == "createNewContent"
1804                  && isFolder( xEnv ) )
1805         {
1806             ucb::ContentInfo arg;
1807             if ( !( aCommand.Argument >>= arg ) )
1808                     ucbhelper::cancelCommandExecution ( getBadArgExcept (), xEnv );
1809             aRet <<= createNewContent( arg );
1810         }
1811         else if ( aCommand.Name == "insert" )
1812         {
1813             ucb::InsertCommandArgument2 arg;
1814             if ( !( aCommand.Argument >>= arg ) )
1815             {
1816                 ucb::InsertCommandArgument insertArg;
1817                 if ( !( aCommand.Argument >>= insertArg ) )
1818                     ucbhelper::cancelCommandExecution ( getBadArgExcept (), xEnv );
1819 
1820                 arg.Data = insertArg.Data;
1821                 arg.ReplaceExisting = insertArg.ReplaceExisting;
1822             }
1823             // store the document id
1824             m_sObjectId = arg.DocumentId;
1825             insert( arg.Data, arg.ReplaceExisting, arg.MimeType, xEnv );
1826         }
1827         else if ( aCommand.Name == "delete" )
1828         {
1829             try
1830             {
1831                 if ( !isFolder( xEnv ) )
1832                 {
1833                     getObject( xEnv )->remove( );
1834                 }
1835                 else
1836                 {
1837                     libcmis::Folder* folder = dynamic_cast< libcmis::Folder* >( getObject( xEnv ).get() );
1838                     if (folder)
1839                         folder->removeTree( );
1840                 }
1841             }
1842             catch ( const libcmis::Exception& e )
1843             {
1844                 SAL_INFO( "ucb.ucp.cmis", "Unexpected libcmis exception: " << e.what( ) );
1845                 ucbhelper::cancelCommandExecution(
1846                                     ucb::IOErrorCode_GENERAL,
1847                                     uno::Sequence< uno::Any >( 0 ),
1848                                     xEnv,
1849                                     o3tl::runtimeToOUString(e.what()));
1850             }
1851         }
1852         else if ( aCommand.Name == "checkout" )
1853         {
1854             aRet <<= checkOut( xEnv );
1855         }
1856         else if ( aCommand.Name == "cancelCheckout" )
1857         {
1858             aRet <<= cancelCheckOut( xEnv );
1859         }
1860         else if ( aCommand.Name == "checkin" )
1861         {
1862             ucb::CheckinArgument aArg;
1863             if ( !( aCommand.Argument >>= aArg ) )
1864             {
1865                 ucbhelper::cancelCommandExecution ( getBadArgExcept(), xEnv );
1866             }
1867             aRet <<= checkIn( aArg, xEnv );
1868         }
1869         else if ( aCommand.Name == "getAllVersions" )
1870         {
1871             aRet <<= getAllVersions( xEnv );
1872         }
1873         else if ( aCommand.Name == "updateProperties" )
1874         {
1875             updateProperties( aCommand.Argument, xEnv );
1876         }
1877         else
1878         {
1879             SAL_INFO( "ucb.ucp.cmis", "Unknown command to execute" );
1880 
1881             ucbhelper::cancelCommandExecution
1882                 ( uno::makeAny( ucb::UnsupportedCommandException
1883                   ( OUString(),
1884                     static_cast< cppu::OWeakObject * >( this ) ) ),
1885                   xEnv );
1886         }
1887 
1888         return aRet;
1889     }
1890 
abort(sal_Int32)1891     void SAL_CALL Content::abort( sal_Int32 /*CommandId*/ )
1892     {
1893         SAL_INFO( "ucb.ucp.cmis", "TODO - Content::abort()" );
1894         // TODO Implement me
1895     }
1896 
queryCreatableContentsInfo()1897     uno::Sequence< ucb::ContentInfo > SAL_CALL Content::queryCreatableContentsInfo()
1898     {
1899         return queryCreatableContentsInfo( uno::Reference< ucb::XCommandEnvironment >() );
1900     }
1901 
createNewContent(const ucb::ContentInfo & Info)1902     uno::Reference< ucb::XContent > SAL_CALL Content::createNewContent(
1903             const ucb::ContentInfo& Info )
1904     {
1905         bool create_document;
1906 
1907         if ( Info.Type == CMIS_FILE_TYPE )
1908             create_document = true;
1909         else if ( Info.Type == CMIS_FOLDER_TYPE )
1910             create_document = false;
1911         else
1912         {
1913             SAL_INFO( "ucb.ucp.cmis", "Unknown type of content to create" );
1914             return uno::Reference< ucb::XContent >();
1915         }
1916 
1917         OUString sParentURL = m_xIdentifier->getContentIdentifier();
1918 
1919         // Set the parent URL for the transient objects
1920         uno::Reference< ucb::XContentIdentifier > xId(new ::ucbhelper::ContentIdentifier(sParentURL));
1921 
1922         try
1923         {
1924             return new ::cmis::Content( m_xContext, m_pProvider, xId, !create_document );
1925         }
1926         catch ( ucb::ContentCreationException & )
1927         {
1928             return uno::Reference< ucb::XContent >();
1929         }
1930     }
1931 
getTypes()1932     uno::Sequence< uno::Type > SAL_CALL Content::getTypes()
1933     {
1934         try
1935         {
1936             if ( isFolder( uno::Reference< ucb::XCommandEnvironment >() ) )
1937             {
1938                 static cppu::OTypeCollection s_aFolderCollection
1939                     (CPPU_TYPE_REF( lang::XTypeProvider ),
1940                      CPPU_TYPE_REF( lang::XServiceInfo ),
1941                      CPPU_TYPE_REF( lang::XComponent ),
1942                      CPPU_TYPE_REF( ucb::XContent ),
1943                      CPPU_TYPE_REF( ucb::XCommandProcessor ),
1944                      CPPU_TYPE_REF( beans::XPropertiesChangeNotifier ),
1945                      CPPU_TYPE_REF( ucb::XCommandInfoChangeNotifier ),
1946                      CPPU_TYPE_REF( beans::XPropertyContainer ),
1947                      CPPU_TYPE_REF( beans::XPropertySetInfoChangeNotifier ),
1948                      CPPU_TYPE_REF( container::XChild ),
1949                      CPPU_TYPE_REF( ucb::XContentCreator ) );
1950                 return s_aFolderCollection.getTypes();
1951             }
1952         }
1953         catch (const uno::RuntimeException&)
1954         {
1955             throw;
1956         }
1957         catch (const uno::Exception& e)
1958         {
1959             uno::Any a(cppu::getCaughtException());
1960             throw lang::WrappedTargetRuntimeException(
1961                 "wrapped Exception " + e.Message,
1962                 uno::Reference<uno::XInterface>(), a);
1963         }
1964 
1965         static cppu::OTypeCollection s_aFileCollection
1966             (CPPU_TYPE_REF( lang::XTypeProvider ),
1967              CPPU_TYPE_REF( lang::XServiceInfo ),
1968              CPPU_TYPE_REF( lang::XComponent ),
1969              CPPU_TYPE_REF( ucb::XContent ),
1970              CPPU_TYPE_REF( ucb::XCommandProcessor ),
1971              CPPU_TYPE_REF( beans::XPropertiesChangeNotifier ),
1972              CPPU_TYPE_REF( ucb::XCommandInfoChangeNotifier ),
1973              CPPU_TYPE_REF( beans::XPropertyContainer ),
1974              CPPU_TYPE_REF( beans::XPropertySetInfoChangeNotifier ),
1975              CPPU_TYPE_REF( container::XChild ) );
1976 
1977         return s_aFileCollection.getTypes();
1978     }
1979 
queryCreatableContentsInfo(const uno::Reference<ucb::XCommandEnvironment> & xEnv)1980     uno::Sequence< ucb::ContentInfo > Content::queryCreatableContentsInfo(
1981         const uno::Reference< ucb::XCommandEnvironment >& xEnv)
1982     {
1983         try
1984         {
1985             if ( isFolder( xEnv ) )
1986             {
1987 
1988                 // Minimum set of props we really need
1989                 uno::Sequence< beans::Property > props
1990                 {
1991                     {
1992                         "Title",
1993                         -1,
1994                         cppu::UnoType<OUString>::get(),
1995                         beans::PropertyAttribute::MAYBEVOID | beans::PropertyAttribute::BOUND
1996                     }
1997                 };
1998 
1999                 return
2000                 {
2001                     {
2002                         CMIS_FILE_TYPE,
2003                         ( ucb::ContentInfoAttribute::INSERT_WITH_INPUTSTREAM |
2004                                       ucb::ContentInfoAttribute::KIND_DOCUMENT ),
2005                         props
2006                     },
2007                     {
2008                         CMIS_FOLDER_TYPE,
2009                         ucb::ContentInfoAttribute::KIND_FOLDER,
2010                         props
2011                     }
2012                 };
2013             }
2014         }
2015         catch (const uno::RuntimeException&)
2016         {
2017             throw;
2018         }
2019         catch (const uno::Exception& e)
2020         {
2021             uno::Any a(cppu::getCaughtException());
2022             throw lang::WrappedTargetRuntimeException(
2023                 "wrapped Exception " + e.Message,
2024                 uno::Reference<uno::XInterface>(), a);
2025         }
2026         return {};
2027     }
2028 
getChildren()2029     std::vector< uno::Reference< ucb::XContent > > Content::getChildren( )
2030     {
2031         std::vector< uno::Reference< ucb::XContent > > results;
2032         SAL_INFO( "ucb.ucp.cmis", "Content::getChildren() " << m_sURL );
2033 
2034         libcmis::FolderPtr pFolder = boost::dynamic_pointer_cast< libcmis::Folder >( getObject( uno::Reference< ucb::XCommandEnvironment >() ) );
2035         if ( nullptr != pFolder )
2036         {
2037             // Get the children from pObject
2038             try
2039             {
2040                 vector< libcmis::ObjectPtr > children = pFolder->getChildren( );
2041 
2042                 // Loop over the results
2043                 for ( const auto& rChild : children )
2044                 {
2045                     // TODO Cache the objects
2046 
2047                     INetURLObject aURL( m_sURL );
2048                     OUString sUser = aURL.GetUser( INetURLObject::DecodeMechanism::WithCharset );
2049 
2050                     URL aUrl( m_sURL );
2051                     OUString sPath( m_sObjectPath );
2052                     if ( !sPath.endsWith("/") )
2053                         sPath += "/";
2054                     sPath += STD_TO_OUSTR( rChild->getName( ) );
2055                     OUString sId = STD_TO_OUSTR( rChild->getId( ) );
2056 
2057                     aUrl.setObjectId( sId );
2058                     aUrl.setObjectPath( sPath );
2059                     aUrl.setUsername( sUser );
2060 
2061                     uno::Reference< ucb::XContentIdentifier > xId = new ucbhelper::ContentIdentifier( aUrl.asString( ) );
2062                     uno::Reference< ucb::XContent > xContent = new Content( m_xContext, m_pProvider, xId, rChild );
2063 
2064                     results.push_back( xContent );
2065                 }
2066             }
2067             catch ( const libcmis::Exception& e )
2068             {
2069                 SAL_INFO( "ucb.ucp.cmis", "Exception thrown: " << e.what() );
2070             }
2071         }
2072 
2073         return results;
2074     }
2075 
setCmisProperty(const std::string & rName,const std::string & rValue,const uno::Reference<ucb::XCommandEnvironment> & xEnv)2076     void Content::setCmisProperty(const std::string& rName, const std::string& rValue, const uno::Reference< ucb::XCommandEnvironment >& xEnv )
2077     {
2078         if ( !getObjectType( xEnv ).get( ) )
2079             return;
2080 
2081         map< string, libcmis::PropertyPtr >::iterator propIt = m_pObjectProps.find(rName);
2082         vector< string > values;
2083         values.push_back(rValue);
2084 
2085         if ( propIt == m_pObjectProps.end( ) && getObjectType( xEnv ).get( ) )
2086         {
2087             map< string, libcmis::PropertyTypePtr > propsTypes = getObjectType( xEnv )->getPropertiesTypes( );
2088             map< string, libcmis::PropertyTypePtr >::iterator typeIt = propsTypes.find(rName);
2089 
2090             if ( typeIt != propsTypes.end( ) )
2091             {
2092                 libcmis::PropertyTypePtr propType = typeIt->second;
2093                 libcmis::PropertyPtr property( new libcmis::Property( propType, values ) );
2094                 m_pObjectProps.insert(pair< string, libcmis::PropertyPtr >(rName, property));
2095             }
2096         }
2097         else if ( propIt != m_pObjectProps.end( ) )
2098         {
2099             propIt->second->setValues( values );
2100         }
2101     }
2102 }
2103 
2104 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */
2105