summaryrefslogtreecommitdiffstats
path: root/ucb/source/ucp/cmis
diff options
context:
space:
mode:
Diffstat (limited to 'ucb/source/ucp/cmis')
-rw-r--r--ucb/source/ucp/cmis/auth_provider.cxx212
-rw-r--r--ucb/source/ucp/cmis/auth_provider.hxx47
-rw-r--r--ucb/source/ucp/cmis/certvalidation_handler.cxx126
-rw-r--r--ucb/source/ucp/cmis/certvalidation_handler.hxx46
-rw-r--r--ucb/source/ucp/cmis/children_provider.hxx26
-rw-r--r--ucb/source/ucp/cmis/cmis_content.cxx2099
-rw-r--r--ucb/source/ucp/cmis/cmis_content.hxx210
-rw-r--r--ucb/source/ucp/cmis/cmis_datasupplier.cxx157
-rw-r--r--ucb/source/ucp/cmis/cmis_datasupplier.hxx70
-rw-r--r--ucb/source/ucp/cmis/cmis_provider.cxx142
-rw-r--r--ucb/source/ucp/cmis/cmis_provider.hxx56
-rw-r--r--ucb/source/ucp/cmis/cmis_repo_content.cxx424
-rw-r--r--ucb/source/ucp/cmis/cmis_repo_content.hxx116
-rw-r--r--ucb/source/ucp/cmis/cmis_resultset.cxx44
-rw-r--r--ucb/source/ucp/cmis/cmis_resultset.hxx40
-rw-r--r--ucb/source/ucp/cmis/cmis_strings.hxx23
-rw-r--r--ucb/source/ucp/cmis/cmis_url.cxx111
-rw-r--r--ucb/source/ucp/cmis/cmis_url.hxx42
-rw-r--r--ucb/source/ucp/cmis/std_inputstream.cxx183
-rw-r--r--ucb/source/ucp/cmis/std_inputstream.hxx83
-rw-r--r--ucb/source/ucp/cmis/std_outputstream.cxx98
-rw-r--r--ucb/source/ucp/cmis/std_outputstream.hxx53
-rw-r--r--ucb/source/ucp/cmis/ucpcmis1.component16
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>