diff options
Diffstat (limited to 'ucb/source/ucp/cmis')
23 files changed, 4424 insertions, 0 deletions
diff --git a/ucb/source/ucp/cmis/auth_provider.cxx b/ucb/source/ucp/cmis/auth_provider.cxx new file mode 100644 index 0000000000..2e81cfb23b --- /dev/null +++ b/ucb/source/ucp/cmis/auth_provider.cxx @@ -0,0 +1,212 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ + +#define OUSTR_TO_STDSTR(s) std::string( OUStringToOString( s, RTL_TEXTENCODING_UTF8 ) ) +#define STD_TO_OUSTR( str ) OUString( str.c_str(), str.length( ), RTL_TEXTENCODING_UTF8 ) + +#include <com/sun/star/task/XInteractionHandler.hpp> +#include <com/sun/star/task/PasswordContainer.hpp> +#include <com/sun/star/task/XPasswordContainer2.hpp> + +#include <comphelper/processfactory.hxx> +#include <ucbhelper/simpleauthenticationrequest.hxx> +#include <ucbhelper/authenticationfallback.hxx> + +#include "auth_provider.hxx" + +using namespace com::sun::star; + +namespace cmis +{ + bool AuthProvider::authenticationQuery( std::string& username, std::string& password ) + { + if ( m_xEnv.is() ) + { + uno::Reference< task::XInteractionHandler > xIH + = m_xEnv->getInteractionHandler(); + + if ( xIH.is() ) + { + rtl::Reference< ucbhelper::SimpleAuthenticationRequest > xRequest + = new ucbhelper::SimpleAuthenticationRequest( + m_sUrl, m_sBindingUrl, OUString(), + STD_TO_OUSTR( username ), + STD_TO_OUSTR( password ), + false, false ); + xIH->handle( xRequest ); + + rtl::Reference< ucbhelper::InteractionContinuation > xSelection + = xRequest->getSelection(); + + if ( xSelection.is() ) + { + // Handler handled the request. + uno::Reference< task::XInteractionAbort > xAbort( + xSelection.get(), uno::UNO_QUERY ); + if ( !xAbort.is() ) + { + const rtl::Reference< + ucbhelper::InteractionSupplyAuthentication > & xSupp + = xRequest->getAuthenticationSupplier(); + + username = OUSTR_TO_STDSTR( xSupp->getUserName() ); + password = OUSTR_TO_STDSTR( xSupp->getPassword() ); + + return true; + } + } + } + } + return false; + } + + std::string AuthProvider::getRefreshToken(std::string& rUsername) + { + std::string refreshToken; + const css::uno::Reference<css::ucb::XCommandEnvironment> xEnv = getXEnv(); + if (xEnv.is()) + { + uno::Reference<task::XInteractionHandler> xIH = xEnv->getInteractionHandler(); + + if (rUsername.empty()) + { + rtl::Reference<ucbhelper::SimpleAuthenticationRequest> xRequest + = new ucbhelper::SimpleAuthenticationRequest( + m_sUrl, m_sBindingUrl, + ucbhelper::SimpleAuthenticationRequest::EntityType::ENTITY_NA, OUString(), + ucbhelper::SimpleAuthenticationRequest::EntityType::ENTITY_MODIFY, + STD_TO_OUSTR(rUsername), + ucbhelper::SimpleAuthenticationRequest::EntityType::ENTITY_NA, OUString()); + xIH->handle(xRequest); + + rtl::Reference<ucbhelper::InteractionContinuation> xSelection + = xRequest->getSelection(); + + if (xSelection.is()) + { + // Handler handled the request. + uno::Reference<task::XInteractionAbort> xAbort(xSelection.get(), + uno::UNO_QUERY); + if (!xAbort.is()) + { + const rtl::Reference<ucbhelper::InteractionSupplyAuthentication>& xSupp + = xRequest->getAuthenticationSupplier(); + + rUsername = OUSTR_TO_STDSTR(xSupp->getUserName()); + } + } + } + + uno::Reference<uno::XComponentContext> xContext + = ::comphelper::getProcessComponentContext(); + uno::Reference<task::XPasswordContainer2> xMasterPasswd + = task::PasswordContainer::create(xContext); + if (xMasterPasswd->hasMasterPassword()) + { + xMasterPasswd->authorizateWithMasterPassword(xIH); + } + if (xMasterPasswd->isPersistentStoringAllowed()) + { + task::UrlRecord aRec + = xMasterPasswd->findForName(m_sBindingUrl, STD_TO_OUSTR(rUsername), xIH); + if (aRec.UserList.hasElements() && aRec.UserList[0].Passwords.hasElements()) + refreshToken = OUSTR_TO_STDSTR(aRec.UserList[0].Passwords[0]); + } + } + return refreshToken; + } + + bool AuthProvider::storeRefreshToken(const std::string& username, const std::string& password, + const std::string& refreshToken) + { + if (refreshToken.empty()) + return false; + if (password == refreshToken) + return true; + const css::uno::Reference<css::ucb::XCommandEnvironment> xEnv = getXEnv(); + if (xEnv.is()) + { + uno::Reference<task::XInteractionHandler> xIH = xEnv->getInteractionHandler(); + uno::Reference<uno::XComponentContext> xContext + = ::comphelper::getProcessComponentContext(); + uno::Reference<task::XPasswordContainer2> xMasterPasswd + = task::PasswordContainer::create(xContext); + uno::Sequence<OUString> aPasswd{ STD_TO_OUSTR(refreshToken) }; + if (xMasterPasswd->isPersistentStoringAllowed()) + { + if (xMasterPasswd->hasMasterPassword()) + { + xMasterPasswd->authorizateWithMasterPassword(xIH); + } + xMasterPasswd->addPersistent(m_sBindingUrl, STD_TO_OUSTR(username), aPasswd, xIH); + return true; + } + } + return false; + } + + css::uno::WeakReference< css::ucb::XCommandEnvironment> AuthProvider::sm_xEnv; + + void AuthProvider::setXEnv(const css::uno::Reference< css::ucb::XCommandEnvironment>& xEnv ) + { + sm_xEnv = xEnv; + } + + css::uno::Reference< css::ucb::XCommandEnvironment> AuthProvider::getXEnv() + { + return sm_xEnv; + } + + char* AuthProvider::copyWebAuthCodeFallback( const char* url, + const char* /*username*/, + const char* /*password*/ ) + { + OUString url_oustr( url, strlen( url ), RTL_TEXTENCODING_UTF8 ); + const css::uno::Reference< + css::ucb::XCommandEnvironment> xEnv = getXEnv( ); + + if ( xEnv.is() ) + { + uno::Reference< task::XInteractionHandler > xIH + = xEnv->getInteractionHandler(); + + if ( xIH.is() ) + { + rtl::Reference< ucbhelper::AuthenticationFallbackRequest > xRequest + = new ucbhelper::AuthenticationFallbackRequest ( + "Open the following link in your browser and " + "paste the code from the URL you have been redirected to in the " + "box below. For example:\n" + "http://localhost/LibreOffice?code=YOUR_CODE", + url_oustr ); + + xIH->handle( xRequest ); + + rtl::Reference< ucbhelper::InteractionContinuation > xSelection + = xRequest->getSelection(); + + if ( xSelection.is() ) + { + // Handler handled the request. + const rtl::Reference< ucbhelper::InteractionAuthFallback >& + xAuthFallback = xRequest->getAuthFallbackInter( ); + if ( xAuthFallback.is() ) + { + OUString code = xAuthFallback->getCode( ); + return strdup( OUSTR_TO_STDSTR( code ).c_str( ) ); + } + } + } + } + + return strdup( "" ); + } +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/ucb/source/ucp/cmis/auth_provider.hxx b/ucb/source/ucp/cmis/auth_provider.hxx new file mode 100644 index 0000000000..af1a420c26 --- /dev/null +++ b/ucb/source/ucp/cmis/auth_provider.hxx @@ -0,0 +1,47 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ +#pragma once + +#include <libcmis/libcmis.hxx> + +#include <com/sun/star/ucb/XCommandEnvironment.hpp> +#include <cppuhelper/weakref.hxx> + +namespace cmis +{ + class AuthProvider : public libcmis::AuthProvider + { + const css::uno::Reference< css::ucb::XCommandEnvironment>& m_xEnv; + static css::uno::WeakReference< css::ucb::XCommandEnvironment> sm_xEnv; + OUString m_sUrl; + OUString m_sBindingUrl; + + public: + AuthProvider ( const css::uno::Reference< css::ucb::XCommandEnvironment> & xEnv, + OUString sUrl, + OUString sBindingUrl ): + m_xEnv( xEnv ), m_sUrl( std::move(sUrl) ), m_sBindingUrl( std::move(sBindingUrl) ) { } + + bool authenticationQuery( std::string& username, std::string& password ) override; + + std::string getRefreshToken( std::string& username ); + bool storeRefreshToken(const std::string& username, const std::string& password, + const std::string& refreshToken); + + static char* copyWebAuthCodeFallback( const char* url, + const char* /*username*/, + const char* /*password*/ ); + + static void setXEnv( const css::uno::Reference< css::ucb::XCommandEnvironment>& xEnv ); + static css::uno::Reference< css::ucb::XCommandEnvironment> getXEnv(); + + }; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/ucb/source/ucp/cmis/certvalidation_handler.cxx b/ucb/source/ucp/cmis/certvalidation_handler.cxx new file mode 100644 index 0000000000..0080df37a4 --- /dev/null +++ b/ucb/source/ucp/cmis/certvalidation_handler.cxx @@ -0,0 +1,126 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + */ + +#include <com/sun/star/security/CertificateContainer.hpp> +#include <com/sun/star/security/XCertificate.hpp> +#include <com/sun/star/security/XCertificateContainer.hpp> +#include <com/sun/star/xml/crypto/SEInitializer.hpp> +#include <com/sun/star/xml/crypto/XSecurityEnvironment.hpp> + +#include <rtl/ref.hxx> +#include <comphelper/sequence.hxx> +#include <ucbhelper/simplecertificatevalidationrequest.hxx> + +#include "certvalidation_handler.hxx" + +#define STD_TO_OUSTR( str ) OUString( str.c_str(), str.length( ), RTL_TEXTENCODING_UTF8 ) + +using namespace com::sun::star; + +namespace cmis +{ + bool CertValidationHandler::validateCertificate( std::vector< std::string > aCertificates ) + { + bool bValidate = false; + if ( !aCertificates.empty() && m_xEnv.is() ) + { + uno::Reference< xml::crypto::XSEInitializer > xSEInitializer; + try + { + xSEInitializer = xml::crypto::SEInitializer::create( m_xContext ); + } + catch ( uno::Exception const & ) + { + } + + if ( xSEInitializer.is() ) + { + uno::Reference< xml::crypto::XXMLSecurityContext > xSecurityContext( + xSEInitializer->createSecurityContext( OUString() ) ); + + uno::Reference< xml::crypto::XSecurityEnvironment > xSecurityEnv( + xSecurityContext->getSecurityEnvironment() ); + + std::vector< std::string >::iterator pIt = aCertificates.begin(); + std::string sCert = *pIt; + // We need to get rid of the PEM header/footer lines + OUString sCleanCert = STD_TO_OUSTR( sCert ); + sCleanCert = sCleanCert.replaceAll( "-----BEGIN CERTIFICATE-----", "" ); + sCleanCert = sCleanCert.replaceAll( "-----END CERTIFICATE-----", "" ); + uno::Reference< security::XCertificate > xCert( + xSecurityEnv->createCertificateFromAscii( + sCleanCert ) ); + + uno::Reference< security::XCertificateContainer > xCertificateContainer; + try + { + xCertificateContainer = security::CertificateContainer::create( m_xContext ); + } + catch ( uno::Exception const & ) + { + } + + if ( xCertificateContainer.is( ) ) + { + security::CertificateContainerStatus status( + xCertificateContainer->hasCertificate( + m_sHostname, xCert->getSubjectName() ) ); + + if ( status != security::CertificateContainerStatus_NOCERT ) + return status == security::CertificateContainerStatus_TRUSTED; + } + + // If we had no certificate, ask what to do + std::vector< uno::Reference< security::XCertificate > > vecCerts; + + for ( ++pIt; pIt != aCertificates.end(); ++pIt ) + { + sCert = *pIt; + uno::Reference< security::XCertificate> xImCert( + xSecurityEnv->createCertificateFromAscii( + STD_TO_OUSTR( sCert ) ) ); + if ( xImCert.is() ) + vecCerts.push_back( xImCert ); + } + + sal_Int64 certValidity = xSecurityEnv->verifyCertificate( xCert, + ::comphelper::containerToSequence( vecCerts ) ); + + uno::Reference< task::XInteractionHandler > xIH( + m_xEnv->getInteractionHandler() ); + if ( xIH.is() ) + { + rtl::Reference< ucbhelper::SimpleCertificateValidationRequest > + xRequest( new ucbhelper::SimpleCertificateValidationRequest( + sal_Int32( certValidity ), xCert, m_sHostname ) ); + xIH->handle( xRequest ); + rtl::Reference< ucbhelper::InteractionContinuation > xSelection + = xRequest->getSelection(); + + if ( xSelection.is() ) + { + uno::Reference< task::XInteractionApprove > xApprove( + xSelection.get(), uno::UNO_QUERY ); + bValidate = xApprove.is(); + + // Store the decision in the container + xCertificateContainer->addCertificate( + m_sHostname, xCert->getSubjectName(), bValidate ); + } + } + } + } + return bValidate; + } +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/ucb/source/ucp/cmis/certvalidation_handler.hxx b/ucb/source/ucp/cmis/certvalidation_handler.hxx new file mode 100644 index 0000000000..ae46c8397f --- /dev/null +++ b/ucb/source/ucp/cmis/certvalidation_handler.hxx @@ -0,0 +1,46 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + */ +#pragma once + +#if defined __GNUC__ && !defined __clang__ +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wdeprecated" +#pragma GCC diagnostic ignored "-Wunused-but-set-parameter" +#endif +#include <libcmis/libcmis.hxx> +#if defined __GNUC__ && !defined __clang__ +#pragma GCC diagnostic pop +#endif + +#include <com/sun/star/ucb/XCommandEnvironment.hpp> +#include <utility> + +namespace cmis +{ + class CertValidationHandler : public libcmis::CertValidationHandler + { + const css::uno::Reference< css::ucb::XCommandEnvironment>& m_xEnv; + const css::uno::Reference< css::uno::XComponentContext >& m_xContext; + OUString m_sHostname; + + public: + CertValidationHandler ( + const css::uno::Reference< css::ucb::XCommandEnvironment>& xEnv, + const css::uno::Reference< css::uno::XComponentContext>& xContext, + OUString sHostname ): + m_xEnv( xEnv ), m_xContext( xContext ), m_sHostname(std::move( sHostname )) { } + + bool validateCertificate( std::vector< std::string > certificates ) override; + }; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/ucb/source/ucp/cmis/children_provider.hxx b/ucb/source/ucp/cmis/children_provider.hxx new file mode 100644 index 0000000000..5a7348fb5f --- /dev/null +++ b/ucb/source/ucp/cmis/children_provider.hxx @@ -0,0 +1,26 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ +#pragma once + +#include <vector> + +#include <com/sun/star/ucb/XContent.hpp> + +namespace cmis +{ + class ChildrenProvider + { + public: + virtual ~ChildrenProvider( ) { }; + + virtual std::vector< css::uno::Reference< css::ucb::XContent > > getChildren( ) = 0; + }; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/ucb/source/ucp/cmis/cmis_content.cxx b/ucb/source/ucp/cmis/cmis_content.cxx new file mode 100644 index 0000000000..d07e389d2b --- /dev/null +++ b/ucb/source/ucp/cmis/cmis_content.cxx @@ -0,0 +1,2099 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ + +#include <string_view> + +#include <boost/make_shared.hpp> + +#include <com/sun/star/beans/IllegalTypeException.hpp> +#include <com/sun/star/beans/PropertyAttribute.hpp> +#include <com/sun/star/beans/PropertyValue.hpp> +#include <com/sun/star/beans/XPropertySetInfo.hpp> +#include <com/sun/star/document/CmisProperty.hpp> +#include <com/sun/star/io/XActiveDataSink.hpp> +#include <com/sun/star/io/XActiveDataStreamer.hpp> +#include <com/sun/star/lang/IllegalAccessException.hpp> +#include <com/sun/star/lang/IllegalArgumentException.hpp> +#include <com/sun/star/lang/WrappedTargetRuntimeException.hpp> +#include <com/sun/star/task/InteractionClassification.hpp> +#include <com/sun/star/ucb/ContentInfo.hpp> +#include <com/sun/star/ucb/ContentInfoAttribute.hpp> +#include <com/sun/star/ucb/InsertCommandArgument2.hpp> +#include <com/sun/star/ucb/InteractiveBadTransferURLException.hpp> +#include <com/sun/star/ucb/InteractiveAugmentedIOException.hpp> +#include <com/sun/star/ucb/MissingInputStreamException.hpp> +#include <com/sun/star/ucb/OpenMode.hpp> +#include <com/sun/star/ucb/UnsupportedCommandException.hpp> +#include <com/sun/star/ucb/UnsupportedDataSinkException.hpp> +#include <com/sun/star/ucb/UnsupportedOpenModeException.hpp> +#include <com/sun/star/ucb/XCommandInfo.hpp> +#include <com/sun/star/ucb/XDynamicResultSet.hpp> +#ifndef SYSTEM_CURL +#include <com/sun/star/xml/crypto/XDigestContext.hpp> +#include <com/sun/star/xml/crypto/DigestID.hpp> +#include <com/sun/star/xml/crypto/NSSInitializer.hpp> +#endif + +#include <comphelper/processfactory.hxx> +#include <comphelper/sequence.hxx> +#include <cppuhelper/exc_hlp.hxx> +#include <cppuhelper/queryinterface.hxx> +#include <config_oauth2.h> +#include <o3tl/runtimetooustring.hxx> +#include <sal/log.hxx> +#include <tools/urlobj.hxx> +#include <tools/long.hxx> +#include <ucbhelper/cancelcommandexecution.hxx> +#include <ucbhelper/content.hxx> +#include <ucbhelper/contentidentifier.hxx> +#include <ucbhelper/propertyvalueset.hxx> +#include <ucbhelper/proxydecider.hxx> +#include <ucbhelper/macros.hxx> +#include <sax/tools/converter.hxx> +#include <systools/curlinit.hxx> + +#include <utility> + +#include "auth_provider.hxx" +#include "certvalidation_handler.hxx" +#include "cmis_content.hxx" +#include "cmis_provider.hxx" +#include "cmis_resultset.hxx" +#include "cmis_strings.hxx" +#include "std_inputstream.hxx" +#include "std_outputstream.hxx" + +#define OUSTR_TO_STDSTR(s) std::string( OUStringToOString( s, RTL_TEXTENCODING_UTF8 ) ) +#define STD_TO_OUSTR( str ) OStringToOUString( str, RTL_TEXTENCODING_UTF8 ) + +using namespace com::sun::star; + +namespace +{ + util::DateTime lcl_boostToUnoTime(const boost::posix_time::ptime& boostTime) + { + util::DateTime unoTime; + unoTime.Year = boostTime.date().year(); + unoTime.Month = boostTime.date().month(); + unoTime.Day = boostTime.date().day(); + unoTime.Hours = boostTime.time_of_day().hours(); + unoTime.Minutes = boostTime.time_of_day().minutes(); + unoTime.Seconds = boostTime.time_of_day().seconds(); + + // TODO FIXME maybe we should compile with BOOST_DATE_TIME_POSIX_TIME_STD_CONFIG + // to actually get nanosecond precision in boostTime? + // use this way rather than total_nanos to avoid overflows with 32-bit long + const tools::Long ticks = boostTime.time_of_day().fractional_seconds(); + tools::Long nanoSeconds = ticks * ( 1000000000 / boost::posix_time::time_duration::ticks_per_second()); + + unoTime.NanoSeconds = nanoSeconds; + + return unoTime; + } + + uno::Any lcl_cmisPropertyToUno( const libcmis::PropertyPtr& pProperty ) + { + uno::Any aValue; + switch ( pProperty->getPropertyType( )->getType( ) ) + { + default: + case libcmis::PropertyType::String: + { + auto aCmisStrings = pProperty->getStrings( ); + uno::Sequence< OUString > aStrings( aCmisStrings.size( ) ); + OUString* aStringsArr = aStrings.getArray( ); + sal_Int32 i = 0; + for ( const auto& rCmisStr : aCmisStrings ) + { + aStringsArr[i++] = STD_TO_OUSTR( rCmisStr ); + } + aValue <<= aStrings; + } + break; + case libcmis::PropertyType::Integer: + { + auto aCmisLongs = pProperty->getLongs( ); + uno::Sequence< sal_Int64 > aLongs( aCmisLongs.size( ) ); + sal_Int64* aLongsArr = aLongs.getArray( ); + sal_Int32 i = 0; + for ( const auto& rCmisLong : aCmisLongs ) + { + aLongsArr[i++] = rCmisLong; + } + aValue <<= aLongs; + } + break; + case libcmis::PropertyType::Decimal: + { + auto aCmisDoubles = pProperty->getDoubles( ); + uno::Sequence< double > aDoubles = comphelper::containerToSequence(aCmisDoubles); + aValue <<= aDoubles; + } + break; + case libcmis::PropertyType::Bool: + { + auto aCmisBools = pProperty->getBools( ); + uno::Sequence< sal_Bool > aBools( aCmisBools.size( ) ); + sal_Bool* aBoolsArr = aBools.getArray( ); + sal_Int32 i = 0; + for ( bool bCmisBool : aCmisBools ) + { + aBoolsArr[i++] = bCmisBool; + } + aValue <<= aBools; + } + break; + case libcmis::PropertyType::DateTime: + { + auto aCmisTimes = pProperty->getDateTimes( ); + uno::Sequence< util::DateTime > aTimes( aCmisTimes.size( ) ); + util::DateTime* aTimesArr = aTimes.getArray( ); + sal_Int32 i = 0; + for ( const auto& rCmisTime : aCmisTimes ) + { + aTimesArr[i++] = lcl_boostToUnoTime( rCmisTime ); + } + aValue <<= aTimes; + } + break; + } + return aValue; + } + + libcmis::PropertyPtr lcl_unoToCmisProperty(const document::CmisProperty& prop ) + { + libcmis::PropertyTypePtr propertyType( new libcmis::PropertyType( ) ); + + OUString id = prop.Id; + OUString name = prop.Name; + bool bUpdatable = prop.Updatable; + bool bRequired = prop.Required; + bool bMultiValued = prop.MultiValued; + bool bOpenChoice = prop.OpenChoice; + uno::Any value = prop.Value; + std::vector< std::string > values; + + libcmis::PropertyType::Type type = libcmis::PropertyType::String; + if ( prop.Type == CMIS_TYPE_STRING ) + { + uno::Sequence< OUString > seqValue; + value >>= seqValue; + std::transform(std::cbegin(seqValue), std::cend(seqValue), std::back_inserter(values), + [](const OUString& rValue) -> std::string { return OUSTR_TO_STDSTR( rValue ); }); + type = libcmis::PropertyType::String; + } + else if ( prop.Type == CMIS_TYPE_BOOL ) + { + uno::Sequence< sal_Bool > seqValue; + value >>= seqValue; + std::transform(std::cbegin(seqValue), std::cend(seqValue), std::back_inserter(values), + [](const bool nValue) -> std::string { return std::string( OString::boolean( nValue ) ); }); + type = libcmis::PropertyType::Bool; + } + else if ( prop.Type == CMIS_TYPE_INTEGER ) + { + uno::Sequence< sal_Int64 > seqValue; + value >>= seqValue; + std::transform(std::cbegin(seqValue), std::cend(seqValue), std::back_inserter(values), + [](const sal_Int64 nValue) -> std::string { return std::string( OString::number( nValue ) ); }); + type = libcmis::PropertyType::Integer; + } + else if ( prop.Type == CMIS_TYPE_DECIMAL ) + { + uno::Sequence< double > seqValue; + value >>= seqValue; + std::transform(std::cbegin(seqValue), std::cend(seqValue), std::back_inserter(values), + [](const double fValue) -> std::string { return std::string( OString::number( fValue ) ); }); + type = libcmis::PropertyType::Decimal; + } + else if ( prop.Type == CMIS_TYPE_DATETIME ) + { + uno::Sequence< util::DateTime > seqValue; + value >>= seqValue; + std::transform(std::cbegin(seqValue), std::cend(seqValue), std::back_inserter(values), + [](const util::DateTime& rValue) -> std::string { + OUStringBuffer aBuffer; + ::sax::Converter::convertDateTime( aBuffer, rValue, nullptr ); + return OUSTR_TO_STDSTR( aBuffer ); + }); + type = libcmis::PropertyType::DateTime; + } + + propertyType->setId( OUSTR_TO_STDSTR( id )); + propertyType->setDisplayName( OUSTR_TO_STDSTR( name ) ); + propertyType->setUpdatable( bUpdatable ); + propertyType->setRequired( bRequired ); + propertyType->setMultiValued( bMultiValued ); + propertyType->setOpenChoice( bOpenChoice ); + propertyType->setType( type ); + + libcmis::PropertyPtr property( new libcmis::Property( propertyType, std::move(values) ) ); + + return property; + } + + uno::Sequence< uno::Any > generateErrorArguments( const cmis::URL & rURL ) + { + uno::Sequence< uno::Any > aArguments{ uno::Any(beans::PropertyValue( + "Binding URL", + - 1, + uno::Any( rURL.getBindingUrl() ), + beans::PropertyState_DIRECT_VALUE )), + uno::Any(beans::PropertyValue( + "Username", + -1, + uno::Any( rURL.getUsername() ), + beans::PropertyState_DIRECT_VALUE )), + uno::Any(beans::PropertyValue( + "Repository Id", + -1, + uno::Any( rURL.getRepositoryId() ), + beans::PropertyState_DIRECT_VALUE )) }; + + return aArguments; + } +} + +namespace cmis +{ + Content::Content( const uno::Reference< uno::XComponentContext >& rxContext, + ContentProvider *pProvider, const uno::Reference< ucb::XContentIdentifier >& Identifier, + libcmis::ObjectPtr pObject ) + : ContentImplHelper( rxContext, pProvider, Identifier ), + m_pProvider( pProvider ), + m_pSession( nullptr ), + m_pObject(std::move( pObject )), + m_sURL( Identifier->getContentIdentifier( ) ), + m_aURL( Identifier->getContentIdentifier( ) ), + m_bTransient( false ), + m_bIsFolder( false ) + { + SAL_INFO( "ucb.ucp.cmis", "Content::Content() " << m_sURL ); + + m_sObjectPath = m_aURL.getObjectPath( ); + m_sObjectId = m_aURL.getObjectId( ); + } + + Content::Content( const uno::Reference< uno::XComponentContext >& rxContext, ContentProvider *pProvider, + const uno::Reference< ucb::XContentIdentifier >& Identifier, + bool bIsFolder ) + : ContentImplHelper( rxContext, pProvider, Identifier ), + m_pProvider( pProvider ), + m_pSession( nullptr ), + m_sURL( Identifier->getContentIdentifier( ) ), + m_aURL( Identifier->getContentIdentifier( ) ), + m_bTransient( true ), + m_bIsFolder( bIsFolder ) + { + SAL_INFO( "ucb.ucp.cmis", "Content::Content() " << m_sURL ); + + m_sObjectPath = m_aURL.getObjectPath( ); + m_sObjectId = m_aURL.getObjectId( ); + } + + Content::~Content() + { + } + + libcmis::Session* Content::getSession( const uno::Reference< ucb::XCommandEnvironment >& xEnv ) + { + // Set the proxy if needed. We are doing that all times as the proxy data shouldn't be cached. + ucbhelper::InternetProxyDecider aProxyDecider( m_xContext ); + INetURLObject aBindingUrl( m_aURL.getBindingUrl( ) ); + const OUString sProxy = aProxyDecider.getProxy( + INetURLObject::GetScheme( aBindingUrl.GetProtocol( ) ), aBindingUrl.GetHost(), aBindingUrl.GetPort() ); + libcmis::SessionFactory::setProxySettings( OUSTR_TO_STDSTR( sProxy ), std::string(), std::string(), std::string() ); + + // Look for a cached session, key is binding url + repo id + OUString sSessionId = m_aURL.getBindingUrl( ) + m_aURL.getRepositoryId( ); + if ( nullptr == m_pSession ) + m_pSession = m_pProvider->getSession( sSessionId, m_aURL.getUsername( ) ); + + if ( nullptr == m_pSession ) + { +#ifndef SYSTEM_CURL + // Initialize NSS library to make sure libcmis (and curl) can access CACERTs using NSS + // when using internal libcurl. + uno::Reference< css::xml::crypto::XNSSInitializer > + xNSSInitializer = css::xml::crypto::NSSInitializer::create( m_xContext ); + + uno::Reference< css::xml::crypto::XDigestContext > xDigestContext( + xNSSInitializer->getDigestContext( css::xml::crypto::DigestID::SHA256, + uno::Sequence< beans::NamedValue >() ), + uno::UNO_SET_THROW ); +#endif + + // Set the SSL Validation handler + libcmis::CertValidationHandlerPtr certHandler( + new CertValidationHandler( xEnv, m_xContext, aBindingUrl.GetHost( ) ) ); + libcmis::SessionFactory::setCertificateValidationHandler( certHandler ); + + // init libcurl callback + libcmis::SessionFactory::setCurlInitProtocolsFunction(&::InitCurl_easy); + + // Get the auth credentials + AuthProvider aAuthProvider(xEnv, m_xIdentifier->getContentIdentifier(), m_aURL.getBindingUrl()); + AuthProvider::setXEnv( xEnv ); + + auto rUsername = OUSTR_TO_STDSTR( m_aURL.getUsername( ) ); + auto rPassword = OUSTR_TO_STDSTR( m_aURL.getPassword( ) ); + + bool bSkipInitialPWAuth = false; + if (m_aURL.getBindingUrl() == ONEDRIVE_BASE_URL + || m_aURL.getBindingUrl() == GDRIVE_BASE_URL) + { + // skip the initial username and pw-auth prompt, the only supported method is the + // auth-code-fallback one (login with your browser, copy code into the dialog) + // TODO: if LO were to listen on localhost for the request, it would be much nicer + // user experience + bSkipInitialPWAuth = true; + rPassword = aAuthProvider.getRefreshToken(rUsername); + } + + bool bIsDone = false; + + while ( !bIsDone ) + { + if (bSkipInitialPWAuth || aAuthProvider.authenticationQuery(rUsername, rPassword)) + { + // Initiate a CMIS session and register it as we found nothing + libcmis::OAuth2DataPtr oauth2Data; + if ( m_aURL.getBindingUrl( ) == GDRIVE_BASE_URL ) + { + // reset the skip, so user gets a chance to cancel + bSkipInitialPWAuth = false; + libcmis::SessionFactory::setOAuth2AuthCodeProvider(AuthProvider::copyWebAuthCodeFallback); + oauth2Data = boost::make_shared<libcmis::OAuth2Data>( + GDRIVE_AUTH_URL, GDRIVE_TOKEN_URL, + GDRIVE_SCOPE, GDRIVE_REDIRECT_URI, + GDRIVE_CLIENT_ID, GDRIVE_CLIENT_SECRET ); + } + if ( m_aURL.getBindingUrl().startsWith( ALFRESCO_CLOUD_BASE_URL ) ) + oauth2Data = boost::make_shared<libcmis::OAuth2Data>( + ALFRESCO_CLOUD_AUTH_URL, ALFRESCO_CLOUD_TOKEN_URL, + ALFRESCO_CLOUD_SCOPE, ALFRESCO_CLOUD_REDIRECT_URI, + ALFRESCO_CLOUD_CLIENT_ID, ALFRESCO_CLOUD_CLIENT_SECRET ); + if ( m_aURL.getBindingUrl( ) == ONEDRIVE_BASE_URL ) + { + // reset the skip, so user gets a chance to cancel + bSkipInitialPWAuth = false; + libcmis::SessionFactory::setOAuth2AuthCodeProvider(AuthProvider::copyWebAuthCodeFallback); + oauth2Data = boost::make_shared<libcmis::OAuth2Data>( + ONEDRIVE_AUTH_URL, ONEDRIVE_TOKEN_URL, + ONEDRIVE_SCOPE, ONEDRIVE_REDIRECT_URI, + ONEDRIVE_CLIENT_ID, ONEDRIVE_CLIENT_SECRET ); + } + try + { + m_pSession = libcmis::SessionFactory::createSession( + OUSTR_TO_STDSTR( m_aURL.getBindingUrl( ) ), + rUsername, rPassword, OUSTR_TO_STDSTR( m_aURL.getRepositoryId( ) ), false, oauth2Data ); + + if ( m_pSession == nullptr ) + { + // Fail: session was not created + ucbhelper::cancelCommandExecution( + ucb::IOErrorCode_INVALID_DEVICE, + generateErrorArguments(m_aURL), + xEnv); + } + else if ( m_pSession->getRepository() == nullptr ) + { + // Fail: no repository or repository is invalid + ucbhelper::cancelCommandExecution( + ucb::IOErrorCode_INVALID_DEVICE, + generateErrorArguments(m_aURL), + xEnv, + "error accessing a repository"); + } + else + { + m_pProvider->registerSession(sSessionId, m_aURL.getUsername( ), m_pSession); + if (m_aURL.getBindingUrl() == ONEDRIVE_BASE_URL + || m_aURL.getBindingUrl() == GDRIVE_BASE_URL) + { + aAuthProvider.storeRefreshToken(rUsername, rPassword, + m_pSession->getRefreshToken()); + } + } + + bIsDone = true; + } + catch( const libcmis::Exception & e ) + { + if ( e.getType() != "permissionDenied" ) + { + SAL_INFO("ucb.ucp.cmis", "Unexpected libcmis exception: " << e.what()); + throw; + } + } + } + else + { + // Silently fail as the user cancelled the authentication + ucbhelper::cancelCommandExecution( + ucb::IOErrorCode_ABORT, + uno::Sequence< uno::Any >( 0 ), + xEnv ); + throw uno::RuntimeException( ); + } + } + } + return m_pSession; + } + + libcmis::ObjectTypePtr const & Content::getObjectType( const uno::Reference< ucb::XCommandEnvironment >& xEnv ) + { + if ( nullptr == m_pObjectType.get( ) && m_bTransient ) + { + std::string typeId = m_bIsFolder ? "cmis:folder" : "cmis:document"; + // The type to create needs to be fetched from the possible children types + // defined in the parent folder. Then, we'll pick up the first one we find matching + // cmis:folder or cmis:document (depending what we need to create). + // The easy case will work in most cases, but not on some servers (like Lotus Live) + libcmis::Folder* pParent = nullptr; + bool bTypeRestricted = false; + try + { + pParent = dynamic_cast< libcmis::Folder* >( getObject( xEnv ).get( ) ); + } + catch ( const libcmis::Exception& ) + { + } + + if ( pParent ) + { + std::map< std::string, libcmis::PropertyPtr >& aProperties = pParent->getProperties( ); + std::map< std::string, libcmis::PropertyPtr >::iterator it = aProperties.find( "cmis:allowedChildObjectTypeIds" ); + if ( it != aProperties.end( ) ) + { + libcmis::PropertyPtr pProperty = it->second; + if ( pProperty ) + { + std::vector< std::string > typesIds = pProperty->getStrings( ); + for ( const auto& rType : typesIds ) + { + bTypeRestricted = true; + libcmis::ObjectTypePtr type = getSession( xEnv )->getType( rType ); + + // FIXME Improve performances by adding getBaseTypeId( ) method to libcmis + if ( type->getBaseType( )->getId( ) == typeId ) + { + m_pObjectType = type; + break; + } + } + } + } + } + + if ( !bTypeRestricted ) + m_pObjectType = getSession( xEnv )->getType( typeId ); + } + return m_pObjectType; + } + + + libcmis::ObjectPtr const & Content::getObject( const uno::Reference< ucb::XCommandEnvironment >& xEnv ) + { + // can't get the session for some reason + // the recent file opening at start up is an example. + try + { + if ( !getSession( xEnv ) ) + return m_pObject; + } + catch ( uno::RuntimeException& ) + { + return m_pObject; + } + if ( !m_pObject.get() ) + { + if ( !m_sObjectId.isEmpty( ) ) + { + try + { + m_pObject = getSession( xEnv )->getObject( OUSTR_TO_STDSTR( m_sObjectId ) ); + } + catch ( const libcmis::Exception& ) + { + SAL_INFO( "ucb.ucp.cmis", "object: " << OUSTR_TO_STDSTR(m_sObjectId)); + throw libcmis::Exception( "Object not found" ); + } + } + else if (!(m_sObjectPath.isEmpty() || m_sObjectPath == "/")) + { + try + { + m_pObject = getSession( xEnv )->getObjectByPath( OUSTR_TO_STDSTR( m_sObjectPath ) ); + } + catch ( const libcmis::Exception& ) + { + // In some cases, getting the object from the path doesn't work, + // but getting the parent from its path and the get the child in the list is OK. + // It's weird, but needed to handle case where the path isn't the folders/files + // names separated by '/' (as in Lotus Live) + INetURLObject aParentUrl( m_sURL ); + std::string sName = OUSTR_TO_STDSTR( aParentUrl.getName( INetURLObject::LAST_SEGMENT, true, INetURLObject::DecodeMechanism::WithCharset ) ); + aParentUrl.removeSegment( ); + OUString sParentUrl = aParentUrl.GetMainURL( INetURLObject::DecodeMechanism::NONE ); + // Avoid infinite recursion if sParentUrl == m_sURL + if (sParentUrl != m_sURL) + { + rtl::Reference<Content> xParent(new Content(m_xContext, m_pProvider, new ucbhelper::ContentIdentifier(sParentUrl))); + libcmis::FolderPtr pParentFolder = boost::dynamic_pointer_cast< libcmis::Folder >(xParent->getObject(xEnv)); + if (pParentFolder) + { + std::vector< libcmis::ObjectPtr > children = pParentFolder->getChildren(); + auto it = std::find_if(children.begin(), children.end(), + [&sName](const libcmis::ObjectPtr& rChild) { return rChild->getName() == sName; }); + if (it != children.end()) + m_pObject = *it; + } + } + + if ( !m_pObject ) + throw libcmis::Exception( "Object not found" ); + } + } + else + { + m_pObject = getSession( xEnv )->getRootFolder( ); + m_sObjectPath = "/"; + m_sObjectId = OUString( ); + } + } + + return m_pObject; + } + + bool Content::isFolder(const uno::Reference< ucb::XCommandEnvironment >& xEnv ) + { + bool bIsFolder = false; + try + { + libcmis::ObjectPtr obj = getObject( xEnv ); + if ( obj ) + bIsFolder = obj->getBaseType( ) == "cmis:folder"; + } + catch ( const libcmis::Exception& e ) + { + SAL_INFO( "ucb.ucp.cmis", "Unexpected libcmis exception: " << e.what( ) ); + + ucbhelper::cancelCommandExecution( + ucb::IOErrorCode_GENERAL, + uno::Sequence< uno::Any >( 0 ), + xEnv, + OUString::createFromAscii( e.what( ) ) ); + + } + return bIsFolder; + } + + uno::Any Content::getBadArgExcept() + { + return uno::Any( lang::IllegalArgumentException( + "Wrong argument type!", + getXWeak(), -1) ); + } + + libcmis::ObjectPtr Content::updateProperties( + const uno::Any& iCmisProps, + const uno::Reference< ucb::XCommandEnvironment >& xEnv ) + { + // Convert iCmisProps to Cmis Properties; + uno::Sequence< document::CmisProperty > aPropsSeq; + iCmisProps >>= aPropsSeq; + std::map< std::string, libcmis::PropertyPtr > aProperties; + + for ( const auto& rProp : std::as_const(aPropsSeq) ) + { + std::string id = OUSTR_TO_STDSTR( rProp.Id ); + libcmis::PropertyPtr prop = lcl_unoToCmisProperty( rProp ); + aProperties.insert( std::pair<std::string, libcmis::PropertyPtr>( id, prop ) ); + } + libcmis::ObjectPtr updateObj; + try + { + updateObj = getObject( xEnv )->updateProperties( aProperties ); + } + catch ( const libcmis::Exception& e ) + { + SAL_INFO( "ucb.ucp.cmis", "Unexpected libcmis exception: "<< e.what( ) ); + } + + return updateObj; + } + + uno::Reference< sdbc::XRow > Content::getPropertyValues( + const uno::Sequence< beans::Property >& rProperties, + const uno::Reference< ucb::XCommandEnvironment >& xEnv ) + { + rtl::Reference< ::ucbhelper::PropertyValueSet > xRow = new ::ucbhelper::PropertyValueSet( m_xContext ); + + for( const beans::Property& rProp : rProperties ) + { + try + { + if ( rProp.Name == "IsDocument" ) + { + try + { + libcmis::ObjectPtr obj = getObject( xEnv ); + if ( obj ) + xRow->appendBoolean( rProp, obj->getBaseType( ) == "cmis:document" ); + } + catch ( const libcmis::Exception& ) + { + if ( m_pObjectType.get( ) ) + xRow->appendBoolean( rProp, getObjectType( xEnv )->getBaseType()->getId( ) == "cmis:document" ); + else + xRow->appendVoid( rProp ); + } + } + else if ( rProp.Name == "IsFolder" ) + { + try + { + libcmis::ObjectPtr obj = getObject( xEnv ); + if ( obj ) + xRow->appendBoolean( rProp, obj->getBaseType( ) == "cmis:folder" ); + else + xRow->appendBoolean( rProp, false ); + } + catch ( const libcmis::Exception& ) + { + if ( m_pObjectType.get( ) ) + xRow->appendBoolean( rProp, getObjectType( xEnv )->getBaseType()->getId( ) == "cmis:folder" ); + else + xRow->appendVoid( rProp ); + } + } + else if ( rProp.Name == "Title" ) + { + OUString sTitle; + try + { + sTitle = STD_TO_OUSTR( getObject( xEnv )->getName() ); + } + catch ( const libcmis::Exception& ) + { + if ( !m_pObjectProps.empty() ) + { + std::map< std::string, libcmis::PropertyPtr >::iterator it = m_pObjectProps.find( "cmis:name" ); + if ( it != m_pObjectProps.end( ) ) + { + std::vector< std::string > values = it->second->getStrings( ); + if ( !values.empty() ) + sTitle = STD_TO_OUSTR( values.front( ) ); + } + } + } + + // Nothing worked... get it from the path + if ( sTitle.isEmpty( ) ) + { + OUString sPath = m_sObjectPath; + + // Get rid of the trailing slash problem + if ( sPath.endsWith("/") ) + sPath = sPath.copy( 0, sPath.getLength() - 1 ); + + // Get the last segment + sal_Int32 nPos = sPath.lastIndexOf( '/' ); + if ( nPos >= 0 ) + sTitle = sPath.copy( nPos + 1 ); + } + + if ( !sTitle.isEmpty( ) ) + xRow->appendString( rProp, sTitle ); + else + xRow->appendVoid( rProp ); + } + else if ( rProp.Name == "ObjectId" ) + { + OUString sId; + try + { + sId = STD_TO_OUSTR( getObject( xEnv )->getId() ); + } + catch ( const libcmis::Exception& ) + { + if ( !m_pObjectProps.empty() ) + { + std::map< std::string, libcmis::PropertyPtr >::iterator it = m_pObjectProps.find( "cmis:objectId" ); + if ( it != m_pObjectProps.end( ) ) + { + std::vector< std::string > values = it->second->getStrings( ); + if ( !values.empty() ) + sId = STD_TO_OUSTR( values.front( ) ); + } + } + } + + if ( !sId.isEmpty( ) ) + xRow->appendString( rProp, sId ); + else + xRow->appendVoid( rProp ); + } + else if ( rProp.Name == "TitleOnServer" ) + { + xRow->appendString( rProp, m_sObjectPath); + } + else if ( rProp.Name == "IsReadOnly" ) + { + boost::shared_ptr< libcmis::AllowableActions > allowableActions = getObject( xEnv )->getAllowableActions( ); + bool bReadOnly = false; + if ( !allowableActions->isAllowed( libcmis::ObjectAction::SetContentStream ) && + !allowableActions->isAllowed( libcmis::ObjectAction::CheckIn ) ) + bReadOnly = true; + + xRow->appendBoolean( rProp, bReadOnly ); + } + else if ( rProp.Name == "DateCreated" ) + { + util::DateTime aTime = lcl_boostToUnoTime( getObject( xEnv )->getCreationDate( ) ); + xRow->appendTimestamp( rProp, aTime ); + } + else if ( rProp.Name == "DateModified" ) + { + util::DateTime aTime = lcl_boostToUnoTime( getObject( xEnv )->getLastModificationDate( ) ); + xRow->appendTimestamp( rProp, aTime ); + } + else if ( rProp.Name == "Size" ) + { + try + { + libcmis::Document* document = dynamic_cast< libcmis::Document* >( getObject( xEnv ).get( ) ); + if ( nullptr != document ) + xRow->appendLong( rProp, document->getContentLength() ); + else + xRow->appendVoid( rProp ); + } + catch ( const libcmis::Exception& ) + { + xRow->appendVoid( rProp ); + } + } + else if ( rProp.Name == "CreatableContentsInfo" ) + { + xRow->appendObject( rProp, uno::Any( queryCreatableContentsInfo( xEnv ) ) ); + } + else if ( rProp.Name == "MediaType" ) + { + try + { + libcmis::Document* document = dynamic_cast< libcmis::Document* >( getObject( xEnv ).get( ) ); + if ( nullptr != document ) + xRow->appendString( rProp, STD_TO_OUSTR( document->getContentType() ) ); + else + xRow->appendVoid( rProp ); + } + catch ( const libcmis::Exception& ) + { + xRow->appendVoid( rProp ); + } + } + else if ( rProp.Name == "IsVolume" ) + { + xRow->appendBoolean( rProp, false ); + } + else if ( rProp.Name == "IsRemote" ) + { + xRow->appendBoolean( rProp, false ); + } + else if ( rProp.Name == "IsRemoveable" ) + { + xRow->appendBoolean( rProp, false ); + } + else if ( rProp.Name == "IsFloppy" ) + { + xRow->appendBoolean( rProp, false ); + } + else if ( rProp.Name == "IsCompactDisc" ) + { + xRow->appendBoolean( rProp, false ); + } + else if ( rProp.Name == "IsHidden" ) + { + xRow->appendBoolean( rProp, false ); + } + else if ( rProp.Name == "TargetURL" ) + { + xRow->appendString( rProp, "" ); + } + else if ( rProp.Name == "BaseURI" ) + { + xRow->appendString( rProp, m_aURL.getBindingUrl( ) ); + } + else if ( rProp.Name == "CmisProperties" ) + { + try + { + libcmis::ObjectPtr object = getObject( xEnv ); + std::map< std::string, libcmis::PropertyPtr >& aProperties = object->getProperties( ); + uno::Sequence< document::CmisProperty > aCmisProperties( aProperties.size( ) ); + document::CmisProperty* pCmisProps = aCmisProperties.getArray( ); + sal_Int32 i = 0; + for ( const auto& [sId, rProperty] : aProperties ) + { + auto sDisplayName = rProperty->getPropertyType()->getDisplayName( ); + bool bUpdatable = rProperty->getPropertyType()->isUpdatable( ); + bool bRequired = rProperty->getPropertyType()->isRequired( ); + bool bMultiValued = rProperty->getPropertyType()->isMultiValued(); + bool bOpenChoice = rProperty->getPropertyType()->isOpenChoice(); + + pCmisProps[i].Id = STD_TO_OUSTR( sId ); + pCmisProps[i].Name = STD_TO_OUSTR( sDisplayName ); + pCmisProps[i].Updatable = bUpdatable; + pCmisProps[i].Required = bRequired; + pCmisProps[i].MultiValued = bMultiValued; + pCmisProps[i].OpenChoice = bOpenChoice; + pCmisProps[i].Value = lcl_cmisPropertyToUno( rProperty ); + switch ( rProperty->getPropertyType( )->getType( ) ) + { + default: + case libcmis::PropertyType::String: + pCmisProps[i].Type = CMIS_TYPE_STRING; + break; + case libcmis::PropertyType::Integer: + pCmisProps[i].Type = CMIS_TYPE_INTEGER; + break; + case libcmis::PropertyType::Decimal: + pCmisProps[i].Type = CMIS_TYPE_DECIMAL; + break; + case libcmis::PropertyType::Bool: + pCmisProps[i].Type = CMIS_TYPE_BOOL; + break; + case libcmis::PropertyType::DateTime: + pCmisProps[i].Type = CMIS_TYPE_DATETIME; + break; + } + ++i; + } + xRow->appendObject( rProp.Name, uno::Any( aCmisProperties ) ); + } + catch ( const libcmis::Exception& ) + { + xRow->appendVoid( rProp ); + } + } + else if ( rProp.Name == "IsVersionable" ) + { + try + { + libcmis::ObjectPtr object = getObject( xEnv ); + bool bIsVersionable = object->getTypeDescription( )->isVersionable( ); + xRow->appendBoolean( rProp, bIsVersionable ); + } + catch ( const libcmis::Exception& ) + { + xRow->appendVoid( rProp ); + } + } + else if ( rProp.Name == "CanCheckOut" ) + { + try + { + libcmis::ObjectPtr pObject = getObject( xEnv ); + libcmis::AllowableActionsPtr aAllowables = pObject->getAllowableActions( ); + bool bAllowed = false; + if ( aAllowables ) + { + bAllowed = aAllowables->isAllowed( libcmis::ObjectAction::CheckOut ); + } + xRow->appendBoolean( rProp, bAllowed ); + } + catch ( const libcmis::Exception& ) + { + xRow->appendVoid( rProp ); + } + } + else if ( rProp.Name == "CanCancelCheckOut" ) + { + try + { + libcmis::ObjectPtr pObject = getObject( xEnv ); + libcmis::AllowableActionsPtr aAllowables = pObject->getAllowableActions( ); + bool bAllowed = false; + if ( aAllowables ) + { + bAllowed = aAllowables->isAllowed( libcmis::ObjectAction::CancelCheckOut ); + } + xRow->appendBoolean( rProp, bAllowed ); + } + catch ( const libcmis::Exception& ) + { + xRow->appendVoid( rProp ); + } + } + else if ( rProp.Name == "CanCheckIn" ) + { + try + { + libcmis::ObjectPtr pObject = getObject( xEnv ); + libcmis::AllowableActionsPtr aAllowables = pObject->getAllowableActions( ); + bool bAllowed = false; + if ( aAllowables ) + { + bAllowed = aAllowables->isAllowed( libcmis::ObjectAction::CheckIn ); + } + xRow->appendBoolean( rProp, bAllowed ); + } + catch ( const libcmis::Exception& ) + { + xRow->appendVoid( rProp ); + } + } + else + SAL_INFO( "ucb.ucp.cmis", "Looking for unsupported property " << rProp.Name ); + } + catch (const libcmis::Exception&) + { + xRow->appendVoid( rProp ); + } + } + + return xRow; + } + + uno::Any Content::open(const ucb::OpenCommandArgument2 & rOpenCommand, + const uno::Reference< ucb::XCommandEnvironment > & xEnv ) + { + bool bIsFolder = isFolder( xEnv ); + + // Handle the case of the non-existing file + if ( !getObject( xEnv ) ) + { + uno::Sequence< uno::Any > aArgs{ uno::Any(m_xIdentifier->getContentIdentifier()) }; + uno::Any aErr( + ucb::InteractiveAugmentedIOException(OUString(), getXWeak(), + task::InteractionClassification_ERROR, + bIsFolder ? ucb::IOErrorCode_NOT_EXISTING_PATH : ucb::IOErrorCode_NOT_EXISTING, aArgs) + ); + + ucbhelper::cancelCommandExecution(aErr, xEnv); + } + + uno::Any aRet; + + bool bOpenFolder = ( + ( rOpenCommand.Mode == ucb::OpenMode::ALL ) || + ( rOpenCommand.Mode == ucb::OpenMode::FOLDERS ) || + ( rOpenCommand.Mode == ucb::OpenMode::DOCUMENTS ) + ); + + if ( bOpenFolder && bIsFolder ) + { + uno::Reference< ucb::XDynamicResultSet > xSet + = new DynamicResultSet(m_xContext, this, rOpenCommand, xEnv ); + aRet <<= xSet; + } + else if ( rOpenCommand.Sink.is() ) + { + if ( + ( rOpenCommand.Mode == ucb::OpenMode::DOCUMENT_SHARE_DENY_NONE ) || + ( rOpenCommand.Mode == ucb::OpenMode::DOCUMENT_SHARE_DENY_WRITE ) + ) + { + ucbhelper::cancelCommandExecution( + uno::Any ( ucb::UnsupportedOpenModeException + ( OUString(), getXWeak(), + sal_Int16( rOpenCommand.Mode ) ) ), + xEnv ); + } + + if ( !feedSink( rOpenCommand.Sink, xEnv ) ) + { + // Note: rOpenCommand.Sink may contain an XStream + // implementation. Support for this type of + // sink is optional... + SAL_INFO( "ucb.ucp.cmis", "Failed to copy data to sink" ); + + ucbhelper::cancelCommandExecution( + uno::Any (ucb::UnsupportedDataSinkException + ( OUString(), getXWeak(), + rOpenCommand.Sink ) ), + xEnv ); + } + } + else + SAL_INFO( "ucb.ucp.cmis", "Open falling through ..." ); + + return aRet; + } + + OUString Content::checkIn( const ucb::CheckinArgument& rArg, + const uno::Reference< ucb::XCommandEnvironment > & xEnv ) + { + ucbhelper::Content aSourceContent( rArg.SourceURL, xEnv, comphelper::getProcessComponentContext( ) ); + uno::Reference< io::XInputStream > xIn = aSourceContent.openStream( ); + + libcmis::ObjectPtr object; + try + { + object = getObject( xEnv ); + } + catch ( const libcmis::Exception& e ) + { + SAL_INFO( "ucb.ucp.cmis", "Unexpected libcmis exception: " << e.what( ) ); + ucbhelper::cancelCommandExecution( + ucb::IOErrorCode_GENERAL, + uno::Sequence< uno::Any >( 0 ), + xEnv, + OUString::createFromAscii( e.what() ) ); + } + + libcmis::Document* pPwc = dynamic_cast< libcmis::Document* >( object.get( ) ); + if ( !pPwc ) + { + ucbhelper::cancelCommandExecution( + ucb::IOErrorCode_GENERAL, + uno::Sequence< uno::Any >( 0 ), + xEnv, + "Checkin only supported by documents" ); + } + + boost::shared_ptr< std::ostream > pOut( new std::ostringstream ( std::ios_base::binary | std::ios_base::in | std::ios_base::out ) ); + uno::Reference < io::XOutputStream > xOutput = new StdOutputStream( pOut ); + copyData( xIn, xOutput ); + + std::map< std::string, libcmis::PropertyPtr > newProperties; + libcmis::DocumentPtr pDoc; + + try + { + pDoc = pPwc->checkIn( rArg.MajorVersion, OUSTR_TO_STDSTR( rArg.VersionComment ), newProperties, + pOut, OUSTR_TO_STDSTR( rArg.MimeType ), OUSTR_TO_STDSTR( rArg.NewTitle ) ); + } + catch ( const libcmis::Exception& e ) + { + SAL_INFO( "ucb.ucp.cmis", "Unexpected libcmis exception: " << e.what( ) ); + ucbhelper::cancelCommandExecution( + ucb::IOErrorCode_GENERAL, + uno::Sequence< uno::Any >( 0 ), + xEnv, + OUString::createFromAscii( e.what() ) ); + } + + // Get the URL and send it back as a result + URL aCmisUrl( m_sURL ); + std::vector< std::string > aPaths = pDoc->getPaths( ); + if ( !aPaths.empty() ) + { + aCmisUrl.setObjectPath(STD_TO_OUSTR(aPaths.front())); + } + else + { + // We may have unfiled document depending on the server, those + // won't have any path, use their ID instead + aCmisUrl.setObjectId(STD_TO_OUSTR(pDoc->getId())); + } + return aCmisUrl.asString( ); + } + + OUString Content::checkOut( const uno::Reference< ucb::XCommandEnvironment > & xEnv ) + { + OUString aRet; + try + { + // Checkout the document if possible + libcmis::DocumentPtr pDoc = boost::dynamic_pointer_cast< libcmis::Document >( getObject( xEnv ) ); + if ( pDoc.get( ) == nullptr ) + { + ucbhelper::cancelCommandExecution( + ucb::IOErrorCode_GENERAL, + uno::Sequence< uno::Any >( 0 ), + xEnv, + "Checkout only supported by documents" ); + } + libcmis::DocumentPtr pPwc = pDoc->checkOut( ); + + // Compute the URL of the Private Working Copy (PWC) + URL aCmisUrl( m_sURL ); + std::vector< std::string > aPaths = pPwc->getPaths( ); + if ( !aPaths.empty() ) + { + auto sPath = aPaths.front( ); + aCmisUrl.setObjectPath( STD_TO_OUSTR( sPath ) ); + } + else + { + // We may have unfiled PWC depending on the server, those + // won't have any path, use their ID instead + auto sId = pPwc->getId( ); + aCmisUrl.setObjectId( STD_TO_OUSTR( sId ) ); + } + aRet = aCmisUrl.asString( ); + } + catch ( const libcmis::Exception& e ) + { + SAL_INFO( "ucb.ucp.cmis", "Unexpected libcmis exception: " << e.what( ) ); + ucbhelper::cancelCommandExecution( + ucb::IOErrorCode_GENERAL, + uno::Sequence< uno::Any >( 0 ), + xEnv, + o3tl::runtimeToOUString(e.what())); + } + return aRet; + } + + OUString Content::cancelCheckOut( const uno::Reference< ucb::XCommandEnvironment > & xEnv ) + { + OUString aRet; + try + { + libcmis::DocumentPtr pPwc = boost::dynamic_pointer_cast< libcmis::Document >( getObject( xEnv ) ); + if ( pPwc.get( ) == nullptr ) + { + ucbhelper::cancelCommandExecution( + ucb::IOErrorCode_GENERAL, + uno::Sequence< uno::Any >( 0 ), + xEnv, + "CancelCheckout only supported by documents" ); + } + pPwc->cancelCheckout( ); + + // Get the Original document (latest version) + std::vector< libcmis::DocumentPtr > aVersions = pPwc->getAllVersions( ); + for ( const auto& rVersion : aVersions ) + { + libcmis::DocumentPtr pVersion = rVersion; + std::map< std::string, libcmis::PropertyPtr > aProps = pVersion->getProperties( ); + bool bIsLatestVersion = false; + std::map< std::string, libcmis::PropertyPtr >::iterator propIt = aProps.find( std::string( "cmis:isLatestVersion" ) ); + if ( propIt != aProps.end( ) && !propIt->second->getBools( ).empty( ) ) + { + bIsLatestVersion = propIt->second->getBools( ).front( ); + } + + if ( bIsLatestVersion ) + { + // Compute the URL of the Document + URL aCmisUrl( m_sURL ); + std::vector< std::string > aPaths = pVersion->getPaths( ); + if ( !aPaths.empty() ) + { + auto sPath = aPaths.front( ); + aCmisUrl.setObjectPath( STD_TO_OUSTR( sPath ) ); + } + else + { + // We may have unfiled doc depending on the server, those + // won't have any path, use their ID instead + auto sId = pVersion->getId( ); + aCmisUrl.setObjectId( STD_TO_OUSTR( sId ) ); + } + aRet = aCmisUrl.asString( ); + break; + } + } + } + catch ( const libcmis::Exception& e ) + { + SAL_INFO( "ucb.ucp.cmis", "Unexpected libcmis exception: " << e.what( ) ); + ucbhelper::cancelCommandExecution( + ucb::IOErrorCode_GENERAL, + uno::Sequence< uno::Any >( 0 ), + xEnv, + o3tl::runtimeToOUString(e.what())); + } + return aRet; + } + + uno::Sequence< document::CmisVersion> Content::getAllVersions( const uno::Reference< ucb::XCommandEnvironment > & xEnv ) + { + try + { + // get the document + libcmis::DocumentPtr pDoc = boost::dynamic_pointer_cast< libcmis::Document >( getObject( xEnv ) ); + if ( pDoc.get( ) == nullptr ) + { + ucbhelper::cancelCommandExecution( + ucb::IOErrorCode_GENERAL, + uno::Sequence< uno::Any >( 0 ), + xEnv, + "Can not get the document" ); + } + std::vector< libcmis::DocumentPtr > aCmisVersions = pDoc->getAllVersions( ); + uno::Sequence< document::CmisVersion > aVersions( aCmisVersions.size( ) ); + auto aVersionsRange = asNonConstRange(aVersions); + int i = 0; + for ( const auto& rVersion : aCmisVersions ) + { + libcmis::DocumentPtr pVersion = rVersion; + aVersionsRange[i].Id = STD_TO_OUSTR( pVersion->getId( ) ); + aVersionsRange[i].Author = STD_TO_OUSTR( pVersion->getCreatedBy( ) ); + aVersionsRange[i].TimeStamp = lcl_boostToUnoTime( pVersion->getLastModificationDate( ) ); + aVersionsRange[i].Comment = STD_TO_OUSTR( pVersion->getStringProperty("cmis:checkinComment") ); + ++i; + } + return aVersions; + } + catch ( const libcmis::Exception& e ) + { + SAL_INFO( "ucb.ucp.cmis", "Unexpected libcmis exception: " << e.what( ) ); + ucbhelper::cancelCommandExecution( + ucb::IOErrorCode_GENERAL, + uno::Sequence< uno::Any >( 0 ), + xEnv, + o3tl::runtimeToOUString(e.what())); + } + return uno::Sequence< document::CmisVersion > ( ); + } + + void Content::transfer( const ucb::TransferInfo& rTransferInfo, + const uno::Reference< ucb::XCommandEnvironment > & xEnv ) + { + // If the source isn't on the same CMIS repository, then simply copy + INetURLObject aSourceUrl( rTransferInfo.SourceURL ); + if ( aSourceUrl.GetProtocol() != INetProtocol::Cmis ) + { + OUString sSrcBindingUrl = URL( rTransferInfo.SourceURL ).getBindingUrl( ); + if ( sSrcBindingUrl != m_aURL.getBindingUrl( ) ) + { + ucbhelper::cancelCommandExecution( + uno::Any( + ucb::InteractiveBadTransferURLException( + "Unsupported URL scheme!", + getXWeak() ) ), + xEnv ); + } + } + + SAL_INFO( "ucb.ucp.cmis", "TODO - Content::transfer()" ); + } + + void Content::insert( const uno::Reference< io::XInputStream > & xInputStream, + bool bReplaceExisting, std::u16string_view rMimeType, + const uno::Reference< ucb::XCommandEnvironment >& xEnv ) + { + if ( !xInputStream.is() ) + { + ucbhelper::cancelCommandExecution( uno::Any + ( ucb::MissingInputStreamException + ( OUString(), getXWeak() ) ), + xEnv ); + } + + // For transient content, the URL is the one of the parent + if ( !m_bTransient ) + return; + + OUString sNewPath; + + // Try to get the object from the server if there is any + libcmis::FolderPtr pFolder; + try + { + pFolder = boost::dynamic_pointer_cast< libcmis::Folder >( getObject( xEnv ) ); + } + catch ( const libcmis::Exception& ) + { + } + + if ( pFolder == nullptr ) + return; + + libcmis::ObjectPtr object; + std::map< std::string, libcmis::PropertyPtr >::iterator it = m_pObjectProps.find( "cmis:name" ); + if ( it == m_pObjectProps.end( ) ) + { + ucbhelper::cancelCommandExecution( uno::Any + ( uno::RuntimeException( "Missing name property", + getXWeak() ) ), + xEnv ); + } + auto newName = it->second->getStrings( ).front( ); + auto newPath = OUSTR_TO_STDSTR( m_sObjectPath ); + if ( !newPath.empty( ) && newPath[ newPath.size( ) - 1 ] != '/' ) + newPath += "/"; + newPath += newName; + try + { + if ( !m_sObjectId.isEmpty( ) ) + object = getSession( xEnv )->getObject( OUSTR_TO_STDSTR( m_sObjectId) ); + else + object = getSession( xEnv )->getObjectByPath( newPath ); + sNewPath = STD_TO_OUSTR( newPath ); + } + catch ( const libcmis::Exception& ) + { + // Nothing matched the path + } + + if ( nullptr != object.get( ) ) + { + // Are the base type matching? + if ( object->getBaseType( ) != m_pObjectType->getBaseType( )->getId() ) + { + ucbhelper::cancelCommandExecution( uno::Any + ( uno::RuntimeException( "Can't change a folder into a document and vice-versa.", + getXWeak() ) ), + xEnv ); + } + + // Update the existing object if it's a document + libcmis::Document* document = dynamic_cast< libcmis::Document* >( object.get( ) ); + if ( nullptr != document ) + { + boost::shared_ptr< std::ostream > pOut( new std::ostringstream ( std::ios_base::binary | std::ios_base::in | std::ios_base::out ) ); + uno::Reference < io::XOutputStream > xOutput = new StdOutputStream( pOut ); + copyData( xInputStream, xOutput ); + try + { + document->setContentStream( pOut, OUSTR_TO_STDSTR( rMimeType ), std::string( ), bReplaceExisting ); + } + catch ( const libcmis::Exception& ) + { + ucbhelper::cancelCommandExecution( uno::Any + ( uno::RuntimeException( "Error when setting document content", + getXWeak() ) ), + xEnv ); + } + } + } + else + { + // We need to create a brand new object... either folder or document + bool bIsFolder = getObjectType( xEnv )->getBaseType( )->getId( ) == "cmis:folder"; + setCmisProperty( "cmis:objectTypeId", getObjectType( xEnv )->getId( ), xEnv ); + + if ( bIsFolder ) + { + try + { + pFolder->createFolder( m_pObjectProps ); + sNewPath = STD_TO_OUSTR( newPath ); + } + catch ( const libcmis::Exception& ) + { + ucbhelper::cancelCommandExecution( uno::Any + ( uno::RuntimeException( "Error when creating folder", + getXWeak() ) ), + xEnv ); + } + } + else + { + boost::shared_ptr< std::ostream > pOut( new std::ostringstream ( std::ios_base::binary | std::ios_base::in | std::ios_base::out ) ); + uno::Reference < io::XOutputStream > xOutput = new StdOutputStream( pOut ); + copyData( xInputStream, xOutput ); + try + { + pFolder->createDocument( m_pObjectProps, pOut, OUSTR_TO_STDSTR( rMimeType ), std::string() ); + sNewPath = STD_TO_OUSTR( newPath ); + } + catch ( const libcmis::Exception& ) + { + ucbhelper::cancelCommandExecution( uno::Any + ( uno::RuntimeException( "Error when creating document", + getXWeak() ) ), + xEnv ); + } + } + } + + if ( sNewPath.isEmpty( ) && m_sObjectId.isEmpty( ) ) + return; + + // Update the current content: it's no longer transient + m_sObjectPath = sNewPath; + URL aUrl( m_sURL ); + aUrl.setObjectPath( m_sObjectPath ); + aUrl.setObjectId( m_sObjectId ); + m_sURL = aUrl.asString( ); + m_pObject.reset( ); + m_pObjectType.reset( ); + m_pObjectProps.clear( ); + m_bTransient = false; + inserted(); + } + + const int TRANSFER_BUFFER_SIZE = 65536; + + void Content::copyData( + const uno::Reference< io::XInputStream >& xIn, + const uno::Reference< io::XOutputStream >& xOut ) + { + uno::Sequence< sal_Int8 > theData( TRANSFER_BUFFER_SIZE ); + + while ( xIn->readBytes( theData, TRANSFER_BUFFER_SIZE ) > 0 ) + xOut->writeBytes( theData ); + + xOut->closeOutput(); + } + + uno::Sequence< uno::Any > Content::setPropertyValues( + const uno::Sequence< beans::PropertyValue >& rValues, + const uno::Reference< ucb::XCommandEnvironment >& xEnv ) + { + try + { + // Get the already set properties if possible + if ( !m_bTransient && getObject( xEnv ).get( ) ) + { + m_pObjectProps.clear( ); + m_pObjectType = getObject( xEnv )->getTypeDescription(); + } + } + catch ( const libcmis::Exception& e ) + { + SAL_INFO( "ucb.ucp.cmis", "Unexpected libcmis exception: " << e.what( ) ); + ucbhelper::cancelCommandExecution( + ucb::IOErrorCode_GENERAL, + uno::Sequence< uno::Any >( 0 ), + xEnv, + o3tl::runtimeToOUString(e.what())); + } + + sal_Int32 nCount = rValues.getLength(); + uno::Sequence< uno::Any > aRet( nCount ); + auto aRetRange = asNonConstRange(aRet); + bool bChanged = false; + const beans::PropertyValue* pValues = rValues.getConstArray(); + for ( sal_Int32 n = 0; n < nCount; ++n ) + { + const beans::PropertyValue& rValue = pValues[ n ]; + if ( rValue.Name == "ContentType" || + rValue.Name == "MediaType" || + rValue.Name == "IsDocument" || + rValue.Name == "IsFolder" || + rValue.Name == "Size" || + rValue.Name == "CreatableContentsInfo" ) + { + lang::IllegalAccessException e ( "Property is read-only!", + getXWeak() ); + aRetRange[ n ] <<= e; + } + else if ( rValue.Name == "Title" ) + { + OUString aNewTitle; + if (!( rValue.Value >>= aNewTitle )) + { + aRetRange[ n ] <<= beans::IllegalTypeException + ( "Property value has wrong type!", + getXWeak() ); + continue; + } + + if ( aNewTitle.isEmpty() ) + { + aRetRange[ n ] <<= lang::IllegalArgumentException + ( "Empty title not allowed!", + getXWeak(), -1 ); + continue; + + } + + setCmisProperty( "cmis:name", OUSTR_TO_STDSTR( aNewTitle ), xEnv ); + bChanged = true; + } + else + { + SAL_INFO( "ucb.ucp.cmis", "Couldn't set property: " << rValue.Name ); + lang::IllegalAccessException e ( "Property is read-only!", + getXWeak() ); + aRetRange[ n ] <<= e; + } + } + + try + { + if ( !m_bTransient && bChanged ) + { + getObject( xEnv )->updateProperties( m_pObjectProps ); + } + } + catch ( const libcmis::Exception& e ) + { + SAL_INFO( "ucb.ucp.cmis", "Unexpected libcmis exception: " << e.what( ) ); + ucbhelper::cancelCommandExecution( + ucb::IOErrorCode_GENERAL, + uno::Sequence< uno::Any >( 0 ), + xEnv, + o3tl::runtimeToOUString(e.what())); + } + + return aRet; + } + + bool Content::feedSink( const uno::Reference< uno::XInterface>& xSink, + const uno::Reference< ucb::XCommandEnvironment >& xEnv ) + { + if ( !xSink.is() ) + return false; + + uno::Reference< io::XOutputStream > xOut(xSink, uno::UNO_QUERY ); + uno::Reference< io::XActiveDataSink > xDataSink(xSink, uno::UNO_QUERY ); + uno::Reference< io::XActiveDataStreamer > xDataStreamer( xSink, uno::UNO_QUERY ); + + if ( !xOut.is() && !xDataSink.is() && ( !xDataStreamer.is() || !xDataStreamer->getStream().is() ) ) + return false; + + if ( xDataStreamer.is() && !xOut.is() ) + xOut = xDataStreamer->getStream()->getOutputStream(); + + try + { + libcmis::Document* document = dynamic_cast< libcmis::Document* >( getObject( xEnv ).get() ); + + if (!document) + return false; + + boost::shared_ptr< std::istream > aIn = document->getContentStream( ); + + uno::Reference< io::XInputStream > xIn = new StdInputStream( aIn ); + if( !xIn.is( ) ) + return false; + + if ( xDataSink.is() ) + xDataSink->setInputStream( xIn ); + else if ( xOut.is() ) + copyData( xIn, xOut ); + } + catch ( const libcmis::Exception& e ) + { + SAL_INFO( "ucb.ucp.cmis", "Unexpected libcmis exception: " << e.what( ) ); + ucbhelper::cancelCommandExecution( + ucb::IOErrorCode_GENERAL, + uno::Sequence< uno::Any >( 0 ), + xEnv, + o3tl::runtimeToOUString(e.what())); + } + + return true; + } + + uno::Sequence< beans::Property > Content::getProperties( + const uno::Reference< ucb::XCommandEnvironment > & ) + { + static const beans::Property aGenericProperties[] = + { + beans::Property( "IsDocument", + -1, cppu::UnoType<bool>::get(), + beans::PropertyAttribute::BOUND | beans::PropertyAttribute::READONLY ), + beans::Property( "IsFolder", + -1, cppu::UnoType<bool>::get(), + beans::PropertyAttribute::BOUND | beans::PropertyAttribute::READONLY ), + beans::Property( "Title", + -1, cppu::UnoType<OUString>::get(), + beans::PropertyAttribute::BOUND ), + beans::Property( "ObjectId", + -1, cppu::UnoType<OUString>::get(), + beans::PropertyAttribute::BOUND ), + beans::Property( "TitleOnServer", + -1, cppu::UnoType<OUString>::get(), + beans::PropertyAttribute::BOUND ), + beans::Property( "IsReadOnly", + -1, cppu::UnoType<bool>::get(), + beans::PropertyAttribute::BOUND | beans::PropertyAttribute::READONLY ), + beans::Property( "DateCreated", + -1, cppu::UnoType<util::DateTime>::get(), + beans::PropertyAttribute::BOUND | beans::PropertyAttribute::READONLY ), + beans::Property( "DateModified", + -1, cppu::UnoType<util::DateTime>::get(), + beans::PropertyAttribute::BOUND | beans::PropertyAttribute::READONLY ), + beans::Property( "Size", + -1, cppu::UnoType<sal_Int64>::get(), + beans::PropertyAttribute::BOUND | beans::PropertyAttribute::READONLY ), + beans::Property( "CreatableContentsInfo", + -1, cppu::UnoType<uno::Sequence< ucb::ContentInfo >>::get(), + beans::PropertyAttribute::BOUND | beans::PropertyAttribute::READONLY ), + beans::Property( "MediaType", + -1, cppu::UnoType<OUString>::get(), + beans::PropertyAttribute::BOUND ), + beans::Property( "CmisProperties", + -1, cppu::UnoType<uno::Sequence< document::CmisProperty>>::get(), + beans::PropertyAttribute::BOUND ), + beans::Property( "IsVersionable", + -1, cppu::UnoType<bool>::get(), + beans::PropertyAttribute::BOUND | beans::PropertyAttribute::READONLY ), + beans::Property( "CanCheckOut", + -1, cppu::UnoType<bool>::get(), + beans::PropertyAttribute::BOUND | beans::PropertyAttribute::READONLY ), + beans::Property( "CanCancelCheckOut", + -1, cppu::UnoType<bool>::get(), + beans::PropertyAttribute::BOUND | beans::PropertyAttribute::READONLY ), + beans::Property( "CanCheckIn", + -1, cppu::UnoType<bool>::get(), + beans::PropertyAttribute::BOUND | beans::PropertyAttribute::READONLY ), + }; + + const int nProps = SAL_N_ELEMENTS(aGenericProperties); + return uno::Sequence< beans::Property > ( aGenericProperties, nProps ); + } + + uno::Sequence< ucb::CommandInfo > Content::getCommands( + const uno::Reference< ucb::XCommandEnvironment > & xEnv ) + { + static const ucb::CommandInfo aCommandInfoTable[] = + { + // Required commands + ucb::CommandInfo + ( "getCommandInfo", + -1, cppu::UnoType<void>::get() ), + ucb::CommandInfo + ( "getPropertySetInfo", + -1, cppu::UnoType<void>::get() ), + ucb::CommandInfo + ( "getPropertyValues", + -1, cppu::UnoType<uno::Sequence< beans::Property >>::get() ), + ucb::CommandInfo + ( "setPropertyValues", + -1, cppu::UnoType<uno::Sequence< beans::PropertyValue >>::get() ), + + // Optional standard commands + ucb::CommandInfo + ( "delete", + -1, cppu::UnoType<bool>::get() ), + ucb::CommandInfo + ( "insert", + -1, cppu::UnoType<ucb::InsertCommandArgument2>::get() ), + ucb::CommandInfo + ( "open", + -1, cppu::UnoType<ucb::OpenCommandArgument2>::get() ), + + // Mandatory CMIS-only commands + ucb::CommandInfo ( "checkout", -1, cppu::UnoType<void>::get() ), + ucb::CommandInfo ( "cancelCheckout", -1, cppu::UnoType<void>::get() ), + ucb::CommandInfo ( "checkIn", -1, + cppu::UnoType<ucb::TransferInfo>::get() ), + ucb::CommandInfo ( "updateProperties", -1, cppu::UnoType<void>::get() ), + ucb::CommandInfo + ( "getAllVersions", + -1, cppu::UnoType<uno::Sequence< document::CmisVersion >>::get() ), + + + // Folder Only, omitted if not a folder + ucb::CommandInfo + ( "transfer", + -1, cppu::UnoType<ucb::TransferInfo>::get() ), + ucb::CommandInfo + ( "createNewContent", + -1, cppu::UnoType<ucb::ContentInfo>::get() ) + }; + + const int nProps = SAL_N_ELEMENTS( aCommandInfoTable ); + return uno::Sequence< ucb::CommandInfo >(aCommandInfoTable, isFolder( xEnv ) ? nProps : nProps - 2); + } + + OUString Content::getParentURL( ) + { + SAL_INFO( "ucb.ucp.cmis", "Content::getParentURL()" ); + OUString parentUrl = "/"; + if ( m_sObjectPath == "/" ) + return parentUrl; + else + { + INetURLObject aUrl( m_sURL ); + if ( aUrl.getSegmentCount( ) > 0 ) + { + URL aCmisUrl( m_sURL ); + aUrl.removeSegment( ); + aCmisUrl.setObjectPath( aUrl.GetURLPath( INetURLObject::DecodeMechanism::WithCharset ) ); + parentUrl = aCmisUrl.asString( ); + } + } + return parentUrl; + } + + XTYPEPROVIDER_COMMON_IMPL( Content ); + + void SAL_CALL Content::acquire() noexcept + { + ContentImplHelper::acquire(); + } + + void SAL_CALL Content::release() noexcept + { + ContentImplHelper::release(); + } + + uno::Any SAL_CALL Content::queryInterface( const uno::Type & rType ) + { + uno::Any aRet = cppu::queryInterface( rType, static_cast< ucb::XContentCreator * >( this ) ); + return aRet.hasValue() ? aRet : ContentImplHelper::queryInterface(rType); + } + + OUString SAL_CALL Content::getImplementationName() + { + return "com.sun.star.comp.CmisContent"; + } + + uno::Sequence< OUString > SAL_CALL Content::getSupportedServiceNames() + { + uno::Sequence<OUString> aSNS { "com.sun.star.ucb.CmisContent" }; + return aSNS; + } + + OUString SAL_CALL Content::getContentType() + { + OUString sRet; + try + { + if (isFolder( uno::Reference< ucb::XCommandEnvironment >() )) + sRet = CMIS_FOLDER_TYPE; + else + sRet = CMIS_FILE_TYPE; + } + catch (const uno::RuntimeException&) + { + throw; + } + catch (const uno::Exception& e) + { + uno::Any a(cppu::getCaughtException()); + throw lang::WrappedTargetRuntimeException( + "wrapped Exception " + e.Message, + uno::Reference<uno::XInterface>(), a); + } + return sRet; + } + + uno::Any SAL_CALL Content::execute( + const ucb::Command& aCommand, + sal_Int32 /*CommandId*/, + const uno::Reference< ucb::XCommandEnvironment >& xEnv ) + { + SAL_INFO( "ucb.ucp.cmis", "Content::execute( ) - " << aCommand.Name ); + uno::Any aRet; + + if ( aCommand.Name == "getPropertyValues" ) + { + uno::Sequence< beans::Property > Properties; + if ( !( aCommand.Argument >>= Properties ) ) + ucbhelper::cancelCommandExecution ( getBadArgExcept (), xEnv ); + aRet <<= getPropertyValues( Properties, xEnv ); + } + else if ( aCommand.Name == "getPropertySetInfo" ) + aRet <<= getPropertySetInfo( xEnv, false ); + else if ( aCommand.Name == "getCommandInfo" ) + aRet <<= getCommandInfo( xEnv, false ); + else if ( aCommand.Name == "open" ) + { + ucb::OpenCommandArgument2 aOpenCommand; + if ( !( aCommand.Argument >>= aOpenCommand ) ) + ucbhelper::cancelCommandExecution ( getBadArgExcept (), xEnv ); + aRet = open( aOpenCommand, xEnv ); + } + else if ( aCommand.Name == "transfer" ) + { + ucb::TransferInfo transferArgs; + if ( !( aCommand.Argument >>= transferArgs ) ) + ucbhelper::cancelCommandExecution ( getBadArgExcept (), xEnv ); + transfer( transferArgs, xEnv ); + } + else if ( aCommand.Name == "setPropertyValues" ) + { + uno::Sequence< beans::PropertyValue > aProperties; + if ( !( aCommand.Argument >>= aProperties ) || !aProperties.hasElements() ) + ucbhelper::cancelCommandExecution ( getBadArgExcept (), xEnv ); + aRet <<= setPropertyValues( aProperties, xEnv ); + } + else if (aCommand.Name == "createNewContent" + && isFolder( xEnv ) ) + { + ucb::ContentInfo arg; + if ( !( aCommand.Argument >>= arg ) ) + ucbhelper::cancelCommandExecution ( getBadArgExcept (), xEnv ); + aRet <<= createNewContent( arg ); + } + else if ( aCommand.Name == "insert" ) + { + ucb::InsertCommandArgument2 arg; + if ( !( aCommand.Argument >>= arg ) ) + { + ucb::InsertCommandArgument insertArg; + if ( !( aCommand.Argument >>= insertArg ) ) + ucbhelper::cancelCommandExecution ( getBadArgExcept (), xEnv ); + + arg.Data = insertArg.Data; + arg.ReplaceExisting = insertArg.ReplaceExisting; + } + // store the document id + m_sObjectId = arg.DocumentId; + insert( arg.Data, arg.ReplaceExisting, arg.MimeType, xEnv ); + } + else if ( aCommand.Name == "delete" ) + { + try + { + if ( !isFolder( xEnv ) ) + { + getObject( xEnv )->remove( ); + } + else + { + libcmis::Folder* folder = dynamic_cast< libcmis::Folder* >( getObject( xEnv ).get() ); + if (folder) + folder->removeTree( ); + } + } + catch ( const libcmis::Exception& e ) + { + SAL_INFO( "ucb.ucp.cmis", "Unexpected libcmis exception: " << e.what( ) ); + ucbhelper::cancelCommandExecution( + ucb::IOErrorCode_GENERAL, + uno::Sequence< uno::Any >( 0 ), + xEnv, + o3tl::runtimeToOUString(e.what())); + } + } + else if ( aCommand.Name == "checkout" ) + { + aRet <<= checkOut( xEnv ); + } + else if ( aCommand.Name == "cancelCheckout" ) + { + aRet <<= cancelCheckOut( xEnv ); + } + else if ( aCommand.Name == "checkin" ) + { + ucb::CheckinArgument aArg; + if ( !( aCommand.Argument >>= aArg ) ) + { + ucbhelper::cancelCommandExecution ( getBadArgExcept(), xEnv ); + } + aRet <<= checkIn( aArg, xEnv ); + } + else if ( aCommand.Name == "getAllVersions" ) + { + aRet <<= getAllVersions( xEnv ); + } + else if ( aCommand.Name == "updateProperties" ) + { + updateProperties( aCommand.Argument, xEnv ); + } + else + { + SAL_INFO( "ucb.ucp.cmis", "Unknown command to execute" ); + + ucbhelper::cancelCommandExecution + ( uno::Any( ucb::UnsupportedCommandException + ( OUString(), + getXWeak() ) ), + xEnv ); + } + + return aRet; + } + + void SAL_CALL Content::abort( sal_Int32 /*CommandId*/ ) + { + SAL_INFO( "ucb.ucp.cmis", "TODO - Content::abort()" ); + // TODO Implement me + } + + uno::Sequence< ucb::ContentInfo > SAL_CALL Content::queryCreatableContentsInfo() + { + return queryCreatableContentsInfo( uno::Reference< ucb::XCommandEnvironment >() ); + } + + uno::Reference< ucb::XContent > SAL_CALL Content::createNewContent( + const ucb::ContentInfo& Info ) + { + bool create_document; + + if ( Info.Type == CMIS_FILE_TYPE ) + create_document = true; + else if ( Info.Type == CMIS_FOLDER_TYPE ) + create_document = false; + else + { + SAL_INFO( "ucb.ucp.cmis", "Unknown type of content to create" ); + return uno::Reference< ucb::XContent >(); + } + + OUString sParentURL = m_xIdentifier->getContentIdentifier(); + + // Set the parent URL for the transient objects + uno::Reference< ucb::XContentIdentifier > xId(new ::ucbhelper::ContentIdentifier(sParentURL)); + + try + { + return new ::cmis::Content( m_xContext, m_pProvider, xId, !create_document ); + } + catch ( ucb::ContentCreationException & ) + { + return uno::Reference< ucb::XContent >(); + } + } + + uno::Sequence< uno::Type > SAL_CALL Content::getTypes() + { + try + { + if ( isFolder( uno::Reference< ucb::XCommandEnvironment >() ) ) + { + static cppu::OTypeCollection s_aFolderCollection + (CPPU_TYPE_REF( lang::XTypeProvider ), + CPPU_TYPE_REF( lang::XServiceInfo ), + CPPU_TYPE_REF( lang::XComponent ), + CPPU_TYPE_REF( ucb::XContent ), + CPPU_TYPE_REF( ucb::XCommandProcessor ), + CPPU_TYPE_REF( beans::XPropertiesChangeNotifier ), + CPPU_TYPE_REF( ucb::XCommandInfoChangeNotifier ), + CPPU_TYPE_REF( beans::XPropertyContainer ), + CPPU_TYPE_REF( beans::XPropertySetInfoChangeNotifier ), + CPPU_TYPE_REF( container::XChild ), + CPPU_TYPE_REF( ucb::XContentCreator ) ); + return s_aFolderCollection.getTypes(); + } + } + catch (const uno::RuntimeException&) + { + throw; + } + catch (const uno::Exception& e) + { + uno::Any a(cppu::getCaughtException()); + throw lang::WrappedTargetRuntimeException( + "wrapped Exception " + e.Message, + uno::Reference<uno::XInterface>(), a); + } + + static cppu::OTypeCollection s_aFileCollection + (CPPU_TYPE_REF( lang::XTypeProvider ), + CPPU_TYPE_REF( lang::XServiceInfo ), + CPPU_TYPE_REF( lang::XComponent ), + CPPU_TYPE_REF( ucb::XContent ), + CPPU_TYPE_REF( ucb::XCommandProcessor ), + CPPU_TYPE_REF( beans::XPropertiesChangeNotifier ), + CPPU_TYPE_REF( ucb::XCommandInfoChangeNotifier ), + CPPU_TYPE_REF( beans::XPropertyContainer ), + CPPU_TYPE_REF( beans::XPropertySetInfoChangeNotifier ), + CPPU_TYPE_REF( container::XChild ) ); + + return s_aFileCollection.getTypes(); + } + + uno::Sequence< ucb::ContentInfo > Content::queryCreatableContentsInfo( + const uno::Reference< ucb::XCommandEnvironment >& xEnv) + { + try + { + if ( isFolder( xEnv ) ) + { + + // Minimum set of props we really need + uno::Sequence< beans::Property > props + { + { + "Title", + -1, + cppu::UnoType<OUString>::get(), + beans::PropertyAttribute::MAYBEVOID | beans::PropertyAttribute::BOUND + } + }; + + return + { + { + CMIS_FILE_TYPE, + ( ucb::ContentInfoAttribute::INSERT_WITH_INPUTSTREAM | + ucb::ContentInfoAttribute::KIND_DOCUMENT ), + props + }, + { + CMIS_FOLDER_TYPE, + ucb::ContentInfoAttribute::KIND_FOLDER, + props + } + }; + } + } + catch (const uno::RuntimeException&) + { + throw; + } + catch (const uno::Exception& e) + { + uno::Any a(cppu::getCaughtException()); + throw lang::WrappedTargetRuntimeException( + "wrapped Exception " + e.Message, + uno::Reference<uno::XInterface>(), a); + } + return {}; + } + + std::vector< uno::Reference< ucb::XContent > > Content::getChildren( ) + { + std::vector< uno::Reference< ucb::XContent > > results; + SAL_INFO( "ucb.ucp.cmis", "Content::getChildren() " << m_sURL ); + + libcmis::FolderPtr pFolder = boost::dynamic_pointer_cast< libcmis::Folder >( getObject( uno::Reference< ucb::XCommandEnvironment >() ) ); + if ( nullptr != pFolder ) + { + // Get the children from pObject + try + { + std::vector< libcmis::ObjectPtr > children = pFolder->getChildren( ); + + // Loop over the results + for ( const auto& rChild : children ) + { + // TODO Cache the objects + + INetURLObject aURL( m_sURL ); + OUString sUser = aURL.GetUser( INetURLObject::DecodeMechanism::WithCharset ); + + URL aUrl( m_sURL ); + OUString sPath( m_sObjectPath ); + if ( !sPath.endsWith("/") ) + sPath += "/"; + sPath += STD_TO_OUSTR( rChild->getName( ) ); + OUString sId = STD_TO_OUSTR( rChild->getId( ) ); + + aUrl.setObjectId( sId ); + aUrl.setObjectPath( sPath ); + aUrl.setUsername( sUser ); + + uno::Reference< ucb::XContentIdentifier > xId = new ucbhelper::ContentIdentifier( aUrl.asString( ) ); + uno::Reference< ucb::XContent > xContent = new Content( m_xContext, m_pProvider, xId, rChild ); + + results.push_back( xContent ); + } + } + catch ( const libcmis::Exception& e ) + { + SAL_INFO( "ucb.ucp.cmis", "Exception thrown: " << e.what() ); + } + } + + return results; + } + + void Content::setCmisProperty(const std::string& rName, const std::string& rValue, const uno::Reference< ucb::XCommandEnvironment >& xEnv ) + { + if ( !getObjectType( xEnv ).get( ) ) + return; + + std::map< std::string, libcmis::PropertyPtr >::iterator propIt = m_pObjectProps.find(rName); + + if ( propIt == m_pObjectProps.end( ) && getObjectType( xEnv ).get( ) ) + { + std::map< std::string, libcmis::PropertyTypePtr > propsTypes = getObjectType( xEnv )->getPropertiesTypes( ); + std::map< std::string, libcmis::PropertyTypePtr >::iterator typeIt = propsTypes.find(rName); + + if ( typeIt != propsTypes.end( ) ) + { + libcmis::PropertyTypePtr propType = typeIt->second; + libcmis::PropertyPtr property( new libcmis::Property( propType, { rValue }) ); + m_pObjectProps.insert(std::pair< std::string, libcmis::PropertyPtr >(rName, property)); + } + } + else if ( propIt != m_pObjectProps.end( ) ) + { + propIt->second->setValues( { rValue } ); + } + } +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/ucb/source/ucp/cmis/cmis_content.hxx b/ucb/source/ucp/cmis/cmis_content.hxx new file mode 100644 index 0000000000..7c2938c65b --- /dev/null +++ b/ucb/source/ucp/cmis/cmis_content.hxx @@ -0,0 +1,210 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ + +#pragma once + +#include <sal/config.h> + +#include <string_view> + +#include "cmis_url.hxx" +#include "children_provider.hxx" + +#include <com/sun/star/io/XInputStream.hpp> +#include <com/sun/star/io/XOutputStream.hpp> +#include <com/sun/star/ucb/CheckinArgument.hpp> +#include <com/sun/star/ucb/CommandFailedException.hpp> +#include <com/sun/star/ucb/ContentCreationException.hpp> +#include <com/sun/star/ucb/OpenCommandArgument2.hpp> +#include <com/sun/star/ucb/TransferInfo.hpp> +#include <com/sun/star/ucb/XContentCreator.hpp> +#include <com/sun/star/document/CmisVersion.hpp> +#include <ucbhelper/contenthelper.hxx> + +#if defined __GNUC__ && !defined __clang__ +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wdeprecated" +#pragma GCC diagnostic ignored "-Wunused-but-set-parameter" +#endif +#include <libcmis/libcmis.hxx> +#if defined __GNUC__ && !defined __clang__ +#pragma GCC diagnostic pop +#endif + +namespace com::sun::star { + namespace beans { + struct Property; + struct PropertyValue; + } + namespace sdbc { + class XRow; + } +} +namespace ucbhelper +{ + class Content; +} + + +namespace cmis +{ + +inline constexpr OUString CMIS_FILE_TYPE = u"application/vnd.libreoffice.cmis-file"_ustr; +inline constexpr OUString CMIS_FOLDER_TYPE = u"application/vnd.libreoffice.cmis-folder"_ustr; + +class ContentProvider; +class Content : public ::ucbhelper::ContentImplHelper, + public css::ucb::XContentCreator, + public ChildrenProvider +{ +private: + ContentProvider* m_pProvider; + libcmis::Session* m_pSession; + libcmis::ObjectPtr m_pObject; + OUString m_sObjectPath; + OUString m_sObjectId; + OUString m_sURL; + cmis::URL m_aURL; + + // Members to be set for non-persistent content + bool m_bTransient; + bool m_bIsFolder; + libcmis::ObjectTypePtr m_pObjectType; + std::map< std::string, libcmis::PropertyPtr > m_pObjectProps; + + bool isFolder( const css::uno::Reference< css::ucb::XCommandEnvironment >& xEnv ); + void setCmisProperty(const std::string& rName, const std::string& rValue, + const css::uno::Reference< css::ucb::XCommandEnvironment >& xEnv ); + + css::uno::Any getBadArgExcept(); + + css::uno::Reference< css::sdbc::XRow > + getPropertyValues( + const css::uno::Sequence< css::beans::Property >& rProperties, + const css::uno::Reference< css::ucb::XCommandEnvironment >& xEnv ); + + libcmis::Session* getSession( const css::uno::Reference< css::ucb::XCommandEnvironment >& xEnv ); + libcmis::ObjectTypePtr const & getObjectType( const css::uno::Reference< css::ucb::XCommandEnvironment >& xEnv ); + +private: + typedef rtl::Reference< Content > ContentRef; + typedef std::vector< ContentRef > ContentRefList; + + /// @throws css::uno::Exception + /// @throws libcmis::Exception + css::uno::Any open(const css::ucb::OpenCommandArgument2 & rArg, + const css::uno::Reference< css::ucb::XCommandEnvironment > & xEnv ); + + /// @throws css::uno::Exception + void transfer( const css::ucb::TransferInfo& rTransferInfo, + const css::uno::Reference< css::ucb::XCommandEnvironment > & xEnv ); + + /// @throws css::uno::Exception + void insert( const css::uno::Reference< css::io::XInputStream > & xInputStream, + bool bReplaceExisting, std::u16string_view rMimeType, + const css::uno::Reference< css::ucb::XCommandEnvironment >& xEnv ); + + /// @throws css::uno::Exception + OUString checkIn( const css::ucb::CheckinArgument& rArg, + const css::uno::Reference< css::ucb::XCommandEnvironment > & xEnv ); + + /// @throws css::uno::Exception + OUString checkOut( const css::uno::Reference< css::ucb::XCommandEnvironment > & xEnv ); + + /// @throws css::uno::Exception + OUString cancelCheckOut( const css::uno::Reference< css::ucb::XCommandEnvironment > & xEnv ); + + static void copyData( const css::uno::Reference< css::io::XInputStream >& xIn, + const css::uno::Reference< css::io::XOutputStream >& xOut ); + + css::uno::Sequence< css::uno::Any > + setPropertyValues( const css::uno::Sequence< css::beans::PropertyValue >& rValues, + const css::uno::Reference< css::ucb::XCommandEnvironment >& xEnv ); + + /// @throws css::uno::Exception + css::uno::Sequence< css::document::CmisVersion > + getAllVersions( const css::uno::Reference< css::ucb::XCommandEnvironment > & xEnv ); + + bool feedSink( const css::uno::Reference< css::uno::XInterface>& aSink, + const css::uno::Reference< css::ucb::XCommandEnvironment >& xEnv ); + +public: + /// @throws css::ucb::ContentCreationException + Content( const css::uno::Reference< css::uno::XComponentContext >& rxContext, + ContentProvider *pProvider, + const css::uno::Reference< css::ucb::XContentIdentifier >& Identifier, + libcmis::ObjectPtr pObject = libcmis::ObjectPtr( ) ); + + /// @throws css::ucb::ContentCreationException + Content( const css::uno::Reference< css::uno::XComponentContext >& rxContext, + ContentProvider *pProvider, + const css::uno::Reference< css::ucb::XContentIdentifier >& Identifier, + bool bIsFolder); + + virtual ~Content() override; + + virtual css::uno::Sequence< css::beans::Property > + getProperties( const css::uno::Reference< css::ucb::XCommandEnvironment > & xEnv ) override; + + libcmis::ObjectPtr updateProperties( + const css::uno::Any& iCmisProps, + const css::uno::Reference< css::ucb::XCommandEnvironment > & xEnv); + + virtual css::uno::Sequence< css::ucb::CommandInfo > + getCommands( const css::uno::Reference< css::ucb::XCommandEnvironment > & xEnv ) override; + + virtual OUString getParentURL() override; + + // XInterface + virtual css::uno::Any SAL_CALL queryInterface( const css::uno::Type & rType ) override; + virtual void SAL_CALL acquire() + noexcept override; + virtual void SAL_CALL release() + noexcept override; + + virtual css::uno::Sequence< sal_Int8 > SAL_CALL getImplementationId() override; + virtual css::uno::Sequence< css::uno::Type > SAL_CALL getTypes() override; + + virtual OUString SAL_CALL + getImplementationName() override; + + virtual css::uno::Sequence< OUString > SAL_CALL + getSupportedServiceNames() override; + + virtual OUString SAL_CALL + getContentType() override; + + virtual css::uno::Any SAL_CALL + execute( const css::ucb::Command& aCommand, + sal_Int32 CommandId, + const css::uno::Reference< css::ucb::XCommandEnvironment >& Environment ) override; + + virtual void SAL_CALL abort( sal_Int32 CommandId ) override; + + virtual css::uno::Sequence< css::ucb::ContentInfo > + SAL_CALL queryCreatableContentsInfo() override; + + virtual css::uno::Reference< css::ucb::XContent > + SAL_CALL createNewContent( const css::ucb::ContentInfo& Info ) override; + + /// @throws css::uno::RuntimeException + css::uno::Sequence< css::ucb::ContentInfo > + queryCreatableContentsInfo( const css::uno::Reference< css::ucb::XCommandEnvironment >& xEnv ); + + virtual std::vector< css::uno::Reference< css::ucb::XContent > > getChildren( ) override; + + /// @throws css::uno::RuntimeException + /// @throws css::ucb::CommandFailedException + /// @throws libcmis::Exception + libcmis::ObjectPtr const & getObject( const css::uno::Reference< css::ucb::XCommandEnvironment >& xEnv ); +}; + +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/ucb/source/ucp/cmis/cmis_datasupplier.cxx b/ucb/source/ucp/cmis/cmis_datasupplier.cxx new file mode 100644 index 0000000000..2a91770cd8 --- /dev/null +++ b/ucb/source/ucp/cmis/cmis_datasupplier.cxx @@ -0,0 +1,157 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ + +#include <com/sun/star/ucb/OpenMode.hpp> + +#include "cmis_datasupplier.hxx" +#include "cmis_content.hxx" + +using namespace com::sun::star; + +namespace cmis +{ + + typedef std::vector< ResultListEntry* > ResultList; + + DataSupplier::DataSupplier( ChildrenProvider* pChildrenProvider, sal_Int32 nOpenMode ) + : m_pChildrenProvider( pChildrenProvider ), mnOpenMode(nOpenMode), mbCountFinal(false) + { + } + + void DataSupplier::getData() + { + if ( mbCountFinal ) + return; + + std::vector< uno::Reference< ucb::XContent > > aChildren = m_pChildrenProvider->getChildren( ); + + // Loop over the results and filter them + for ( const auto& rChild : aChildren ) + { + OUString sContentType = rChild->getContentType( ); + bool bIsFolder = sContentType != CMIS_FILE_TYPE; + if ( ( mnOpenMode == ucb::OpenMode::FOLDERS && bIsFolder ) || + ( mnOpenMode == ucb::OpenMode::DOCUMENTS && !bIsFolder ) || + ( mnOpenMode == ucb::OpenMode::ALL ) ) + { + maResults.emplace_back( rChild ); + } + } + mbCountFinal = true; + } + + DataSupplier::~DataSupplier() + { + } + + OUString DataSupplier::queryContentIdentifierString( sal_uInt32 nIndex ) + { + auto const xTemp(queryContentIdentifier(nIndex)); + return (xTemp.is()) ? xTemp->getContentIdentifier() : OUString(); + } + + uno::Reference< ucb::XContentIdentifier > DataSupplier::queryContentIdentifier( sal_uInt32 nIndex ) + { + auto const xTemp(queryContent(nIndex)); + return (xTemp.is()) ? xTemp->getIdentifier() : uno::Reference<ucb::XContentIdentifier>(); + } + + uno::Reference< ucb::XContent > DataSupplier::queryContent( sal_uInt32 nIndex ) + { + if (!getResult(nIndex)) + return uno::Reference<ucb::XContent>(); + + return maResults[ nIndex ].xContent; + } + + bool DataSupplier::getResult( sal_uInt32 nIndex ) + { + if ( maResults.size() > nIndex ) // Result already present. + return true; + + getData(); + return maResults.size() > nIndex; + } + + sal_uInt32 DataSupplier::totalCount() + { + getData(); + return maResults.size(); + } + + sal_uInt32 DataSupplier::currentCount() + { + return maResults.size(); + } + + bool DataSupplier::isCountFinal() + { + return mbCountFinal; + } + + uno::Reference< sdbc::XRow > DataSupplier::queryPropertyValues( sal_uInt32 nIndex ) + { + if ( nIndex < maResults.size() ) + { + uno::Reference< sdbc::XRow > xRow = maResults[ nIndex ].xRow; + if ( xRow.is() ) + { + // Already cached. + return xRow; + } + } + + if ( getResult( nIndex ) ) + { + uno::Reference< ucb::XContent > xContent( queryContent( nIndex ) ); + if ( xContent.is() ) + { + try + { + uno::Reference< ucb::XCommandProcessor > xCmdProc( + xContent, uno::UNO_QUERY_THROW ); + sal_Int32 nCmdId( xCmdProc->createCommandIdentifier() ); + ucb::Command aCmd; + aCmd.Name = "getPropertyValues"; + aCmd.Handle = -1; + aCmd.Argument <<= getResultSet()->getProperties(); + uno::Any aResult( xCmdProc->execute( + aCmd, nCmdId, getResultSet()->getEnvironment() ) ); + uno::Reference< sdbc::XRow > xRow; + if ( aResult >>= xRow ) + { + maResults[ nIndex ].xRow = xRow; + return xRow; + } + } + catch ( uno::Exception const & ) + { + } + } + } + return uno::Reference< sdbc::XRow >(); + } + + void DataSupplier::releasePropertyValues( sal_uInt32 nIndex ) + { + if ( nIndex < maResults.size() ) + maResults[ nIndex ].xRow.clear(); + } + + void DataSupplier::close() + { + } + + void DataSupplier::validate() + { + } + +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/ucb/source/ucp/cmis/cmis_datasupplier.hxx b/ucb/source/ucp/cmis/cmis_datasupplier.hxx new file mode 100644 index 0000000000..35d5429ec6 --- /dev/null +++ b/ucb/source/ucp/cmis/cmis_datasupplier.hxx @@ -0,0 +1,70 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ + +#pragma once + +#include <vector> + +#include <ucbhelper/resultset.hxx> + +#include "children_provider.hxx" + +namespace cmis +{ + + class Content; + + struct ResultListEntry + { + css::uno::Reference< css::ucb::XContent > xContent; + css::uno::Reference< css::sdbc::XRow > xRow; + + explicit ResultListEntry( css::uno::Reference< css::ucb::XContent > xCnt ) : xContent( std::move(xCnt) ) + { + } + }; + + class DataSupplier : public ucbhelper::ResultSetDataSupplier + { + private: + ChildrenProvider* m_pChildrenProvider; + sal_Int32 mnOpenMode; + bool mbCountFinal; + void getData(); + std::vector< ResultListEntry > maResults; + + public: + DataSupplier( ChildrenProvider* pChildrenProvider, sal_Int32 nOpenMode ); + + virtual ~DataSupplier() override; + + virtual OUString queryContentIdentifierString( sal_uInt32 nIndex ) override; + virtual css::uno::Reference< css::ucb::XContentIdentifier > + queryContentIdentifier( sal_uInt32 nIndex ) override; + virtual css::uno::Reference< css::ucb::XContent > + queryContent( sal_uInt32 nIndex ) override; + + virtual bool getResult( sal_uInt32 nIndex ) override; + + virtual sal_uInt32 totalCount() override; + virtual sal_uInt32 currentCount() override; + virtual bool isCountFinal() override; + + virtual css::uno::Reference< css::sdbc::XRow > + queryPropertyValues( sal_uInt32 nIndex ) override; + virtual void releasePropertyValues( sal_uInt32 nIndex ) override; + + virtual void close() override; + + virtual void validate() override; + }; + +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/ucb/source/ucp/cmis/cmis_provider.cxx b/ucb/source/ucp/cmis/cmis_provider.cxx new file mode 100644 index 0000000000..290b9c7556 --- /dev/null +++ b/ucb/source/ucp/cmis/cmis_provider.cxx @@ -0,0 +1,142 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ + +#include <comphelper/processfactory.hxx> +#include <cppuhelper/queryinterface.hxx> +#include <cppuhelper/weak.hxx> +#include <ucbhelper/macros.hxx> +#include <com/sun/star/ucb/ContentCreationException.hpp> +#include <com/sun/star/ucb/IllegalIdentifierException.hpp> + +#include "cmis_content.hxx" +#include "cmis_provider.hxx" +#include "cmis_repo_content.hxx" + +using namespace com::sun::star; + +namespace cmis +{ +uno::Reference< css::ucb::XContent > SAL_CALL +ContentProvider::queryContent( + const uno::Reference< css::ucb::XContentIdentifier >& Identifier ) +{ + osl::MutexGuard aGuard( m_aMutex ); + + // Check, if a content with given id already exists... + uno::Reference< ucb::XContent > xContent = queryExistingContent( Identifier ); + if ( xContent.is() ) + return xContent; + + try + { + URL aUrl( Identifier->getContentIdentifier( ) ); + if ( aUrl.getRepositoryId( ).isEmpty( ) ) + { + xContent = new RepoContent( m_xContext, this, Identifier ); + registerNewContent( xContent ); + } + else + { + xContent = new Content( m_xContext, this, Identifier ); + registerNewContent( xContent ); + } + } + catch ( css::ucb::ContentCreationException const & ) + { + throw css::ucb::IllegalIdentifierException(); + } + + if ( !xContent->getIdentifier().is() ) + throw css::ucb::IllegalIdentifierException(); + + return xContent; +} + +libcmis::Session* ContentProvider::getSession( const OUString& sBindingUrl, const OUString& sUsername ) +{ + libcmis::Session* pSession = nullptr; + std::map< std::pair< OUString, OUString >, libcmis::Session* >::iterator it + = m_aSessionCache.find( std::pair< OUString, OUString >( sBindingUrl, sUsername ) ); + if ( it != m_aSessionCache.end( ) ) + { + pSession = it->second; + } + return pSession; +} + +void ContentProvider::registerSession( const OUString& sBindingUrl, const OUString& sUsername, libcmis::Session* pSession ) +{ + m_aSessionCache.insert( std::pair< std::pair< OUString, OUString >, libcmis::Session* > + ( + std::pair< OUString, OUString >( sBindingUrl, sUsername ), + pSession + ) ); +} + +ContentProvider::ContentProvider( + const uno::Reference< uno::XComponentContext >& rxContext ) +: ::ucbhelper::ContentProviderImplHelper( rxContext ) +{ +} + +ContentProvider::~ContentProvider() +{ +} + +//XInterface +void SAL_CALL ContentProvider::acquire() + noexcept +{ + OWeakObject::acquire(); +} + +void SAL_CALL ContentProvider::release() + noexcept +{ + OWeakObject::release(); +} + +css::uno::Any SAL_CALL ContentProvider::queryInterface( const css::uno::Type & rType ) +{ + css::uno::Any aRet = cppu::queryInterface( rType, + static_cast< lang::XTypeProvider* >(this), + static_cast< lang::XServiceInfo* >(this), + static_cast< css::ucb::XContentProvider* >(this) + ); + return aRet.hasValue() ? aRet : OWeakObject::queryInterface( rType ); +} + +XTYPEPROVIDER_IMPL_3( ContentProvider, + lang::XTypeProvider, + lang::XServiceInfo, + css::ucb::XContentProvider ); + +sal_Bool ContentProvider::supportsService(const OUString& sServiceName) +{ + return cppu::supportsService(this, sServiceName); +} +OUString ContentProvider::getImplementationName() +{ + return "com.sun.star.comp.CmisContentProvider"; +} +css::uno::Sequence< OUString > ContentProvider::getSupportedServiceNames() +{ + return { "com.sun.star.ucb.CmisContentProvider" }; +} + +} + +extern "C" SAL_DLLPUBLIC_EXPORT css::uno::XInterface* +ucb_cmis_ContentProvider_get_implementation( + css::uno::XComponentContext* context, css::uno::Sequence<css::uno::Any> const&) +{ + return cppu::acquire(new cmis::ContentProvider(context)); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/ucb/source/ucp/cmis/cmis_provider.hxx b/ucb/source/ucp/cmis/cmis_provider.hxx new file mode 100644 index 0000000000..127ec46ca4 --- /dev/null +++ b/ucb/source/ucp/cmis/cmis_provider.hxx @@ -0,0 +1,56 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ + +#pragma once + +#include <com/sun/star/beans/Property.hpp> +#include <com/sun/star/lang/XSingleServiceFactory.hpp> +#include <com/sun/star/lang/XMultiServiceFactory.hpp> +#include <ucbhelper/providerhelper.hxx> +#include <libcmis/libcmis.hxx> + +namespace cmis +{ + +class ContentProvider : public ::ucbhelper::ContentProviderImplHelper +{ +private: + std::map< std::pair< OUString, OUString >, libcmis::Session* > m_aSessionCache; + +public: + explicit ContentProvider( const css::uno::Reference< css::uno::XComponentContext >& rxContext ); + virtual ~ContentProvider() override; + + // XInterface + virtual css::uno::Any SAL_CALL queryInterface( const css::uno::Type & rType ) override; + virtual void SAL_CALL acquire() + noexcept override; + virtual void SAL_CALL release() + noexcept override; + + // XTypeProvider + virtual css::uno::Sequence< sal_Int8 > SAL_CALL getImplementationId() override; + virtual css::uno::Sequence< css::uno::Type > SAL_CALL getTypes() override; + + // XServiceInfo + virtual OUString SAL_CALL getImplementationName() override; + virtual sal_Bool SAL_CALL supportsService( const OUString& ServiceName ) override; + virtual css::uno::Sequence< OUString > SAL_CALL getSupportedServiceNames() override; + + // XContentProvider + virtual css::uno::Reference< css::ucb::XContent > SAL_CALL + queryContent( const css::uno::Reference< css::ucb::XContentIdentifier >& Identifier ) override; + + libcmis::Session* getSession( const OUString& sBindingUrl, const OUString& sUsername ); + void registerSession( const OUString& sBindingUrl, const OUString& sUsername, libcmis::Session* pSession ); +}; + +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/ucb/source/ucp/cmis/cmis_repo_content.cxx b/ucb/source/ucp/cmis/cmis_repo_content.cxx new file mode 100644 index 0000000000..455df22f86 --- /dev/null +++ b/ucb/source/ucp/cmis/cmis_repo_content.cxx @@ -0,0 +1,424 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ + +#include <sal/config.h> + +#include <boost/make_shared.hpp> + +#include <com/sun/star/beans/PropertyAttribute.hpp> +#include <com/sun/star/beans/PropertyValue.hpp> +#include <com/sun/star/beans/XPropertySetInfo.hpp> +#include <com/sun/star/lang/IllegalArgumentException.hpp> +#include <com/sun/star/ucb/XCommandInfo.hpp> +#include <com/sun/star/ucb/XDynamicResultSet.hpp> +#ifndef SYSTEM_CURL +#include <com/sun/star/xml/crypto/XDigestContext.hpp> +#include <com/sun/star/xml/crypto/DigestID.hpp> +#include <com/sun/star/xml/crypto/NSSInitializer.hpp> +#endif + +#include <config_oauth2.h> +#include <rtl/uri.hxx> +#include <sal/log.hxx> +#include <tools/urlobj.hxx> +#include <ucbhelper/cancelcommandexecution.hxx> +#include <ucbhelper/contentidentifier.hxx> +#include <ucbhelper/propertyvalueset.hxx> +#include <ucbhelper/proxydecider.hxx> +#include <ucbhelper/macros.hxx> + +#include "auth_provider.hxx" +#include "certvalidation_handler.hxx" +#include "cmis_content.hxx" +#include "cmis_provider.hxx" +#include "cmis_repo_content.hxx" +#include "cmis_resultset.hxx" +#include <memory> + +#define OUSTR_TO_STDSTR(s) std::string( OUStringToOString( s, RTL_TEXTENCODING_UTF8 ) ) +#define STD_TO_OUSTR( str ) OUString( str.c_str(), str.length( ), RTL_TEXTENCODING_UTF8 ) + +using namespace com::sun::star; + +namespace cmis +{ + RepoContent::RepoContent( const uno::Reference< uno::XComponentContext >& rxContext, + ContentProvider *pProvider, const uno::Reference< ucb::XContentIdentifier >& Identifier, + std::vector< libcmis::RepositoryPtr > && aRepos ) + : ContentImplHelper( rxContext, pProvider, Identifier ), + m_pProvider( pProvider ), + m_aURL( Identifier->getContentIdentifier( ) ), + m_aRepositories( std::move(aRepos) ) + { + // Split the URL into bits + OUString sURL = m_xIdentifier->getContentIdentifier( ); + SAL_INFO( "ucb.ucp.cmis", "RepoContent::RepoContent() " << sURL ); + + m_sRepositoryId = m_aURL.getObjectPath(); + if (!m_sRepositoryId.isEmpty() && m_sRepositoryId[0] == '/') + m_sRepositoryId = m_sRepositoryId.copy(1); + } + + RepoContent::~RepoContent() + { + } + + uno::Any RepoContent::getBadArgExcept() + { + return uno::Any( lang::IllegalArgumentException( + "Wrong argument type!", + getXWeak(), -1) ); + } + + uno::Reference< sdbc::XRow > RepoContent::getPropertyValues( + const uno::Sequence< beans::Property >& rProperties, + const uno::Reference< ucb::XCommandEnvironment >& xEnv ) + { + rtl::Reference< ::ucbhelper::PropertyValueSet > xRow = new ::ucbhelper::PropertyValueSet( m_xContext ); + + for( const beans::Property& rProp : rProperties ) + { + try + { + if ( rProp.Name == "IsDocument" ) + { + xRow->appendBoolean( rProp, false ); + } + else if ( rProp.Name == "IsFolder" ) + { + xRow->appendBoolean( rProp, true ); + } + else if ( rProp.Name == "Title" ) + { + xRow->appendString( rProp, STD_TO_OUSTR( getRepository( xEnv )->getName( ) ) ); + } + else if ( rProp.Name == "IsReadOnly" ) + { + xRow->appendBoolean( rProp, true ); + } + else + { + xRow->appendVoid( rProp ); + SAL_INFO( "ucb.ucp.cmis", "Looking for unsupported property " << rProp.Name ); + } + } + catch (const libcmis::Exception&) + { + xRow->appendVoid( rProp ); + } + } + + return xRow; + } + + void RepoContent::getRepositories( const uno::Reference< ucb::XCommandEnvironment > & xEnv ) + { +#ifndef SYSTEM_CURL + // Initialize NSS library to make sure libcmis (and curl) can access CACERTs using NSS + // when using internal libcurl. + uno::Reference< css::xml::crypto::XNSSInitializer > + xNSSInitializer = css::xml::crypto::NSSInitializer::create( m_xContext ); + + uno::Reference< css::xml::crypto::XDigestContext > xDigestContext( + xNSSInitializer->getDigestContext( css::xml::crypto::DigestID::SHA256, + uno::Sequence< beans::NamedValue >() ), + uno::UNO_SET_THROW ); +#endif + + // Set the proxy if needed. We are doing that all times as the proxy data shouldn't be cached. + ucbhelper::InternetProxyDecider aProxyDecider( m_xContext ); + INetURLObject aBindingUrl( m_aURL.getBindingUrl( ) ); + const OUString sProxy = aProxyDecider.getProxy( + INetURLObject::GetScheme( aBindingUrl.GetProtocol( ) ), aBindingUrl.GetHost(), aBindingUrl.GetPort() ); + libcmis::SessionFactory::setProxySettings( OUSTR_TO_STDSTR( sProxy ), std::string(), std::string(), std::string() ); + + if ( !m_aRepositories.empty() ) + return; + + // Set the SSL Validation handler + libcmis::CertValidationHandlerPtr certHandler( + new CertValidationHandler( xEnv, m_xContext, aBindingUrl.GetHost( ) ) ); + libcmis::SessionFactory::setCertificateValidationHandler( certHandler ); + + // Get the auth credentials + AuthProvider authProvider( xEnv, m_xIdentifier->getContentIdentifier( ), m_aURL.getBindingUrl( ) ); + AuthProvider::setXEnv( xEnv ); + + std::string rUsername = OUSTR_TO_STDSTR( m_aURL.getUsername( ) ); + std::string rPassword = OUSTR_TO_STDSTR( m_aURL.getPassword( ) ); + + bool bIsDone = false; + + while( !bIsDone ) + { + if ( authProvider.authenticationQuery( rUsername, rPassword ) ) + { + try + { + // Create a session to get repositories + libcmis::OAuth2DataPtr oauth2Data; + if ( m_aURL.getBindingUrl( ) == GDRIVE_BASE_URL ) + { + libcmis::SessionFactory::setOAuth2AuthCodeProvider( AuthProvider::copyWebAuthCodeFallback ); + oauth2Data = boost::make_shared<libcmis::OAuth2Data>( + GDRIVE_AUTH_URL, GDRIVE_TOKEN_URL, + GDRIVE_SCOPE, GDRIVE_REDIRECT_URI, + GDRIVE_CLIENT_ID, GDRIVE_CLIENT_SECRET ); + } + if ( m_aURL.getBindingUrl().startsWith( ALFRESCO_CLOUD_BASE_URL ) ) + oauth2Data = boost::make_shared<libcmis::OAuth2Data>( + ALFRESCO_CLOUD_AUTH_URL, ALFRESCO_CLOUD_TOKEN_URL, + ALFRESCO_CLOUD_SCOPE, ALFRESCO_CLOUD_REDIRECT_URI, + ALFRESCO_CLOUD_CLIENT_ID, ALFRESCO_CLOUD_CLIENT_SECRET ); + if ( m_aURL.getBindingUrl( ) == ONEDRIVE_BASE_URL ) + { + libcmis::SessionFactory::setOAuth2AuthCodeProvider( AuthProvider::copyWebAuthCodeFallback ); + oauth2Data = boost::make_shared<libcmis::OAuth2Data>( + ONEDRIVE_AUTH_URL, ONEDRIVE_TOKEN_URL, + ONEDRIVE_SCOPE, ONEDRIVE_REDIRECT_URI, + ONEDRIVE_CLIENT_ID, ONEDRIVE_CLIENT_SECRET ); + } + + std::unique_ptr<libcmis::Session> session(libcmis::SessionFactory::createSession( + OUSTR_TO_STDSTR( m_aURL.getBindingUrl( ) ), + rUsername, rPassword, "", false, oauth2Data )); + if (!session) + ucbhelper::cancelCommandExecution( + ucb::IOErrorCode_INVALID_DEVICE, + uno::Sequence< uno::Any >( 0 ), + xEnv ); + m_aRepositories = session->getRepositories( ); + + bIsDone = true; + } + catch ( const libcmis::Exception& e ) + { + SAL_INFO( "ucb.ucp.cmis", "Error getting repositories: " << e.what() ); + + if ( e.getType() != "permissionDenied" ) + { + ucbhelper::cancelCommandExecution( + ucb::IOErrorCode_INVALID_DEVICE, + uno::Sequence< uno::Any >( 0 ), + xEnv ); + } + } + } + else + { + // Throw user cancelled exception + ucbhelper::cancelCommandExecution( + ucb::IOErrorCode_ABORT, + uno::Sequence< uno::Any >( 0 ), + xEnv, + "Authentication cancelled" ); + } + } + } + + libcmis::RepositoryPtr RepoContent::getRepository( const uno::Reference< ucb::XCommandEnvironment > & xEnv ) + { + // Ensure we have the repositories extracted + getRepositories( xEnv ); + + libcmis::RepositoryPtr repo; + + if ( !m_sRepositoryId.isEmpty() ) + { + auto it = std::find_if(m_aRepositories.begin(), m_aRepositories.end(), + [&](const libcmis::RepositoryPtr& rRepo) { return STD_TO_OUSTR(rRepo->getId()) == m_sRepositoryId; }); + if (it != m_aRepositories.end()) + repo = *it; + } + else + repo = m_aRepositories.front( ); + return repo; + } + + uno::Sequence< beans::Property > RepoContent::getProperties( + const uno::Reference< ucb::XCommandEnvironment > & /*xEnv*/ ) + { + static const beans::Property aGenericProperties[] = + { + beans::Property( "IsDocument", + -1, cppu::UnoType<bool>::get(), + beans::PropertyAttribute::BOUND | beans::PropertyAttribute::READONLY ), + beans::Property( "IsFolder", + -1, cppu::UnoType<bool>::get(), + beans::PropertyAttribute::BOUND | beans::PropertyAttribute::READONLY ), + beans::Property( "Title", + -1, cppu::UnoType<OUString>::get(), + beans::PropertyAttribute::BOUND ), + beans::Property( "IsReadOnly", + -1, cppu::UnoType<bool>::get(), + beans::PropertyAttribute::BOUND | beans::PropertyAttribute::READONLY ), + }; + + const int nProps = SAL_N_ELEMENTS(aGenericProperties); + return uno::Sequence< beans::Property > ( aGenericProperties, nProps ); + } + + uno::Sequence< ucb::CommandInfo > RepoContent::getCommands( + const uno::Reference< ucb::XCommandEnvironment > & /*xEnv*/ ) + { + static const ucb::CommandInfo aCommandInfoTable[] = + { + // Required commands + ucb::CommandInfo + ( "getCommandInfo", + -1, cppu::UnoType<void>::get() ), + ucb::CommandInfo + ( "getPropertySetInfo", + -1, cppu::UnoType<void>::get() ), + ucb::CommandInfo + ( "getPropertyValues", + -1, cppu::UnoType<uno::Sequence< beans::Property >>::get() ), + ucb::CommandInfo + ( "setPropertyValues", + -1, cppu::UnoType<uno::Sequence< beans::PropertyValue >>::get() ), + + // Optional standard commands + ucb::CommandInfo + ( "open", + -1, cppu::UnoType<ucb::OpenCommandArgument2>::get() ), + }; + + const int nProps = SAL_N_ELEMENTS(aCommandInfoTable); + return uno::Sequence< ucb::CommandInfo >(aCommandInfoTable, nProps ); + } + + OUString RepoContent::getParentURL( ) + { + SAL_INFO( "ucb.ucp.cmis", "RepoContent::getParentURL()" ); + + // TODO Implement me + + return OUString(); + } + + XTYPEPROVIDER_COMMON_IMPL( RepoContent ); + + OUString SAL_CALL RepoContent::getImplementationName() + { + return "com.sun.star.comp.CmisRepoContent"; + } + + uno::Sequence< OUString > SAL_CALL RepoContent::getSupportedServiceNames() + { + return { "com.sun.star.ucb.Content" }; + } + + OUString SAL_CALL RepoContent::getContentType() + { + return CMIS_REPO_TYPE; + } + + uno::Any SAL_CALL RepoContent::execute( + const ucb::Command& aCommand, + sal_Int32 /*CommandId*/, + const uno::Reference< ucb::XCommandEnvironment >& xEnv ) + { + SAL_INFO( "ucb.ucp.cmis", "RepoContent::execute( ) - " << aCommand.Name ); + + uno::Any aRet; + + if ( aCommand.Name == "getPropertyValues" ) + { + uno::Sequence< beans::Property > Properties; + if ( !( aCommand.Argument >>= Properties ) ) + ucbhelper::cancelCommandExecution ( getBadArgExcept (), xEnv ); + aRet <<= getPropertyValues( Properties, xEnv ); + } + else if ( aCommand.Name == "getPropertySetInfo" ) + aRet <<= getPropertySetInfo( xEnv, false ); + else if ( aCommand.Name == "getCommandInfo" ) + aRet <<= getCommandInfo( xEnv, false ); + else if ( aCommand.Name == "open" ) + { + ucb::OpenCommandArgument2 aOpenCommand; + if ( !( aCommand.Argument >>= aOpenCommand ) ) + ucbhelper::cancelCommandExecution ( getBadArgExcept (), xEnv ); + const ucb::OpenCommandArgument2& rOpenCommand = aOpenCommand; + + getRepositories( xEnv ); + uno::Reference< ucb::XDynamicResultSet > xSet + = new DynamicResultSet(m_xContext, this, rOpenCommand, xEnv ); + aRet <<= xSet; + } + else + { + SAL_INFO( "ucb.ucp.cmis", "Command not allowed" ); + } + + return aRet; + } + + void SAL_CALL RepoContent::abort( sal_Int32 /*CommandId*/ ) + { + SAL_INFO( "ucb.ucp.cmis", "TODO - RepoContent::abort()" ); + // TODO Implement me + } + + uno::Sequence< uno::Type > SAL_CALL RepoContent::getTypes() + { + static cppu::OTypeCollection s_aFolderCollection + (CPPU_TYPE_REF( lang::XTypeProvider ), + CPPU_TYPE_REF( lang::XServiceInfo ), + CPPU_TYPE_REF( lang::XComponent ), + CPPU_TYPE_REF( ucb::XContent ), + CPPU_TYPE_REF( ucb::XCommandProcessor ), + CPPU_TYPE_REF( beans::XPropertiesChangeNotifier ), + CPPU_TYPE_REF( ucb::XCommandInfoChangeNotifier ), + CPPU_TYPE_REF( beans::XPropertyContainer ), + CPPU_TYPE_REF( beans::XPropertySetInfoChangeNotifier ), + CPPU_TYPE_REF( container::XChild ) ); + return s_aFolderCollection.getTypes(); + } + + std::vector< uno::Reference< ucb::XContent > > RepoContent::getChildren( ) + { + std::vector< uno::Reference< ucb::XContent > > result; + + // TODO Cache the results somehow + SAL_INFO( "ucb.ucp.cmis", "RepoContent::getChildren" ); + + if ( m_sRepositoryId.isEmpty( ) ) + { + for ( const auto& rRepo : m_aRepositories ) + { + URL aUrl( m_aURL ); + aUrl.setObjectPath( STD_TO_OUSTR( rRepo->getId( ) ) ); + + uno::Reference< ucb::XContentIdentifier > xId = new ucbhelper::ContentIdentifier( aUrl.asString( ) ); + uno::Reference< ucb::XContent > xContent = new RepoContent( m_xContext, m_pProvider, xId, std::vector(m_aRepositories) ); + + result.push_back( xContent ); + } + } + else + { + // Return the repository root as child + OUString sUrl; + OUString sEncodedBinding = rtl::Uri::encode( + m_aURL.getBindingUrl( ) + "#" + m_sRepositoryId, + rtl_UriCharClassRelSegment, + rtl_UriEncodeKeepEscapes, + RTL_TEXTENCODING_UTF8 ); + sUrl = "vnd.libreoffice.cmis://" + sEncodedBinding; + + uno::Reference< ucb::XContentIdentifier > xId = new ucbhelper::ContentIdentifier( sUrl ); + uno::Reference< ucb::XContent > xContent = new Content( m_xContext, m_pProvider, xId ); + + result.push_back( xContent ); + } + return result; + } +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/ucb/source/ucp/cmis/cmis_repo_content.hxx b/ucb/source/ucp/cmis/cmis_repo_content.hxx new file mode 100644 index 0000000000..0f78bf0be1 --- /dev/null +++ b/ucb/source/ucp/cmis/cmis_repo_content.hxx @@ -0,0 +1,116 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ + +#pragma once + +#include "cmis_url.hxx" +#include "children_provider.hxx" + +#include <com/sun/star/io/XInputStream.hpp> +#include <com/sun/star/io/XOutputStream.hpp> +#include <com/sun/star/ucb/ContentCreationException.hpp> +#include <com/sun/star/ucb/OpenCommandArgument2.hpp> +#include <com/sun/star/ucb/TransferInfo.hpp> +#include <com/sun/star/ucb/XContentCreator.hpp> +#include <ucbhelper/contenthelper.hxx> +#include <libcmis/libcmis.hxx> + +#include <vector> +#include <list> + +namespace com::sun::star { + namespace beans { + struct Property; + struct PropertyValue; + } + namespace sdbc { + class XRow; + } +} +namespace ucbhelper +{ + class Content; +} + + +namespace cmis +{ +inline constexpr OUString CMIS_REPO_TYPE = u"application/vnd.libreoffice.cmis-repository"_ustr; + +class ContentProvider; +class RepoContent : public ::ucbhelper::ContentImplHelper, + public ChildrenProvider +{ +private: + ContentProvider* m_pProvider; + URL m_aURL; + OUString m_sRepositoryId; + + std::vector< libcmis::RepositoryPtr > m_aRepositories; + +private: + + css::uno::Any getBadArgExcept(); + + css::uno::Reference< css::sdbc::XRow > + getPropertyValues( + const css::uno::Sequence< css::beans::Property >& rProperties, + const css::uno::Reference< css::ucb::XCommandEnvironment >& xEnv ); + + /* + * Call me to ensure the repositories have been fetched + */ + void getRepositories( const css::uno::Reference< css::ucb::XCommandEnvironment >& xEnv ); + + libcmis::RepositoryPtr getRepository( const css::uno::Reference< css::ucb::XCommandEnvironment >& xEnv ); + +public: + /// @throws css::ucb::ContentCreationException + RepoContent( const css::uno::Reference< + css::uno::XComponentContext >& rxContext, ContentProvider *pProvider, + const css::uno::Reference< css::ucb::XContentIdentifier >& Identifier, + std::vector< libcmis::RepositoryPtr > && aRepos = std::vector< libcmis::RepositoryPtr > ( ) ); + + virtual ~RepoContent() override; + + virtual css::uno::Sequence< css::beans::Property > + getProperties( const css::uno::Reference< css::ucb::XCommandEnvironment > & xEnv ) override; + + virtual css::uno::Sequence< css::ucb::CommandInfo > + getCommands( const css::uno::Reference< css::ucb::XCommandEnvironment > & xEnv ) override; + + virtual OUString getParentURL() override; + + // XInterface + + virtual css::uno::Sequence< sal_Int8 > SAL_CALL getImplementationId() override; + virtual css::uno::Sequence< css::uno::Type > SAL_CALL getTypes() override; + + virtual OUString SAL_CALL + getImplementationName() override; + + virtual css::uno::Sequence< OUString > SAL_CALL + getSupportedServiceNames() override; + + virtual OUString SAL_CALL + getContentType() override; + + virtual css::uno::Any SAL_CALL + execute( const css::ucb::Command& aCommand, + sal_Int32 CommandId, + const css::uno::Reference< css::ucb::XCommandEnvironment >& Environment ) override; + + virtual void SAL_CALL abort( sal_Int32 CommandId ) override; + + virtual std::vector< css::uno::Reference< css::ucb::XContent > > getChildren( ) override; +}; + +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/ucb/source/ucp/cmis/cmis_resultset.cxx b/ucb/source/ucp/cmis/cmis_resultset.cxx new file mode 100644 index 0000000000..782953b65a --- /dev/null +++ b/ucb/source/ucp/cmis/cmis_resultset.cxx @@ -0,0 +1,44 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ + +#include "cmis_datasupplier.hxx" +#include "cmis_resultset.hxx" + +using namespace com::sun::star::lang; +using namespace com::sun::star::ucb; +using namespace com::sun::star::uno; + +namespace cmis +{ + DynamicResultSet::DynamicResultSet( + const Reference< XComponentContext >& rxContext, + ChildrenProvider* pChildrenProvider, + const OpenCommandArgument2& rCommand, + const Reference< XCommandEnvironment >& rxEnv ) : + ResultSetImplHelper( rxContext, rCommand ), + m_pChildrenProvider( pChildrenProvider ), + m_xEnv( rxEnv ) + { + } + + void DynamicResultSet::initStatic() + { + m_xResultSet1 = new ::ucbhelper::ResultSet( + m_xContext, m_aCommand.Properties, + new DataSupplier( m_pChildrenProvider, m_aCommand.Mode ), m_xEnv ); + } + + void DynamicResultSet::initDynamic() + { + initStatic(); + m_xResultSet2 = m_xResultSet1; + } +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/ucb/source/ucp/cmis/cmis_resultset.hxx b/ucb/source/ucp/cmis/cmis_resultset.hxx new file mode 100644 index 0000000000..bb113b6b26 --- /dev/null +++ b/ucb/source/ucp/cmis/cmis_resultset.hxx @@ -0,0 +1,40 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ + +#pragma once + +#include <ucbhelper/resultsethelper.hxx> +#include <com/sun/star/ucb/XCommandEnvironment.hpp> + +#include "children_provider.hxx" + +namespace cmis +{ + + class DynamicResultSet : public ::ucbhelper::ResultSetImplHelper + { + ChildrenProvider* m_pChildrenProvider; + css::uno::Reference< css::ucb::XCommandEnvironment > m_xEnv; + + private: + virtual void initStatic() override; + virtual void initDynamic() override; + + public: + + DynamicResultSet( + const css::uno::Reference< css::uno::XComponentContext >& rxContext, + ChildrenProvider* pChildrenProvider, + const css::ucb::OpenCommandArgument2& rCommand, + const css::uno::Reference< css::ucb::XCommandEnvironment >& rxEnv ); + + }; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/ucb/source/ucp/cmis/cmis_strings.hxx b/ucb/source/ucp/cmis/cmis_strings.hxx new file mode 100644 index 0000000000..d0537238bb --- /dev/null +++ b/ucb/source/ucp/cmis/cmis_strings.hxx @@ -0,0 +1,23 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + */ + +#pragma once + +#include <rtl/ustring.hxx> + +inline constexpr OUString CMIS_TYPE_STRING = u"String"_ustr; +inline constexpr OUString CMIS_TYPE_INTEGER = u"Integer"_ustr; +inline constexpr OUString CMIS_TYPE_DECIMAL = u"Decimal"_ustr; +inline constexpr OUString CMIS_TYPE_DATETIME = u"Datetime"_ustr; +inline constexpr OUString CMIS_TYPE_BOOL = u"Bool"_ustr; + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/ucb/source/ucp/cmis/cmis_url.cxx b/ucb/source/ucp/cmis/cmis_url.cxx new file mode 100644 index 0000000000..86fde73b94 --- /dev/null +++ b/ucb/source/ucp/cmis/cmis_url.cxx @@ -0,0 +1,111 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ + +#include <sal/config.h> + +#include <rtl/uri.hxx> +#include <tools/urlobj.hxx> + +#include "cmis_url.hxx" + +namespace cmis +{ + URL::URL( std::u16string_view urlStr ) + { + INetURLObject aUrl( urlStr ); + + // Decode the authority to get the binding URL and repository id + OUString sDecodedHost = aUrl.GetHost( INetURLObject::DecodeMechanism::WithCharset ); + INetURLObject aHostUrl( sDecodedHost ); + m_sBindingUrl = aHostUrl.GetURLNoMark( ); + m_sRepositoryId = aHostUrl.GetMark( ); + + m_sUser = aUrl.GetUser( INetURLObject::DecodeMechanism::WithCharset ); + m_sPass = aUrl.GetPass( INetURLObject::DecodeMechanism::WithCharset ); + + // Store the path to the object + m_sPath = aUrl.GetURLPath( INetURLObject::DecodeMechanism::WithCharset ); + m_sId = aUrl.GetMark( INetURLObject::DecodeMechanism::WithCharset ); + + if ( m_sPath == "/" && m_sBindingUrl.indexOf( "google" ) != -1 ) + m_sId = "root"; + } + + + void URL::setObjectPath( const OUString& sPath ) + { + m_sPath = sPath; + } + + void URL::setObjectId( const OUString& sId ) + { + m_sId = sId; + } + + void URL::setUsername( const OUString& sUser ) + { + m_sUser = sUser; + } + + OUString URL::asString( ) const + { + OUString sUrl; + // Related tdf#96174, can no longer save on Google Drive + // the user field may contain characters that need to be escaped according to + // RFC3896 userinfo URI field + // see <https://tools.ietf.org/html/rfc3986#section-3.2.1> + OUString sEncodedUser = ( m_sUser.isEmpty() ? + OUString() : + rtl::Uri::encode( m_sUser, rtl_UriCharClassUserinfo, + rtl_UriEncodeIgnoreEscapes, RTL_TEXTENCODING_UTF8) ); + OUString sEncodedBinding = rtl::Uri::encode( + m_sBindingUrl + "#" + m_sRepositoryId, + rtl_UriCharClassRelSegment, + rtl_UriEncodeKeepEscapes, + RTL_TEXTENCODING_UTF8 ); + sUrl = "vnd.libreoffice.cmis://" + + ( sEncodedUser.isEmpty() ? OUString( ) : (sEncodedUser + "@") ) + + sEncodedBinding; + + if ( !m_sPath.isEmpty( ) ) + { + sal_Int32 nPos = -1; + OUStringBuffer sEncodedPath; + do + { + sal_Int32 nStartPos = nPos + 1; + nPos = m_sPath.indexOf( '/', nStartPos ); + sal_Int32 nLen = nPos - nStartPos; + if ( nPos == -1 ) + nLen = m_sPath.getLength( ) - nStartPos; + OUString sSegment = m_sPath.copy( nStartPos, nLen ); + + if ( !sSegment.isEmpty( ) ) + { + sEncodedPath.append("/" + rtl::Uri::encode( sSegment, + rtl_UriCharClassRelSegment, + rtl_UriEncodeKeepEscapes, + RTL_TEXTENCODING_UTF8 )); + } + } + while ( nPos != -1 ); + sUrl += sEncodedPath; + } else if ( !m_sId.isEmpty( ) ) + { + sUrl += "#" + rtl::Uri::encode( m_sId, + rtl_UriCharClassRelSegment, + rtl_UriEncodeKeepEscapes, + RTL_TEXTENCODING_UTF8 ); + } + + return sUrl; + } +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/ucb/source/ucp/cmis/cmis_url.hxx b/ucb/source/ucp/cmis/cmis_url.hxx new file mode 100644 index 0000000000..2346bf1e01 --- /dev/null +++ b/ucb/source/ucp/cmis/cmis_url.hxx @@ -0,0 +1,42 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ +#pragma once + +#include <rtl/ustring.hxx> + +namespace cmis +{ + class URL + { + private: + OUString m_sBindingUrl; + OUString m_sRepositoryId; + OUString m_sPath; + OUString m_sId; + OUString m_sUser; + OUString m_sPass; + + public: + explicit URL( std::u16string_view urlStr ); + + const OUString& getObjectPath() const { return m_sPath; } + const OUString& getObjectId() const { return m_sId; } + const OUString& getBindingUrl() const { return m_sBindingUrl; } + const OUString& getRepositoryId() const { return m_sRepositoryId; } + const OUString& getUsername() const { return m_sUser; } + const OUString& getPassword() const { return m_sPass; } + void setObjectPath( const OUString& sPath ); + void setObjectId( const OUString& sId ); + void setUsername( const OUString& sUser ); + + OUString asString( ) const; + }; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/ucb/source/ucp/cmis/std_inputstream.cxx b/ucb/source/ucp/cmis/std_inputstream.cxx new file mode 100644 index 0000000000..548e782088 --- /dev/null +++ b/ucb/source/ucp/cmis/std_inputstream.cxx @@ -0,0 +1,183 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ + +#include <sal/config.h> + +#include <com/sun/star/io/IOException.hpp> +#include <com/sun/star/lang/IllegalArgumentException.hpp> +#include <sal/log.hxx> +#include <cppuhelper/queryinterface.hxx> +#include <utility> + +#include "std_inputstream.hxx" + +using namespace com::sun::star; + +namespace cmis +{ + StdInputStream::StdInputStream( boost::shared_ptr< std::istream > pStream ) : + m_pStream(std::move( pStream )), + m_nLength( 0 ) + { + if (m_pStream) + { + auto nInitPos = m_pStream->tellg( ); + m_pStream->seekg( 0, std::ios_base::end ); + auto nEndPos = m_pStream->tellg( ); + m_pStream->seekg( nInitPos, std::ios_base::beg ); + + m_nLength = sal_Int64( nEndPos - nInitPos ); + } + } + + StdInputStream::~StdInputStream() + { + } + + uno::Any SAL_CALL StdInputStream::queryInterface( const uno::Type& rType ) + { + uno::Any aRet = ::cppu::queryInterface( rType, + static_cast< XInputStream* >( this ), + static_cast< XSeekable* >( this ) ); + + return aRet.hasValue() ? aRet : OWeakObject::queryInterface( rType ); + } + + void SAL_CALL StdInputStream::acquire( ) noexcept + { + OWeakObject::acquire(); + } + + void SAL_CALL StdInputStream::release( ) noexcept + { + OWeakObject::release(); + } + + sal_Int32 SAL_CALL StdInputStream::readBytes( uno::Sequence< sal_Int8 >& aData, sal_Int32 nBytesToRead ) + { + std::scoped_lock aGuard( m_aMutex ); + + if ( 0 <= nBytesToRead && aData.getLength() < nBytesToRead ) + aData.realloc( nBytesToRead ); + + if (!m_pStream) + throw io::IOException( ); + + sal_Int32 nRead = 0; + try + { + m_pStream->read( reinterpret_cast< char* >( aData.getArray( ) ), nBytesToRead ); + nRead = m_pStream->gcount(); + } + catch ( const std::ios_base::failure& e ) + { + SAL_INFO( "ucb.ucp.cmis", "StdInputStream::readBytes() error: " << e.what() ); + throw io::IOException( ); + } + + return nRead; + } + + sal_Int32 SAL_CALL StdInputStream::readSomeBytes( uno::Sequence< sal_Int8 >& aData, + sal_Int32 nMaxBytesToRead ) + { + std::scoped_lock aGuard( m_aMutex ); + + if ( 0 <= nMaxBytesToRead && aData.getLength() < nMaxBytesToRead ) + aData.realloc( nMaxBytesToRead ); + + if (!m_pStream) + throw io::IOException( ); + + sal_Int32 nRead = 0; + try + { + nRead = m_pStream->readsome( reinterpret_cast< char* >( aData.getArray( ) ), nMaxBytesToRead ); + } + catch ( const std::ios_base::failure& e ) + { + SAL_INFO( "ucb.ucp.cmis", "StdInputStream::readBytes() error: " << e.what() ); + throw io::IOException( ); + } + return nRead; + } + + void SAL_CALL StdInputStream::skipBytes( sal_Int32 nBytesToSkip ) + { + std::scoped_lock aGuard( m_aMutex ); + + if (!m_pStream) + throw io::IOException( ); + + try + { + m_pStream->seekg( nBytesToSkip, std::ios_base::cur ); + } + catch ( const std::ios_base::failure& e ) + { + SAL_INFO( "ucb.ucp.cmis", "StdInputStream::readBytes() error: " << e.what() ); + throw io::IOException( ); + } + } + + sal_Int32 SAL_CALL StdInputStream::available( ) + { + return std::min<sal_Int64>( SAL_MAX_INT32, m_nLength - getPosition() ); + } + + void SAL_CALL StdInputStream::closeInput( ) + { + // No need to implement this for an istream + } + + void SAL_CALL StdInputStream::seek( sal_Int64 location ) + { + std::scoped_lock aGuard( m_aMutex ); + + if ( location < 0 || location > m_nLength ) + throw lang::IllegalArgumentException( + "Location can't be negative or greater than the length", + getXWeak(), 0 ); + + if (!m_pStream) + throw io::IOException( ); + + try + { + m_pStream->clear( ); // may be needed to rewind the stream + m_pStream->seekg( location, std::ios_base::beg ); + } + catch ( const std::ios_base::failure& e ) + { + SAL_INFO( "ucb.ucp.cmis", "StdInputStream::readBytes() error: " << e.what() ); + throw io::IOException( ); + } + } + + sal_Int64 SAL_CALL StdInputStream::getPosition( ) + { + std::scoped_lock aGuard( m_aMutex ); + + if (!m_pStream) + throw io::IOException( ); + + sal_Int64 nPos = m_pStream->tellg( ); + if ( -1 == nPos ) + throw io::IOException( ); + + return nPos; + } + + sal_Int64 SAL_CALL StdInputStream::getLength( ) + { + return m_nLength; + } +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/ucb/source/ucp/cmis/std_inputstream.hxx b/ucb/source/ucp/cmis/std_inputstream.hxx new file mode 100644 index 0000000000..1904d7ebe9 --- /dev/null +++ b/ucb/source/ucp/cmis/std_inputstream.hxx @@ -0,0 +1,83 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ + +#pragma once + +#include <boost/shared_ptr.hpp> +#include <istream> + +#include <mutex> +#include <cppuhelper/weak.hxx> +#include <com/sun/star/io/XInputStream.hpp> +#include <com/sun/star/io/XSeekable.hpp> + +namespace cmis +{ + /** Implements a seekable InputStream + * working on an std::istream + */ + class StdInputStream + : public cppu::OWeakObject, + public css::io::XInputStream, + public css::io::XSeekable + { + public: + + StdInputStream( boost::shared_ptr< std::istream > pStream ); + + virtual ~StdInputStream() override; + + virtual css::uno::Any SAL_CALL queryInterface ( const css::uno::Type& rType ) override; + + virtual void SAL_CALL acquire( ) noexcept override; + + virtual void SAL_CALL release( ) noexcept override; + + virtual sal_Int32 SAL_CALL + readBytes ( css::uno::Sequence< sal_Int8 >& aData, + sal_Int32 nBytesToRead ) override; + + virtual sal_Int32 SAL_CALL + readSomeBytes ( css::uno::Sequence< sal_Int8 >& aData, + sal_Int32 nMaxBytesToRead ) override; + + virtual void SAL_CALL + skipBytes ( sal_Int32 nBytesToSkip ) override; + + virtual sal_Int32 SAL_CALL + available ( ) override; + + virtual void SAL_CALL + closeInput ( ) override; + + + /** XSeekable + */ + + virtual void SAL_CALL + seek ( sal_Int64 location ) override; + + + virtual sal_Int64 SAL_CALL + getPosition ( ) override; + + + virtual sal_Int64 SAL_CALL + getLength ( ) override; + + private: + + std::mutex m_aMutex; + boost::shared_ptr< std::istream > m_pStream; + sal_Int64 m_nLength; + }; + +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/ucb/source/ucp/cmis/std_outputstream.cxx b/ucb/source/ucp/cmis/std_outputstream.cxx new file mode 100644 index 0000000000..ead2147b93 --- /dev/null +++ b/ucb/source/ucp/cmis/std_outputstream.cxx @@ -0,0 +1,98 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ + +#include <sal/config.h> + +#include <com/sun/star/io/IOException.hpp> +#include <sal/log.hxx> +#include <cppuhelper/queryinterface.hxx> +#include <utility> + +#include "std_outputstream.hxx" + +using namespace com::sun::star; + +namespace cmis +{ + StdOutputStream::StdOutputStream( boost::shared_ptr< std::ostream > pStream ) : + m_pStream(std::move( pStream )) + { + } + + StdOutputStream::~StdOutputStream() + { + if (m_pStream) + m_pStream->setstate( std::ios::eofbit ); + } + + uno::Any SAL_CALL StdOutputStream::queryInterface( const uno::Type& rType ) + { + uno::Any aRet = ::cppu::queryInterface( rType, static_cast< XOutputStream* >( this ) ); + + return aRet.hasValue() ? aRet : OWeakObject::queryInterface( rType ); + } + + void SAL_CALL StdOutputStream::acquire( ) noexcept + { + OWeakObject::acquire(); + } + + void SAL_CALL StdOutputStream::release( ) noexcept + { + OWeakObject::release(); + } + + void SAL_CALL StdOutputStream::writeBytes ( const uno::Sequence< sal_Int8 >& aData ) + { + std::scoped_lock aGuard( m_aMutex ); + + if (!m_pStream) + throw io::IOException( ); + + try + { + m_pStream->write( reinterpret_cast< const char* >( aData.getConstArray( ) ), aData.getLength( ) ); + } + catch ( const std::ios_base::failure& e ) + { + SAL_INFO( "ucb.ucp.cmis", "Exception caught when calling write: " << e.what() ); + throw io::IOException( ); + } + } + + void SAL_CALL StdOutputStream::flush ( ) + { + std::scoped_lock aGuard( m_aMutex ); + + if (!m_pStream) + throw io::IOException( ); + + try + { + m_pStream->flush( ); + } + catch ( const std::ios_base::failure& e ) + { + SAL_INFO( "ucb.ucp.cmis", "Exception caught when calling flush: " << e.what() ); + throw io::IOException( ); + } + } + + void SAL_CALL StdOutputStream::closeOutput ( ) + { + std::scoped_lock aGuard( m_aMutex ); + + if (!m_pStream) + throw io::IOException( ); + + m_pStream->setstate( std::ios_base::eofbit ); + } +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/ucb/source/ucp/cmis/std_outputstream.hxx b/ucb/source/ucp/cmis/std_outputstream.hxx new file mode 100644 index 0000000000..3201e14efe --- /dev/null +++ b/ucb/source/ucp/cmis/std_outputstream.hxx @@ -0,0 +1,53 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ + +#pragma once + +#include <boost/shared_ptr.hpp> +#include <ostream> + +#include <mutex> +#include <cppuhelper/weak.hxx> +#include <com/sun/star/io/XOutputStream.hpp> + +namespace cmis +{ + /** Implements a OutputStream + * working on an std::ostream + */ + class StdOutputStream : + public cppu::OWeakObject, + public css::io::XOutputStream + { + public: + + StdOutputStream( boost::shared_ptr< std::ostream > pStream ); + + virtual ~StdOutputStream( ) override; + + virtual css::uno::Any SAL_CALL queryInterface( const css::uno::Type& rType ) override; + + virtual void SAL_CALL acquire ( ) noexcept override; + + virtual void SAL_CALL release ( ) noexcept override; + + virtual void SAL_CALL writeBytes ( const css::uno::Sequence< sal_Int8 >& aData ) override; + + virtual void SAL_CALL flush ( ) override; + + virtual void SAL_CALL closeOutput ( ) override; + + private: + + std::mutex m_aMutex; + boost::shared_ptr< std::ostream > m_pStream; + }; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/ucb/source/ucp/cmis/ucpcmis1.component b/ucb/source/ucp/cmis/ucpcmis1.component new file mode 100644 index 0000000000..101a892a06 --- /dev/null +++ b/ucb/source/ucp/cmis/ucpcmis1.component @@ -0,0 +1,16 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + --> + +<component loader="com.sun.star.loader.SharedLibrary" environment="@CPPU_ENV@" + xmlns="http://openoffice.org/2010/uno-components"> + <implementation name="com.sun.star.comp.CmisContentProvider" + constructor="ucb_cmis_ContentProvider_get_implementation" single-instance="true"> + <service name="com.sun.star.ucb.CmisContentProvider"/> + </implementation> +</component> |