summaryrefslogtreecommitdiffstats
path: root/ucb/source/ucp
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-15 05:54:39 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-15 05:54:39 +0000
commit267c6f2ac71f92999e969232431ba04678e7437e (patch)
tree358c9467650e1d0a1d7227a21dac2e3d08b622b2 /ucb/source/ucp
parentInitial commit. (diff)
downloadlibreoffice-267c6f2ac71f92999e969232431ba04678e7437e.tar.xz
libreoffice-267c6f2ac71f92999e969232431ba04678e7437e.zip
Adding upstream version 4:24.2.0.upstream/4%24.2.0
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'ucb/source/ucp')
-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
-rw-r--r--ucb/source/ucp/expand/ucpexpand.cxx176
-rw-r--r--ucb/source/ucp/expand/ucpexpand1.component26
-rw-r--r--ucb/source/ucp/ext/ucpext.component26
-rw-r--r--ucb/source/ucp/ext/ucpext_content.cxx624
-rw-r--r--ucb/source/ucp/ext/ucpext_content.hxx134
-rw-r--r--ucb/source/ucp/ext/ucpext_datasupplier.cxx314
-rw-r--r--ucb/source/ucp/ext/ucpext_datasupplier.hxx89
-rw-r--r--ucb/source/ucp/ext/ucpext_provider.cxx169
-rw-r--r--ucb/source/ucp/ext/ucpext_provider.hxx54
-rw-r--r--ucb/source/ucp/ext/ucpext_resultset.cxx76
-rw-r--r--ucb/source/ucp/ext/ucpext_resultset.hxx61
-rw-r--r--ucb/source/ucp/file/bc.cxx1170
-rw-r--r--ucb/source/ucp/file/bc.hxx257
-rw-r--r--ucb/source/ucp/file/filcmd.cxx123
-rw-r--r--ucb/source/ucp/file/filcmd.hxx80
-rw-r--r--ucb/source/ucp/file/filerror.hxx105
-rw-r--r--ucb/source/ucp/file/filglob.cxx855
-rw-r--r--ucb/source/ucp/file/filglob.hxx95
-rw-r--r--ucb/source/ucp/file/filid.cxx61
-rw-r--r--ucb/source/ucp/file/filid.hxx55
-rw-r--r--ucb/source/ucp/file/filinl.hxx58
-rw-r--r--ucb/source/ucp/file/filinpstr.cxx165
-rw-r--r--ucb/source/ucp/file/filinpstr.hxx87
-rw-r--r--ucb/source/ucp/file/filinsreq.cxx77
-rw-r--r--ucb/source/ucp/file/filinsreq.hxx142
-rw-r--r--ucb/source/ucp/file/filnot.cxx208
-rw-r--r--ucb/source/ucp/file/filnot.hxx116
-rw-r--r--ucb/source/ucp/file/filprp.cxx94
-rw-r--r--ucb/source/ucp/file/filprp.hxx53
-rw-r--r--ucb/source/ucp/file/filrec.cxx192
-rw-r--r--ucb/source/ucp/file/filrec.hxx76
-rw-r--r--ucb/source/ucp/file/filrow.cxx293
-rw-r--r--ucb/source/ucp/file/filrow.hxx114
-rw-r--r--ucb/source/ucp/file/filrset.cxx679
-rw-r--r--ucb/source/ucp/file/filrset.hxx434
-rw-r--r--ucb/source/ucp/file/filstr.cxx288
-rw-r--r--ucb/source/ucp/file/filstr.hxx155
-rw-r--r--ucb/source/ucp/file/filtask.cxx2936
-rw-r--r--ucb/source/ucp/file/filtask.hxx647
-rw-r--r--ucb/source/ucp/file/prov.cxx447
-rw-r--r--ucb/source/ucp/file/prov.hxx157
-rw-r--r--ucb/source/ucp/file/ucpfile1.component26
-rw-r--r--ucb/source/ucp/gio/gio_content.cxx1347
-rw-r--r--ucb/source/ucp/gio/gio_content.hxx187
-rw-r--r--ucb/source/ucp/gio/gio_datasupplier.cxx257
-rw-r--r--ucb/source/ucp/gio/gio_datasupplier.hxx88
-rw-r--r--ucb/source/ucp/gio/gio_inputstream.cxx91
-rw-r--r--ucb/source/ucp/gio/gio_inputstream.hxx58
-rw-r--r--ucb/source/ucp/gio/gio_mount.cxx211
-rw-r--r--ucb/source/ucp/gio/gio_mount.hxx88
-rw-r--r--ucb/source/ucp/gio/gio_outputstream.cxx78
-rw-r--r--ucb/source/ucp/gio/gio_outputstream.hxx62
-rw-r--r--ucb/source/ucp/gio/gio_provider.cxx139
-rw-r--r--ucb/source/ucp/gio/gio_provider.hxx52
-rw-r--r--ucb/source/ucp/gio/gio_resultset.cxx55
-rw-r--r--ucb/source/ucp/gio/gio_resultset.hxx46
-rw-r--r--ucb/source/ucp/gio/gio_seekable.cxx126
-rw-r--r--ucb/source/ucp/gio/gio_seekable.hxx60
-rw-r--r--ucb/source/ucp/gio/ucpgio.component26
-rw-r--r--ucb/source/ucp/hierarchy/dynamicresultset.cxx78
-rw-r--r--ucb/source/ucp/hierarchy/dynamicresultset.hxx45
-rw-r--r--ucb/source/ucp/hierarchy/hierarchycontent.cxx1776
-rw-r--r--ucb/source/ucp/hierarchy/hierarchycontent.hxx255
-rw-r--r--ucb/source/ucp/hierarchy/hierarchycontentcaps.cxx681
-rw-r--r--ucb/source/ucp/hierarchy/hierarchydata.cxx1097
-rw-r--r--ucb/source/ucp/hierarchy/hierarchydata.hxx134
-rw-r--r--ucb/source/ucp/hierarchy/hierarchydatasource.cxx863
-rw-r--r--ucb/source/ucp/hierarchy/hierarchydatasource.hxx83
-rw-r--r--ucb/source/ucp/hierarchy/hierarchydatasupplier.cxx369
-rw-r--r--ucb/source/ucp/hierarchy/hierarchydatasupplier.hxx93
-rw-r--r--ucb/source/ucp/hierarchy/hierarchyprovider.cxx244
-rw-r--r--ucb/source/ucp/hierarchy/hierarchyprovider.hxx101
-rw-r--r--ucb/source/ucp/hierarchy/hierarchyuri.cxx178
-rw-r--r--ucb/source/ucp/hierarchy/hierarchyuri.hxx67
-rw-r--r--ucb/source/ucp/hierarchy/ucphier1.component31
-rw-r--r--ucb/source/ucp/image/ucpimage.component17
-rw-r--r--ucb/source/ucp/image/ucpimage.cxx165
-rw-r--r--ucb/source/ucp/inc/urihelper.hxx107
-rw-r--r--ucb/source/ucp/package/pkgcontent.cxx2689
-rw-r--r--ucb/source/ucp/package/pkgcontent.hxx276
-rw-r--r--ucb/source/ucp/package/pkgcontentcaps.cxx504
-rw-r--r--ucb/source/ucp/package/pkgdatasupplier.cxx425
-rw-r--r--ucb/source/ucp/package/pkgdatasupplier.hxx87
-rw-r--r--ucb/source/ucp/package/pkgprovider.cxx272
-rw-r--r--ucb/source/ucp/package/pkgprovider.hxx86
-rw-r--r--ucb/source/ucp/package/pkgresultset.cxx79
-rw-r--r--ucb/source/ucp/package/pkgresultset.hxx47
-rw-r--r--ucb/source/ucp/package/pkguri.cxx234
-rw-r--r--ucb/source/ucp/package/pkguri.hxx89
-rw-r--r--ucb/source/ucp/package/ucppkg1.component26
-rw-r--r--ucb/source/ucp/tdoc/tdoc_content.cxx2843
-rw-r--r--ucb/source/ucp/tdoc/tdoc_content.hxx283
-rw-r--r--ucb/source/ucp/tdoc/tdoc_contentcaps.cxx631
-rw-r--r--ucb/source/ucp/tdoc/tdoc_datasupplier.cxx394
-rw-r--r--ucb/source/ucp/tdoc/tdoc_datasupplier.hxx94
-rw-r--r--ucb/source/ucp/tdoc/tdoc_docmgr.cxx692
-rw-r--r--ucb/source/ucp/tdoc/tdoc_docmgr.hxx159
-rw-r--r--ucb/source/ucp/tdoc/tdoc_documentcontentfactory.cxx115
-rw-r--r--ucb/source/ucp/tdoc/tdoc_documentcontentfactory.hxx58
-rw-r--r--ucb/source/ucp/tdoc/tdoc_passwordrequest.cxx191
-rw-r--r--ucb/source/ucp/tdoc/tdoc_passwordrequest.hxx87
-rw-r--r--ucb/source/ucp/tdoc/tdoc_provider.cxx574
-rw-r--r--ucb/source/ucp/tdoc/tdoc_provider.hxx140
-rw-r--r--ucb/source/ucp/tdoc/tdoc_resultset.cxx79
-rw-r--r--ucb/source/ucp/tdoc/tdoc_resultset.hxx47
-rw-r--r--ucb/source/ucp/tdoc/tdoc_stgelems.cxx882
-rw-r--r--ucb/source/ucp/tdoc/tdoc_stgelems.hxx341
-rw-r--r--ucb/source/ucp/tdoc/tdoc_storage.cxx618
-rw-r--r--ucb/source/ucp/tdoc/tdoc_storage.hxx162
-rw-r--r--ucb/source/ucp/tdoc/tdoc_uri.cxx111
-rw-r--r--ucb/source/ucp/tdoc/tdoc_uri.hxx108
-rw-r--r--ucb/source/ucp/tdoc/ucptdoc1.component30
-rw-r--r--ucb/source/ucp/webdav-curl/ContentProperties.cxx566
-rw-r--r--ucb/source/ucp/webdav-curl/ContentProperties.hxx174
-rw-r--r--ucb/source/ucp/webdav-curl/CurlSession.cxx2442
-rw-r--r--ucb/source/ucp/webdav-curl/CurlSession.hxx145
-rw-r--r--ucb/source/ucp/webdav-curl/CurlUri.cxx330
-rw-r--r--ucb/source/ucp/webdav-curl/CurlUri.hxx94
-rw-r--r--ucb/source/ucp/webdav-curl/DAVAuthListener.hxx43
-rw-r--r--ucb/source/ucp/webdav-curl/DAVAuthListenerImpl.hxx62
-rw-r--r--ucb/source/ucp/webdav-curl/DAVException.hxx176
-rw-r--r--ucb/source/ucp/webdav-curl/DAVProperties.cxx204
-rw-r--r--ucb/source/ucp/webdav-curl/DAVProperties.hxx57
-rw-r--r--ucb/source/ucp/webdav-curl/DAVRequestEnvironment.hxx46
-rw-r--r--ucb/source/ucp/webdav-curl/DAVResource.hxx58
-rw-r--r--ucb/source/ucp/webdav-curl/DAVResourceAccess.cxx1182
-rw-r--r--ucb/source/ucp/webdav-curl/DAVResourceAccess.hxx219
-rw-r--r--ucb/source/ucp/webdav-curl/DAVSession.hxx196
-rw-r--r--ucb/source/ucp/webdav-curl/DAVSessionFactory.cxx81
-rw-r--r--ucb/source/ucp/webdav-curl/DAVSessionFactory.hxx74
-rw-r--r--ucb/source/ucp/webdav-curl/DAVTypes.cxx207
-rw-r--r--ucb/source/ucp/webdav-curl/DAVTypes.hxx200
-rw-r--r--ucb/source/ucp/webdav-curl/DateTimeHelper.cxx258
-rw-r--r--ucb/source/ucp/webdav-curl/DateTimeHelper.hxx55
-rw-r--r--ucb/source/ucp/webdav-curl/PropertyMap.hxx55
-rw-r--r--ucb/source/ucp/webdav-curl/PropfindCache.cxx93
-rw-r--r--ucb/source/ucp/webdav-curl/PropfindCache.hxx80
-rw-r--r--ucb/source/ucp/webdav-curl/SerfLockStore.cxx255
-rw-r--r--ucb/source/ucp/webdav-curl/SerfLockStore.hxx92
-rw-r--r--ucb/source/ucp/webdav-curl/UCBDeadPropertyValue.cxx220
-rw-r--r--ucb/source/ucp/webdav-curl/UCBDeadPropertyValue.hxx45
-rw-r--r--ucb/source/ucp/webdav-curl/ucpdav1.component26
-rw-r--r--ucb/source/ucp/webdav-curl/webdavcontent.cxx4305
-rw-r--r--ucb/source/ucp/webdav-curl/webdavcontent.hxx305
-rw-r--r--ucb/source/ucp/webdav-curl/webdavcontentcaps.cxx636
-rw-r--r--ucb/source/ucp/webdav-curl/webdavdatasupplier.cxx441
-rw-r--r--ucb/source/ucp/webdav-curl/webdavdatasupplier.hxx92
-rw-r--r--ucb/source/ucp/webdav-curl/webdavprovider.cxx174
-rw-r--r--ucb/source/ucp/webdav-curl/webdavprovider.hxx100
-rw-r--r--ucb/source/ucp/webdav-curl/webdavresponseparser.cxx1001
-rw-r--r--ucb/source/ucp/webdav-curl/webdavresponseparser.hxx38
-rw-r--r--ucb/source/ucp/webdav-curl/webdavresultset.cxx76
-rw-r--r--ucb/source/ucp/webdav-curl/webdavresultset.hxx48
176 files changed, 54177 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>
diff --git a/ucb/source/ucp/expand/ucpexpand.cxx b/ucb/source/ucp/expand/ucpexpand.cxx
new file mode 100644
index 0000000000..413100bc54
--- /dev/null
+++ b/ucb/source/ucp/expand/ucpexpand.cxx
@@ -0,0 +1,176 @@
+/* -*- 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:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+
+#include <rtl/uri.hxx>
+#include <comphelper/compbase.hxx>
+#include <cppuhelper/supportsservice.hxx>
+#include <ucbhelper/content.hxx>
+#include <com/sun/star/uno/XComponentContext.hpp>
+#include <com/sun/star/lang/DisposedException.hpp>
+#include <com/sun/star/lang/XServiceInfo.hpp>
+#include <com/sun/star/util/theMacroExpander.hpp>
+#include <com/sun/star/ucb/IllegalIdentifierException.hpp>
+#include <com/sun/star/ucb/XContentProvider.hpp>
+#include <comphelper/diagnose_ex.hxx>
+
+
+using namespace ::com::sun::star;
+
+namespace
+{
+
+typedef comphelper::WeakComponentImplHelper<
+ lang::XServiceInfo, ucb::XContentProvider > t_impl_helper;
+
+
+class ExpandContentProviderImpl : public t_impl_helper
+{
+ uno::Reference< uno::XComponentContext > m_xComponentContext;
+ uno::Reference< util::XMacroExpander > m_xMacroExpander;
+ OUString expandUri(
+ uno::Reference< ucb::XContentIdentifier > const & xIdentifier ) const;
+
+protected:
+ void check() const;
+
+public:
+ explicit ExpandContentProviderImpl(
+ uno::Reference< uno::XComponentContext > const & xComponentContext )
+ : m_xComponentContext( xComponentContext ),
+ m_xMacroExpander( util::theMacroExpander::get(xComponentContext) )
+ {}
+
+ // XServiceInfo
+ virtual OUString SAL_CALL getImplementationName() override;
+ virtual sal_Bool SAL_CALL supportsService( OUString const & serviceName ) override;
+ virtual uno::Sequence< OUString > SAL_CALL getSupportedServiceNames() override;
+
+ // XContentProvider
+ virtual uno::Reference< ucb::XContent > SAL_CALL queryContent(
+ uno::Reference< ucb::XContentIdentifier > const & xIdentifier ) override;
+ virtual sal_Int32 SAL_CALL compareContentIds(
+ uno::Reference< ucb::XContentIdentifier > const & xId1,
+ uno::Reference< ucb::XContentIdentifier > const & xId2 ) override;
+};
+
+
+void ExpandContentProviderImpl::check() const
+{
+ // xxx todo guard?
+// MutexGuard guard( m_mutex );
+ if (m_bDisposed)
+ {
+ throw lang::DisposedException(
+ "expand content provider instance has "
+ "already been disposed!",
+ const_cast< ExpandContentProviderImpl * >(this)->getXWeak() );
+ }
+}
+
+// XServiceInfo
+
+OUString ExpandContentProviderImpl::getImplementationName()
+{
+ check();
+ return "com.sun.star.comp.ucb.ExpandContentProvider";
+}
+
+
+uno::Sequence< OUString > ExpandContentProviderImpl::getSupportedServiceNames()
+{
+ check();
+ return {
+ "com.sun.star.ucb.ExpandContentProvider",
+ "com.sun.star.ucb.ContentProvider"
+ };
+}
+
+sal_Bool ExpandContentProviderImpl::supportsService(OUString const & serviceName )
+{
+ return cppu::supportsService(this, serviceName);
+}
+
+OUString ExpandContentProviderImpl::expandUri(
+ uno::Reference< ucb::XContentIdentifier > const & xIdentifier ) const
+{
+ OUString uri( xIdentifier->getContentIdentifier() );
+ if (!uri.startsWithIgnoreAsciiCase("vnd.sun.star.expand:", &uri))
+ {
+ throw ucb::IllegalIdentifierException(
+ "expected protocol vnd.sun.star.expand!",
+ const_cast< ExpandContentProviderImpl * >(this)->getXWeak() );
+ }
+ // decode uric class chars
+ OUString str = ::rtl::Uri::decode(uri, rtl_UriDecodeWithCharset, RTL_TEXTENCODING_UTF8);
+ // expand macro string
+ return m_xMacroExpander->expandMacros( str );
+}
+
+// XContentProvider
+
+uno::Reference< ucb::XContent > ExpandContentProviderImpl::queryContent(
+ uno::Reference< ucb::XContentIdentifier > const & xIdentifier )
+{
+ check();
+ OUString uri( expandUri( xIdentifier ) );
+
+ ::ucbhelper::Content ucb_content;
+ if (::ucbhelper::Content::create(
+ uri, uno::Reference< ucb::XCommandEnvironment >(),
+ m_xComponentContext, ucb_content ))
+ {
+ return ucb_content.get();
+ }
+ else
+ {
+ return uno::Reference< ucb::XContent >();
+ }
+}
+
+
+sal_Int32 ExpandContentProviderImpl::compareContentIds(
+ uno::Reference< ucb::XContentIdentifier > const & xId1,
+ uno::Reference< ucb::XContentIdentifier > const & xId2 )
+{
+ check();
+ try
+ {
+ OUString uri1( expandUri( xId1 ) );
+ OUString uri2( expandUri( xId2 ) );
+ return uri1.compareTo( uri2 );
+ }
+ catch (const ucb::IllegalIdentifierException &)
+ {
+ TOOLS_WARN_EXCEPTION( "ucb", "" );
+ return -1;
+ }
+}
+
+
+}
+
+extern "C" SAL_DLLPUBLIC_EXPORT css::uno::XInterface*
+ucb_expand_ExpandContentProviderImpl_get_implementation(
+ css::uno::XComponentContext* context, css::uno::Sequence<css::uno::Any> const&)
+{
+ return cppu::acquire(new ExpandContentProviderImpl(context));
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/ucb/source/ucp/expand/ucpexpand1.component b/ucb/source/ucp/expand/ucpexpand1.component
new file mode 100644
index 0000000000..480974a4d3
--- /dev/null
+++ b/ucb/source/ucp/expand/ucpexpand1.component
@@ -0,0 +1,26 @@
+<?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/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-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.ucb.ExpandContentProvider"
+ constructor="ucb_expand_ExpandContentProviderImpl_get_implementation">
+ <service name="com.sun.star.ucb.ExpandContentProvider"/>
+ </implementation>
+</component>
diff --git a/ucb/source/ucp/ext/ucpext.component b/ucb/source/ucp/ext/ucpext.component
new file mode 100644
index 0000000000..879837e5f0
--- /dev/null
+++ b/ucb/source/ucp/ext/ucpext.component
@@ -0,0 +1,26 @@
+<?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/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ -->
+
+<component loader="com.sun.star.loader.SharedLibrary" environment="@CPPU_ENV@"
+ xmlns="http://openoffice.org/2010/uno-components">
+ <implementation name="org.openoffice.comp.ucp.ext.ContentProvider"
+ constructor="ucb_ext_ContentProvider_get_implementation" single-instance="true">
+ <service name="com.sun.star.ucb.ExtensionContentProvider"/>
+ </implementation>
+</component>
diff --git a/ucb/source/ucp/ext/ucpext_content.cxx b/ucb/source/ucp/ext/ucpext_content.cxx
new file mode 100644
index 0000000000..ee9966a52b
--- /dev/null
+++ b/ucb/source/ucp/ext/ucpext_content.cxx
@@ -0,0 +1,624 @@
+/* -*- 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:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include "ucpext_content.hxx"
+#include "ucpext_provider.hxx"
+#include "ucpext_resultset.hxx"
+
+#include <com/sun/star/beans/PropertyAttribute.hpp>
+#include <com/sun/star/beans/XPropertySetInfo.hpp>
+#include <com/sun/star/lang/IllegalAccessException.hpp>
+#include <com/sun/star/sdbc/XRow.hpp>
+#include <com/sun/star/ucb/XCommandInfo.hpp>
+#include <com/sun/star/ucb/OpenCommandArgument2.hpp>
+#include <com/sun/star/ucb/OpenMode.hpp>
+#include <com/sun/star/ucb/UnsupportedCommandException.hpp>
+#include <com/sun/star/ucb/XDynamicResultSet.hpp>
+#include <com/sun/star/deployment/PackageInformationProvider.hpp>
+
+#include <o3tl/string_view.hxx>
+#include <ucbhelper/propertyvalueset.hxx>
+#include <ucbhelper/cancelcommandexecution.hxx>
+#include <ucbhelper/content.hxx>
+#include <comphelper/diagnose_ex.hxx>
+#include <rtl/ustrbuf.hxx>
+#include <rtl/uri.hxx>
+#include <sal/macros.h>
+#include <sal/log.hxx>
+
+#include <algorithm>
+#include <string_view>
+
+
+namespace ucb::ucp::ext
+{
+
+
+ using ::com::sun::star::uno::Reference;
+ using ::com::sun::star::uno::UNO_SET_THROW;
+ using ::com::sun::star::uno::Exception;
+ using ::com::sun::star::uno::Any;
+ using ::com::sun::star::uno::Sequence;
+ using ::com::sun::star::uno::XComponentContext;
+ using ::com::sun::star::ucb::XContentIdentifier;
+ using ::com::sun::star::ucb::XCommandEnvironment;
+ using ::com::sun::star::ucb::Command;
+ using ::com::sun::star::beans::Property;
+ using ::com::sun::star::lang::IllegalArgumentException;
+ using ::com::sun::star::beans::PropertyValue;
+ using ::com::sun::star::ucb::OpenCommandArgument2;
+ using ::com::sun::star::ucb::XDynamicResultSet;
+ using ::com::sun::star::ucb::UnsupportedCommandException;
+ using ::com::sun::star::sdbc::XRow;
+ using ::com::sun::star::beans::PropertyChangeEvent;
+ using ::com::sun::star::lang::IllegalAccessException;
+ using ::com::sun::star::ucb::CommandInfo;
+ using ::com::sun::star::deployment::PackageInformationProvider;
+ using ::com::sun::star::deployment::XPackageInformationProvider;
+
+ namespace OpenMode = ::com::sun::star::ucb::OpenMode;
+ namespace PropertyAttribute = ::com::sun::star::beans::PropertyAttribute;
+
+
+ //= helper
+
+ namespace
+ {
+
+ OUString lcl_compose( std::u16string_view i_rBaseURL, const OUString& i_rRelativeURL )
+ {
+ ENSURE_OR_RETURN( !i_rBaseURL.empty(), "illegal base URL", i_rRelativeURL );
+
+ OUStringBuffer aComposer( i_rBaseURL );
+ if ( !o3tl::ends_with(i_rBaseURL, u"/") )
+ aComposer.append( '/' );
+ aComposer.append( i_rRelativeURL );
+ return aComposer.makeStringAndClear();
+ }
+
+
+ struct SelectPropertyName
+ {
+ const OUString& operator()( const Property& i_rProperty ) const
+ {
+ return i_rProperty.Name;
+ }
+ };
+ }
+
+
+ //= Content
+
+
+ Content::Content( const Reference< XComponentContext >& rxContext, ::ucbhelper::ContentProviderImplHelper* i_pProvider,
+ const Reference< XContentIdentifier >& i_rIdentifier )
+ :Content_Base( rxContext, i_pProvider, i_rIdentifier )
+ ,m_eExtContentType( E_UNKNOWN )
+ {
+ const OUString sURL( getIdentifier()->getContentIdentifier() );
+ if ( denotesRootContent( sURL ) )
+ {
+ m_eExtContentType = E_ROOT;
+ }
+ else
+ {
+ const std::u16string_view sRelativeURL( sURL.subView( ContentProvider::getRootURL().getLength() ) );
+ const size_t nSepPos = sRelativeURL.find( '/' );
+ if ( ( nSepPos == std::u16string_view::npos ) || ( nSepPos == sRelativeURL.size() - 1 ) )
+ {
+ m_eExtContentType = E_EXTENSION_ROOT;
+ }
+ else
+ {
+ m_eExtContentType = E_EXTENSION_CONTENT;
+ }
+ }
+
+ if ( m_eExtContentType == E_ROOT )
+ return;
+
+ const OUString sRootURL = ContentProvider::getRootURL();
+ m_sExtensionId = sURL.copy( sRootURL.getLength() );
+
+ const sal_Int32 nNextSep = m_sExtensionId.indexOf( '/' );
+ if ( nNextSep > -1 )
+ {
+ m_sPathIntoExtension = m_sExtensionId.copy( nNextSep + 1 );
+ m_sExtensionId = m_sExtensionId.copy( 0, nNextSep );
+ }
+ m_sExtensionId = Content::decodeIdentifier( m_sExtensionId );
+ }
+
+
+ Content::~Content()
+ {
+ }
+
+
+ OUString SAL_CALL Content::getImplementationName()
+ {
+ return "org.openoffice.comp.ucp.ext.Content";
+ }
+
+
+ Sequence< OUString > SAL_CALL Content::getSupportedServiceNames()
+ {
+ return { "com.sun.star.ucb.Content", "com.sun.star.ucb.ExtensionContent" };
+ }
+
+
+ OUString SAL_CALL Content::getContentType()
+ {
+ impl_determineContentType();
+ return *m_aContentType;
+ }
+
+
+ Any SAL_CALL Content::execute( const Command& aCommand, sal_Int32 /* CommandId */, const Reference< XCommandEnvironment >& i_rEnvironment )
+ {
+ Any aRet;
+
+ if ( aCommand.Name == "getPropertyValues" )
+ {
+ Sequence< Property > Properties;
+ if ( !( aCommand.Argument >>= Properties ) )
+ {
+ ::ucbhelper::cancelCommandExecution( Any( IllegalArgumentException(
+ OUString(), *this, -1 ) ),
+ i_rEnvironment );
+ // unreachable
+ }
+
+ aRet <<= getPropertyValues( Properties, i_rEnvironment );
+ }
+ else if ( aCommand.Name == "setPropertyValues" )
+ {
+ Sequence< PropertyValue > aProperties;
+ if ( !( aCommand.Argument >>= aProperties ) )
+ {
+ ::ucbhelper::cancelCommandExecution( Any( IllegalArgumentException(
+ OUString(), *this, -1 ) ),
+ i_rEnvironment );
+ // unreachable
+ }
+
+ if ( !aProperties.hasElements() )
+ {
+ ::ucbhelper::cancelCommandExecution( Any( IllegalArgumentException(
+ OUString(), *this, -1 ) ),
+ i_rEnvironment );
+ // unreachable
+ }
+
+ aRet <<= setPropertyValues( aProperties );
+ }
+ else if ( aCommand.Name == "getPropertySetInfo" )
+ {
+ // implemented by base class.
+ aRet <<= getPropertySetInfo( i_rEnvironment );
+ }
+ else if ( aCommand.Name == "getCommandInfo" )
+ {
+ // implemented by base class.
+ aRet <<= getCommandInfo( i_rEnvironment );
+ }
+ else if ( aCommand.Name == "open" )
+ {
+ OpenCommandArgument2 aOpenCommand;
+ if ( !( aCommand.Argument >>= aOpenCommand ) )
+ {
+ ::ucbhelper::cancelCommandExecution( Any( IllegalArgumentException(
+ OUString(), *this, -1 ) ),
+ i_rEnvironment );
+ // unreachable
+ }
+
+ bool bOpenFolder =
+ ( ( aOpenCommand.Mode == OpenMode::ALL ) ||
+ ( aOpenCommand.Mode == OpenMode::FOLDERS ) ||
+ ( aOpenCommand.Mode == OpenMode::DOCUMENTS ) );
+
+
+ if ( bOpenFolder && impl_isFolder() )
+ {
+ Reference< XDynamicResultSet > xSet = new ResultSet( m_xContext, this, aOpenCommand, i_rEnvironment );
+ aRet <<= xSet;
+ }
+
+ if ( aOpenCommand.Sink.is() )
+ {
+ const OUString sPhysicalContentURL( getPhysicalURL() );
+ ::ucbhelper::Content aRequestedContent( sPhysicalContentURL, i_rEnvironment, m_xContext );
+ aRet = aRequestedContent.executeCommand( "open", Any( aOpenCommand ) );
+ }
+ }
+
+ else
+ {
+ ::ucbhelper::cancelCommandExecution( Any( UnsupportedCommandException(
+ OUString(), *this ) ),
+ i_rEnvironment );
+ // unreachable
+ }
+
+ return aRet;
+ }
+
+
+ void SAL_CALL Content::abort( sal_Int32 )
+ {
+ }
+
+
+ OUString Content::encodeIdentifier( const OUString& i_rIdentifier )
+ {
+ return ::rtl::Uri::encode( i_rIdentifier, rtl_UriCharClassRegName, rtl_UriEncodeIgnoreEscapes,
+ RTL_TEXTENCODING_UTF8 );
+ }
+
+
+ OUString Content::decodeIdentifier( const OUString& i_rIdentifier )
+ {
+ return ::rtl::Uri::decode( i_rIdentifier, rtl_UriDecodeWithCharset, RTL_TEXTENCODING_UTF8 );
+ }
+
+
+ bool Content::denotesRootContent( std::u16string_view i_rContentIdentifier )
+ {
+ const OUString sRootURL( ContentProvider::getRootURL() );
+ if ( i_rContentIdentifier == sRootURL )
+ return true;
+
+ // the root URL contains only two trailing /, but we also recognize 3 of them as denoting the root URL
+ if ( o3tl::starts_with(i_rContentIdentifier, sRootURL )
+ && ( sal_Int32(i_rContentIdentifier.size()) == sRootURL.getLength() + 1 )
+ && ( i_rContentIdentifier[ i_rContentIdentifier.size() - 1 ] == '/' )
+ )
+ return true;
+
+ return false;
+ }
+
+
+ OUString Content::getParentURL()
+ {
+ const OUString sRootURL( ContentProvider::getRootURL() );
+
+ switch ( m_eExtContentType )
+ {
+ case E_ROOT:
+ // don't have a parent
+ return sRootURL;
+
+ case E_EXTENSION_ROOT:
+ // our parent is the root itself
+ return sRootURL;
+
+ case E_EXTENSION_CONTENT:
+ {
+ const OUString sURL = m_xIdentifier->getContentIdentifier();
+
+ // cut the root URL
+ if ( !sURL.match( sRootURL ) )
+ {
+ SAL_INFO( "ucb.ucp.ext", "illegal URL structure - no root" );
+ break;
+ }
+
+ OUString sRelativeURL( sURL.copy( sRootURL.getLength() ) );
+
+ // cut the extension ID
+ const OUString sSeparatedExtensionId( encodeIdentifier( m_sExtensionId ) + "/" );
+ if ( !sRelativeURL.match( sSeparatedExtensionId ) )
+ {
+ SAL_INFO( "ucb.ucp.ext", "illegal URL structure - no extension ID" );
+ break;
+ }
+
+ sRelativeURL = sRelativeURL.copy( sSeparatedExtensionId.getLength() );
+
+ // cut the final slash (if any)
+ if ( sRelativeURL.isEmpty() )
+ {
+ SAL_INFO( "ucb.ucp.ext", "illegal URL structure - ExtensionContent should have a level below the extension ID" );
+ break;
+ }
+
+ if ( sRelativeURL.endsWith("/") )
+ sRelativeURL = sRelativeURL.copy( 0, sRelativeURL.getLength() - 1 );
+
+ // remove the last segment
+ const sal_Int32 nLastSep = sRelativeURL.lastIndexOf( '/' );
+ sRelativeURL = sRelativeURL.copy( 0, nLastSep != -1 ? nLastSep : 0 );
+
+ return sRootURL + sSeparatedExtensionId + sRelativeURL;
+ }
+
+ default:
+ OSL_FAIL( "Content::getParentURL: unhandled case!" );
+ break;
+ }
+ return OUString();
+ }
+
+
+ Reference< XRow > Content::getArtificialNodePropertyValues( const Reference< XComponentContext >& rxContext,
+ const Sequence< Property >& i_rProperties, const OUString& i_rTitle )
+ {
+ // note: empty sequence means "get values of all supported properties".
+ ::rtl::Reference< ::ucbhelper::PropertyValueSet > xRow = new ::ucbhelper::PropertyValueSet( rxContext );
+
+ if ( i_rProperties.hasElements() )
+ {
+ for ( const Property& rProp : i_rProperties )
+ {
+ // Process Core properties.
+ if ( rProp.Name == "ContentType" )
+ {
+ xRow->appendString ( rProp, ContentProvider::getArtificialNodeContentType() );
+ }
+ else if ( rProp.Name == "Title" )
+ {
+ xRow->appendString ( rProp, i_rTitle );
+ }
+ else if ( rProp.Name == "IsDocument" )
+ {
+ xRow->appendBoolean( rProp, false );
+ }
+ else if ( rProp.Name == "IsFolder" )
+ {
+ xRow->appendBoolean( rProp, true );
+ }
+ else
+ {
+ // append empty entry.
+ xRow->appendVoid( rProp );
+ }
+ }
+ }
+ else
+ {
+ // Append all Core Properties.
+ xRow->appendString ( Property( "ContentType",
+ -1,
+ cppu::UnoType<OUString>::get(),
+ PropertyAttribute::BOUND | PropertyAttribute::READONLY ),
+ ContentProvider::getArtificialNodeContentType() );
+ xRow->appendString ( Property( "Title",
+ -1,
+ cppu::UnoType<OUString>::get(),
+ PropertyAttribute::BOUND | PropertyAttribute::READONLY ),
+ i_rTitle );
+ xRow->appendBoolean( Property( "IsDocument",
+ -1,
+ cppu::UnoType<bool>::get(),
+ PropertyAttribute::BOUND | PropertyAttribute::READONLY ),
+ false );
+ xRow->appendBoolean( Property( "IsFolder",
+ -1,
+ cppu::UnoType<bool>::get(),
+ PropertyAttribute::BOUND | PropertyAttribute::READONLY ),
+ true );
+ }
+
+ return xRow;
+ }
+
+
+ OUString Content::getPhysicalURL() const
+ {
+ ENSURE_OR_RETURN( m_eExtContentType != E_ROOT, "illegal call", OUString() );
+
+ // create a ucb::XContent for the physical file within the deployed extension
+ const Reference< XPackageInformationProvider > xPackageInfo = PackageInformationProvider::get(m_xContext);
+ const OUString sPackageLocation( xPackageInfo->getPackageLocation( m_sExtensionId ) );
+
+ if ( m_sPathIntoExtension.isEmpty() )
+ return sPackageLocation;
+ return lcl_compose( sPackageLocation, m_sPathIntoExtension );
+ }
+
+
+ Reference< XRow > Content::getPropertyValues( const Sequence< Property >& i_rProperties, const Reference< XCommandEnvironment >& i_rEnv )
+ {
+ ::osl::Guard< ::osl::Mutex > aGuard( m_aMutex );
+
+ switch ( m_eExtContentType )
+ {
+ case E_ROOT:
+ return getArtificialNodePropertyValues( m_xContext, i_rProperties, ContentProvider::getRootURL() );
+ case E_EXTENSION_ROOT:
+ return getArtificialNodePropertyValues( m_xContext, i_rProperties, m_sExtensionId );
+ case E_EXTENSION_CONTENT:
+ {
+ const OUString sPhysicalContentURL( getPhysicalURL() );
+ ::ucbhelper::Content aRequestedContent( sPhysicalContentURL, i_rEnv, m_xContext );
+
+ // translate the property request
+ Sequence< OUString > aPropertyNames( i_rProperties.getLength() );
+ ::std::transform(
+ i_rProperties.begin(),
+ i_rProperties.end(),
+ aPropertyNames.getArray(),
+ SelectPropertyName()
+ );
+ const Sequence< Any > aPropertyValues = aRequestedContent.getPropertyValues( aPropertyNames );
+ const ::rtl::Reference< ::ucbhelper::PropertyValueSet > xValueRow = new ::ucbhelper::PropertyValueSet( m_xContext );
+ sal_Int32 i=0;
+ for ( const Any* value = aPropertyValues.getConstArray();
+ value != aPropertyValues.getConstArray() + aPropertyValues.getLength();
+ ++value, ++i
+ )
+ {
+ xValueRow->appendObject( aPropertyNames[i], *value );
+ }
+ return xValueRow;
+ }
+
+ default:
+ OSL_FAIL( "Content::getPropertyValues: unhandled case!" );
+ break;
+ }
+
+ OSL_FAIL( "Content::getPropertyValues: unreachable!" );
+ return nullptr;
+ }
+
+
+ Sequence< Any > Content::setPropertyValues( const Sequence< PropertyValue >& i_rValues)
+ {
+ ::osl::ClearableGuard< osl::Mutex > aGuard( m_aMutex );
+
+ Sequence< Any > aRet( i_rValues.getLength() );
+
+ PropertyChangeEvent aEvent;
+ aEvent.Source = getXWeak();
+ aEvent.Further = false;
+ aEvent.PropertyHandle = -1;
+
+ for ( auto& rRet : asNonConstRange(aRet) )
+ {
+ // all our properties are read-only ...
+ rRet <<= IllegalAccessException("property is read-only.", *this );
+ }
+
+ return aRet;
+ }
+
+
+ Sequence< CommandInfo > Content::getCommands( const Reference< XCommandEnvironment > & /*xEnv*/ )
+ {
+ static const CommandInfo aCommandInfoTable[] =
+ {
+ // Mandatory commands
+
+ CommandInfo(
+ "getCommandInfo",
+ -1,
+ cppu::UnoType<void>::get()
+ ),
+ CommandInfo(
+ "getPropertySetInfo",
+ -1,
+ cppu::UnoType<void>::get()
+ ),
+ CommandInfo(
+ "getPropertyValues",
+ -1,
+ cppu::UnoType<Sequence< Property >>::get()
+ ),
+ CommandInfo(
+ "setPropertyValues",
+ -1,
+ cppu::UnoType<Sequence< PropertyValue >>::get()
+ )
+
+ // Optional standard commands
+
+ , CommandInfo(
+ "open",
+ -1,
+ cppu::UnoType<OpenCommandArgument2>::get()
+ )
+ };
+
+ return Sequence< CommandInfo >( aCommandInfoTable, SAL_N_ELEMENTS(aCommandInfoTable) );
+ }
+
+
+ Sequence< Property > Content::getProperties( const Reference< XCommandEnvironment > & /*xEnv*/ )
+ {
+ static const Property aProperties[] =
+ {
+ Property(
+ "ContentType",
+ -1,
+ cppu::UnoType<OUString>::get(),
+ PropertyAttribute::BOUND | PropertyAttribute::READONLY
+ ),
+ Property(
+ "IsDocument",
+ -1,
+ cppu::UnoType<bool>::get(),
+ PropertyAttribute::BOUND | PropertyAttribute::READONLY
+ ),
+ Property(
+ "IsFolder",
+ -1,
+ cppu::UnoType<bool>::get(),
+ PropertyAttribute::BOUND | PropertyAttribute::READONLY
+ ),
+ Property(
+ "Title",
+ -1,
+ cppu::UnoType<OUString>::get(),
+ PropertyAttribute::BOUND | PropertyAttribute::READONLY
+ )
+ };
+ return Sequence< Property >( aProperties, SAL_N_ELEMENTS( aProperties ) );
+ }
+
+
+ bool Content::impl_isFolder()
+ {
+ if ( m_aIsFolder.has_value() )
+ return *m_aIsFolder;
+
+ bool bIsFolder = false;
+ try
+ {
+ Sequence< Property > aProps{ { /*Name*/ "IsFolder", {}, {}, {} } };
+ Reference< XRow > xRow( getPropertyValues( aProps, nullptr ), UNO_SET_THROW );
+ bIsFolder = xRow->getBoolean(1);
+ }
+ catch( const Exception& )
+ {
+ DBG_UNHANDLED_EXCEPTION("ucb.ucp.ext");
+ }
+ m_aIsFolder = bIsFolder;
+ return *m_aIsFolder;
+ }
+
+
+ void Content::impl_determineContentType()
+ {
+ if ( !!m_aContentType )
+ return;
+
+ m_aContentType = ContentProvider::getArtificialNodeContentType();
+ if ( m_eExtContentType != E_EXTENSION_CONTENT )
+ return;
+
+ try
+ {
+ Sequence< Property > aProps{ { /*Name*/ "ContentType", {}, {}, {} } };
+ Reference< XRow > xRow( getPropertyValues( aProps, nullptr ), UNO_SET_THROW );
+ m_aContentType = xRow->getString(1);
+ }
+ catch( const Exception& )
+ {
+ DBG_UNHANDLED_EXCEPTION("ucb.ucp.ext");
+ }
+ }
+
+
+} // namespace ucp::ext
+
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/ucb/source/ucp/ext/ucpext_content.hxx b/ucb/source/ucp/ext/ucpext_content.hxx
new file mode 100644
index 0000000000..99df646f73
--- /dev/null
+++ b/ucb/source/ucp/ext/ucpext_content.hxx
@@ -0,0 +1,134 @@
+/* -*- 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:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#pragma once
+
+#include <com/sun/star/sdbc/XRow.hpp>
+#include <com/sun/star/beans/PropertyValue.hpp>
+
+#include <ucbhelper/contenthelper.hxx>
+
+#include <optional>
+
+
+namespace ucb::ucp::ext
+{
+
+
+ //= ExtensionContentType
+
+ enum ExtensionContentType
+ {
+ E_ROOT,
+ E_EXTENSION_ROOT,
+ E_EXTENSION_CONTENT,
+
+ E_UNKNOWN
+ };
+
+
+ //= ContentProvider
+
+ typedef ::ucbhelper::ContentImplHelper Content_Base;
+ class Content : public Content_Base
+ {
+ public:
+ Content(
+ const css::uno::Reference< css::uno::XComponentContext >& rxContext,
+ ::ucbhelper::ContentProviderImplHelper* pProvider,
+ const css::uno::Reference< css::ucb::XContentIdentifier >& Identifier
+ );
+
+ static css::uno::Reference< css::sdbc::XRow >
+ getArtificialNodePropertyValues(
+ const css::uno::Reference< css::uno::XComponentContext >& rxContext,
+ const css::uno::Sequence< css::beans::Property >& rProperties,
+ const OUString& rTitle
+ );
+
+ css::uno::Reference< css::sdbc::XRow >
+ getPropertyValues(
+ const css::uno::Sequence< css::beans::Property >& rProperties,
+ const css::uno::Reference< css::ucb::XCommandEnvironment >& xEnv
+ );
+
+ static OUString
+ encodeIdentifier( const OUString& i_rIdentifier );
+ static OUString
+ decodeIdentifier( const OUString& i_rIdentifier );
+
+ virtual OUString getParentURL() override;
+
+ ExtensionContentType getExtensionContentType() const { return m_eExtContentType; }
+
+ /** retrieves the URL of the underlying physical content. Not to be called when getExtensionContentType()
+ returns E_ROOT.
+ */
+ OUString getPhysicalURL() const;
+
+ protected:
+ virtual ~Content() override;
+
+ // XServiceInfo
+ virtual OUString SAL_CALL getImplementationName( ) override;
+ virtual css::uno::Sequence< OUString > SAL_CALL getSupportedServiceNames( ) override;
+
+ // XContent
+ virtual OUString SAL_CALL getContentType() override;
+
+ // XCommandProcessor
+ 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;
+
+ private:
+ virtual css::uno::Sequence< css::beans::Property > getProperties( const css::uno::Reference< css::ucb::XCommandEnvironment >& i_rEnv ) override;
+ virtual css::uno::Sequence< css::ucb::CommandInfo > getCommands( const css::uno::Reference< css::ucb::XCommandEnvironment >& i_rEnv ) override;
+
+ css::uno::Sequence< css::uno::Any >
+ setPropertyValues(
+ const css::uno::Sequence< css::beans::PropertyValue >& rValues
+ );
+
+ static bool denotesRootContent( std::u16string_view i_rContentIdentifier );
+
+ bool impl_isFolder();
+ void impl_determineContentType();
+
+ private:
+ ExtensionContentType m_eExtContentType;
+ ::std::optional< bool > m_aIsFolder;
+ ::std::optional< OUString > m_aContentType;
+ OUString m_sExtensionId;
+ OUString m_sPathIntoExtension;
+ };
+
+
+} // namespace ucb::ucp::ext
+
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/ucb/source/ucp/ext/ucpext_datasupplier.cxx b/ucb/source/ucp/ext/ucpext_datasupplier.cxx
new file mode 100644
index 0000000000..9470cdeb4c
--- /dev/null
+++ b/ucb/source/ucp/ext/ucpext_datasupplier.cxx
@@ -0,0 +1,314 @@
+/* -*- 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:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+
+#include "ucpext_datasupplier.hxx"
+#include "ucpext_content.hxx"
+#include "ucpext_provider.hxx"
+
+#include <com/sun/star/deployment/PackageInformationProvider.hpp>
+#include <com/sun/star/ucb/IllegalIdentifierException.hpp>
+#include <o3tl/string_view.hxx>
+#include <ucbhelper/contentidentifier.hxx>
+#include <ucbhelper/providerhelper.hxx>
+#include <ucbhelper/content.hxx>
+#include <ucbhelper/propertyvalueset.hxx>
+#include <comphelper/diagnose_ex.hxx>
+#include <rtl/ustrbuf.hxx>
+#include <sal/log.hxx>
+
+#include <memory>
+#include <string_view>
+#include <utility>
+
+
+namespace ucb::ucp::ext
+{
+
+
+ using ::com::sun::star::uno::Reference;
+ using ::com::sun::star::uno::UNO_QUERY_THROW;
+ using ::com::sun::star::uno::UNO_SET_THROW;
+ using ::com::sun::star::uno::Exception;
+ using ::com::sun::star::uno::Sequence;
+ using ::com::sun::star::uno::XComponentContext;
+ using ::com::sun::star::ucb::XContent;
+ using ::com::sun::star::ucb::XContentIdentifier;
+ using ::com::sun::star::sdbc::XRow;
+ using ::com::sun::star::ucb::IllegalIdentifierException;
+ using ::com::sun::star::deployment::PackageInformationProvider;
+ using ::com::sun::star::deployment::XPackageInformationProvider;
+ using ::com::sun::star::sdbc::XResultSet;
+
+
+ //= helper
+
+ namespace
+ {
+ OUString lcl_compose( std::u16string_view i_rBaseURL, const OUString& i_rRelativeURL )
+ {
+ ENSURE_OR_RETURN( !i_rBaseURL.empty(), "illegal base URL", i_rRelativeURL );
+
+ OUStringBuffer aComposer( i_rBaseURL );
+ if ( !o3tl::ends_with(i_rBaseURL, u"/") )
+ aComposer.append( '/' );
+ aComposer.append( i_rRelativeURL );
+ return aComposer.makeStringAndClear();
+ }
+ }
+
+
+ //= DataSupplier
+
+
+ DataSupplier::DataSupplier( const Reference< XComponentContext >& rxContext,
+ ::rtl::Reference< Content > i_xContent )
+ :m_xContent(std::move( i_xContent ))
+ ,m_xContext( rxContext )
+ {
+ }
+
+
+ void DataSupplier::fetchData()
+ {
+ try
+ {
+ const Reference< XPackageInformationProvider > xPackageInfo = PackageInformationProvider::get( m_xContext );
+
+ const OUString sContentIdentifier( m_xContent->getIdentifier()->getContentIdentifier() );
+
+ switch ( m_xContent->getExtensionContentType() )
+ {
+ case E_ROOT:
+ {
+ const Sequence< Sequence< OUString > > aExtensionInfo( xPackageInfo->getExtensionList() );
+ for ( auto const & extInfo : aExtensionInfo )
+ {
+ if ( !extInfo.hasElements() )
+ {
+ SAL_WARN( "ucb.ucp.ext", "illegal extension info" );
+ continue;
+ }
+
+ const OUString& rLocalId = extInfo[0];
+ ResultListEntry aEntry;
+ aEntry.sId = ContentProvider::getRootURL() + Content::encodeIdentifier( rLocalId ) + "/";
+ m_aResults.push_back( aEntry );
+ }
+ }
+ break;
+ case E_EXTENSION_ROOT:
+ case E_EXTENSION_CONTENT:
+ {
+ const OUString sPackageLocation( m_xContent->getPhysicalURL() );
+ ::ucbhelper::Content aWrappedContent( sPackageLocation, getResultSet()->getEnvironment(), m_xContext );
+
+ // obtain the properties which our result set is set up for from the wrapped content
+ Sequence< OUString > aPropertyNames { "Title" };
+
+ const Reference< XResultSet > xFolderContent( aWrappedContent.createCursor( aPropertyNames ), UNO_SET_THROW );
+ const Reference< XRow > xContentRow( xFolderContent, UNO_QUERY_THROW );
+ while ( xFolderContent->next() )
+ {
+ ResultListEntry aEntry;
+ aEntry.sId = lcl_compose( sContentIdentifier, xContentRow->getString( 1 ) );
+ m_aResults.push_back( aEntry );
+ }
+ }
+ break;
+ default:
+ OSL_FAIL( "DataSupplier::fetchData: unimplemented content type!" );
+ break;
+ }
+ }
+ catch( const Exception& )
+ {
+ DBG_UNHANDLED_EXCEPTION("ucb.ucp.ext");
+ }
+ }
+
+
+ DataSupplier::~DataSupplier()
+ {
+ }
+
+
+ OUString DataSupplier::queryContentIdentifierString( sal_uInt32 i_nIndex )
+ {
+ std::unique_lock aGuard( m_aMutex );
+
+ if ( i_nIndex < m_aResults.size() )
+ {
+ const OUString sId = m_aResults[ i_nIndex ].sId;
+ if ( !sId.isEmpty() )
+ return sId;
+ }
+
+ OSL_FAIL( "DataSupplier::queryContentIdentifierString: illegal index, or illegal result entry id!" );
+ return OUString();
+ }
+
+
+ Reference< XContentIdentifier > DataSupplier::queryContentIdentifier( sal_uInt32 i_nIndex )
+ {
+ std::unique_lock aGuard( m_aMutex );
+
+ if ( i_nIndex < m_aResults.size() )
+ {
+ Reference< XContentIdentifier > xId( m_aResults[ i_nIndex ].xId );
+ if ( xId.is() )
+ return xId;
+ }
+
+ OUString sId = queryContentIdentifierString( i_nIndex );
+ if ( !sId.isEmpty() )
+ {
+ Reference< XContentIdentifier > xId = new ::ucbhelper::ContentIdentifier( sId );
+ m_aResults[ i_nIndex ].xId = xId;
+ return xId;
+ }
+
+ return Reference< XContentIdentifier >();
+ }
+
+
+ Reference< XContent > DataSupplier::queryContent( sal_uInt32 i_nIndex )
+ {
+ std::unique_lock aGuard( m_aMutex );
+ ENSURE_OR_RETURN( i_nIndex < m_aResults.size(), "illegal index!", nullptr );
+
+
+ ::rtl::Reference< Content > pContent( m_aResults[ i_nIndex ].pContent );
+ if ( pContent.is() )
+ return pContent;
+
+ Reference< XContentIdentifier > xId( queryContentIdentifier( i_nIndex ) );
+ if ( xId.is() )
+ {
+ try
+ {
+ Reference< XContent > xContent( m_xContent->getProvider()->queryContent( xId ) );
+ pContent.set( dynamic_cast< Content* >( xContent.get() ) );
+ OSL_ENSURE( pContent.is() || !xContent.is(), "DataSupplier::queryContent: invalid content implementation!" );
+ m_aResults[ i_nIndex ].pContent = pContent;
+ return pContent;
+
+ }
+ catch ( const IllegalIdentifierException& )
+ {
+ DBG_UNHANDLED_EXCEPTION("ucb.ucp.ext");
+ }
+ }
+
+ return Reference< XContent >();
+ }
+
+
+ bool DataSupplier::getResult( sal_uInt32 i_nIndex )
+ {
+ std::unique_lock aGuard( m_aMutex );
+
+ // true if result already present.
+ return m_aResults.size() > i_nIndex;
+ }
+
+
+ sal_uInt32 DataSupplier::totalCount()
+ {
+ std::unique_lock aGuard( m_aMutex );
+ return m_aResults.size();
+ }
+
+
+ sal_uInt32 DataSupplier::currentCount()
+ {
+ return m_aResults.size();
+ }
+
+
+ bool DataSupplier::isCountFinal()
+ {
+ return true;
+ }
+
+
+ Reference< XRow > DataSupplier::queryPropertyValues( sal_uInt32 i_nIndex )
+ {
+ std::unique_lock aGuard( m_aMutex );
+ ENSURE_OR_RETURN( i_nIndex < m_aResults.size(), "DataSupplier::queryPropertyValues: illegal index!", nullptr );
+
+ Reference< XRow > xRow = m_aResults[ i_nIndex ].xRow;
+ if ( xRow.is() )
+ return xRow;
+
+ ENSURE_OR_RETURN( queryContent( i_nIndex ).is(), "could not retrieve the content", nullptr );
+
+ switch ( m_xContent->getExtensionContentType() )
+ {
+ case E_ROOT:
+ {
+ const OUString& rId( m_aResults[ i_nIndex ].sId );
+ const OUString sRootURL( ContentProvider::getRootURL() );
+ OUString sTitle = Content::decodeIdentifier( rId.copy( sRootURL.getLength() ) );
+ if ( sTitle.endsWith("/") )
+ sTitle = sTitle.copy( 0, sTitle.getLength() - 1 );
+ xRow = Content::getArtificialNodePropertyValues( m_xContext, getResultSet()->getProperties(), sTitle );
+ }
+ break;
+
+ case E_EXTENSION_ROOT:
+ case E_EXTENSION_CONTENT:
+ {
+ xRow = m_aResults[ i_nIndex ].pContent->getPropertyValues(
+ getResultSet()->getProperties(), getResultSet()->getEnvironment() );
+ }
+ break;
+ default:
+ OSL_FAIL( "DataSupplier::queryPropertyValues: unhandled case!" );
+ break;
+ }
+
+ m_aResults[ i_nIndex ].xRow = xRow;
+ return xRow;
+ }
+
+
+ void DataSupplier::releasePropertyValues( sal_uInt32 i_nIndex )
+ {
+ std::unique_lock aGuard( m_aMutex );
+
+ if ( i_nIndex < m_aResults.size() )
+ m_aResults[ i_nIndex ].xRow.clear();
+ }
+
+
+ void DataSupplier::close()
+ {
+ }
+
+
+ void DataSupplier::validate()
+ {
+ }
+
+
+} // namespace ucb::ucp::ext
+
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/ucb/source/ucp/ext/ucpext_datasupplier.hxx b/ucb/source/ucp/ext/ucpext_datasupplier.hxx
new file mode 100644
index 0000000000..a7198fc9ba
--- /dev/null
+++ b/ucb/source/ucp/ext/ucpext_datasupplier.hxx
@@ -0,0 +1,89 @@
+/* -*- 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:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#pragma once
+
+#include "ucpext_content.hxx"
+
+#include <rtl/ref.hxx>
+#include <ucbhelper/resultset.hxx>
+
+#include <mutex>
+#include <vector>
+
+
+namespace ucb::ucp::ext
+{
+
+
+ class Content;
+
+
+ //= DataSupplier
+
+ class DataSupplier : public ::ucbhelper::ResultSetDataSupplier
+ {
+ public:
+ DataSupplier(
+ const css::uno::Reference< css::uno::XComponentContext >& rxContext,
+ rtl::Reference< Content > xContent
+ );
+
+ void fetchData();
+
+ protected:
+ 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;
+
+ private:
+ struct ResultListEntry
+ {
+ OUString sId;
+ css::uno::Reference< css::ucb::XContentIdentifier > xId;
+ ::rtl::Reference< Content > pContent;
+ css::uno::Reference< css::sdbc::XRow > xRow;
+ };
+ typedef ::std::vector< ResultListEntry > ResultList;
+ std::mutex m_aMutex;
+ ResultList m_aResults;
+ ::rtl::Reference< Content > m_xContent;
+ css::uno::Reference< css::uno::XComponentContext > m_xContext;
+ };
+
+
+} // namespace ucp::ext
+
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/ucb/source/ucp/ext/ucpext_provider.cxx b/ucb/source/ucp/ext/ucpext_provider.cxx
new file mode 100644
index 0000000000..97822af93f
--- /dev/null
+++ b/ucb/source/ucp/ext/ucpext_provider.cxx
@@ -0,0 +1,169 @@
+/* -*- 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:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include "ucpext_provider.hxx"
+#include "ucpext_content.hxx"
+
+#include <com/sun/star/ucb/IllegalIdentifierException.hpp>
+#include <cppuhelper/weak.hxx>
+#include <ucbhelper/contentidentifier.hxx>
+#include <osl/mutex.hxx>
+#include <rtl/ustrbuf.hxx>
+
+
+namespace ucb::ucp::ext
+{
+
+
+ using ::com::sun::star::uno::Reference;
+ using ::com::sun::star::uno::XInterface;
+ using ::com::sun::star::uno::Sequence;
+ using ::com::sun::star::ucb::XContentIdentifier;
+ using ::com::sun::star::ucb::IllegalIdentifierException;
+ using ::com::sun::star::ucb::XContent;
+ using ::com::sun::star::uno::XComponentContext;
+
+
+ //= ContentProvider
+
+
+ ContentProvider::ContentProvider( const Reference< XComponentContext >& rxContext )
+ :ContentProvider_Base( rxContext )
+ {
+ }
+
+
+ ContentProvider::~ContentProvider()
+ {
+ }
+
+
+ OUString SAL_CALL ContentProvider::getImplementationName()
+ {
+ return "org.openoffice.comp.ucp.ext.ContentProvider";
+ }
+
+
+ Sequence< OUString > SAL_CALL ContentProvider::getSupportedServiceNames( )
+ {
+ return { "com.sun.star.ucb.ContentProvider", "com.sun.star.ucb.ExtensionContentProvider" };
+ }
+
+
+ OUString ContentProvider::getRootURL()
+ {
+ return "vnd.sun.star.extension://";
+ }
+
+
+ OUString ContentProvider::getArtificialNodeContentType()
+ {
+ return "application/vnd.sun.star.extension-content";
+ }
+
+
+ namespace
+ {
+ void lcl_ensureAndTransfer( std::u16string_view& io_rIdentifierFragment, OUStringBuffer& o_rNormalization, const sal_Unicode i_nLeadingChar )
+ {
+ if ( ( io_rIdentifierFragment.empty() ) || ( io_rIdentifierFragment[0] != i_nLeadingChar ) )
+ throw IllegalIdentifierException();
+ io_rIdentifierFragment = io_rIdentifierFragment.substr( 1 );
+ o_rNormalization.append( i_nLeadingChar );
+ }
+ }
+
+
+ Reference< XContent > SAL_CALL ContentProvider::queryContent( const Reference< XContentIdentifier >& i_rIdentifier )
+ {
+ // Check URL scheme...
+ static constexpr OUString sScheme( u"vnd.sun.star.extension"_ustr );
+ if ( !i_rIdentifier->getContentProviderScheme().equalsIgnoreAsciiCase( sScheme ) )
+ throw IllegalIdentifierException();
+
+ // normalize the identifier
+ const OUString sIdentifier( i_rIdentifier->getContentIdentifier() );
+
+ // the scheme needs to be lower-case
+ OUStringBuffer aComposer( sIdentifier.copy( 0, sScheme.getLength() ).toAsciiLowerCase() );
+
+ // one : is required after the scheme
+ std::u16string_view sRemaining( sIdentifier.subView( sScheme.getLength() ) );
+ lcl_ensureAndTransfer( sRemaining, aComposer, ':' );
+
+ // and at least one /
+ lcl_ensureAndTransfer( sRemaining, aComposer, '/' );
+
+ // the normalized form requires one additional /, but we also accept identifiers which don't have it
+ if ( sRemaining.empty() )
+ {
+ // the root content is a special case, it requires /
+ aComposer.append( "//" );
+ }
+ else
+ {
+ if ( sRemaining[0] != '/' )
+ {
+ aComposer.append( OUString::Concat("/") + sRemaining );
+ }
+ else
+ {
+ lcl_ensureAndTransfer( sRemaining, aComposer, '/' );
+ // by now, we moved "vnd.sun.star.extension://" from the URL to aComposer
+ if ( sRemaining.empty() )
+ {
+ // again, it's the root content, but one / is missing
+ aComposer.append( '/' );
+ }
+ else
+ {
+ aComposer.append( sRemaining );
+ }
+ }
+ }
+ const Reference< XContentIdentifier > xNormalizedIdentifier( new ::ucbhelper::ContentIdentifier( aComposer.makeStringAndClear() ) );
+
+ ::osl::MutexGuard aGuard( m_aMutex );
+
+ // check if a content with given id already exists...
+ Reference< XContent > xContent( queryExistingContent( xNormalizedIdentifier ) );
+ if ( xContent.is() )
+ return xContent;
+
+ // create a new content
+ xContent = new Content( m_xContext, this, xNormalizedIdentifier );
+ if ( !xContent->getIdentifier().is() )
+ throw IllegalIdentifierException();
+
+ registerNewContent( xContent );
+ return xContent;
+ }
+
+
+} // namespace ucb::ucp::ext
+
+
+extern "C" SAL_DLLPUBLIC_EXPORT css::uno::XInterface*
+ucb_ext_ContentProvider_get_implementation(
+ css::uno::XComponentContext* context, css::uno::Sequence<css::uno::Any> const&)
+{
+ return cppu::acquire(new ucb::ucp::ext::ContentProvider(context));
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/ucb/source/ucp/ext/ucpext_provider.hxx b/ucb/source/ucp/ext/ucpext_provider.hxx
new file mode 100644
index 0000000000..e1dae34472
--- /dev/null
+++ b/ucb/source/ucp/ext/ucpext_provider.hxx
@@ -0,0 +1,54 @@
+/* -*- 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:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#pragma once
+
+#include <ucbhelper/providerhelper.hxx>
+
+
+namespace ucb::ucp::ext
+{
+
+
+ //= ContentProvider
+
+ typedef ::ucbhelper::ContentProviderImplHelper ContentProvider_Base;
+ class ContentProvider : public ContentProvider_Base
+ {
+ public:
+ explicit ContentProvider( const css::uno::Reference< css::uno::XComponentContext >& rxContext );
+ virtual ~ContentProvider() override;
+
+ // XServiceInfo
+ virtual OUString SAL_CALL getImplementationName( ) 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;
+
+ public:
+ static OUString getRootURL();
+ static OUString getArtificialNodeContentType();
+ };
+
+
+} // namespace ucb::ucp::ext
+
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/ucb/source/ucp/ext/ucpext_resultset.cxx b/ucb/source/ucp/ext/ucpext_resultset.cxx
new file mode 100644
index 0000000000..4dd38b2005
--- /dev/null
+++ b/ucb/source/ucp/ext/ucpext_resultset.cxx
@@ -0,0 +1,76 @@
+/* -*- 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:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include "ucpext_resultset.hxx"
+#include "ucpext_content.hxx"
+#include "ucpext_datasupplier.hxx"
+
+#include <ucbhelper/resultset.hxx>
+#include <utility>
+
+
+namespace ucb::ucp::ext
+{
+
+
+ using ::com::sun::star::uno::Reference;
+ using ::com::sun::star::uno::XComponentContext;
+ using ::com::sun::star::ucb::OpenCommandArgument2;
+ using ::com::sun::star::ucb::XCommandEnvironment;
+
+
+ //= ResultSet
+
+
+ ResultSet::ResultSet( const Reference< XComponentContext >& rxContext, ::rtl::Reference< Content > i_xContent,
+ const OpenCommandArgument2& i_rCommand, const Reference< XCommandEnvironment >& i_rEnv )
+ :ResultSetImplHelper( rxContext, i_rCommand )
+ ,m_xEnvironment( i_rEnv )
+ ,m_xContent(std::move( i_xContent ))
+ {
+ }
+
+
+ void ResultSet::initStatic()
+ {
+ ::rtl::Reference< DataSupplier > pDataSupplier( new DataSupplier(
+ m_xContext,
+ m_xContent
+ ) );
+ m_xResultSet1 = new ::ucbhelper::ResultSet(
+ m_xContext,
+ m_aCommand.Properties,
+ pDataSupplier,
+ m_xEnvironment
+ );
+ pDataSupplier->fetchData();
+ }
+
+
+ void ResultSet::initDynamic()
+ {
+ initStatic();
+ m_xResultSet2 = m_xResultSet1;
+ }
+
+
+} // namespace ucb::ucp::ext
+
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/ucb/source/ucp/ext/ucpext_resultset.hxx b/ucb/source/ucp/ext/ucpext_resultset.hxx
new file mode 100644
index 0000000000..a9dcd60923
--- /dev/null
+++ b/ucb/source/ucp/ext/ucpext_resultset.hxx
@@ -0,0 +1,61 @@
+/* -*- 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:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#pragma once
+
+#include "ucpext_content.hxx"
+
+#include <rtl/ref.hxx>
+#include <ucbhelper/resultsethelper.hxx>
+#include <com/sun/star/ucb/XCommandEnvironment.hpp>
+
+
+namespace ucb::ucp::ext
+{
+
+
+ class Content;
+
+
+ //= ResultSet
+
+ class ResultSet : public ::ucbhelper::ResultSetImplHelper
+ {
+ public:
+ ResultSet(
+ const css::uno::Reference< css::uno::XComponentContext >& rxContext,
+ rtl::Reference< Content > i_xContent,
+ const css::ucb::OpenCommandArgument2& i_rCommand,
+ const css::uno::Reference< css::ucb::XCommandEnvironment >& i_rEnv
+ );
+
+ private:
+ css::uno::Reference< css::ucb::XCommandEnvironment > m_xEnvironment;
+ ::rtl::Reference< Content > m_xContent;
+
+ private:
+ virtual void initStatic() override;
+ virtual void initDynamic() override;
+ };
+
+
+} // namespace ucp::ext
+
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/ucb/source/ucp/file/bc.cxx b/ucb/source/ucp/file/bc.cxx
new file mode 100644
index 0000000000..8d66f26fa8
--- /dev/null
+++ b/ucb/source/ucp/file/bc.cxx
@@ -0,0 +1,1170 @@
+/* -*- 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:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include <rtl/uri.hxx>
+#include <rtl/ustrbuf.hxx>
+#include <rtl/ref.hxx>
+
+#include <comphelper/diagnose_ex.hxx>
+#include <com/sun/star/lang/NoSupportException.hpp>
+#include <com/sun/star/sdbc/SQLException.hpp>
+#include <com/sun/star/ucb/IllegalIdentifierException.hpp>
+#include <com/sun/star/ucb/OpenMode.hpp>
+#include <com/sun/star/beans/IllegalTypeException.hpp>
+#include <com/sun/star/io/XActiveDataStreamer.hpp>
+#include <com/sun/star/io/XOutputStream.hpp>
+#include <com/sun/star/io/XActiveDataSink.hpp>
+#include <com/sun/star/ucb/NameClash.hpp>
+#include <comphelper/fileurl.hxx>
+#include <cppuhelper/supportsservice.hxx>
+#include <utility>
+#include "filglob.hxx"
+#include "filid.hxx"
+#include "filrow.hxx"
+#include "bc.hxx"
+#include "prov.hxx"
+#include "filerror.hxx"
+#include "filinsreq.hxx"
+
+using namespace fileaccess;
+using namespace com::sun::star;
+using namespace com::sun::star::uno;
+using namespace com::sun::star::ucb;
+
+#if OSL_DEBUG_LEVEL > 0
+#define THROW_WHERE SAL_WHERE
+#else
+#define THROW_WHERE ""
+#endif
+
+class fileaccess::PropertyListeners
+{
+ typedef comphelper::OInterfaceContainerHelper4<beans::XPropertiesChangeListener> ContainerHelper;
+ std::unordered_map<OUString, ContainerHelper> m_aMap;
+
+public:
+ void disposeAndClear(std::unique_lock<std::mutex>& rGuard, const lang::EventObject& rEvt)
+ {
+ // create a copy, because do not fire event in a guarded section
+ std::unordered_map<OUString, ContainerHelper> tempMap = std::move(m_aMap);
+ for (auto& rPair : tempMap)
+ rPair.second.disposeAndClear(rGuard, rEvt);
+ }
+ void addInterface(std::unique_lock<std::mutex>& rGuard, const OUString& rKey, const uno::Reference<beans::XPropertiesChangeListener>& rListener)
+ {
+ m_aMap[rKey].addInterface(rGuard, rListener);
+ }
+ void removeInterface(std::unique_lock<std::mutex>& rGuard, const OUString& rKey, const uno::Reference<beans::XPropertiesChangeListener>& rListener)
+ {
+ // search container with id rKey
+ auto iter = m_aMap.find(rKey);
+ // container found?
+ if (iter != m_aMap.end())
+ iter->second.removeInterface(rGuard, rListener);
+ }
+ std::vector< OUString > getContainedTypes(std::unique_lock<std::mutex>& rGuard) const
+ {
+ std::vector<OUString> aInterfaceTypes;
+ aInterfaceTypes.reserve(m_aMap.size());
+ for (const auto& rPair : m_aMap)
+ // are interfaces added to this container?
+ if (rPair.second.getLength(rGuard))
+ // yes, put the type in the array
+ aInterfaceTypes.push_back(rPair.first);
+ return aInterfaceTypes;
+ }
+ comphelper::OInterfaceContainerHelper4<beans::XPropertiesChangeListener>* getContainer(std::unique_lock<std::mutex>& , const OUString& rKey)
+ {
+ auto iter = m_aMap.find(rKey);
+ if (iter != m_aMap.end())
+ return &iter->second;
+ return nullptr;
+ }
+};
+
+
+/****************************************************************************************/
+/* */
+/* BaseContent */
+/* */
+/****************************************************************************************/
+
+
+// Private Constructor for just inserted Contents
+
+BaseContent::BaseContent( TaskManager* pMyShell,
+ OUString parentName,
+ bool bFolder )
+ : m_pMyShell( pMyShell ),
+ m_aUncPath(std::move( parentName )),
+ m_bFolder( bFolder ),
+ m_nState( JustInserted )
+{
+ m_pMyShell->m_pProvider->acquire();
+ // No registering, since we have no name
+}
+
+
+// Constructor for full featured Contents
+
+BaseContent::BaseContent( TaskManager* pMyShell,
+ const Reference< XContentIdentifier >& xContentIdentifier,
+ OUString aUncPath )
+ : m_pMyShell( pMyShell ),
+ m_xContentIdentifier( xContentIdentifier ),
+ m_aUncPath(std::move( aUncPath )),
+ m_bFolder( false ),
+ m_nState( FullFeatured )
+{
+ m_pMyShell->m_pProvider->acquire();
+ m_pMyShell->registerNotifier( m_aUncPath,this );
+ m_pMyShell->insertDefaultProperties( m_aUncPath );
+}
+
+
+BaseContent::~BaseContent( )
+{
+ if( ( m_nState & FullFeatured ) || ( m_nState & Deleted ) )
+ {
+ m_pMyShell->deregisterNotifier( m_aUncPath,this );
+ }
+ m_pMyShell->m_pProvider->release();
+}
+
+
+// XComponent
+
+
+void SAL_CALL
+BaseContent::addEventListener( const Reference< lang::XEventListener >& Listener )
+{
+ std::unique_lock aGuard( m_aMutex );
+
+ m_aDisposeEventListeners.addInterface( aGuard, Listener );
+}
+
+
+void SAL_CALL
+BaseContent::removeEventListener( const Reference< lang::XEventListener >& Listener )
+{
+ std::unique_lock aGuard( m_aMutex );
+
+ m_aDisposeEventListeners.removeInterface( aGuard, Listener );
+}
+
+
+void SAL_CALL
+BaseContent::dispose()
+{
+ lang::EventObject aEvt;
+ aEvt.Source = static_cast< XContent* >( this );
+
+ std::unique_lock aGuard( m_aMutex );
+
+ std::unique_ptr<PropertyListeners> pPropertyListener = std::move(m_pPropertyListener);
+
+ m_aDisposeEventListeners.disposeAndClear( aGuard, aEvt );
+ m_aContentEventListeners.disposeAndClear( aGuard, aEvt );
+
+ if( pPropertyListener )
+ pPropertyListener->disposeAndClear( aGuard, aEvt );
+
+ m_aPropertySetInfoChangeListeners.disposeAndClear( aGuard, aEvt );
+}
+
+// XServiceInfo
+OUString SAL_CALL
+BaseContent::getImplementationName()
+{
+ return "com.sun.star.comp.ucb.FileContent";
+}
+
+sal_Bool SAL_CALL
+BaseContent::supportsService( const OUString& ServiceName )
+{
+ return cppu::supportsService( this, ServiceName );
+}
+
+Sequence< OUString > SAL_CALL
+BaseContent::getSupportedServiceNames()
+{
+ Sequence<OUString> ret { "com.sun.star.ucb.FileContent" };
+ return ret;
+}
+
+// XCommandProcessor
+
+
+sal_Int32 SAL_CALL
+BaseContent::createCommandIdentifier()
+{
+ return m_pMyShell->getCommandId();
+}
+
+
+void SAL_CALL
+BaseContent::abort( sal_Int32 /*CommandId*/ )
+{
+}
+
+
+Any SAL_CALL
+BaseContent::execute( const Command& aCommand,
+ sal_Int32 CommandId,
+ const Reference< XCommandEnvironment >& Environment )
+{
+ if( ! CommandId )
+ // A Command with commandid zero cannot be aborted
+ CommandId = createCommandIdentifier();
+
+ m_pMyShell->startTask( CommandId,
+ Environment );
+
+ Any aAny;
+
+ if (aCommand.Name == "getPropertySetInfo") // No exceptions
+ {
+ aAny <<= getPropertySetInfo();
+ }
+ else if (aCommand.Name == "getCommandInfo") // no exceptions
+ {
+ aAny <<= getCommandInfo();
+ }
+ else if ( aCommand.Name == "setPropertyValues" )
+ {
+ Sequence< beans::PropertyValue > sPropertyValues;
+
+ if( ! ( aCommand.Argument >>= sPropertyValues ) )
+ m_pMyShell->installError( CommandId,
+ TASKHANDLING_WRONG_SETPROPERTYVALUES_ARGUMENT );
+ else
+ aAny <<= setPropertyValues( CommandId,sPropertyValues ); // calls endTask by itself
+ }
+ else if ( aCommand.Name == "getPropertyValues" )
+ {
+ Sequence< beans::Property > ListOfRequestedProperties;
+
+ if( ! ( aCommand.Argument >>= ListOfRequestedProperties ) )
+ m_pMyShell->installError( CommandId,
+ TASKHANDLING_WRONG_GETPROPERTYVALUES_ARGUMENT );
+ else
+ aAny <<= getPropertyValues( CommandId,
+ ListOfRequestedProperties );
+ }
+ else if ( aCommand.Name == "open" )
+ {
+ OpenCommandArgument2 aOpenArgument;
+ if( ! ( aCommand.Argument >>= aOpenArgument ) )
+ m_pMyShell->installError( CommandId,
+ TASKHANDLING_WRONG_OPEN_ARGUMENT );
+ else
+ {
+ Reference< XDynamicResultSet > result = open( CommandId,aOpenArgument );
+ if( result.is() )
+ aAny <<= result;
+ }
+ }
+ else if ( aCommand.Name == "delete" )
+ {
+ if( ! aCommand.Argument.has< bool >() )
+ m_pMyShell->installError( CommandId,
+ TASKHANDLING_WRONG_DELETE_ARGUMENT );
+ else
+ deleteContent( CommandId );
+ }
+ else if ( aCommand.Name == "transfer" )
+ {
+ TransferInfo aTransferInfo;
+ if( ! ( aCommand.Argument >>= aTransferInfo ) )
+ m_pMyShell->installError( CommandId,
+ TASKHANDLING_WRONG_TRANSFER_ARGUMENT );
+ else
+ transfer( CommandId, aTransferInfo );
+ }
+ else if ( aCommand.Name == "insert" )
+ {
+ InsertCommandArgument aInsertArgument;
+ if( ! ( aCommand.Argument >>= aInsertArgument ) )
+ m_pMyShell->installError( CommandId,
+ TASKHANDLING_WRONG_INSERT_ARGUMENT );
+ else
+ insert( CommandId,aInsertArgument );
+ }
+ else if ( aCommand.Name == "getCasePreservingURL" )
+ {
+ Reference< sdbc::XRow > xRow = getPropertyValues( CommandId, { { "CasePreservingURL", -1, cppu::UnoType<sal_Bool>::get(), 0 } });
+ OUString CasePreservingURL = xRow->getString(1);
+ if(!xRow->wasNull())
+ aAny <<= CasePreservingURL;
+ }
+ else if ( aCommand.Name == "createNewContent" )
+ {
+ ucb::ContentInfo aArg;
+ if ( !( aCommand.Argument >>= aArg ) )
+ m_pMyShell->installError( CommandId,
+ TASKHANDLING_WRONG_CREATENEWCONTENT_ARGUMENT );
+ else
+ aAny <<= createNewContent( aArg );
+ }
+ else
+ m_pMyShell->installError( CommandId,
+ TASKHANDLER_UNSUPPORTED_COMMAND );
+
+
+ // This is the only function allowed to throw an exception
+ endTask( CommandId );
+
+ return aAny;
+}
+
+
+void SAL_CALL
+BaseContent::addPropertiesChangeListener(
+ const Sequence< OUString >& PropertyNames,
+ const Reference< beans::XPropertiesChangeListener >& Listener )
+{
+ if( ! Listener.is() )
+ return;
+
+ std::unique_lock aGuard( m_aMutex );
+
+ if( ! m_pPropertyListener )
+ m_pPropertyListener.reset( new PropertyListeners );
+
+
+ if( !PropertyNames.hasElements() )
+ m_pPropertyListener->addInterface( aGuard, OUString(),Listener );
+ else
+ {
+ Reference< beans::XPropertySetInfo > xProp = m_pMyShell->info_p( m_aUncPath );
+ for( const auto& rName : PropertyNames )
+ if( xProp->hasPropertyByName( rName ) )
+ m_pPropertyListener->addInterface( aGuard, rName,Listener );
+ }
+}
+
+
+void SAL_CALL
+BaseContent::removePropertiesChangeListener( const Sequence< OUString >& PropertyNames,
+ const Reference< beans::XPropertiesChangeListener >& Listener )
+{
+ if( ! Listener.is() )
+ return;
+
+ std::unique_lock aGuard( m_aMutex );
+
+ if( ! m_pPropertyListener )
+ return;
+
+ for( const auto& rName : PropertyNames )
+ m_pPropertyListener->removeInterface( aGuard, rName,Listener );
+
+ m_pPropertyListener->removeInterface( aGuard, OUString(), Listener );
+}
+
+
+// XContent
+
+
+Reference< ucb::XContentIdentifier > SAL_CALL
+BaseContent::getIdentifier()
+{
+ return m_xContentIdentifier;
+}
+
+
+OUString SAL_CALL
+BaseContent::getContentType()
+{
+ if( !( m_nState & Deleted ) )
+ {
+ if( m_nState & JustInserted )
+ {
+ if ( m_bFolder )
+ return TaskManager::FolderContentType;
+ else
+ return TaskManager::FileContentType;
+ }
+ else
+ {
+ try
+ {
+ // Who am I ?
+ Reference< sdbc::XRow > xRow = getPropertyValues( -1, { { "IsDocument", -1, cppu::UnoType<sal_Bool>::get(), 0 } });
+ bool IsDocument = xRow->getBoolean( 1 );
+
+ if ( !xRow->wasNull() )
+ {
+ if ( IsDocument )
+ return TaskManager::FileContentType;
+ else
+ return TaskManager::FolderContentType;
+ }
+ else
+ {
+ OSL_FAIL( "BaseContent::getContentType - Property value was null!" );
+ }
+ }
+ catch (const sdbc::SQLException&)
+ {
+ TOOLS_WARN_EXCEPTION("ucb.ucp.file", "");
+ }
+ }
+ }
+
+ return OUString();
+}
+
+
+void SAL_CALL
+BaseContent::addContentEventListener(
+ const Reference< XContentEventListener >& Listener )
+{
+ std::unique_lock aGuard( m_aMutex );
+
+ m_aContentEventListeners.addInterface( aGuard, Listener );
+}
+
+
+void SAL_CALL
+BaseContent::removeContentEventListener(
+ const Reference< XContentEventListener >& Listener )
+{
+ std::unique_lock aGuard( m_aMutex );
+
+ m_aContentEventListeners.removeInterface( aGuard, Listener );
+}
+
+
+// XPropertyContainer
+
+
+void SAL_CALL
+BaseContent::addProperty(
+ const OUString& Name,
+ sal_Int16 Attributes,
+ const Any& DefaultValue )
+{
+ if( ( m_nState & JustInserted ) || ( m_nState & Deleted ) || Name.isEmpty() )
+ {
+ throw lang::IllegalArgumentException( THROW_WHERE, uno::Reference< uno::XInterface >(), 0 );
+ }
+
+ m_pMyShell->associate( m_aUncPath,Name,DefaultValue,Attributes );
+}
+
+
+void SAL_CALL
+BaseContent::removeProperty( const OUString& Name )
+{
+
+ if( m_nState & Deleted )
+ throw beans::UnknownPropertyException( Name );
+
+ m_pMyShell->deassociate( m_aUncPath, Name );
+}
+
+
+// XContentCreator
+
+
+Sequence< ContentInfo > SAL_CALL
+BaseContent::queryCreatableContentsInfo()
+{
+ return TaskManager::queryCreatableContentsInfo();
+}
+
+
+Reference< XContent > SAL_CALL
+BaseContent::createNewContent( const ContentInfo& Info )
+{
+ // Check type.
+ if ( Info.Type.isEmpty() )
+ return Reference< XContent >();
+
+ bool bFolder = Info.Type == TaskManager::FolderContentType;
+ if ( !bFolder )
+ {
+ if ( Info.Type != TaskManager::FileContentType )
+ {
+ // Neither folder nor file to create!
+ return Reference< XContent >();
+ }
+ }
+
+ // Who am I ?
+ bool IsDocument = false;
+
+ try
+ {
+ Reference< sdbc::XRow > xRow = getPropertyValues( -1, { { "IsDocument", -1, cppu::UnoType<sal_Bool>::get(), 0 } });
+ IsDocument = xRow->getBoolean( 1 );
+
+ if ( xRow->wasNull() )
+ {
+ IsDocument = false;
+// OSL_FAIL( // "BaseContent::createNewContent - Property value was null!" );
+// return Reference< XContent >();
+ }
+ }
+ catch (const sdbc::SQLException&)
+ {
+ TOOLS_WARN_EXCEPTION("ucb.ucp.file", "");
+ return Reference< XContent >();
+ }
+
+ OUString dstUncPath;
+
+ if( IsDocument )
+ {
+ // KSO: Why is a document a XContentCreator? This is quite unusual.
+ dstUncPath = getParentName( m_aUncPath );
+ }
+ else
+ dstUncPath = m_aUncPath;
+
+ return new BaseContent( m_pMyShell, dstUncPath, bFolder );
+}
+
+
+// XPropertySetInfoChangeNotifier
+
+
+void SAL_CALL
+BaseContent::addPropertySetInfoChangeListener(
+ const Reference< beans::XPropertySetInfoChangeListener >& Listener )
+{
+ std::unique_lock aGuard( m_aMutex );
+
+ m_aPropertySetInfoChangeListeners.addInterface( aGuard, Listener );
+}
+
+
+void SAL_CALL
+BaseContent::removePropertySetInfoChangeListener(
+ const Reference< beans::XPropertySetInfoChangeListener >& Listener )
+{
+ std::unique_lock aGuard( m_aMutex );
+
+ m_aPropertySetInfoChangeListeners.removeInterface( aGuard, Listener );
+}
+
+
+// XChild
+
+
+Reference< XInterface > SAL_CALL
+BaseContent::getParent()
+{
+ OUString ParentUnq = getParentName( m_aUncPath );
+ OUString ParentUrl;
+
+
+ bool err = fileaccess::TaskManager::getUrlFromUnq( ParentUnq, ParentUrl );
+ if( err )
+ return Reference< XInterface >( nullptr );
+
+ rtl::Reference<FileContentIdentifier> Identifier = new FileContentIdentifier( ParentUnq );
+
+ try
+ {
+ return Reference<XInterface>( m_pMyShell->m_pProvider->queryContent( Identifier ), UNO_QUERY );
+ }
+ catch (const IllegalIdentifierException&)
+ {
+ return Reference< XInterface >();
+ }
+}
+
+
+void SAL_CALL
+BaseContent::setParent(
+ const Reference< XInterface >& )
+{
+ throw lang::NoSupportException( THROW_WHERE );
+}
+
+
+// Private Methods
+
+
+Reference< XCommandInfo >
+BaseContent::getCommandInfo()
+{
+ if( m_nState & Deleted )
+ return Reference< XCommandInfo >();
+
+ return m_pMyShell->info_c();
+}
+
+
+Reference< beans::XPropertySetInfo >
+BaseContent::getPropertySetInfo()
+{
+ if( m_nState & Deleted )
+ return Reference< beans::XPropertySetInfo >();
+
+ return m_pMyShell->info_p( m_aUncPath );
+}
+
+Reference< sdbc::XRow >
+BaseContent::getPropertyValues(
+ sal_Int32 nMyCommandIdentifier,
+ const Sequence< beans::Property >& PropertySet )
+{
+ sal_Int32 nProps = PropertySet.getLength();
+ if ( !nProps )
+ return Reference< sdbc::XRow >();
+
+ if( m_nState & Deleted )
+ {
+ Sequence< Any > aValues( nProps );
+ return Reference< sdbc::XRow >( new XRow_impl( m_pMyShell, aValues ) );
+ }
+
+ if( m_nState & JustInserted )
+ {
+ Sequence< Any > aValues( nProps );
+ Any* pValues = aValues.getArray();
+
+ const beans::Property* pProps = PropertySet.getConstArray();
+
+ for ( sal_Int32 n = 0; n < nProps; ++n )
+ {
+ const beans::Property& rProp = pProps[ n ];
+ Any& rValue = pValues[ n ];
+
+ if ( rProp.Name == "ContentType" )
+ {
+ rValue <<= (m_bFolder ? TaskManager::FolderContentType
+ : TaskManager::FileContentType);
+ }
+ else if ( rProp.Name == "IsFolder" )
+ {
+ rValue <<= m_bFolder;
+ }
+ else if ( rProp.Name == "IsDocument" )
+ {
+ rValue <<= !m_bFolder;
+ }
+ }
+
+ return Reference< sdbc::XRow >(
+ new XRow_impl( m_pMyShell, aValues ) );
+ }
+
+ return m_pMyShell->getv( nMyCommandIdentifier,
+ m_aUncPath,
+ PropertySet );
+}
+
+
+Sequence< Any >
+BaseContent::setPropertyValues(
+ sal_Int32 nMyCommandIdentifier,
+ const Sequence< beans::PropertyValue >& Values )
+{
+ if( m_nState & Deleted )
+ { // To do
+ return Sequence< Any >( Values.getLength() );
+ }
+
+ static constexpr OUString Title(u"Title"_ustr);
+
+ // Special handling for files which have to be inserted
+ if( m_nState & JustInserted )
+ {
+ for( const auto& rValue : Values )
+ {
+ if( rValue.Name == Title )
+ {
+ OUString NewTitle;
+ if( rValue.Value >>= NewTitle )
+ {
+ if ( m_nState & NameForInsertionSet )
+ {
+ // User wants to set another Title before "insert".
+ // m_aUncPath contains previous own URI.
+
+ sal_Int32 nLastSlash = m_aUncPath.lastIndexOf( '/' );
+ bool bTrailingSlash = false;
+ if ( nLastSlash == m_aUncPath.getLength() - 1 )
+ {
+ bTrailingSlash = true;
+ nLastSlash
+ = m_aUncPath.lastIndexOf( '/', nLastSlash );
+ }
+
+ OSL_ENSURE( nLastSlash != -1,
+ "BaseContent::setPropertyValues: "
+ "Invalid URL!" );
+
+ OUStringBuffer aBuf(
+ m_aUncPath.subView( 0, nLastSlash + 1 ) );
+
+ if ( !NewTitle.isEmpty() )
+ {
+ aBuf.append( NewTitle );
+ if ( bTrailingSlash )
+ aBuf.append( '/' );
+ }
+ else
+ {
+ m_nState &= ~NameForInsertionSet;
+ }
+
+ m_aUncPath = aBuf.makeStringAndClear();
+ }
+ else
+ {
+ if ( !NewTitle.isEmpty() )
+ {
+ // Initial Title before "insert".
+ // m_aUncPath contains parent's URI.
+
+ if( !m_aUncPath.endsWith( "/" ) )
+ m_aUncPath += "/";
+
+ m_aUncPath += rtl::Uri::encode( NewTitle,
+ rtl_UriCharClassPchar,
+ rtl_UriEncodeIgnoreEscapes,
+ RTL_TEXTENCODING_UTF8 );
+ m_nState |= NameForInsertionSet;
+ }
+ }
+ }
+ }
+ }
+
+ return Sequence< Any >( Values.getLength() );
+ }
+ else
+ {
+ Sequence< Any > ret = m_pMyShell->setv( m_aUncPath, // Does not handle Title
+ Values );
+ auto retRange = asNonConstRange(ret);
+
+ // Special handling Title: Setting Title is equivalent to a renaming of the underlying file
+ for( sal_Int32 i = 0; i < Values.getLength(); ++i )
+ {
+ if( Values[i].Name != Title )
+ continue; // handled by setv
+
+ OUString NewTitle;
+ if( !( Values[i].Value >>= NewTitle ) )
+ {
+ retRange[i] <<= beans::IllegalTypeException( THROW_WHERE );
+ break;
+ }
+ else if( NewTitle.isEmpty() )
+ {
+ retRange[i] <<= lang::IllegalArgumentException( THROW_WHERE, uno::Reference< uno::XInterface >(), 0 );
+ break;
+ }
+
+
+ OUString aDstName = getParentName( m_aUncPath );
+ if( !aDstName.endsWith("/") )
+ aDstName += "/";
+
+ aDstName += rtl::Uri::encode( NewTitle,
+ rtl_UriCharClassPchar,
+ rtl_UriEncodeIgnoreEscapes,
+ RTL_TEXTENCODING_UTF8 );
+
+ m_pMyShell->move( nMyCommandIdentifier, // move notifies the children also;
+ m_aUncPath,
+ aDstName,
+ NameClash::KEEP );
+
+ try
+ {
+ endTask( nMyCommandIdentifier );
+ }
+ catch(const Exception& e)
+ {
+ retRange[i] <<= e;
+ }
+
+ // NameChanges come back through a ContentEvent
+ break; // only handling Title
+ } // end for
+
+ return ret;
+ }
+}
+
+
+Reference< XDynamicResultSet >
+BaseContent::open(
+ sal_Int32 nMyCommandIdentifier,
+ const OpenCommandArgument2& aCommandArgument )
+{
+ Reference< XDynamicResultSet > retValue;
+
+ if( m_nState & Deleted )
+ {
+ m_pMyShell->installError( nMyCommandIdentifier,
+ TASKHANDLING_DELETED_STATE_IN_OPEN_COMMAND );
+ }
+ else if( m_nState & JustInserted )
+ {
+ m_pMyShell->installError( nMyCommandIdentifier,
+ TASKHANDLING_INSERTED_STATE_IN_OPEN_COMMAND );
+ }
+ else
+ {
+ if( aCommandArgument.Mode == OpenMode::DOCUMENT ||
+ aCommandArgument.Mode == OpenMode::DOCUMENT_SHARE_DENY_NONE )
+
+ {
+ Reference< io::XOutputStream > outputStream( aCommandArgument.Sink,UNO_QUERY );
+ if( outputStream.is() )
+ {
+ m_pMyShell->page( nMyCommandIdentifier,
+ m_aUncPath,
+ outputStream );
+ }
+
+ bool bLock = ( aCommandArgument.Mode != OpenMode::DOCUMENT_SHARE_DENY_NONE );
+
+ Reference< io::XActiveDataSink > activeDataSink( aCommandArgument.Sink,UNO_QUERY );
+ if( activeDataSink.is() )
+ {
+ activeDataSink->setInputStream( m_pMyShell->open( nMyCommandIdentifier,
+ m_aUncPath,
+ bLock ) );
+ }
+
+ Reference< io::XActiveDataStreamer > activeDataStreamer( aCommandArgument.Sink,UNO_QUERY );
+ if( activeDataStreamer.is() )
+ {
+ activeDataStreamer->setStream( m_pMyShell->open_rw( nMyCommandIdentifier,
+ m_aUncPath,
+ bLock ) );
+ }
+ }
+ else if ( aCommandArgument.Mode == OpenMode::ALL ||
+ aCommandArgument.Mode == OpenMode::FOLDERS ||
+ aCommandArgument.Mode == OpenMode::DOCUMENTS )
+ {
+ retValue = m_pMyShell->ls( nMyCommandIdentifier,
+ m_aUncPath,
+ aCommandArgument.Mode,
+ aCommandArgument.Properties,
+ aCommandArgument.SortingInfo );
+ }
+// else if( aCommandArgument.Mode ==
+// OpenMode::DOCUMENT_SHARE_DENY_NONE ||
+// aCommandArgument.Mode ==
+// OpenMode::DOCUMENT_SHARE_DENY_WRITE )
+// m_pMyShell->installError( nMyCommandIdentifier,
+// TASKHANDLING_UNSUPPORTED_OPEN_MODE,
+// aCommandArgument.Mode);
+ else
+ m_pMyShell->installError( nMyCommandIdentifier,
+ TASKHANDLING_UNSUPPORTED_OPEN_MODE,
+ aCommandArgument.Mode);
+ }
+
+ return retValue;
+}
+
+
+void
+BaseContent::deleteContent( sal_Int32 nMyCommandIdentifier )
+{
+ if( m_nState & Deleted )
+ return;
+
+ if( m_pMyShell->remove( nMyCommandIdentifier,m_aUncPath ) )
+ {
+ std::unique_lock aGuard( m_aMutex );
+ m_nState |= Deleted;
+ }
+}
+
+
+void
+BaseContent::transfer( sal_Int32 nMyCommandIdentifier,
+ const TransferInfo& aTransferInfo )
+{
+ if( m_nState & Deleted )
+ return;
+
+ if( !comphelper::isFileUrl(aTransferInfo.SourceURL) )
+ {
+ m_pMyShell->installError( nMyCommandIdentifier,
+ TASKHANDLING_TRANSFER_INVALIDSCHEME );
+ return;
+ }
+
+ OUString srcUnc;
+ if( fileaccess::TaskManager::getUnqFromUrl( aTransferInfo.SourceURL,srcUnc ) )
+ {
+ m_pMyShell->installError( nMyCommandIdentifier,
+ TASKHANDLING_TRANSFER_INVALIDURL );
+ return;
+ }
+
+ OUString srcUncPath = srcUnc;
+
+ // Determine the new title !
+ OUString NewTitle;
+ if( !aTransferInfo.NewTitle.isEmpty() )
+ NewTitle = rtl::Uri::encode( aTransferInfo.NewTitle,
+ rtl_UriCharClassPchar,
+ rtl_UriEncodeIgnoreEscapes,
+ RTL_TEXTENCODING_UTF8 );
+ else
+ NewTitle = srcUncPath.copy( 1 + srcUncPath.lastIndexOf( '/' ) );
+
+ // Is destination a document or a folder ?
+ Reference< sdbc::XRow > xRow = getPropertyValues( nMyCommandIdentifier,{ { "IsDocument", -1, cppu::UnoType<sal_Bool>::get(), 0 } } );
+ bool IsDocument = xRow->getBoolean( 1 );
+ if( xRow->wasNull() )
+ { // Destination file type could not be determined
+ m_pMyShell->installError( nMyCommandIdentifier,
+ TASKHANDLING_TRANSFER_DESTFILETYPE );
+ return;
+ }
+
+ OUString dstUncPath;
+ if( IsDocument )
+ { // as sibling
+ sal_Int32 lastSlash = m_aUncPath.lastIndexOf( '/' );
+ dstUncPath = m_aUncPath.copy(0,lastSlash );
+ }
+ else
+ // as child
+ dstUncPath = m_aUncPath;
+
+ dstUncPath += "/" + NewTitle;
+
+ sal_Int32 NameClash = aTransferInfo.NameClash;
+
+ if( aTransferInfo.MoveData )
+ m_pMyShell->move( nMyCommandIdentifier,srcUncPath,dstUncPath,NameClash );
+ else
+ m_pMyShell->copy( nMyCommandIdentifier,srcUncPath,dstUncPath,NameClash );
+}
+
+
+void BaseContent::insert( sal_Int32 nMyCommandIdentifier,
+ const InsertCommandArgument& aInsertArgument )
+{
+ if( m_nState & FullFeatured )
+ {
+ m_pMyShell->write( nMyCommandIdentifier,
+ m_aUncPath,
+ aInsertArgument.ReplaceExisting,
+ aInsertArgument.Data );
+ return;
+ }
+
+ if( ! ( m_nState & JustInserted ) )
+ {
+ m_pMyShell->installError( nMyCommandIdentifier,
+ TASKHANDLING_NOFRESHINSERT_IN_INSERT_COMMAND );
+ return;
+ }
+
+ // Inserts the content, which has the flag m_bIsFresh
+
+ if( ! (m_nState & NameForInsertionSet) )
+ {
+ m_pMyShell->installError( nMyCommandIdentifier,
+ TASKHANDLING_NONAMESET_INSERT_COMMAND );
+ return;
+ }
+
+ // Inserting a document or a file?
+ bool bDocument = false;
+
+ Reference< sdbc::XRow > xRow = getPropertyValues( -1, { { "IsDocument", -1, cppu::UnoType<sal_Bool>::get(), 0 } });
+
+ bool contentTypeSet = true; // is set to false, if contentType not set
+ try
+ {
+ bDocument = xRow->getBoolean( 1 );
+ if( xRow->wasNull() )
+ contentTypeSet = false;
+
+ }
+ catch (const sdbc::SQLException&)
+ {
+ TOOLS_WARN_EXCEPTION("ucb.ucp.file", "");
+ contentTypeSet = false;
+ }
+
+ if( ! contentTypeSet )
+ {
+ m_pMyShell->installError( nMyCommandIdentifier,
+ TASKHANDLING_NOCONTENTTYPE_INSERT_COMMAND );
+ return;
+ }
+
+
+ bool success = false;
+ if( bDocument )
+ success = m_pMyShell->mkfil( nMyCommandIdentifier,
+ m_aUncPath,
+ aInsertArgument.ReplaceExisting,
+ aInsertArgument.Data );
+ else
+ {
+ while( ! success )
+ {
+ success = m_pMyShell->mkdir( nMyCommandIdentifier,
+ m_aUncPath,
+ aInsertArgument.ReplaceExisting );
+ if( success )
+ break;
+
+ XInteractionRequestImpl aRequestImpl(
+ rtl::Uri::decode(
+ OUString(getTitle(m_aUncPath)),
+ rtl_UriDecodeWithCharset,
+ RTL_TEXTENCODING_UTF8),
+ getXWeak(),
+ m_pMyShell,nMyCommandIdentifier);
+ uno::Reference<task::XInteractionRequest> const& xReq(aRequestImpl.getRequest());
+
+ m_pMyShell->handleTask( nMyCommandIdentifier, xReq );
+ if (aRequestImpl.aborted() || aRequestImpl.newName().isEmpty())
+ // means aborting
+ break;
+
+ // determine new uncpath
+ m_pMyShell->clearError( nMyCommandIdentifier );
+ m_aUncPath = getParentName( m_aUncPath );
+ if( !m_aUncPath.endsWith( "/" ) )
+ m_aUncPath += "/";
+
+ m_aUncPath += rtl::Uri::encode( aRequestImpl.newName(),
+ rtl_UriCharClassPchar,
+ rtl_UriEncodeIgnoreEscapes,
+ RTL_TEXTENCODING_UTF8 );
+ }
+ }
+
+ if ( ! success )
+ return;
+
+ m_xContentIdentifier.set( new FileContentIdentifier( m_aUncPath ) );
+
+ m_pMyShell->registerNotifier( m_aUncPath,this );
+ m_pMyShell->insertDefaultProperties( m_aUncPath );
+
+ std::unique_lock aGuard( m_aMutex );
+ m_nState = FullFeatured;
+}
+
+
+void BaseContent::endTask( sal_Int32 CommandId )
+{
+ // This is the only function allowed to throw an exception
+ m_pMyShell->endTask( CommandId,m_aUncPath,this );
+}
+
+
+std::optional<ContentEventNotifier>
+BaseContent::cDEL()
+{
+ std::unique_lock aGuard( m_aMutex );
+
+ m_nState |= Deleted;
+
+ if( m_aContentEventListeners.getLength(aGuard) == 0 )
+ return {};
+
+ return ContentEventNotifier( m_pMyShell,
+ this,
+ m_xContentIdentifier,
+ m_aContentEventListeners.getElements(aGuard) );
+}
+
+
+std::optional<ContentEventNotifier>
+BaseContent::cEXC( const OUString& aNewName )
+{
+ std::unique_lock aGuard( m_aMutex );
+
+ Reference< XContentIdentifier > xOldRef = m_xContentIdentifier;
+ m_aUncPath = aNewName;
+ m_xContentIdentifier = new FileContentIdentifier( aNewName );
+
+ if( m_aContentEventListeners.getLength(aGuard) == 0 )
+ return {};
+ return ContentEventNotifier( m_pMyShell,
+ this,
+ m_xContentIdentifier,
+ xOldRef,
+ m_aContentEventListeners.getElements(aGuard) );
+}
+
+
+std::optional<ContentEventNotifier>
+BaseContent::cCEL()
+{
+ std::unique_lock aGuard( m_aMutex );
+ if( m_aContentEventListeners.getLength(aGuard) == 0 )
+ return {};
+ return ContentEventNotifier( m_pMyShell,
+ this,
+ m_xContentIdentifier,
+ m_aContentEventListeners.getElements(aGuard) );
+}
+
+std::optional<PropertySetInfoChangeNotifier>
+BaseContent::cPSL()
+{
+ std::unique_lock aGuard( m_aMutex );
+ if( m_aPropertySetInfoChangeListeners.getLength(aGuard) == 0 )
+ return {};
+ return PropertySetInfoChangeNotifier( this, m_aPropertySetInfoChangeListeners.getElements(aGuard) );
+}
+
+
+std::optional<PropertyChangeNotifier>
+BaseContent::cPCL()
+{
+ std::unique_lock aGuard( m_aMutex );
+
+ if (!m_pPropertyListener)
+ return {};
+
+ const std::vector< OUString > seqNames = m_pPropertyListener->getContainedTypes(aGuard);
+ if( seqNames.empty() )
+ return {};
+
+ ListenerMap listener;
+ for( const auto& rName : seqNames )
+ {
+ comphelper::OInterfaceContainerHelper4<beans::XPropertiesChangeListener>* pContainer = m_pPropertyListener->getContainer(aGuard, rName);
+ if (!pContainer)
+ continue;
+ listener[rName] = pContainer->getElements(aGuard);
+ }
+
+ return PropertyChangeNotifier( this, std::move(listener) );
+}
+
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/ucb/source/ucp/file/bc.hxx b/ucb/source/ucp/file/bc.hxx
new file mode 100644
index 0000000000..ca1f045fb5
--- /dev/null
+++ b/ucb/source/ucp/file/bc.hxx
@@ -0,0 +1,257 @@
+/* -*- 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:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#pragma once
+
+#include <mutex>
+#include <rtl/ustring.hxx>
+#include <cppuhelper/implbase.hxx>
+#include <comphelper/interfacecontainer4.hxx>
+#include <com/sun/star/uno/XInterface.hpp>
+#include <com/sun/star/lang/XComponent.hpp>
+#include <com/sun/star/ucb/XCommandProcessor.hpp>
+#include <com/sun/star/beans/XPropertiesChangeNotifier.hpp>
+#include <com/sun/star/ucb/XContent.hpp>
+#include <com/sun/star/ucb/XDynamicResultSet.hpp>
+#include <com/sun/star/lang/XServiceInfo.hpp>
+#include <com/sun/star/sdbc/XRow.hpp>
+#include <com/sun/star/beans/Property.hpp>
+#include <com/sun/star/beans/PropertyValue.hpp>
+#include <com/sun/star/ucb/XCommandInfo.hpp>
+#include <com/sun/star/ucb/InsertCommandArgument.hpp>
+#include <com/sun/star/beans/XPropertySetInfo.hpp>
+#include <com/sun/star/beans/XPropertyContainer.hpp>
+#include <com/sun/star/beans/XPropertySetInfoChangeNotifier.hpp>
+#include <com/sun/star/beans/XPropertySetInfoChangeListener.hpp>
+#include <com/sun/star/container/XChild.hpp>
+#include <com/sun/star/ucb/XContentCreator.hpp>
+#include <com/sun/star/ucb/OpenCommandArgument2.hpp>
+#include <com/sun/star/ucb/TransferInfo.hpp>
+#include "filtask.hxx"
+
+
+namespace fileaccess {
+
+ class PropertyListeners;
+ class TaskManager;
+
+ class BaseContent:
+ public cppu::WeakImplHelper<
+ css::lang::XComponent,
+ css::lang::XServiceInfo,
+ css::ucb::XCommandProcessor,
+ css::beans::XPropertiesChangeNotifier,
+ css::beans::XPropertyContainer,
+ css::beans::XPropertySetInfoChangeNotifier,
+ css::ucb::XContentCreator,
+ css::container::XChild,
+ css::ucb::XContent>,
+ public fileaccess::Notifier // implementation class
+ {
+ private:
+
+ // A special creator for inserted contents; Creates an ugly object
+ BaseContent( TaskManager* pMyShell,
+ OUString parentName,
+ bool bFolder );
+
+ public:
+ BaseContent(
+ TaskManager* pMyShell,
+ const css::uno::Reference< css::ucb::XContentIdentifier >& xContentIdentifier,
+ OUString aUnqPath );
+
+ virtual ~BaseContent() override;
+
+ // XComponent
+ virtual void SAL_CALL
+ dispose() override;
+
+ virtual void SAL_CALL
+ addEventListener(
+ const css::uno::Reference< css::lang::XEventListener >& xListener ) override;
+
+ virtual void SAL_CALL
+ removeEventListener( const css::uno::Reference< css::lang::XEventListener >& aListener ) 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;
+
+
+ // XCommandProcessor
+ virtual sal_Int32 SAL_CALL
+ createCommandIdentifier() 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;
+
+
+ // XContent
+ virtual css::uno::Reference< css::ucb::XContentIdentifier > SAL_CALL
+ getIdentifier() override;
+
+ virtual OUString SAL_CALL
+ getContentType() override;
+
+ virtual void SAL_CALL
+ addContentEventListener(
+ const css::uno::Reference< css::ucb::XContentEventListener >& Listener ) override;
+
+ virtual void SAL_CALL
+ removeContentEventListener(
+ const css::uno::Reference< css::ucb::XContentEventListener >& Listener ) override;
+
+ // XPropertiesChangeNotifier
+
+ virtual void SAL_CALL
+ addPropertiesChangeListener(
+ const css::uno::Sequence< OUString >& PropertyNames,
+ const css::uno::Reference< css::beans::XPropertiesChangeListener >& Listener ) override;
+
+ virtual void SAL_CALL
+ removePropertiesChangeListener( const css::uno::Sequence< OUString >& PropertyNames,
+ const css::uno::Reference< css::beans::XPropertiesChangeListener >& Listener ) override;
+
+ // XPropertyContainer
+
+ virtual void SAL_CALL
+ addProperty(
+ const OUString& Name,
+ sal_Int16 Attributes,
+ const css::uno::Any& DefaultValue ) override;
+
+ virtual void SAL_CALL
+ removeProperty( const OUString& Name ) override;
+
+ // XPropertySetInfoChangeNotifier
+
+ virtual void SAL_CALL
+ addPropertySetInfoChangeListener(
+ const css::uno::Reference< css::beans::XPropertySetInfoChangeListener >& Listener ) override;
+
+ virtual void SAL_CALL
+ removePropertySetInfoChangeListener(
+ const css::uno::Reference< css::beans::XPropertySetInfoChangeListener >& Listener ) override;
+
+
+ // XContentCreator
+
+ 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;
+
+
+ // XChild
+ virtual css::uno::Reference< css::uno::XInterface > SAL_CALL
+ getParent() override;
+
+ // Not supported
+ virtual void SAL_CALL
+ setParent( const css::uno::Reference< css::uno::XInterface >& Parent ) override;
+
+
+ // Notifier
+
+ std::optional<ContentEventNotifier> cDEL() override;
+ std::optional<ContentEventNotifier> cEXC( const OUString& aNewName ) override;
+ std::optional<ContentEventNotifier> cCEL() override;
+ std::optional<PropertySetInfoChangeNotifier> cPSL() override;
+ std::optional<PropertyChangeNotifier> cPCL() override;
+
+ private:
+ // Data members
+ TaskManager* m_pMyShell;
+ css::uno::Reference< css::ucb::XContentIdentifier > m_xContentIdentifier;
+ OUString m_aUncPath;
+
+ enum state { NameForInsertionSet = 1,
+ JustInserted = 2,
+ Deleted = 4,
+ FullFeatured = 8 };
+ bool m_bFolder;
+ sal_uInt16 m_nState;
+
+ std::mutex m_aMutex;
+ comphelper::OInterfaceContainerHelper4<css::lang::XEventListener> m_aDisposeEventListeners;
+ comphelper::OInterfaceContainerHelper4<css::ucb::XContentEventListener> m_aContentEventListeners;
+ comphelper::OInterfaceContainerHelper4<css::beans::XPropertySetInfoChangeListener> m_aPropertySetInfoChangeListeners;
+ std::unique_ptr<PropertyListeners> m_pPropertyListener;
+
+
+ // Private Methods
+ /// @throws css::uno::RuntimeException
+ css::uno::Reference< css::ucb::XCommandInfo >
+ getCommandInfo();
+
+ /// @throws css::uno::RuntimeException
+ css::uno::Reference< css::beans::XPropertySetInfo >
+ getPropertySetInfo();
+
+ /// @throws css::uno::RuntimeException
+ css::uno::Reference< css::sdbc::XRow >
+ getPropertyValues(
+ sal_Int32 nMyCommandIdentifier,
+ const css::uno::Sequence< css::beans::Property >& PropertySet );
+
+ css::uno::Sequence< css::uno::Any >
+ setPropertyValues(
+ sal_Int32 nMyCommandIdentifier,
+ const css::uno::Sequence< css::beans::PropertyValue >& Values );
+
+ css::uno::Reference< css::ucb::XDynamicResultSet >
+ open(
+ sal_Int32 nMyCommandIdentifier,
+ const css::ucb::OpenCommandArgument2& aCommandArgument );
+
+ void
+ deleteContent( sal_Int32 nMyCommandIdentifier );
+
+
+ void
+ transfer( sal_Int32 nMyCommandIdentifier,
+ const css::ucb::TransferInfo& aTransferInfo );
+
+ void
+ insert( sal_Int32 nMyCommandIdentifier,
+ const css::ucb::InsertCommandArgument& aInsertArgument );
+
+ void endTask( sal_Int32 CommandId );
+
+ friend class ContentEventNotifier;
+ };
+
+} // end namespace fileaccess
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/ucb/source/ucp/file/filcmd.cxx b/ucb/source/ucp/file/filcmd.cxx
new file mode 100644
index 0000000000..7bbfca94e0
--- /dev/null
+++ b/ucb/source/ucp/file/filcmd.cxx
@@ -0,0 +1,123 @@
+/* -*- 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:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include <sal/config.h>
+#include <cppuhelper/queryinterface.hxx>
+
+#include <com/sun/star/ucb/UnsupportedCommandException.hpp>
+
+#include "filcmd.hxx"
+#include "filtask.hxx"
+
+using namespace fileaccess;
+using namespace com::sun::star;
+using namespace com::sun::star::ucb;
+
+#if OSL_DEBUG_LEVEL > 0
+#define THROW_WHERE SAL_WHERE
+#else
+#define THROW_WHERE ""
+#endif
+
+XCommandInfo_impl::XCommandInfo_impl( TaskManager* pMyShell )
+ : m_pMyShell( pMyShell )
+{
+}
+
+XCommandInfo_impl::~XCommandInfo_impl()
+{
+}
+
+
+void SAL_CALL
+XCommandInfo_impl::acquire()
+ noexcept
+{
+ OWeakObject::acquire();
+}
+
+
+void SAL_CALL
+XCommandInfo_impl::release()
+ noexcept
+{
+ OWeakObject::release();
+}
+
+
+uno::Any SAL_CALL
+XCommandInfo_impl::queryInterface( const uno::Type& rType )
+{
+ uno::Any aRet = cppu::queryInterface( rType,
+ static_cast< XCommandInfo* >(this) );
+ return aRet.hasValue() ? aRet : OWeakObject::queryInterface( rType );
+}
+
+
+uno::Sequence< CommandInfo > SAL_CALL
+XCommandInfo_impl::getCommands()
+{
+ return m_pMyShell->m_sCommandInfo;
+}
+
+
+CommandInfo SAL_CALL
+XCommandInfo_impl::getCommandInfoByName(
+ const OUString& aName )
+{
+ auto pCommand = std::find_if(std::cbegin(m_pMyShell->m_sCommandInfo), std::cend(m_pMyShell->m_sCommandInfo),
+ [&aName](const CommandInfo& rCommand) { return rCommand.Name == aName; });
+ if (pCommand != std::cend(m_pMyShell->m_sCommandInfo))
+ return *pCommand;
+
+ throw UnsupportedCommandException( THROW_WHERE );
+}
+
+
+CommandInfo SAL_CALL
+XCommandInfo_impl::getCommandInfoByHandle(
+ sal_Int32 Handle )
+{
+ auto pCommand = std::find_if(std::cbegin(m_pMyShell->m_sCommandInfo), std::cend(m_pMyShell->m_sCommandInfo),
+ [&Handle](const CommandInfo& rCommand) { return rCommand.Handle == Handle; });
+ if (pCommand != std::cend(m_pMyShell->m_sCommandInfo))
+ return *pCommand;
+
+ throw UnsupportedCommandException( THROW_WHERE );
+}
+
+
+sal_Bool SAL_CALL
+XCommandInfo_impl::hasCommandByName(
+ const OUString& aName )
+{
+ return std::any_of(std::cbegin(m_pMyShell->m_sCommandInfo), std::cend(m_pMyShell->m_sCommandInfo),
+ [&aName](const CommandInfo& rCommand) { return rCommand.Name == aName; });
+}
+
+
+sal_Bool SAL_CALL
+XCommandInfo_impl::hasCommandByHandle(
+ sal_Int32 Handle )
+{
+ return std::any_of(std::cbegin(m_pMyShell->m_sCommandInfo), std::cend(m_pMyShell->m_sCommandInfo),
+ [&Handle](const CommandInfo& rCommand) { return rCommand.Handle == Handle; });
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/ucb/source/ucp/file/filcmd.hxx b/ucb/source/ucp/file/filcmd.hxx
new file mode 100644
index 0000000000..4a7729e89b
--- /dev/null
+++ b/ucb/source/ucp/file/filcmd.hxx
@@ -0,0 +1,80 @@
+/* -*- 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:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+#pragma once
+
+#include <rtl/ustring.hxx>
+#include <cppuhelper/weak.hxx>
+#include <com/sun/star/ucb/XCommandInfo.hpp>
+
+
+namespace fileaccess {
+
+
+ // forward
+ class TaskManager;
+
+
+ class XCommandInfo_impl
+ : public cppu::OWeakObject,
+ public css::ucb::XCommandInfo
+ {
+ public:
+
+ explicit XCommandInfo_impl( TaskManager* pMyShell );
+
+ virtual ~XCommandInfo_impl() override;
+
+ // XInterface
+ virtual css::uno::Any SAL_CALL
+ queryInterface( const css::uno::Type& aType ) override;
+
+ virtual void SAL_CALL
+ acquire()
+ noexcept override;
+
+ virtual void SAL_CALL
+ release()
+ noexcept override;
+
+ // XCommandInfo
+
+ virtual css::uno::Sequence< css::ucb::CommandInfo > SAL_CALL
+ getCommands() override;
+
+ virtual css::ucb::CommandInfo SAL_CALL
+ getCommandInfoByName( const OUString& Name ) override;
+
+ virtual css::ucb::CommandInfo SAL_CALL
+ getCommandInfoByHandle( sal_Int32 Handle ) override;
+
+ virtual sal_Bool SAL_CALL
+ hasCommandByName( const OUString& Name ) override;
+
+ virtual sal_Bool SAL_CALL
+ hasCommandByHandle( sal_Int32 Handle ) override;
+
+
+ private:
+
+ TaskManager* m_pMyShell;
+ };
+
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/ucb/source/ucp/file/filerror.hxx b/ucb/source/ucp/file/filerror.hxx
new file mode 100644
index 0000000000..c75d11ea19
--- /dev/null
+++ b/ucb/source/ucp/file/filerror.hxx
@@ -0,0 +1,105 @@
+/* -*- 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:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#pragma once
+
+namespace fileaccess {
+
+// Error codes used to deliver the resulting exceptions
+
+#define TASKHANDLER_NO_ERROR 0
+#define TASKHANDLER_UNSUPPORTED_COMMAND 1
+#define TASKHANDLING_WRONG_SETPROPERTYVALUES_ARGUMENT 2
+#define TASKHANDLING_WRONG_GETPROPERTYVALUES_ARGUMENT 3
+#define TASKHANDLING_WRONG_OPEN_ARGUMENT 4
+#define TASKHANDLING_WRONG_DELETE_ARGUMENT 5
+#define TASKHANDLING_WRONG_TRANSFER_ARGUMENT 6
+#define TASKHANDLING_WRONG_INSERT_ARGUMENT 7
+#define TASKHANDLING_WRONG_CREATENEWCONTENT_ARGUMENT 8
+#define TASKHANDLING_UNSUPPORTED_OPEN_MODE 9
+
+#define TASKHANDLING_DELETED_STATE_IN_OPEN_COMMAND 10
+#define TASKHANDLING_INSERTED_STATE_IN_OPEN_COMMAND 11
+
+#define TASKHANDLING_OPEN_FILE_FOR_PAGING 12
+#define TASKHANDLING_NOTCONNECTED_FOR_PAGING 13
+#define TASKHANDLING_BUFFERSIZEEXCEEDED_FOR_PAGING 14
+#define TASKHANDLING_IOEXCEPTION_FOR_PAGING 15
+#define TASKHANDLING_READING_FILE_FOR_PAGING 16
+
+#define TASKHANDLING_OPEN_FOR_INPUTSTREAM 17
+#define TASKHANDLING_OPEN_FOR_STREAM 18
+#define TASKHANDLING_OPEN_FOR_DIRECTORYLISTING 19
+
+#define TASKHANDLING_NOFRESHINSERT_IN_INSERT_COMMAND 22
+#define TASKHANDLING_NONAMESET_INSERT_COMMAND 23
+#define TASKHANDLING_NOCONTENTTYPE_INSERT_COMMAND 24
+
+#define TASKHANDLING_NO_OPEN_FILE_FOR_OVERWRITE 26
+#define TASKHANDLING_NO_OPEN_FILE_FOR_WRITE 27
+#define TASKHANDLING_NOTCONNECTED_FOR_WRITE 28
+#define TASKHANDLING_BUFFERSIZEEXCEEDED_FOR_WRITE 29
+#define TASKHANDLING_IOEXCEPTION_FOR_WRITE 30
+#define TASKHANDLING_FILEIOERROR_FOR_WRITE 31
+#define TASKHANDLING_FILEIOERROR_FOR_NO_SPACE 71
+#define TASKHANDLING_FILESIZE_FOR_WRITE 32
+#define TASKHANDLING_INPUTSTREAM_FOR_WRITE 33
+#define TASKHANDLING_NOREPLACE_FOR_WRITE 34
+#define TASKHANDLING_ENSUREDIR_FOR_WRITE 35
+
+#define TASKHANDLING_FOLDER_EXISTS_MKDIR 69
+#define TASKHANDLING_INVALID_NAME_MKDIR 70
+#define TASKHANDLING_CREATEDIRECTORY_MKDIR 36
+
+#define TASKHANDLING_NOSUCHFILEORDIR_FOR_REMOVE 38
+#define TASKHANDLING_VALIDFILESTATUS_FOR_REMOVE 39
+#define TASKHANDLING_OPENDIRECTORY_FOR_REMOVE 40
+#define TASKHANDLING_DELETEFILE_FOR_REMOVE 41
+#define TASKHANDLING_DELETEDIRECTORY_FOR_REMOVE 42
+#define TASKHANDLING_FILETYPE_FOR_REMOVE 43
+#define TASKHANDLING_VALIDFILESTATUSWHILE_FOR_REMOVE 44
+#define TASKHANDLING_DIRECTORYEXHAUSTED_FOR_REMOVE 45
+
+#define TASKHANDLING_TRANSFER_ACCESSINGROOT 46
+#define TASKHANDLING_TRANSFER_INVALIDSCHEME 47
+#define TASKHANDLING_TRANSFER_INVALIDURL 48
+#define TASKHANDLING_TRANSFER_DESTFILETYPE 50
+#define TASKHANDLING_TRANSFER_BY_MOVE_SOURCE 51
+#define TASKHANDLING_TRANSFER_BY_MOVE_SOURCESTAT 52
+#define TASKHANDLING_KEEPERROR_FOR_MOVE 53
+#define TASKHANDLING_NAMECLASH_FOR_MOVE 54
+#define TASKHANDLING_NAMECLASHMOVE_FOR_MOVE 55
+#define TASKHANDLING_NAMECLASHSUPPORT_FOR_MOVE 56
+#define TASKHANDLING_OVERWRITE_FOR_MOVE 57
+#define TASKHANDLING_RENAME_FOR_MOVE 58
+#define TASKHANDLING_RENAMEMOVE_FOR_MOVE 59
+
+#define TASKHANDLING_TRANSFER_BY_COPY_SOURCE 60
+#define TASKHANDLING_TRANSFER_BY_COPY_SOURCESTAT 61
+#define TASKHANDLING_KEEPERROR_FOR_COPY 62
+#define TASKHANDLING_OVERWRITE_FOR_COPY 63
+#define TASKHANDLING_RENAME_FOR_COPY 64
+#define TASKHANDLING_RENAMEMOVE_FOR_COPY 65
+#define TASKHANDLING_NAMECLASH_FOR_COPY 66
+#define TASKHANDLING_NAMECLASHMOVE_FOR_COPY 67
+#define TASKHANDLING_NAMECLASHSUPPORT_FOR_COPY 68
+
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/ucb/source/ucp/file/filglob.cxx b/ucb/source/ucp/file/filglob.cxx
new file mode 100644
index 0000000000..ef44d7b253
--- /dev/null
+++ b/ucb/source/ucp/file/filglob.cxx
@@ -0,0 +1,855 @@
+/* -*- 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:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include <sal/config.h>
+
+#include <string_view>
+
+#include "filglob.hxx"
+#include "filerror.hxx"
+#include "bc.hxx"
+#include <osl/file.hxx>
+#include <ucbhelper/cancelcommandexecution.hxx>
+#include <com/sun/star/ucb/UnsupportedCommandException.hpp>
+#include <com/sun/star/ucb/UnsupportedOpenModeException.hpp>
+#include <com/sun/star/lang/IllegalArgumentException.hpp>
+#include <com/sun/star/ucb/IOErrorCode.hpp>
+#include <com/sun/star/ucb/MissingPropertiesException.hpp>
+#include <com/sun/star/ucb/MissingInputStreamException.hpp>
+#include <com/sun/star/ucb/NameClashException.hpp>
+#include <com/sun/star/ucb/InteractiveBadTransferURLException.hpp>
+#include <com/sun/star/ucb/UnsupportedNameClashException.hpp>
+#include <com/sun/star/beans/PropertyState.hpp>
+#include <com/sun/star/beans/PropertyValue.hpp>
+#include <com/sun/star/ucb/InteractiveAugmentedIOException.hpp>
+#include <com/sun/star/uno/Any.hxx>
+#include <com/sun/star/uno/Sequence.hxx>
+#include <o3tl/string_view.hxx>
+#include <osl/diagnose.h>
+#include <rtl/uri.hxx>
+#include <rtl/ustring.hxx>
+#include <sal/types.h>
+
+using namespace ucbhelper;
+using namespace osl;
+using namespace ::com::sun::star;
+using namespace com::sun::star::task;
+using namespace com::sun::star::beans;
+using namespace com::sun::star::lang;
+using namespace com::sun::star::uno;
+using namespace com::sun::star::ucb;
+
+namespace {
+
+ Sequence< Any > generateErrorArguments(
+ OUString const & rPhysicalUrl)
+ {
+ OUString aResourceName;
+ OUString aResourceType;
+ bool bRemovable = false;
+ bool bResourceName = false;
+ bool bResourceType = false;
+ bool bRemoveProperty = false;
+
+ if (osl::FileBase::getSystemPathFromFileURL(
+ rPhysicalUrl,
+ aResourceName)
+ == osl::FileBase::E_None)
+ bResourceName = true;
+
+ // The resource types "folder" (i.e., directory) and
+ // "volume" seem to be
+ // the most interesting when producing meaningful error messages:
+ osl::DirectoryItem aItem;
+ if (osl::DirectoryItem::get(rPhysicalUrl, aItem) ==
+ osl::FileBase::E_None)
+ {
+ osl::FileStatus aStatus( osl_FileStatus_Mask_Type );
+ if (aItem.getFileStatus(aStatus) == osl::FileBase::E_None)
+ switch (aStatus.getFileType())
+ {
+ case osl::FileStatus::Directory:
+ aResourceType = "folder";
+ bResourceType = true;
+ break;
+
+ case osl::FileStatus::Volume:
+ {
+ aResourceType = "volume";
+ bResourceType = true;
+ osl::VolumeInfo aVolumeInfo(
+ osl_VolumeInfo_Mask_Attributes );
+ if( osl::Directory::getVolumeInfo(
+ rPhysicalUrl,aVolumeInfo ) ==
+ osl::FileBase::E_None )
+ {
+ bRemovable = aVolumeInfo.getRemoveableFlag();
+ bRemoveProperty = true;
+ }
+ }
+ break;
+ case osl::FileStatus::Regular:
+ case osl::FileStatus::Fifo:
+ case osl::FileStatus::Socket:
+ case osl::FileStatus::Link:
+ case osl::FileStatus::Special:
+ case osl::FileStatus::Unknown:
+ // do nothing for now
+ break;
+ }
+ }
+
+ Sequence< Any > aArguments( 1 +
+ (bResourceName ? 1 : 0) +
+ (bResourceType ? 1 : 0) +
+ (bRemoveProperty ? 1 : 0) );
+ auto pArguments = aArguments.getArray();
+ sal_Int32 i = 0;
+ pArguments[i++]
+ <<= PropertyValue("Uri",
+ -1,
+ Any(rPhysicalUrl),
+ PropertyState_DIRECT_VALUE);
+ if (bResourceName)
+ pArguments[i++]
+ <<= PropertyValue("ResourceName",
+ -1,
+ Any(aResourceName),
+ PropertyState_DIRECT_VALUE);
+ if (bResourceType)
+ pArguments[i++]
+ <<= PropertyValue("ResourceType",
+ -1,
+ Any(aResourceType),
+ PropertyState_DIRECT_VALUE);
+ if (bRemoveProperty)
+ pArguments[i++]
+ <<= PropertyValue("Removable",
+ -1,
+ Any(bRemovable),
+ PropertyState_DIRECT_VALUE);
+
+ return aArguments;
+ }
+}
+
+
+namespace fileaccess {
+
+
+ bool isChild( std::u16string_view srcUnqPath,
+ std::u16string_view dstUnqPath )
+ {
+ static const sal_Unicode slash = '/';
+ // Simple lexical comparison
+ size_t srcL = srcUnqPath.size();
+ size_t dstL = dstUnqPath.size();
+
+ return (
+ ( srcUnqPath == dstUnqPath )
+ ||
+ ( ( dstL > srcL )
+ &&
+ o3tl::starts_with(dstUnqPath, srcUnqPath)
+ &&
+ ( dstUnqPath[ srcL ] == slash ) )
+ );
+ }
+
+
+ OUString newName(
+ std::u16string_view aNewPrefix,
+ std::u16string_view aOldPrefix,
+ std::u16string_view old_Name )
+ {
+ size_t srcL = aOldPrefix.size();
+
+ return OUString::Concat(aNewPrefix) + old_Name.substr( srcL );
+ }
+
+
+ std::u16string_view getTitle( std::u16string_view aPath )
+ {
+ size_t lastIndex = aPath.rfind( '/' );
+ return aPath.substr( lastIndex + 1 );
+ }
+
+
+ OUString getParentName( std::u16string_view aFileName )
+ {
+ size_t lastIndex = aFileName.rfind( '/' );
+ OUString aParent( aFileName.substr( 0,lastIndex ) );
+
+ if( aParent.endsWith(":") && aParent.getLength() == 6 )
+ aParent += "/";
+
+ if ( aParent == "file://" )
+ aParent = "file:///";
+
+ return aParent;
+ }
+
+
+ osl::FileBase::RC osl_File_copy( const OUString& strPath,
+ const OUString& strDestPath,
+ bool test )
+ {
+ if( test )
+ {
+ osl::DirectoryItem aItem;
+ if( osl::DirectoryItem::get( strDestPath,aItem ) != osl::FileBase:: E_NOENT )
+ return osl::FileBase::E_EXIST;
+ }
+
+ return osl::File::copy( strPath,strDestPath );
+ }
+
+
+ osl::FileBase::RC osl_File_move( const OUString& strPath,
+ const OUString& strDestPath,
+ bool test )
+ {
+ if( test )
+ {
+ osl::DirectoryItem aItem;
+ if( osl::DirectoryItem::get( strDestPath,aItem ) != osl::FileBase:: E_NOENT )
+ return osl::FileBase::E_EXIST;
+ }
+
+ return osl::File::move( strPath,strDestPath );
+ }
+
+ void throw_handler(
+ sal_Int32 errorCode,
+ sal_Int32 minorCode,
+ const Reference< XCommandEnvironment >& xEnv,
+ const OUString& aUncPath,
+ BaseContent* pContent,
+ bool isHandled )
+ {
+ Reference<XCommandProcessor> xComProc(pContent);
+ Any aAny;
+ IOErrorCode ioErrorCode;
+
+ if( errorCode == TASKHANDLER_UNSUPPORTED_COMMAND )
+ {
+ aAny <<= UnsupportedCommandException( OSL_LOG_PREFIX );
+ cancelCommandExecution( aAny,xEnv );
+ }
+ else if( errorCode == TASKHANDLING_WRONG_SETPROPERTYVALUES_ARGUMENT ||
+ errorCode == TASKHANDLING_WRONG_GETPROPERTYVALUES_ARGUMENT ||
+ errorCode == TASKHANDLING_WRONG_OPEN_ARGUMENT ||
+ errorCode == TASKHANDLING_WRONG_DELETE_ARGUMENT ||
+ errorCode == TASKHANDLING_WRONG_TRANSFER_ARGUMENT ||
+ errorCode == TASKHANDLING_WRONG_INSERT_ARGUMENT ||
+ errorCode == TASKHANDLING_WRONG_CREATENEWCONTENT_ARGUMENT )
+ {
+ IllegalArgumentException excep;
+ excep.ArgumentPosition = 0;
+ cancelCommandExecution(Any(excep), xEnv);
+ }
+ else if( errorCode == TASKHANDLING_UNSUPPORTED_OPEN_MODE )
+ {
+ UnsupportedOpenModeException excep;
+ excep.Mode = sal::static_int_cast< sal_Int16 >(minorCode);
+ cancelCommandExecution( Any(excep),xEnv );
+ }
+ else if(errorCode == TASKHANDLING_DELETED_STATE_IN_OPEN_COMMAND ||
+ errorCode == TASKHANDLING_INSERTED_STATE_IN_OPEN_COMMAND ||
+ errorCode == TASKHANDLING_NOFRESHINSERT_IN_INSERT_COMMAND )
+ {
+ // What to do here?
+ }
+ else if(
+ // error in opening file
+ errorCode == TASKHANDLING_NO_OPEN_FILE_FOR_OVERWRITE ||
+ // error in opening file
+ errorCode == TASKHANDLING_NO_OPEN_FILE_FOR_WRITE ||
+ // error in opening file
+ errorCode == TASKHANDLING_OPEN_FOR_STREAM ||
+ // error in opening file
+ errorCode == TASKHANDLING_OPEN_FOR_INPUTSTREAM ||
+ // error in opening file
+ errorCode == TASKHANDLING_OPEN_FILE_FOR_PAGING )
+ {
+ switch( minorCode )
+ {
+ case FileBase::E_NAMETOOLONG:
+ // pathname was too long
+ ioErrorCode = IOErrorCode_NAME_TOO_LONG;
+ break;
+ case FileBase::E_NXIO:
+ // No such device or address
+ case FileBase::E_NODEV:
+ // No such device
+ ioErrorCode = IOErrorCode_INVALID_DEVICE;
+ break;
+ case FileBase::E_NOTDIR:
+ ioErrorCode = IOErrorCode_NOT_EXISTING_PATH;
+ break;
+ case FileBase::E_NOENT:
+ // No such file or directory
+ ioErrorCode = IOErrorCode_NOT_EXISTING;
+ break;
+ case FileBase::E_ROFS:
+ // #i4735# handle ROFS transparently as ACCESS_DENIED
+ case FileBase::E_ACCES:
+ case FileBase::E_PERM:
+ // permission denied<P>
+ ioErrorCode = IOErrorCode_ACCESS_DENIED;
+ break;
+ case FileBase::E_ISDIR:
+ // Is a directory<p>
+ ioErrorCode = IOErrorCode_NO_FILE;
+ break;
+ case FileBase::E_NOTREADY:
+ ioErrorCode = IOErrorCode_DEVICE_NOT_READY;
+ break;
+ case FileBase::E_MFILE:
+ // too many open files used by the process
+ case FileBase::E_NFILE:
+ // too many open files in the system
+ ioErrorCode = IOErrorCode_OUT_OF_FILE_HANDLES;
+ break;
+ case FileBase::E_INVAL:
+ // the format of the parameters was not valid
+ ioErrorCode = IOErrorCode_INVALID_PARAMETER;
+ break;
+ case FileBase::E_NOMEM:
+ // not enough memory for allocating structures
+ ioErrorCode = IOErrorCode_OUT_OF_MEMORY;
+ break;
+ case FileBase::E_BUSY:
+ // Text file busy
+ ioErrorCode = IOErrorCode_LOCKING_VIOLATION;
+ break;
+ case FileBase::E_AGAIN:
+ // Operation would block
+ ioErrorCode = IOErrorCode_LOCKING_VIOLATION;
+ break;
+ case FileBase::E_NOLCK: // No record locks available
+ ioErrorCode = IOErrorCode_LOCKING_VIOLATION;
+ break;
+ case FileBase::E_NOSYS:
+ ioErrorCode = IOErrorCode_NOT_SUPPORTED;
+ break;
+ case FileBase::E_FAULT: // Bad address
+ case FileBase::E_LOOP: // Too many symbolic links encountered
+ case FileBase::E_NOSPC: // No space left on device
+ case FileBase::E_INTR: // function call was interrupted
+ case FileBase::E_IO: // I/O error
+ case FileBase::E_MULTIHOP: // Multihop attempted
+ case FileBase::E_NOLINK: // Link has been severed
+ default:
+ ioErrorCode = IOErrorCode_GENERAL;
+ break;
+ }
+
+ cancelCommandExecution(
+ ioErrorCode,
+ generateErrorArguments(aUncPath),
+ xEnv,
+ "an error occurred during file opening",
+ xComProc);
+ }
+ else if( errorCode == TASKHANDLING_OPEN_FOR_DIRECTORYLISTING ||
+ errorCode == TASKHANDLING_OPENDIRECTORY_FOR_REMOVE )
+ {
+ switch( minorCode )
+ {
+ case FileBase::E_INVAL:
+ // the format of the parameters was not valid
+ ioErrorCode = IOErrorCode_INVALID_PARAMETER;
+ break;
+ case FileBase::E_NOENT:
+ // the specified path doesn't exist
+ ioErrorCode = IOErrorCode_NOT_EXISTING;
+ break;
+ case FileBase::E_NOTDIR:
+ // the specified path is not a directory
+ ioErrorCode = IOErrorCode_NO_DIRECTORY;
+ break;
+ case FileBase::E_NOMEM:
+ // not enough memory for allocating structures
+ ioErrorCode = IOErrorCode_OUT_OF_MEMORY;
+ break;
+ case FileBase::E_ROFS:
+ // #i4735# handle ROFS transparently as ACCESS_DENIED
+ case FileBase::E_ACCES: // permission denied
+ ioErrorCode = IOErrorCode_ACCESS_DENIED;
+ break;
+ case FileBase::E_NOTREADY:
+ ioErrorCode = IOErrorCode_DEVICE_NOT_READY;
+ break;
+ case FileBase::E_MFILE:
+ // too many open files used by the process
+ case FileBase::E_NFILE:
+ // too many open files in the system
+ ioErrorCode = IOErrorCode_OUT_OF_FILE_HANDLES;
+ break;
+ case FileBase::E_NAMETOOLONG:
+ // File name too long
+ ioErrorCode = IOErrorCode_NAME_TOO_LONG;
+ break;
+ case FileBase::E_LOOP:
+ // Too many symbolic links encountered<p>
+ default:
+ ioErrorCode = IOErrorCode_GENERAL;
+ break;
+ }
+
+ cancelCommandExecution(
+ ioErrorCode,
+ generateErrorArguments(aUncPath),
+ xEnv,
+ "an error occurred during opening a directory",
+ xComProc);
+ }
+ else if( errorCode == TASKHANDLING_NOTCONNECTED_FOR_WRITE ||
+ errorCode == TASKHANDLING_BUFFERSIZEEXCEEDED_FOR_WRITE ||
+ errorCode == TASKHANDLING_IOEXCEPTION_FOR_WRITE ||
+ errorCode == TASKHANDLING_NOTCONNECTED_FOR_PAGING ||
+ errorCode == TASKHANDLING_BUFFERSIZEEXCEEDED_FOR_PAGING ||
+ errorCode == TASKHANDLING_IOEXCEPTION_FOR_PAGING )
+ {
+ ioErrorCode = IOErrorCode_UNKNOWN;
+ cancelCommandExecution(
+ ioErrorCode,
+ generateErrorArguments(aUncPath),
+ xEnv,
+ "an error occurred writing or reading from a file",
+ xComProc );
+ }
+ else if( errorCode == TASKHANDLING_FILEIOERROR_FOR_NO_SPACE )
+ {
+ ioErrorCode = IOErrorCode_OUT_OF_DISK_SPACE;
+ cancelCommandExecution(
+ ioErrorCode,
+ generateErrorArguments(aUncPath),
+ xEnv,
+ "device full",
+ xComProc);
+ }
+ else if( errorCode == TASKHANDLING_FILEIOERROR_FOR_WRITE ||
+ errorCode == TASKHANDLING_READING_FILE_FOR_PAGING )
+ {
+ switch( minorCode )
+ {
+ case FileBase::E_INVAL:
+ // the format of the parameters was not valid
+ ioErrorCode = IOErrorCode_INVALID_PARAMETER;
+ break;
+ case FileBase::E_FBIG:
+ // File too large
+ ioErrorCode = IOErrorCode_CANT_WRITE;
+ break;
+ case FileBase::E_NOSPC:
+ // No space left on device
+ ioErrorCode = IOErrorCode_OUT_OF_DISK_SPACE;
+ break;
+ case FileBase::E_NXIO:
+ // No such device or address
+ ioErrorCode = IOErrorCode_INVALID_DEVICE;
+ break;
+ case FileBase::E_NOLINK:
+ // Link has been severed
+ case FileBase::E_ISDIR:
+ // Is a directory
+ ioErrorCode = IOErrorCode_NO_FILE;
+ break;
+ case FileBase::E_AGAIN:
+ // Operation would block
+ ioErrorCode = IOErrorCode_LOCKING_VIOLATION;
+ break;
+ case FileBase::E_TIMEDOUT:
+ ioErrorCode = IOErrorCode_DEVICE_NOT_READY;
+ break;
+ case FileBase::E_NOLCK: // No record locks available
+ ioErrorCode = IOErrorCode_LOCKING_VIOLATION;
+ break;
+ case FileBase::E_IO: // I/O error
+ case FileBase::E_BADF: // Bad file
+ case FileBase::E_FAULT: // Bad address
+ case FileBase::E_INTR: // function call was interrupted
+ default:
+ ioErrorCode = IOErrorCode_GENERAL;
+ break;
+ }
+ cancelCommandExecution(
+ ioErrorCode,
+ generateErrorArguments(aUncPath),
+ xEnv,
+ "an error occurred during opening a file",
+ xComProc);
+ }
+ else if( errorCode == TASKHANDLING_NONAMESET_INSERT_COMMAND ||
+ errorCode == TASKHANDLING_NOCONTENTTYPE_INSERT_COMMAND )
+ {
+ static constexpr OUString sTitle = u"Title"_ustr;
+ static constexpr OUString sContentType = u"ContentType"_ustr;
+ Sequence< OUString > aSeq{ (errorCode == TASKHANDLING_NONAMESET_INSERT_COMMAND)
+ ? sTitle
+ : sContentType };
+
+ aAny <<= MissingPropertiesException(
+ "a property is missing, necessary to create a content",
+ xComProc,
+ aSeq);
+ cancelCommandExecution(aAny,xEnv);
+ }
+ else if( errorCode == TASKHANDLING_FILESIZE_FOR_WRITE )
+ {
+ switch( minorCode )
+ {
+ case FileBase::E_INVAL:
+ // the format of the parameters was not valid
+ case FileBase::E_OVERFLOW:
+ // The resulting file offset would be a value which cannot
+ // be represented correctly for regular files
+ ioErrorCode = IOErrorCode_INVALID_PARAMETER;
+ break;
+ default:
+ ioErrorCode = IOErrorCode_GENERAL;
+ break;
+ }
+ cancelCommandExecution(
+ ioErrorCode,
+ generateErrorArguments(aUncPath),
+ xEnv,
+ "there were problems with the filesize",
+ xComProc);
+ }
+ else if(errorCode == TASKHANDLING_INPUTSTREAM_FOR_WRITE)
+ {
+ aAny <<=
+ MissingInputStreamException(
+ "the inputstream is missing, necessary to create a content",
+ xComProc);
+ cancelCommandExecution(aAny,xEnv);
+ }
+ else if( errorCode == TASKHANDLING_NOREPLACE_FOR_WRITE )
+ // Overwrite = false and file exists
+ {
+ NameClashException excep("file exists and overwrite forbidden",
+ Reference<XInterface>(xComProc, UNO_QUERY),
+ InteractionClassification_ERROR, OUString(getTitle(aUncPath)));
+ cancelCommandExecution( Any(excep), xEnv );
+ }
+ else if( errorCode == TASKHANDLING_INVALID_NAME_MKDIR )
+ {
+ PropertyValue prop;
+ prop.Name = "ResourceName";
+ prop.Handle = -1;
+ OUString aClashingName(
+ rtl::Uri::decode(
+ OUString(getTitle(aUncPath)),
+ rtl_UriDecodeWithCharset,
+ RTL_TEXTENCODING_UTF8));
+ prop.Value <<= aClashingName;
+ InteractiveAugmentedIOException excep(
+ "the name contained invalid characters", Reference<XInterface>(xComProc, UNO_QUERY),
+ InteractionClassification_ERROR, IOErrorCode_INVALID_CHARACTER, { Any(prop) });
+ if(isHandled)
+ throw excep;
+ cancelCommandExecution( Any(excep), xEnv );
+// ioErrorCode = IOErrorCode_INVALID_CHARACTER;
+// cancelCommandExecution(
+// ioErrorCode,
+// generateErrorArguments(aUncPath),
+// xEnv,
+// OUString( "the name contained invalid characters"),
+// xComProc );
+ }
+ else if( errorCode == TASKHANDLING_FOLDER_EXISTS_MKDIR )
+ {
+ NameClashException excep("folder exists and overwrite forbidden", xComProc,
+ InteractionClassification_ERROR, OUString(getTitle(aUncPath)));
+ if(isHandled)
+ throw excep;
+ cancelCommandExecution( Any(excep), xEnv );
+// ioErrorCode = IOErrorCode_ALREADY_EXISTING;
+// cancelCommandExecution(
+// ioErrorCode,
+// generateErrorArguments(aUncPath),
+// xEnv,
+// OUString( "the folder exists"),
+// xComProc );
+ }
+ else if( errorCode == TASKHANDLING_ENSUREDIR_FOR_WRITE ||
+ errorCode == TASKHANDLING_CREATEDIRECTORY_MKDIR )
+ {
+ switch( minorCode )
+ {
+ case FileBase::E_ACCES:
+ ioErrorCode = IOErrorCode_ACCESS_DENIED;
+ break;
+ case FileBase::E_ROFS:
+ ioErrorCode = IOErrorCode_WRITE_PROTECTED;
+ break;
+ case FileBase::E_NAMETOOLONG:
+ ioErrorCode = IOErrorCode_NAME_TOO_LONG;
+ break;
+ default:
+ ioErrorCode = IOErrorCode_NOT_EXISTING_PATH;
+ break;
+ }
+ cancelCommandExecution(
+ ioErrorCode,
+ generateErrorArguments(getParentName(aUncPath)),
+ //TODO! ok to supply physical URL to getParentName()?
+ xEnv,
+ "a folder could not be created",
+ xComProc );
+ }
+ else if( errorCode == TASKHANDLING_VALIDFILESTATUSWHILE_FOR_REMOVE ||
+ errorCode == TASKHANDLING_VALIDFILESTATUS_FOR_REMOVE ||
+ errorCode == TASKHANDLING_NOSUCHFILEORDIR_FOR_REMOVE )
+ {
+ switch( minorCode )
+ {
+ case FileBase::E_INVAL: // the format of the parameters was not valid
+ ioErrorCode = IOErrorCode_INVALID_PARAMETER;
+ break;
+ case FileBase::E_NOMEM: // not enough memory for allocating structures
+ ioErrorCode = IOErrorCode_OUT_OF_MEMORY;
+ break;
+ case FileBase::E_ROFS: // #i4735# handle ROFS transparently as ACCESS_DENIED
+ case FileBase::E_ACCES: // permission denied
+ ioErrorCode = IOErrorCode_ACCESS_DENIED;
+ break;
+ case FileBase::E_MFILE: // too many open files used by the process
+ case FileBase::E_NFILE: // too many open files in the system
+ ioErrorCode = IOErrorCode_OUT_OF_FILE_HANDLES;
+ break;
+ case FileBase::E_NOLINK: // Link has been severed
+ case FileBase::E_NOENT: // No such file or directory
+ ioErrorCode = IOErrorCode_NOT_EXISTING;
+ break;
+ case FileBase::E_NAMETOOLONG: // File name too long
+ ioErrorCode = IOErrorCode_NAME_TOO_LONG;
+ break;
+ case FileBase::E_NOTDIR: // A component of the path prefix of path is not a directory
+ ioErrorCode = IOErrorCode_NOT_EXISTING_PATH;
+ break;
+ case FileBase::E_LOOP: // Too many symbolic links encountered
+ case FileBase::E_IO: // I/O error
+ case FileBase::E_MULTIHOP: // Multihop attempted
+ case FileBase::E_FAULT: // Bad address
+ case FileBase::E_INTR: // function call was interrupted
+ case FileBase::E_NOSYS: // Function not implemented
+ case FileBase::E_NOSPC: // No space left on device
+ case FileBase::E_NXIO: // No such device or address
+ case FileBase::E_OVERFLOW: // Value too large for defined data type
+ case FileBase::E_BADF: // Invalid oslDirectoryItem parameter
+ default:
+ ioErrorCode = IOErrorCode_GENERAL;
+ break;
+ }
+ cancelCommandExecution(
+ ioErrorCode,
+ generateErrorArguments(aUncPath),
+ xEnv,
+ "a file status object could not be filled",
+ xComProc );
+ }
+ else if( errorCode == TASKHANDLING_DELETEFILE_FOR_REMOVE ||
+ errorCode == TASKHANDLING_DELETEDIRECTORY_FOR_REMOVE )
+ {
+ switch( minorCode )
+ {
+ case FileBase::E_INVAL: // the format of the parameters was not valid
+ ioErrorCode = IOErrorCode_INVALID_PARAMETER;
+ break;
+ case FileBase::E_NOMEM: // not enough memory for allocating structures
+ ioErrorCode = IOErrorCode_OUT_OF_MEMORY;
+ break;
+ case FileBase::E_ACCES: // Permission denied
+ ioErrorCode = IOErrorCode_ACCESS_DENIED;
+ break;
+ case FileBase::E_PERM: // Operation not permitted
+ ioErrorCode = IOErrorCode_NOT_SUPPORTED;
+ break;
+ case FileBase::E_NAMETOOLONG: // File name too long
+ ioErrorCode = IOErrorCode_NAME_TOO_LONG;
+ break;
+ case FileBase::E_NOLINK: // Link has been severed
+ case FileBase::E_NOENT: // No such file or directory
+ ioErrorCode = IOErrorCode_NOT_EXISTING;
+ break;
+ case FileBase::E_ISDIR: // Is a directory
+ case FileBase::E_ROFS: // Read-only file system
+ ioErrorCode = IOErrorCode_NOT_SUPPORTED;
+ break;
+ case FileBase::E_BUSY: // Device or resource busy
+ ioErrorCode = IOErrorCode_LOCKING_VIOLATION;
+ break;
+ case FileBase::E_FAULT: // Bad address
+ case FileBase::E_LOOP: // Too many symbolic links encountered
+ case FileBase::E_IO: // I/O error
+ case FileBase::E_INTR: // function call was interrupted
+ case FileBase::E_MULTIHOP: // Multihop attempted
+ default:
+ ioErrorCode = IOErrorCode_GENERAL;
+ break;
+ }
+ cancelCommandExecution(
+ ioErrorCode,
+ generateErrorArguments(aUncPath),
+ xEnv,
+ "a file or directory could not be deleted",
+ xComProc );
+ }
+ else if( errorCode == TASKHANDLING_TRANSFER_BY_COPY_SOURCE ||
+ errorCode == TASKHANDLING_TRANSFER_BY_COPY_SOURCESTAT ||
+ errorCode == TASKHANDLING_TRANSFER_BY_MOVE_SOURCE ||
+ errorCode == TASKHANDLING_TRANSFER_BY_MOVE_SOURCESTAT ||
+ errorCode == TASKHANDLING_TRANSFER_DESTFILETYPE ||
+ errorCode == TASKHANDLING_FILETYPE_FOR_REMOVE ||
+ errorCode == TASKHANDLING_DIRECTORYEXHAUSTED_FOR_REMOVE ||
+ errorCode == TASKHANDLING_TRANSFER_INVALIDURL )
+ {
+ OUString aMsg;
+ switch( minorCode )
+ {
+ case FileBase::E_NOENT: // No such file or directory
+ if ( errorCode == TASKHANDLING_TRANSFER_BY_COPY_SOURCE ||
+ errorCode == TASKHANDLING_TRANSFER_BY_COPY_SOURCESTAT ||
+ errorCode == TASKHANDLING_TRANSFER_BY_MOVE_SOURCE ||
+ errorCode == TASKHANDLING_TRANSFER_BY_MOVE_SOURCESTAT )
+ {
+ ioErrorCode = IOErrorCode_NOT_EXISTING;
+ aMsg = "source file/folder does not exist";
+ break;
+ }
+ else
+ {
+ ioErrorCode = IOErrorCode_GENERAL;
+ aMsg = "a general error during transfer command";
+ break;
+ }
+ default:
+ ioErrorCode = IOErrorCode_GENERAL;
+ aMsg = "a general error during transfer command";
+ break;
+ }
+ cancelCommandExecution(
+ ioErrorCode,
+ generateErrorArguments(aUncPath),
+ xEnv,
+ aMsg,
+ xComProc );
+ }
+ else if( errorCode == TASKHANDLING_TRANSFER_ACCESSINGROOT )
+ {
+ ioErrorCode = IOErrorCode_WRITE_PROTECTED;
+ cancelCommandExecution(
+ ioErrorCode,
+ generateErrorArguments(aUncPath),
+ xEnv,
+ "accessing the root during transfer",
+ xComProc );
+ }
+ else if( errorCode == TASKHANDLING_TRANSFER_INVALIDSCHEME )
+ {
+ aAny <<= InteractiveBadTransferURLException(
+ "bad transfer url",
+ xComProc);
+ cancelCommandExecution( aAny,xEnv );
+ }
+ else if( errorCode == TASKHANDLING_OVERWRITE_FOR_MOVE ||
+ errorCode == TASKHANDLING_OVERWRITE_FOR_COPY ||
+ errorCode == TASKHANDLING_NAMECLASHMOVE_FOR_MOVE ||
+ errorCode == TASKHANDLING_NAMECLASHMOVE_FOR_COPY ||
+ errorCode == TASKHANDLING_KEEPERROR_FOR_MOVE ||
+ errorCode == TASKHANDLING_KEEPERROR_FOR_COPY ||
+ errorCode == TASKHANDLING_RENAME_FOR_MOVE ||
+ errorCode == TASKHANDLING_RENAME_FOR_COPY ||
+ errorCode == TASKHANDLING_RENAMEMOVE_FOR_MOVE ||
+ errorCode == TASKHANDLING_RENAMEMOVE_FOR_COPY )
+ {
+ OUString aMsg(
+ "general error during transfer");
+
+ switch( minorCode )
+ {
+ case FileBase::E_EXIST:
+ ioErrorCode = IOErrorCode_ALREADY_EXISTING;
+ break;
+ case FileBase::E_INVAL: // the format of the parameters was not valid
+ ioErrorCode = IOErrorCode_INVALID_PARAMETER;
+ break;
+ case FileBase::E_NOMEM: // not enough memory for allocating structures
+ ioErrorCode = IOErrorCode_OUT_OF_MEMORY;
+ break;
+ case FileBase::E_ACCES: // Permission denied
+ ioErrorCode = IOErrorCode_ACCESS_DENIED;
+ break;
+ case FileBase::E_PERM: // Operation not permitted
+ ioErrorCode = IOErrorCode_NOT_SUPPORTED;
+ break;
+ case FileBase::E_NAMETOOLONG: // File name too long
+ ioErrorCode = IOErrorCode_NAME_TOO_LONG;
+ break;
+ case FileBase::E_NOENT: // No such file or directory
+ ioErrorCode = IOErrorCode_NOT_EXISTING;
+ aMsg = "file/folder does not exist";
+ break;
+ case FileBase::E_ROFS: // Read-only file system<p>
+ ioErrorCode = IOErrorCode_NOT_EXISTING;
+ break;
+ default:
+ ioErrorCode = IOErrorCode_GENERAL;
+ break;
+ }
+ cancelCommandExecution(
+ ioErrorCode,
+ generateErrorArguments(aUncPath),
+ xEnv,
+ aMsg,
+ xComProc );
+ }
+ else if( errorCode == TASKHANDLING_NAMECLASH_FOR_COPY ||
+ errorCode == TASKHANDLING_NAMECLASH_FOR_MOVE )
+ {
+ NameClashException excep("name clash during copy or move",
+ Reference<XInterface>(xComProc, UNO_QUERY),
+ InteractionClassification_ERROR, OUString(getTitle(aUncPath)));
+
+ cancelCommandExecution(Any(excep), xEnv);
+ }
+ else if( errorCode == TASKHANDLING_NAMECLASHSUPPORT_FOR_MOVE ||
+ errorCode == TASKHANDLING_NAMECLASHSUPPORT_FOR_COPY )
+ {
+ UnsupportedNameClashException excep(
+ "name clash value not supported during copy or move",
+ Reference<XInterface>(xComProc, UNO_QUERY), minorCode);
+
+ cancelCommandExecution(Any(excep), xEnv);
+ }
+ else
+ {
+ // case TASKHANDLER_NO_ERROR:
+ return;
+ }
+ }
+
+
+} // end namespace fileaccess
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/ucb/source/ucp/file/filglob.hxx b/ucb/source/ucp/file/filglob.hxx
new file mode 100644
index 0000000000..c113f62ecb
--- /dev/null
+++ b/ucb/source/ucp/file/filglob.hxx
@@ -0,0 +1,95 @@
+/* -*- 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:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+#pragma once
+
+#include <sal/config.h>
+
+#include <string_view>
+
+#include <rtl/ustring.hxx>
+#include <osl/file.hxx>
+#include <com/sun/star/ucb/XCommandEnvironment.hpp>
+
+
+namespace fileaccess {
+
+ class BaseContent;
+
+ /******************************************************************************/
+ /* */
+ /* Helper functions */
+ /* */
+ /******************************************************************************/
+
+
+ // Returns true if dstUnqPath is a child from srcUnqPath or both are equal
+
+ extern bool isChild( std::u16string_view srcUnqPath,
+ std::u16string_view dstUnqPath );
+
+
+ // Changes the prefix in name
+ extern OUString newName( std::u16string_view aNewPrefix,
+ std::u16string_view aOldPrefix,
+ std::u16string_view old_Name );
+
+ // returns the last part of the given url as title
+ extern std::u16string_view getTitle( std::u16string_view aPath );
+
+ // returns the url without last part as parentname
+ // In case aFileName is root ( file:/// ) root is returned
+
+ extern OUString getParentName( std::u16string_view aFileName );
+
+ /**
+ * special copy:
+ * On test = true, the implementation determines whether the
+ * destination exists and returns the appropriate errorcode E_EXIST.
+ * osl::File::copy copies unchecked.
+ */
+
+ extern osl::FileBase::RC osl_File_copy( const OUString& strPath,
+ const OUString& strDestPath,
+ bool test );
+
+ /**
+ * special move:
+ * On test = true, the implementation determines whether the
+ * destination exists and returns the appropriate errorcode E_EXIST.
+ * osl::File::move moves unchecked
+ */
+
+ extern osl::FileBase::RC osl_File_move( const OUString& strPath,
+ const OUString& strDestPath,
+ bool test = false );
+
+ // This function implements the global exception handler of the file_ucp;
+ // It never returns;
+
+ extern void throw_handler( sal_Int32 errorCode,
+ sal_Int32 minorCode,
+ const css::uno::Reference< css::ucb::XCommandEnvironment >& xEnv,
+ const OUString& aUncPath,
+ BaseContent* pContent,
+ bool isHandled);
+ // the physical URL of the object
+
+} // end namespace fileaccess
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/ucb/source/ucp/file/filid.cxx b/ucb/source/ucp/file/filid.cxx
new file mode 100644
index 0000000000..d42fd194d6
--- /dev/null
+++ b/ucb/source/ucp/file/filid.cxx
@@ -0,0 +1,61 @@
+/* -*- 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:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include "filid.hxx"
+#include "filtask.hxx"
+
+using namespace fileaccess;
+using namespace com::sun::star;
+using namespace com::sun::star::ucb;
+
+
+FileContentIdentifier::FileContentIdentifier(
+ const OUString& aUnqPath,
+ bool IsNormalized )
+{
+ if( IsNormalized )
+ {
+ fileaccess::TaskManager::getUrlFromUnq( aUnqPath,m_aContentId );
+ }
+ else
+ {
+ m_aContentId = aUnqPath;
+ }
+ TaskManager::getScheme( m_aProviderScheme );
+}
+
+FileContentIdentifier::~FileContentIdentifier()
+{
+}
+
+OUString
+SAL_CALL
+FileContentIdentifier::getContentIdentifier()
+{
+ return m_aContentId;
+}
+
+
+OUString SAL_CALL
+FileContentIdentifier::getContentProviderScheme()
+{
+ return m_aProviderScheme;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/ucb/source/ucp/file/filid.hxx b/ucb/source/ucp/file/filid.hxx
new file mode 100644
index 0000000000..dcb5449b0e
--- /dev/null
+++ b/ucb/source/ucp/file/filid.hxx
@@ -0,0 +1,55 @@
+/* -*- 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:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+#pragma once
+
+#include <rtl/ustring.hxx>
+#include <cppuhelper/implbase.hxx>
+#include <com/sun/star/ucb/XContentIdentifier.hpp>
+
+namespace fileaccess {
+
+ class TaskManager;
+
+ class FileContentIdentifier :
+ public cppu::WeakImplHelper<css::ucb::XContentIdentifier>
+ {
+
+ // This implementation has to be reworked
+ public:
+ FileContentIdentifier( const OUString& aUnqPath,
+ bool IsNormalized = true );
+
+ virtual ~FileContentIdentifier() override;
+
+ // XContentIdentifier
+ virtual OUString SAL_CALL
+ getContentIdentifier() override;
+
+ virtual OUString SAL_CALL
+ getContentProviderScheme() override;
+
+ private:
+ OUString m_aContentId; // The URL string
+ OUString m_aProviderScheme;
+ };
+
+} // end namespace fileaccess
+
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/ucb/source/ucp/file/filinl.hxx b/ucb/source/ucp/file/filinl.hxx
new file mode 100644
index 0000000000..3ea985ebc6
--- /dev/null
+++ b/ucb/source/ucp/file/filinl.hxx
@@ -0,0 +1,58 @@
+/* -*- 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:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+#pragma once
+
+#include "filtask.hxx"
+
+using namespace fileaccess;
+
+inline const bool& TaskManager::MyProperty::IsNative() const
+{
+ return isNative;
+}
+inline const sal_Int32& TaskManager::MyProperty::getHandle() const
+{
+ return Handle;
+}
+inline const css::uno::Type& TaskManager::MyProperty::getType() const
+{
+ return Typ;
+}
+inline const css::uno::Any& TaskManager::MyProperty::getValue() const
+{
+ return Value;
+}
+inline const css::beans::PropertyState& TaskManager::MyProperty::getState() const
+{
+ return State;
+}
+inline const sal_Int16& TaskManager::MyProperty::getAttributes() const
+{
+ return Attributes;
+}
+inline void TaskManager::MyProperty::setValue( css::uno::Any theValue ) const
+{
+ const_cast<MyProperty*>(this)->Value = std::move(theValue);
+}
+inline void TaskManager::MyProperty::setState( const css::beans::PropertyState& theState ) const
+{
+ const_cast<MyProperty*>(this)->State = theState;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/ucb/source/ucp/file/filinpstr.cxx b/ucb/source/ucp/file/filinpstr.cxx
new file mode 100644
index 0000000000..5838dcda39
--- /dev/null
+++ b/ucb/source/ucp/file/filinpstr.cxx
@@ -0,0 +1,165 @@
+/* -*- 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:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include <sal/config.h>
+
+#include <com/sun/star/io/IOException.hpp>
+#include <com/sun/star/lang/IllegalArgumentException.hpp>
+
+#include "filinpstr.hxx"
+#include "filerror.hxx"
+
+using namespace fileaccess;
+using namespace com::sun::star;
+
+#if OSL_DEBUG_LEVEL > 0
+#define THROW_WHERE SAL_WHERE
+#else
+#define THROW_WHERE ""
+#endif
+
+XInputStream_impl::XInputStream_impl( const OUString& aUncPath, bool bLock )
+ : m_aFile( aUncPath ),
+ m_nErrorCode( TASKHANDLER_NO_ERROR ),
+ m_nMinorErrorCode( TASKHANDLER_NO_ERROR )
+{
+ sal_uInt32 nFlags = osl_File_OpenFlag_Read;
+ if ( !bLock )
+ nFlags |= osl_File_OpenFlag_NoLock;
+
+ osl::FileBase::RC err = m_aFile.open( nFlags );
+ if( err != osl::FileBase::E_None )
+ {
+ m_nIsOpen = false;
+ m_aFile.close();
+
+ m_nErrorCode = TASKHANDLING_OPEN_FOR_INPUTSTREAM;
+ m_nMinorErrorCode = err;
+ }
+ else
+ m_nIsOpen = true;
+}
+
+
+XInputStream_impl::~XInputStream_impl()
+{
+ try
+ {
+ closeInput();
+ }
+ catch (io::IOException const &)
+ {
+ OSL_FAIL("unexpected situation");
+ }
+ catch (uno::RuntimeException const &)
+ {
+ OSL_FAIL("unexpected situation");
+ }
+}
+
+sal_Int32 SAL_CALL
+XInputStream_impl::readBytes(
+ uno::Sequence< sal_Int8 >& aData,
+ sal_Int32 nBytesToRead )
+{
+ if( ! m_nIsOpen ) throw io::IOException( THROW_WHERE );
+
+ aData.realloc(nBytesToRead);
+ //TODO! translate memory exhaustion (if it were detectable...) into
+ // io::BufferSizeExceededException
+
+ sal_uInt64 nrc(0);
+ if(m_aFile.read( aData.getArray(),sal_uInt64(nBytesToRead),nrc )
+ != osl::FileBase::E_None)
+ throw io::IOException( THROW_WHERE );
+
+ // Shrink aData in case we read less than nBytesToRead (XInputStream
+ // documentation does not tell whether this is required, and I do not know
+ // if any code relies on this, so be conservative---SB):
+ if (sal::static_int_cast<sal_Int32>(nrc) != nBytesToRead)
+ aData.realloc(sal_Int32(nrc));
+ return static_cast<sal_Int32>(nrc);
+}
+
+sal_Int32 SAL_CALL
+XInputStream_impl::readSomeBytes(
+ uno::Sequence< sal_Int8 >& aData,
+ sal_Int32 nMaxBytesToRead )
+{
+ return readBytes( aData,nMaxBytesToRead );
+}
+
+
+void SAL_CALL
+XInputStream_impl::skipBytes( sal_Int32 nBytesToSkip )
+{
+ m_aFile.setPos( osl_Pos_Current, sal_uInt64( nBytesToSkip ) );
+}
+
+
+sal_Int32 SAL_CALL
+XInputStream_impl::available()
+{
+ sal_Int64 avail = getLength() - getPosition();
+ return std::min<sal_Int64>(avail, SAL_MAX_INT32);
+}
+
+
+void SAL_CALL
+XInputStream_impl::closeInput()
+{
+ if( m_nIsOpen )
+ {
+ osl::FileBase::RC err = m_aFile.close();
+ if( err != osl::FileBase::E_None )
+ throw io::IOException( THROW_WHERE );
+ m_nIsOpen = false;
+ }
+}
+
+
+void SAL_CALL
+XInputStream_impl::seek( sal_Int64 location )
+{
+ if( location < 0 )
+ throw lang::IllegalArgumentException( THROW_WHERE, uno::Reference< uno::XInterface >(), 0 );
+ if( osl::FileBase::E_None != m_aFile.setPos( osl_Pos_Absolut, sal_uInt64( location ) ) )
+ throw io::IOException( THROW_WHERE );
+}
+
+
+sal_Int64 SAL_CALL
+XInputStream_impl::getPosition()
+{
+ sal_uInt64 uPos;
+ if( osl::FileBase::E_None != m_aFile.getPos( uPos ) )
+ throw io::IOException( THROW_WHERE );
+ return sal_Int64( uPos );
+}
+
+sal_Int64 SAL_CALL
+XInputStream_impl::getLength()
+{
+ sal_uInt64 uEndPos;
+ if ( m_aFile.getSize(uEndPos) != osl::FileBase::E_None )
+ throw io::IOException( THROW_WHERE );
+ return sal_Int64( uEndPos );
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/ucb/source/ucp/file/filinpstr.hxx b/ucb/source/ucp/file/filinpstr.hxx
new file mode 100644
index 0000000000..971872fdbb
--- /dev/null
+++ b/ucb/source/ucp/file/filinpstr.hxx
@@ -0,0 +1,87 @@
+/* -*- 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:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+#pragma once
+
+#include <rtl/ustring.hxx>
+#include <cppuhelper/implbase.hxx>
+#include <com/sun/star/io/XSeekable.hpp>
+#include <com/sun/star/io/XInputStream.hpp>
+
+#include "filrec.hxx"
+
+namespace fileaccess {
+
+ class TaskManager;
+
+ class XInputStream_impl final
+ : public cppu::WeakImplHelper<css::io::XInputStream, css::io::XSeekable>
+ {
+ public:
+
+ XInputStream_impl( const OUString& aUncPath, bool bLock );
+
+ virtual ~XInputStream_impl() override;
+
+ /**
+ * Returns an error code as given by filerror.hxx
+ */
+
+ sal_Int32 CtorSuccess() const { return m_nErrorCode;}
+ sal_Int32 getMinorError() const { return m_nMinorErrorCode;}
+
+ 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;
+
+ virtual void SAL_CALL
+ seek( sal_Int64 location ) override;
+
+ virtual sal_Int64 SAL_CALL
+ getPosition() override;
+
+ virtual sal_Int64 SAL_CALL
+ getLength() override;
+
+ private:
+
+ bool m_nIsOpen;
+
+ ReconnectingFile m_aFile;
+
+ sal_Int32 m_nErrorCode;
+ sal_Int32 m_nMinorErrorCode;
+ };
+} // end namespace XInputStream_impl
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/ucb/source/ucp/file/filinsreq.cxx b/ucb/source/ucp/file/filinsreq.cxx
new file mode 100644
index 0000000000..0b8ebfb539
--- /dev/null
+++ b/ucb/source/ucp/file/filinsreq.cxx
@@ -0,0 +1,77 @@
+/* -*- 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:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include "filinsreq.hxx"
+#include "filtask.hxx"
+
+#include <comphelper/interaction.hxx>
+
+#include <com/sun/star/ucb/IOErrorCode.hpp>
+#include <com/sun/star/ucb/InteractiveAugmentedIOException.hpp>
+#include <com/sun/star/ucb/NameClashException.hpp>
+#include <com/sun/star/beans/PropertyValue.hpp>
+
+
+using namespace cppu;
+using namespace com::sun::star;
+using namespace com::sun::star::uno;
+using namespace com::sun::star::lang;
+using namespace com::sun::star::task;
+using namespace com::sun::star::ucb;
+using namespace com::sun::star::beans;
+using namespace fileaccess;
+
+
+XInteractionRequestImpl::XInteractionRequestImpl(
+ const OUString& aClashingName,
+ const Reference<XInterface>& xOrigin,
+ TaskManager *pShell,sal_Int32 CommandId)
+ : p1( new XInteractionSupplyNameImpl ),
+ p2( new XInteractionAbortImpl ),
+ m_xOrigin(xOrigin)
+{
+ sal_Int32 nErrorCode(0), nMinorError(0);
+ if( pShell )
+ pShell->retrieveError(CommandId,nErrorCode,nMinorError);
+ std::vector<uno::Reference<task::XInteractionContinuation>> continuations{
+ Reference<XInteractionContinuation>(p1),
+ Reference<XInteractionContinuation>(p2) };
+ Any aAny;
+ if(nErrorCode == TASKHANDLING_FOLDER_EXISTS_MKDIR)
+ {
+ NameClashException excep("folder exists and overwrite forbidden", m_xOrigin,
+ InteractionClassification_ERROR, aClashingName);
+ aAny <<= excep;
+ }
+ else if(nErrorCode == TASKHANDLING_INVALID_NAME_MKDIR)
+ {
+ PropertyValue prop;
+ prop.Name = "ResourceName";
+ prop.Handle = -1;
+ prop.Value <<= aClashingName;
+ InteractiveAugmentedIOException excep("the name contained invalid characters", m_xOrigin,
+ InteractionClassification_ERROR,
+ IOErrorCode_INVALID_CHARACTER, { Any(prop) });
+ aAny <<= excep;
+
+ }
+ m_xRequest.set(new ::comphelper::OInteractionRequest(aAny, std::move(continuations)));
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/ucb/source/ucp/file/filinsreq.hxx b/ucb/source/ucp/file/filinsreq.hxx
new file mode 100644
index 0000000000..7f2e59a4c9
--- /dev/null
+++ b/ucb/source/ucp/file/filinsreq.hxx
@@ -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/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+
+#pragma once
+
+#include <rtl/ustring.hxx>
+#include <com/sun/star/uno/XInterface.hpp>
+#include <com/sun/star/task/XInteractionAbort.hpp>
+#include <com/sun/star/ucb/XInteractionSupplyName.hpp>
+#include <com/sun/star/task/XInteractionRequest.hpp>
+#include <cppuhelper/implbase.hxx>
+
+
+namespace fileaccess {
+
+
+ class TaskManager;
+
+
+class XInteractionSupplyNameImpl : public cppu::WeakImplHelper<
+ css::ucb::XInteractionSupplyName >
+ {
+ public:
+
+ XInteractionSupplyNameImpl()
+ : m_bSelected(false)
+ {
+ }
+
+ virtual void SAL_CALL select() override
+ {
+ m_bSelected = true;
+ }
+
+ void SAL_CALL setName(const OUString& Name) override
+ {
+ m_aNewName = Name;
+ }
+
+ const OUString& getName() const
+ {
+ return m_aNewName;
+ }
+
+ bool isSelected() const
+ {
+ return m_bSelected;
+ }
+
+ private:
+
+ bool m_bSelected;
+ OUString m_aNewName;
+ };
+
+
+ class XInteractionAbortImpl : public cppu::WeakImplHelper<
+ css::task::XInteractionAbort >
+ {
+ public:
+
+ XInteractionAbortImpl()
+ : m_bSelected(false)
+ {
+ }
+
+ virtual void SAL_CALL select() override
+ {
+ m_bSelected = true;
+ }
+
+
+ bool isSelected() const
+ {
+ return m_bSelected;
+ }
+
+ private:
+
+ bool m_bSelected;
+ };
+
+
+ class XInteractionRequestImpl
+ {
+ public:
+
+ XInteractionRequestImpl(
+ const OUString& aClashingName,
+ const css::uno::Reference< css::uno::XInterface>& xOrigin,
+ TaskManager* pShell,
+ sal_Int32 CommandId);
+
+ bool aborted() const
+ {
+ return p2->isSelected();
+ }
+
+ OUString newName() const
+ {
+ if( p1->isSelected() )
+ return p1->getName();
+ else
+ return OUString();
+ }
+
+ css::uno::Reference<css::task::XInteractionRequest> const& getRequest() const
+ {
+ return m_xRequest;
+ }
+
+ private:
+
+ XInteractionSupplyNameImpl* p1;
+ XInteractionAbortImpl* p2;
+
+ css::uno::Reference<css::task::XInteractionRequest> m_xRequest;
+
+ css::uno::Reference< css::uno::XInterface> m_xOrigin;
+ };
+
+}
+
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/ucb/source/ucp/file/filnot.cxx b/ucb/source/ucp/file/filnot.cxx
new file mode 100644
index 0000000000..d384dc0ea7
--- /dev/null
+++ b/ucb/source/ucp/file/filnot.cxx
@@ -0,0 +1,208 @@
+/* -*- 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:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include <com/sun/star/ucb/ContentAction.hpp>
+#include <com/sun/star/beans/PropertySetInfoChange.hpp>
+#include <rtl/ref.hxx>
+#include "filnot.hxx"
+#include "filid.hxx"
+#include "bc.hxx"
+#include "prov.hxx"
+
+
+using namespace fileaccess;
+using namespace com::sun::star;
+using namespace com::sun::star::ucb;
+
+
+ContentEventNotifier::ContentEventNotifier( TaskManager* pMyShell,
+ const uno::Reference< XContent >& xCreatorContent,
+ const uno::Reference< XContentIdentifier >& xCreatorId,
+ std::vector< uno::Reference< ucb::XContentEventListener > >&& sListeners )
+ : m_pMyShell( pMyShell ),
+ m_xCreatorContent( xCreatorContent ),
+ m_xCreatorId( xCreatorId ),
+ m_sListeners( std::move(sListeners) )
+{
+}
+
+
+ContentEventNotifier::ContentEventNotifier( TaskManager* pMyShell,
+ const uno::Reference< XContent >& xCreatorContent,
+ const uno::Reference< XContentIdentifier >& xCreatorId,
+ const uno::Reference< XContentIdentifier >& xOldId,
+ std::vector< uno::Reference< ucb::XContentEventListener > >&& sListeners )
+ : m_pMyShell( pMyShell ),
+ m_xCreatorContent( xCreatorContent ),
+ m_xCreatorId( xCreatorId ),
+ m_xOldId( xOldId ),
+ m_sListeners( std::move(sListeners) )
+{
+}
+
+
+void ContentEventNotifier::notifyChildInserted( const OUString& aChildName ) const
+{
+ rtl::Reference<FileContentIdentifier> xChildId = new FileContentIdentifier( aChildName );
+
+ uno::Reference< XContent > xChildContent = m_pMyShell->m_pProvider->queryContent( xChildId );
+
+ ContentEvent aEvt( m_xCreatorContent,
+ ContentAction::INSERTED,
+ xChildContent,
+ m_xCreatorId );
+
+ for( const auto& ref : m_sListeners )
+ ref->contentEvent( aEvt );
+}
+
+void ContentEventNotifier::notifyDeleted() const
+{
+ ContentEvent aEvt( m_xCreatorContent,
+ ContentAction::DELETED,
+ m_xCreatorContent,
+ m_xCreatorId );
+
+
+ for( const auto& ref : m_sListeners )
+ ref->contentEvent( aEvt );
+}
+
+
+void ContentEventNotifier::notifyRemoved( const OUString& aChildName ) const
+{
+ rtl::Reference<FileContentIdentifier> xChildId = new FileContentIdentifier( aChildName );
+
+ rtl::Reference<BaseContent> pp = new BaseContent( m_pMyShell,xChildId,aChildName );
+ {
+ std::unique_lock aGuard( pp->m_aMutex );
+ pp->m_nState |= BaseContent::Deleted;
+ }
+
+ ContentEvent aEvt( m_xCreatorContent,
+ ContentAction::REMOVED,
+ pp,
+ m_xCreatorId );
+
+ for( const auto& ref : m_sListeners )
+ ref->contentEvent( aEvt );
+}
+
+void ContentEventNotifier::notifyExchanged() const
+{
+ ContentEvent aEvt( m_xCreatorContent,
+ ContentAction::EXCHANGED,
+ m_xCreatorContent,
+ m_xOldId );
+
+ for( const auto& ref : m_sListeners )
+ ref->contentEvent( aEvt );
+}
+
+/*********************************************************************************/
+/* */
+/* PropertySetInfoChangeNotifier */
+/* */
+/*********************************************************************************/
+
+
+PropertySetInfoChangeNotifier::PropertySetInfoChangeNotifier(
+ const uno::Reference< XContent >& xCreatorContent,
+ std::vector< uno::Reference< beans::XPropertySetInfoChangeListener > >&& sListeners )
+ : m_xCreatorContent( xCreatorContent ),
+ m_sListeners( std::move(sListeners) )
+{
+
+}
+
+
+void
+PropertySetInfoChangeNotifier::notifyPropertyAdded( const OUString & aPropertyName ) const
+{
+ beans::PropertySetInfoChangeEvent aEvt( m_xCreatorContent,
+ aPropertyName,
+ -1,
+ beans::PropertySetInfoChange::PROPERTY_INSERTED );
+
+ for( const auto& ref : m_sListeners )
+ ref->propertySetInfoChange( aEvt );
+}
+
+
+void
+PropertySetInfoChangeNotifier::notifyPropertyRemoved( const OUString & aPropertyName ) const
+{
+ beans::PropertySetInfoChangeEvent aEvt( m_xCreatorContent,
+ aPropertyName,
+ -1,
+ beans::PropertySetInfoChange::PROPERTY_REMOVED );
+
+ for( const auto& ref : m_sListeners )
+ ref->propertySetInfoChange( aEvt );
+}
+
+
+/*********************************************************************************/
+/* */
+/* PropertySetInfoChangeNotifier */
+/* */
+/*********************************************************************************/
+
+
+PropertyChangeNotifier::PropertyChangeNotifier(
+ const css::uno::Reference< XContent >& xCreatorContent,
+ ListenerMap&& pListeners )
+ : m_xCreatorContent( xCreatorContent ),
+ m_aListeners( std::move(pListeners) )
+{
+}
+
+
+void PropertyChangeNotifier::notifyPropertyChanged(
+ const uno::Sequence< beans::PropertyChangeEvent >& seqChanged ) const
+{
+ uno::Sequence< beans::PropertyChangeEvent > Changes = seqChanged;
+
+ for( auto& rChange : asNonConstRange(Changes) )
+ rChange.Source = m_xCreatorContent;
+
+ // notify listeners for all Events
+
+ auto it = m_aListeners.find( OUString() );
+ if (it != m_aListeners.end())
+ {
+ const std::vector< uno::Reference< beans::XPropertiesChangeListener > >& seqList = it->second;
+ for( const auto& rListener : seqList )
+ rListener->propertiesChange( Changes );
+ }
+
+ for( const auto& rChange : std::as_const(Changes) )
+ {
+ uno::Sequence< beans::PropertyChangeEvent > seq{ rChange };
+ it = m_aListeners.find( rChange.PropertyName );
+ if (it != m_aListeners.end())
+ {
+ const std::vector< uno::Reference< beans::XPropertiesChangeListener > >& seqList = it->second;
+ for( const auto& rListener : seqList )
+ rListener->propertiesChange( seq );
+ }
+ }
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/ucb/source/ucp/file/filnot.hxx b/ucb/source/ucp/file/filnot.hxx
new file mode 100644
index 0000000000..898a57dd03
--- /dev/null
+++ b/ucb/source/ucp/file/filnot.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/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+#pragma once
+
+#include <com/sun/star/uno/Sequence.hxx>
+#include <com/sun/star/beans/PropertyChangeEvent.hpp>
+#include <com/sun/star/beans/XPropertySetInfoChangeListener.hpp>
+#include <com/sun/star/beans/XPropertiesChangeListener.hpp>
+#include <com/sun/star/ucb/XContentIdentifier.hpp>
+#include <com/sun/star/ucb/XContent.hpp>
+#include <optional>
+#include <unordered_map>
+#include <vector>
+
+namespace fileaccess {
+
+ class TaskManager;
+
+ class ContentEventNotifier
+ {
+ private:
+ TaskManager* m_pMyShell;
+ css::uno::Reference< css::ucb::XContent > m_xCreatorContent;
+ css::uno::Reference< css::ucb::XContentIdentifier > m_xCreatorId;
+ css::uno::Reference< css::ucb::XContentIdentifier > m_xOldId;
+ std::vector< css::uno::Reference< css::ucb::XContentEventListener > > m_sListeners;
+ public:
+
+ ContentEventNotifier(
+ TaskManager* pMyShell,
+ const css::uno::Reference< css::ucb::XContent >& xCreatorContent,
+ const css::uno::Reference< css::ucb::XContentIdentifier >& xCreatorId,
+ std::vector< css::uno::Reference< css::ucb::XContentEventListener > >&& sListeners );
+
+ ContentEventNotifier(
+ TaskManager* pMyShell,
+ const css::uno::Reference< css::ucb::XContent >& xCreatorContent,
+ const css::uno::Reference< css::ucb::XContentIdentifier >& xCreatorId,
+ const css::uno::Reference< css::ucb::XContentIdentifier >& xOldId,
+ std::vector< css::uno::Reference< css::ucb::XContentEventListener > >&& sListeners );
+
+ void notifyChildInserted( const OUString& aChildName ) const;
+ void notifyDeleted() const;
+ void notifyRemoved( const OUString& aChildName ) const;
+ void notifyExchanged() const;
+ };
+
+
+ class PropertySetInfoChangeNotifier
+ {
+ private:
+ css::uno::Reference< css::ucb::XContent > m_xCreatorContent;
+ std::vector< css::uno::Reference< css::beans::XPropertySetInfoChangeListener > > m_sListeners;
+ public:
+ PropertySetInfoChangeNotifier(
+ const css::uno::Reference< css::ucb::XContent >& xCreatorContent,
+ std::vector< css::uno::Reference< css::beans::XPropertySetInfoChangeListener > >&& sListeners );
+
+ void notifyPropertyAdded( const OUString & aPropertyName ) const;
+ void notifyPropertyRemoved( const OUString & aPropertyName ) const;
+ };
+
+
+ typedef std::unordered_map< OUString,
+ std::vector< css::uno::Reference< css::beans::XPropertiesChangeListener > > > ListenerMap;
+
+ class PropertyChangeNotifier
+ {
+ private:
+ css::uno::Reference< css::ucb::XContent > m_xCreatorContent;
+ ListenerMap m_aListeners;
+ public:
+ PropertyChangeNotifier(
+ const css::uno::Reference< css::ucb::XContent >& xCreatorContent,
+ ListenerMap&& pListeners );
+
+ void notifyPropertyChanged(
+ const css::uno::Sequence< css::beans::PropertyChangeEvent >& seqChanged ) const;
+ };
+
+
+ class Notifier
+ {
+ public:
+ // Side effect of this function is the change of the name
+ virtual std::optional<ContentEventNotifier> cEXC( const OUString& aNewName ) = 0;
+ // Side effect is the change of the state of the object to "deleted".
+ virtual std::optional<ContentEventNotifier> cDEL() = 0;
+ virtual std::optional<ContentEventNotifier> cCEL() = 0;
+ virtual std::optional<PropertySetInfoChangeNotifier> cPSL() = 0;
+ virtual std::optional<PropertyChangeNotifier> cPCL() = 0;
+
+ protected:
+ ~Notifier() {}
+ };
+
+
+} // end namespace fileaccess
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/ucb/source/ucp/file/filprp.cxx b/ucb/source/ucp/file/filprp.cxx
new file mode 100644
index 0000000000..ed352a9e9a
--- /dev/null
+++ b/ucb/source/ucp/file/filprp.cxx
@@ -0,0 +1,94 @@
+/* -*- 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:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include "filtask.hxx"
+#include "prov.hxx"
+#include "filprp.hxx"
+#include "filinl.hxx"
+
+using namespace fileaccess;
+using namespace com::sun::star;
+using namespace com::sun::star::uno;
+using namespace com::sun::star::ucb;
+
+XPropertySetInfo_impl::XPropertySetInfo_impl( TaskManager* pMyShell,const OUString& aUnqPath )
+ : m_pMyShell( pMyShell ),
+ m_seq( 0 )
+{
+ m_pMyShell->m_pProvider->acquire();
+
+ TaskManager::ContentMap::iterator it = m_pMyShell->m_aContent.find( aUnqPath );
+
+ TaskManager::PropertySet& properties = it->second.properties;
+
+ m_seq.realloc( properties.size() );
+ auto p_seq = m_seq.getArray();
+
+ sal_Int32 count = 0;
+ for( const auto& rProp : properties )
+ {
+ p_seq[ count++ ] = beans::Property( rProp.getPropertyName(),
+ rProp.getHandle(),
+ rProp.getType(),
+ rProp.getAttributes() );
+ }
+}
+
+
+XPropertySetInfo_impl::XPropertySetInfo_impl( TaskManager* pMyShell,const Sequence< beans::Property >& seq )
+ : m_pMyShell( pMyShell ),
+ m_seq( seq )
+{
+ m_pMyShell->m_pProvider->acquire();
+}
+
+
+XPropertySetInfo_impl::~XPropertySetInfo_impl()
+{
+ m_pMyShell->m_pProvider->release();
+}
+
+
+beans::Property SAL_CALL
+XPropertySetInfo_impl::getPropertyByName( const OUString& aName )
+{
+ auto pProp = std::find_if(std::cbegin(m_seq), std::cend(m_seq),
+ [&aName](const beans::Property& rProp) { return rProp.Name == aName; });
+ if (pProp != std::cend(m_seq))
+ return *pProp;
+
+ throw beans::UnknownPropertyException( aName );
+}
+
+
+Sequence< beans::Property > SAL_CALL
+XPropertySetInfo_impl::getProperties()
+{
+ return m_seq;
+}
+
+
+sal_Bool SAL_CALL
+XPropertySetInfo_impl::hasPropertyByName( const OUString& aName )
+{
+ return std::any_of(std::cbegin(m_seq), std::cend(m_seq),
+ [&aName](const beans::Property& rProp) { return rProp.Name == aName; });
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/ucb/source/ucp/file/filprp.hxx b/ucb/source/ucp/file/filprp.hxx
new file mode 100644
index 0000000000..4fef247d29
--- /dev/null
+++ b/ucb/source/ucp/file/filprp.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/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+#pragma once
+
+#include <com/sun/star/beans/XPropertySetInfo.hpp>
+#include <cppuhelper/implbase.hxx>
+
+
+namespace fileaccess {
+
+ class TaskManager;
+
+class XPropertySetInfo_impl : public cppu::WeakImplHelper<
+ css::beans::XPropertySetInfo >
+ {
+ public:
+ XPropertySetInfo_impl( TaskManager* pMyShell,const OUString& aUnqPath );
+ XPropertySetInfo_impl( TaskManager* pMyShell,const css::uno::Sequence< css::beans::Property >& seq );
+
+ virtual ~XPropertySetInfo_impl() override;
+
+ virtual css::uno::Sequence< css::beans::Property > SAL_CALL
+ getProperties() override;
+
+ virtual css::beans::Property SAL_CALL
+ getPropertyByName( const OUString& aName ) override;
+
+ virtual sal_Bool SAL_CALL
+ hasPropertyByName( const OUString& Name ) override;
+
+ private:
+ TaskManager* m_pMyShell;
+ css::uno::Sequence< css::beans::Property > m_seq;
+ };
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/ucb/source/ucp/file/filrec.cxx b/ucb/source/ucp/file/filrec.cxx
new file mode 100644
index 0000000000..5af520bc35
--- /dev/null
+++ b/ucb/source/ucp/file/filrec.cxx
@@ -0,0 +1,192 @@
+/* -*- 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:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+
+#include "filrec.hxx"
+
+namespace fileaccess {
+
+void ReconnectingFile::disconnect()
+{
+ m_aFile.close();
+ m_bDisconnect = true;
+}
+
+bool ReconnectingFile::reconnect()
+{
+ bool bResult = false;
+ if ( m_bFlagsSet )
+ {
+ disconnect();
+ if ( m_aFile.open( m_nFlags ) == ::osl::FileBase::E_None
+ || m_aFile.open( osl_File_OpenFlag_Read ) == ::osl::FileBase::E_None )
+ {
+ m_bDisconnect = false;
+ bResult = true;
+ }
+ }
+
+ return bResult;
+}
+
+::osl::FileBase::RC ReconnectingFile::open( sal_uInt32 uFlags )
+{
+ ::osl::FileBase::RC nResult = m_aFile.open( uFlags );
+ if ( nResult == ::osl::FileBase::E_None )
+ {
+ if ( uFlags & osl_File_OpenFlag_Create )
+ m_nFlags = (uFlags & ( ~osl_File_OpenFlag_Create )) | osl_File_OpenFlag_Write;
+ else
+ m_nFlags = uFlags;
+
+ m_bFlagsSet = true;
+ }
+
+ return nResult;
+}
+
+::osl::FileBase::RC ReconnectingFile::close()
+{
+ m_nFlags = 0;
+ m_bFlagsSet = false;
+ m_bDisconnect = false;
+
+ return m_aFile.close();
+}
+
+::osl::FileBase::RC ReconnectingFile::setPos( sal_uInt32 uHow, sal_Int64 uPos )
+{
+ ::osl::FileBase::RC nRes = ::osl::FileBase::E_NETWORK;
+
+ if ( uHow == osl_Pos_Absolut && uPos > 0 )
+ {
+ if ( m_bDisconnect )
+ {
+ if ( reconnect() )
+ nRes = m_aFile.setPos( uHow, uPos );
+ }
+ else
+ {
+ // E_INVAL error code means in this case that
+ // the file handler is invalid
+ nRes = m_aFile.setPos( uHow, uPos );
+ if ( ( nRes == ::osl::FileBase::E_NETWORK
+ || nRes == ::osl::FileBase::E_INVAL )
+ && reconnect() )
+ nRes = m_aFile.setPos( uHow, uPos );
+ }
+ }
+ else
+ {
+ if ( !m_bDisconnect )
+ nRes = m_aFile.setPos( uHow, uPos );
+ }
+
+ return nRes;
+}
+
+::osl::FileBase::RC ReconnectingFile::getPos( sal_uInt64& uPos )
+{
+ if ( m_bDisconnect )
+ return ::osl::FileBase::E_NETWORK;
+
+ return m_aFile.getPos( uPos );
+}
+
+::osl::FileBase::RC ReconnectingFile::setSize( sal_uInt64 uSize )
+{
+ ::osl::FileBase::RC nRes = ::osl::FileBase::E_NETWORK;
+
+ if ( uSize == 0 )
+ {
+ if ( m_bDisconnect )
+ {
+ if ( reconnect() )
+ nRes = m_aFile.setSize( uSize );
+ }
+ else
+ {
+ // E_INVAL error code means in this case that
+ // the file handler is invalid
+ nRes = m_aFile.setSize( uSize );
+ if ( ( nRes == ::osl::FileBase::E_NETWORK
+ || nRes == ::osl::FileBase::E_INVAL )
+ && reconnect() )
+ nRes = m_aFile.setSize( uSize );
+ }
+ }
+ else
+ {
+ if ( !m_bDisconnect )
+ nRes = m_aFile.setSize( uSize );
+ }
+
+ return nRes;
+}
+
+::osl::FileBase::RC ReconnectingFile::getSize( sal_uInt64 &rSize )
+{
+ ::osl::FileBase::RC nRes = ::osl::FileBase::E_NETWORK;
+
+ if ( !m_bDisconnect )
+ nRes = m_aFile.getSize( rSize );
+
+ // E_INVAL error code means in this case that
+ // the file handler is invalid
+ if ( ( nRes == ::osl::FileBase::E_NETWORK
+ || nRes == ::osl::FileBase::E_INVAL )
+ && reconnect() )
+ {
+ nRes = m_aFile.getSize( rSize );
+
+ // the repairing should be disconnected, since the position might be wrong
+ // but the result should be retrieved
+ disconnect();
+ }
+
+ return nRes;
+}
+
+::osl::FileBase::RC ReconnectingFile::read( void *pBuffer, sal_uInt64 uBytesRequested, sal_uInt64& rBytesRead )
+{
+ if ( m_bDisconnect )
+ return ::osl::FileBase::E_NETWORK;
+
+ return m_aFile.read( pBuffer, uBytesRequested, rBytesRead );
+}
+
+::osl::FileBase::RC ReconnectingFile::write(const void *pBuffer, sal_uInt64 uBytesToWrite, sal_uInt64& rBytesWritten)
+{
+ if ( m_bDisconnect )
+ return ::osl::FileBase::E_NETWORK;
+
+ return m_aFile.write( pBuffer, uBytesToWrite, rBytesWritten );
+}
+
+::osl::FileBase::RC ReconnectingFile::sync() const
+{
+ if ( m_bDisconnect )
+ return ::osl::FileBase::E_NETWORK;
+
+ return m_aFile.sync();
+}
+
+} // namespace fileaccess
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/ucb/source/ucp/file/filrec.hxx b/ucb/source/ucp/file/filrec.hxx
new file mode 100644
index 0000000000..552aedc7db
--- /dev/null
+++ b/ucb/source/ucp/file/filrec.hxx
@@ -0,0 +1,76 @@
+/* -*- 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:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#pragma once
+
+#include <osl/file.hxx>
+
+namespace fileaccess {
+
+class ReconnectingFile
+{
+ ::osl::File m_aFile;
+
+ sal_uInt32 m_nFlags;
+ bool m_bFlagsSet;
+
+ bool m_bDisconnect;
+
+ ReconnectingFile( ReconnectingFile const & ) = delete;
+ ReconnectingFile& operator=( ReconnectingFile const & ) = delete;
+
+public:
+
+ explicit ReconnectingFile( const OUString& aFileURL )
+ : m_aFile( aFileURL )
+ , m_nFlags( 0 )
+ , m_bFlagsSet( false )
+ , m_bDisconnect( false )
+ {}
+
+ ~ReconnectingFile()
+ {
+ close();
+ }
+
+ void disconnect();
+ bool reconnect();
+
+ ::osl::FileBase::RC open( sal_uInt32 uFlags );
+
+ ::osl::FileBase::RC close();
+
+ ::osl::FileBase::RC setPos( sal_uInt32 uHow, sal_Int64 uPos );
+
+ ::osl::FileBase::RC getPos( sal_uInt64& uPos );
+
+ ::osl::FileBase::RC setSize( sal_uInt64 uSize );
+
+ ::osl::FileBase::RC getSize( sal_uInt64 &rSize );
+
+ ::osl::FileBase::RC read( void *pBuffer, sal_uInt64 uBytesRequested, sal_uInt64& rBytesRead );
+
+ ::osl::FileBase::RC write(const void *pBuffer, sal_uInt64 uBytesToWrite, sal_uInt64& rBytesWritten);
+
+ ::osl::FileBase::RC sync() const;
+};
+
+} // namespace fileaccess
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/ucb/source/ucp/file/filrow.cxx b/ucb/source/ucp/file/filrow.cxx
new file mode 100644
index 0000000000..e352e79199
--- /dev/null
+++ b/ucb/source/ucp/file/filrow.cxx
@@ -0,0 +1,293 @@
+/* -*- 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:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include "filrow.hxx"
+#include "filtask.hxx"
+#include <com/sun/star/script/CannotConvertException.hpp>
+#include <com/sun/star/script/Converter.hpp>
+#include <com/sun/star/sdbc/SQLException.hpp>
+
+using namespace fileaccess;
+using namespace com::sun::star;
+using namespace css::uno;
+
+#if OSL_DEBUG_LEVEL > 0
+#define THROW_WHERE SAL_WHERE
+#else
+#define THROW_WHERE ""
+#endif
+
+// Function for TypeConverting
+
+template< class _type_ >
+static bool convert( TaskManager const * pShell,
+ uno::Reference< script::XTypeConverter >& xConverter,
+ const uno::Any& rValue,
+ _type_& aReturn )
+{
+ // Try first without converting
+ bool no_success = ! ( rValue >>= aReturn );
+
+ if ( no_success )
+ {
+ if( ! xConverter.is() )
+ {
+ xConverter = script::Converter::create(pShell->m_xContext);
+ }
+
+ try
+ {
+ if( rValue.hasValue() )
+ {
+ uno::Any aConvertedValue
+ = xConverter->convertTo( rValue,cppu::UnoType<_type_>::get() );
+ no_success = ! ( aConvertedValue >>= aReturn );
+ }
+ else
+ no_success = true;
+ }
+ catch (const lang::IllegalArgumentException&)
+ {
+ no_success = true;
+ }
+ catch (const script::CannotConvertException&)
+ {
+ no_success = true;
+ }
+ }
+ return no_success;
+}
+
+
+XRow_impl::XRow_impl( TaskManager* pMyShell,const uno::Sequence< uno::Any >& seq )
+ : m_aValueMap( seq ),
+ m_nWasNull(false),
+ m_pMyShell( pMyShell )
+{
+}
+
+XRow_impl::~XRow_impl()
+{
+}
+
+
+sal_Bool SAL_CALL
+XRow_impl::wasNull()
+{
+ return m_nWasNull;
+}
+
+
+OUString SAL_CALL
+XRow_impl::getString(
+ sal_Int32 columnIndex )
+{
+ if( isIndexOutOfBounds( columnIndex ) )
+ throw sdbc::SQLException( THROW_WHERE, uno::Reference< uno::XInterface >(), OUString(), 0, uno::Any() );
+ return getValue<OUString>(columnIndex);
+}
+
+sal_Bool SAL_CALL
+XRow_impl::getBoolean(
+ sal_Int32 columnIndex )
+{
+ if( isIndexOutOfBounds( columnIndex ) )
+ throw sdbc::SQLException( THROW_WHERE, uno::Reference< uno::XInterface >(), OUString(), 0, uno::Any() );
+ return getValue<bool>(columnIndex);
+}
+
+
+sal_Int8 SAL_CALL
+XRow_impl::getByte(
+ sal_Int32 columnIndex )
+{
+ if( isIndexOutOfBounds( columnIndex ) )
+ throw sdbc::SQLException( THROW_WHERE, uno::Reference< uno::XInterface >(), OUString(), 0, uno::Any() );
+ return getValue<sal_Int8>(columnIndex);
+}
+
+sal_Int16 SAL_CALL
+XRow_impl::getShort(
+ sal_Int32 columnIndex )
+{
+ if( isIndexOutOfBounds( columnIndex ) )
+ throw sdbc::SQLException( THROW_WHERE, uno::Reference< uno::XInterface >(), OUString(), 0, uno::Any() );
+ return getValue<sal_Int16>(columnIndex);
+}
+
+
+sal_Int32 SAL_CALL
+XRow_impl::getInt(
+ sal_Int32 columnIndex )
+{
+ if( isIndexOutOfBounds( columnIndex ) )
+ throw sdbc::SQLException( THROW_WHERE, uno::Reference< uno::XInterface >(), OUString(), 0, uno::Any() );
+ return getValue<sal_Int32>(columnIndex);
+}
+
+sal_Int64 SAL_CALL
+XRow_impl::getLong(
+ sal_Int32 columnIndex )
+{
+ if( isIndexOutOfBounds( columnIndex ) )
+ throw sdbc::SQLException( THROW_WHERE, uno::Reference< uno::XInterface >(), OUString(), 0, uno::Any() );
+ return getValue<sal_Int64>(columnIndex);
+}
+
+float SAL_CALL
+XRow_impl::getFloat(
+ sal_Int32 columnIndex )
+{
+ if( isIndexOutOfBounds( columnIndex ) )
+ throw sdbc::SQLException( THROW_WHERE, uno::Reference< uno::XInterface >(), OUString(), 0, uno::Any() );
+ return getValue<float>(columnIndex);
+}
+
+double SAL_CALL
+XRow_impl::getDouble(
+ sal_Int32 columnIndex )
+{
+ if( isIndexOutOfBounds( columnIndex ) )
+ throw sdbc::SQLException( THROW_WHERE, uno::Reference< uno::XInterface >(), OUString(), 0, uno::Any() );
+ return getValue<double>(columnIndex);
+}
+
+uno::Sequence< sal_Int8 > SAL_CALL
+XRow_impl::getBytes(
+ sal_Int32 columnIndex )
+{
+ if( isIndexOutOfBounds( columnIndex ) )
+ throw sdbc::SQLException( THROW_WHERE, uno::Reference< uno::XInterface >(), OUString(), 0, uno::Any() );
+ return getValue<uno::Sequence< sal_Int8 >>(columnIndex);
+}
+
+util::Date SAL_CALL
+XRow_impl::getDate(
+ sal_Int32 columnIndex )
+{
+ if( isIndexOutOfBounds( columnIndex ) )
+ throw sdbc::SQLException( THROW_WHERE, uno::Reference< uno::XInterface >(), OUString(), 0, uno::Any() );
+ return getValue<util::Date>(columnIndex);
+}
+
+util::Time SAL_CALL
+XRow_impl::getTime(
+ sal_Int32 columnIndex )
+{
+ if( isIndexOutOfBounds( columnIndex ) )
+ throw sdbc::SQLException( THROW_WHERE, uno::Reference< uno::XInterface >(), OUString(), 0, uno::Any() );
+ return getValue<util::Time>(columnIndex);
+}
+
+util::DateTime SAL_CALL
+XRow_impl::getTimestamp(
+ sal_Int32 columnIndex )
+{
+ if( isIndexOutOfBounds( columnIndex ) )
+ throw sdbc::SQLException( THROW_WHERE, uno::Reference< uno::XInterface >(), OUString(), 0, uno::Any() );
+ return getValue<util::DateTime>(columnIndex);
+}
+
+
+uno::Reference< io::XInputStream > SAL_CALL
+XRow_impl::getBinaryStream(
+ sal_Int32 columnIndex )
+{
+ if( isIndexOutOfBounds( columnIndex ) )
+ throw sdbc::SQLException( THROW_WHERE, uno::Reference< uno::XInterface >(), OUString(), 0, uno::Any() );
+ return getValue<uno::Reference< io::XInputStream >>(columnIndex);
+}
+
+
+uno::Reference< io::XInputStream > SAL_CALL
+XRow_impl::getCharacterStream(
+ sal_Int32 columnIndex )
+{
+ if( isIndexOutOfBounds( columnIndex ) )
+ throw sdbc::SQLException( THROW_WHERE, uno::Reference< uno::XInterface >(), OUString(), 0, uno::Any() );
+ return getValue<uno::Reference< io::XInputStream >>(columnIndex);
+}
+
+
+uno::Any SAL_CALL
+XRow_impl::getObject(
+ sal_Int32 columnIndex,
+ const uno::Reference< container::XNameAccess >& )
+{
+ if( isIndexOutOfBounds( columnIndex ) )
+ throw sdbc::SQLException( THROW_WHERE, uno::Reference< uno::XInterface >(), OUString(), 0, uno::Any() );
+ std::scoped_lock aGuard( m_aMutex );
+ uno::Any Value = m_aValueMap[columnIndex - 1];
+ m_nWasNull = !Value.hasValue();
+ return Value;
+}
+
+uno::Reference< sdbc::XRef > SAL_CALL
+XRow_impl::getRef(
+ sal_Int32 columnIndex )
+{
+ if( isIndexOutOfBounds( columnIndex ) )
+ throw sdbc::SQLException( THROW_WHERE, uno::Reference< uno::XInterface >(), OUString(), 0, uno::Any() );
+ return getValue<uno::Reference< sdbc::XRef >>(columnIndex);
+}
+
+uno::Reference< sdbc::XBlob > SAL_CALL
+XRow_impl::getBlob(
+ sal_Int32 columnIndex )
+{
+ if( isIndexOutOfBounds( columnIndex ) )
+ throw sdbc::SQLException( THROW_WHERE, uno::Reference< uno::XInterface >(), OUString(), 0, uno::Any() );
+ return getValue<uno::Reference< sdbc::XBlob >>(columnIndex);
+}
+
+uno::Reference< sdbc::XClob > SAL_CALL
+XRow_impl::getClob(
+ sal_Int32 columnIndex )
+{
+ if( isIndexOutOfBounds( columnIndex ) )
+ throw sdbc::SQLException( THROW_WHERE, uno::Reference< uno::XInterface >(), OUString(), 0, uno::Any() );
+ return getValue<uno::Reference< sdbc::XClob >>(columnIndex);
+}
+
+
+uno::Reference< sdbc::XArray > SAL_CALL
+XRow_impl::getArray(
+ sal_Int32 columnIndex )
+{
+ if( isIndexOutOfBounds( columnIndex ) )
+ throw sdbc::SQLException( THROW_WHERE, uno::Reference< uno::XInterface >(), OUString(), 0, uno::Any() );
+ return getValue<uno::Reference< sdbc::XArray >>(columnIndex);
+}
+
+bool
+XRow_impl::isIndexOutOfBounds(sal_Int32 nIndex) const
+{
+ return nIndex < 1 || m_aValueMap.getLength() < nIndex;
+}
+
+template<typename T>
+T XRow_impl::getValue(sal_Int32 columnIndex)
+{
+ T aValue{};
+ std::scoped_lock aGuard( m_aMutex );
+ m_nWasNull = ::convert<T>( m_pMyShell, m_xTypeConverter, m_aValueMap[ --columnIndex ], aValue );
+ return aValue;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/ucb/source/ucp/file/filrow.hxx b/ucb/source/ucp/file/filrow.hxx
new file mode 100644
index 0000000000..1a33565a6a
--- /dev/null
+++ b/ucb/source/ucp/file/filrow.hxx
@@ -0,0 +1,114 @@
+/* -*- 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:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+#pragma once
+
+#include <com/sun/star/sdbc/XRow.hpp>
+#include <com/sun/star/script/XTypeConverter.hpp>
+#include <cppuhelper/implbase.hxx>
+#include <mutex>
+
+namespace fileaccess {
+
+ class TaskManager;
+
+ class XRow_impl: public cppu::WeakImplHelper<
+ css::sdbc::XRow >
+ {
+ public:
+ XRow_impl( TaskManager* pShell,const css::uno::Sequence< css::uno::Any >& aValueMap );
+ virtual ~XRow_impl() override;
+
+ virtual sal_Bool SAL_CALL
+ wasNull() override;
+
+ virtual OUString SAL_CALL
+ getString( sal_Int32 columnIndex ) override;
+
+ virtual sal_Bool SAL_CALL
+ getBoolean( sal_Int32 columnIndex ) override;
+
+ virtual sal_Int8 SAL_CALL
+ getByte( sal_Int32 columnIndex ) override;
+
+ virtual sal_Int16 SAL_CALL
+ getShort( sal_Int32 columnIndex ) override;
+
+ virtual sal_Int32 SAL_CALL
+ getInt( sal_Int32 columnIndex ) override;
+
+ virtual sal_Int64 SAL_CALL
+ getLong( sal_Int32 columnIndex ) override;
+
+ virtual float SAL_CALL
+ getFloat( sal_Int32 columnIndex ) override;
+
+ virtual double SAL_CALL
+ getDouble(
+ sal_Int32 columnIndex ) override;
+
+ virtual css::uno::Sequence< sal_Int8 > SAL_CALL
+ getBytes( sal_Int32 columnIndex ) override;
+
+ virtual css::util::Date SAL_CALL
+ getDate( sal_Int32 columnIndex ) override;
+
+ virtual css::util::Time SAL_CALL
+ getTime( sal_Int32 columnIndex ) override;
+
+ virtual css::util::DateTime SAL_CALL
+ getTimestamp( sal_Int32 columnIndex ) override;
+
+ virtual css::uno::Reference< css::io::XInputStream > SAL_CALL
+ getBinaryStream( sal_Int32 columnIndex ) override;
+
+ virtual css::uno::Reference< css::io::XInputStream > SAL_CALL
+ getCharacterStream( sal_Int32 columnIndex ) override;
+
+ virtual css::uno::Any SAL_CALL
+ getObject(
+ sal_Int32 columnIndex,
+ const css::uno::Reference< css::container::XNameAccess >& typeMap ) override;
+
+ virtual css::uno::Reference< css::sdbc::XRef > SAL_CALL
+ getRef( sal_Int32 columnIndex ) override;
+
+ virtual css::uno::Reference< css::sdbc::XBlob > SAL_CALL
+ getBlob( sal_Int32 columnIndex ) override;
+
+ virtual css::uno::Reference< css::sdbc::XClob > SAL_CALL
+ getClob( sal_Int32 columnIndex ) override;
+
+ virtual css::uno::Reference< css::sdbc::XArray > SAL_CALL
+ getArray( sal_Int32 columnIndex ) override;
+
+ private:
+ std::mutex m_aMutex;
+ css::uno::Sequence< css::uno::Any > m_aValueMap;
+ bool m_nWasNull;
+ TaskManager* m_pMyShell;
+ css::uno::Reference< css::script::XTypeConverter > m_xTypeConverter;
+
+ bool isIndexOutOfBounds( sal_Int32 nIndex ) const;
+ template<typename T>
+ T getValue(sal_Int32 columnIndex);
+ };
+
+} // end namespace fileaccess
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/ucb/source/ucp/file/filrset.cxx b/ucb/source/ucp/file/filrset.cxx
new file mode 100644
index 0000000000..2990df183b
--- /dev/null
+++ b/ucb/source/ucp/file/filrset.cxx
@@ -0,0 +1,679 @@
+/* -*- 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:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include <sal/config.h>
+#include <sal/log.hxx>
+
+#include <com/sun/star/sdbc/SQLException.hpp>
+#include <com/sun/star/ucb/ListenerAlreadySetException.hpp>
+#include <com/sun/star/ucb/ServiceNotFoundException.hpp>
+#include <com/sun/star/ucb/WelcomeDynamicResultSetStruct.hpp>
+#include "filid.hxx"
+#include "filtask.hxx"
+#include "filprp.hxx"
+#include "filrset.hxx"
+#include <com/sun/star/ucb/OpenMode.hpp>
+#include "prov.hxx"
+#include <com/sun/star/uno/Reference.h>
+
+#include <com/sun/star/beans/PropertyAttribute.hpp>
+#include <com/sun/star/ucb/ListActionType.hpp>
+#include <com/sun/star/ucb/XSourceInitialization.hpp>
+#include <com/sun/star/ucb/CachedDynamicResultSetStubFactory.hpp>
+#include <ucbhelper/resultsetmetadata.hxx>
+
+using namespace fileaccess;
+using namespace com::sun::star;
+
+#if OSL_DEBUG_LEVEL > 0
+#define THROW_WHERE SAL_WHERE
+#else
+#define THROW_WHERE ""
+#endif
+
+XResultSet_impl::XResultSet_impl( TaskManager* pMyShell,
+ const OUString& aUnqPath,
+ sal_Int32 OpenMode,
+ const uno::Sequence< beans::Property >& seq,
+ const uno::Sequence< ucb::NumberedSortingInfo >& seqSort )
+ : m_pMyShell( pMyShell )
+ , m_nRow( -1 )
+ , m_nWasNull ( false )
+ , m_nOpenMode( OpenMode )
+ , m_bRowCountFinal( false )
+ , m_aBaseDirectory( aUnqPath )
+ , m_aFolder( aUnqPath )
+ , m_sProperty( seq )
+ , m_sSortingInfo( seqSort )
+ , m_nErrorCode( TASKHANDLER_NO_ERROR )
+ , m_nMinorErrorCode( TASKHANDLER_NO_ERROR )
+{
+ osl::FileBase::RC err = m_aFolder.open();
+ if( err != osl::FileBase::E_None )
+ {
+ m_nIsOpen = false;
+ m_aFolder.close();
+
+ m_nErrorCode = TASKHANDLING_OPEN_FOR_DIRECTORYLISTING;
+ m_nMinorErrorCode = err;
+ }
+ else
+ m_nIsOpen = true;
+}
+
+
+XResultSet_impl::~XResultSet_impl()
+{
+ if( m_nIsOpen )
+ m_aFolder.close();
+}
+
+
+void SAL_CALL
+XResultSet_impl::disposing( const lang::EventObject& )
+{
+ // To do, but what
+}
+
+
+void SAL_CALL
+XResultSet_impl::addEventListener(
+ const uno::Reference< lang::XEventListener >& Listener )
+{
+ std::unique_lock aGuard( m_aMutex );
+
+ m_aDisposeEventListeners.addInterface( aGuard, Listener );
+}
+
+
+void SAL_CALL
+XResultSet_impl::removeEventListener(
+ const uno::Reference< lang::XEventListener >& Listener )
+{
+ std::unique_lock aGuard( m_aMutex );
+
+ m_aDisposeEventListeners.removeInterface( aGuard, Listener );
+}
+
+
+void SAL_CALL
+XResultSet_impl::dispose()
+{
+ std::unique_lock aGuard( m_aMutex );
+
+ lang::EventObject aEvt;
+ aEvt.Source = static_cast< lang::XComponent * >( this );
+
+ m_aDisposeEventListeners.disposeAndClear( aGuard, aEvt );
+ m_aRowCountListeners.disposeAndClear( aGuard, aEvt );
+ m_aIsFinalListeners.disposeAndClear( aGuard, aEvt );
+}
+
+
+void XResultSet_impl::rowCountChanged(std::unique_lock<std::mutex>& rGuard)
+{
+ sal_Int32 aOldValue,aNewValue;
+ std::vector< uno::Reference< beans::XPropertyChangeListener > > seq = m_aRowCountListeners.getElements(rGuard);
+ aNewValue = m_aItems.size();
+ aOldValue = aNewValue-1;
+ beans::PropertyChangeEvent aEv;
+ aEv.PropertyName = "RowCount";
+ aEv.Further = false;
+ aEv.PropertyHandle = -1;
+ aEv.OldValue <<= aOldValue;
+ aEv.NewValue <<= aNewValue;
+ for( const auto& listener : seq )
+ listener->propertyChange( aEv );
+}
+
+
+void XResultSet_impl::isFinalChanged()
+{
+ std::vector< uno::Reference< beans::XPropertyChangeListener > > seq;
+ {
+ std::unique_lock aGuard( m_aMutex );
+ seq = m_aIsFinalListeners.getElements(aGuard);
+ m_bRowCountFinal = true;
+ }
+ beans::PropertyChangeEvent aEv;
+ aEv.PropertyName = "IsRowCountFinal";
+ aEv.Further = false;
+ aEv.PropertyHandle = -1;
+ aEv.OldValue <<= false;
+ aEv.NewValue <<= true;
+ for( const auto& listener : seq )
+ listener->propertyChange( aEv );
+}
+
+
+bool
+XResultSet_impl::OneMore()
+{
+ if( ! m_nIsOpen )
+ return false;
+
+ osl::FileBase::RC err;
+ bool IsRegular;
+ OUString aUnqPath;
+ osl::DirectoryItem aDirIte;
+ uno::Reference< sdbc::XRow > aRow;
+
+ while( true )
+ {
+ err = m_aFolder.getNextItem( aDirIte );
+
+ if( err == osl::FileBase::E_NOENT || err == osl::FileBase::E_INVAL )
+ {
+ m_aFolder.close();
+ isFinalChanged();
+ m_nIsOpen = false;
+ return m_nIsOpen;
+ }
+ else if( err == osl::FileBase::E_None )
+ {
+ if (!m_pMyShell->getv( m_sProperty, aDirIte, aUnqPath, IsRegular, aRow ))
+ {
+ SAL_WARN(
+ "ucb.ucp.file",
+ "getting dir item in <" << m_aBaseDirectory << "> failed");
+ continue;
+ }
+
+ if( m_nOpenMode == ucb::OpenMode::DOCUMENTS && IsRegular )
+ {
+ std::unique_lock aGuard( m_aMutex );
+ m_aItems.push_back( aRow );
+ m_aIdents.emplace_back( );
+ m_aUnqPath.push_back( aUnqPath );
+ rowCountChanged(aGuard);
+ return true;
+
+ }
+ else if( m_nOpenMode == ucb::OpenMode::DOCUMENTS && ! IsRegular )
+ {
+ continue;
+ }
+ else if( m_nOpenMode == ucb::OpenMode::FOLDERS && ! IsRegular )
+ {
+ std::unique_lock aGuard( m_aMutex );
+ m_aItems.push_back( aRow );
+ m_aIdents.emplace_back( );
+ m_aUnqPath.push_back( aUnqPath );
+ rowCountChanged(aGuard);
+ return true;
+ }
+ else if( m_nOpenMode == ucb::OpenMode::FOLDERS && IsRegular )
+ {
+ continue;
+ }
+ else
+ {
+ std::unique_lock aGuard( m_aMutex );
+ m_aItems.push_back( aRow );
+ m_aIdents.emplace_back( );
+ m_aUnqPath.push_back( aUnqPath );
+ rowCountChanged(aGuard);
+ return true;
+ }
+ }
+ else // error fetching anything
+ {
+ throw sdbc::SQLException( THROW_WHERE, uno::Reference< uno::XInterface >(), OUString(), 0, uno::Any() );
+ }
+ }
+}
+
+
+sal_Bool SAL_CALL
+XResultSet_impl::next()
+{
+ bool test;
+ if( ++m_nRow < sal::static_int_cast<sal_Int32>(m_aItems.size()) ) test = true;
+ else
+ test = OneMore();
+ return test;
+}
+
+
+sal_Bool SAL_CALL
+XResultSet_impl::isBeforeFirst()
+{
+ return m_nRow == -1;
+}
+
+
+sal_Bool SAL_CALL
+XResultSet_impl::isAfterLast()
+{
+ return m_nRow >= sal::static_int_cast<sal_Int32>(m_aItems.size()); // Cannot happen, if m_aFolder.isOpen()
+}
+
+
+sal_Bool SAL_CALL
+XResultSet_impl::isFirst()
+{
+ return m_nRow == 0;
+}
+
+
+sal_Bool SAL_CALL
+XResultSet_impl::isLast()
+{
+ if( m_nRow == sal::static_int_cast<sal_Int32>(m_aItems.size()) - 1 )
+ return ! OneMore();
+ else
+ return false;
+}
+
+
+void SAL_CALL
+XResultSet_impl::beforeFirst()
+{
+ m_nRow = -1;
+}
+
+
+void SAL_CALL
+XResultSet_impl::afterLast()
+{
+ m_nRow = sal::static_int_cast<sal_Int32>(m_aItems.size());
+ while( OneMore() )
+ ++m_nRow;
+}
+
+
+sal_Bool SAL_CALL
+XResultSet_impl::first()
+{
+ m_nRow = -1;
+ return next();
+}
+
+
+sal_Bool SAL_CALL
+XResultSet_impl::last()
+{
+ m_nRow = sal::static_int_cast<sal_Int32>(m_aItems.size()) - 1;
+ while( OneMore() )
+ ++m_nRow;
+ return true;
+}
+
+
+sal_Int32 SAL_CALL
+XResultSet_impl::getRow()
+{
+ // Test, whether behind last row
+ if( -1 == m_nRow || m_nRow >= sal::static_int_cast<sal_Int32>(m_aItems.size()) )
+ return 0;
+ else
+ return m_nRow+1;
+}
+
+
+sal_Bool SAL_CALL XResultSet_impl::absolute( sal_Int32 row )
+{
+ if( row >= 0 )
+ {
+ m_nRow = row - 1;
+ if( row >= sal::static_int_cast<sal_Int32>(m_aItems.size()) )
+ while( row-- && OneMore() )
+ ;
+ }
+ else
+ {
+ last();
+ m_nRow += ( row + 1 );
+ if( m_nRow < -1 )
+ m_nRow = -1;
+ }
+
+ return 0<= m_nRow && m_nRow < sal::static_int_cast<sal_Int32>(m_aItems.size());
+}
+
+
+sal_Bool SAL_CALL
+XResultSet_impl::relative( sal_Int32 row )
+{
+ if( isAfterLast() || isBeforeFirst() )
+ throw sdbc::SQLException( THROW_WHERE, uno::Reference< uno::XInterface >(), OUString(), 0, uno::Any() );
+ if( row > 0 )
+ while( row-- ) next();
+ else if( row < 0 )
+ while( row++ && m_nRow > - 1 ) previous();
+
+ return 0 <= m_nRow && m_nRow < sal::static_int_cast<sal_Int32>(m_aItems.size());
+}
+
+
+sal_Bool SAL_CALL
+XResultSet_impl::previous()
+{
+ if( m_nRow > sal::static_int_cast<sal_Int32>(m_aItems.size()) )
+ m_nRow = sal::static_int_cast<sal_Int32>(m_aItems.size()); // Correct Handling of afterLast
+ if( 0 <= m_nRow ) -- m_nRow;
+
+ return 0 <= m_nRow && m_nRow < sal::static_int_cast<sal_Int32>(m_aItems.size());
+}
+
+
+void SAL_CALL
+XResultSet_impl::refreshRow()
+{
+ // get the row from the filesystem
+}
+
+
+sal_Bool SAL_CALL
+XResultSet_impl::rowUpdated()
+{
+ return false;
+}
+
+sal_Bool SAL_CALL
+XResultSet_impl::rowInserted()
+{
+ return false;
+}
+
+sal_Bool SAL_CALL
+XResultSet_impl::rowDeleted()
+{
+ return false;
+}
+
+
+uno::Reference< uno::XInterface > SAL_CALL
+XResultSet_impl::getStatement()
+{
+ return uno::Reference< uno::XInterface >();
+}
+
+
+// XCloseable
+
+void SAL_CALL
+XResultSet_impl::close()
+{
+ if( m_nIsOpen )
+ {
+ m_aFolder.close();
+ isFinalChanged();
+ std::unique_lock aGuard( m_aMutex );
+ m_nIsOpen = false;
+ }
+}
+
+
+OUString SAL_CALL
+XResultSet_impl::queryContentIdentifierString()
+{
+ uno::Reference< ucb::XContentIdentifier > xContentId
+ = queryContentIdentifier();
+
+ if( xContentId.is() )
+ return xContentId->getContentIdentifier();
+ else
+ return OUString();
+}
+
+
+uno::Reference< ucb::XContentIdentifier > SAL_CALL
+XResultSet_impl::queryContentIdentifier()
+{
+ if( 0 <= m_nRow && m_nRow < sal::static_int_cast<sal_Int32>(m_aItems.size()) )
+ {
+ if( ! m_aIdents[m_nRow].is() )
+ {
+ m_aIdents[m_nRow].set( new FileContentIdentifier( m_aUnqPath[ m_nRow ] ) );
+ }
+ return m_aIdents[m_nRow];
+ }
+ return uno::Reference< ucb::XContentIdentifier >();
+}
+
+
+uno::Reference< ucb::XContent > SAL_CALL
+XResultSet_impl::queryContent()
+{
+ if( 0 <= m_nRow && m_nRow < sal::static_int_cast<sal_Int32>(m_aItems.size()) )
+ return m_pMyShell->m_pProvider->queryContent( queryContentIdentifier() );
+ else
+ return uno::Reference< ucb::XContent >();
+}
+
+
+// XDynamicResultSet
+
+
+// virtual
+uno::Reference< sdbc::XResultSet > SAL_CALL
+XResultSet_impl::getStaticResultSet()
+{
+ std::unique_lock aGuard( m_aMutex );
+
+ if ( m_xListener.is() )
+ throw ucb::ListenerAlreadySetException( THROW_WHERE );
+
+ return uno::Reference< sdbc::XResultSet >( this );
+}
+
+
+// virtual
+void SAL_CALL
+XResultSet_impl::setListener(
+ const uno::Reference< ucb::XDynamicResultSetListener >& Listener )
+{
+ std::unique_lock aGuard( m_aMutex );
+
+ if ( m_xListener.is() )
+ throw ucb::ListenerAlreadySetException( THROW_WHERE );
+
+ m_xListener = Listener;
+
+
+ // Create "welcome event" and send it to listener.
+
+
+ // Note: We only have the implementation for a static result set at the
+ // moment (src590). The dynamic result sets passed to the listener
+ // are a fake. This implementation will never call "notify" at the
+ // listener to propagate any changes!!!
+
+ uno::Any aInfo;
+ aInfo <<= ucb::WelcomeDynamicResultSetStruct( this, /* "old" */
+ this /* "new" */ );
+
+ uno::Sequence< ucb::ListAction > aActions( 1 );
+ aActions.getArray()[ 0 ] = ucb::ListAction( 0, // Position; not used
+ 0, // Count; not used
+ ucb::ListActionType::WELCOME,
+ aInfo );
+ aGuard.unlock();
+
+ Listener->notify(
+ ucb::ListEvent(
+ getXWeak(), aActions ) );
+}
+
+
+// virtual
+void SAL_CALL
+XResultSet_impl::connectToCache(
+ const uno::Reference< ucb::XDynamicResultSet > & xCache )
+{
+ if( m_xListener.is() )
+ throw ucb::ListenerAlreadySetException( THROW_WHERE );
+
+ uno::Reference< ucb::XSourceInitialization > xTarget(
+ xCache, uno::UNO_QUERY );
+ if( xTarget.is() && m_pMyShell->m_xContext.is() )
+ {
+ uno::Reference< ucb::XCachedDynamicResultSetStubFactory > xStubFactory;
+ try
+ {
+ xStubFactory
+ = ucb::CachedDynamicResultSetStubFactory::create(
+ m_pMyShell->m_xContext );
+ }
+ catch ( uno::Exception const & )
+ {
+ }
+
+ if( xStubFactory.is() )
+ {
+ xStubFactory->connectToCache(
+ this, xCache,m_sSortingInfo, nullptr );
+ return;
+ }
+ }
+ throw ucb::ServiceNotFoundException( THROW_WHERE );
+}
+
+
+// virtual
+sal_Int16 SAL_CALL
+XResultSet_impl::getCapabilities()
+{
+ // Never set ucb::ContentResultSetCapability::SORTED
+ // - Underlying content cannot provide sorted data...
+ return 0;
+}
+
+// XResultSetMetaDataSupplier
+uno::Reference< sdbc::XResultSetMetaData > SAL_CALL
+XResultSet_impl::getMetaData()
+{
+ auto pProp = std::find_if(std::cbegin(m_sProperty), std::cend(m_sProperty),
+ [](const beans::Property& rProp) { return rProp.Name == "Title"; });
+ if (pProp != std::cend(m_sProperty))
+ {
+ std::vector< ::ucbhelper::ResultSetColumnData >
+ aColumnData( m_sProperty.getLength() );
+ auto n = std::distance(std::cbegin(m_sProperty), pProp);
+ // @@@ #82177# - Determine correct value!
+ aColumnData[ n ].isCaseSensitive = false;
+
+ return new ::ucbhelper::ResultSetMetaData(
+ m_pMyShell->m_xContext,
+ m_sProperty,
+ std::move(aColumnData) );
+ }
+
+ return new ::ucbhelper::ResultSetMetaData( m_pMyShell->m_xContext, m_sProperty );
+}
+
+
+// XPropertySet
+uno::Reference< beans::XPropertySetInfo > SAL_CALL
+XResultSet_impl::getPropertySetInfo()
+{
+
+ uno::Sequence< beans::Property > seq
+ {
+ { "RowCount", -1, cppu::UnoType<sal_Int32>::get(), beans::PropertyAttribute::READONLY },
+ { "IsRowCountFinal", -1, cppu::UnoType<sal_Bool>::get(), beans::PropertyAttribute::READONLY }
+ };
+
+ return new XPropertySetInfo_impl( m_pMyShell, seq );
+}
+
+
+void SAL_CALL XResultSet_impl::setPropertyValue(
+ const OUString& aPropertyName, const uno::Any& )
+{
+ if( aPropertyName == "IsRowCountFinal" ||
+ aPropertyName == "RowCount" )
+ return;
+ throw beans::UnknownPropertyException( aPropertyName );
+}
+
+
+uno::Any SAL_CALL XResultSet_impl::getPropertyValue(
+ const OUString& PropertyName )
+{
+ if( PropertyName == "IsRowCountFinal" )
+ {
+ return uno::Any(m_bRowCountFinal);
+ }
+ else if ( PropertyName == "RowCount" )
+ {
+ sal_Int32 count = sal::static_int_cast<sal_Int32>(m_aItems.size());
+ return uno::Any(count);
+ }
+ else
+ throw beans::UnknownPropertyException( PropertyName );
+}
+
+
+void SAL_CALL XResultSet_impl::addPropertyChangeListener(
+ const OUString& aPropertyName,
+ const uno::Reference< beans::XPropertyChangeListener >& xListener )
+{
+ if( aPropertyName == "IsRowCountFinal" )
+ {
+ std::unique_lock aGuard( m_aMutex );
+
+ m_aIsFinalListeners.addInterface( aGuard, xListener );
+ }
+ else if ( aPropertyName == "RowCount" )
+ {
+ std::unique_lock aGuard( m_aMutex );
+
+ m_aRowCountListeners.addInterface( aGuard, xListener );
+ }
+ else
+ throw beans::UnknownPropertyException( aPropertyName );
+}
+
+
+void SAL_CALL XResultSet_impl::removePropertyChangeListener(
+ const OUString& aPropertyName,
+ const uno::Reference< beans::XPropertyChangeListener >& aListener )
+{
+ if( aPropertyName == "IsRowCountFinal" )
+ {
+ std::unique_lock aGuard( m_aMutex );
+
+ m_aIsFinalListeners.removeInterface( aGuard, aListener );
+ }
+ else if ( aPropertyName == "RowCount" )
+ {
+ std::unique_lock aGuard( m_aMutex );
+
+ m_aRowCountListeners.removeInterface( aGuard, aListener );
+ }
+ else
+ throw beans::UnknownPropertyException( aPropertyName );
+}
+
+void SAL_CALL XResultSet_impl::addVetoableChangeListener(
+ const OUString&,
+ const uno::Reference< beans::XVetoableChangeListener >& )
+{
+}
+
+
+void SAL_CALL XResultSet_impl::removeVetoableChangeListener(
+ const OUString&,
+ const uno::Reference< beans::XVetoableChangeListener >& )
+{
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/ucb/source/ucp/file/filrset.hxx b/ucb/source/ucp/file/filrset.hxx
new file mode 100644
index 0000000000..3361405dd5
--- /dev/null
+++ b/ucb/source/ucp/file/filrset.hxx
@@ -0,0 +1,434 @@
+/* -*- 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:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+#pragma once
+
+#include <mutex>
+#include <vector>
+#include <osl/file.hxx>
+
+#include <comphelper/interfacecontainer4.hxx>
+#include <com/sun/star/ucb/XContentAccess.hpp>
+#include <com/sun/star/sdbc/XCloseable.hpp>
+#include <com/sun/star/beans/XPropertySet.hpp>
+#include <com/sun/star/ucb/XDynamicResultSet.hpp>
+#include <com/sun/star/ucb/XDynamicResultSetListener.hpp>
+#include <com/sun/star/sdbc/XResultSetMetaDataSupplier.hpp>
+#include <com/sun/star/ucb/NumberedSortingInfo.hpp>
+#include <com/sun/star/ucb/XContentIdentifier.hpp>
+#include <com/sun/star/beans/Property.hpp>
+#include "filrow.hxx"
+#include <cppuhelper/implbase.hxx>
+
+namespace fileaccess {
+
+class XResultSet_impl :
+ public cppu::WeakImplHelper< css::lang::XEventListener,
+ css::sdbc::XRow,
+ css::sdbc::XResultSet,
+ css::ucb::XDynamicResultSet,
+ css::sdbc::XCloseable,
+ css::sdbc::XResultSetMetaDataSupplier,
+ css::beans::XPropertySet,
+ css::ucb::XContentAccess >
+ {
+ public:
+
+ XResultSet_impl( TaskManager* pMyShell,
+ const OUString& aUnqPath,
+ sal_Int32 OpenMode,
+ const css::uno::Sequence< css::beans::Property >& seq,
+ const css::uno::Sequence< css::ucb::NumberedSortingInfo >& seqSort );
+
+ virtual ~XResultSet_impl() override;
+
+ sal_Int32 CtorSuccess() const { return m_nErrorCode;}
+ sal_Int32 getMinorError() const { return m_nMinorErrorCode;}
+
+ // XEventListener
+ virtual void SAL_CALL
+ disposing( const css::lang::EventObject& Source ) override;
+
+ // XComponent
+ virtual void SAL_CALL
+ dispose() override;
+
+ virtual void SAL_CALL
+ addEventListener(
+ const css::uno::Reference< css::lang::XEventListener >& xListener ) override;
+
+ virtual void SAL_CALL
+ removeEventListener( const css::uno::Reference< css::lang::XEventListener >& aListener ) override;
+
+
+ // XRow
+ virtual sal_Bool SAL_CALL
+ wasNull() override
+ {
+ if( 0<= m_nRow && m_nRow < sal::static_int_cast<sal_Int32>(m_aItems.size()) )
+ m_nWasNull = m_aItems[m_nRow]->wasNull();
+ else
+ m_nWasNull = true;
+ return m_nWasNull;
+ }
+
+ virtual OUString SAL_CALL
+ getString( sal_Int32 columnIndex ) override
+ {
+ if( 0 <= m_nRow && m_nRow < sal::static_int_cast<sal_Int32>(m_aItems.size()) )
+ return m_aItems[m_nRow]->getString( columnIndex );
+ else
+ return OUString();
+ }
+
+ virtual sal_Bool SAL_CALL
+ getBoolean( sal_Int32 columnIndex ) override
+ {
+ if( 0 <= m_nRow && m_nRow < sal::static_int_cast<sal_Int32>(m_aItems.size()) )
+ return m_aItems[m_nRow]->getBoolean( columnIndex );
+ else
+ return false;
+ }
+
+ virtual sal_Int8 SAL_CALL
+ getByte( sal_Int32 columnIndex ) override
+ {
+ if( 0 <= m_nRow && m_nRow < sal::static_int_cast<sal_Int32>(m_aItems.size()) )
+ return m_aItems[m_nRow]->getByte( columnIndex );
+ else
+ return sal_Int8( 0 );
+ }
+
+ virtual sal_Int16 SAL_CALL
+ getShort( sal_Int32 columnIndex ) override
+ {
+ if( 0 <= m_nRow && m_nRow < sal::static_int_cast<sal_Int32>(m_aItems.size()) )
+ return m_aItems[m_nRow]->getShort( columnIndex );
+ else
+ return sal_Int16( 0 );
+ }
+
+ virtual sal_Int32 SAL_CALL
+ getInt( sal_Int32 columnIndex ) override
+ {
+ if( 0 <= m_nRow && m_nRow < sal::static_int_cast<sal_Int32>(m_aItems.size()) )
+ return m_aItems[m_nRow]->getInt( columnIndex );
+ else
+ return 0;
+ }
+
+ virtual sal_Int64 SAL_CALL
+ getLong( sal_Int32 columnIndex ) override
+ {
+ if( 0 <= m_nRow && m_nRow < sal::static_int_cast<sal_Int32>(m_aItems.size()) )
+ return m_aItems[m_nRow]->getLong( columnIndex );
+ else
+ return sal_Int64( 0 );
+ }
+
+ virtual float SAL_CALL
+ getFloat( sal_Int32 columnIndex ) override
+ {
+ if( 0 <= m_nRow && m_nRow < sal::static_int_cast<sal_Int32>(m_aItems.size()) )
+ return m_aItems[m_nRow]->getFloat( columnIndex );
+ else
+ return float( 0 );
+ }
+
+ virtual double SAL_CALL
+ getDouble( sal_Int32 columnIndex ) override
+ {
+ if( 0 <= m_nRow && m_nRow < sal::static_int_cast<sal_Int32>(m_aItems.size()) )
+ return m_aItems[m_nRow]->getDouble( columnIndex );
+ else
+ return double( 0 );
+ }
+
+ virtual css::uno::Sequence< sal_Int8 > SAL_CALL
+ getBytes( sal_Int32 columnIndex ) override
+ {
+ if( 0 <= m_nRow && m_nRow < sal::static_int_cast<sal_Int32>(m_aItems.size()) )
+ return m_aItems[m_nRow]->getBytes( columnIndex );
+ else
+ return css::uno::Sequence< sal_Int8 >();
+ }
+
+ virtual css::util::Date SAL_CALL
+ getDate( sal_Int32 columnIndex ) override
+ {
+ if( 0 <= m_nRow && m_nRow < sal::static_int_cast<sal_Int32>(m_aItems.size()) )
+ return m_aItems[m_nRow]->getDate( columnIndex );
+ else
+ return css::util::Date();
+ }
+
+ virtual css::util::Time SAL_CALL
+ getTime( sal_Int32 columnIndex ) override
+ {
+ if( 0 <= m_nRow && m_nRow < sal::static_int_cast<sal_Int32>(m_aItems.size()) )
+ return m_aItems[m_nRow]->getTime( columnIndex );
+ else
+ return css::util::Time();
+ }
+
+ virtual css::util::DateTime SAL_CALL
+ getTimestamp( sal_Int32 columnIndex ) override
+ {
+ if( 0 <= m_nRow && m_nRow < sal::static_int_cast<sal_Int32>(m_aItems.size()) )
+ return m_aItems[m_nRow]->getTimestamp( columnIndex );
+ else
+ return css::util::DateTime();
+ }
+
+ virtual css::uno::Reference< css::io::XInputStream > SAL_CALL
+ getBinaryStream( sal_Int32 columnIndex ) override
+ {
+ if( 0 <= m_nRow && m_nRow < sal::static_int_cast<sal_Int32>(m_aItems.size()) )
+ return m_aItems[m_nRow]->getBinaryStream( columnIndex );
+ else
+ return css::uno::Reference< css::io::XInputStream >();
+ }
+
+ virtual css::uno::Reference< css::io::XInputStream > SAL_CALL
+ getCharacterStream( sal_Int32 columnIndex ) override
+ {
+ if( 0 <= m_nRow && m_nRow < sal::static_int_cast<sal_Int32>(m_aItems.size()) )
+ return m_aItems[m_nRow]->getCharacterStream( columnIndex );
+ else
+ return css::uno::Reference< css::io::XInputStream >();
+ }
+
+ virtual css::uno::Any SAL_CALL
+ getObject( sal_Int32 columnIndex,
+ const css::uno::Reference< css::container::XNameAccess >& typeMap ) override
+ {
+ if( 0 <= m_nRow && m_nRow < sal::static_int_cast<sal_Int32>(m_aItems.size()) )
+ return m_aItems[m_nRow]->getObject( columnIndex,typeMap );
+ else
+ return css::uno::Any();
+ }
+
+ virtual css::uno::Reference< css::sdbc::XRef > SAL_CALL
+ getRef( sal_Int32 columnIndex ) override
+ {
+ if( 0 <= m_nRow && m_nRow < sal::static_int_cast<sal_Int32>(m_aItems.size()) )
+ return m_aItems[m_nRow]->getRef( columnIndex );
+ else
+ return css::uno::Reference< css::sdbc::XRef >();
+ }
+
+ virtual css::uno::Reference< css::sdbc::XBlob > SAL_CALL
+ getBlob( sal_Int32 columnIndex ) override
+ {
+ if( 0 <= m_nRow && m_nRow < sal::static_int_cast<sal_Int32>(m_aItems.size()) )
+ return m_aItems[m_nRow]->getBlob( columnIndex );
+ else
+ return css::uno::Reference< css::sdbc::XBlob >();
+ }
+
+ virtual css::uno::Reference< css::sdbc::XClob > SAL_CALL
+ getClob( sal_Int32 columnIndex ) override
+ {
+ if( 0 <= m_nRow && m_nRow < sal::static_int_cast<sal_Int32>(m_aItems.size()) )
+ return m_aItems[m_nRow]->getClob( columnIndex );
+ else
+ return css::uno::Reference< css::sdbc::XClob >();
+ }
+
+ virtual css::uno::Reference< css::sdbc::XArray > SAL_CALL
+ getArray( sal_Int32 columnIndex ) override
+ {
+ if( 0 <= m_nRow && m_nRow < sal::static_int_cast<sal_Int32>(m_aItems.size()) )
+ return m_aItems[m_nRow]->getArray( columnIndex );
+ else
+ return css::uno::Reference< css::sdbc::XArray >();
+ }
+
+
+ // XResultSet
+
+ virtual sal_Bool SAL_CALL
+ next() override;
+
+ virtual sal_Bool SAL_CALL
+ isBeforeFirst() override;
+
+ virtual sal_Bool SAL_CALL
+ isAfterLast() override;
+
+ virtual sal_Bool SAL_CALL
+ isFirst() override;
+
+ virtual sal_Bool SAL_CALL
+ isLast() override;
+
+ virtual void SAL_CALL
+ beforeFirst() override;
+
+ virtual void SAL_CALL
+ afterLast() override;
+
+ virtual sal_Bool SAL_CALL
+ first() override;
+
+ virtual sal_Bool SAL_CALL
+ last() override;
+
+ virtual sal_Int32 SAL_CALL
+ getRow() override;
+
+ virtual sal_Bool SAL_CALL
+ absolute( sal_Int32 row ) override;
+
+ virtual sal_Bool SAL_CALL
+ relative( sal_Int32 rows ) override;
+
+ virtual sal_Bool SAL_CALL
+ previous() override;
+
+ virtual void SAL_CALL
+ refreshRow() override;
+
+ virtual sal_Bool SAL_CALL
+ rowUpdated() override;
+
+ virtual sal_Bool SAL_CALL
+ rowInserted() override;
+
+ virtual sal_Bool SAL_CALL
+ rowDeleted() override;
+
+
+ virtual css::uno::Reference< css::uno::XInterface > SAL_CALL
+ getStatement() override;
+
+
+ // XDynamicResultSet
+
+ virtual css::uno::Reference< css::sdbc::XResultSet > SAL_CALL
+ getStaticResultSet() override;
+
+ virtual void SAL_CALL
+ setListener(
+ const css::uno::Reference<
+ css::ucb::XDynamicResultSetListener >& Listener ) override;
+
+ virtual void SAL_CALL
+ connectToCache( const css::uno::Reference< css::ucb::XDynamicResultSet > & xCache ) override;
+
+ virtual sal_Int16 SAL_CALL
+ getCapabilities() override;
+
+
+ // XCloseable
+
+ virtual void SAL_CALL
+ close() override;
+
+ // XContentAccess
+
+ virtual OUString SAL_CALL
+ queryContentIdentifierString() override;
+
+ virtual css::uno::Reference< css::ucb::XContentIdentifier > SAL_CALL
+ queryContentIdentifier() override;
+
+ virtual css::uno::Reference< css::ucb::XContent > SAL_CALL
+ queryContent() override;
+
+ // XResultSetMetaDataSupplier
+ virtual css::uno::Reference< css::sdbc::XResultSetMetaData > SAL_CALL
+ getMetaData() override;
+
+
+ // XPropertySet
+ virtual css::uno::Reference< css::beans::XPropertySetInfo > SAL_CALL
+ getPropertySetInfo() override;
+
+ virtual void SAL_CALL setPropertyValue(
+ const OUString& aPropertyName,
+ const css::uno::Any& aValue ) override;
+
+ virtual css::uno::Any SAL_CALL
+ getPropertyValue(
+ const OUString& PropertyName ) override;
+
+ virtual void SAL_CALL
+ addPropertyChangeListener(
+ const OUString& aPropertyName,
+ const css::uno::Reference< css::beans::XPropertyChangeListener >& xListener ) override;
+
+ virtual void SAL_CALL
+ removePropertyChangeListener(
+ const OUString& aPropertyName,
+ const css::uno::Reference< css::beans::XPropertyChangeListener >& aListener ) override;
+
+ virtual void SAL_CALL
+ addVetoableChangeListener(
+ const OUString& PropertyName,
+ const css::uno::Reference< css::beans::XVetoableChangeListener >& aListener ) override;
+
+ virtual void SAL_CALL removeVetoableChangeListener(
+ const OUString& PropertyName,
+ const css::uno::Reference< css::beans::XVetoableChangeListener >& aListener ) override;
+
+ private:
+
+ TaskManager* m_pMyShell;
+ bool m_nIsOpen;
+ sal_Int32 m_nRow;
+ bool m_nWasNull;
+ sal_Int32 m_nOpenMode;
+ bool m_bRowCountFinal;
+
+ typedef std::vector< css::uno::Reference< css::ucb::XContentIdentifier > > IdentSet;
+ typedef std::vector< css::uno::Reference< css::sdbc::XRow > > ItemSet;
+
+ IdentSet m_aIdents;
+ ItemSet m_aItems;
+ std::vector< OUString > m_aUnqPath;
+ const OUString m_aBaseDirectory;
+
+ osl::Directory m_aFolder;
+ css::uno::Sequence< css::beans::Property > m_sProperty;
+ css::uno::Sequence< css::ucb::NumberedSortingInfo > m_sSortingInfo;
+
+ std::mutex m_aMutex;
+ comphelper::OInterfaceContainerHelper4<css::lang::XEventListener> m_aDisposeEventListeners;
+ comphelper::OInterfaceContainerHelper4<css::beans::XPropertyChangeListener> m_aRowCountListeners;
+ comphelper::OInterfaceContainerHelper4<css::beans::XPropertyChangeListener> m_aIsFinalListeners;
+
+ css::uno::Reference< css::ucb::XDynamicResultSetListener > m_xListener;
+
+ sal_Int32 m_nErrorCode;
+ sal_Int32 m_nMinorErrorCode;
+
+ // Methods
+ /// @throws css::sdbc::SQLException
+ /// @throws css::uno::RuntimeException
+ bool OneMore();
+
+ void rowCountChanged(std::unique_lock<std::mutex>&);
+ void isFinalChanged();
+ };
+
+
+} // end namespace fileaccess
+
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/ucb/source/ucp/file/filstr.cxx b/ucb/source/ucp/file/filstr.cxx
new file mode 100644
index 0000000000..70c235da7d
--- /dev/null
+++ b/ucb/source/ucp/file/filstr.cxx
@@ -0,0 +1,288 @@
+/* -*- 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:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include <sal/config.h>
+
+#include <com/sun/star/io/BufferSizeExceededException.hpp>
+#include <com/sun/star/io/IOException.hpp>
+#include <com/sun/star/lang/IllegalArgumentException.hpp>
+#include <com/sun/star/uno/RuntimeException.hpp>
+#include <osl/diagnose.h>
+#include "filstr.hxx"
+#include "filerror.hxx"
+
+using namespace fileaccess;
+using namespace com::sun::star;
+
+#if OSL_DEBUG_LEVEL > 0
+#define THROW_WHERE SAL_WHERE
+#else
+#define THROW_WHERE ""
+#endif
+
+/******************************************************************************/
+/* */
+/* XStream_impl implementation */
+/* */
+/******************************************************************************/
+
+XStream_impl::XStream_impl( const OUString& aUncPath, bool bLock )
+ : m_bInputStreamCalled( false ),
+ m_bOutputStreamCalled( false ),
+ m_aFile( aUncPath ),
+ m_nErrorCode( TASKHANDLER_NO_ERROR ),
+ m_nMinorErrorCode( TASKHANDLER_NO_ERROR )
+{
+ sal_uInt32 nFlags = ( osl_File_OpenFlag_Read | osl_File_OpenFlag_Write );
+ if ( !bLock )
+ nFlags |= osl_File_OpenFlag_NoLock;
+
+ osl::FileBase::RC err = m_aFile.open( nFlags );
+ if( err != osl::FileBase::E_None )
+ {
+ m_nIsOpen = false;
+ m_aFile.close();
+
+ m_nErrorCode = TASKHANDLING_OPEN_FOR_STREAM;
+ m_nMinorErrorCode = err;
+ }
+ else
+ m_nIsOpen = true;
+}
+
+
+XStream_impl::~XStream_impl()
+{
+ try
+ {
+ closeStream();
+ }
+ catch (const io::IOException&)
+ {
+ OSL_FAIL("unexpected situation");
+ }
+ catch (const uno::RuntimeException&)
+ {
+ OSL_FAIL("unexpected situation");
+ }
+}
+
+
+uno::Reference< io::XInputStream > SAL_CALL
+XStream_impl::getInputStream( )
+{
+ {
+ std::scoped_lock aGuard( m_aMutex );
+ m_bInputStreamCalled = true;
+ }
+ return uno::Reference< io::XInputStream >( this );
+}
+
+
+uno::Reference< io::XOutputStream > SAL_CALL
+XStream_impl::getOutputStream( )
+{
+ {
+ std::scoped_lock aGuard( m_aMutex );
+ m_bOutputStreamCalled = true;
+ }
+ return uno::Reference< io::XOutputStream >( this );
+}
+
+
+void SAL_CALL XStream_impl::truncate()
+{
+ if (osl::FileBase::E_None != m_aFile.setSize(0))
+ throw io::IOException( THROW_WHERE );
+
+ if (osl::FileBase::E_None != m_aFile.setPos(osl_Pos_Absolut,sal_uInt64(0)))
+ throw io::IOException( THROW_WHERE );
+}
+
+
+// XStream_impl private non interface methods
+
+
+sal_Int32 SAL_CALL
+XStream_impl::readBytes(
+ uno::Sequence< sal_Int8 >& aData,
+ sal_Int32 nBytesToRead )
+{
+ if( ! m_nIsOpen )
+ throw io::IOException( THROW_WHERE );
+
+ try
+ {
+ aData.realloc(nBytesToRead);
+ }
+ catch (const std::bad_alloc&)
+ {
+ if( m_nIsOpen ) m_aFile.close();
+ throw io::BufferSizeExceededException( THROW_WHERE );
+ }
+
+ sal_uInt64 nrc(0);
+ if(m_aFile.read( aData.getArray(), sal_uInt64(nBytesToRead), nrc )
+ != osl::FileBase::E_None)
+ {
+ throw io::IOException( THROW_WHERE );
+ }
+ if (nrc != static_cast<sal_uInt64>(nBytesToRead))
+ aData.realloc(nrc);
+ return static_cast<sal_Int32>(nrc);
+}
+
+sal_Int32
+XStream_impl::readSomeBytes(
+ sal_Int8* pData,
+ sal_Int32 nBytesToRead )
+{
+ if( ! m_nIsOpen )
+ throw io::IOException( THROW_WHERE );
+
+ sal_uInt64 nrc(0);
+ if(m_aFile.read( pData, sal_uInt64(nBytesToRead), nrc )
+ != osl::FileBase::E_None)
+ {
+ throw io::IOException( THROW_WHERE );
+ }
+ return static_cast<sal_Int32>(nrc);
+}
+
+sal_Int32 SAL_CALL
+XStream_impl::readSomeBytes(
+ uno::Sequence< sal_Int8 >& aData,
+ sal_Int32 nMaxBytesToRead )
+{
+ return readBytes( aData,nMaxBytesToRead );
+}
+
+
+void SAL_CALL
+XStream_impl::skipBytes( sal_Int32 nBytesToSkip )
+{
+ m_aFile.setPos( osl_Pos_Current, sal_uInt64( nBytesToSkip ) );
+}
+
+
+sal_Int32 SAL_CALL
+XStream_impl::available()
+{
+ sal_Int64 avail = getLength() - getPosition();
+ return std::min<sal_Int64>(avail, SAL_MAX_INT32);
+}
+
+
+void SAL_CALL
+XStream_impl::writeBytes( const uno::Sequence< sal_Int8 >& aData )
+{
+ sal_uInt32 length = aData.getLength();
+ if(length)
+ {
+ sal_uInt64 nWrittenBytes(0);
+ const sal_Int8* p = aData.getConstArray();
+ if(osl::FileBase::E_None != m_aFile.write(static_cast<void const *>(p),sal_uInt64(length),nWrittenBytes) ||
+ nWrittenBytes != length )
+ throw io::IOException( THROW_WHERE );
+ }
+}
+
+
+void
+XStream_impl::closeStream()
+{
+ if( m_nIsOpen )
+ {
+ osl::FileBase::RC err = m_aFile.close();
+
+ if( err != osl::FileBase::E_None ) {
+ throw io::IOException("could not close file");
+ }
+
+ m_nIsOpen = false;
+ }
+}
+
+void SAL_CALL
+XStream_impl::closeInput()
+{
+ std::scoped_lock aGuard( m_aMutex );
+ m_bInputStreamCalled = false;
+
+ if( ! m_bOutputStreamCalled )
+ closeStream();
+}
+
+
+void SAL_CALL
+XStream_impl::closeOutput()
+{
+ std::scoped_lock aGuard( m_aMutex );
+ m_bOutputStreamCalled = false;
+
+ if( ! m_bInputStreamCalled )
+ closeStream();
+}
+
+
+void SAL_CALL
+XStream_impl::seek( sal_Int64 location )
+{
+ if( location < 0 )
+ throw lang::IllegalArgumentException( THROW_WHERE, uno::Reference< uno::XInterface >(), 0 );
+ if( osl::FileBase::E_None != m_aFile.setPos( osl_Pos_Absolut, sal_uInt64( location ) ) )
+ throw io::IOException( THROW_WHERE );
+}
+
+
+sal_Int64 SAL_CALL
+XStream_impl::getPosition()
+{
+ sal_uInt64 uPos;
+ if( osl::FileBase::E_None != m_aFile.getPos( uPos ) )
+ throw io::IOException( THROW_WHERE );
+ return sal_Int64( uPos );
+}
+
+sal_Int64 SAL_CALL
+XStream_impl::getLength()
+{
+ sal_uInt64 uEndPos;
+ if ( m_aFile.getSize(uEndPos) != osl::FileBase::E_None )
+ throw io::IOException( THROW_WHERE );
+ return sal_Int64( uEndPos );
+}
+
+void SAL_CALL
+XStream_impl::flush()
+{}
+
+void XStream_impl::waitForCompletion()
+{
+ // At least on UNIX, to reliably learn about any errors encountered by
+ // asynchronous NFS write operations, without closing the file directly
+ // afterwards, there appears to be no cheaper way than to call fsync:
+ if (m_nIsOpen && m_aFile.sync() != osl::FileBase::E_None) {
+ throw io::IOException(
+ "could not synchronize file to disc",
+ getXWeak());
+ }
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/ucb/source/ucp/file/filstr.hxx b/ucb/source/ucp/file/filstr.hxx
new file mode 100644
index 0000000000..acef94aa8c
--- /dev/null
+++ b/ucb/source/ucp/file/filstr.hxx
@@ -0,0 +1,155 @@
+/* -*- 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:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+#pragma once
+
+#include <rtl/ustring.hxx>
+#include <com/sun/star/io/XSeekable.hpp>
+#include <com/sun/star/io/XTruncate.hpp>
+#include <com/sun/star/io/XInputStream.hpp>
+#include <com/sun/star/io/XOutputStream.hpp>
+#include <com/sun/star/io/XStream.hpp>
+#include <com/sun/star/io/XAsyncOutputMonitor.hpp>
+#include <com/sun/star/lang/XUnoTunnel.hpp>
+#include <comphelper/bytereader.hxx>
+#include <cppuhelper/implbase.hxx>
+#include <mutex>
+
+#include "filrec.hxx"
+
+namespace fileaccess {
+
+ // forward:
+ class TaskManager;
+
+class XStream_impl : public cppu::WeakImplHelper<
+ css::io::XStream,
+ css::io::XSeekable,
+ css::io::XInputStream,
+ css::io::XOutputStream,
+ css::io::XTruncate,
+ css::io::XAsyncOutputMonitor >,
+ public comphelper::ByteReader
+ {
+
+ public:
+
+ XStream_impl( const OUString& aUncPath, bool bLock );
+
+ /**
+ * Returns an error code as given by filerror.hxx
+ */
+
+ sal_Int32 CtorSuccess() const { return m_nErrorCode;}
+ sal_Int32 getMinorError() const { return m_nMinorErrorCode;}
+
+ virtual ~XStream_impl() override;
+
+ // XStream
+
+ virtual css::uno::Reference< css::io::XInputStream > SAL_CALL
+ getInputStream() override;
+
+ virtual css::uno::Reference< css::io::XOutputStream > SAL_CALL
+ getOutputStream() override;
+
+
+ // XTruncate
+
+ virtual void SAL_CALL truncate() override;
+
+
+ // XInputStream
+
+ sal_Int32 SAL_CALL
+ readBytes(
+ css::uno::Sequence< sal_Int8 >& aData,
+ sal_Int32 nBytesToRead ) override;
+
+ sal_Int32 SAL_CALL
+ readSomeBytes(
+ css::uno::Sequence< sal_Int8 >& aData,
+ sal_Int32 nMaxBytesToRead ) override;
+
+
+ void SAL_CALL
+ skipBytes( sal_Int32 nBytesToSkip ) override;
+
+ sal_Int32 SAL_CALL
+ available() override;
+
+ void SAL_CALL
+ closeInput() override;
+
+ // XSeekable
+
+ void SAL_CALL
+ seek( sal_Int64 location ) override;
+
+ sal_Int64 SAL_CALL
+ getPosition() override;
+
+ sal_Int64 SAL_CALL
+ getLength() override;
+
+
+ // XOutputStream
+
+ void SAL_CALL
+ writeBytes( const css::uno::Sequence< sal_Int8 >& aData ) override;
+
+
+ void SAL_CALL
+ flush() override;
+
+
+ void SAL_CALL
+ closeOutput() override;
+
+ virtual void SAL_CALL waitForCompletion() override;
+
+ // utl::ByteReader
+ virtual sal_Int32
+ readSomeBytes(
+ sal_Int8* aData,
+ sal_Int32 nMaxBytesToRead ) override;
+
+ private:
+
+ std::mutex m_aMutex;
+ bool m_bInputStreamCalled,m_bOutputStreamCalled;
+ bool m_nIsOpen;
+
+ ReconnectingFile m_aFile;
+
+ sal_Int32 m_nErrorCode;
+ sal_Int32 m_nMinorErrorCode;
+
+ // Implementation methods
+
+ /// @throws css::io::NotConnectedException
+ /// @throws css::io::IOException
+ /// @throws css::uno::RuntimeException
+ void
+ closeStream();
+
+ };
+
+} // end namespace XStream_impl
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/ucb/source/ucp/file/filtask.cxx b/ucb/source/ucp/file/filtask.cxx
new file mode 100644
index 0000000000..930f952d00
--- /dev/null
+++ b/ucb/source/ucp/file/filtask.cxx
@@ -0,0 +1,2936 @@
+/* -*- 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:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include <config_features.h>
+
+#include <sal/config.h>
+#include <sal/log.hxx>
+
+#if HAVE_FEATURE_MACOSX_SANDBOX
+#include <sys/stat.h>
+#endif
+
+#include <com/sun/star/beans/IllegalTypeException.hpp>
+#include <com/sun/star/beans/NotRemoveableException.hpp>
+#include <com/sun/star/beans/PropertyAttribute.hpp>
+#include <com/sun/star/beans/PropertyExistException.hpp>
+#include <com/sun/star/io/BufferSizeExceededException.hpp>
+#include <com/sun/star/io/NotConnectedException.hpp>
+#include <com/sun/star/io/IOException.hpp>
+#include <com/sun/star/lang/IllegalAccessException.hpp>
+#include <com/sun/star/task/InteractionClassification.hpp>
+#include <com/sun/star/ucb/ContentInfoAttribute.hpp>
+#include <com/sun/star/ucb/DuplicateCommandIdentifierException.hpp>
+#include <com/sun/star/ucb/IOErrorCode.hpp>
+#include <com/sun/star/ucb/InsertCommandArgument.hpp>
+#include <com/sun/star/ucb/InteractiveAugmentedIOException.hpp>
+#include <com/sun/star/ucb/NameClash.hpp>
+#include <com/sun/star/ucb/OpenCommandArgument.hpp>
+#include <com/sun/star/ucb/Store.hpp>
+#include <com/sun/star/ucb/TransferInfo.hpp>
+#include <comphelper/propertysequence.hxx>
+#include <rtl/ref.hxx>
+#include <rtl/uri.hxx>
+
+#include "filtask.hxx"
+#include "filcmd.hxx"
+#include "filglob.hxx"
+#include "filinpstr.hxx"
+#include "filprp.hxx"
+#include "filrset.hxx"
+#include "filstr.hxx"
+#include "prov.hxx"
+
+/******************************************************************************/
+/* */
+/* TaskHandling */
+/* */
+/******************************************************************************/
+
+
+using namespace fileaccess;
+using namespace com::sun::star;
+using namespace com::sun::star::uno;
+using namespace com::sun::star::ucb;
+
+#if OSL_DEBUG_LEVEL > 0
+#define THROW_WHERE SAL_WHERE
+#else
+#define THROW_WHERE ""
+#endif
+
+TaskManager::UnqPathData::UnqPathData() = default;
+
+TaskManager::UnqPathData::UnqPathData(TaskManager::UnqPathData&&) = default;
+
+
+TaskManager::UnqPathData::~UnqPathData()
+{
+}
+
+TaskManager::MyProperty::MyProperty( const OUString& thePropertyName )
+ : PropertyName( thePropertyName )
+ , Handle(-1)
+ , isNative(false)
+ , State(beans::PropertyState_AMBIGUOUS_VALUE)
+ , Attributes(0)
+{
+ // empty
+}
+
+TaskManager::MyProperty::MyProperty( bool theisNative,
+ const OUString& thePropertyName,
+ sal_Int32 theHandle,
+ const css::uno::Type& theTyp,
+ const css::uno::Any& theValue,
+ const css::beans::PropertyState& theState,
+ sal_Int16 theAttributes )
+ : PropertyName( thePropertyName ),
+ Handle( theHandle ),
+ isNative( theisNative ),
+ Typ( theTyp ),
+ Value( theValue ),
+ State( theState ),
+ Attributes( theAttributes )
+{
+ // empty
+}
+
+#include "filinl.hxx"
+
+ // Default properties
+
+constexpr OUString Title( u"Title"_ustr );
+constexpr OUString CasePreservingURL( u"CasePreservingURL"_ustr );
+constexpr OUString IsDocument( u"IsDocument"_ustr );
+constexpr OUString IsFolder( u"IsFolder"_ustr );
+constexpr OUString DateModified( u"DateModified"_ustr );
+constexpr OUString Size( u"Size"_ustr );
+constexpr OUString IsVolume( u"IsVolume"_ustr );
+constexpr OUString IsRemoveable( u"IsRemoveable"_ustr );
+constexpr OUString IsRemote( u"IsRemote"_ustr );
+constexpr OUString IsCompactDisc( u"IsCompactDisc"_ustr );
+constexpr OUString IsFloppy( u"IsFloppy"_ustr );
+constexpr OUString IsHidden( u"IsHidden"_ustr );
+constexpr OUString ContentType( u"ContentType"_ustr );
+constexpr OUString IsReadOnly( u"IsReadOnly"_ustr );
+constexpr OUString CreatableContentsInfo( u"CreatableContentsInfo"_ustr );
+
+TaskManager::TaskManager( const uno::Reference< uno::XComponentContext >& rxContext,
+ FileProvider* pProvider, bool bWithConfig )
+ : m_nCommandId( 0 ),
+ m_pProvider( pProvider ),
+ m_xContext( rxContext ),
+ // Commands
+ m_sCommandInfo{
+ { /* Name */ "getCommandInfo",
+ /* Handle */ -1,
+ /* ArgType */ cppu::UnoType<void>::get() },
+
+ { /* Name */ "getPropertySetInfo",
+ /* Handle */ -1,
+ /* ArgType */ cppu::UnoType<void>::get() },
+
+ { /* Name */ "getPropertyValues",
+ /* Handle */ -1,
+ /* ArgType */ cppu::UnoType<uno::Sequence< beans::Property >>::get() },
+
+ { /* Name */ "setPropertyValues",
+ /* Handle */ -1,
+ /* ArgType */ cppu::UnoType<uno::Sequence< beans::PropertyValue >>::get() },
+
+ { /* Name */ "open",
+ /* Handle */ -1,
+ /* ArgType */ cppu::UnoType<OpenCommandArgument>::get() },
+
+ { /* Name */ "transfer",
+ /* Handle */ -1,
+ /* ArgType */ cppu::UnoType<TransferInfo>::get() },
+
+ { /* Name */ "delete",
+ /* Handle */ -1,
+ /* ArgType */ cppu::UnoType<sal_Bool>::get() },
+
+ { /* Name */ "insert",
+ /* Handle */ -1,
+ /* ArgType */ cppu::UnoType<InsertCommandArgument>::get() },
+
+ { /* Name */ "createNewContent",
+ /* Handle */ -1,
+ /* ArgType */ cppu::UnoType<ucb::ContentInfo>::get() } }
+{
+ // Title
+ m_aDefaultProperties.insert( MyProperty( true,
+ Title,
+ -1 ,
+ cppu::UnoType<OUString>::get(),
+ uno::Any(),
+ beans::PropertyState_DEFAULT_VALUE,
+ beans::PropertyAttribute::MAYBEVOID
+ | beans::PropertyAttribute::BOUND ) );
+
+ // CasePreservingURL
+ m_aDefaultProperties.insert(
+ MyProperty( true,
+ CasePreservingURL,
+ -1 ,
+ cppu::UnoType<OUString>::get(),
+ uno::Any(),
+ beans::PropertyState_DEFAULT_VALUE,
+ beans::PropertyAttribute::MAYBEVOID
+ | beans::PropertyAttribute::BOUND
+ | beans::PropertyAttribute::READONLY ) );
+
+
+ // IsFolder
+ m_aDefaultProperties.insert( MyProperty( true,
+ IsFolder,
+ -1 ,
+ cppu::UnoType<sal_Bool>::get(),
+ uno::Any(),
+ beans::PropertyState_DEFAULT_VALUE,
+ beans::PropertyAttribute::MAYBEVOID
+ | beans::PropertyAttribute::BOUND
+ | beans::PropertyAttribute::READONLY ) );
+
+
+ // IsDocument
+ m_aDefaultProperties.insert( MyProperty( true,
+ IsDocument,
+ -1 ,
+ cppu::UnoType<sal_Bool>::get(),
+ uno::Any(),
+ beans::PropertyState_DEFAULT_VALUE,
+ beans::PropertyAttribute::MAYBEVOID
+ | beans::PropertyAttribute::BOUND
+ | beans::PropertyAttribute::READONLY ) );
+
+ // Removable
+ m_aDefaultProperties.insert( MyProperty( true,
+ IsVolume,
+ -1 ,
+ cppu::UnoType<sal_Bool>::get(),
+ uno::Any(),
+ beans::PropertyState_DEFAULT_VALUE,
+ beans::PropertyAttribute::MAYBEVOID
+ | beans::PropertyAttribute::BOUND
+ | beans::PropertyAttribute::READONLY ) );
+
+
+ // Removable
+ m_aDefaultProperties.insert( MyProperty( true,
+ IsRemoveable,
+ -1 ,
+ cppu::UnoType<sal_Bool>::get(),
+ uno::Any(),
+ beans::PropertyState_DEFAULT_VALUE,
+ beans::PropertyAttribute::MAYBEVOID
+ | beans::PropertyAttribute::BOUND
+ | beans::PropertyAttribute::READONLY ) );
+
+ // Remote
+ m_aDefaultProperties.insert( MyProperty( true,
+ IsRemote,
+ -1 ,
+ cppu::UnoType<sal_Bool>::get(),
+ uno::Any(),
+ beans::PropertyState_DEFAULT_VALUE,
+ beans::PropertyAttribute::MAYBEVOID
+ | beans::PropertyAttribute::BOUND
+ | beans::PropertyAttribute::READONLY ) );
+
+ // CompactDisc
+ m_aDefaultProperties.insert( MyProperty( true,
+ IsCompactDisc,
+ -1 ,
+ cppu::UnoType<sal_Bool>::get(),
+ uno::Any(),
+ beans::PropertyState_DEFAULT_VALUE,
+ beans::PropertyAttribute::MAYBEVOID
+ | beans::PropertyAttribute::BOUND
+ | beans::PropertyAttribute::READONLY ) );
+
+ // Floppy
+ m_aDefaultProperties.insert( MyProperty( true,
+ IsFloppy,
+ -1 ,
+ cppu::UnoType<sal_Bool>::get(),
+ uno::Any(),
+ beans::PropertyState_DEFAULT_VALUE,
+ beans::PropertyAttribute::MAYBEVOID
+ | beans::PropertyAttribute::BOUND
+ | beans::PropertyAttribute::READONLY ) );
+
+ // Hidden
+ m_aDefaultProperties.insert(
+ MyProperty(
+ true,
+ IsHidden,
+ -1 ,
+ cppu::UnoType<sal_Bool>::get(),
+ uno::Any(),
+ beans::PropertyState_DEFAULT_VALUE,
+ beans::PropertyAttribute::MAYBEVOID
+ | beans::PropertyAttribute::BOUND
+#if defined(_WIN32)
+ ));
+#else
+ | beans::PropertyAttribute::READONLY)); // under unix/linux only readable
+#endif
+
+
+ // ContentType
+ m_aDefaultProperties.insert( MyProperty( false,
+ ContentType,
+ -1 ,
+ cppu::UnoType<OUString>::get(),
+ uno::Any(OUString()),
+ beans::PropertyState_DEFAULT_VALUE,
+ beans::PropertyAttribute::MAYBEVOID
+ | beans::PropertyAttribute::BOUND
+ | beans::PropertyAttribute::READONLY ) );
+
+
+ // DateModified
+ m_aDefaultProperties.insert( MyProperty( true,
+ DateModified,
+ -1 ,
+ cppu::UnoType<util::DateTime>::get(),
+ uno::Any(),
+ beans::PropertyState_DEFAULT_VALUE,
+ beans::PropertyAttribute::MAYBEVOID
+ | beans::PropertyAttribute::BOUND ) );
+
+ // Size
+ m_aDefaultProperties.insert( MyProperty( true,
+ Size,
+ -1,
+ cppu::UnoType<sal_Int64>::get(),
+ uno::Any(),
+ beans::PropertyState_DEFAULT_VALUE,
+ beans::PropertyAttribute::MAYBEVOID
+ | beans::PropertyAttribute::BOUND ) );
+
+ // IsReadOnly
+ m_aDefaultProperties.insert( MyProperty( true,
+ IsReadOnly,
+ -1 ,
+ cppu::UnoType<sal_Bool>::get(),
+ uno::Any(),
+ beans::PropertyState_DEFAULT_VALUE,
+ beans::PropertyAttribute::MAYBEVOID
+ | beans::PropertyAttribute::BOUND ) );
+
+
+ // CreatableContentsInfo
+ m_aDefaultProperties.insert( MyProperty( true,
+ CreatableContentsInfo,
+ -1 ,
+ cppu::UnoType<uno::Sequence< ucb::ContentInfo >>::get(),
+ uno::Any(),
+ beans::PropertyState_DEFAULT_VALUE,
+ beans::PropertyAttribute::MAYBEVOID
+ | beans::PropertyAttribute::BOUND
+ | beans::PropertyAttribute::READONLY ) );
+
+ if(bWithConfig)
+ {
+ uno::Reference< XPropertySetRegistryFactory > xRegFac = ucb::Store::create( m_xContext );
+ // Open/create a registry
+ m_xFileRegistry = xRegFac->createPropertySetRegistry( OUString() );
+ }
+}
+
+
+TaskManager::~TaskManager()
+{
+}
+
+
+void
+TaskManager::startTask(
+ sal_Int32 CommandId,
+ const uno::Reference< XCommandEnvironment >& xCommandEnv )
+{
+ std::unique_lock aGuard( m_aMutex );
+ TaskMap::iterator it = m_aTaskMap.find( CommandId );
+ if( it != m_aTaskMap.end() )
+ {
+ throw DuplicateCommandIdentifierException( OSL_LOG_PREFIX );
+ }
+ m_aTaskMap.emplace( CommandId, TaskHandling( xCommandEnv ));
+}
+
+
+void
+TaskManager::endTask( sal_Int32 CommandId,
+ const OUString& aUncPath,
+ BaseContent* pContent)
+{
+ std::unique_lock aGuard( m_aMutex );
+ TaskMap::iterator it = m_aTaskMap.find( CommandId );
+ if( it == m_aTaskMap.end() )
+ return;
+
+ sal_Int32 ErrorCode = it->second.getInstalledError();
+ sal_Int32 MinorCode = it->second.getMinorErrorCode();
+ bool isHandled = it->second.isHandled();
+
+ Reference< XCommandEnvironment > xComEnv
+ = it->second.getCommandEnvironment();
+
+ m_aTaskMap.erase( it );
+
+ aGuard.unlock();
+
+ if( ErrorCode != TASKHANDLER_NO_ERROR )
+ throw_handler(
+ ErrorCode,
+ MinorCode,
+ xComEnv,
+ aUncPath,
+ pContent,
+ isHandled);
+}
+
+
+void TaskManager::clearError( sal_Int32 CommandId )
+{
+ std::unique_lock aGuard( m_aMutex );
+ TaskMap::iterator it = m_aTaskMap.find( CommandId );
+ if( it != m_aTaskMap.end() )
+ it->second.clearError();
+}
+
+
+void TaskManager::retrieveError( sal_Int32 CommandId,
+ sal_Int32 &ErrorCode,
+ sal_Int32 &minorCode)
+{
+ std::unique_lock aGuard( m_aMutex );
+ TaskMap::iterator it = m_aTaskMap.find( CommandId );
+ if( it != m_aTaskMap.end() )
+ {
+ ErrorCode = it->second.getInstalledError();
+ minorCode = it->second. getMinorErrorCode();
+ }
+}
+
+
+void TaskManager::installError( sal_Int32 CommandId,
+ sal_Int32 ErrorCode,
+ sal_Int32 MinorCode )
+{
+ std::unique_lock aGuard( m_aMutex );
+ TaskMap::iterator it = m_aTaskMap.find( CommandId );
+ if( it != m_aTaskMap.end() )
+ it->second.installError( ErrorCode,MinorCode );
+}
+
+
+sal_Int32
+TaskManager::getCommandId()
+{
+ std::unique_lock aGuard( m_aMutex );
+ return ++m_nCommandId;
+}
+
+
+void TaskManager::handleTask(
+ sal_Int32 CommandId,
+ const uno::Reference< task::XInteractionRequest >& request )
+{
+ std::unique_lock aGuard( m_aMutex );
+ TaskMap::iterator it = m_aTaskMap.find( CommandId );
+ uno::Reference< task::XInteractionHandler > xInt;
+ if( it != m_aTaskMap.end() )
+ {
+ xInt = it->second.getInteractionHandler();
+ if( xInt.is() )
+ xInt->handle( request );
+ it->second.setHandled();
+ }
+}
+
+/*********************************************************************************/
+/* */
+/* de/registerNotifier-Implementation */
+/* */
+/*********************************************************************************/
+
+
+// This two methods register and deregister a change listener for the content belonging
+// to URL aUnqPath
+
+
+void
+TaskManager::registerNotifier( const OUString& aUnqPath, Notifier* pNotifier )
+{
+ std::unique_lock aGuard( m_aMutex );
+
+ ContentMap::iterator it =
+ m_aContent.emplace( aUnqPath, UnqPathData() ).first;
+
+ std::vector< Notifier* >& nlist = it->second.notifier;
+
+ std::vector<Notifier*>::iterator it1 = std::find(nlist.begin(), nlist.end(), pNotifier);
+ if( it1 != nlist.end() ) // Every "Notifier" only once
+ {
+ return;
+ }
+ nlist.push_back( pNotifier );
+}
+
+
+void
+TaskManager::deregisterNotifier( const OUString& aUnqPath,Notifier* pNotifier )
+{
+ std::unique_lock aGuard( m_aMutex );
+
+ ContentMap::iterator it = m_aContent.find( aUnqPath );
+ if( it == m_aContent.end() )
+ return;
+
+ std::erase(it->second.notifier, pNotifier);
+
+ if( it->second.notifier.empty() )
+ m_aContent.erase( it );
+}
+
+
+/*********************************************************************************/
+/* */
+/* de/associate-Implementation */
+/* */
+/*********************************************************************************/
+
+// Used to associate and deassociate a new property with
+// the content belonging to URL UnqPath.
+// The default value and the attributes are input
+
+
+void
+TaskManager::associate( const OUString& aUnqPath,
+ const OUString& PropertyName,
+ const uno::Any& DefaultValue,
+ const sal_Int16 Attributes )
+{
+ MyProperty newProperty( false,
+ PropertyName,
+ -1,
+ DefaultValue.getValueType(),
+ DefaultValue,
+ beans::PropertyState_DEFAULT_VALUE,
+ Attributes );
+
+ auto it1 = m_aDefaultProperties.find( newProperty );
+ if( it1 != m_aDefaultProperties.end() )
+ throw beans::PropertyExistException( THROW_WHERE );
+
+ {
+ std::unique_lock aGuard( m_aMutex );
+
+ ContentMap::iterator it = m_aContent.emplace( aUnqPath,UnqPathData() ).first;
+
+ // Load the XPersistentPropertySetInfo and create it, if it does not exist
+ load( it,true );
+
+ PropertySet& properties = it->second.properties;
+ it1 = properties.find( newProperty );
+ if( it1 != properties.end() )
+ throw beans::PropertyExistException(THROW_WHERE );
+
+ // Property does not exist
+ properties.insert( newProperty );
+ it->second.xC->addProperty( PropertyName,Attributes,DefaultValue );
+ }
+ notifyPropertyAdded( getPropertySetListeners( aUnqPath ), PropertyName );
+}
+
+
+void
+TaskManager::deassociate( const OUString& aUnqPath,
+ const OUString& PropertyName )
+{
+ MyProperty oldProperty( PropertyName );
+
+ auto it1 = m_aDefaultProperties.find( oldProperty );
+ if( it1 != m_aDefaultProperties.end() )
+ throw beans::NotRemoveableException( THROW_WHERE );
+
+ std::unique_lock aGuard( m_aMutex );
+
+ ContentMap::iterator it = m_aContent.emplace( aUnqPath,UnqPathData() ).first;
+
+ load( it, false );
+
+ PropertySet& properties = it->second.properties;
+
+ it1 = properties.find( oldProperty );
+ if( it1 == properties.end() )
+ throw beans::UnknownPropertyException( PropertyName );
+
+ properties.erase( it1 );
+
+ if( it->second.xC.is() )
+ it->second.xC->removeProperty( PropertyName );
+
+ if( properties.size() == 9 )
+ {
+ MyProperty ContentTProperty( ContentType );
+
+ if( properties.find( ContentTProperty )->getState() == beans::PropertyState_DEFAULT_VALUE )
+ {
+ it->second.xS = nullptr;
+ it->second.xC = nullptr;
+ it->second.xA = nullptr;
+ if(m_xFileRegistry.is())
+ m_xFileRegistry->removePropertySet( aUnqPath );
+ }
+ }
+ aGuard.unlock();
+ notifyPropertyRemoved( getPropertySetListeners( aUnqPath ), PropertyName );
+}
+
+
+/*********************************************************************************/
+/* */
+/* page-Implementation */
+/* */
+/*********************************************************************************/
+
+// Given an xOutputStream, this method writes the content of the file belonging to
+// URL aUnqPath into the XOutputStream
+
+
+void TaskManager::page( sal_Int32 CommandId,
+ const OUString& aUnqPath,
+ const uno::Reference< io::XOutputStream >& xOutputStream )
+{
+ osl::File aFile( aUnqPath );
+ osl::FileBase::RC err = aFile.open( osl_File_OpenFlag_Read );
+
+ if( err != osl::FileBase::E_None )
+ {
+ aFile.close();
+ installError( CommandId,
+ TASKHANDLING_OPEN_FILE_FOR_PAGING,
+ err );
+ return;
+ }
+
+ const sal_uInt64 bfz = 4*1024;
+ sal_Int8 BFF[bfz];
+ sal_uInt64 nrc; // Retrieved number of Bytes;
+
+ do
+ {
+ err = aFile.read( static_cast<void*>(BFF),bfz,nrc );
+ if( err == osl::FileBase::E_None )
+ {
+ // coverity[overrun-buffer-arg : FALSE] - coverity has difficulty with css::uno::Sequence
+ uno::Sequence< sal_Int8 > seq( BFF, static_cast<sal_uInt32>(nrc) );
+ try
+ {
+ xOutputStream->writeBytes( seq );
+ }
+ catch (const io::NotConnectedException&)
+ {
+ installError( CommandId,
+ TASKHANDLING_NOTCONNECTED_FOR_PAGING );
+ break;
+ }
+ catch (const io::BufferSizeExceededException&)
+ {
+ installError( CommandId,
+ TASKHANDLING_BUFFERSIZEEXCEEDED_FOR_PAGING );
+ break;
+ }
+ catch (const io::IOException&)
+ {
+ installError( CommandId,
+ TASKHANDLING_IOEXCEPTION_FOR_PAGING );
+ break;
+ }
+ }
+ else
+ {
+ installError( CommandId,
+ TASKHANDLING_READING_FILE_FOR_PAGING,
+ err );
+ break;
+ }
+ } while( nrc == bfz );
+
+
+ aFile.close();
+
+
+ try
+ {
+ xOutputStream->closeOutput();
+ }
+ catch (const io::NotConnectedException&)
+ {
+ }
+ catch (const io::BufferSizeExceededException&)
+ {
+ }
+ catch (const io::IOException&)
+ {
+ }
+}
+
+
+/*********************************************************************************/
+/* */
+/* open-Implementation */
+/* */
+/*********************************************************************************/
+
+// Given a file URL aUnqPath, this methods returns a XInputStream which reads from the open file.
+
+
+uno::Reference< io::XInputStream >
+TaskManager::open( sal_Int32 CommandId,
+ const OUString& aUnqPath,
+ bool bLock )
+{
+ rtl::Reference<XInputStream_impl> pInputStream(new XInputStream_impl( aUnqPath, bLock )); // from filinpstr.hxx
+
+ sal_Int32 ErrorCode = pInputStream->CtorSuccess();
+
+ if( ErrorCode != TASKHANDLER_NO_ERROR )
+ {
+ installError( CommandId,
+ ErrorCode,
+ pInputStream->getMinorError() );
+
+ pInputStream.clear();
+ }
+
+ return pInputStream;
+}
+
+
+/*********************************************************************************/
+/* */
+/* open for read/write access-Implementation */
+/* */
+/*********************************************************************************/
+
+// Given a file URL aUnqPath, this methods returns a XStream which can be used
+// to read and write from/to the file.
+
+
+uno::Reference< io::XStream >
+TaskManager::open_rw( sal_Int32 CommandId,
+ const OUString& aUnqPath,
+ bool bLock )
+{
+ rtl::Reference<XStream_impl> pStream(new XStream_impl( aUnqPath, bLock )); // from filstr.hxx
+
+ sal_Int32 ErrorCode = pStream->CtorSuccess();
+
+ if( ErrorCode != TASKHANDLER_NO_ERROR )
+ {
+ installError( CommandId,
+ ErrorCode,
+ pStream->getMinorError() );
+
+ pStream.clear();
+ }
+ return pStream;
+}
+
+
+/*********************************************************************************/
+/* */
+/* ls-Implementation */
+/* */
+/*********************************************************************************/
+
+// This method returns the result set containing the children of the directory belonging
+// to file URL aUnqPath
+
+
+uno::Reference< XDynamicResultSet >
+TaskManager::ls( sal_Int32 CommandId,
+ const OUString& aUnqPath,
+ const sal_Int32 OpenMode,
+ const uno::Sequence< beans::Property >& seq,
+ const uno::Sequence< NumberedSortingInfo >& seqSort )
+{
+ rtl::Reference<XResultSet_impl> p(new XResultSet_impl( this,aUnqPath,OpenMode,seq,seqSort ));
+
+ sal_Int32 ErrorCode = p->CtorSuccess();
+
+ if( ErrorCode != TASKHANDLER_NO_ERROR )
+ {
+ installError( CommandId,
+ ErrorCode,
+ p->getMinorError() );
+
+ p.clear();
+ }
+
+ return p;
+}
+
+
+/*********************************************************************************/
+/* */
+/* info_c implementation */
+/* */
+/*********************************************************************************/
+// Info for commands
+
+uno::Reference< XCommandInfo >
+TaskManager::info_c()
+{
+ return new XCommandInfo_impl( this );
+}
+
+
+/*********************************************************************************/
+/* */
+/* info_p-Implementation */
+/* */
+/*********************************************************************************/
+// Info for the properties
+
+uno::Reference< beans::XPropertySetInfo >
+TaskManager::info_p( const OUString& aUnqPath )
+{
+ std::unique_lock aGuard( m_aMutex );
+ return new XPropertySetInfo_impl( this,aUnqPath );
+}
+
+
+/*********************************************************************************/
+/* */
+/* setv-Implementation */
+/* */
+/*********************************************************************************/
+
+// Sets the values of the properties belonging to fileURL aUnqPath
+
+
+uno::Sequence< uno::Any >
+TaskManager::setv( const OUString& aUnqPath,
+ const uno::Sequence< beans::PropertyValue >& values )
+{
+ std::unique_lock aGuard( m_aMutex );
+
+ sal_Int32 propChanged = 0;
+ uno::Sequence< uno::Any > ret( values.getLength() );
+ auto retRange = asNonConstRange(ret);
+ uno::Sequence< beans::PropertyChangeEvent > seqChanged( values.getLength() );
+ auto seqChangedRange = asNonConstRange(seqChanged);
+
+ TaskManager::ContentMap::iterator it = m_aContent.find( aUnqPath );
+ PropertySet& properties = it->second.properties;
+ TaskManager::PropertySet::const_iterator it1;
+ uno::Any aAny;
+
+ for( sal_Int32 i = 0; i < values.getLength(); ++i )
+ {
+ MyProperty toset( values[i].Name );
+ it1 = properties.find( toset );
+ if( it1 == properties.end() )
+ {
+ retRange[i] <<= beans::UnknownPropertyException( THROW_WHERE );
+ continue;
+ }
+
+ aAny = it1->getValue();
+ if( aAny == values[i].Value )
+ continue; // nothing needs to be changed
+
+ if( it1->getAttributes() & beans::PropertyAttribute::READONLY )
+ {
+ retRange[i] <<= lang::IllegalAccessException( THROW_WHERE );
+ continue;
+ }
+
+ seqChangedRange[ propChanged ].PropertyName = values[i].Name;
+ seqChangedRange[ propChanged ].PropertyHandle = -1;
+ seqChangedRange[ propChanged ].Further = false;
+ seqChangedRange[ propChanged ].OldValue = aAny;
+ seqChangedRange[ propChanged++ ].NewValue = values[i].Value;
+
+ it1->setValue( values[i].Value ); // Put the new value into the local cash
+
+ if( ! it1->IsNative() )
+ {
+ // Also put logical properties into storage
+ if( !it->second.xS.is() )
+ load( it, true );
+
+ if( ( values[i].Name == ContentType ) &&
+ it1->getState() == beans::PropertyState_DEFAULT_VALUE )
+ { // Special logic for ContentType
+ // 09.07.01: Not reached anymore, because ContentType is readonly
+ it1->setState( beans::PropertyState_DIRECT_VALUE );
+ it->second.xC->addProperty( values[i].Name,
+ beans::PropertyAttribute::MAYBEVOID,
+ values[i].Value );
+ }
+
+ try
+ {
+ it->second.xS->setPropertyValue( values[i].Name,values[i].Value );
+ }
+ catch (const uno::Exception&e)
+ {
+ --propChanged; // unsuccessful setting
+ retRange[i] <<= e;
+ }
+ }
+ else
+ {
+ // native properties
+ // Setting of physical file properties
+ if( values[i].Name == Size )
+ {
+ sal_Int64 newSize = 0;
+ if( values[i].Value >>= newSize )
+ { // valid value for the size
+ osl::File aFile(aUnqPath);
+ bool err =
+ aFile.open(osl_File_OpenFlag_Write) != osl::FileBase::E_None ||
+ aFile.setSize(sal_uInt64(newSize)) != osl::FileBase::E_None ||
+ aFile.close() != osl::FileBase::E_None;
+
+ if( err )
+ {
+ --propChanged; // unsuccessful setting
+ uno::Sequence<uno::Any> names(comphelper::InitAnyPropertySequence(
+ {
+ {"Uri", uno::Any(aUnqPath)}
+ }));
+ retRange[i] <<= InteractiveAugmentedIOException(
+ OUString(),
+ nullptr,
+ task::InteractionClassification_ERROR,
+ IOErrorCode_GENERAL,
+ names );
+ }
+ }
+ else
+ retRange[i] <<= beans::IllegalTypeException( THROW_WHERE );
+ }
+ else if(values[i].Name == IsReadOnly ||
+ values[i].Name == IsHidden)
+ {
+ bool value = false;
+ if( values[i].Value >>= value )
+ {
+ osl::DirectoryItem aDirItem;
+ osl::FileBase::RC err =
+ osl::DirectoryItem::get(aUnqPath,aDirItem);
+ sal_uInt64 nAttributes(0);
+ if(err == osl::FileBase::E_None)
+ {
+ osl::FileStatus aFileStatus(osl_FileStatus_Mask_Attributes);
+ err = aDirItem.getFileStatus(aFileStatus);
+ if(err == osl::FileBase::E_None &&
+ aFileStatus.isValid(osl_FileStatus_Mask_Attributes))
+ nAttributes = aFileStatus.getAttributes();
+ }
+ // now we have the attributes provided all went well.
+ if(err == osl::FileBase::E_None) {
+ if(values[i].Name == IsReadOnly)
+ {
+ nAttributes &= ~(osl_File_Attribute_OwnWrite |
+ osl_File_Attribute_GrpWrite |
+ osl_File_Attribute_OthWrite |
+ osl_File_Attribute_ReadOnly);
+ if(value)
+ nAttributes |= osl_File_Attribute_ReadOnly;
+ else
+ nAttributes |= (
+ osl_File_Attribute_OwnWrite |
+ osl_File_Attribute_GrpWrite |
+ osl_File_Attribute_OthWrite);
+ }
+ else if(values[i].Name == IsHidden)
+ {
+ nAttributes &= ~(osl_File_Attribute_Hidden);
+ if(value)
+ nAttributes |= osl_File_Attribute_Hidden;
+ }
+ err = osl::File::setAttributes(
+ aUnqPath,nAttributes);
+ }
+
+ if( err != osl::FileBase::E_None )
+ {
+ --propChanged; // unsuccessful setting
+ uno::Sequence<uno::Any> names(comphelper::InitAnyPropertySequence(
+ {
+ {"Uri", uno::Any(aUnqPath)}
+ }));
+ IOErrorCode ioError;
+ switch( err )
+ {
+ case osl::FileBase::E_NOMEM:
+ // not enough memory for allocating structures <br>
+ ioError = IOErrorCode_OUT_OF_MEMORY;
+ break;
+ case osl::FileBase::E_INVAL:
+ // the format of the parameters was not valid<p>
+ ioError = IOErrorCode_INVALID_PARAMETER;
+ break;
+ case osl::FileBase::E_NAMETOOLONG:
+ // File name too long<br>
+ ioError = IOErrorCode_NAME_TOO_LONG;
+ break;
+ case osl::FileBase::E_NOENT:
+ // No such file or directory<br>
+ case osl::FileBase::E_NOLINK:
+ // Link has been severed<br>
+ ioError = IOErrorCode_NOT_EXISTING;
+ break;
+ case osl::FileBase::E_ROFS:
+ // #i4735# handle ROFS transparently
+ // as ACCESS_DENIED
+ case osl::FileBase::E_PERM:
+ case osl::FileBase::E_ACCES:
+ // permission denied<br>
+ ioError = IOErrorCode_ACCESS_DENIED;
+ break;
+ case osl::FileBase::E_LOOP:
+ // Too many symbolic links encountered<br>
+ case osl::FileBase::E_FAULT:
+ // Bad address<br>
+ case osl::FileBase::E_IO:
+ // I/O error<br>
+ case osl::FileBase::E_NOSYS:
+ // Function not implemented<br>
+ case osl::FileBase::E_MULTIHOP:
+ // Multihop attempted<br>
+ case osl::FileBase::E_INTR:
+ // function call was interrupted<p>
+ default:
+ ioError = IOErrorCode_GENERAL;
+ break;
+ }
+ retRange[i] <<= InteractiveAugmentedIOException(
+ OUString(),
+ nullptr,
+ task::InteractionClassification_ERROR,
+ ioError,
+ names );
+ }
+ }
+ else
+ retRange[i] <<= beans::IllegalTypeException( THROW_WHERE );
+ }
+ }
+ } // end for
+
+ aGuard.unlock();
+ if( propChanged )
+ {
+ seqChanged.realloc( propChanged );
+ notifyPropertyChanges( getPropertyChangeNotifier( aUnqPath ), seqChanged );
+ }
+
+ return ret;
+}
+
+/*********************************************************************************/
+/* */
+/* getv-Implementation */
+/* */
+/*********************************************************************************/
+
+// Reads the values of the properties belonging to fileURL aUnqPath;
+// Returns an XRow object containing the values in the requested order.
+
+
+uno::Reference< sdbc::XRow >
+TaskManager::getv( sal_Int32 CommandId,
+ const OUString& aUnqPath,
+ const uno::Sequence< beans::Property >& properties )
+{
+ uno::Sequence< uno::Any > seq( properties.getLength() );
+
+ sal_Int32 n_Mask;
+ getMaskFromProperties( n_Mask,properties );
+ osl::FileStatus aFileStatus( n_Mask );
+
+ osl::DirectoryItem aDirItem;
+ osl::FileBase::RC nError1 = osl::DirectoryItem::get( aUnqPath,aDirItem );
+ if( nError1 != osl::FileBase::E_None )
+ installError(CommandId,
+ TASKHANDLING_OPEN_FILE_FOR_PAGING, // BEAWARE, REUSED
+ nError1);
+
+ osl::FileBase::RC nError2 = aDirItem.getFileStatus( aFileStatus );
+ if( nError1 == osl::FileBase::E_None &&
+ nError2 != osl::FileBase::E_None )
+ installError(CommandId,
+ TASKHANDLING_OPEN_FILE_FOR_PAGING, // BEAWARE, REUSED
+ nError2);
+
+ {
+ std::unique_lock aGuard( m_aMutex );
+
+ TaskManager::ContentMap::iterator it = m_aContent.find( aUnqPath );
+ commit( aGuard, it, aFileStatus );
+
+ PropertySet& propset = it->second.properties;
+
+ std::transform(properties.begin(), properties.end(), seq.getArray(),
+ [&propset](const beans::Property& rProp) -> uno::Any {
+ MyProperty readProp( rProp.Name );
+ auto it1 = propset.find( readProp );
+ if( it1 == propset.end() )
+ return uno::Any();
+ return it1->getValue();
+ });
+ }
+
+ return new XRow_impl( this,seq );
+}
+
+
+/********************************************************************************/
+/* */
+/* transfer-commandos */
+/* */
+/********************************************************************************/
+
+
+/********************************************************************************/
+/* */
+/* move-implementation */
+/* */
+/********************************************************************************/
+
+// Moves the content belonging to fileURL srcUnqPath to fileURL dstUnqPath.
+
+
+void
+TaskManager::move( sal_Int32 CommandId,
+ const OUString& srcUnqPath,
+ const OUString& dstUnqPathIn,
+ const sal_Int32 NameClash )
+{
+ // --> #i88446# Method notifyContentExchanged( getContentExchangedEventListeners( srcUnqPath,dstUnqPath,!isDocument ) ); crashes if
+ // srcUnqPath and dstUnqPathIn are equal
+ if( srcUnqPath == dstUnqPathIn )
+ return;
+
+ osl::FileBase::RC nError;
+ OUString dstUnqPath( dstUnqPathIn );
+
+ switch( NameClash )
+ {
+ case NameClash::KEEP:
+ {
+ nError = osl_File_move( srcUnqPath,dstUnqPath,true );
+ if( nError != osl::FileBase::E_None && nError != osl::FileBase::E_EXIST )
+ {
+ installError( CommandId,
+ TASKHANDLING_KEEPERROR_FOR_MOVE,
+ nError );
+ return;
+ }
+ break;
+ }
+ case NameClash::OVERWRITE:
+ {
+ // stat to determine whether we have a symlink
+ OUString targetPath(dstUnqPath);
+
+ osl::FileStatus aStatus(osl_FileStatus_Mask_Type|osl_FileStatus_Mask_LinkTargetURL);
+ osl::DirectoryItem aItem;
+ (void)osl::DirectoryItem::get(dstUnqPath,aItem);
+ (void)aItem.getFileStatus(aStatus);
+
+ if( aStatus.isValid(osl_FileStatus_Mask_Type) &&
+ aStatus.isValid(osl_FileStatus_Mask_LinkTargetURL) &&
+ aStatus.getFileType() == osl::FileStatus::Link )
+ targetPath = aStatus.getLinkTargetURL();
+
+ // Will do nothing if file does not exist.
+ osl::File::remove( targetPath );
+
+ nError = osl_File_move( srcUnqPath,targetPath );
+ if( nError != osl::FileBase::E_None )
+ {
+ installError( CommandId,
+ TASKHANDLING_OVERWRITE_FOR_MOVE,
+ nError );
+ return;
+ }
+ break;
+ }
+ case NameClash::RENAME:
+ {
+ OUString newDstUnqPath;
+ nError = osl_File_move( srcUnqPath,dstUnqPath,true );
+ if( nError == osl::FileBase::E_EXIST )
+ {
+ // "invent" a new valid title.
+
+ sal_Int32 nPos = -1;
+ sal_Int32 nLastDot = dstUnqPath.lastIndexOf( '.' );
+ sal_Int32 nLastSlash = dstUnqPath.lastIndexOf( '/' );
+ if( ( nLastSlash < nLastDot ) // dot is part of last(!) path segment
+ && ( nLastSlash != ( nLastDot - 1 ) ) ) // file name does not start with a dot
+ nPos = nLastDot;
+ else
+ nPos = dstUnqPath.getLength();
+
+ sal_Int32 nTry = 0;
+
+ do
+ {
+ newDstUnqPath = dstUnqPath;
+
+ OUString aPostfix = "_" + OUString::number( ++nTry );
+
+ newDstUnqPath = newDstUnqPath.replaceAt( nPos, 0, aPostfix );
+
+ nError = osl_File_move( srcUnqPath,newDstUnqPath,true );
+ }
+ while( ( nError == osl::FileBase::E_EXIST ) && ( nTry < 10000 ) );
+ }
+
+ if( nError == osl::FileBase::E_EXIST )
+ {
+ installError( CommandId,
+ TASKHANDLING_RENAME_FOR_MOVE );
+ return;
+ }
+ else if( nError != osl::FileBase::E_None )
+ {
+ installError( CommandId,
+ TASKHANDLING_RENAMEMOVE_FOR_MOVE,
+ nError );
+ return;
+ }
+ else
+ dstUnqPath = newDstUnqPath;
+
+ break;
+ }
+ case NameClash::ERROR:
+ {
+ nError = osl_File_move( srcUnqPath,dstUnqPath,true );
+ if( nError == osl::FileBase::E_EXIST )
+ {
+ installError( CommandId,
+ TASKHANDLING_NAMECLASH_FOR_MOVE );
+ return;
+ }
+ else if( nError != osl::FileBase::E_None )
+ {
+ installError( CommandId,
+ TASKHANDLING_NAMECLASHMOVE_FOR_MOVE,
+ nError );
+ return;
+ }
+ break;
+ }
+ case NameClash::ASK:
+ default:
+ {
+ nError = osl_File_move( srcUnqPath,dstUnqPath,true );
+ if( nError == osl::FileBase::E_EXIST )
+ {
+ installError( CommandId,
+ TASKHANDLING_NAMECLASHSUPPORT_FOR_MOVE,
+ NameClash::ASK);
+ return;
+ }
+ }
+ break;
+ }
+
+ // Determine, whether we have moved a file or a folder
+ osl::DirectoryItem aItem;
+ nError = osl::DirectoryItem::get( dstUnqPath,aItem );
+ if( nError != osl::FileBase::E_None )
+ {
+ installError( CommandId,
+ TASKHANDLING_TRANSFER_BY_MOVE_SOURCE,
+ nError );
+ return;
+ }
+ osl::FileStatus aStatus( osl_FileStatus_Mask_Type );
+ nError = aItem.getFileStatus( aStatus );
+ if( nError != osl::FileBase::E_None || ! aStatus.isValid( osl_FileStatus_Mask_Type ) )
+ {
+ installError( CommandId,
+ TASKHANDLING_TRANSFER_BY_MOVE_SOURCESTAT,
+ nError );
+ return;
+ }
+ bool isDocument = ( aStatus.getFileType() == osl::FileStatus::Regular );
+
+
+ copyPersistentSet( srcUnqPath,dstUnqPath,!isDocument );
+
+ OUString aDstParent = getParentName( dstUnqPath );
+ OUString aSrcParent = getParentName( srcUnqPath );
+
+ notifyInsert( getContentEventListeners( aDstParent ),dstUnqPath );
+ if( aDstParent != aSrcParent )
+ notifyContentRemoved( getContentEventListeners( aSrcParent ),srcUnqPath );
+
+ notifyContentExchanged( getContentExchangedEventListeners( srcUnqPath,dstUnqPath,!isDocument ) );
+ erasePersistentSet( srcUnqPath,!isDocument );
+}
+
+
+/********************************************************************************/
+/* */
+/* copy-implementation */
+/* */
+/********************************************************************************/
+
+// Copies the content belonging to fileURL srcUnqPath to fileURL dstUnqPath ( files and directories )
+
+
+namespace {
+
+bool getType(
+ TaskManager & task, sal_Int32 id, OUString const & fileUrl,
+ osl::DirectoryItem * item, osl::FileStatus::Type * type)
+{
+ OSL_ASSERT(item != nullptr && type != nullptr);
+ osl::FileBase::RC err = osl::DirectoryItem::get(fileUrl, *item);
+ if (err != osl::FileBase::E_None) {
+ task.installError(id, TASKHANDLING_TRANSFER_BY_COPY_SOURCE, err);
+ return false;
+ }
+ osl::FileStatus stat(osl_FileStatus_Mask_Type);
+ err = item->getFileStatus(stat);
+ if (err != osl::FileBase::E_None) {
+ task.installError(id, TASKHANDLING_TRANSFER_BY_COPY_SOURCESTAT, err);
+ return false;
+ }
+ *type = stat.getFileType();
+ return true;
+}
+
+}
+
+void
+TaskManager::copy(
+ sal_Int32 CommandId,
+ const OUString& srcUnqPath,
+ const OUString& dstUnqPathIn,
+ sal_Int32 NameClash )
+{
+ osl::FileBase::RC nError;
+ OUString dstUnqPath( dstUnqPathIn );
+
+ // Resolve symbolic links within the source path. If srcUnqPath denotes a
+ // symbolic link (targeting either a file or a folder), the contents of the
+ // target is copied (recursively, in the case of a folder). However, if
+ // recursively copying the contents of a folder causes a symbolic link to be
+ // copied, the symbolic link itself is copied.
+ osl::DirectoryItem item;
+ osl::FileStatus::Type type;
+ if (!getType(*this, CommandId, srcUnqPath, &item, &type)) {
+ return;
+ }
+ OUString rslvdSrcUnqPath;
+ if (type == osl::FileStatus::Link) {
+ osl::FileStatus stat(osl_FileStatus_Mask_LinkTargetURL);
+ nError = item.getFileStatus(stat);
+ if (nError != osl::FileBase::E_None) {
+ installError(
+ CommandId, TASKHANDLING_TRANSFER_BY_COPY_SOURCESTAT, nError);
+ return;
+ }
+ rslvdSrcUnqPath = stat.getLinkTargetURL();
+ if (!getType(*this, CommandId, srcUnqPath, &item, &type)) {
+ return;
+ }
+ } else {
+ rslvdSrcUnqPath = srcUnqPath;
+ }
+
+ bool isDocument
+ = type != osl::FileStatus::Directory && type != osl::FileStatus::Volume;
+ FileUrlType IsWhat = isDocument ? FileUrlType::File : FileUrlType::Folder;
+
+ switch( NameClash )
+ {
+ case NameClash::KEEP:
+ {
+ nError = copy_recursive( rslvdSrcUnqPath,dstUnqPath,IsWhat,true );
+ if( nError != osl::FileBase::E_None && nError != osl::FileBase::E_EXIST )
+ {
+ installError( CommandId,
+ TASKHANDLING_KEEPERROR_FOR_COPY,
+ nError );
+ return;
+ }
+ break;
+ }
+ case NameClash::OVERWRITE:
+ {
+ // remove (..., MustExist = sal_False).
+ remove( CommandId, dstUnqPath, IsWhat, false );
+
+ // copy.
+ nError = copy_recursive( rslvdSrcUnqPath,dstUnqPath,IsWhat,false );
+ if( nError != osl::FileBase::E_None )
+ {
+ installError( CommandId,
+ TASKHANDLING_OVERWRITE_FOR_COPY,
+ nError );
+ return;
+ }
+ break;
+ }
+ case NameClash::RENAME:
+ {
+ OUString newDstUnqPath = dstUnqPath;
+ nError = copy_recursive( rslvdSrcUnqPath,dstUnqPath,IsWhat,true );
+
+ if( nError == osl::FileBase::E_EXIST )
+ {
+ // "invent" a new valid title.
+
+ sal_Int32 nPos = -1;
+ sal_Int32 nLastDot = dstUnqPath.lastIndexOf( '.' );
+ sal_Int32 nLastSlash = dstUnqPath.lastIndexOf( '/' );
+ if ( ( nLastSlash < nLastDot ) // dot is part of last(!) path segment
+ && ( nLastSlash != ( nLastDot - 1 ) ) ) // file name does not start with a dot
+ nPos = nLastDot;
+ else
+ nPos = dstUnqPath.getLength();
+
+ sal_Int32 nTry = 0;
+
+ do
+ {
+ newDstUnqPath = dstUnqPath;
+
+ OUString aPostfix = "_" + OUString::number( ++nTry );
+
+ newDstUnqPath = newDstUnqPath.replaceAt( nPos, 0, aPostfix );
+
+ nError = copy_recursive( rslvdSrcUnqPath,newDstUnqPath,IsWhat,true );
+ }
+ while( ( nError == osl::FileBase::E_EXIST ) && ( nTry < 10000 ) );
+ }
+
+ if( nError == osl::FileBase::E_EXIST )
+ {
+ installError( CommandId,
+ TASKHANDLING_RENAME_FOR_COPY );
+ return;
+ }
+ else if( nError != osl::FileBase::E_None )
+ {
+ installError( CommandId,
+ TASKHANDLING_RENAMEMOVE_FOR_COPY,
+ nError );
+ return;
+ }
+ else
+ dstUnqPath = newDstUnqPath;
+
+ break;
+ }
+ case NameClash::ERROR:
+ {
+ nError = copy_recursive( rslvdSrcUnqPath,dstUnqPath,IsWhat,true );
+
+ if( nError == osl::FileBase::E_EXIST )
+ {
+ installError( CommandId,
+ TASKHANDLING_NAMECLASH_FOR_COPY );
+ return;
+ }
+ else if( nError != osl::FileBase::E_None )
+ {
+ installError( CommandId,
+ TASKHANDLING_NAMECLASHMOVE_FOR_COPY,
+ nError );
+ return;
+ }
+ break;
+ }
+ case NameClash::ASK:
+ default:
+ {
+ nError = copy_recursive( rslvdSrcUnqPath,dstUnqPath,IsWhat,true );
+
+ if( nError == osl::FileBase::E_EXIST )
+ {
+ installError( CommandId,
+ TASKHANDLING_NAMECLASHSUPPORT_FOR_COPY,
+ NameClash);
+ return;
+ }
+ break;
+ }
+ }
+
+ copyPersistentSet( srcUnqPath,dstUnqPath, !isDocument );
+ notifyInsert( getContentEventListeners( getParentName( dstUnqPath ) ),dstUnqPath );
+}
+
+
+/********************************************************************************/
+/* */
+/* remove-implementation */
+/* */
+/********************************************************************************/
+
+// Deletes the content belonging to fileURL aUnqPath( recursively in case of directory )
+// Return: success of operation
+
+
+bool
+TaskManager::remove( sal_Int32 CommandId,
+ const OUString& aUnqPath,
+ FileUrlType IsWhat,
+ bool MustExist )
+{
+ sal_Int32 nMask = osl_FileStatus_Mask_Type | osl_FileStatus_Mask_FileURL;
+
+ osl::DirectoryItem aItem;
+ osl::FileStatus aStatus( nMask );
+ osl::FileBase::RC nError;
+
+ if( IsWhat == FileUrlType::Unknown ) // Determine whether we are removing a directory or a file
+ {
+ nError = osl::DirectoryItem::get( aUnqPath, aItem );
+ if( nError != osl::FileBase::E_None )
+ {
+ if (MustExist)
+ {
+ installError( CommandId,
+ TASKHANDLING_NOSUCHFILEORDIR_FOR_REMOVE,
+ nError );
+ }
+ return (!MustExist);
+ }
+
+ nError = aItem.getFileStatus( aStatus );
+ if( nError != osl::FileBase::E_None || ! aStatus.isValid( nMask ) )
+ {
+ installError( CommandId,
+ TASKHANDLING_VALIDFILESTATUS_FOR_REMOVE,
+ nError != osl::FileBase::E_None ? nError : TASKHANDLER_NO_ERROR );
+ return false;
+ }
+
+ if( aStatus.getFileType() == osl::FileStatus::Regular ||
+ aStatus.getFileType() == osl::FileStatus::Link )
+ IsWhat = FileUrlType::File;
+ else if( aStatus.getFileType() == osl::FileStatus::Directory ||
+ aStatus.getFileType() == osl::FileStatus::Volume )
+ IsWhat = FileUrlType::Folder;
+ }
+
+
+ if( IsWhat == FileUrlType::File )
+ {
+ nError = osl::File::remove( aUnqPath );
+ if( nError != osl::FileBase::E_None )
+ {
+ if (MustExist)
+ {
+ installError( CommandId,
+ TASKHANDLING_DELETEFILE_FOR_REMOVE,
+ nError );
+ }
+ return (!MustExist);
+ }
+ else
+ {
+ notifyContentDeleted( getContentDeletedEventListeners(aUnqPath) );
+ erasePersistentSet( aUnqPath ); // Removes from XPersistentPropertySet
+ }
+ }
+ else if( IsWhat == FileUrlType::Folder )
+ {
+ osl::Directory aDirectory( aUnqPath );
+
+ nError = aDirectory.open();
+ if( nError != osl::FileBase::E_None )
+ {
+ if (MustExist)
+ {
+ installError( CommandId,
+ TASKHANDLING_OPENDIRECTORY_FOR_REMOVE,
+ nError );
+ }
+ return (!MustExist);
+ }
+
+ bool whileSuccess = true;
+ FileUrlType recurse = FileUrlType::Unknown;
+ OUString name;
+
+ nError = aDirectory.getNextItem( aItem );
+ while( nError == osl::FileBase::E_None )
+ {
+ nError = aItem.getFileStatus( aStatus );
+ if( nError != osl::FileBase::E_None || ! aStatus.isValid( nMask ) )
+ {
+ installError( CommandId,
+ TASKHANDLING_VALIDFILESTATUSWHILE_FOR_REMOVE,
+ nError != osl::FileBase::E_None ? nError : TASKHANDLER_NO_ERROR );
+ whileSuccess = false;
+ break;
+ }
+
+ if( aStatus.getFileType() == osl::FileStatus::Regular ||
+ aStatus.getFileType() == osl::FileStatus::Link )
+ recurse = FileUrlType::File;
+ else if( aStatus.getFileType() == osl::FileStatus::Directory ||
+ aStatus.getFileType() == osl::FileStatus::Volume )
+ recurse = FileUrlType::Folder;
+
+ name = aStatus.getFileURL();
+ whileSuccess = remove( CommandId, name, recurse, MustExist );
+ if( !whileSuccess )
+ break;
+
+ nError = aDirectory.getNextItem( aItem );
+ }
+
+ aDirectory.close();
+
+ if( ! whileSuccess )
+ return false; // error code is installed
+
+ if( nError != osl::FileBase::E_NOENT )
+ {
+ installError( CommandId,
+ TASKHANDLING_DIRECTORYEXHAUSTED_FOR_REMOVE,
+ nError );
+ return false;
+ }
+
+ nError = osl::Directory::remove( aUnqPath );
+ if( nError != osl::FileBase::E_None )
+ {
+ if (MustExist)
+ {
+ installError( CommandId,
+ TASKHANDLING_DELETEDIRECTORY_FOR_REMOVE,
+ nError );
+ }
+ return (!MustExist);
+ }
+ else
+ {
+ notifyContentDeleted( getContentDeletedEventListeners(aUnqPath) );
+ erasePersistentSet( aUnqPath );
+ }
+ }
+ else // Don't know what to remove
+ {
+ installError( CommandId,
+ TASKHANDLING_FILETYPE_FOR_REMOVE );
+ return false;
+ }
+
+ return true;
+}
+
+
+/********************************************************************************/
+/* */
+/* mkdir-implementation */
+/* */
+/********************************************************************************/
+
+// Creates new directory with given URL, recursively if necessary
+// Return:: success of operation
+
+
+bool
+TaskManager::mkdir( sal_Int32 CommandId,
+ const OUString& rUnqPath,
+ bool OverWrite )
+{
+ OUString aUnqPath;
+
+ // remove trailing slash
+ if ( rUnqPath.endsWith("/") )
+ aUnqPath = rUnqPath.copy( 0, rUnqPath.getLength() - 1 );
+ else
+ aUnqPath = rUnqPath;
+
+ osl::FileBase::RC nError = osl::Directory::create( aUnqPath );
+
+ switch ( nError )
+ {
+ case osl::FileBase::E_EXIST: // Directory cannot be overwritten
+ {
+ if( !OverWrite )
+ {
+ installError( CommandId,
+ TASKHANDLING_FOLDER_EXISTS_MKDIR );
+ return false;
+ }
+ else
+ return true;
+ }
+ case osl::FileBase::E_INVAL:
+ {
+ installError(CommandId,
+ TASKHANDLING_INVALID_NAME_MKDIR);
+ return false;
+ }
+ case osl::FileBase::E_None:
+ {
+ OUString aPrtPath = getParentName( aUnqPath );
+ notifyInsert( getContentEventListeners( aPrtPath ),aUnqPath );
+ return true;
+ }
+ default:
+ return ensuredir(
+ CommandId,
+ aUnqPath,
+ TASKHANDLING_CREATEDIRECTORY_MKDIR );
+ }
+}
+
+
+/********************************************************************************/
+/* */
+/* mkfil-implementation */
+/* */
+/********************************************************************************/
+
+// Creates new file with given URL.
+// The content of aInputStream becomes the content of the file
+// Return:: success of operation
+
+
+bool
+TaskManager::mkfil( sal_Int32 CommandId,
+ const OUString& aUnqPath,
+ bool Overwrite,
+ const uno::Reference< io::XInputStream >& aInputStream )
+{
+ // return value unimportant
+ bool bSuccess = write( CommandId,
+ aUnqPath,
+ Overwrite,
+ aInputStream );
+ if ( bSuccess )
+ {
+ OUString aPrtPath = getParentName( aUnqPath );
+ notifyInsert( getContentEventListeners( aPrtPath ),aUnqPath );
+ }
+ return bSuccess;
+}
+
+
+/********************************************************************************/
+/* */
+/* write-implementation */
+/* */
+/********************************************************************************/
+
+// writes to the file with given URL.
+// The content of aInputStream becomes the content of the file
+// Return:: success of operation
+
+
+bool
+TaskManager::write( sal_Int32 CommandId,
+ const OUString& aUnqPath,
+ bool OverWrite,
+ const uno::Reference< io::XInputStream >& aInputStream )
+{
+ if( ! aInputStream.is() )
+ {
+ installError( CommandId,
+ TASKHANDLING_INPUTSTREAM_FOR_WRITE );
+ return false;
+ }
+
+ // Create parent path, if necessary.
+ if ( ! ensuredir( CommandId,
+ getParentName( aUnqPath ),
+ TASKHANDLING_ENSUREDIR_FOR_WRITE ) )
+ return false;
+
+ osl::FileBase::RC err;
+ osl::File aFile( aUnqPath );
+
+ if( OverWrite )
+ {
+ err = aFile.open( osl_File_OpenFlag_Write | osl_File_OpenFlag_Create );
+
+ if( err != osl::FileBase::E_None )
+ {
+ aFile.close();
+ err = aFile.open( osl_File_OpenFlag_Write );
+
+ if( err != osl::FileBase::E_None )
+ {
+ installError( CommandId,
+ TASKHANDLING_NO_OPEN_FILE_FOR_OVERWRITE,
+ err );
+ return false;
+ }
+
+ // the existing file was just opened and should be overwritten now,
+ // truncate it first
+
+ err = aFile.setSize( 0 );
+ if( err != osl::FileBase::E_None )
+ {
+ installError( CommandId,
+ TASKHANDLING_FILESIZE_FOR_WRITE,
+ err );
+ return false;
+ }
+ }
+ }
+ else
+ {
+ err = aFile.open( osl_File_OpenFlag_Read | osl_File_OpenFlag_NoLock );
+ if( err == osl::FileBase::E_None ) // The file exists and shall not be overwritten
+ {
+ installError( CommandId,
+ TASKHANDLING_NOREPLACE_FOR_WRITE, // Now an exception
+ err );
+
+ aFile.close();
+ return false;
+ }
+
+ // as a temporary solution the creation does not lock the file at all
+ // in future it should be possible to create the file without lock explicitly
+ err = aFile.open( osl_File_OpenFlag_Write | osl_File_OpenFlag_Create | osl_File_OpenFlag_NoLock );
+
+ if( err != osl::FileBase::E_None )
+ {
+ aFile.close();
+ installError( CommandId,
+ TASKHANDLING_NO_OPEN_FILE_FOR_WRITE,
+ err );
+ return false;
+ }
+ }
+
+ bool bSuccess = true;
+
+ sal_uInt64 nWrittenBytes;
+ sal_Int32 nReadBytes = 0, nRequestedBytes = 32768 /*32k*/;
+ uno::Sequence< sal_Int8 > seq( nRequestedBytes );
+
+ do
+ {
+ try
+ {
+ nReadBytes = aInputStream->readBytes( seq,
+ nRequestedBytes );
+ }
+ catch( const io::NotConnectedException& )
+ {
+ installError( CommandId,
+ TASKHANDLING_NOTCONNECTED_FOR_WRITE );
+ bSuccess = false;
+ break;
+ }
+ catch( const io::BufferSizeExceededException& )
+ {
+ installError( CommandId,
+ TASKHANDLING_BUFFERSIZEEXCEEDED_FOR_WRITE );
+ bSuccess = false;
+ break;
+ }
+ catch( const io::IOException& )
+ {
+ installError( CommandId,
+ TASKHANDLING_IOEXCEPTION_FOR_WRITE );
+ bSuccess = false;
+ break;
+ }
+
+ if( nReadBytes )
+ {
+ const sal_Int8* p = seq.getConstArray();
+
+ err = aFile.write( static_cast<void const *>(p),
+ sal_uInt64( nReadBytes ),
+ nWrittenBytes );
+
+ if( err != osl::FileBase::E_None )
+ {
+ installError( CommandId,
+ TASKHANDLING_FILEIOERROR_FOR_WRITE,
+ err );
+ bSuccess = false;
+ break;
+ }
+ else if( nWrittenBytes != sal_uInt64( nReadBytes ) )
+ {
+ installError( CommandId,
+ TASKHANDLING_FILEIOERROR_FOR_NO_SPACE );
+ bSuccess = false;
+ break;
+ }
+ }
+ } while( nReadBytes == nRequestedBytes );
+
+ err = aFile.close();
+ if( err != osl::FileBase::E_None )
+ {
+ installError( CommandId,
+ TASKHANDLING_FILEIOERROR_FOR_WRITE,
+ err );
+ bSuccess = false;
+ }
+
+ return bSuccess;
+}
+
+
+/*********************************************************************************/
+/* */
+/* insertDefaultProperties-Implementation */
+/* */
+/*********************************************************************************/
+
+
+void TaskManager::insertDefaultProperties( const OUString& aUnqPath )
+{
+ std::unique_lock aGuard(m_aMutex);
+ insertDefaultProperties(aGuard, aUnqPath);
+}
+
+void TaskManager::insertDefaultProperties( std::unique_lock<std::mutex>& /*rGuard*/, const OUString& aUnqPath )
+{
+ ContentMap::iterator it =
+ m_aContent.emplace( aUnqPath,UnqPathData() ).first;
+
+ load( it, false );
+
+ MyProperty ContentTProperty( ContentType );
+
+ PropertySet& properties = it->second.properties;
+ bool ContentNotDefau = properties.find( ContentTProperty ) != properties.end();
+
+ properties.reserve(properties.size() + m_aDefaultProperties.size());
+ for (auto const& defaultprop : m_aDefaultProperties)
+ {
+ if( !ContentNotDefau || defaultprop.getPropertyName() != ContentType )
+ properties.insert( defaultprop );
+ }
+}
+
+
+/******************************************************************************/
+/* */
+/* mapping of file urls */
+/* to uncpath and vice versa */
+/* */
+/******************************************************************************/
+
+
+bool TaskManager::getUnqFromUrl( const OUString& Url, OUString& Unq )
+{
+ if ( Url == "file:///" || Url == "file://localhost/" || Url == "file://127.0.0.1/" )
+ {
+ Unq = "file:///";
+ return false;
+ }
+
+ bool err = osl::FileBase::E_None != osl::FileBase::getSystemPathFromFileURL( Url,Unq );
+
+ Unq = Url;
+
+ sal_Int32 l = Unq.getLength()-1;
+ if( ! err && Unq.endsWith("/") &&
+ Unq.indexOf( '/', RTL_CONSTASCII_LENGTH("//") ) != -1 )
+ Unq = Unq.copy(0, l);
+
+ return err;
+}
+
+
+bool TaskManager::getUrlFromUnq( const OUString& Unq,OUString& Url )
+{
+ bool err = osl::FileBase::E_None != osl::FileBase::getSystemPathFromFileURL( Unq,Url );
+
+ Url = Unq;
+
+ return err;
+}
+
+
+// Helper function for public copy
+
+osl::FileBase::RC
+TaskManager::copy_recursive( const OUString& srcUnqPath,
+ const OUString& dstUnqPath,
+ FileUrlType TypeToCopy,
+ bool testExistBeforeCopy )
+{
+ osl::FileBase::RC err = osl::FileBase::E_None;
+
+ if( TypeToCopy == FileUrlType::File ) // Document
+ {
+ err = osl_File_copy( srcUnqPath,dstUnqPath,testExistBeforeCopy );
+ }
+ else if( TypeToCopy == FileUrlType::Folder )
+ {
+ osl::Directory aDir( srcUnqPath );
+ (void)aDir.open();
+
+ err = osl::Directory::create( dstUnqPath );
+ osl::FileBase::RC next = err;
+ if( err == osl::FileBase::E_None )
+ {
+ sal_Int32 const n_Mask = osl_FileStatus_Mask_FileURL | osl_FileStatus_Mask_FileName | osl_FileStatus_Mask_Type;
+
+ osl::DirectoryItem aDirItem;
+
+ while( err == osl::FileBase::E_None )
+ {
+ next = aDir.getNextItem( aDirItem );
+ if (next != osl::FileBase::E_None )
+ break;
+ bool IsDoc = false;
+ osl::FileStatus aFileStatus( n_Mask );
+ aDirItem.getFileStatus( aFileStatus );
+ if( aFileStatus.isValid( osl_FileStatus_Mask_Type ) )
+ IsDoc = aFileStatus.getFileType() == osl::FileStatus::Regular;
+
+ // Getting the information for the next recursive copy
+ FileUrlType newTypeToCopy = IsDoc ? FileUrlType::File : FileUrlType::Folder;
+
+ OUString newSrcUnqPath;
+ if( aFileStatus.isValid( osl_FileStatus_Mask_FileURL ) )
+ newSrcUnqPath = aFileStatus.getFileURL();
+
+ OUString newDstUnqPath = dstUnqPath;
+ OUString tit;
+ if( aFileStatus.isValid( osl_FileStatus_Mask_FileName ) )
+ tit = rtl::Uri::encode( aFileStatus.getFileName(),
+ rtl_UriCharClassPchar,
+ rtl_UriEncodeIgnoreEscapes,
+ RTL_TEXTENCODING_UTF8 );
+
+ if( !newDstUnqPath.endsWith( "/" ) )
+ newDstUnqPath += "/";
+
+ newDstUnqPath += tit;
+
+ if ( newSrcUnqPath != dstUnqPath )
+ err = copy_recursive( newSrcUnqPath,newDstUnqPath,newTypeToCopy,false );
+ }
+
+ if( err == osl::FileBase::E_None && next != osl::FileBase::E_NOENT )
+ err = next;
+ }
+ aDir.close();
+ }
+
+ return err;
+}
+
+
+// Helper function for mkfil,mkdir and write
+// Creates whole path
+// returns success of the operation
+
+
+bool TaskManager::ensuredir( sal_Int32 CommandId,
+ const OUString& rUnqPath,
+ sal_Int32 errorCode )
+{
+ OUString aPath;
+
+ if ( rUnqPath.isEmpty() )
+ return false;
+
+ if ( rUnqPath.endsWith("/") )
+ aPath = rUnqPath.copy( 0, rUnqPath.getLength() - 1 );
+ else
+ aPath = rUnqPath;
+
+#if HAVE_FEATURE_MACOSX_SANDBOX
+
+ // Avoid annoying sandbox messages in the system.log from the
+ // below aDirectory.open(), which ends up calling opendir().
+ // Surely it is easier to just call stat()? Calling stat() on an
+ // arbitrary (?) directory does not seem to cause any sandbox
+ // violation, while opendir() does. (Sorry I could not be bothered
+ // to use some complex cross-platform abstraction over stat() here
+ // in this macOS specific code block.)
+
+ OUString aDirName;
+ struct stat s;
+ if( osl::FileBase::getSystemPathFromFileURL( aPath, aDirName ) == osl::FileBase::E_None &&
+ stat(OUStringToOString( aDirName, RTL_TEXTENCODING_UTF8).getStr(), &s ) == 0 &&
+ S_ISDIR( s.st_mode ) )
+ return sal_True;
+#endif
+
+ // HACK: create directory on a mount point with nobrowse option
+ // returns ENOSYS in any case !!
+ osl::Directory aDirectory( aPath );
+ osl::FileBase::RC nError = aDirectory.open();
+ aDirectory.close();
+
+ if( nError == osl::File::E_None )
+ return true;
+
+ nError = osl::Directory::create( aPath );
+
+ if( nError == osl::File::E_None )
+ notifyInsert( getContentEventListeners( getParentName( aPath ) ),aPath );
+
+ bool bSuccess = ( nError == osl::File::E_None || nError == osl::FileBase::E_EXIST );
+
+ if( ! bSuccess )
+ {
+ OUString aParentDir = getParentName( aPath );
+
+ if ( aParentDir != aPath )
+ { // Create first the parent directory
+ bSuccess = ensuredir( CommandId,
+ getParentName( aPath ),
+ errorCode );
+
+ // After parent directory structure exists try it one's more
+
+ if ( bSuccess )
+ { // Parent directory exists, retry creation of directory
+ nError = osl::Directory::create( aPath );
+
+ if( nError == osl::File::E_None )
+ notifyInsert( getContentEventListeners( getParentName( aPath ) ),aPath );
+
+ bSuccess =( nError == osl::File::E_None || nError == osl::FileBase::E_EXIST );
+ }
+ }
+ }
+
+ if( ! bSuccess )
+ installError( CommandId,
+ errorCode,
+ nError );
+
+ return bSuccess;
+}
+
+
+// Given a sequence of properties seq, this method determines the mask
+// used to instantiate an osl::FileStatus, so that a call to
+// osl::DirectoryItem::getFileStatus fills the required fields.
+
+
+void
+TaskManager::getMaskFromProperties(
+ sal_Int32& n_Mask,
+ const uno::Sequence< beans::Property >& seq )
+{
+ n_Mask = 0;
+ for(const auto& rProp : seq) {
+ if(rProp.Name == Title)
+ n_Mask |= osl_FileStatus_Mask_FileName;
+ else if(rProp.Name == CasePreservingURL)
+ n_Mask |= osl_FileStatus_Mask_FileURL;
+ else if(rProp.Name == IsDocument ||
+ rProp.Name == IsFolder ||
+ rProp.Name == IsVolume ||
+ rProp.Name == IsRemoveable ||
+ rProp.Name == IsRemote ||
+ rProp.Name == IsCompactDisc ||
+ rProp.Name == IsFloppy ||
+ rProp.Name == ContentType)
+ n_Mask |= (osl_FileStatus_Mask_Type | osl_FileStatus_Mask_LinkTargetURL);
+ else if(rProp.Name == Size)
+ n_Mask |= (osl_FileStatus_Mask_FileSize |
+ osl_FileStatus_Mask_Type |
+ osl_FileStatus_Mask_LinkTargetURL);
+ else if(rProp.Name == IsHidden ||
+ rProp.Name == IsReadOnly)
+ n_Mask |= osl_FileStatus_Mask_Attributes;
+ else if(rProp.Name == DateModified)
+ n_Mask |= osl_FileStatus_Mask_ModifyTime;
+ }
+}
+
+
+/*********************************************************************************/
+/* */
+/* load-Implementation */
+/* */
+/*********************************************************************************/
+
+// Load the properties from configuration, if create == true create them.
+// The Properties are stored under the url belonging to it->first.
+
+
+void
+TaskManager::load( const ContentMap::iterator& it, bool create )
+{
+ if( ( it->second.xS.is() && it->second.xC.is() && it->second.xA.is() )
+ || !m_xFileRegistry.is() )
+ return;
+
+
+ uno::Reference< ucb::XPersistentPropertySet > xS = m_xFileRegistry->openPropertySet( it->first,create );
+ if( xS.is() )
+ {
+ it->second.xS = xS;
+ it->second.xC.set(xS, uno::UNO_QUERY);
+ it->second.xA.set(xS, uno::UNO_QUERY);
+
+ // Now put in all values in the storage in the local hash;
+
+ PropertySet& properties = it->second.properties;
+ const uno::Sequence< beans::Property > seq = xS->getPropertySetInfo()->getProperties();
+
+ for( const auto& rProp : seq )
+ {
+ MyProperty readProp( false,
+ rProp.Name,
+ rProp.Handle,
+ rProp.Type,
+ xS->getPropertyValue( rProp.Name ),
+ beans::PropertyState_DIRECT_VALUE,
+ rProp.Attributes );
+ properties.insert( readProp );
+ }
+ }
+ else if( create )
+ {
+ // Catastrophic error
+ }
+}
+
+
+/*********************************************************************************/
+/* */
+/* commit-Implementation */
+/* */
+/*********************************************************************************/
+// Commit inserts the determined properties in the filestatus object into
+// the internal map, so that is possible to determine on a subsequent
+// setting of file properties which properties have changed without filestat
+
+
+void
+TaskManager::commit( std::unique_lock<std::mutex>& rGuard,
+ const TaskManager::ContentMap::iterator& it,
+ const osl::FileStatus& aFileStatus )
+{
+ TaskManager::PropertySet::const_iterator it1;
+
+ if( it->second.properties.empty() )
+ {
+ OUString aPath = it->first;
+ insertDefaultProperties( rGuard, aPath );
+ }
+
+ PropertySet& properties = it->second.properties;
+
+ it1 = properties.find( MyProperty( Title ) );
+ if( it1 != properties.end() )
+ {
+ if( aFileStatus.isValid( osl_FileStatus_Mask_FileName ) )
+ {
+ it1->setValue( uno::Any(aFileStatus.getFileName()) );
+ }
+ }
+
+ it1 = properties.find( MyProperty( CasePreservingURL ) );
+ if( it1 != properties.end() )
+ {
+ if( aFileStatus.isValid( osl_FileStatus_Mask_FileURL ) )
+ {
+ it1->setValue( uno::Any(aFileStatus.getFileURL()) );
+ }
+ }
+
+
+ bool isDirectory;
+
+ sal_Int64 dirSize = 0;
+
+ if( aFileStatus.isValid( osl_FileStatus_Mask_FileSize ) )
+ dirSize = aFileStatus.getFileSize();
+
+ if( aFileStatus.isValid( osl_FileStatus_Mask_Type ) )
+ {
+ bool isFile,isVolume;
+ if( osl::FileStatus::Link == aFileStatus.getFileType() &&
+ aFileStatus.isValid( osl_FileStatus_Mask_LinkTargetURL ) )
+ {
+ osl::DirectoryItem aDirItem;
+ osl::FileStatus aFileStatus2( osl_FileStatus_Mask_Type );
+ if( osl::FileBase::E_None == osl::DirectoryItem::get( aFileStatus.getLinkTargetURL(),aDirItem ) &&
+ osl::FileBase::E_None == aDirItem.getFileStatus( aFileStatus2 ) &&
+ aFileStatus2.isValid( osl_FileStatus_Mask_Type ) )
+ {
+ isVolume = osl::FileStatus::Volume == aFileStatus2.getFileType();
+ isDirectory =
+ osl::FileStatus::Volume == aFileStatus2.getFileType() ||
+ osl::FileStatus::Directory == aFileStatus2.getFileType();
+ isFile =
+ osl::FileStatus::Regular == aFileStatus2.getFileType();
+
+ if( aFileStatus2.isValid( osl_FileStatus_Mask_FileSize ) )
+ dirSize = aFileStatus2.getFileSize();
+ }
+ else
+ {
+ // extremely ugly, but otherwise default construction
+ // of aDirItem and aFileStatus2
+ // before the preceding if
+ isVolume = osl::FileStatus::Volume == aFileStatus.getFileType();
+ isDirectory =
+ osl::FileStatus::Volume == aFileStatus.getFileType() ||
+ osl::FileStatus::Directory == aFileStatus.getFileType();
+ isFile =
+ osl::FileStatus::Regular == aFileStatus.getFileType();
+ }
+ }
+ else
+ {
+ isVolume = osl::FileStatus::Volume == aFileStatus.getFileType();
+ isDirectory =
+ osl::FileStatus::Volume == aFileStatus.getFileType() ||
+ osl::FileStatus::Directory == aFileStatus.getFileType();
+ isFile =
+ osl::FileStatus::Regular == aFileStatus.getFileType();
+ }
+
+ it1 = properties.find( MyProperty( IsVolume ) );
+ if( it1 != properties.end() )
+ it1->setValue( uno::Any( isVolume ) );
+
+ it1 = properties.find( MyProperty( IsFolder ) );
+ if( it1 != properties.end() )
+ it1->setValue( uno::Any( isDirectory ) );
+
+ it1 = properties.find( MyProperty( IsDocument ) );
+ if( it1 != properties.end() )
+ it1->setValue( uno::Any( isFile ) );
+
+ osl::VolumeInfo aVolumeInfo( osl_VolumeInfo_Mask_Attributes );
+ if( isVolume &&
+ osl::FileBase::E_None == osl::Directory::getVolumeInfo( it->first,aVolumeInfo ) &&
+ aVolumeInfo.isValid( osl_VolumeInfo_Mask_Attributes ) )
+ {
+ // Retrieve the flags;
+ bool isRemote = aVolumeInfo.getRemoteFlag();
+ bool isRemoveable = aVolumeInfo.getRemoveableFlag();
+ bool isCompactDisc = aVolumeInfo.getCompactDiscFlag();
+ bool isFloppy = aVolumeInfo.getFloppyDiskFlag();
+
+ it1 = properties.find( MyProperty( IsRemote ) );
+ if( it1 != properties.end() )
+ it1->setValue( uno::Any( isRemote ) );
+
+ it1 = properties.find( MyProperty( IsRemoveable ) );
+ if( it1 != properties.end() )
+ it1->setValue( uno::Any( isRemoveable ) );
+
+ it1 = properties.find( MyProperty( IsCompactDisc ) );
+ if( it1 != properties.end() )
+ it1->setValue( uno::Any( isCompactDisc ) );
+
+ it1 = properties.find( MyProperty( IsFloppy ) );
+ if( it1 != properties.end() )
+ it1->setValue( uno::Any( isFloppy ) );
+ }
+ else
+ {
+ uno::Any aAny(false);
+ it1 = properties.find( MyProperty( IsRemote ) );
+ if( it1 != properties.end() )
+ it1->setValue( aAny );
+
+ it1 = properties.find( MyProperty( IsRemoveable ) );
+ if( it1 != properties.end() )
+ it1->setValue( aAny );
+
+ it1 = properties.find( MyProperty( IsCompactDisc ) );
+ if( it1 != properties.end() )
+ it1->setValue( aAny );
+
+ it1 = properties.find( MyProperty( IsFloppy ) );
+ if( it1 != properties.end() )
+ it1->setValue( aAny );
+ }
+ }
+ else
+ {
+ isDirectory = false;
+ }
+
+ it1 = properties.find( MyProperty( Size ) );
+ if( it1 != properties.end() )
+ it1->setValue( uno::Any( dirSize ) );
+
+ it1 = properties.find( MyProperty( IsReadOnly ) );
+ if( it1 != properties.end() )
+ {
+ if( aFileStatus.isValid( osl_FileStatus_Mask_Attributes ) )
+ {
+ sal_uInt64 Attr = aFileStatus.getAttributes();
+ bool readonly = ( Attr & osl_File_Attribute_ReadOnly ) != 0;
+ it1->setValue( uno::Any( readonly ) );
+ }
+ }
+
+ it1 = properties.find( MyProperty( IsHidden ) );
+ if( it1 != properties.end() )
+ {
+ if( aFileStatus.isValid( osl_FileStatus_Mask_Attributes ) )
+ {
+ sal_uInt64 Attr = aFileStatus.getAttributes();
+ bool ishidden = ( Attr & osl_File_Attribute_Hidden ) != 0;
+ it1->setValue( uno::Any( ishidden ) );
+ }
+ }
+
+ it1 = properties.find( MyProperty( DateModified ) );
+ if( it1 != properties.end() )
+ {
+ if( aFileStatus.isValid( osl_FileStatus_Mask_ModifyTime ) )
+ {
+ TimeValue temp = aFileStatus.getModifyTime();
+
+ // Convert system time to local time (for EA)
+ TimeValue myLocalTime;
+ if (!osl_getLocalTimeFromSystemTime( &temp, &myLocalTime ))
+ {
+ SAL_WARN(
+ "ucb.ucp.file",
+ "cannot convert (" << temp.Seconds << ", " << temp.Nanosec
+ << ") to local time");
+ myLocalTime = temp;
+ }
+
+ oslDateTime myDateTime;
+ osl_getDateTimeFromTimeValue( &myLocalTime, &myDateTime );
+ util::DateTime aDateTime;
+
+ aDateTime.NanoSeconds = myDateTime.NanoSeconds;
+ aDateTime.Seconds = myDateTime.Seconds;
+ aDateTime.Minutes = myDateTime.Minutes;
+ aDateTime.Hours = myDateTime.Hours;
+ aDateTime.Day = myDateTime.Day;
+ aDateTime.Month = myDateTime.Month;
+ aDateTime.Year = myDateTime.Year;
+ it1->setValue( uno::Any( aDateTime ) );
+ }
+ }
+
+ it1 = properties.find( MyProperty( CreatableContentsInfo ) );
+ if( it1 != properties.end() )
+ it1->setValue( uno::Any(
+ isDirectory || !aFileStatus.isValid( osl_FileStatus_Mask_Type )
+ ? queryCreatableContentsInfo()
+ : uno::Sequence< ucb::ContentInfo >() ) );
+}
+
+
+// Special optimized method for getting the properties of a
+// directoryitem, which is returned by osl::DirectoryItem::getNextItem()
+
+
+bool
+TaskManager::getv(
+ const uno::Sequence< beans::Property >& properties,
+ osl::DirectoryItem& aDirItem,
+ OUString& aUnqPath,
+ bool& aIsRegular,
+ uno::Reference< sdbc::XRow > & row )
+{
+ uno::Sequence< uno::Any > seq( properties.getLength() );
+
+ sal_Int32 n_Mask;
+ getMaskFromProperties( n_Mask,properties );
+
+ // Always retrieve the type and the target URL because item might be a link
+ osl::FileStatus aFileStatus( n_Mask |
+ osl_FileStatus_Mask_FileURL |
+ osl_FileStatus_Mask_Type |
+ osl_FileStatus_Mask_LinkTargetURL );
+
+ osl::FileBase::RC aRes = aDirItem.getFileStatus( aFileStatus );
+ if ( aRes != osl::FileBase::E_None )
+ {
+ SAL_WARN(
+ "ucb.ucp.file",
+ "osl::DirectoryItem::getFileStatus failed with " << +aRes);
+ return false;
+ }
+
+ aUnqPath = aFileStatus.getFileURL();
+
+ // If the directory item type is a link retrieve the type of the target
+
+ if ( aFileStatus.getFileType() == osl::FileStatus::Link )
+ {
+ // Assume failure
+ aIsRegular = false;
+ osl::DirectoryItem aTargetItem;
+ (void)osl::DirectoryItem::get( aFileStatus.getLinkTargetURL(), aTargetItem );
+ if ( aTargetItem.is() )
+ {
+ osl::FileStatus aTargetStatus( osl_FileStatus_Mask_Type );
+
+ if ( osl::FileBase::E_None == aTargetItem.getFileStatus( aTargetStatus ) )
+ aIsRegular =
+ aTargetStatus.getFileType() == osl::FileStatus::Regular;
+ }
+ }
+ else
+ aIsRegular = aFileStatus.getFileType() == osl::FileStatus::Regular;
+
+ {
+ std::unique_lock aGuard( m_aMutex );
+
+ insertDefaultProperties( aGuard, aUnqPath );
+
+ TaskManager::ContentMap::iterator it = m_aContent.find( aUnqPath );
+ commit( aGuard, it, aFileStatus );
+
+ PropertySet& propset = it->second.properties;
+
+ std::transform(properties.begin(), properties.end(), seq.getArray(),
+ [&propset](const beans::Property& rProp) -> uno::Any {
+ MyProperty readProp( rProp.Name );
+ auto it1 = propset.find( readProp );
+ if( it1 == propset.end() )
+ return uno::Any();
+ return it1->getValue();
+ });
+ }
+
+ row = new XRow_impl( this,seq );
+ return true;
+}
+
+
+// EventListener
+
+
+std::vector< ContentEventNotifier >
+TaskManager::getContentEventListeners( const OUString& aName )
+{
+ std::vector< ContentEventNotifier > listeners;
+ {
+ std::unique_lock aGuard( m_aMutex );
+ TaskManager::ContentMap::iterator it = m_aContent.find( aName );
+ if( it != m_aContent.end() && !it->second.notifier.empty() )
+ {
+ std::vector<Notifier*>& listOfNotifiers = it->second.notifier;
+ for (auto const& pointer : listOfNotifiers)
+ {
+ std::optional<ContentEventNotifier> notifier = pointer->cCEL();
+ if( notifier )
+ listeners.push_back( std::move(*notifier) );
+ }
+ }
+ }
+ return listeners;
+}
+
+
+std::vector< ContentEventNotifier >
+TaskManager::getContentDeletedEventListeners( const OUString& aName )
+{
+ std::vector< ContentEventNotifier > listeners;
+ {
+ std::unique_lock aGuard( m_aMutex );
+ TaskManager::ContentMap::iterator it = m_aContent.find( aName );
+ if( it != m_aContent.end() && !it->second.notifier.empty() )
+ {
+ std::vector<Notifier*>& listOfNotifiers = it->second.notifier;
+ for (auto const& pointer : listOfNotifiers)
+ {
+ std::optional<ContentEventNotifier> notifier = pointer->cDEL();
+ if( notifier )
+ listeners.push_back( std::move(*notifier) );
+ }
+ }
+ }
+ return listeners;
+}
+
+void TaskManager::notifyInsert(const std::vector<ContentEventNotifier>& listeners,
+ const OUString& aChildName)
+{
+ for (const auto & l : listeners )
+ {
+ l.notifyChildInserted( aChildName );
+ }
+}
+
+void TaskManager::notifyContentDeleted(
+ const std::vector<ContentEventNotifier>& listeners)
+{
+ for( auto const & l : listeners )
+ {
+ l.notifyDeleted();
+ }
+}
+
+void TaskManager::notifyContentRemoved(
+ const std::vector<ContentEventNotifier>& listeners, const OUString& aChildName)
+{
+ for( auto const & l : listeners )
+ {
+ l.notifyRemoved( aChildName );
+ }
+}
+
+
+std::vector< PropertySetInfoChangeNotifier >
+TaskManager::getPropertySetListeners( const OUString& aName )
+{
+ std::vector< PropertySetInfoChangeNotifier > listeners;
+ {
+ std::unique_lock aGuard( m_aMutex );
+ TaskManager::ContentMap::iterator it = m_aContent.find( aName );
+ if( it != m_aContent.end() && !it->second.notifier.empty() )
+ {
+ std::vector<Notifier*>& listOfNotifiers = it->second.notifier;
+ for (auto const& pointer : listOfNotifiers)
+ {
+ std::optional<PropertySetInfoChangeNotifier> notifier = pointer->cPSL();
+ if( notifier )
+ listeners.push_back( std::move(*notifier) );
+ }
+ }
+ }
+ return listeners;
+}
+
+void TaskManager::notifyPropertyAdded(
+ const std::vector<PropertySetInfoChangeNotifier>& listeners,
+ const OUString& aPropertyName)
+{
+ for( auto const & l : listeners )
+ {
+ l.notifyPropertyAdded( aPropertyName );
+ }
+}
+
+void TaskManager::notifyPropertyRemoved(
+ const std::vector<PropertySetInfoChangeNotifier>& listeners,
+ const OUString& aPropertyName)
+{
+ for( auto const & l : listeners )
+ {
+ l.notifyPropertyRemoved( aPropertyName );
+ }
+}
+
+
+std::vector< ContentEventNotifier >
+TaskManager::getContentExchangedEventListeners( const OUString& aOldPrefix,
+ const OUString& aNewPrefix,
+ bool withChildren )
+{
+ std::vector< ContentEventNotifier > aVector;
+
+ sal_Int32 count;
+ OUString aOldName;
+ OUString aNewName;
+ std::vector< OUString > oldChildList;
+
+ {
+ std::unique_lock aGuard( m_aMutex );
+
+ if( ! withChildren )
+ {
+ aOldName = aOldPrefix;
+ aNewName = aNewPrefix;
+ count = 1;
+ }
+ else
+ {
+ for (auto const& content : m_aContent)
+ {
+ if( isChild( aOldPrefix, content.first ) )
+ {
+ oldChildList.push_back( content.first );
+ }
+ }
+ count = oldChildList.size();
+ }
+
+
+ for( sal_Int32 j = 0; j < count; ++j )
+ {
+ if( withChildren )
+ {
+ aOldName = oldChildList[j];
+ aNewName = newName( aNewPrefix,aOldPrefix,aOldName );
+ }
+
+ TaskManager::ContentMap::iterator itold = m_aContent.find( aOldName );
+ if( itold != m_aContent.end() )
+ {
+ TaskManager::ContentMap::iterator itnew = m_aContent.emplace(
+ aNewName,UnqPathData() ).first;
+
+ // copy Ownership also
+ itnew->second.properties = std::move(itold->second.properties);
+
+ // copy existing list
+ std::vector< Notifier* > copyList;
+ std::swap(copyList, itnew->second.notifier);
+ itnew->second.notifier = std::move(itold->second.notifier);
+
+ m_aContent.erase( itold );
+
+ if (itnew != m_aContent.end())
+ {
+ if (!itnew->second.notifier.empty())
+ {
+ std::vector<Notifier*>& listOfNotifiers = itnew->second.notifier;
+ for (auto const& pointer : listOfNotifiers)
+ {
+ std::optional<ContentEventNotifier> notifier = pointer->cEXC( aNewName );
+ if( notifier )
+ aVector.push_back( std::move(*notifier) );
+ }
+ }
+
+ // Merge with preexisting notifiers
+ // However, these may be in status BaseContent::Deleted
+ itnew->second.notifier.insert(itnew->second.notifier.end(),
+ copyList.begin(), copyList.end() );
+ }
+ }
+ }
+ }
+
+ return aVector;
+}
+
+void TaskManager::notifyContentExchanged(
+ const std::vector<ContentEventNotifier>& listeners_vec)
+{
+ for( auto & l : listeners_vec)
+ {
+ l.notifyExchanged();
+ }
+}
+
+
+std::vector< PropertyChangeNotifier >
+TaskManager::getPropertyChangeNotifier( const OUString& aName )
+{
+ std::vector< PropertyChangeNotifier > listeners;
+ {
+ std::unique_lock aGuard( m_aMutex );
+ TaskManager::ContentMap::iterator it = m_aContent.find( aName );
+ if( it != m_aContent.end() && !it->second.notifier.empty() )
+ {
+ std::vector<Notifier*>& listOfNotifiers = it->second.notifier;
+ for (auto const& pointer : listOfNotifiers)
+ {
+ std::optional<PropertyChangeNotifier> notifier = pointer->cPCL();
+ if( notifier )
+ listeners.push_back( std::move(*notifier) );
+ }
+ }
+ }
+ return listeners;
+}
+
+void TaskManager::notifyPropertyChanges(
+ const std::vector<PropertyChangeNotifier>& listeners,
+ const uno::Sequence<beans::PropertyChangeEvent>& seqChanged)
+{
+ for( auto const & l : listeners )
+ {
+ l.notifyPropertyChanged( seqChanged );
+ }
+}
+
+
+/********************************************************************************/
+/* remove persistent propertyset */
+/********************************************************************************/
+
+void
+TaskManager::erasePersistentSetWithoutChildren( const OUString& aUnqPath )
+{
+ {
+ // Release possible references
+ std::unique_lock aGuard( m_aMutex );
+ ContentMap::iterator it = m_aContent.find( aUnqPath );
+ if( it != m_aContent.end() )
+ {
+ it->second.xS = nullptr;
+ it->second.xC = nullptr;
+ it->second.xA = nullptr;
+
+ it->second.properties.clear();
+ }
+ }
+
+ m_xFileRegistry->removePropertySet( aUnqPath );
+}
+
+void
+TaskManager::erasePersistentSet( const OUString& aUnqPath,
+ bool withChildren )
+{
+ if( ! m_xFileRegistry.is() )
+ {
+ OSL_ASSERT( m_xFileRegistry.is() );
+ return;
+ }
+
+ if( ! withChildren )
+ {
+ erasePersistentSetWithoutChildren(aUnqPath);
+ return;
+ }
+
+ uno::Reference< container::XNameAccess > xName( m_xFileRegistry,uno::UNO_QUERY );
+ const uno::Sequence< OUString > seqNames = xName->getElementNames();
+
+ OUString old_Name = aUnqPath;
+
+ for( const auto& rName : seqNames )
+ {
+ if( ! ( isChild( old_Name,rName ) ) )
+ continue;
+
+ old_Name = rName;
+
+ erasePersistentSetWithoutChildren(old_Name);
+ }
+}
+
+
+/********************************************************************************/
+/* copy persistent propertyset */
+/* from srcUnqPath to dstUnqPath */
+/********************************************************************************/
+
+void
+TaskManager::copyPersistentSetWithoutChildren( const OUString& srcUnqPath,
+ const OUString& dstUnqPath )
+{
+ uno::Reference< XPersistentPropertySet > x_src =
+ m_xFileRegistry->openPropertySet( srcUnqPath,false );
+ m_xFileRegistry->removePropertySet( dstUnqPath );
+
+ if( ! x_src.is() )
+ return;
+
+ const uno::Sequence< beans::Property > seqProperty =
+ x_src->getPropertySetInfo()->getProperties();
+
+ if( ! seqProperty.hasElements() )
+ return;
+
+ uno::Reference< XPersistentPropertySet >
+ x_dstS = m_xFileRegistry->openPropertySet( dstUnqPath,true );
+ uno::Reference< beans::XPropertyContainer >
+ x_dstC( x_dstS,uno::UNO_QUERY );
+
+ for( const auto& rProperty : seqProperty )
+ {
+ x_dstC->addProperty( rProperty.Name,
+ rProperty.Attributes,
+ x_src->getPropertyValue( rProperty.Name ) );
+ }
+}
+
+void
+TaskManager::copyPersistentSet( const OUString& srcUnqPath,
+ const OUString& dstUnqPath,
+ bool withChildren )
+{
+ if( ! m_xFileRegistry.is() )
+ {
+ OSL_ASSERT( m_xFileRegistry.is() );
+ return;
+ }
+
+ if( ! withChildren )
+ {
+ copyPersistentSetWithoutChildren(srcUnqPath, dstUnqPath);
+ return;
+ }
+
+ uno::Reference< container::XNameAccess > xName( m_xFileRegistry,uno::UNO_QUERY );
+ const uno::Sequence< OUString > seqNames = xName->getElementNames();
+
+ OUString new_Name;
+
+ for( const auto& rName : seqNames )
+ {
+ if( ! ( isChild( srcUnqPath,rName ) ) )
+ continue;
+
+ new_Name = newName( dstUnqPath,srcUnqPath,rName );
+
+ copyPersistentSetWithoutChildren(rName, new_Name);
+ }
+}
+
+uno::Sequence< ucb::ContentInfo > TaskManager::queryCreatableContentsInfo()
+{
+
+
+ uno::Sequence< beans::Property > props
+ {
+ { "Title", -1, cppu::UnoType<OUString>::get(), beans::PropertyAttribute::MAYBEVOID | beans::PropertyAttribute::BOUND }
+ };
+ return
+ {
+ { FileContentType, ucb::ContentInfoAttribute::INSERT_WITH_INPUTSTREAM | ucb::ContentInfoAttribute::KIND_DOCUMENT, props },
+ { FolderContentType, ucb::ContentInfoAttribute::KIND_FOLDER, props }
+ };
+}
+
+/*******************************************************************************/
+/* */
+/* some miscellaneous static functions */
+/* */
+/*******************************************************************************/
+
+void
+TaskManager::getScheme( OUString& Scheme )
+{
+ Scheme = "file";
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/ucb/source/ucp/file/filtask.hxx b/ucb/source/ucp/file/filtask.hxx
new file mode 100644
index 0000000000..9d41692554
--- /dev/null
+++ b/ucb/source/ucp/file/filtask.hxx
@@ -0,0 +1,647 @@
+/* -*- 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:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+#pragma once
+
+#include <o3tl/sorted_vector.hxx>
+#include <osl/file.hxx>
+#include <rtl/ustring.hxx>
+
+#include <osl/mutex.hxx>
+#include <com/sun/star/uno/Sequence.hxx>
+#include <com/sun/star/beans/PropertyChangeEvent.hpp>
+#include <com/sun/star/ucb/XCommandInfo.hpp>
+#include <com/sun/star/beans/Property.hpp>
+#include <com/sun/star/beans/PropertyValue.hpp>
+#include <com/sun/star/io/XStream.hpp>
+#include <com/sun/star/io/XOutputStream.hpp>
+#include <com/sun/star/io/XInputStream.hpp>
+#include <com/sun/star/beans/XPropertySetInfo.hpp>
+#include <com/sun/star/ucb/NumberedSortingInfo.hpp>
+#include <com/sun/star/sdbc/XRow.hpp>
+#include <com/sun/star/uno/XComponentContext.hpp>
+#include <com/sun/star/ucb/XDynamicResultSet.hpp>
+#include <com/sun/star/beans/XPropertyContainer.hpp>
+#include <com/sun/star/beans/XPropertyAccess.hpp>
+#include <com/sun/star/ucb/ContentInfo.hpp>
+#include <com/sun/star/ucb/XCommandEnvironment.hpp>
+#include <com/sun/star/ucb/XPersistentPropertySet.hpp>
+#include <com/sun/star/ucb/XPropertySetRegistry.hpp>
+#include <com/sun/star/task/XInteractionHandler.hpp>
+#include <com/sun/star/task/XInteractionRequest.hpp>
+#include "filerror.hxx"
+#include "filnot.hxx"
+#include <mutex>
+#include <unordered_map>
+#include <unordered_set>
+#include <vector>
+
+namespace fileaccess
+{
+ class BaseContent;
+ class FileProvider;
+ class XPropertySetInfo_impl;
+ class XCommandInfo_impl;
+ class XResultSet_impl;
+
+ /*
+ * The relevant methods in this class all have as first argument the CommandId,
+ * so if necessary, every method has access to its relevant XInteractionHandler and
+ * XProgressHandler.
+ */
+
+
+ class TaskManager
+ {
+ friend class XPropertySetInfo_impl;
+ friend class XResultSet_impl;
+ friend class XCommandInfo_impl;
+
+ private:
+
+ class TaskHandling
+ {
+ private:
+
+ bool m_bHandled;
+ sal_Int32 m_nErrorCode,m_nMinorCode;
+ css::uno::Reference< css::task::XInteractionHandler > m_xInteractionHandler;
+ css::uno::Reference< css::ucb::XCommandEnvironment > m_xCommandEnvironment;
+
+
+ public:
+
+ explicit TaskHandling(
+ css::uno::Reference< css::ucb::XCommandEnvironment > xCommandEnv )
+ : m_bHandled( false ),
+ m_nErrorCode( TASKHANDLER_NO_ERROR ),
+ m_nMinorCode( TASKHANDLER_NO_ERROR ),
+ m_xCommandEnvironment( std::move(xCommandEnv) )
+ {
+ }
+
+ void setHandled()
+ {
+ m_bHandled = true;
+ }
+
+ bool isHandled() const
+ {
+ return m_bHandled;
+ }
+
+ void clearError()
+ {
+ m_nErrorCode = TASKHANDLER_NO_ERROR;
+ m_nMinorCode = TASKHANDLER_NO_ERROR;
+ }
+
+ void installError( sal_Int32 nErrorCode,
+ sal_Int32 nMinorCode )
+ {
+ m_nErrorCode = nErrorCode;
+ m_nMinorCode = nMinorCode;
+ }
+
+ sal_Int32 getInstalledError() const
+ {
+ return m_nErrorCode;
+ }
+
+ sal_Int32 getMinorErrorCode() const
+ {
+ return m_nMinorCode;
+ }
+
+ css::uno::Reference< css::task::XInteractionHandler > const &
+ getInteractionHandler()
+ {
+ if( ! m_xInteractionHandler.is() && m_xCommandEnvironment.is() )
+ m_xInteractionHandler = m_xCommandEnvironment->getInteractionHandler();
+
+ return m_xInteractionHandler;
+ }
+
+ const css::uno::Reference< css::ucb::XCommandEnvironment >&
+ getCommandEnvironment() const
+ {
+ return m_xCommandEnvironment;
+ }
+
+ }; // end class TaskHandling
+
+
+ typedef std::unordered_map< sal_Int32,TaskHandling > TaskMap;
+ private:
+
+ std::mutex m_aMutex;
+ sal_Int32 m_nCommandId;
+ TaskMap m_aTaskMap;
+
+
+ public:
+ class MyProperty
+ {
+ private:
+ OUString PropertyName;
+ sal_Int32 Handle;
+ bool isNative;
+ css::uno::Type Typ; // Duplicates information in Value
+ css::uno::Any Value;
+ css::beans::PropertyState State;
+ sal_Int16 Attributes;
+ public:
+ explicit MyProperty( const OUString& thePropertyName );
+ MyProperty( bool theIsNative,
+ const OUString& thePropertyName,
+ sal_Int32 theHandle,
+ const css::uno::Type& theTyp,
+ const css::uno::Any& theValue,
+ const css::beans::PropertyState& theState,
+ sal_Int16 theAttributes );
+
+ inline const bool& IsNative() const;
+ const OUString& getPropertyName() const { return PropertyName; }
+ inline const sal_Int32& getHandle() const;
+ inline const css::uno::Type& getType() const;
+ inline const css::uno::Any& getValue() const;
+ inline const css::beans::PropertyState& getState() const;
+ inline const sal_Int16& getAttributes() const;
+
+ // The set* functions are declared const, because the key of "this" stays intact
+ inline void setValue( css::uno::Any theValue ) const;
+ inline void setState( const css::beans::PropertyState& theState ) const;
+ };
+
+ struct MyPropertyLess
+ {
+ bool operator()( const MyProperty& rKey1, const MyProperty& rKey2 ) const
+ {
+ return rKey1.getPropertyName() < rKey2.getPropertyName();
+ }
+ };
+
+ typedef o3tl::sorted_vector< MyProperty, MyPropertyLess > PropertySet;
+
+ class UnqPathData
+ {
+ public:
+ UnqPathData();
+ UnqPathData(UnqPathData&&);
+ ~UnqPathData();
+
+ PropertySet properties;
+ std::vector< Notifier* > notifier;
+
+ // Three views on the PersistentPropertySet
+ css::uno::Reference< css::ucb::XPersistentPropertySet > xS;
+ css::uno::Reference< css::beans::XPropertyContainer > xC;
+ css::uno::Reference< css::beans::XPropertyAccess > xA;
+ };
+
+ typedef std::unordered_map< OUString,UnqPathData > ContentMap;
+
+ TaskManager( const css::uno::Reference< css::uno::XComponentContext >& rxContext,
+ FileProvider* pProvider, bool bWithConfig );
+ ~TaskManager();
+
+ /// @throws css::ucb::DuplicateCommandIdentifierException
+ void startTask(
+ sal_Int32 CommandId,
+ const css::uno::Reference< css::ucb::XCommandEnvironment >& xCommandEnv );
+
+ sal_Int32 getCommandId();
+
+
+ /**
+ * The error code may be one of the error codes defined in
+ * filerror.hxx.
+ * The minor code refines the information given in ErrorCode.
+ */
+
+ void installError( sal_Int32 CommandId,
+ sal_Int32 ErrorCode,
+ sal_Int32 minorCode = TASKHANDLER_NO_ERROR );
+
+ void retrieveError( sal_Int32 CommandId,
+ sal_Int32 &ErrorCode,
+ sal_Int32 &minorCode);
+
+ /**
+ * Deinstalls the task and evaluates a possibly set error code.
+ * "endTask" throws in case an error code is set the corresponding exception.
+ */
+
+ void endTask( sal_Int32 CommandId,
+ // the physical URL of the object
+ const OUString& aUnqPath,
+ BaseContent* pContent);
+
+
+ /**
+ * Handles an interactionrequest
+ */
+
+ void handleTask( sal_Int32 CommandId,
+ const css::uno::Reference< css::task::XInteractionRequest >& request );
+
+ /**
+ * Clears any error which are set on the commandid
+ */
+
+ void clearError( sal_Int32 );
+
+ /**
+ * This two methods register and deregister a change listener for the content belonging
+ * to URL aUnqPath
+ */
+
+ void registerNotifier( const OUString& aUnqPath,Notifier* pNotifier );
+
+ void deregisterNotifier( const OUString& aUnqPath,Notifier* pNotifier );
+
+
+ /**
+ * Used to associate and deassociate a new property with
+ * the content belonging to URL UnqPath.
+ * The default value and the attributes are input
+ *
+ * @throws css::beans::PropertyExistException
+ * @throws css::beans::IllegalTypeException
+ * @throws css::uno::RuntimeException
+ */
+
+ void associate( const OUString& UnqPath,
+ const OUString& PropertyName,
+ const css::uno::Any& DefaultValue,
+ const sal_Int16 Attributes );
+
+ /// @throws css::beans::UnknownPropertyException
+ /// @throws css::beans::NotRemoveableException
+ /// @throws css::uno::RuntimeException
+ void deassociate( const OUString& UnqPath,
+ const OUString& PropertyName );
+
+
+ // Every method having a command id is not allowed to throw anything,
+ // but instead must install every error code in the task handler
+
+
+ /**
+ * Given an xOutputStream, this method writes the content of the file belonging to
+ * URL aUnqPath into the XOutputStream
+ */
+
+ void page( sal_Int32 CommandId,
+ const OUString& aUnqPath,
+ const css::uno::Reference< css::io::XOutputStream >& xOutputStream );
+
+
+ /**
+ * Given a file URL aUnqPath, this methods returns a XInputStream which reads from the open file.
+ */
+
+ css::uno::Reference< css::io::XInputStream >
+ open( sal_Int32 CommandId,
+ const OUString& aUnqPath,
+ bool bLock );
+
+
+ /**
+ * Given a file URL aUnqPath, this methods returns a XStream which can be used
+ * to read and write from/to the file.
+ */
+
+ css::uno::Reference< css::io::XStream >
+ open_rw( sal_Int32 CommandId,
+ const OUString& aUnqPath,
+ bool bLock );
+
+
+ /**
+ * This method returns the result set containing the children of the directory belonging
+ * to file URL aUnqPath
+ */
+
+ css::uno::Reference< css::ucb::XDynamicResultSet >
+ ls( sal_Int32 CommandId,
+ const OUString& aUnqPath,
+ const sal_Int32 OpenMode,
+ const css::uno::Sequence< css::beans::Property >& sProperty,
+ const css::uno::Sequence< css::ucb::NumberedSortingInfo > & sSortingInfo );
+
+
+ /**
+ * Info methods
+ */
+
+ // Info for commands
+ css::uno::Reference< css::ucb::XCommandInfo >
+ info_c();
+
+ // Info for the properties
+ css::uno::Reference< css::beans::XPropertySetInfo >
+ info_p( const OUString& aUnqPath );
+
+
+ /**
+ * Sets the values of the properties belonging to fileURL aUnqPath
+ */
+
+ css::uno::Sequence< css::uno::Any >
+ setv( const OUString& aUnqPath,
+ const css::uno::Sequence< css::beans::PropertyValue >& values );
+
+
+ /**
+ * Reads the values of the properties belonging to fileURL aUnqPath;
+ * Returns an XRow object containing the values in the requested order.
+ */
+
+ css::uno::Reference< css::sdbc::XRow >
+ getv( sal_Int32 CommandId,
+ const OUString& aUnqPath,
+ const css::uno::Sequence< css::beans::Property >& properties );
+
+
+ /********************************************************************************/
+ /* transfer-commands */
+ /********************************************************************************/
+
+ /**
+ * Moves the content belonging to fileURL srcUnqPath to fileURL dstUnqPath( files and directories )
+ */
+
+ void
+ move( sal_Int32 CommandId,
+ const OUString& srcUnqPath, // Full file(folder)-path
+ const OUString& dstUnqPath, // Path to the destination-directory
+ const sal_Int32 NameClash );
+
+ /**
+ * Copies the content belonging to fileURL srcUnqPath to fileURL dstUnqPath ( files and directories )
+ */
+
+ void
+ copy( sal_Int32 CommandId, // See "move"
+ const OUString& srcUnqPath,
+ const OUString& dstUnqPath,
+ sal_Int32 NameClash );
+
+ enum class FileUrlType { Folder = 1, File = -1, Unknown = 0 };
+
+ /**
+ * Deletes the content belonging to fileURL aUnqPath( recursively in case of directory )
+ */
+
+ bool
+ remove( sal_Int32 CommandId,
+ const OUString& aUnqPath,
+ FileUrlType eTypeToMove = FileUrlType::Unknown,
+ bool MustExist = true );
+
+
+ /********************************************************************************/
+ /* write and create - commandos */
+ /********************************************************************************/
+
+ /**
+ * Creates new directory with given URL, recursively if necessary
+ * Return:: success of operation
+ */
+
+ bool
+ mkdir( sal_Int32 CommandId,
+ const OUString& aDirectoryName,
+ bool OverWrite );
+
+
+ /**
+ * Creates new file with given URL.
+ * The content of aInputStream becomes the content of the file
+ * Return:: success of operation
+ */
+
+ bool
+ mkfil( sal_Int32 CommandId,
+ const OUString& aFileName,
+ bool OverWrite,
+ const css::uno::Reference< css::io::XInputStream >& aInputStream );
+
+
+ /**
+ * writes to the file with given URL.
+ * The content of aInputStream becomes the content of the file
+ * Return:: success of operation
+ */
+ bool
+ write( sal_Int32 CommandId,
+ const OUString& aUnqPath,
+ bool OverWrite,
+ const css::uno::Reference< css::io::XInputStream >& aInputStream );
+
+
+ void insertDefaultProperties( const OUString& aUnqPath );
+
+ static css::uno::Sequence< css::ucb::ContentInfo >
+ queryCreatableContentsInfo();
+
+
+ /******************************************************************************/
+ /* */
+ /* mapping of file urls */
+ /* to uncpath and vice versa */
+ /* */
+ /******************************************************************************/
+
+ static bool getUnqFromUrl( const OUString& Url, OUString& Unq );
+
+ static bool getUrlFromUnq( const OUString& Unq, OUString& Url );
+
+
+ FileProvider* m_pProvider;
+ css::uno::Reference< css::uno::XComponentContext > m_xContext;
+ css::uno::Reference< css::ucb::XPropertySetRegistry > m_xFileRegistry;
+
+ private:
+
+ void insertDefaultProperties( std::unique_lock<std::mutex>& rGuard, const OUString& aUnqPath );
+
+ /********************************************************************************/
+ /* get eventListeners */
+ /********************************************************************************/
+
+ std::vector< ContentEventNotifier >
+ getContentEventListeners( const OUString& aName );
+
+ std::vector< ContentEventNotifier >
+ getContentDeletedEventListeners( const OUString& aName );
+
+ std::vector< ContentEventNotifier >
+ getContentExchangedEventListeners( const OUString& aOldPrefix,
+ const OUString& aNewPrefix,
+ bool withChildren );
+
+ std::vector< PropertyChangeNotifier >
+ getPropertyChangeNotifier( const OUString& aName );
+
+ std::vector< PropertySetInfoChangeNotifier >
+ getPropertySetListeners( const OUString& aName );
+
+
+ /********************************************************************************/
+ /* notify eventListeners */
+ /********************************************************************************/
+
+ static void notifyPropertyChanges(
+ const std::vector<PropertyChangeNotifier>& listeners,
+ const css::uno::Sequence<css::beans::PropertyChangeEvent>& seqChanged);
+
+ static void notifyContentExchanged(
+ const std::vector<ContentEventNotifier>& listeners_vec);
+
+ static void
+ notifyInsert(const std::vector<ContentEventNotifier>& listeners,
+ const OUString& aChildName);
+
+ static void
+ notifyContentDeleted(const std::vector<ContentEventNotifier>& listeners);
+
+ static void
+ notifyContentRemoved(const std::vector<ContentEventNotifier>& listeners,
+ const OUString& aChildName);
+
+ static void notifyPropertyAdded(
+ const std::vector<PropertySetInfoChangeNotifier>& listeners,
+ const OUString& aPropertyName);
+
+ static void notifyPropertyRemoved(
+ const std::vector<PropertySetInfoChangeNotifier>& listeners,
+ const OUString& aPropertyName);
+
+ /********************************************************************************/
+ /* remove persistent propertyset */
+ /********************************************************************************/
+
+ void erasePersistentSetWithoutChildren( const OUString& aUnqPath );
+ void erasePersistentSet( const OUString& aUnqPath,
+ bool withChildren = false );
+
+ /********************************************************************************/
+ /* copy persistent propertyset */
+ /* from srcUnqPath to dstUnqPath */
+ /********************************************************************************/
+
+ void copyPersistentSetWithoutChildren( const OUString& srcUnqPath,
+ const OUString& dstUnqPath );
+ void copyPersistentSet( const OUString& srcUnqPath,
+ const OUString& dstUnqPath,
+ bool withChildren );
+
+
+ // Special optimized method for getting the properties of a directoryitem, which
+ // is returned by osl::DirectoryItem::getNextItem()
+
+ bool
+ getv( const css::uno::Sequence< css::beans::Property >& properties,
+ osl::DirectoryItem& DirItem,
+ OUString& aUnqPath,
+ bool& bIsRegular,
+ css::uno::Reference< css::sdbc::XRow > & row );
+
+
+ /**
+ * Load the properties from configuration, if create == true create them.
+ * The Properties are stored under the url belonging to it->first.
+ */
+
+ void load( const TaskManager::ContentMap::iterator& it,
+ bool create );
+
+ /**
+ * Commit inserts the determined properties in the filestatus object into
+ * the internal map, so that is possible to determine on a subsequent
+ * setting of file properties which properties have changed without filestat
+ */
+
+ void
+ commit(
+ std::unique_lock<std::mutex>& rGuard,
+ const TaskManager::ContentMap::iterator& it,
+ const osl::FileStatus& aFileStatus );
+
+ /**
+ * Given a Sequence of properties seq, this method determines the mask
+ * used to instantiate an osl::FileStatus, so that a call to
+ * osl::DirectoryItem::getFileStatus fills the required fields.
+ */
+
+ static void
+ getMaskFromProperties(
+ sal_Int32& n_Mask,
+ const css::uno::Sequence< css::beans::Property >& seq );
+
+
+ // Helper function for public copy
+
+ osl::FileBase::RC
+ copy_recursive(
+ const OUString& srcUnqPath,
+ const OUString& dstUnqPath,
+ FileUrlType TypeToCopy,
+ bool testExistence );
+
+
+ // Helper function for mkfil,mkdir and write
+ // Creates whole path
+ // returns success of the operation
+ // The call determines the errorCode, which should be used to install
+ // any error
+
+ bool
+ ensuredir( sal_Int32 CommandId,
+ const OUString& aDirectoryName,
+ sal_Int32 errorCode );
+
+ // General
+ ContentMap m_aContent;
+
+
+ public:
+
+ static constexpr OUString FolderContentType =
+ u"application/vnd.sun.staroffice.fsys-folder"_ustr;
+ static constexpr OUString FileContentType =
+ u"application/vnd.sun.staroffice.fsys-file"_ustr;
+
+
+ private:
+
+ PropertySet m_aDefaultProperties;
+ css::uno::Sequence< css::ucb::CommandInfo > m_sCommandInfo;
+
+ public:
+ // Miscellaneous:
+ // Methods for "writeComponentInfo" and "createComponentFactory"
+
+ static void getScheme( OUString& Scheme );
+ };
+
+} // end namespace TaskHandling
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/ucb/source/ucp/file/prov.cxx b/ucb/source/ucp/file/prov.cxx
new file mode 100644
index 0000000000..eab4ec5300
--- /dev/null
+++ b/ucb/source/ucp/file/prov.cxx
@@ -0,0 +1,447 @@
+/* -*- 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:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include <osl/security.hxx>
+#include <osl/file.hxx>
+#include <osl/socket.h>
+#include <cppuhelper/queryinterface.hxx>
+#include <comphelper/processfactory.hxx>
+#include <com/sun/star/beans/PropertyAttribute.hpp>
+#include <com/sun/star/ucb/FileSystemNotation.hpp>
+#include <com/sun/star/ucb/IllegalIdentifierException.hpp>
+#include <cppuhelper/supportsservice.hxx>
+#include "filglob.hxx"
+#include "filid.hxx"
+#include "filtask.hxx"
+#include "bc.hxx"
+#include "prov.hxx"
+
+using namespace fileaccess;
+using namespace com::sun::star;
+using namespace com::sun::star::uno;
+using namespace com::sun::star::lang;
+using namespace com::sun::star::beans;
+using namespace com::sun::star::ucb;
+using namespace com::sun::star::container;
+
+#if OSL_DEBUG_LEVEL > 0
+#define THROW_WHERE SAL_WHERE
+#else
+#define THROW_WHERE ""
+#endif
+
+
+/****************************************************************************/
+/* */
+/* */
+/* FileProvider */
+/* */
+/* */
+/****************************************************************************/
+FileProvider::FileProvider( const Reference< XComponentContext >& rxContext )
+ : m_xContext(rxContext)
+ , m_FileSystemNotation(FileSystemNotation::UNKNOWN_NOTATION)
+{
+}
+
+FileProvider::~FileProvider()
+{
+}
+
+// XInitialization
+void FileProvider::init()
+{
+ if( ! m_pMyShell )
+ m_pMyShell.reset( new TaskManager( m_xContext, this, true ) );
+}
+
+
+void SAL_CALL
+FileProvider::initialize(
+ const Sequence< Any >& aArguments )
+{
+ if( ! m_pMyShell ) {
+ OUString config;
+ if( aArguments.hasElements() &&
+ (aArguments[0] >>= config) &&
+ config == "NoConfig" )
+ m_pMyShell.reset( new TaskManager( m_xContext, this, false ) );
+ else
+ m_pMyShell.reset( new TaskManager( m_xContext, this, true ) );
+ }
+}
+
+// XServiceInfo methods.
+OUString SAL_CALL
+FileProvider::getImplementationName()
+{
+ return "com.sun.star.comp.ucb.FileProvider";
+}
+
+sal_Bool SAL_CALL FileProvider::supportsService(const OUString& ServiceName )
+{
+ return cppu::supportsService(this, ServiceName);
+}
+
+Sequence< OUString > SAL_CALL
+FileProvider::getSupportedServiceNames()
+{
+ return { "com.sun.star.ucb.FileContentProvider" };
+}
+
+// XContent
+
+
+Reference< XContent > SAL_CALL
+FileProvider::queryContent(
+ const Reference< XContentIdentifier >& xIdentifier )
+{
+ init();
+ OUString aUnc;
+ bool err = fileaccess::TaskManager::getUnqFromUrl( xIdentifier->getContentIdentifier(),
+ aUnc );
+
+ if( err )
+ {
+ throw IllegalIdentifierException( THROW_WHERE );
+ }
+
+ return Reference< XContent >( new BaseContent( m_pMyShell.get(), xIdentifier, aUnc ) );
+}
+
+
+sal_Int32 SAL_CALL
+FileProvider::compareContentIds(
+ const Reference< XContentIdentifier >& Id1,
+ const Reference< XContentIdentifier >& Id2 )
+{
+ init();
+ OUString aUrl1 = Id1->getContentIdentifier();
+ OUString aUrl2 = Id2->getContentIdentifier();
+
+ sal_Int32 iComp = aUrl1.compareTo( aUrl2 );
+
+ if ( 0 != iComp )
+ {
+ OUString aPath1, aPath2;
+
+ fileaccess::TaskManager::getUnqFromUrl( aUrl1, aPath1 );
+ fileaccess::TaskManager::getUnqFromUrl( aUrl2, aPath2 );
+
+ osl::FileBase::RC error;
+ osl::DirectoryItem aItem1, aItem2;
+
+ error = osl::DirectoryItem::get( aPath1, aItem1 );
+ if ( error == osl::FileBase::E_None )
+ error = osl::DirectoryItem::get( aPath2, aItem2 );
+
+ if ( error != osl::FileBase::E_None )
+ return iComp;
+
+ osl::FileStatus aStatus1( osl_FileStatus_Mask_FileURL );
+ osl::FileStatus aStatus2( osl_FileStatus_Mask_FileURL );
+ error = aItem1.getFileStatus( aStatus1 );
+ if ( error == osl::FileBase::E_None )
+ error = aItem2.getFileStatus( aStatus2 );
+
+ if ( error == osl::FileBase::E_None )
+ {
+ iComp = aStatus1.getFileURL().compareTo( aStatus2.getFileURL() );
+
+// Quick hack for Windows to threat all file systems as case insensitive
+#ifdef _WIN32
+ if ( 0 != iComp )
+ {
+ error = osl::FileBase::getSystemPathFromFileURL( aStatus1.getFileURL(), aPath1 );
+ if ( error == osl::FileBase::E_None )
+ error = osl::FileBase::getSystemPathFromFileURL( aStatus2.getFileURL(), aPath2 );
+
+ if ( error == osl::FileBase::E_None )
+ iComp = aPath1.compareToIgnoreAsciiCase( aPath2 );
+ }
+#endif
+ }
+ }
+
+ return iComp;
+}
+
+
+Reference< XContentIdentifier > SAL_CALL
+FileProvider::createContentIdentifier(
+ const OUString& ContentId )
+{
+ init();
+ return new FileContentIdentifier( ContentId,false );
+}
+
+
+//XPropertySetInfoImpl
+
+namespace {
+
+class XPropertySetInfoImpl2
+ : public cppu::OWeakObject,
+ public XPropertySetInfo
+{
+public:
+ XPropertySetInfoImpl2();
+
+ // XInterface
+ virtual Any SAL_CALL
+ queryInterface( const Type& aType ) override;
+
+ virtual void SAL_CALL
+ acquire()
+ noexcept override;
+
+ virtual void SAL_CALL
+ release()
+ noexcept override;
+
+
+ virtual Sequence< Property > SAL_CALL
+ getProperties() override;
+
+ virtual Property SAL_CALL
+ getPropertyByName( const OUString& aName ) override;
+
+ virtual sal_Bool SAL_CALL
+ hasPropertyByName( const OUString& Name ) override;
+
+
+private:
+ Sequence< Property > m_seq;
+};
+
+}
+
+XPropertySetInfoImpl2::XPropertySetInfoImpl2()
+ : m_seq{ Property( "HostName",
+ -1,
+ cppu::UnoType<OUString>::get(),
+ PropertyAttribute::READONLY ),
+ Property( "HomeDirectory",
+ -1,
+ cppu::UnoType<OUString>::get(),
+ PropertyAttribute::READONLY ),
+ Property( "FileSystemNotation",
+ -1,
+ cppu::UnoType<sal_Int32>::get(),
+ PropertyAttribute::READONLY )}
+{
+}
+
+void SAL_CALL
+XPropertySetInfoImpl2::acquire()
+ noexcept
+{
+ OWeakObject::acquire();
+}
+
+
+void SAL_CALL
+XPropertySetInfoImpl2::release()
+ noexcept
+{
+ OWeakObject::release();
+}
+
+
+Any SAL_CALL
+XPropertySetInfoImpl2::queryInterface( const Type& rType )
+{
+ Any aRet = cppu::queryInterface( rType,
+ static_cast< XPropertySetInfo* >(this) );
+ return aRet.hasValue() ? aRet : OWeakObject::queryInterface( rType );
+}
+
+
+Property SAL_CALL
+XPropertySetInfoImpl2::getPropertyByName( const OUString& aName )
+{
+ auto pProp = std::find_if(std::cbegin(m_seq), std::cend(m_seq),
+ [&aName](const Property& rProp) { return rProp.Name == aName; });
+ if (pProp != std::cend(m_seq))
+ return *pProp;
+
+ throw UnknownPropertyException( aName );
+}
+
+
+Sequence< Property > SAL_CALL
+XPropertySetInfoImpl2::getProperties()
+{
+ return m_seq;
+}
+
+
+sal_Bool SAL_CALL
+XPropertySetInfoImpl2::hasPropertyByName(
+ const OUString& aName )
+{
+ return std::any_of(std::cbegin(m_seq), std::cend(m_seq),
+ [&aName](const Property& rProp) { return rProp.Name == aName; });
+}
+
+
+void FileProvider::initProperties()
+{
+ std::scoped_lock aGuard( m_aMutex );
+ if( m_xPropertySetInfo.is() )
+ return;
+
+ osl_getLocalHostname( &m_HostName.pData );
+
+#if defined ( UNX )
+ m_FileSystemNotation = FileSystemNotation::UNIX_NOTATION;
+#elif defined( _WIN32 )
+ m_FileSystemNotation = FileSystemNotation::DOS_NOTATION;
+#else
+ m_FileSystemNotation = FileSystemNotation::UNKNOWN_NOTATION;
+#endif
+ osl::Security aSecurity;
+ aSecurity.getHomeDir( m_HomeDirectory );
+
+ // static const sal_Int32 UNKNOWN_NOTATION = (sal_Int32)0;
+ // static const sal_Int32 UNIX_NOTATION = (sal_Int32)1;
+ // static const sal_Int32 DOS_NOTATION = (sal_Int32)2;
+ // static const sal_Int32 MAC_NOTATION = (sal_Int32)3;
+
+ m_xPropertySetInfo = new XPropertySetInfoImpl2();
+}
+
+
+// XPropertySet
+
+Reference< XPropertySetInfo > SAL_CALL
+FileProvider::getPropertySetInfo( )
+{
+ initProperties();
+ return m_xPropertySetInfo;
+}
+
+
+void SAL_CALL
+FileProvider::setPropertyValue( const OUString& aPropertyName,
+ const Any& )
+{
+ if( !(aPropertyName == "FileSystemNotation" ||
+ aPropertyName == "HomeDirectory" ||
+ aPropertyName == "HostName") )
+ throw UnknownPropertyException( aPropertyName );
+}
+
+
+Any SAL_CALL
+FileProvider::getPropertyValue(
+ const OUString& aPropertyName )
+{
+ initProperties();
+ if( aPropertyName == "FileSystemNotation" )
+ {
+ return Any(m_FileSystemNotation);
+ }
+ else if( aPropertyName == "HomeDirectory" )
+ {
+ return Any(m_HomeDirectory);
+ }
+ else if( aPropertyName == "HostName" )
+ {
+ return Any(m_HostName);
+ }
+ else
+ throw UnknownPropertyException( aPropertyName );
+}
+
+
+void SAL_CALL
+FileProvider::addPropertyChangeListener(
+ const OUString&,
+ const Reference< XPropertyChangeListener >& )
+{
+}
+
+
+void SAL_CALL
+FileProvider::removePropertyChangeListener(
+ const OUString&,
+ const Reference< XPropertyChangeListener >& )
+{
+}
+
+void SAL_CALL
+FileProvider::addVetoableChangeListener(
+ const OUString&,
+ const Reference< XVetoableChangeListener >& )
+{
+}
+
+
+void SAL_CALL
+FileProvider::removeVetoableChangeListener(
+ const OUString&,
+ const Reference< XVetoableChangeListener >& )
+{
+}
+
+
+// XFileIdentifierConverter
+
+sal_Int32 SAL_CALL
+FileProvider::getFileProviderLocality( const OUString& BaseURL )
+{
+ // If the base URL is a 'file' URL, return 10 (very 'local'), otherwise
+ // return -1 (mismatch). What is missing is a fast comparison to ASCII,
+ // ignoring case:
+ return BaseURL.getLength() >= 5
+ && (BaseURL[0] == 'F' || BaseURL[0] == 'f')
+ && (BaseURL[1] == 'I' || BaseURL[1] == 'i')
+ && (BaseURL[2] == 'L' || BaseURL[2] == 'l')
+ && (BaseURL[3] == 'E' || BaseURL[3] == 'e')
+ && BaseURL[4] == ':' ?
+ 10 : -1;
+}
+
+OUString SAL_CALL FileProvider::getFileURLFromSystemPath( const OUString&,
+ const OUString& SystemPath )
+{
+ OUString aNormalizedPath;
+ if ( osl::FileBase::getFileURLFromSystemPath( SystemPath,aNormalizedPath ) != osl::FileBase::E_None )
+ return OUString();
+
+ return aNormalizedPath;
+}
+
+OUString SAL_CALL FileProvider::getSystemPathFromFileURL( const OUString& URL )
+{
+ OUString aSystemPath;
+ if (osl::FileBase::getSystemPathFromFileURL( URL,aSystemPath ) != osl::FileBase::E_None )
+ return OUString();
+
+ return aSystemPath;
+}
+
+extern "C" SAL_DLLPUBLIC_EXPORT css::uno::XInterface*
+ucb_file_FileProvider_get_implementation(
+ css::uno::XComponentContext* context, css::uno::Sequence<css::uno::Any> const&)
+{
+ return cppu::acquire(new FileProvider(context));
+}
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/ucb/source/ucp/file/prov.hxx b/ucb/source/ucp/file/prov.hxx
new file mode 100644
index 0000000000..530010be1f
--- /dev/null
+++ b/ucb/source/ucp/file/prov.hxx
@@ -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/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#pragma once
+
+#include <com/sun/star/uno/XComponentContext.hpp>
+#include <com/sun/star/lang/XInitialization.hpp>
+#include <com/sun/star/lang/XServiceInfo.hpp>
+#include <com/sun/star/ucb/XContentProvider.hpp>
+#include <com/sun/star/ucb/XContentIdentifierFactory.hpp>
+#include <com/sun/star/beans/XPropertySet.hpp>
+#include <com/sun/star/ucb/XFileIdentifierConverter.hpp>
+#include <cppuhelper/implbase.hxx>
+#include <memory>
+#include <mutex>
+
+// FileProvider
+
+
+namespace fileaccess {
+
+ // Forward declaration
+
+ class BaseContent;
+ class TaskManager;
+
+ class FileProvider: public cppu::WeakImplHelper <
+ css::lang::XServiceInfo,
+ css::lang::XInitialization,
+ css::ucb::XContentProvider,
+ css::ucb::XContentIdentifierFactory,
+ css::beans::XPropertySet,
+ css::ucb::XFileIdentifierConverter >
+ {
+ friend class BaseContent;
+ public:
+
+ explicit FileProvider( const css::uno::Reference< css::uno::XComponentContext >& rxContext );
+ virtual ~FileProvider() 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;
+
+
+ // XInitialization
+ virtual void SAL_CALL
+ initialize(
+ const css::uno::Sequence< css::uno::Any >& aArguments ) override;
+
+
+ // XContentProvider
+ virtual css::uno::Reference< css::ucb::XContent > SAL_CALL
+ queryContent(
+ const css::uno::Reference< css::ucb::XContentIdentifier >& Identifier ) override;
+
+ // XContentIdentifierFactory
+
+ virtual css::uno::Reference< css::ucb::XContentIdentifier > SAL_CALL
+ createContentIdentifier(
+ const OUString& ContentId ) override;
+
+
+ virtual sal_Int32 SAL_CALL
+ compareContentIds(
+ const css::uno::Reference< css::ucb::XContentIdentifier >& Id1,
+ const css::uno::Reference< css::ucb::XContentIdentifier >& Id2 ) override;
+
+ // XPropertySet
+
+ virtual css::uno::Reference< css::beans::XPropertySetInfo > SAL_CALL
+ getPropertySetInfo( ) override;
+
+ virtual void SAL_CALL
+ setPropertyValue(
+ const OUString& aPropertyName,
+ const css::uno::Any& aValue ) override;
+
+ virtual css::uno::Any SAL_CALL
+ getPropertyValue(
+ const OUString& PropertyName ) override;
+
+ virtual void SAL_CALL
+ addPropertyChangeListener(
+ const OUString& aPropertyName,
+ const css::uno::Reference< css::beans::XPropertyChangeListener >& xListener ) override;
+
+ virtual void SAL_CALL
+ removePropertyChangeListener(
+ const OUString& aPropertyName,
+ const css::uno::Reference< css::beans::XPropertyChangeListener >& aListener ) override;
+
+ virtual void SAL_CALL
+ addVetoableChangeListener(
+ const OUString& PropertyName,
+ const css::uno::Reference< css::beans::XVetoableChangeListener >& aListener ) override;
+
+ virtual void SAL_CALL
+ removeVetoableChangeListener(
+ const OUString& PropertyName,
+ const css::uno::Reference< css::beans::XVetoableChangeListener >& aListener ) override;
+
+
+ // XFileIdentifierConverter
+
+ virtual sal_Int32 SAL_CALL
+ getFileProviderLocality( const OUString& BaseURL ) override;
+
+ virtual OUString SAL_CALL getFileURLFromSystemPath( const OUString& BaseURL,
+ const OUString& SystemPath ) override;
+
+ virtual OUString SAL_CALL getSystemPathFromFileURL( const OUString& URL ) override;
+
+
+ private:
+ // methods
+ void init();
+
+ // Members
+ css::uno::Reference< css::uno::XComponentContext > m_xContext;
+
+ void initProperties();
+ std::mutex m_aMutex;
+ OUString m_HostName;
+ OUString m_HomeDirectory;
+ sal_Int32 m_FileSystemNotation;
+
+ css::uno::Reference< css::beans::XPropertySetInfo > m_xPropertySetInfo;
+
+ std::unique_ptr<TaskManager> m_pMyShell;
+ };
+
+} // end namespace fileaccess
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/ucb/source/ucp/file/ucpfile1.component b/ucb/source/ucp/file/ucpfile1.component
new file mode 100644
index 0000000000..5a2efaf330
--- /dev/null
+++ b/ucb/source/ucp/file/ucpfile1.component
@@ -0,0 +1,26 @@
+<?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/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-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.ucb.FileProvider"
+ constructor="ucb_file_FileProvider_get_implementation">
+ <service name="com.sun.star.ucb.FileContentProvider"/>
+ </implementation>
+</component>
diff --git a/ucb/source/ucp/gio/gio_content.cxx b/ucb/source/ucp/gio/gio_content.cxx
new file mode 100644
index 0000000000..51df5a4e3c
--- /dev/null
+++ b/ucb/source/ucp/gio/gio_content.cxx
@@ -0,0 +1,1347 @@
+/* -*- 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:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include <sal/config.h>
+
+#include <rtl/uri.hxx>
+#include <utility>
+
+#include <string.h>
+#include <sys/types.h>
+#include <sal/macros.h>
+#include <osl/time.h>
+#include <sal/log.hxx>
+
+#include <com/sun/star/beans/IllegalTypeException.hpp>
+#include <com/sun/star/beans/PropertyValue.hpp>
+#include <com/sun/star/beans/PropertyAttribute.hpp>
+#include <com/sun/star/beans/XPropertySetInfo.hpp>
+#include <com/sun/star/io/IOException.hpp>
+#include <com/sun/star/io/XActiveDataSink.hpp>
+#include <com/sun/star/io/XOutputStream.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/ucb/ContentInfoAttribute.hpp>
+#include <com/sun/star/ucb/InsertCommandArgument.hpp>
+#include <com/sun/star/ucb/InteractiveAugmentedIOException.hpp>
+#include <com/sun/star/ucb/InteractiveNetworkGeneralException.hpp>
+#include <com/sun/star/ucb/InteractiveNetworkResolveNameException.hpp>
+#include <com/sun/star/ucb/NameClashException.hpp>
+#include <com/sun/star/ucb/OpenMode.hpp>
+#include <com/sun/star/ucb/XCommandInfo.hpp>
+#include <com/sun/star/ucb/MissingInputStreamException.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/XDynamicResultSet.hpp>
+#include <com/sun/star/ucb/XContentCreator.hpp>
+
+#include <comphelper/seekableinput.hxx>
+#include <cppuhelper/exc_hlp.hxx>
+#include <cppuhelper/queryinterface.hxx>
+#include <ucbhelper/contentidentifier.hxx>
+#include <ucbhelper/propertyvalueset.hxx>
+#include <ucbhelper/cancelcommandexecution.hxx>
+#include <ucbhelper/macros.hxx>
+#include <vcl/svapp.hxx>
+
+#include "gio_content.hxx"
+#include "gio_provider.hxx"
+#include "gio_resultset.hxx"
+#include "gio_inputstream.hxx"
+#include "gio_outputstream.hxx"
+#include "gio_mount.hxx"
+
+namespace gio
+{
+
+Content::Content(
+ const css::uno::Reference< css::uno::XComponentContext >& rxContext,
+ ContentProvider* pProvider,
+ const css::uno::Reference< css::ucb::XContentIdentifier >& Identifier)
+ : ContentImplHelper( rxContext, pProvider, Identifier ),
+ m_pProvider( pProvider ), mpFile (nullptr), mpInfo( nullptr ), mbTransient(false)
+{
+ SAL_INFO("ucb.ucp.gio", "New Content ('" << m_xIdentifier->getContentIdentifier() << "')");
+}
+
+Content::Content(
+ const css::uno::Reference< css::uno::XComponentContext >& rxContext,
+ ContentProvider* pProvider,
+ const css::uno::Reference< css::ucb::XContentIdentifier >& Identifier,
+ bool bIsFolder)
+ : ContentImplHelper( rxContext, pProvider, Identifier ),
+ m_pProvider( pProvider ), mpFile (nullptr), mpInfo( nullptr ), mbTransient(true)
+{
+ SAL_INFO("ucb.ucp.gio", "Create Content ('" << m_xIdentifier->getContentIdentifier() << "')");
+ mpInfo = g_file_info_new();
+ g_file_info_set_file_type(mpInfo, bIsFolder ? G_FILE_TYPE_DIRECTORY : G_FILE_TYPE_REGULAR);
+}
+
+Content::~Content()
+{
+ if (mpInfo) g_object_unref(mpInfo);
+ if (mpFile) g_object_unref(mpFile);
+}
+
+OUString Content::getParentURL()
+{
+ OUString sURL;
+ if (GFile* pFile = g_file_get_parent(getGFile()))
+ {
+ char* pPath = g_file_get_uri(pFile);
+ g_object_unref(pFile);
+ sURL = OUString::createFromAscii(pPath);
+ g_free(pPath);
+ }
+ return sURL;
+}
+
+void SAL_CALL Content::abort( sal_Int32 /*CommandId*/ )
+{
+ //TODO
+ //stick a map from each CommandId to a new GCancellable and propagate
+ //it throughout the g_file_* calls
+}
+
+OUString SAL_CALL Content::getContentType()
+{
+ return isFolder(css::uno::Reference< css::ucb::XCommandEnvironment >())
+ ? GIO_FOLDER_TYPE
+ : GIO_FILE_TYPE;
+}
+
+#define EXCEPT(aExcept) \
+do { \
+ if (bThrow) throw aExcept;\
+ aRet <<= aExcept;\
+} while(false)
+
+css::uno::Any convertToException(GError *pError, const css::uno::Reference< css::uno::XInterface >& rContext, bool bThrow)
+{
+ css::uno::Any aRet;
+
+ gint eCode = pError->code;
+ OUString sMessage(pError->message, strlen(pError->message), RTL_TEXTENCODING_UTF8);
+ g_error_free(pError);
+
+ OUString sName;
+
+ css::uno::Sequence< css::uno::Any > aArgs{ css::uno::Any(sName) };
+
+ switch (eCode)
+ {
+ case G_IO_ERROR_FAILED:
+ { css::io::IOException aExcept(sMessage, rContext);
+ EXCEPT(aExcept); }
+ break;
+ case G_IO_ERROR_NOT_MOUNTED:
+ { css::ucb::InteractiveAugmentedIOException aExcept(sMessage, rContext,
+ css::task::InteractionClassification_ERROR, css::ucb::IOErrorCode_NOT_EXISTING_PATH, aArgs);
+ EXCEPT(aExcept); }
+ break;
+ case G_IO_ERROR_NOT_FOUND:
+ { css::ucb::InteractiveAugmentedIOException aExcept(sMessage, rContext,
+ css::task::InteractionClassification_ERROR, css::ucb::IOErrorCode_NOT_EXISTING, aArgs);
+ EXCEPT(aExcept); }
+ break;
+ case G_IO_ERROR_EXISTS:
+ { css::ucb::NameClashException aExcept(sMessage, rContext,
+ css::task::InteractionClassification_ERROR, sName);
+ EXCEPT(aExcept); }
+ break;
+ case G_IO_ERROR_INVALID_ARGUMENT:
+ { css::lang::IllegalArgumentException aExcept(sMessage, rContext, -1 );
+ EXCEPT(aExcept); }
+ break;
+ case G_IO_ERROR_PERMISSION_DENIED:
+ { css::ucb::InteractiveAugmentedIOException aExcept(sMessage, rContext,
+ css::task::InteractionClassification_ERROR, css::ucb::IOErrorCode_ACCESS_DENIED, aArgs);
+ EXCEPT(aExcept); }
+ break;
+ case G_IO_ERROR_IS_DIRECTORY:
+ { css::ucb::InteractiveAugmentedIOException aExcept(sMessage, rContext,
+ css::task::InteractionClassification_ERROR, css::ucb::IOErrorCode_NO_FILE, aArgs);
+ EXCEPT(aExcept); }
+ break;
+ case G_IO_ERROR_NOT_REGULAR_FILE:
+ { css::ucb::InteractiveAugmentedIOException aExcept(sMessage, rContext,
+ css::task::InteractionClassification_ERROR, css::ucb::IOErrorCode_NO_FILE, aArgs);
+ EXCEPT(aExcept); }
+ break;
+ case G_IO_ERROR_NOT_DIRECTORY:
+ { css::ucb::InteractiveAugmentedIOException aExcept(sMessage, rContext,
+ css::task::InteractionClassification_ERROR, css::ucb::IOErrorCode_NO_DIRECTORY, aArgs);
+ EXCEPT(aExcept); }
+ break;
+ case G_IO_ERROR_FILENAME_TOO_LONG:
+ { css::ucb::InteractiveAugmentedIOException aExcept(sMessage, rContext,
+ css::task::InteractionClassification_ERROR, css::ucb::IOErrorCode_NAME_TOO_LONG, aArgs);
+ EXCEPT(aExcept); }
+ break;
+ case G_IO_ERROR_FAILED_HANDLED: /* Operation failed and a helper program
+ has already interacted with the user. Do not display any error
+ dialog */
+ case G_IO_ERROR_PENDING:
+ { css::ucb::InteractiveAugmentedIOException aExcept(sMessage, rContext,
+ css::task::InteractionClassification_ERROR, css::ucb::IOErrorCode_PENDING, aArgs);
+ EXCEPT(aExcept); }
+ break;
+ case G_IO_ERROR_CLOSED:
+ case G_IO_ERROR_CANCELLED:
+ case G_IO_ERROR_TOO_MANY_LINKS:
+ case G_IO_ERROR_WRONG_ETAG:
+ { css::ucb::InteractiveAugmentedIOException aExcept(sMessage, rContext,
+ css::task::InteractionClassification_ERROR, css::ucb::IOErrorCode_GENERAL, aArgs);
+ EXCEPT(aExcept); }
+ break;
+ case G_IO_ERROR_NOT_SUPPORTED:
+ case G_IO_ERROR_CANT_CREATE_BACKUP:
+ case G_IO_ERROR_WOULD_MERGE:
+ { css::ucb::InteractiveAugmentedIOException aExcept(sMessage, rContext,
+ css::task::InteractionClassification_ERROR, css::ucb::IOErrorCode_NOT_SUPPORTED, aArgs);
+ EXCEPT(aExcept); }
+ break;
+ case G_IO_ERROR_NO_SPACE:
+ { css::ucb::InteractiveAugmentedIOException aExcept(sMessage, rContext,
+ css::task::InteractionClassification_ERROR, css::ucb::IOErrorCode_OUT_OF_DISK_SPACE, aArgs);
+ EXCEPT(aExcept); }
+ break;
+ case G_IO_ERROR_INVALID_FILENAME:
+ { css::ucb::InteractiveAugmentedIOException aExcept(sMessage, rContext,
+ css::task::InteractionClassification_ERROR, css::ucb::IOErrorCode_INVALID_CHARACTER, aArgs);
+ EXCEPT(aExcept); }
+ break;
+ case G_IO_ERROR_READ_ONLY:
+ { css::ucb::InteractiveAugmentedIOException aExcept(sMessage, rContext,
+ css::task::InteractionClassification_ERROR, css::ucb::IOErrorCode_WRITE_PROTECTED, aArgs);
+ EXCEPT(aExcept); }
+ break;
+ case G_IO_ERROR_TIMED_OUT:
+ { css::ucb::InteractiveAugmentedIOException aExcept(sMessage, rContext,
+ css::task::InteractionClassification_ERROR, css::ucb::IOErrorCode_DEVICE_NOT_READY, aArgs);
+ EXCEPT(aExcept); }
+ break;
+ case G_IO_ERROR_WOULD_RECURSE:
+ { css::ucb::InteractiveAugmentedIOException aExcept(sMessage, rContext,
+ css::task::InteractionClassification_ERROR, css::ucb::IOErrorCode_RECURSIVE, aArgs);
+ EXCEPT(aExcept); }
+ break;
+ case G_IO_ERROR_BUSY:
+ case G_IO_ERROR_WOULD_BLOCK:
+ { css::ucb::InteractiveAugmentedIOException aExcept(sMessage, rContext,
+ css::task::InteractionClassification_ERROR, css::ucb::IOErrorCode_LOCKING_VIOLATION, aArgs);
+ EXCEPT(aExcept); }
+ break;
+ case G_IO_ERROR_HOST_NOT_FOUND:
+ { css::ucb::InteractiveNetworkResolveNameException aExcept(sMessage, rContext,
+ css::task::InteractionClassification_ERROR, OUString());
+ EXCEPT(aExcept);}
+ break;
+ default:
+ case G_IO_ERROR_ALREADY_MOUNTED:
+ case G_IO_ERROR_NOT_EMPTY:
+ case G_IO_ERROR_NOT_SYMBOLIC_LINK:
+ case G_IO_ERROR_NOT_MOUNTABLE_FILE:
+ { css::ucb::InteractiveNetworkGeneralException aExcept(sMessage, rContext,
+ css::task::InteractionClassification_ERROR);
+ EXCEPT(aExcept);}
+ break;
+ }
+ return aRet;
+}
+
+void convertToIOException(GError *pError, const css::uno::Reference< css::uno::XInterface >& rContext)
+{
+ try
+ {
+ convertToException(pError, rContext);
+ }
+ catch (const css::io::IOException&)
+ {
+ throw;
+ }
+ catch (const css::uno::RuntimeException&)
+ {
+ throw;
+ }
+ catch (const css::uno::Exception& e)
+ {
+ css::uno::Any a(cppu::getCaughtException());
+ throw css::lang::WrappedTargetRuntimeException(
+ "wrapped Exception " + e.Message,
+ css::uno::Reference<css::uno::XInterface>(), a);
+ }
+}
+
+css::uno::Any Content::mapGIOError( GError *pError )
+{
+ if (!pError)
+ return getBadArgExcept();
+
+ return convertToException(pError, getXWeak(), false);
+}
+
+css::uno::Any Content::getBadArgExcept()
+{
+ return css::uno::Any( css::lang::IllegalArgumentException(
+ "Wrong argument type!",
+ getXWeak(), -1) );
+}
+
+namespace {
+
+class MountOperation
+{
+ ucb::ucp::gio::glib::MainContextRef mContext;
+ GMainLoop *mpLoop;
+ GMountOperation *mpAuthentication;
+ GError *mpError;
+ static void Completed(GObject *source, GAsyncResult *res, gpointer user_data);
+public:
+ explicit MountOperation(const css::uno::Reference< css::ucb::XCommandEnvironment >& xEnv);
+ ~MountOperation();
+ GError *Mount(GFile *pFile);
+};
+
+}
+
+MountOperation::MountOperation(const css::uno::Reference< css::ucb::XCommandEnvironment >& xEnv) : mpError(nullptr)
+{
+ ucb::ucp::gio::glib::MainContextRef oldContext(g_main_context_ref_thread_default());
+ mContext.reset(g_main_context_new());
+ mpLoop = g_main_loop_new(mContext.get(), FALSE);
+ g_main_context_push_thread_default(mContext.get());
+ mpAuthentication = ooo_mount_operation_new(std::move(oldContext), xEnv);
+}
+
+void MountOperation::Completed(GObject *source, GAsyncResult *res, gpointer user_data)
+{
+ MountOperation *pThis = static_cast<MountOperation*>(user_data);
+ g_file_mount_enclosing_volume_finish(G_FILE(source), res, &(pThis->mpError));
+ g_main_loop_quit(pThis->mpLoop);
+}
+
+GError *MountOperation::Mount(GFile *pFile)
+{
+ g_file_mount_enclosing_volume(pFile, G_MOUNT_MOUNT_NONE, mpAuthentication, nullptr, MountOperation::Completed, this);
+ {
+ //HACK: At least the gdk_threads_set_lock_functions(GdkThreadsEnter,
+ // GdkThreadsLeave) call in vcl/unx/gtk/app/gtkinst.cxx will lead to
+ // GdkThreadsLeave unlock the SolarMutex down to zero at the end of
+ // g_main_loop_run, so we need ~SolarMutexReleaser to raise it back to
+ // the original value again:
+ if (comphelper::SolarMutex::get()->IsCurrentThread())
+ {
+ SolarMutexReleaser rel;
+ g_main_loop_run(mpLoop);
+ }
+ else
+ {
+ g_main_loop_run(mpLoop);
+ }
+ }
+ return mpError;
+}
+
+MountOperation::~MountOperation()
+{
+ g_object_unref(mpAuthentication);
+ g_main_context_pop_thread_default(mContext.get());
+ g_main_loop_unref(mpLoop);
+}
+
+GFileInfo* Content::getGFileInfo(const css::uno::Reference< css::ucb::XCommandEnvironment >& xEnv, GError **ppError)
+{
+ GError * err = nullptr;
+ if (mpInfo == nullptr && !mbTransient) {
+ for (bool retried = false;; retried = true) {
+ mpInfo = g_file_query_info(
+ getGFile(), "*", G_FILE_QUERY_INFO_NONE, nullptr, &err);
+ if (mpInfo != nullptr) {
+ break;
+ }
+ assert(err != nullptr);
+ if (err->code != G_IO_ERROR_NOT_MOUNTED || retried) {
+ break;
+ }
+ SAL_INFO(
+ "ucb.ucp.gio",
+ "G_IO_ERROR_NOT_MOUNTED \"" << err->message
+ << "\", trying to mount");
+ g_error_free(err);
+ err = MountOperation(xEnv).Mount(getGFile());
+ if (err != nullptr) {
+ break;
+ }
+ }
+ }
+ if (ppError != nullptr) {
+ *ppError = err;
+ } else if (err != nullptr) {
+ SAL_WARN(
+ "ucb.ucp.gio",
+ "ignoring GError \"" << err->message << "\" for <"
+ << m_xIdentifier->getContentIdentifier() << ">");
+ g_error_free(err);
+ }
+ return mpInfo;
+}
+
+GFile* Content::getGFile()
+{
+ if (!mpFile)
+ mpFile = g_file_new_for_uri(OUStringToOString(m_xIdentifier->getContentIdentifier(), RTL_TEXTENCODING_UTF8).getStr());
+ return mpFile;
+}
+
+bool Content::isFolder(const css::uno::Reference< css::ucb::XCommandEnvironment >& xEnv)
+{
+ GFileInfo *pInfo = getGFileInfo(xEnv);
+ return pInfo && (g_file_info_get_file_type(pInfo) == G_FILE_TYPE_DIRECTORY);
+}
+
+static css::util::DateTime getDateFromUnix (time_t t)
+{
+ TimeValue tv;
+ tv.Nanosec = 0;
+ // coverity[store_truncates_time_t] - TODO: sal_uInt32 TimeValue::Seconds is only large enough
+ // for integer time_t values < 2^32 representing dates until year 2106:
+ tv.Seconds = t;
+ oslDateTime dt;
+
+ if ( osl_getDateTimeFromTimeValue( &tv, &dt ) )
+ return css::util::DateTime( 0, dt.Seconds, dt.Minutes, dt.Hours,
+ dt.Day, dt.Month, dt.Year, false);
+ else
+ return css::util::DateTime();
+}
+
+css::uno::Reference< css::sdbc::XRow > Content::getPropertyValues(
+ const css::uno::Sequence< css::beans::Property >& rProperties,
+ const css::uno::Reference< css::ucb::XCommandEnvironment >& xEnv )
+{
+ rtl::Reference< ::ucbhelper::PropertyValueSet > xRow = new ::ucbhelper::PropertyValueSet( m_xContext );
+
+ GFileInfo *pInfo = nullptr;
+ for( const css::beans::Property& rProp : rProperties )
+ {
+ if ( rProp.Name == "IsDocument" )
+ {
+ getFileInfo(xEnv, &pInfo, true);
+ if (pInfo != nullptr && g_file_info_has_attribute(pInfo, G_FILE_ATTRIBUTE_STANDARD_TYPE))
+ xRow->appendBoolean( rProp, ( g_file_info_get_file_type( pInfo ) == G_FILE_TYPE_REGULAR ||
+ g_file_info_get_file_type( pInfo ) == G_FILE_TYPE_UNKNOWN ) );
+ else
+ xRow->appendVoid( rProp );
+ }
+ else if ( rProp.Name == "IsFolder" )
+ {
+ getFileInfo(xEnv, &pInfo, true);
+ if (pInfo != nullptr && g_file_info_has_attribute( pInfo, G_FILE_ATTRIBUTE_STANDARD_TYPE) )
+ xRow->appendBoolean( rProp, ( g_file_info_get_file_type( pInfo ) == G_FILE_TYPE_DIRECTORY ));
+ else
+ xRow->appendVoid( rProp );
+ }
+ else if ( rProp.Name == "Title" )
+ {
+ getFileInfo(xEnv, &pInfo, false);
+ if (pInfo != nullptr && g_file_info_has_attribute(pInfo, G_FILE_ATTRIBUTE_STANDARD_DISPLAY_NAME))
+ {
+ const char *pName = g_file_info_get_display_name(pInfo);
+ xRow->appendString( rProp, OUString(pName, strlen(pName), RTL_TEXTENCODING_UTF8) );
+ }
+ else
+ xRow->appendVoid(rProp);
+ }
+ else if ( rProp.Name == "IsReadOnly" )
+ {
+ getFileInfo(xEnv, &pInfo, true);
+ if (pInfo != nullptr && g_file_info_has_attribute( pInfo, G_FILE_ATTRIBUTE_ACCESS_CAN_WRITE ) )
+ xRow->appendBoolean( rProp, !g_file_info_get_attribute_boolean( pInfo, G_FILE_ATTRIBUTE_ACCESS_CAN_WRITE) );
+ else
+ xRow->appendVoid( rProp );
+ }
+ else if ( rProp.Name == "DateCreated" )
+ {
+ getFileInfo(xEnv, &pInfo, true);
+ if (pInfo != nullptr && g_file_info_has_attribute( pInfo, G_FILE_ATTRIBUTE_TIME_CREATED ) )
+ xRow->appendTimestamp( rProp, getDateFromUnix(g_file_info_get_attribute_uint64(pInfo, G_FILE_ATTRIBUTE_TIME_CREATED)) );
+ else
+ xRow->appendVoid( rProp );
+ }
+ else if ( rProp.Name == "DateModified" )
+ {
+ getFileInfo(xEnv, &pInfo, true);
+ if (pInfo != nullptr && g_file_info_has_attribute( pInfo, G_FILE_ATTRIBUTE_TIME_CHANGED ) )
+ xRow->appendTimestamp( rProp, getDateFromUnix(g_file_info_get_attribute_uint64(pInfo, G_FILE_ATTRIBUTE_TIME_CHANGED)) );
+ else
+ xRow->appendVoid( rProp );
+ }
+ else if ( rProp.Name == "Size" )
+ {
+ getFileInfo(xEnv, &pInfo, true);
+ if (pInfo != nullptr && g_file_info_has_attribute( pInfo, G_FILE_ATTRIBUTE_STANDARD_SIZE) )
+ xRow->appendLong( rProp, ( g_file_info_get_size( pInfo ) ));
+ else
+ xRow->appendVoid( rProp );
+ }
+ else if ( rProp.Name == "IsVolume" )
+ {
+ //What do we use this for ?
+ xRow->appendBoolean( rProp, false );
+ }
+ else if ( rProp.Name == "IsCompactDisc" )
+ {
+ getFileInfo(xEnv, &pInfo, true);
+ if (pInfo != nullptr && g_file_info_has_attribute( pInfo, G_FILE_ATTRIBUTE_MOUNTABLE_CAN_EJECT ) )
+ xRow->appendBoolean( rProp, g_file_info_get_attribute_boolean(pInfo, G_FILE_ATTRIBUTE_MOUNTABLE_CAN_EJECT) );
+ else
+ xRow->appendVoid( rProp );
+ }
+ else if ( rProp.Name == "IsRemoveable" )
+ {
+ getFileInfo(xEnv, &pInfo, true);
+ if (pInfo != nullptr && g_file_info_has_attribute( pInfo, G_FILE_ATTRIBUTE_MOUNTABLE_CAN_UNMOUNT ) )
+ xRow->appendBoolean( rProp, g_file_info_get_attribute_boolean(pInfo, G_FILE_ATTRIBUTE_MOUNTABLE_CAN_UNMOUNT ) );
+ else
+ xRow->appendVoid( rProp );
+ }
+ else if ( rProp.Name == "IsFloppy" )
+ {
+ xRow->appendBoolean( rProp, false );
+ }
+ else if ( rProp.Name == "IsHidden" )
+ {
+ getFileInfo(xEnv, &pInfo, true);
+ if (pInfo != nullptr && g_file_info_has_attribute( pInfo, G_FILE_ATTRIBUTE_STANDARD_IS_HIDDEN) )
+ xRow->appendBoolean( rProp, ( g_file_info_get_is_hidden ( pInfo ) ) );
+ else
+ xRow->appendVoid( rProp );
+ }
+ else if ( rProp.Name == "CreatableContentsInfo" )
+ {
+ xRow->appendObject( rProp, css::uno::Any( queryCreatableContentsInfo( xEnv ) ) );
+ }
+ else
+ {
+ SAL_WARN(
+ "ucb.ucp.gio",
+ "Looking for unsupported property " << rProp.Name);
+ xRow->appendVoid(rProp);
+ }
+ }
+
+ return xRow;
+}
+
+static css::lang::IllegalAccessException
+getReadOnlyException( const css::uno::Reference< css::uno::XInterface >& rContext )
+{
+ return css::lang::IllegalAccessException ("Property is read-only!", rContext );
+}
+
+void Content::queryChildren( ContentRefList& rChildren )
+{
+ // Obtain a list with a snapshot of all currently instantiated contents
+ // from provider and extract the contents which are direct children
+ // of this content.
+
+ ucbhelper::ContentRefList aAllContents;
+ m_xProvider->queryExistingContents( aAllContents );
+
+ OUString aURL = m_xIdentifier->getContentIdentifier();
+ sal_Int32 nURLPos = aURL.lastIndexOf( '/' );
+
+ if ( nURLPos != ( aURL.getLength() - 1 ) )
+ aURL += "/";
+
+ sal_Int32 nLen = aURL.getLength();
+
+ for ( const auto& rContent : aAllContents )
+ {
+ ucbhelper::ContentImplHelperRef xChild = rContent;
+ OUString aChildURL = xChild->getIdentifier()->getContentIdentifier();
+
+ // Is aURL a prefix of aChildURL?
+ if ( ( aChildURL.getLength() > nLen ) && aChildURL.startsWith( aURL ) )
+ {
+ sal_Int32 nPos = aChildURL.indexOf( '/', nLen );
+
+ if ( ( nPos == -1 ) || ( nPos == ( aChildURL.getLength() - 1 ) ) )
+ {
+ // No further slashes / only a final slash. It's a child!
+ rChildren.emplace_back(static_cast< ::gio::Content * >(xChild.get() ) );
+ }
+ }
+ }
+}
+
+bool Content::exchangeIdentity( const css::uno::Reference< css::ucb::XContentIdentifier >& xNewId )
+{
+ if ( !xNewId.is() )
+ return false;
+
+ css::uno::Reference< css::ucb::XContent > xThis = this;
+
+ if ( mbTransient )
+ {
+ m_xIdentifier = xNewId;
+ return false;
+ }
+
+ OUString aOldURL = m_xIdentifier->getContentIdentifier();
+
+ // Exchange own identity.
+ if ( exchange( xNewId ) )
+ {
+ // Process instantiated children...
+ ContentRefList aChildren;
+ queryChildren( aChildren );
+
+ for ( const auto& rChild : aChildren )
+ {
+ ContentRef xChild = rChild;
+
+ // Create new content identifier for the child...
+ css::uno::Reference< css::ucb::XContentIdentifier > xOldChildId = xChild->getIdentifier();
+ OUString aOldChildURL = xOldChildId->getContentIdentifier();
+ OUString aNewChildURL = aOldChildURL.replaceAt(
+ 0, aOldURL.getLength(), xNewId->getContentIdentifier() );
+
+ css::uno::Reference< css::ucb::XContentIdentifier > xNewChildId
+ = new ::ucbhelper::ContentIdentifier( aNewChildURL );
+
+ if ( !xChild->exchangeIdentity( xNewChildId ) )
+ return false;
+ }
+ return true;
+ }
+
+ return false;
+}
+
+void Content::getFileInfo(
+ css::uno::Reference<css::ucb::XCommandEnvironment> const & env, GFileInfo ** info, bool fail)
+{
+ assert(info != nullptr);
+ if (*info != nullptr)
+ return;
+
+ GError * err = nullptr;
+ *info = getGFileInfo(env, &err);
+ if (*info == nullptr && !mbTransient && fail)
+ {
+ ucbhelper::cancelCommandExecution(mapGIOError(err), env);
+ }
+ else if (err != nullptr)
+ {
+ g_error_free(err);
+ }
+}
+
+css::uno::Sequence< css::uno::Any > Content::setPropertyValues(
+ const css::uno::Sequence< css::beans::PropertyValue >& rValues,
+ const css::uno::Reference< css::ucb::XCommandEnvironment >& xEnv )
+{
+ GError *pError=nullptr;
+ GFileInfo *pNewInfo=nullptr;
+ GFileInfo *pInfo = getGFileInfo(xEnv, &pError);
+ if (pInfo)
+ pNewInfo = g_file_info_dup(pInfo);
+ else
+ {
+ if (!mbTransient)
+ ucbhelper::cancelCommandExecution(mapGIOError(pError), xEnv);
+ else
+ {
+ if (pError)
+ g_error_free(pError);
+ pNewInfo = g_file_info_new();
+ }
+ }
+
+ sal_Int32 nCount = rValues.getLength();
+
+ css::beans::PropertyChangeEvent aEvent;
+ aEvent.Source = getXWeak();
+ aEvent.Further = false;
+ aEvent.PropertyHandle = -1;
+
+ sal_Int32 nChanged = 0, nTitlePos = -1;
+ OUString aNewTitle;
+ css::uno::Sequence< css::beans::PropertyChangeEvent > aChanges(nCount);
+ auto aChangesRange = asNonConstRange(aChanges);
+
+ css::uno::Sequence< css::uno::Any > aRet( nCount );
+ auto aRetRange = asNonConstRange(aRet);
+ const css::beans::PropertyValue* pValues = rValues.getConstArray();
+ for ( sal_Int32 n = 0; n < nCount; ++n )
+ {
+ const css::beans::PropertyValue& rValue = pValues[ n ];
+ SAL_INFO("ucb.ucp.gio", "Set prop '" << rValue.Name << "'");
+ if ( rValue.Name == "ContentType" ||
+ rValue.Name == "MediaType" ||
+ rValue.Name == "IsDocument" ||
+ rValue.Name == "IsFolder" ||
+ rValue.Name == "Size" ||
+ rValue.Name == "CreatableContentsInfo" )
+ {
+ aRetRange[ n ] <<= getReadOnlyException( getXWeak() );
+ }
+ else if ( rValue.Name == "Title" )
+ {
+ if (!( rValue.Value >>= aNewTitle ))
+ {
+ aRetRange[ n ] <<= css::beans::IllegalTypeException
+ ( "Property value has wrong type!",
+ getXWeak() );
+ continue;
+ }
+
+ if ( aNewTitle.isEmpty() )
+ {
+ aRetRange[ n ] <<= css::lang::IllegalArgumentException
+ ( "Empty title not allowed!",
+ getXWeak(), -1 );
+ continue;
+
+ }
+
+ OString sNewTitle = OUStringToOString(aNewTitle, RTL_TEXTENCODING_UTF8);
+ const char *newName = sNewTitle.getStr();
+ const char *oldName = g_file_info_get_name( pInfo);
+
+ if (!newName || !oldName || strcmp(newName, oldName))
+ {
+ SAL_INFO("ucb.ucp.gio", "Set new name to '" << newName << "'");
+
+ aEvent.PropertyName = "Title";
+ if (oldName)
+ aEvent.OldValue <<= OUString(oldName, strlen(oldName), RTL_TEXTENCODING_UTF8);
+ aEvent.NewValue <<= aNewTitle;
+ aChangesRange[ nChanged ] = aEvent;
+ nTitlePos = nChanged++;
+
+ g_file_info_set_name(pNewInfo, newName);
+ }
+ }
+ else
+ {
+ SAL_WARN("ucb.ucp.gio", "Unknown property " << rValue.Name);
+ aRetRange[ n ] <<= getReadOnlyException( getXWeak() );
+ }
+ }
+
+ if (nChanged)
+ {
+ bool bOk = true;
+ if (!mbTransient)
+ {
+ if ((bOk = doSetFileInfo(pNewInfo)))
+ {
+ for (sal_Int32 i = 0; i < nChanged; ++i)
+ aRetRange[ i ] = getBadArgExcept();
+ }
+ }
+
+ if (bOk)
+ {
+ if (nTitlePos > -1)
+ {
+ OUString aNewURL = getParentURL();
+ if (!aNewURL.isEmpty() && aNewURL[aNewURL.getLength() - 1] != '/')
+ aNewURL += "/";
+ aNewURL += aNewTitle;
+
+ css::uno::Reference< css::ucb::XContentIdentifier > xNewId
+ = new ::ucbhelper::ContentIdentifier( aNewURL );
+
+ if (!exchangeIdentity( xNewId ) )
+ {
+ aRetRange[ nTitlePos ] <<= css::uno::Exception
+ ( "Exchange failed!",
+ getXWeak() );
+ }
+ }
+
+ if (!mbTransient) //Discard and refetch
+ {
+ g_object_unref(mpInfo);
+ mpInfo = nullptr;
+ }
+
+ if (mpInfo)
+ {
+ g_file_info_copy_into(pNewInfo, mpInfo);
+ g_object_unref(pNewInfo);
+ }
+ else
+ mpInfo = pNewInfo;
+
+ pNewInfo = nullptr;
+
+ if (mpFile) //Discard and refetch
+ {
+ g_object_unref(mpFile);
+ mpFile = nullptr;
+ }
+ }
+
+ aChanges.realloc( nChanged );
+ notifyPropertiesChange( aChanges );
+ }
+
+ if (pNewInfo)
+ g_object_unref(pNewInfo);
+
+ return aRet;
+}
+
+bool Content::doSetFileInfo(GFileInfo *pNewInfo)
+{
+ g_assert (!mbTransient);
+
+ bool bOk = true;
+ GFile *pFile = getGFile();
+ if(!g_file_set_attributes_from_info(pFile, pNewInfo, G_FILE_QUERY_INFO_NONE, nullptr, nullptr))
+ bOk = false;
+ return bOk;
+}
+
+const int TRANSFER_BUFFER_SIZE = 65536;
+
+void Content::copyData( const css::uno::Reference< css::io::XInputStream >& xIn,
+ const css::uno::Reference< css::io::XOutputStream >& xOut )
+{
+ css::uno::Sequence< sal_Int8 > theData( TRANSFER_BUFFER_SIZE );
+
+ g_return_if_fail( xIn.is() && xOut.is() );
+
+ while ( xIn->readBytes( theData, TRANSFER_BUFFER_SIZE ) > 0 )
+ xOut->writeBytes( theData );
+
+ xOut->closeOutput();
+}
+
+bool Content::feedSink( const css::uno::Reference< css::uno::XInterface >& xSink )
+{
+ if ( !xSink.is() )
+ return false;
+
+ css::uno::Reference< css::io::XOutputStream > xOut(xSink, css::uno::UNO_QUERY );
+ css::uno::Reference< css::io::XActiveDataSink > xDataSink(xSink, css::uno::UNO_QUERY );
+
+ if ( !xOut.is() && !xDataSink.is() )
+ return false;
+
+ GError *pError=nullptr;
+ GFileInputStream *pStream = g_file_read(getGFile(), nullptr, &pError);
+ if (!pStream)
+ convertToException(pError, getXWeak());
+
+ css::uno::Reference< css::io::XInputStream > xIn(
+ new comphelper::OSeekableInputWrapper(
+ new ::gio::InputStream(pStream), m_xContext));
+
+ if ( xOut.is() )
+ copyData( xIn, xOut );
+
+ if ( xDataSink.is() )
+ xDataSink->setInputStream( xIn );
+
+ return true;
+}
+
+css::uno::Any Content::open(const css::ucb::OpenCommandArgument2 & rOpenCommand,
+ const css::uno::Reference< css::ucb::XCommandEnvironment > & xEnv )
+{
+ bool bIsFolder = isFolder(xEnv);
+
+ if (!g_file_query_exists(getGFile(), nullptr))
+ {
+ css::uno::Sequence< css::uno::Any > aArgs{ css::uno::Any(
+ m_xIdentifier->getContentIdentifier()) };
+ css::uno::Any aErr(
+ css::ucb::InteractiveAugmentedIOException(OUString(), getXWeak(),
+ css::task::InteractionClassification_ERROR,
+ bIsFolder ? css::ucb::IOErrorCode_NOT_EXISTING_PATH : css::ucb::IOErrorCode_NOT_EXISTING, aArgs)
+ );
+
+ ucbhelper::cancelCommandExecution(aErr, xEnv);
+ }
+
+ css::uno::Any aRet;
+
+ bool bOpenFolder = (
+ ( rOpenCommand.Mode == css::ucb::OpenMode::ALL ) ||
+ ( rOpenCommand.Mode == css::ucb::OpenMode::FOLDERS ) ||
+ ( rOpenCommand.Mode == css::ucb::OpenMode::DOCUMENTS )
+ );
+
+ if ( bOpenFolder && bIsFolder )
+ {
+ css::uno::Reference< css::ucb::XDynamicResultSet > xSet
+ = new DynamicResultSet( m_xContext, this, rOpenCommand, xEnv );
+ aRet <<= xSet;
+ }
+ else if ( rOpenCommand.Sink.is() )
+ {
+ if (
+ ( rOpenCommand.Mode == css::ucb::OpenMode::DOCUMENT_SHARE_DENY_NONE ) ||
+ ( rOpenCommand.Mode == css::ucb::OpenMode::DOCUMENT_SHARE_DENY_WRITE )
+ )
+ {
+ ucbhelper::cancelCommandExecution(
+ css::uno::Any ( css::ucb::UnsupportedOpenModeException
+ ( OUString(), getXWeak(),
+ sal_Int16( rOpenCommand.Mode ) ) ),
+ xEnv );
+ }
+
+ if ( !feedSink( rOpenCommand.Sink ) )
+ {
+ // Note: rOpenCommand.Sink may contain an XStream
+ // implementation. Support for this type of
+ // sink is optional...
+ SAL_WARN("ucb.ucp.gio", "Failed to load data from '" << m_xIdentifier->getContentIdentifier() << "'");
+
+ ucbhelper::cancelCommandExecution(
+ css::uno::Any (css::ucb::UnsupportedDataSinkException
+ ( OUString(), getXWeak(),
+ rOpenCommand.Sink ) ),
+ xEnv );
+ }
+ }
+ else
+ SAL_INFO("ucb.ucp.gio", "Open falling through ...");
+ return aRet;
+}
+
+css::uno::Any SAL_CALL Content::execute(
+ const css::ucb::Command& aCommand,
+ sal_Int32 /*CommandId*/,
+ const css::uno::Reference< css::ucb::XCommandEnvironment >& xEnv )
+{
+ SAL_INFO("ucb.ucp.gio", "Content::execute " << aCommand.Name);
+ css::uno::Any aRet;
+
+ if ( aCommand.Name == "getPropertyValues" )
+ {
+ css::uno::Sequence< css::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" )
+ {
+ css::ucb::OpenCommandArgument2 aOpenCommand;
+ if ( !( aCommand.Argument >>= aOpenCommand ) )
+ ucbhelper::cancelCommandExecution ( getBadArgExcept (), xEnv );
+ aRet = open( aOpenCommand, xEnv );
+ }
+ else if ( aCommand.Name == "transfer" )
+ {
+ css::ucb::TransferInfo transferArgs;
+ if ( !( aCommand.Argument >>= transferArgs ) )
+ ucbhelper::cancelCommandExecution ( getBadArgExcept (), xEnv );
+ transfer( transferArgs, xEnv );
+ }
+ else if ( aCommand.Name == "setPropertyValues" )
+ {
+ css::uno::Sequence< css::beans::PropertyValue > aProperties;
+ if ( !( aCommand.Argument >>= aProperties ) || !aProperties.hasElements() )
+ ucbhelper::cancelCommandExecution ( getBadArgExcept (), xEnv );
+ aRet <<= setPropertyValues( aProperties, xEnv );
+ }
+ else if (aCommand.Name == "createNewContent"
+ && isFolder( xEnv ) )
+ {
+ css::ucb::ContentInfo arg;
+ if ( !( aCommand.Argument >>= arg ) )
+ ucbhelper::cancelCommandExecution ( getBadArgExcept (), xEnv );
+ aRet <<= createNewContent( arg );
+ }
+ else if ( aCommand.Name == "insert" )
+ {
+ css::ucb::InsertCommandArgument arg;
+ if ( !( aCommand.Argument >>= arg ) )
+ ucbhelper::cancelCommandExecution ( getBadArgExcept (), xEnv );
+ insert( arg.Data, arg.ReplaceExisting, xEnv );
+ }
+ else if ( aCommand.Name == "delete" )
+ {
+ bool bDeletePhysical = false;
+ aCommand.Argument >>= bDeletePhysical;
+
+ //If no delete physical, try and trashcan it, if that doesn't work go
+ //ahead and try and delete it anyway
+ if (!bDeletePhysical && !g_file_trash(getGFile(), nullptr, nullptr))
+ bDeletePhysical = true;
+
+ if (bDeletePhysical)
+ {
+ GError *pError = nullptr;
+ if (!g_file_delete( getGFile(), nullptr, &pError))
+ ucbhelper::cancelCommandExecution(mapGIOError(pError), xEnv);
+ }
+
+ destroy( bDeletePhysical );
+ }
+ else
+ {
+ SAL_WARN("ucb.ucp.gio", "Unknown command " << aCommand.Name);
+
+ ucbhelper::cancelCommandExecution
+ ( css::uno::Any( css::ucb::UnsupportedCommandException
+ ( OUString(),
+ getXWeak() ) ),
+ xEnv );
+ }
+
+ return aRet;
+}
+
+void Content::destroy( bool bDeletePhysical )
+{
+ css::uno::Reference< css::ucb::XContent > xThis = this;
+
+ deleted();
+
+ ::gio::Content::ContentRefList aChildren;
+ queryChildren( aChildren );
+
+ for ( auto& rChild : aChildren )
+ {
+ rChild->destroy( bDeletePhysical );
+ }
+}
+
+void Content::insert(const css::uno::Reference< css::io::XInputStream > &xInputStream,
+ bool bReplaceExisting, const css::uno::Reference< css::ucb::XCommandEnvironment > &xEnv )
+{
+ GError *pError = nullptr;
+ GFileInfo *pInfo = getGFileInfo(xEnv);
+
+ if ( pInfo &&
+ g_file_info_has_attribute(pInfo, G_FILE_ATTRIBUTE_STANDARD_TYPE) &&
+ g_file_info_get_file_type(pInfo) == G_FILE_TYPE_DIRECTORY )
+ {
+ SAL_INFO("ucb.ucp.gio", "Make directory");
+ if( !g_file_make_directory( getGFile(), nullptr, &pError))
+ ucbhelper::cancelCommandExecution(mapGIOError(pError), xEnv);
+ return;
+ }
+
+ if ( !xInputStream.is() )
+ {
+ ucbhelper::cancelCommandExecution( css::uno::Any
+ ( css::ucb::MissingInputStreamException
+ ( OUString(), getXWeak() ) ),
+ xEnv );
+ }
+
+ GFileOutputStream* pOutStream = nullptr;
+ if ( bReplaceExisting )
+ {
+ if (!(pOutStream = g_file_replace(getGFile(), nullptr, false, G_FILE_CREATE_PRIVATE, nullptr, &pError)))
+ ucbhelper::cancelCommandExecution(mapGIOError(pError), xEnv);
+ }
+ else
+ {
+ if (!(pOutStream = g_file_create (getGFile(), G_FILE_CREATE_PRIVATE, nullptr, &pError)))
+ ucbhelper::cancelCommandExecution(mapGIOError(pError), xEnv);
+ }
+
+ css::uno::Reference < css::io::XOutputStream > xOutput = new ::gio::OutputStream(pOutStream);
+ copyData( xInputStream, xOutput );
+
+ if (mbTransient)
+ {
+ mbTransient = false;
+ inserted();
+ }
+}
+
+const GFileCopyFlags DEFAULT_COPYDATA_FLAGS =
+ static_cast<GFileCopyFlags>(G_FILE_COPY_OVERWRITE|G_FILE_COPY_TARGET_DEFAULT_PERMS);
+
+void Content::transfer( const css::ucb::TransferInfo& aTransferInfo, const css::uno::Reference< css::ucb::XCommandEnvironment >& xEnv )
+{
+ OUString sDest = m_xIdentifier->getContentIdentifier();
+ if (!sDest.endsWith("/")) {
+ sDest += "/";
+ }
+ if (!aTransferInfo.NewTitle.isEmpty())
+ {
+ sDest += rtl::Uri::encode( aTransferInfo.NewTitle,
+ rtl_UriCharClassPchar,
+ rtl_UriEncodeIgnoreEscapes,
+ RTL_TEXTENCODING_UTF8 );
+ }
+ else
+ sDest += OUString::createFromAscii(g_file_get_basename(getGFile()));
+
+ GFile *pDest = g_file_new_for_uri(OUStringToOString(sDest, RTL_TEXTENCODING_UTF8).getStr());
+ GFile *pSource = g_file_new_for_uri(OUStringToOString(aTransferInfo.SourceURL, RTL_TEXTENCODING_UTF8).getStr());
+
+ bool bSuccess = false;
+ GError *pError = nullptr;
+ if (aTransferInfo.MoveData)
+ bSuccess = g_file_move(pSource, pDest, G_FILE_COPY_OVERWRITE, nullptr, nullptr, nullptr, &pError);
+ else
+ bSuccess = g_file_copy(pSource, pDest, DEFAULT_COPYDATA_FLAGS, nullptr, nullptr, nullptr, &pError);
+ g_object_unref(pSource);
+ g_object_unref(pDest);
+ if (!bSuccess) {
+ SAL_INFO(
+ "ucb.ucp.gio",
+ "transfer <" << aTransferInfo.SourceURL << "> to <" << sDest << "> (MoveData = "
+ << int(aTransferInfo.MoveData) << ") failed with \"" << pError->message << "\"");
+ ucbhelper::cancelCommandExecution(mapGIOError(pError), xEnv);
+ }
+}
+
+css::uno::Sequence< css::ucb::ContentInfo > Content::queryCreatableContentsInfo(
+ const css::uno::Reference< css::ucb::XCommandEnvironment >& xEnv)
+{
+ if ( isFolder( xEnv ) )
+ {
+
+ // Minimum set of props we really need
+ css::uno::Sequence< css::beans::Property > props
+ {
+ { "Title", -1, cppu::UnoType<OUString>::get(), css::beans::PropertyAttribute::MAYBEVOID | css::beans::PropertyAttribute::BOUND }
+ };
+
+ return
+ {
+ { GIO_FILE_TYPE, ( css::ucb::ContentInfoAttribute::INSERT_WITH_INPUTSTREAM | css::ucb::ContentInfoAttribute::KIND_DOCUMENT ), props },
+ { GIO_FOLDER_TYPE, css::ucb::ContentInfoAttribute::KIND_FOLDER, props }
+ };
+ }
+ else
+ {
+ return {};
+ }
+}
+
+css::uno::Sequence< css::ucb::ContentInfo > SAL_CALL Content::queryCreatableContentsInfo()
+{
+ return queryCreatableContentsInfo( css::uno::Reference< css::ucb::XCommandEnvironment >() );
+}
+
+css::uno::Reference< css::ucb::XContent >
+ SAL_CALL Content::createNewContent( const css::ucb::ContentInfo& Info )
+{
+ bool create_document;
+ const char *name;
+
+ if ( Info.Type == GIO_FILE_TYPE )
+ create_document = true;
+ else if ( Info.Type == GIO_FOLDER_TYPE )
+ create_document = false;
+ else
+ {
+ SAL_WARN("ucb.ucp.gio", "Failed to create new content '" << Info.Type << "'");
+ return css::uno::Reference< css::ucb::XContent >();
+ }
+
+ SAL_INFO("ucb.ucp.gio", "createNewContent (" << create_document << ")");
+ OUString aURL = m_xIdentifier->getContentIdentifier();
+
+ if ( ( aURL.lastIndexOf( '/' ) + 1 ) != aURL.getLength() )
+ aURL += "/";
+
+ name = create_document ? "[New_Content]" : "[New_Collection]";
+ aURL += OUString::createFromAscii( name );
+
+ css::uno::Reference< css::ucb::XContentIdentifier > xId(new ::ucbhelper::ContentIdentifier(aURL));
+
+ try
+ {
+ return new ::gio::Content( m_xContext, m_pProvider, xId, !create_document );
+ } catch ( css::ucb::ContentCreationException & )
+ {
+ return css::uno::Reference< css::ucb::XContent >();
+ }
+}
+
+css::uno::Sequence< css::uno::Type > SAL_CALL Content::getTypes()
+{
+ if ( isFolder( css::uno::Reference< css::ucb::XCommandEnvironment >() ) )
+ {
+ static cppu::OTypeCollection s_aFolderCollection
+ (CPPU_TYPE_REF( css::lang::XTypeProvider ),
+ CPPU_TYPE_REF( css::lang::XServiceInfo ),
+ CPPU_TYPE_REF( css::lang::XComponent ),
+ CPPU_TYPE_REF( css::ucb::XContent ),
+ CPPU_TYPE_REF( css::ucb::XCommandProcessor ),
+ CPPU_TYPE_REF( css::beans::XPropertiesChangeNotifier ),
+ CPPU_TYPE_REF( css::ucb::XCommandInfoChangeNotifier ),
+ CPPU_TYPE_REF( css::beans::XPropertyContainer ),
+ CPPU_TYPE_REF( css::beans::XPropertySetInfoChangeNotifier ),
+ CPPU_TYPE_REF( css::container::XChild ),
+ CPPU_TYPE_REF( css::ucb::XContentCreator ) );
+ return s_aFolderCollection.getTypes();
+ }
+ else
+ {
+ static cppu::OTypeCollection s_aFileCollection
+ (CPPU_TYPE_REF( css::lang::XTypeProvider ),
+ CPPU_TYPE_REF( css::lang::XServiceInfo ),
+ CPPU_TYPE_REF( css::lang::XComponent ),
+ CPPU_TYPE_REF( css::ucb::XContent ),
+ CPPU_TYPE_REF( css::ucb::XCommandProcessor ),
+ CPPU_TYPE_REF( css::beans::XPropertiesChangeNotifier ),
+ CPPU_TYPE_REF( css::ucb::XCommandInfoChangeNotifier ),
+ CPPU_TYPE_REF( css::beans::XPropertyContainer ),
+ CPPU_TYPE_REF( css::beans::XPropertySetInfoChangeNotifier ),
+ CPPU_TYPE_REF( css::container::XChild ) );
+
+ return s_aFileCollection.getTypes();
+ }
+}
+
+css::uno::Sequence< css::beans::Property > Content::getProperties(
+ const css::uno::Reference< css::ucb::XCommandEnvironment > & /*xEnv*/ )
+{
+ static const css::beans::Property aGenericProperties[] =
+ {
+ css::beans::Property( "IsDocument",
+ -1, cppu::UnoType<bool>::get(),
+ css::beans::PropertyAttribute::BOUND | css::beans::PropertyAttribute::READONLY ),
+ css::beans::Property( "IsFolder",
+ -1, cppu::UnoType<bool>::get(),
+ css::beans::PropertyAttribute::BOUND | css::beans::PropertyAttribute::READONLY ),
+ css::beans::Property( "Title",
+ -1, cppu::UnoType<OUString>::get(),
+ css::beans::PropertyAttribute::BOUND ),
+ css::beans::Property( "IsReadOnly",
+ -1, cppu::UnoType<bool>::get(),
+ css::beans::PropertyAttribute::BOUND | css::beans::PropertyAttribute::READONLY ),
+ css::beans::Property( "DateCreated",
+ -1, cppu::UnoType<css::util::DateTime>::get(),
+ css::beans::PropertyAttribute::BOUND | css::beans::PropertyAttribute::READONLY ),
+ css::beans::Property( "DateModified",
+ -1, cppu::UnoType<css::util::DateTime>::get(),
+ css::beans::PropertyAttribute::BOUND | css::beans::PropertyAttribute::READONLY ),
+ css::beans::Property( "Size",
+ -1, cppu::UnoType<sal_Int64>::get(),
+ css::beans::PropertyAttribute::BOUND | css::beans::PropertyAttribute::READONLY ),
+ css::beans::Property( "IsVolume",
+ 1, cppu::UnoType<bool>::get(),
+ css::beans::PropertyAttribute::BOUND | css::beans::PropertyAttribute::READONLY ),
+ css::beans::Property( "IsCompactDisc",
+ -1, cppu::UnoType<bool>::get(),
+ css::beans::PropertyAttribute::BOUND | css::beans::PropertyAttribute::READONLY ),
+ css::beans::Property( "IsRemoveable",
+ -1, cppu::UnoType<bool>::get(),
+ css::beans::PropertyAttribute::BOUND | css::beans::PropertyAttribute::READONLY ),
+ css::beans::Property( "IsHidden",
+ -1, cppu::UnoType<bool>::get(),
+ css::beans::PropertyAttribute::BOUND | css::beans::PropertyAttribute::READONLY ),
+ css::beans::Property( "CreatableContentsInfo",
+ -1, cppu::UnoType<css::uno::Sequence< css::ucb::ContentInfo >>::get(),
+ css::beans::PropertyAttribute::BOUND | css::beans::PropertyAttribute::READONLY )
+ };
+
+ const int nProps = SAL_N_ELEMENTS(aGenericProperties);
+ return css::uno::Sequence< css::beans::Property > ( aGenericProperties, nProps );
+}
+
+css::uno::Sequence< css::ucb::CommandInfo > Content::getCommands( const css::uno::Reference< css::ucb::XCommandEnvironment > & xEnv)
+{
+ static const css::ucb::CommandInfo aCommandInfoTable[] =
+ {
+ // Required commands
+ css::ucb::CommandInfo
+ ( "getCommandInfo",
+ -1, cppu::UnoType<void>::get() ),
+ css::ucb::CommandInfo
+ ( "getPropertySetInfo",
+ -1, cppu::UnoType<void>::get() ),
+ css::ucb::CommandInfo
+ ( "getPropertyValues",
+ -1, cppu::UnoType<css::uno::Sequence< css::beans::Property >>::get() ),
+ css::ucb::CommandInfo
+ ( "setPropertyValues",
+ -1, cppu::UnoType<css::uno::Sequence< css::beans::PropertyValue >>::get() ),
+
+ // Optional standard commands
+ css::ucb::CommandInfo
+ ( "delete",
+ -1, cppu::UnoType<bool>::get() ),
+ css::ucb::CommandInfo
+ ( "insert",
+ -1, cppu::UnoType<css::ucb::InsertCommandArgument>::get() ),
+ css::ucb::CommandInfo
+ ( "open",
+ -1, cppu::UnoType<css::ucb::OpenCommandArgument2>::get() ),
+
+ // Folder Only, omitted if not a folder
+ css::ucb::CommandInfo
+ ( "transfer",
+ -1, cppu::UnoType<css::ucb::TransferInfo>::get() ),
+ css::ucb::CommandInfo
+ ( "createNewContent",
+ -1, cppu::UnoType<css::ucb::ContentInfo>::get() )
+ };
+
+ const int nProps = SAL_N_ELEMENTS(aCommandInfoTable);
+ return css::uno::Sequence< css::ucb::CommandInfo >(aCommandInfoTable, isFolder(xEnv) ? nProps : nProps - 2);
+}
+
+XTYPEPROVIDER_COMMON_IMPL( Content );
+
+void SAL_CALL Content::acquire() noexcept
+{
+ ContentImplHelper::acquire();
+}
+
+void SAL_CALL Content::release() noexcept
+{
+ ContentImplHelper::release();
+}
+
+css::uno::Any SAL_CALL Content::queryInterface( const css::uno::Type & rType )
+{
+ css::uno::Any aRet = cppu::queryInterface( rType, static_cast< css::ucb::XContentCreator * >( this ) );
+ return aRet.hasValue() ? aRet : ContentImplHelper::queryInterface(rType);
+}
+
+OUString SAL_CALL Content::getImplementationName()
+{
+ return "com.sun.star.comp.GIOContent";
+}
+
+css::uno::Sequence< OUString > SAL_CALL Content::getSupportedServiceNames()
+{
+ css::uno::Sequence<OUString> aSNS { "com.sun.star.ucb.GIOContent" };
+ return aSNS;
+}
+
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/ucb/source/ucp/gio/gio_content.hxx b/ucb/source/ucp/gio/gio_content.hxx
new file mode 100644
index 0000000000..243b74a4a2
--- /dev/null
+++ b/ucb/source/ucp/gio/gio_content.hxx
@@ -0,0 +1,187 @@
+/* -*- 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:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#pragma once
+
+#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 <gio/gio.h>
+
+#include <vector>
+
+namespace com::sun::star {
+ namespace beans {
+ struct Property;
+ struct PropertyValue;
+ }
+ namespace sdbc {
+ class XRow;
+ }
+}
+namespace ucbhelper
+{
+ class Content;
+}
+
+
+namespace gio
+{
+
+
+inline constexpr OUString GIO_FILE_TYPE = u"application/vnd.sun.staroffice.gio-file"_ustr;
+inline constexpr OUString GIO_FOLDER_TYPE = u"application/vnd.sun.staroffice.gio-folder"_ustr;
+
+css::uno::Any convertToException(GError *pError,
+ const css::uno::Reference< css::uno::XInterface >& rContext, bool bThrow=true);
+/// @throws css::io::IOException
+/// @throws css::uno::RuntimeException
+void convertToIOException(GError *pError,
+ const css::uno::Reference< css::uno::XInterface >& rContext);
+
+class ContentProvider;
+class Content : public ::ucbhelper::ContentImplHelper, public css::ucb::XContentCreator
+{
+private:
+ ContentProvider *m_pProvider;
+ GFile* mpFile;
+ GFileInfo *mpInfo;
+ bool mbTransient;
+
+ GFileInfo *getGFileInfo(const css::uno::Reference< css::ucb::XCommandEnvironment >& xEnv,
+ GError **ppError=nullptr);
+ bool isFolder(const css::uno::Reference< css::ucb::XCommandEnvironment >& xEnv);
+
+ css::uno::Any mapGIOError( GError *error );
+ 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 );
+private:
+ typedef rtl::Reference< Content > ContentRef;
+ typedef std::vector< ContentRef > ContentRefList;
+
+ void queryChildren( ContentRefList& rChildren );
+
+ bool doSetFileInfo ( GFileInfo *pNewInfo );
+
+ /// @throws css::uno::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, const css::uno::Reference< css::ucb::XCommandEnvironment >& xEnv );
+
+ /// @throws css::uno::Exception
+ void destroy( bool bDeletePhysical );
+
+ 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 );
+
+ bool feedSink( const css::uno::Reference< css::uno::XInterface>& aSink );
+
+ bool exchangeIdentity(const css::uno::Reference< css::ucb::XContentIdentifier >& xNewId);
+
+ void getFileInfo(
+ css::uno::Reference<css::ucb::XCommandEnvironment> const & env, GFileInfo ** info,
+ bool fail);
+
+public:
+ /// @throws css::ucb::ContentCreationException
+ Content( const css::uno::Reference<
+ css::uno::XComponentContext >& rxContext, ContentProvider *pProvider,
+ const css::uno::Reference< css::ucb::XContentIdentifier >& Identifier);
+
+ /// @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;
+
+ 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);
+
+ GFile* getGFile();
+};
+
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/ucb/source/ucp/gio/gio_datasupplier.cxx b/ucb/source/ucp/gio/gio_datasupplier.cxx
new file mode 100644
index 0000000000..01fdc3e574
--- /dev/null
+++ b/ucb/source/ucp/gio/gio_datasupplier.cxx
@@ -0,0 +1,257 @@
+/* -*- 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:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include <ucbhelper/contentidentifier.hxx>
+#include <ucbhelper/providerhelper.hxx>
+#include <com/sun/star/ucb/IllegalIdentifierException.hpp>
+#include <com/sun/star/ucb/OpenMode.hpp>
+#include <utility>
+
+#include "gio_datasupplier.hxx"
+#include "gio_content.hxx"
+
+using namespace gio;
+
+namespace gio
+{
+
+DataSupplier::DataSupplier( rtl::Reference< ::gio::Content > xContent, sal_Int32 nOpenMode )
+ : mxContent(std::move(xContent)), mnOpenMode(nOpenMode), mbCountFinal(false)
+{
+}
+
+bool DataSupplier::getData()
+{
+ if (mbCountFinal)
+ return true;
+
+ GFile *pFile = mxContent->getGFile();
+
+ GFileEnumerator* pEnumerator = g_file_enumerate_children(pFile, "*",
+ G_FILE_QUERY_INFO_NONE, nullptr, nullptr);
+
+ if (!pEnumerator)
+ return false;
+
+ GFileInfo *pInfo = nullptr;
+ while ((pInfo = g_file_enumerator_next_file (pEnumerator, nullptr, nullptr)))
+ {
+ switch ( mnOpenMode )
+ {
+ case css::ucb::OpenMode::FOLDERS:
+ if (g_file_info_get_file_type(pInfo) != G_FILE_TYPE_DIRECTORY)
+ continue;
+ break;
+ case css::ucb::OpenMode::DOCUMENTS:
+ if (g_file_info_get_file_type(pInfo) != G_FILE_TYPE_REGULAR)
+ continue;
+ break;
+ case css::ucb::OpenMode::ALL:
+ default:
+ break;
+ }
+
+ maResults.emplace_back( new ResultListEntry( pInfo ) );
+ g_object_unref(pInfo);
+ }
+
+ mbCountFinal = true;
+
+ g_file_enumerator_close(pEnumerator, nullptr, nullptr);
+ return true;
+}
+
+DataSupplier::~DataSupplier()
+{
+}
+
+OUString DataSupplier::queryContentIdentifierString( sal_uInt32 nIndex )
+{
+ if ( nIndex < maResults.size() )
+ {
+ OUString aId = maResults[ nIndex ]->aId;
+ if ( aId.getLength() )
+ {
+ // Already cached.
+ return aId;
+ }
+ }
+
+ if ( getResult( nIndex ) )
+ {
+ GFile *pFile = mxContent->getGFile();
+ char* parent = g_file_get_uri(pFile);
+ OUString aId = OUString::createFromAscii( parent );
+ g_free(parent);
+
+ char *escaped_name =
+ g_uri_escape_string( g_file_info_get_name(maResults[ nIndex ]->pInfo) , nullptr, false);
+
+ if ( ( aId.lastIndexOf( '/' ) + 1 ) != aId.getLength() )
+ aId += "/";
+
+ aId += OUString::createFromAscii( escaped_name );
+
+ g_free( escaped_name );
+
+ maResults[ nIndex ]->aId = aId;
+ return aId;
+ }
+
+ return OUString();
+}
+
+css::uno::Reference< css::ucb::XContentIdentifier > DataSupplier::queryContentIdentifier( sal_uInt32 nIndex )
+{
+ if ( nIndex < maResults.size() )
+ {
+ css::uno::Reference< css::ucb::XContentIdentifier > xId = maResults[ nIndex ]->xId;
+ if ( xId.is() )
+ {
+ // Already cached.
+ return xId;
+ }
+ }
+
+ OUString aId = queryContentIdentifierString( nIndex );
+ if ( aId.getLength() )
+ {
+ css::uno::Reference< css::ucb::XContentIdentifier > xId = new ucbhelper::ContentIdentifier( aId );
+ maResults[ nIndex ]->xId = xId;
+ return xId;
+ }
+
+ return css::uno::Reference< css::ucb::XContentIdentifier >();
+}
+
+css::uno::Reference< css::ucb::XContent > DataSupplier::queryContent( sal_uInt32 nIndex )
+{
+ if ( nIndex < maResults.size() )
+ {
+ css::uno::Reference< css::ucb::XContent > xContent = maResults[ nIndex ]->xContent;
+ if ( xContent.is() )
+ {
+ // Already cached.
+ return xContent;
+ }
+ }
+
+ css::uno::Reference< css::ucb::XContentIdentifier > xId = queryContentIdentifier( nIndex );
+ if ( xId.is() )
+ {
+ try
+ {
+ css::uno::Reference< css::ucb::XContent > xContent = mxContent->getProvider()->queryContent( xId );
+ maResults[ nIndex ]->xContent = xContent;
+ return xContent;
+ }
+ catch ( css::ucb::IllegalIdentifierException& )
+ {
+ }
+ }
+ return css::uno::Reference< css::ucb::XContent >();
+}
+
+bool DataSupplier::getResult( sal_uInt32 nIndex )
+{
+ if ( maResults.size() > nIndex ) // Result already present.
+ return true;
+
+ if ( getData() && maResults.size() > nIndex )
+ return true;
+
+ return false;
+}
+
+sal_uInt32 DataSupplier::totalCount()
+{
+ getData();
+ return maResults.size();
+}
+
+sal_uInt32 DataSupplier::currentCount()
+{
+ return maResults.size();
+}
+
+bool DataSupplier::isCountFinal()
+{
+ return mbCountFinal;
+}
+
+css::uno::Reference< css::sdbc::XRow > DataSupplier::queryPropertyValues( sal_uInt32 nIndex )
+{
+ if ( nIndex < maResults.size() )
+ {
+ css::uno::Reference< css::sdbc::XRow > xRow = maResults[ nIndex ]->xRow;
+ if ( xRow.is() )
+ {
+ // Already cached.
+ return xRow;
+ }
+ }
+
+ if ( getResult( nIndex ) )
+ {
+ css::uno::Reference< css::ucb::XContent > xContent( queryContent( nIndex ) );
+ if ( xContent.is() )
+ {
+ try
+ {
+ css::uno::Reference< css::ucb::XCommandProcessor > xCmdProc(
+ xContent, css::uno::UNO_QUERY_THROW );
+ sal_Int32 nCmdId( xCmdProc->createCommandIdentifier() );
+ css::ucb::Command aCmd;
+ aCmd.Name = "getPropertyValues";
+ aCmd.Handle = -1;
+ aCmd.Argument <<= getResultSet()->getProperties();
+ css::uno::Any aResult( xCmdProc->execute(
+ aCmd, nCmdId, getResultSet()->getEnvironment() ) );
+ css::uno::Reference< css::sdbc::XRow > xRow;
+ if ( aResult >>= xRow )
+ {
+ maResults[ nIndex ]->xRow = xRow;
+ return xRow;
+ }
+ }
+ catch ( css::uno::Exception const & )
+ {
+ }
+ }
+ }
+ return css::uno::Reference< css::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/gio/gio_datasupplier.hxx b/ucb/source/ucp/gio/gio_datasupplier.hxx
new file mode 100644
index 0000000000..e2ca1d8277
--- /dev/null
+++ b/ucb/source/ucp/gio/gio_datasupplier.hxx
@@ -0,0 +1,88 @@
+/* -*- 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:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#pragma once
+
+#include <ucbhelper/resultset.hxx>
+#include "gio_content.hxx"
+#include <memory>
+#include <vector>
+
+namespace gio
+{
+
+class Content;
+
+struct ResultListEntry
+{
+ OUString aId;
+ css::uno::Reference< css::ucb::XContentIdentifier > xId;
+ css::uno::Reference< css::ucb::XContent > xContent;
+ css::uno::Reference< css::sdbc::XRow > xRow;
+ GFileInfo *pInfo;
+
+ explicit ResultListEntry( GFileInfo *pInInfo ) : pInfo(pInInfo)
+ {
+ g_object_ref( pInfo );
+ }
+
+ ~ResultListEntry()
+ {
+ g_object_unref( pInfo );
+ }
+};
+
+typedef std::vector< std::unique_ptr<ResultListEntry> > ResultList;
+
+class DataSupplier : public ucbhelper::ResultSetDataSupplier
+{
+private:
+ rtl::Reference< ::gio::Content > mxContent;
+ sal_Int32 mnOpenMode;
+ bool mbCountFinal;
+ bool getData();
+ ResultList maResults;
+public:
+ DataSupplier( rtl::Reference< Content > xContent, 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/gio/gio_inputstream.cxx b/ucb/source/ucp/gio/gio_inputstream.cxx
new file mode 100644
index 0000000000..5d07586ec6
--- /dev/null
+++ b/ucb/source/ucp/gio/gio_inputstream.cxx
@@ -0,0 +1,91 @@
+/* -*- 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:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include <sal/config.h>
+
+#include <com/sun/star/io/BufferSizeExceededException.hpp>
+#include <com/sun/star/io/NotConnectedException.hpp>
+
+#include "gio_inputstream.hxx"
+#include "gio_content.hxx"
+
+namespace gio
+{
+
+InputStream::InputStream(GFileInputStream *pStream): mpStream(pStream)
+{
+ if (!mpStream)
+ throw css::io::NotConnectedException();
+}
+
+InputStream::~InputStream()
+{
+ closeInput();
+}
+
+sal_Int32 SAL_CALL InputStream::available()
+{
+ return 0;
+}
+
+void SAL_CALL InputStream::closeInput()
+{
+ if (mpStream)
+ g_input_stream_close(G_INPUT_STREAM(mpStream), nullptr, nullptr);
+}
+
+void SAL_CALL InputStream::skipBytes( sal_Int32 nBytesToSkip )
+{
+ // Conservatively call readBytes and discard the read data, but given this
+ // InputStream will always be wrapped in comphelper::OSeekableInputWrapper,
+ // this function will never be called anyway:
+ css::uno::Sequence<sal_Int8> data;
+ readBytes(data, nBytesToSkip);
+}
+
+sal_Int32 SAL_CALL InputStream::readBytes( css::uno::Sequence< sal_Int8 >& aData, sal_Int32 nBytesToRead )
+{
+ if (!mpStream)
+ throw css::io::NotConnectedException();
+
+ try
+ {
+ aData.realloc( nBytesToRead );
+ }
+ catch ( const css::uno::Exception & )
+ {
+ throw css::io::BufferSizeExceededException();
+ }
+
+ gsize nBytesRead = 0;
+ GError *pError=nullptr;
+ if (!g_input_stream_read_all(G_INPUT_STREAM(mpStream), aData.getArray(), nBytesToRead, &nBytesRead, nullptr, &pError))
+ convertToIOException(pError, getXWeak());
+ aData.realloc(nBytesRead);
+ return nBytesRead;
+}
+
+sal_Int32 SAL_CALL InputStream::readSomeBytes( css::uno::Sequence< sal_Int8 >& aData, sal_Int32 nMaxBytesToRead )
+{
+ return readBytes(aData, nMaxBytesToRead);
+}
+
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/ucb/source/ucp/gio/gio_inputstream.hxx b/ucb/source/ucp/gio/gio_inputstream.hxx
new file mode 100644
index 0000000000..9608bc5b35
--- /dev/null
+++ b/ucb/source/ucp/gio/gio_inputstream.hxx
@@ -0,0 +1,58 @@
+/* -*- 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:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#pragma once
+
+#include <sal/types.h>
+#include <rtl/ustring.hxx>
+#include <cppuhelper/implbase.hxx>
+
+#include <com/sun/star/io/XInputStream.hpp>
+
+#include <gio/gio.h>
+
+namespace gio
+{
+
+class InputStream final : public cppu::WeakImplHelper<css::io::XInputStream>
+{
+private:
+ GFileInputStream *mpStream;
+
+public:
+ explicit InputStream ( GFileInputStream *pStream );
+ virtual ~InputStream() override;
+
+ // XInputStream
+ 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;
+};
+
+} // namespace gio
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/ucb/source/ucp/gio/gio_mount.cxx b/ucb/source/ucp/gio/gio_mount.cxx
new file mode 100644
index 0000000000..1d2dbcea81
--- /dev/null
+++ b/ucb/source/ucp/gio/gio_mount.cxx
@@ -0,0 +1,211 @@
+/* -*- 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:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include <sal/config.h>
+
+#include <utility>
+
+#include "gio_mount.hxx"
+#include <ucbhelper/simpleauthenticationrequest.hxx>
+#include <string.h>
+
+#ifdef __GNUC__
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Wunused-function"
+#if defined __clang__
+#if __has_warning("-Wdeprecated-volatile")
+#pragma clang diagnostic ignored "-Wdeprecated-volatile"
+#endif
+#endif
+#endif
+G_DEFINE_TYPE (OOoMountOperation, ooo_mount_operation, G_TYPE_MOUNT_OPERATION);
+#ifdef __GNUC__
+#pragma GCC diagnostic pop
+#endif
+
+static void ooo_mount_operation_ask_password (GMountOperation *op,
+ const char *message, const char *default_user, const char *default_domain,
+ GAskPasswordFlags flags);
+
+static void ooo_mount_operation_init (OOoMountOperation *op)
+{
+ op->m_pPrevPassword = nullptr;
+ op->m_pPrevUsername = nullptr;
+}
+
+static void ooo_mount_operation_finalize (GObject *object)
+{
+ OOoMountOperation *mount_op = OOO_MOUNT_OPERATION (object);
+ if (mount_op->m_pPrevUsername)
+ free(mount_op->m_pPrevUsername);
+ if (mount_op->m_pPrevPassword)
+ free(mount_op->m_pPrevPassword);
+ mount_op->context.reset();
+
+ G_OBJECT_CLASS (ooo_mount_operation_parent_class)->finalize (object);
+}
+
+static void ooo_mount_operation_class_init (OOoMountOperationClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+ object_class->finalize = ooo_mount_operation_finalize;
+
+ GMountOperationClass *mount_op_class = G_MOUNT_OPERATION_CLASS (klass);
+ mount_op_class->ask_password = ooo_mount_operation_ask_password;
+}
+
+namespace {
+
+// Temporarily undo the g_main_context_push_thread_default done in the surrounding MountOperation
+// ctor (in ucb/source/ucp/gio/gio_content.cxx):
+struct GlibThreadDefaultMainContextScope {
+public:
+ GlibThreadDefaultMainContextScope(GMainContext * context): context_(context)
+ { g_main_context_push_thread_default(context_); }
+
+ ~GlibThreadDefaultMainContextScope() { g_main_context_pop_thread_default(context_); }
+
+private:
+ GMainContext * context_;
+};
+
+}
+
+static void ooo_mount_operation_ask_password (GMountOperation *op,
+ const char * /*message*/, const char *default_user,
+ const char *default_domain, GAskPasswordFlags flags)
+{
+ css::uno::Reference< css::task::XInteractionHandler > xIH;
+
+ OOoMountOperation *pThis = reinterpret_cast<OOoMountOperation*>(op);
+ GlibThreadDefaultMainContextScope scope(pThis->context.get());
+
+ const css::uno::Reference< css::ucb::XCommandEnvironment > &xEnv = *(pThis->pEnv);
+
+ if (xEnv.is())
+ xIH = xEnv->getInteractionHandler();
+
+ if (!xIH.is())
+ {
+ g_mount_operation_reply (op, G_MOUNT_OPERATION_ABORTED);
+ return;
+ }
+
+ OUString aDomain, aUserName, aPassword;
+
+ if (default_user)
+ aUserName = OUString(default_user, strlen(default_user), RTL_TEXTENCODING_UTF8);
+
+ ucbhelper::SimpleAuthenticationRequest::EntityType eUserName =
+ (flags & G_ASK_PASSWORD_NEED_USERNAME)
+ ? ucbhelper::SimpleAuthenticationRequest::ENTITY_MODIFY
+ : aUserName.isEmpty() ? ucbhelper::SimpleAuthenticationRequest::ENTITY_NA
+ : ucbhelper::SimpleAuthenticationRequest::ENTITY_FIXED;
+
+ ucbhelper::SimpleAuthenticationRequest::EntityType ePassword =
+ (flags & G_ASK_PASSWORD_NEED_PASSWORD)
+ ? ucbhelper::SimpleAuthenticationRequest::ENTITY_MODIFY
+ : ucbhelper::SimpleAuthenticationRequest::ENTITY_NA;
+
+ OUString aPrevPassword, aPrevUsername;
+ if (pThis->m_pPrevUsername)
+ aPrevUsername = OUString(pThis->m_pPrevUsername, strlen(pThis->m_pPrevUsername), RTL_TEXTENCODING_UTF8);
+ if (pThis->m_pPrevPassword)
+ aPrevPassword = OUString(pThis->m_pPrevPassword, strlen(pThis->m_pPrevPassword), RTL_TEXTENCODING_UTF8);
+
+ //The damn dialog is stupidly broken, so do like webdav, i.e. "#102871#"
+ if ( aUserName.isEmpty() )
+ aUserName = aPrevUsername;
+
+ if ( aPassword.isEmpty() )
+ aPassword = aPrevPassword;
+
+ ucbhelper::SimpleAuthenticationRequest::EntityType eDomain =
+ (flags & G_ASK_PASSWORD_NEED_DOMAIN)
+ ? ucbhelper::SimpleAuthenticationRequest::ENTITY_MODIFY
+ : ucbhelper::SimpleAuthenticationRequest::ENTITY_NA;
+
+ if (default_domain)
+ aDomain = OUString(default_domain, strlen(default_domain), RTL_TEXTENCODING_UTF8);
+
+ rtl::Reference< ucbhelper::SimpleAuthenticationRequest > xRequest
+ = new ucbhelper::SimpleAuthenticationRequest (OUString() /* FIXME: provide URL here */, OUString(), eDomain, aDomain, eUserName, aUserName, ePassword, aPassword);
+
+ xIH->handle( xRequest );
+
+ rtl::Reference< ucbhelper::InteractionContinuation > xSelection = xRequest->getSelection();
+
+ if ( !xSelection.is() )
+ {
+ g_mount_operation_reply (op, G_MOUNT_OPERATION_ABORTED);
+ return;
+ }
+
+ css::uno::Reference< css::task::XInteractionAbort > xAbort(xSelection.get(), css::uno::UNO_QUERY );
+ if ( xAbort.is() )
+ {
+ g_mount_operation_reply (op, G_MOUNT_OPERATION_ABORTED);
+ return;
+ }
+
+ const rtl::Reference< ucbhelper::InteractionSupplyAuthentication > & xSupp = xRequest->getAuthenticationSupplier();
+ aUserName = xSupp->getUserName();
+ aPassword = xSupp->getPassword();
+
+ if (flags & G_ASK_PASSWORD_NEED_USERNAME)
+ g_mount_operation_set_username(op, OUStringToOString(aUserName, RTL_TEXTENCODING_UTF8).getStr());
+
+ if (flags & G_ASK_PASSWORD_NEED_PASSWORD)
+ g_mount_operation_set_password(op, OUStringToOString(aPassword, RTL_TEXTENCODING_UTF8).getStr());
+
+ if (flags & G_ASK_PASSWORD_NEED_DOMAIN)
+ g_mount_operation_set_domain(op, OUStringToOString(xSupp->getRealm(), RTL_TEXTENCODING_UTF8).getStr());
+
+ switch (xSupp->getRememberPasswordMode())
+ {
+ default:
+ case css::ucb::RememberAuthentication_NO:
+ g_mount_operation_set_password_save(op, G_PASSWORD_SAVE_NEVER);
+ break;
+ case css::ucb::RememberAuthentication_SESSION:
+ g_mount_operation_set_password_save(op, G_PASSWORD_SAVE_FOR_SESSION);
+ break;
+ case css::ucb::RememberAuthentication_PERSISTENT:
+ g_mount_operation_set_password_save(op, G_PASSWORD_SAVE_PERMANENTLY);
+ break;
+ }
+
+ if (pThis->m_pPrevPassword)
+ free(pThis->m_pPrevPassword);
+ pThis->m_pPrevPassword = strdup(OUStringToOString(aPassword, RTL_TEXTENCODING_UTF8).getStr());
+ if (pThis->m_pPrevUsername)
+ free(pThis->m_pPrevUsername);
+ pThis->m_pPrevUsername = strdup(OUStringToOString(aUserName, RTL_TEXTENCODING_UTF8).getStr());
+ g_mount_operation_reply (op, G_MOUNT_OPERATION_HANDLED);
+}
+
+GMountOperation *ooo_mount_operation_new(ucb::ucp::gio::glib::MainContextRef && context, const css::uno::Reference< css::ucb::XCommandEnvironment >& rEnv)
+{
+ OOoMountOperation *pRet = static_cast<OOoMountOperation*>(g_object_new (OOO_TYPE_MOUNT_OPERATION, nullptr));
+ pRet->context = std::move(context);
+ pRet->pEnv = &rEnv;
+ return &pRet->parent_instance;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/ucb/source/ucp/gio/gio_mount.hxx b/ucb/source/ucp/gio/gio_mount.hxx
new file mode 100644
index 0000000000..2ddcaebf57
--- /dev/null
+++ b/ucb/source/ucp/gio/gio_mount.hxx
@@ -0,0 +1,88 @@
+/* -*- 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:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#pragma once
+
+#include <sal/config.h>
+
+#include <memory>
+
+#include <com/sun/star/ucb/XCommandEnvironment.hpp>
+#include <gio/gio.h>
+
+G_BEGIN_DECLS
+
+#define OOO_TYPE_MOUNT_OPERATION (ooo_mount_operation_get_type ())
+#define OOO_MOUNT_OPERATION(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), OOO_TYPE_MOUNT_OPERATION, OOoMountOperation))
+#define OOO_MOUNT_OPERATION_CLASS(k) (G_TYPE_CHECK_CLASS_CAST((k), OOO_TYPE_MOUNT_OPERATION, OOoMountOperationClass))
+#define OOO_IS_MOUNT_OPERATION(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), OOO_TYPE_MOUNT_OPERATION))
+#define OOO_IS_MOUNT_OPERATION_CLASS(k) (G_TYPE_CHECK_CLASS_TYPE ((k), OOO_TYPE_MOUNT_OPERATION))
+#define OOO_MOUNT_OPERATION_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), OOO_TYPE_MOUNT_OPERATION, OOoMountOperationClass))
+
+namespace ucb::ucp::gio::glib {
+
+namespace detail {
+
+struct MainContextUnref {
+ void operator ()(GMainContext * context) {
+ if (context != nullptr) {
+ g_main_context_unref(context);
+ }
+ }
+};
+
+}
+
+using MainContextRef = std::unique_ptr<GMainContext, detail::MainContextUnref>;
+
+}
+
+struct OOoMountOperation
+{
+ GMountOperation parent_instance;
+
+ ucb::ucp::gio::glib::MainContextRef context;
+ const css::uno::Reference< css::ucb::XCommandEnvironment > *pEnv;
+ char *m_pPrevUsername;
+ char *m_pPrevPassword;
+
+private:
+ // Managed via ooo_mount_operation_new and ooo_mount_operation_finalize:
+ OOoMountOperation() = delete;
+ ~OOoMountOperation() = delete;
+};
+
+struct OOoMountOperationClass
+{
+ GMountOperationClass parent_class;
+
+ /* Padding for future expansion */
+ void (*_gtk_reserved1) (void);
+ void (*_gtk_reserved2) (void);
+ void (*_gtk_reserved3) (void);
+ void (*_gtk_reserved4) (void);
+};
+
+
+GType ooo_mount_operation_get_type();
+GMountOperation *ooo_mount_operation_new(ucb::ucp::gio::glib::MainContextRef && context, const css::uno::Reference< css::ucb::XCommandEnvironment >& rEnv);
+
+G_END_DECLS
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/ucb/source/ucp/gio/gio_outputstream.cxx b/ucb/source/ucp/gio/gio_outputstream.cxx
new file mode 100644
index 0000000000..0da373c0df
--- /dev/null
+++ b/ucb/source/ucp/gio/gio_outputstream.cxx
@@ -0,0 +1,78 @@
+/* -*- 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:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include <sal/config.h>
+
+#include <com/sun/star/io/NotConnectedException.hpp>
+#include <cppuhelper/queryinterface.hxx>
+
+#include "gio_outputstream.hxx"
+#include "gio_content.hxx"
+
+namespace gio
+{
+
+OutputStream::OutputStream(GFileOutputStream *pStream) : Seekable(G_SEEKABLE(pStream)), mpStream(pStream)
+{
+ if (!mpStream)
+ throw css::io::NotConnectedException();
+}
+
+OutputStream::~OutputStream()
+{
+ closeOutput();
+}
+
+void SAL_CALL OutputStream::writeBytes( const css::uno::Sequence< sal_Int8 >& rData )
+{
+ if (!mpStream)
+ throw css::io::NotConnectedException();
+
+ GError *pError=nullptr;
+ if (!g_output_stream_write_all(G_OUTPUT_STREAM(mpStream), rData.getConstArray(), rData.getLength(), nullptr, nullptr, &pError))
+ convertToIOException(pError, getXWeak());
+}
+
+void SAL_CALL OutputStream::flush()
+{
+ if (!mpStream)
+ throw css::io::NotConnectedException();
+
+ GError *pError=nullptr;
+ if (!g_output_stream_flush(G_OUTPUT_STREAM(mpStream), nullptr, &pError))
+ convertToIOException(pError, getXWeak());
+}
+
+void SAL_CALL OutputStream::closeOutput()
+{
+ if (mpStream)
+ g_output_stream_close(G_OUTPUT_STREAM(mpStream), nullptr, nullptr);
+}
+
+css::uno::Any OutputStream::queryInterface( const css::uno::Type &type )
+{
+ css::uno::Any aRet = ::cppu::queryInterface ( type,
+ static_cast< XOutputStream * >( this ) );
+
+ return aRet.hasValue() ? aRet : Seekable::queryInterface( type );
+}
+
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/ucb/source/ucp/gio/gio_outputstream.hxx b/ucb/source/ucp/gio/gio_outputstream.hxx
new file mode 100644
index 0000000000..40bdbcfc42
--- /dev/null
+++ b/ucb/source/ucp/gio/gio_outputstream.hxx
@@ -0,0 +1,62 @@
+/* -*- 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:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#pragma once
+
+#include <sal/types.h>
+#include <rtl/ustring.hxx>
+#include <cppuhelper/weak.hxx>
+
+#include <com/sun/star/io/XOutputStream.hpp>
+#include <com/sun/star/io/XTruncate.hpp>
+#include <com/sun/star/io/XSeekable.hpp>
+
+#include "gio_seekable.hxx"
+
+namespace gio
+{
+
+class OutputStream final :
+ public css::io::XOutputStream,
+ public Seekable
+{
+private:
+ GFileOutputStream *mpStream;
+
+public:
+ explicit OutputStream ( GFileOutputStream *pStream );
+ virtual ~OutputStream() override;
+
+ // XInterface
+ virtual css::uno::Any SAL_CALL queryInterface(const css::uno::Type & type ) override;
+ virtual void SAL_CALL acquire() noexcept override { OWeakObject::acquire(); }
+ virtual void SAL_CALL release() noexcept override { OWeakObject::release(); }
+
+ // XOutputStream
+ 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;
+};
+
+} // namespace gio
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/ucb/source/ucp/gio/gio_provider.cxx b/ucb/source/ucp/gio/gio_provider.cxx
new file mode 100644
index 0000000000..b6dd31fbbc
--- /dev/null
+++ b/ucb/source/ucp/gio/gio_provider.cxx
@@ -0,0 +1,139 @@
+/* -*- 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:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include <sal/log.hxx>
+#include <ucbhelper/contenthelper.hxx>
+#include <ucbhelper/macros.hxx>
+#include <cppuhelper/queryinterface.hxx>
+#include <cppuhelper/weak.hxx>
+#include <com/sun/star/ucb/ContentCreationException.hpp>
+#include <com/sun/star/ucb/IllegalIdentifierException.hpp>
+#include "gio_provider.hxx"
+#include "gio_content.hxx"
+
+namespace gio
+{
+css::uno::Reference< css::ucb::XContent > SAL_CALL
+ContentProvider::queryContent(
+ const css::uno::Reference< css::ucb::XContentIdentifier >& Identifier )
+{
+ SAL_INFO("ucb.ucp.gio", "QueryContent: " << Identifier->getContentIdentifier());
+ osl::MutexGuard aGuard( m_aMutex );
+
+ // Check, if a content with given id already exists...
+ css::uno::Reference< css::ucb::XContent > xContent = queryExistingContent( Identifier );
+ if ( xContent.is() )
+ return xContent;
+
+ try
+ {
+ xContent = new ::gio::Content(m_xContext, this, Identifier);
+ }
+ catch ( css::ucb::ContentCreationException const & )
+ {
+ throw css::ucb::IllegalIdentifierException();
+ }
+
+ if ( !xContent->getIdentifier().is() )
+ throw css::ucb::IllegalIdentifierException();
+
+ return xContent;
+}
+
+ContentProvider::ContentProvider(
+ const css::uno::Reference< css::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< css::lang::XTypeProvider* >(this),
+ static_cast< css::lang::XServiceInfo* >(this),
+ static_cast< css::ucb::XContentProvider* >(this)
+ );
+ return aRet.hasValue() ? aRet : OWeakObject::queryInterface( rType );
+}
+
+XTYPEPROVIDER_IMPL_3( ContentProvider,
+ css::lang::XTypeProvider,
+ css::lang::XServiceInfo,
+ css::ucb::XContentProvider );
+
+css::uno::Sequence< OUString > SAL_CALL ContentProvider::getSupportedServiceNames()
+{
+ return { "com.sun.star.ucb.GIOContentProvider" };
+}
+
+OUString SAL_CALL ContentProvider::getImplementationName()
+{
+ return "com.sun.star.comp.GIOContentProvider";
+}
+
+sal_Bool SAL_CALL ContentProvider::supportsService(const OUString& aServiceName)
+{
+ return cppu::supportsService(this, aServiceName);
+}
+
+
+}
+
+// gio creates threads we don't want in online's forkit
+static bool isDisabled()
+{
+ const char *pDisable = getenv("UNODISABLELIBRARY");
+ if (!pDisable)
+ return false;
+ OString aDisable(pDisable, strlen(pDisable));
+ return aDisable.indexOf("ucpgio1") >= 0;
+}
+
+
+
+extern "C" SAL_DLLPUBLIC_EXPORT css::uno::XInterface*
+ucb_gio_ContentProvider_get_implementation(
+ css::uno::XComponentContext* context , css::uno::Sequence<css::uno::Any> const&)
+{
+ if (isDisabled())
+ return nullptr;
+#if !GLIB_CHECK_VERSION(2,36,0)
+ g_type_init();
+#endif
+ return cppu::acquire(new gio::ContentProvider(context));
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/ucb/source/ucp/gio/gio_provider.hxx b/ucb/source/ucp/gio/gio_provider.hxx
new file mode 100644
index 0000000000..f90d09805b
--- /dev/null
+++ b/ucb/source/ucp/gio/gio_provider.hxx
@@ -0,0 +1,52 @@
+/* -*- 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:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#pragma once
+
+#include <ucbhelper/providerhelper.hxx>
+
+namespace gio
+{
+class ContentProvider : public ::ucbhelper::ContentProviderImplHelper
+{
+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;
+};
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/ucb/source/ucp/gio/gio_resultset.cxx b/ucb/source/ucp/gio/gio_resultset.cxx
new file mode 100644
index 0000000000..a91c552b79
--- /dev/null
+++ b/ucb/source/ucp/gio/gio_resultset.cxx
@@ -0,0 +1,55 @@
+/* -*- 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:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include <utility>
+
+#include "gio_datasupplier.hxx"
+#include "gio_resultset.hxx"
+
+using namespace com::sun::star::lang;
+using namespace com::sun::star::ucb;
+using namespace com::sun::star::uno;
+
+using namespace gio;
+
+DynamicResultSet::DynamicResultSet(
+ const Reference< XComponentContext >& rxContext,
+ rtl::Reference< Content > xContent,
+ const OpenCommandArgument2& rCommand,
+ const Reference< XCommandEnvironment >& rxEnv )
+ : ResultSetImplHelper( rxContext, rCommand ),
+ m_xContent(std::move( xContent )),
+ m_xEnv( rxEnv )
+{
+}
+
+void DynamicResultSet::initStatic()
+{
+ m_xResultSet1 = new ::ucbhelper::ResultSet(
+ m_xContext, m_aCommand.Properties,
+ new DataSupplier( m_xContent, 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/gio/gio_resultset.hxx b/ucb/source/ucp/gio/gio_resultset.hxx
new file mode 100644
index 0000000000..69d9d6f7c3
--- /dev/null
+++ b/ucb/source/ucp/gio/gio_resultset.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:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#pragma once
+
+#include <ucbhelper/resultsethelper.hxx>
+#include "gio_content.hxx"
+
+namespace gio
+{
+
+ class DynamicResultSet : public ::ucbhelper::ResultSetImplHelper
+ {
+ rtl::Reference< Content > m_xContent;
+ 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,
+ rtl::Reference< Content > xContent,
+ 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/gio/gio_seekable.cxx b/ucb/source/ucp/gio/gio_seekable.cxx
new file mode 100644
index 0000000000..a041852256
--- /dev/null
+++ b/ucb/source/ucp/gio/gio_seekable.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:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include <sal/config.h>
+
+#include <com/sun/star/io/IOException.hpp>
+#include <com/sun/star/io/NotConnectedException.hpp>
+#include <cppuhelper/queryinterface.hxx>
+
+#include "gio_seekable.hxx"
+#include "gio_content.hxx"
+
+namespace gio
+{
+
+Seekable::Seekable(GSeekable *pStream) : mpStream(pStream)
+{
+ if (!mpStream)
+ throw css::io::NotConnectedException();
+}
+
+Seekable::~Seekable()
+{
+}
+
+void SAL_CALL Seekable::truncate()
+{
+ if (!mpStream)
+ throw css::io::NotConnectedException();
+
+ if (!g_seekable_can_truncate(mpStream))
+ throw css::io::IOException("Truncate unsupported",
+ getXWeak());
+
+ GError *pError=nullptr;
+ if (!g_seekable_truncate(mpStream, 0, nullptr, &pError))
+ convertToIOException(pError, getXWeak());
+}
+
+void SAL_CALL Seekable::seek( sal_Int64 location )
+{
+ if (!mpStream)
+ throw css::io::NotConnectedException();
+
+ if (!g_seekable_can_seek(mpStream))
+ throw css::io::IOException("Seek unsupported",
+ getXWeak());
+
+ GError *pError=nullptr;
+ if (!g_seekable_seek(mpStream, location, G_SEEK_SET, nullptr, &pError))
+ convertToIOException(pError, getXWeak());
+}
+
+sal_Int64 SAL_CALL Seekable::getPosition()
+{
+ if (!mpStream)
+ throw css::io::NotConnectedException();
+
+ return g_seekable_tell(mpStream);
+}
+
+sal_Int64 SAL_CALL Seekable::getLength()
+{
+ if (!mpStream)
+ throw css::io::NotConnectedException();
+
+ bool bOk = false;
+ sal_uInt64 nSize = 0;
+
+ GFileInfo* pInfo = G_IS_FILE_INPUT_STREAM(mpStream)
+ ? g_file_input_stream_query_info(G_FILE_INPUT_STREAM(mpStream), G_FILE_ATTRIBUTE_STANDARD_SIZE, nullptr, nullptr)
+ : g_file_output_stream_query_info(G_FILE_OUTPUT_STREAM(mpStream), G_FILE_ATTRIBUTE_STANDARD_SIZE, nullptr, nullptr);
+
+ if (pInfo)
+ {
+ if (g_file_info_has_attribute(pInfo, G_FILE_ATTRIBUTE_STANDARD_SIZE))
+ {
+ nSize = g_file_info_get_size(pInfo);
+ bOk = true;
+ }
+ g_object_unref(pInfo);
+ }
+
+ if (!bOk)
+ {
+ GError *pError=nullptr;
+ sal_Int64 nCurr = getPosition();
+ if (!g_seekable_seek(mpStream, 0, G_SEEK_END, nullptr, &pError))
+ convertToIOException(pError, getXWeak());
+ nSize = getPosition();
+ seek(nCurr);
+ }
+
+ return nSize;
+}
+
+css::uno::Any Seekable::queryInterface( const css::uno::Type &type )
+{
+ css::uno::Any aRet = ::cppu::queryInterface ( type,
+ static_cast< XSeekable * >( this ) );
+
+ if (!aRet.hasValue() && g_seekable_can_truncate(mpStream))
+ aRet = ::cppu::queryInterface ( type, static_cast< XTruncate * >( this ) );
+
+ return aRet.hasValue() ? aRet : OWeakObject::queryInterface( type );
+}
+
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/ucb/source/ucp/gio/gio_seekable.hxx b/ucb/source/ucp/gio/gio_seekable.hxx
new file mode 100644
index 0000000000..9bf284b890
--- /dev/null
+++ b/ucb/source/ucp/gio/gio_seekable.hxx
@@ -0,0 +1,60 @@
+/* -*- 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:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#pragma once
+
+#include <sal/types.h>
+#include <rtl/ustring.hxx>
+#include <cppuhelper/weak.hxx>
+
+#include <com/sun/star/io/XTruncate.hpp>
+#include <com/sun/star/io/XSeekable.hpp>
+
+#include <gio/gio.h>
+
+namespace gio
+{
+class Seekable : public css::io::XTruncate, public css::io::XSeekable, public ::cppu::OWeakObject
+{
+private:
+ GSeekable* mpStream;
+
+public:
+ explicit Seekable(GSeekable* pStream);
+ virtual ~Seekable() override;
+
+ // XInterface
+ virtual css::uno::Any SAL_CALL queryInterface(const css::uno::Type& type) override;
+ virtual void SAL_CALL acquire() noexcept override { OWeakObject::acquire(); }
+ virtual void SAL_CALL release() noexcept override { OWeakObject::release(); }
+
+ // XSeekable
+ virtual void SAL_CALL seek(sal_Int64 location) override;
+
+ virtual sal_Int64 SAL_CALL getPosition() override;
+
+ virtual sal_Int64 SAL_CALL getLength() override;
+
+ // XTruncate
+ virtual void SAL_CALL truncate() override;
+};
+
+} // namespace gio
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/ucb/source/ucp/gio/ucpgio.component b/ucb/source/ucp/gio/ucpgio.component
new file mode 100644
index 0000000000..7d128067a8
--- /dev/null
+++ b/ucb/source/ucp/gio/ucpgio.component
@@ -0,0 +1,26 @@
+<?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/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-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.GIOContentProvider"
+ constructor="ucb_gio_ContentProvider_get_implementation" single-instance="true">
+ <service name="com.sun.star.ucb.GIOContentProvider"/>
+ </implementation>
+</component>
diff --git a/ucb/source/ucp/hierarchy/dynamicresultset.cxx b/ucb/source/ucp/hierarchy/dynamicresultset.cxx
new file mode 100644
index 0000000000..f9abe0667f
--- /dev/null
+++ b/ucb/source/ucp/hierarchy/dynamicresultset.cxx
@@ -0,0 +1,78 @@
+/* -*- 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:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+
+/**************************************************************************
+ TODO
+ **************************************************************************
+
+ - This implementation is not a dynamic result set!!! It only implements
+ the necessary interfaces, but never recognizes/notifies changes!!!
+
+ *************************************************************************/
+#include <utility>
+
+#include "hierarchydatasupplier.hxx"
+#include "dynamicresultset.hxx"
+
+using namespace com::sun::star;
+using namespace hierarchy_ucp;
+
+
+// DynamicResultSet Implementation.
+
+
+DynamicResultSet::DynamicResultSet(
+ const uno::Reference< uno::XComponentContext >& rxContext,
+ rtl::Reference< HierarchyContent > xContent,
+ const ucb::OpenCommandArgument2& rCommand )
+: ResultSetImplHelper( rxContext, rCommand ),
+ m_xContent(std::move( xContent ))
+{
+}
+
+
+// Non-interface methods.
+
+
+void DynamicResultSet::initStatic()
+{
+ m_xResultSet1
+ = new ::ucbhelper::ResultSet(
+ m_xContext,
+ m_aCommand.Properties,
+ new HierarchyResultSetDataSupplier( m_xContext,
+ m_xContent,
+ m_aCommand.Mode ) );
+}
+
+
+void DynamicResultSet::initDynamic()
+{
+ m_xResultSet1
+ = new ::ucbhelper::ResultSet(
+ m_xContext,
+ m_aCommand.Properties,
+ new HierarchyResultSetDataSupplier( m_xContext,
+ m_xContent,
+ m_aCommand.Mode ) );
+ m_xResultSet2 = m_xResultSet1;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/ucb/source/ucp/hierarchy/dynamicresultset.hxx b/ucb/source/ucp/hierarchy/dynamicresultset.hxx
new file mode 100644
index 0000000000..ed10c38745
--- /dev/null
+++ b/ucb/source/ucp/hierarchy/dynamicresultset.hxx
@@ -0,0 +1,45 @@
+/* -*- 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:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#pragma once
+
+#include <rtl/ref.hxx>
+#include <ucbhelper/resultsethelper.hxx>
+#include "hierarchycontent.hxx"
+
+namespace hierarchy_ucp {
+
+class DynamicResultSet : public ::ucbhelper::ResultSetImplHelper
+{
+ rtl::Reference< HierarchyContent > m_xContent;
+
+private:
+ virtual void initStatic() override;
+ virtual void initDynamic() override;
+
+public:
+ DynamicResultSet(
+ const css::uno::Reference< css::uno::XComponentContext >& rxContext,
+ rtl::Reference< HierarchyContent > xContent,
+ const css::ucb::OpenCommandArgument2& rCommand );
+};
+
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/ucb/source/ucp/hierarchy/hierarchycontent.cxx b/ucb/source/ucp/hierarchy/hierarchycontent.cxx
new file mode 100644
index 0000000000..885dc86639
--- /dev/null
+++ b/ucb/source/ucp/hierarchy/hierarchycontent.cxx
@@ -0,0 +1,1776 @@
+/* -*- 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:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+
+/**************************************************************************
+ TODO
+ **************************************************************************
+
+ - optimize transfer command. "Move" should be implementable much more
+ efficient!
+
+ **************************************************************************
+
+ - Root Folder vs. 'normal' Folder
+ - root doesn't support command 'delete'
+ - root doesn't support command 'insert'
+ - root needs not created via XContentCreator - queryContent with root
+ folder id ( HIERARCHY_ROOT_FOLDER_URL ) always returns a value != 0
+ - root has no parent.
+
+ *************************************************************************/
+#include <osl/diagnose.h>
+
+#include <rtl/ustring.hxx>
+#include <com/sun/star/beans/IllegalTypeException.hpp>
+#include <com/sun/star/beans/PropertyAttribute.hpp>
+#include <com/sun/star/beans/PropertyExistException.hpp>
+#include <com/sun/star/beans/PropertyState.hpp>
+#include <com/sun/star/lang/IllegalAccessException.hpp>
+#include <com/sun/star/ucb/ContentInfoAttribute.hpp>
+#include <com/sun/star/ucb/IllegalIdentifierException.hpp>
+#include <com/sun/star/ucb/InsertCommandArgument.hpp>
+#include <com/sun/star/ucb/InteractiveBadTransferURLException.hpp>
+#include <com/sun/star/ucb/MissingPropertiesException.hpp>
+#include <com/sun/star/ucb/NameClash.hpp>
+#include <com/sun/star/ucb/NameClashException.hpp>
+#include <com/sun/star/ucb/OpenCommandArgument2.hpp>
+#include <com/sun/star/ucb/TransferInfo.hpp>
+#include <com/sun/star/ucb/UnsupportedCommandException.hpp>
+#include <com/sun/star/ucb/UnsupportedNameClashException.hpp>
+#include <com/sun/star/ucb/XCommandInfo.hpp>
+#include <com/sun/star/ucb/XPersistentPropertySet.hpp>
+#include <com/sun/star/uno/Any.hxx>
+#include <com/sun/star/uno/Sequence.hxx>
+#include <comphelper/propertysequence.hxx>
+#include <comphelper/sequence.hxx>
+#include <cppuhelper/queryinterface.hxx>
+#include <ucbhelper/contentidentifier.hxx>
+#include <ucbhelper/propertyvalueset.hxx>
+#include <ucbhelper/cancelcommandexecution.hxx>
+#include <ucbhelper/macros.hxx>
+#include <utility>
+#include "hierarchycontent.hxx"
+#include "hierarchyprovider.hxx"
+#include "dynamicresultset.hxx"
+#include "hierarchyuri.hxx"
+
+#include "../inc/urihelper.hxx"
+
+using namespace com::sun::star;
+using namespace hierarchy_ucp;
+
+
+// HierarchyContent Implementation.
+
+
+// static ( "virtual" ctor )
+rtl::Reference<HierarchyContent> HierarchyContent::create(
+ const uno::Reference< uno::XComponentContext >& rxContext,
+ HierarchyContentProvider* pProvider,
+ const uno::Reference< ucb::XContentIdentifier >& Identifier )
+{
+ // Fail, if content does not exist.
+ HierarchyContentProperties aProps;
+ if ( !loadData( rxContext, pProvider, Identifier, aProps ) )
+ return nullptr;
+
+ return new HierarchyContent( rxContext, pProvider, Identifier, std::move(aProps) );
+}
+
+
+// static ( "virtual" ctor )
+rtl::Reference<HierarchyContent> HierarchyContent::create(
+ const uno::Reference< uno::XComponentContext >& rxContext,
+ HierarchyContentProvider* pProvider,
+ const uno::Reference< ucb::XContentIdentifier >& Identifier,
+ const ucb::ContentInfo& Info )
+{
+ if ( Info.Type.isEmpty() )
+ return nullptr;
+
+ if ( Info.Type != HIERARCHY_FOLDER_CONTENT_TYPE && Info.Type != HIERARCHY_LINK_CONTENT_TYPE )
+ return nullptr;
+
+ return new HierarchyContent( rxContext, pProvider, Identifier, Info );
+}
+
+
+HierarchyContent::HierarchyContent(
+ const uno::Reference< uno::XComponentContext >& rxContext,
+ HierarchyContentProvider* pProvider,
+ const uno::Reference< ucb::XContentIdentifier >& Identifier,
+ HierarchyContentProperties aProps )
+: ContentImplHelper( rxContext, pProvider, Identifier ),
+ m_aProps(std::move( aProps )),
+ m_eState( PERSISTENT ),
+ m_pProvider( pProvider ),
+ m_bCheckedReadOnly( false ),
+ m_bIsReadOnly( true )
+{
+ setKind( Identifier );
+}
+
+
+HierarchyContent::HierarchyContent(
+ const uno::Reference< uno::XComponentContext >& rxContext,
+ HierarchyContentProvider* pProvider,
+ const uno::Reference< ucb::XContentIdentifier >& Identifier,
+ const ucb::ContentInfo& Info )
+ : ContentImplHelper( rxContext, pProvider, Identifier ),
+ m_aProps( Info.Type == HIERARCHY_FOLDER_CONTENT_TYPE ? HierarchyEntryData::FOLDER : HierarchyEntryData::LINK ),
+ m_eState( TRANSIENT ),
+ m_pProvider( pProvider ),
+ m_bCheckedReadOnly( false ),
+ m_bIsReadOnly( true )
+{
+ setKind( Identifier );
+}
+
+
+// virtual
+HierarchyContent::~HierarchyContent()
+{
+}
+
+
+// XInterface methods.
+
+
+// virtual
+void SAL_CALL HierarchyContent::acquire()
+ noexcept
+{
+ ContentImplHelper::acquire();
+}
+
+
+// virtual
+void SAL_CALL HierarchyContent::release()
+ noexcept
+{
+ ContentImplHelper::release();
+}
+
+
+// virtual
+uno::Any SAL_CALL HierarchyContent::queryInterface( const uno::Type & rType )
+{
+ uno::Any aRet = ContentImplHelper::queryInterface( rType );
+
+ if ( !aRet.hasValue() )
+ {
+ // Note: isReadOnly may be relative expensive. So avoid calling it
+ // unless it is really necessary.
+ aRet = cppu::queryInterface(
+ rType, static_cast< ucb::XContentCreator * >( this ) );
+ if ( aRet.hasValue() )
+ {
+ if ( !isFolder() || isReadOnly() )
+ return uno::Any();
+ }
+ }
+
+ return aRet;
+}
+
+
+// XTypeProvider methods.
+
+
+XTYPEPROVIDER_COMMON_IMPL( HierarchyContent );
+
+
+// virtual
+uno::Sequence< uno::Type > SAL_CALL HierarchyContent::getTypes()
+{
+ if ( isFolder() && !isReadOnly() )
+ {
+ static cppu::OTypeCollection s_aFolderTypes(
+ 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_aFolderTypes.getTypes();
+ }
+ else
+ {
+ static cppu::OTypeCollection s_aDocumentTypes(
+ 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_aDocumentTypes.getTypes();
+ }
+}
+
+
+// XServiceInfo methods.
+
+
+// virtual
+OUString SAL_CALL HierarchyContent::getImplementationName()
+{
+ return "com.sun.star.comp.ucb.HierarchyContent";
+}
+
+
+// virtual
+uno::Sequence< OUString > SAL_CALL
+HierarchyContent::getSupportedServiceNames()
+{
+ uno::Sequence< OUString > aSNS( 1 );
+
+ if ( m_eKind == LINK )
+ aSNS.getArray()[ 0 ] = "com.sun.star.ucb.HierarchyLinkContent";
+ else if ( m_eKind == FOLDER )
+ aSNS.getArray()[ 0 ] = "com.sun.star.ucb.HierarchyFolderContent";
+ else
+ aSNS.getArray()[ 0 ] = "com.sun.star.ucb.HierarchyRootFolderContent";
+
+ return aSNS;
+}
+
+
+// XContent methods.
+
+
+// virtual
+OUString SAL_CALL HierarchyContent::getContentType()
+{
+ return m_aProps.getContentType();
+}
+
+
+// virtual
+uno::Reference< ucb::XContentIdentifier > SAL_CALL
+HierarchyContent::getIdentifier()
+{
+ // Transient?
+ if ( m_eState == TRANSIENT )
+ {
+ // Transient contents have no identifier.
+ return uno::Reference< ucb::XContentIdentifier >();
+ }
+
+ return ContentImplHelper::getIdentifier();
+}
+
+
+// XCommandProcessor methods.
+
+
+// virtual
+uno::Any SAL_CALL HierarchyContent::execute(
+ const ucb::Command& aCommand,
+ sal_Int32 /*CommandId*/,
+ const uno::Reference< ucb::XCommandEnvironment >& Environment )
+{
+ uno::Any aRet;
+
+ if ( aCommand.Name == "getPropertyValues" )
+ {
+
+ // getPropertyValues
+
+
+ uno::Sequence< beans::Property > Properties;
+ if ( !( aCommand.Argument >>= Properties ) )
+ {
+ ucbhelper::cancelCommandExecution(
+ uno::Any( lang::IllegalArgumentException(
+ "Wrong argument type!",
+ getXWeak(),
+ -1 ) ),
+ Environment );
+ // Unreachable
+ }
+
+ aRet <<= getPropertyValues( Properties );
+ }
+ else if ( aCommand.Name == "setPropertyValues" )
+ {
+
+ // setPropertyValues
+
+
+ uno::Sequence< beans::PropertyValue > aProperties;
+ if ( !( aCommand.Argument >>= aProperties ) )
+ {
+ ucbhelper::cancelCommandExecution(
+ uno::Any( lang::IllegalArgumentException(
+ "Wrong argument type!",
+ getXWeak(),
+ -1 ) ),
+ Environment );
+ // Unreachable
+ }
+
+ if ( !aProperties.hasElements() )
+ {
+ ucbhelper::cancelCommandExecution(
+ uno::Any( lang::IllegalArgumentException(
+ "No properties!",
+ getXWeak(),
+ -1 ) ),
+ Environment );
+ // Unreachable
+ }
+
+ aRet <<= setPropertyValues( aProperties, Environment );
+ }
+ else if ( aCommand.Name == "getPropertySetInfo" )
+ {
+
+ // getPropertySetInfo
+
+
+ aRet <<= getPropertySetInfo( Environment );
+ }
+ else if ( aCommand.Name == "getCommandInfo" )
+ {
+
+ // getCommandInfo
+
+
+ aRet <<= getCommandInfo( Environment );
+ }
+ else if ( aCommand.Name == "open" && isFolder() )
+ {
+
+ // open command for a folder content
+
+
+ ucb::OpenCommandArgument2 aOpenCommand;
+ if ( !( aCommand.Argument >>= aOpenCommand ) )
+ {
+ ucbhelper::cancelCommandExecution(
+ uno::Any( lang::IllegalArgumentException(
+ "Wrong argument type!",
+ getXWeak(),
+ -1 ) ),
+ Environment );
+ // Unreachable
+ }
+
+ uno::Reference< ucb::XDynamicResultSet > xSet
+ = new DynamicResultSet( m_xContext, this, aOpenCommand );
+ aRet <<= xSet;
+ }
+ else if ( aCommand.Name == "insert" && ( m_eKind != ROOT ) && !isReadOnly() )
+ {
+
+ // insert
+ // ( Not available at root folder )
+
+
+ ucb::InsertCommandArgument aArg;
+ if ( !( aCommand.Argument >>= aArg ) )
+ {
+ ucbhelper::cancelCommandExecution(
+ uno::Any( lang::IllegalArgumentException(
+ "Wrong argument type!",
+ getXWeak(),
+ -1 ) ),
+ Environment );
+ // Unreachable
+ }
+
+ sal_Int32 nNameClash = aArg.ReplaceExisting
+ ? ucb::NameClash::OVERWRITE
+ : ucb::NameClash::ERROR;
+ insert( nNameClash, Environment );
+ }
+ else if ( aCommand.Name == "delete" && ( m_eKind != ROOT ) && !isReadOnly() )
+ {
+
+ // delete
+ // ( Not available at root folder )
+
+
+ bool bDeletePhysical = false;
+ aCommand.Argument >>= bDeletePhysical;
+ destroy( bDeletePhysical, Environment );
+
+ // Remove own and all children's persistent data.
+ if ( !removeData() )
+ {
+ uno::Sequence<uno::Any> aArgs(comphelper::InitAnyPropertySequence(
+ {
+ {"Uri", uno::Any(m_xIdentifier->getContentIdentifier())}
+ }));
+ ucbhelper::cancelCommandExecution(
+ ucb::IOErrorCode_CANT_WRITE,
+ aArgs,
+ Environment,
+ "Cannot remove persistent data!",
+ this );
+ // Unreachable
+ }
+
+ // Remove own and all children's Additional Core Properties.
+ removeAdditionalPropertySet();
+ }
+ else if ( aCommand.Name == "transfer" && isFolder() && !isReadOnly() )
+ {
+
+ // transfer
+ // ( Not available at link objects )
+
+
+ ucb::TransferInfo aInfo;
+ if ( !( aCommand.Argument >>= aInfo ) )
+ {
+ OSL_FAIL( "Wrong argument type!" );
+ ucbhelper::cancelCommandExecution(
+ uno::Any( lang::IllegalArgumentException(
+ "Wrong argument type!",
+ getXWeak(),
+ -1 ) ),
+ Environment );
+ // Unreachable
+ }
+
+ transfer( aInfo, Environment );
+ }
+ else if ( aCommand.Name == "createNewContent" && isFolder() && !isReadOnly() )
+ {
+
+ // createNewContent
+ // ( Not available at link objects )
+
+
+ ucb::ContentInfo aInfo;
+ if ( !( aCommand.Argument >>= aInfo ) )
+ {
+ OSL_FAIL( "Wrong argument type!" );
+ ucbhelper::cancelCommandExecution(
+ uno::Any( lang::IllegalArgumentException(
+ "Wrong argument type!",
+ getXWeak(),
+ -1 ) ),
+ Environment );
+ // Unreachable
+ }
+
+ aRet <<= createNewContent( aInfo );
+ }
+ else
+ {
+
+ // Unsupported command
+
+
+ ucbhelper::cancelCommandExecution(
+ uno::Any( ucb::UnsupportedCommandException(
+ OUString(),
+ getXWeak() ) ),
+ Environment );
+ // Unreachable
+ }
+
+ return aRet;
+}
+
+
+// virtual
+void SAL_CALL HierarchyContent::abort( sal_Int32 /*CommandId*/ )
+{
+ // @@@ Generally, no action takes much time...
+}
+
+
+// XContentCreator methods.
+
+
+// virtual
+uno::Sequence< ucb::ContentInfo > SAL_CALL
+HierarchyContent::queryCreatableContentsInfo()
+{
+ return m_aProps.getCreatableContentsInfo();
+}
+
+
+// virtual
+uno::Reference< ucb::XContent > SAL_CALL
+HierarchyContent::createNewContent( const ucb::ContentInfo& Info )
+{
+ if ( isFolder() )
+ {
+ osl::Guard< osl::Mutex > aGuard( m_aMutex );
+
+ if ( Info.Type.isEmpty() )
+ return uno::Reference< ucb::XContent >();
+
+ bool bCreateFolder = Info.Type == HIERARCHY_FOLDER_CONTENT_TYPE;
+
+ if ( !bCreateFolder && Info.Type != HIERARCHY_LINK_CONTENT_TYPE )
+ return uno::Reference< ucb::XContent >();
+
+ OUString aURL = m_xIdentifier->getContentIdentifier();
+
+ OSL_ENSURE( !aURL.isEmpty(),
+ "HierarchyContent::createNewContent - empty identifier!" );
+
+ if ( ( aURL.lastIndexOf( '/' ) + 1 ) != aURL.getLength() )
+ aURL += "/";
+
+ if ( bCreateFolder )
+ aURL += "New_Folder";
+ else
+ aURL += "New_Link";
+
+ uno::Reference< ucb::XContentIdentifier > xId
+ = new ::ucbhelper::ContentIdentifier( aURL );
+
+ return create( m_xContext, m_pProvider, xId, Info );
+ }
+ else
+ {
+ OSL_FAIL( "createNewContent called on non-folder object!" );
+ return uno::Reference< ucb::XContent >();
+ }
+}
+
+
+// virtual
+OUString HierarchyContent::getParentURL()
+{
+ HierarchyUri aUri( m_xIdentifier->getContentIdentifier() );
+ return aUri.getParentUri();
+}
+
+
+//static
+bool HierarchyContent::hasData(
+ const uno::Reference< uno::XComponentContext >& rxContext,
+ HierarchyContentProvider* pProvider,
+ const uno::Reference< ucb::XContentIdentifier >& Identifier )
+{
+ OUString aURL = Identifier->getContentIdentifier();
+
+ // Am I a root folder?
+ HierarchyUri aUri( aURL );
+ if ( aUri.isRootFolder() )
+ {
+ // hasData must always return 'true' for root folder
+ // even if no persistent data exist!!!
+ return true;
+ }
+
+ return HierarchyEntry( rxContext, pProvider, aURL ).hasData();
+}
+
+
+//static
+bool HierarchyContent::loadData(
+ const uno::Reference< uno::XComponentContext >& rxContext,
+ HierarchyContentProvider* pProvider,
+ const uno::Reference< ucb::XContentIdentifier >& Identifier,
+ HierarchyContentProperties& rProps )
+{
+ OUString aURL = Identifier->getContentIdentifier();
+
+ // Am I a root folder?
+ HierarchyUri aUri( aURL );
+ if ( aUri.isRootFolder() )
+ {
+ rProps = HierarchyContentProperties( HierarchyEntryData::FOLDER );
+ }
+ else
+ {
+ HierarchyEntry aEntry( rxContext, pProvider, aURL );
+ HierarchyEntryData aData;
+ if ( !aEntry.getData( aData ) )
+ return false;
+
+ rProps = HierarchyContentProperties( aData );
+ }
+ return true;
+}
+
+
+bool HierarchyContent::storeData()
+{
+ HierarchyEntry aEntry(
+ m_xContext, m_pProvider, m_xIdentifier->getContentIdentifier() );
+ return aEntry.setData( m_aProps.getHierarchyEntryData() );
+}
+
+
+void HierarchyContent::renameData(
+ const uno::Reference< ucb::XContentIdentifier >& xOldId,
+ const uno::Reference< ucb::XContentIdentifier >& xNewId )
+{
+ HierarchyEntry aEntry(
+ m_xContext, m_pProvider, xOldId->getContentIdentifier() );
+ aEntry.move( xNewId->getContentIdentifier(),
+ m_aProps.getHierarchyEntryData() );
+}
+
+
+bool HierarchyContent::removeData()
+{
+ HierarchyEntry aEntry(
+ m_xContext, m_pProvider, m_xIdentifier->getContentIdentifier() );
+ return aEntry.remove();
+}
+
+
+void HierarchyContent::setKind(
+ const uno::Reference< ucb::XContentIdentifier >& Identifier )
+{
+ if ( m_aProps.getIsFolder() )
+ {
+ // Am I a root folder?
+ HierarchyUri aUri( Identifier->getContentIdentifier() );
+ if ( aUri.isRootFolder() )
+ m_eKind = ROOT;
+ else
+ m_eKind = FOLDER;
+ }
+ else
+ m_eKind = LINK;
+}
+
+
+bool HierarchyContent::isReadOnly()
+{
+ if ( !m_bCheckedReadOnly )
+ {
+ osl::Guard< osl::Mutex > aGuard( m_aMutex );
+ if ( !m_bCheckedReadOnly )
+ {
+ m_bCheckedReadOnly = true;
+ m_bIsReadOnly = true;
+
+ HierarchyUri aUri( m_xIdentifier->getContentIdentifier() );
+ uno::Reference< lang::XMultiServiceFactory > xConfigProv
+ = m_pProvider->getConfigProvider( aUri.getService() );
+ if ( xConfigProv.is() )
+ {
+ uno::Sequence< OUString > aNames
+ = xConfigProv->getAvailableServiceNames();
+ m_bIsReadOnly = comphelper::findValue(aNames, "com.sun.star.ucb.HierarchyDataReadWriteAccess") == -1;
+ }
+ }
+ }
+
+ return m_bIsReadOnly;
+}
+
+
+uno::Reference< ucb::XContentIdentifier >
+HierarchyContent::makeNewIdentifier( const OUString& rTitle )
+{
+ osl::Guard< osl::Mutex > aGuard( m_aMutex );
+
+ // Assemble new content identifier...
+ HierarchyUri aUri( m_xIdentifier->getContentIdentifier() );
+ OUString aNewURL = aUri.getParentUri() + "/" +
+ ::ucb_impl::urihelper::encodeSegment( rTitle );
+
+ return uno::Reference< ucb::XContentIdentifier >(
+ new ::ucbhelper::ContentIdentifier( aNewURL ) );
+}
+
+
+void HierarchyContent::queryChildren( HierarchyContentRefVector& rChildren )
+{
+ if ( ( m_eKind != FOLDER ) && ( m_eKind != ROOT ) )
+ return;
+
+ // Obtain a list with a snapshot of all currently instantiated contents
+ // from provider and extract the contents which are direct children
+ // of this content.
+
+ ::ucbhelper::ContentRefList aAllContents;
+ m_xProvider->queryExistingContents( aAllContents );
+
+ OUString aURL = m_xIdentifier->getContentIdentifier();
+ sal_Int32 nURLPos = aURL.lastIndexOf( '/' );
+
+ if ( nURLPos != ( aURL.getLength() - 1 ) )
+ {
+ // No trailing slash found. Append.
+ aURL += "/";
+ }
+
+ sal_Int32 nLen = aURL.getLength();
+
+ for ( const auto& rContent : aAllContents )
+ {
+ ::ucbhelper::ContentImplHelperRef xChild = rContent;
+ OUString aChildURL
+ = xChild->getIdentifier()->getContentIdentifier();
+
+ // Is aURL a prefix of aChildURL?
+ if ( ( aChildURL.getLength() > nLen ) &&
+ ( aChildURL.startsWith( aURL ) ) )
+ {
+ sal_Int32 nPos = aChildURL.indexOf( '/', nLen );
+
+ if ( ( nPos == -1 ) ||
+ ( nPos == ( aChildURL.getLength() - 1 ) ) )
+ {
+ // No further slashes/ only a final slash. It's a child!
+ rChildren.emplace_back(
+ static_cast< HierarchyContent * >( xChild.get() ) );
+ }
+ }
+ }
+}
+
+
+bool HierarchyContent::exchangeIdentity(
+ const uno::Reference< ucb::XContentIdentifier >& xNewId )
+{
+ if ( !xNewId.is() )
+ return false;
+
+ osl::ClearableGuard< osl::Mutex > aGuard( m_aMutex );
+
+ uno::Reference< ucb::XContent > xThis = this;
+
+ // Already persistent?
+ if ( m_eState != PERSISTENT )
+ {
+ OSL_FAIL( "HierarchyContent::exchangeIdentity - Not persistent!" );
+ return false;
+ }
+
+ // Am I the root folder?
+ if ( m_eKind == ROOT )
+ {
+ OSL_FAIL( "HierarchyContent::exchangeIdentity - "
+ "Not supported by root folder!" );
+ return false;
+ }
+
+ // Exchange own identity.
+
+ // Fail, if a content with given id already exists.
+ if ( !hasData( xNewId ) )
+ {
+ OUString aOldURL = m_xIdentifier->getContentIdentifier();
+
+ aGuard.clear();
+ if ( exchange( xNewId ) )
+ {
+ if ( m_eKind == FOLDER )
+ {
+ // Process instantiated children...
+
+ HierarchyContentRefVector aChildren;
+ queryChildren( aChildren );
+
+ for ( const auto& rChild : aChildren )
+ {
+ HierarchyContentRef xChild = rChild;
+
+ // Create new content identifier for the child...
+ uno::Reference< ucb::XContentIdentifier > xOldChildId
+ = xChild->getIdentifier();
+ OUString aOldChildURL
+ = xOldChildId->getContentIdentifier();
+ OUString aNewChildURL
+ = aOldChildURL.replaceAt(
+ 0,
+ aOldURL.getLength(),
+ xNewId->getContentIdentifier() );
+ uno::Reference< ucb::XContentIdentifier > xNewChildId
+ = new ::ucbhelper::ContentIdentifier( aNewChildURL );
+
+ if ( !xChild->exchangeIdentity( xNewChildId ) )
+ return false;
+ }
+ }
+ return true;
+ }
+ }
+
+ OSL_FAIL( "HierarchyContent::exchangeIdentity - "
+ "Panic! Cannot exchange identity!" );
+ return false;
+}
+
+
+// static
+uno::Reference< sdbc::XRow > HierarchyContent::getPropertyValues(
+ const uno::Reference< uno::XComponentContext >& rxContext,
+ const uno::Sequence< beans::Property >& rProperties,
+ const HierarchyContentProperties& rData,
+ HierarchyContentProvider* pProvider,
+ const OUString& rContentId )
+{
+ // Note: Empty sequence means "get values of all supported properties".
+
+ rtl::Reference< ::ucbhelper::PropertyValueSet > xRow
+ = new ::ucbhelper::PropertyValueSet( rxContext );
+
+ if ( rProperties.hasElements() )
+ {
+ uno::Reference< beans::XPropertySet > xAdditionalPropSet;
+ bool bTriedToGetAdditionalPropSet = false;
+
+ for ( const beans::Property& rProp : rProperties )
+ {
+ // Process Core properties.
+
+ if ( rProp.Name == "ContentType" )
+ {
+ xRow->appendString ( rProp, rData.getContentType() );
+ }
+ else if ( rProp.Name == "Title" )
+ {
+ xRow->appendString ( rProp, rData.getTitle() );
+ }
+ else if ( rProp.Name == "IsDocument" )
+ {
+ xRow->appendBoolean( rProp, rData.getIsDocument() );
+ }
+ else if ( rProp.Name == "IsFolder" )
+ {
+ xRow->appendBoolean( rProp, rData.getIsFolder() );
+ }
+ else if ( rProp.Name == "CreatableContentsInfo" )
+ {
+ xRow->appendObject(
+ rProp, uno::Any( rData.getCreatableContentsInfo() ) );
+ }
+ else if ( rProp.Name == "TargetURL" )
+ {
+ // TargetURL is only supported by links.
+
+ if ( rData.getIsDocument() )
+ xRow->appendString( rProp, rData.getTargetURL() );
+ else
+ xRow->appendVoid( rProp );
+ }
+ else
+ {
+ // Not a Core Property! Maybe it's an Additional Core Property?!
+
+ if ( !bTriedToGetAdditionalPropSet && !xAdditionalPropSet.is() )
+ {
+ xAdditionalPropSet =
+ pProvider->getAdditionalPropertySet( rContentId,
+ false );
+ bTriedToGetAdditionalPropSet = true;
+ }
+
+ if ( xAdditionalPropSet.is() )
+ {
+ if ( !xRow->appendPropertySetValue(
+ xAdditionalPropSet,
+ rProp ) )
+ {
+ // Append empty entry.
+ xRow->appendVoid( rProp );
+ }
+ }
+ else
+ {
+ // Append empty entry.
+ xRow->appendVoid( rProp );
+ }
+ }
+ }
+ }
+ else
+ {
+ // Append all Core Properties.
+ xRow->appendString (
+ beans::Property( "ContentType",
+ -1,
+ cppu::UnoType<OUString>::get(),
+ beans::PropertyAttribute::BOUND
+ | beans::PropertyAttribute::READONLY ),
+ rData.getContentType() );
+ xRow->appendString (
+ beans::Property( "Title",
+ -1,
+ cppu::UnoType<OUString>::get(),
+ // @@@ Might actually be read-only!
+ beans::PropertyAttribute::BOUND ),
+ rData.getTitle() );
+ xRow->appendBoolean(
+ beans::Property( "IsDocument",
+ -1,
+ cppu::UnoType<bool>::get(),
+ beans::PropertyAttribute::BOUND
+ | beans::PropertyAttribute::READONLY ),
+ rData.getIsDocument() );
+ xRow->appendBoolean(
+ beans::Property( "IsFolder",
+ -1,
+ cppu::UnoType<bool>::get(),
+ beans::PropertyAttribute::BOUND
+ | beans::PropertyAttribute::READONLY ),
+ rData.getIsFolder() );
+
+ if ( rData.getIsDocument() )
+ xRow->appendString(
+ beans::Property( "TargetURL",
+ -1,
+ cppu::UnoType<OUString>::get(),
+ // @@@ Might actually be read-only!
+ beans::PropertyAttribute::BOUND ),
+ rData.getTargetURL() );
+ xRow->appendObject(
+ beans::Property(
+ "CreatableContentsInfo",
+ -1,
+ cppu::UnoType<uno::Sequence< ucb::ContentInfo >>::get(),
+ beans::PropertyAttribute::BOUND
+ | beans::PropertyAttribute::READONLY ),
+ uno::Any( rData.getCreatableContentsInfo() ) );
+
+ // Append all Additional Core Properties.
+
+ uno::Reference< beans::XPropertySet > xSet =
+ pProvider->getAdditionalPropertySet( rContentId, false );
+ xRow->appendPropertySet( xSet );
+ }
+
+ return xRow;
+}
+
+
+uno::Reference< sdbc::XRow > HierarchyContent::getPropertyValues(
+ const uno::Sequence< beans::Property >& rProperties )
+{
+ osl::Guard< osl::Mutex > aGuard( m_aMutex );
+ return getPropertyValues( m_xContext,
+ rProperties,
+ m_aProps,
+ m_pProvider,
+ m_xIdentifier->getContentIdentifier() );
+}
+
+
+uno::Sequence< uno::Any > HierarchyContent::setPropertyValues(
+ const uno::Sequence< beans::PropertyValue >& rValues,
+ const uno::Reference< ucb::XCommandEnvironment > & xEnv )
+{
+ osl::ResettableGuard< osl::Mutex > aGuard( m_aMutex );
+
+ uno::Sequence< uno::Any > aRet( rValues.getLength() );
+ auto aRetRange = asNonConstRange(aRet);
+ uno::Sequence< beans::PropertyChangeEvent > aChanges( rValues.getLength() );
+ sal_Int32 nChanged = 0;
+
+ beans::PropertyChangeEvent aEvent;
+ aEvent.Source = getXWeak();
+ aEvent.Further = false;
+// aEvent.PropertyName =
+ aEvent.PropertyHandle = -1;
+// aEvent.OldValue =
+// aEvent.NewValue =
+
+ const beans::PropertyValue* pValues = rValues.getConstArray();
+ sal_Int32 nCount = rValues.getLength();
+
+ uno::Reference< ucb::XPersistentPropertySet > xAdditionalPropSet;
+ bool bTriedToGetAdditionalPropSet = false;
+
+ bool bExchange = false;
+ OUString aOldTitle;
+ OUString aOldName;
+ sal_Int32 nTitlePos = -1;
+
+ for ( sal_Int32 n = 0; n < nCount; ++n )
+ {
+ const beans::PropertyValue& rValue = pValues[ n ];
+
+ if ( rValue.Name == "ContentType" )
+ {
+ // Read-only property!
+ aRetRange[ n ] <<= lang::IllegalAccessException(
+ "Property is read-only!",
+ getXWeak() );
+ }
+ else if ( rValue.Name == "IsDocument" )
+ {
+ // Read-only property!
+ aRetRange[ n ] <<= lang::IllegalAccessException(
+ "Property is read-only!",
+ getXWeak() );
+ }
+ else if ( rValue.Name == "IsFolder" )
+ {
+ // Read-only property!
+ aRetRange[ n ] <<= lang::IllegalAccessException(
+ "Property is read-only!",
+ getXWeak() );
+ }
+ else if ( rValue.Name == "CreatableContentsInfo" )
+ {
+ // Read-only property!
+ aRetRange[ n ] <<= lang::IllegalAccessException(
+ "Property is read-only!",
+ getXWeak() );
+ }
+ else if ( rValue.Name == "Title" )
+ {
+ if ( isReadOnly() )
+ {
+ aRetRange[ n ] <<= lang::IllegalAccessException(
+ "Property is read-only!",
+ getXWeak() );
+ }
+ else
+ {
+ OUString aNewValue;
+ if ( rValue.Value >>= aNewValue )
+ {
+ // No empty titles!
+ if ( !aNewValue.isEmpty() )
+ {
+ if ( aNewValue != m_aProps.getTitle() )
+ {
+ // modified title -> modified URL -> exchange !
+ if ( m_eState == PERSISTENT )
+ bExchange = true;
+
+ aOldTitle = m_aProps.getTitle();
+ aOldName = m_aProps.getName();
+
+ m_aProps.setTitle( aNewValue );
+ m_aProps.setName(
+ ::ucb_impl::urihelper::encodeSegment(
+ aNewValue ) );
+
+ // property change event will be set later...
+
+ // remember position within sequence of values
+ // (for error handling).
+ nTitlePos = n;
+ }
+ }
+ else
+ {
+ aRetRange[ n ] <<= lang::IllegalArgumentException(
+ "Empty title not allowed!",
+ getXWeak(),
+ -1 );
+ }
+ }
+ else
+ {
+ aRetRange[ n ] <<= beans::IllegalTypeException(
+ "Property value has wrong type!",
+ getXWeak() );
+ }
+ }
+ }
+ else if ( rValue.Name == "TargetURL" )
+ {
+ if ( isReadOnly() )
+ {
+ aRetRange[ n ] <<= lang::IllegalAccessException(
+ "Property is read-only!",
+ getXWeak() );
+ }
+ else
+ {
+ // TargetURL is only supported by links.
+
+ if ( m_eKind == LINK )
+ {
+ OUString aNewValue;
+ if ( rValue.Value >>= aNewValue )
+ {
+ // No empty target URL's!
+ if ( !aNewValue.isEmpty() )
+ {
+ if ( aNewValue != m_aProps.getTargetURL() )
+ {
+ aEvent.PropertyName = rValue.Name;
+ aEvent.OldValue <<= m_aProps.getTargetURL();
+ aEvent.NewValue <<= aNewValue;
+
+ aChanges.getArray()[ nChanged ] = aEvent;
+
+ m_aProps.setTargetURL( aNewValue );
+ nChanged++;
+ }
+ }
+ else
+ {
+ aRetRange[ n ] <<= lang::IllegalArgumentException(
+ "Empty target URL not allowed!",
+ getXWeak(),
+ -1 );
+ }
+ }
+ else
+ {
+ aRetRange[ n ] <<= beans::IllegalTypeException(
+ "Property value has wrong type!",
+ getXWeak() );
+ }
+ }
+ else
+ {
+ aRetRange[ n ] <<= beans::UnknownPropertyException(
+ "TargetURL only supported by links!",
+ getXWeak() );
+ }
+ }
+ }
+ else
+ {
+ // Not a Core Property! Maybe it's an Additional Core Property?!
+
+ if ( !bTriedToGetAdditionalPropSet && !xAdditionalPropSet.is() )
+ {
+ xAdditionalPropSet = getAdditionalPropertySet( false );
+ bTriedToGetAdditionalPropSet = true;
+ }
+
+ if ( xAdditionalPropSet.is() )
+ {
+ try
+ {
+ uno::Any aOldValue = xAdditionalPropSet->getPropertyValue(
+ rValue.Name );
+ if ( aOldValue != rValue.Value )
+ {
+ xAdditionalPropSet->setPropertyValue(
+ rValue.Name, rValue.Value );
+
+ aEvent.PropertyName = rValue.Name;
+ aEvent.OldValue = aOldValue;
+ aEvent.NewValue = rValue.Value;
+
+ aChanges.getArray()[ nChanged ] = aEvent;
+ nChanged++;
+ }
+ }
+ catch ( beans::UnknownPropertyException const & e )
+ {
+ aRetRange[ n ] <<= e;
+ }
+ catch ( lang::WrappedTargetException const & e )
+ {
+ aRetRange[ n ] <<= e;
+ }
+ catch ( beans::PropertyVetoException const & e )
+ {
+ aRetRange[ n ] <<= e;
+ }
+ catch ( lang::IllegalArgumentException const & e )
+ {
+ aRetRange[ n ] <<= e;
+ }
+ }
+ else
+ {
+ aRetRange[ n ] <<= uno::Exception(
+ "No property set for storing the value!",
+ getXWeak() );
+ }
+ }
+ }
+
+ if ( bExchange )
+ {
+ uno::Reference< ucb::XContentIdentifier > xOldId
+ = m_xIdentifier;
+ uno::Reference< ucb::XContentIdentifier > xNewId
+ = makeNewIdentifier( m_aProps.getTitle() );
+
+ aGuard.clear();
+ if ( exchangeIdentity( xNewId ) )
+ {
+ // Adapt persistent data.
+ renameData( xOldId, xNewId );
+
+ // Adapt Additional Core Properties.
+ renameAdditionalPropertySet( xOldId->getContentIdentifier(),
+ xNewId->getContentIdentifier() );
+ }
+ else
+ {
+ // Roll-back.
+ m_aProps.setTitle( aOldTitle );
+ m_aProps.setName ( aOldName );
+
+ aOldTitle.clear();
+ aOldName.clear();
+
+ // Set error .
+ aRetRange[ nTitlePos ] <<= uno::Exception(
+ "Exchange failed!",
+ getXWeak() );
+ }
+ aGuard.reset();
+ }
+
+ if ( !aOldTitle.isEmpty() )
+ {
+ aEvent.PropertyName = "Title";
+ aEvent.OldValue <<= aOldTitle;
+ aEvent.NewValue <<= m_aProps.getTitle();
+
+ aChanges.getArray()[ nChanged ] = aEvent;
+ nChanged++;
+ }
+
+ if ( nChanged > 0 )
+ {
+ // Save changes, if content was already made persistent.
+ if ( !bExchange && ( m_eState == PERSISTENT ) )
+ {
+ if ( !storeData() )
+ {
+ uno::Sequence<uno::Any> aArgs(comphelper::InitAnyPropertySequence(
+ {
+ {"Uri", uno::Any(m_xIdentifier->getContentIdentifier())}
+ }));
+ ucbhelper::cancelCommandExecution(
+ ucb::IOErrorCode_CANT_WRITE,
+ aArgs,
+ xEnv,
+ "Cannot store persistent data!",
+ this );
+ // Unreachable
+ }
+ }
+
+ aChanges.realloc( nChanged );
+
+ aGuard.clear();
+ notifyPropertiesChange( aChanges );
+ }
+
+ return aRet;
+}
+
+
+void HierarchyContent::insert( sal_Int32 nNameClashResolve,
+ const uno::Reference<
+ ucb::XCommandEnvironment > & xEnv )
+{
+ osl::ClearableGuard< osl::Mutex > aGuard( m_aMutex );
+
+ // Am I the root folder?
+ if ( m_eKind == ROOT )
+ {
+ ucbhelper::cancelCommandExecution(
+ uno::Any( ucb::UnsupportedCommandException(
+ "Not supported by root folder!",
+ getXWeak() ) ),
+ xEnv );
+ // Unreachable
+ }
+
+ // Check, if all required properties were set.
+ if ( m_aProps.getTitle().isEmpty() )
+ {
+ uno::Sequence<OUString> aProps { "Title" };
+ ucbhelper::cancelCommandExecution(
+ uno::Any( ucb::MissingPropertiesException(
+ OUString(),
+ getXWeak(),
+ aProps ) ),
+ xEnv );
+ // Unreachable
+ }
+
+ // Assemble new content identifier...
+
+ uno::Reference< ucb::XContentIdentifier > xId
+ = makeNewIdentifier( m_aProps.getTitle() );
+
+ // Handle possible name clash...
+
+ switch ( nNameClashResolve )
+ {
+ // fail.
+ case ucb::NameClash::ERROR:
+ if ( hasData( xId ) )
+ {
+ ucbhelper::cancelCommandExecution(
+ uno::Any(
+ ucb::NameClashException(
+ OUString(),
+ getXWeak(),
+ task::InteractionClassification_ERROR,
+ m_aProps.getTitle() ) ),
+ xEnv );
+ // Unreachable
+ }
+ break;
+
+ // replace existing object.
+ case ucb::NameClash::OVERWRITE:
+ break;
+
+ // "invent" a new valid title.
+ case ucb::NameClash::RENAME:
+ if ( hasData( xId ) )
+ {
+ sal_Int32 nTry = 0;
+
+ do
+ {
+ OUString aNewId = xId->getContentIdentifier() + "_" + OUString::number( ++nTry );
+ xId = new ::ucbhelper::ContentIdentifier( aNewId );
+ }
+ while ( hasData( xId ) && ( nTry < 1000 ) );
+
+ if ( nTry == 1000 )
+ {
+ ucbhelper::cancelCommandExecution(
+ uno::Any(
+ ucb::UnsupportedNameClashException(
+ "Unable to resolve name clash!",
+ getXWeak(),
+ nNameClashResolve ) ),
+ xEnv );
+ // Unreachable
+ }
+ else
+ {
+ OUString aNewTitle( m_aProps.getTitle() + "_" + OUString::number( nTry ) );
+ m_aProps.setTitle( aNewTitle );
+ }
+ }
+ break;
+
+ case ucb::NameClash::KEEP: // deprecated
+ case ucb::NameClash::ASK:
+ default:
+ if ( hasData( xId ) )
+ {
+ ucbhelper::cancelCommandExecution(
+ uno::Any(
+ ucb::UnsupportedNameClashException(
+ OUString(),
+ getXWeak(),
+ nNameClashResolve ) ),
+ xEnv );
+ // Unreachable
+ }
+ break;
+ }
+
+ // Identifier changed?
+ bool bNewId = ( xId->getContentIdentifier()
+ != m_xIdentifier->getContentIdentifier() );
+ m_xIdentifier = xId;
+
+ if ( !storeData() )
+ {
+ uno::Sequence<uno::Any> aArgs(comphelper::InitAnyPropertySequence(
+ {
+ {"Uri", uno::Any(m_xIdentifier->getContentIdentifier())}
+ }));
+ ucbhelper::cancelCommandExecution(
+ ucb::IOErrorCode_CANT_WRITE,
+ aArgs,
+ xEnv,
+ "Cannot store persistent data!",
+ this );
+ // Unreachable
+ }
+
+ m_eState = PERSISTENT;
+
+ if ( bNewId )
+ {
+ aGuard.clear();
+ inserted();
+ }
+}
+
+
+void HierarchyContent::destroy( bool bDeletePhysical,
+ const uno::Reference<
+ ucb::XCommandEnvironment > & xEnv )
+{
+ // @@@ take care about bDeletePhysical -> trashcan support
+
+ osl::ClearableGuard< osl::Mutex > aGuard( m_aMutex );
+
+ uno::Reference< ucb::XContent > xThis = this;
+
+ // Persistent?
+ if ( m_eState != PERSISTENT )
+ {
+ ucbhelper::cancelCommandExecution(
+ uno::Any( ucb::UnsupportedCommandException(
+ "Not persistent!",
+ getXWeak() ) ),
+ xEnv );
+ // Unreachable
+ }
+
+ // Am I the root folder?
+ if ( m_eKind == ROOT )
+ {
+ ucbhelper::cancelCommandExecution(
+ uno::Any( ucb::UnsupportedCommandException(
+ "Not supported by root folder!",
+ getXWeak() ) ),
+ xEnv );
+ // Unreachable
+ }
+
+ m_eState = DEAD;
+
+ aGuard.clear();
+ deleted();
+
+ if ( m_eKind == FOLDER )
+ {
+ // Process instantiated children...
+
+ HierarchyContentRefVector aChildren;
+ queryChildren( aChildren );
+
+ for ( auto & child : aChildren)
+ {
+ child->destroy( bDeletePhysical, xEnv );
+ }
+ }
+}
+
+
+void HierarchyContent::transfer(
+ const ucb::TransferInfo& rInfo,
+ const uno::Reference< ucb::XCommandEnvironment > & xEnv )
+{
+ osl::ClearableGuard< osl::Mutex > aGuard( m_aMutex );
+
+ // Persistent?
+ if ( m_eState != PERSISTENT )
+ {
+ ucbhelper::cancelCommandExecution(
+ uno::Any( ucb::UnsupportedCommandException(
+ "Not persistent!",
+ getXWeak() ) ),
+ xEnv );
+ // Unreachable
+ }
+
+ // Is source a hierarchy content?
+ if ( !rInfo.SourceURL.startsWith( HIERARCHY_URL_SCHEME ":/" ) )
+ {
+ ucbhelper::cancelCommandExecution(
+ uno::Any( ucb::InteractiveBadTransferURLException(
+ OUString(),
+ getXWeak() ) ),
+ xEnv );
+ // Unreachable
+ }
+
+ // Is source not a parent of me / not me?
+ OUString aId = m_xIdentifier->getContentIdentifier();
+ sal_Int32 nPos = aId.lastIndexOf( '/' );
+ if ( nPos != ( aId.getLength() - 1 ) )
+ {
+ // No trailing slash found. Append.
+ aId += "/";
+ }
+
+ if ( rInfo.SourceURL.getLength() <= aId.getLength() )
+ {
+ if ( aId.startsWith( rInfo.SourceURL ) )
+ {
+ uno::Sequence<uno::Any> aArgs(comphelper::InitAnyPropertySequence(
+ {
+ {"Uri", uno::Any(rInfo.SourceURL)}
+ }));
+ ucbhelper::cancelCommandExecution(
+ ucb::IOErrorCode_RECURSIVE,
+ aArgs,
+ xEnv,
+ "Target is equal to or is a child of source!",
+ this );
+ // Unreachable
+ }
+ }
+
+
+ // 0) Obtain content object for source.
+
+
+ uno::Reference< ucb::XContentIdentifier > xId
+ = new ::ucbhelper::ContentIdentifier( rInfo.SourceURL );
+
+ // Note: The static cast is okay here, because its sure that
+ // m_xProvider is always the HierarchyContentProvider.
+ rtl::Reference< HierarchyContent > xSource;
+
+ try
+ {
+ xSource = static_cast< HierarchyContent * >(
+ m_xProvider->queryContent( xId ).get() );
+ }
+ catch ( ucb::IllegalIdentifierException const & )
+ {
+ // queryContent
+ }
+
+ if ( !xSource.is() )
+ {
+ uno::Sequence<uno::Any> aArgs(comphelper::InitAnyPropertySequence(
+ {
+ {"Uri", uno::Any(xId->getContentIdentifier())}
+ }));
+ ucbhelper::cancelCommandExecution(
+ ucb::IOErrorCode_CANT_READ,
+ aArgs,
+ xEnv,
+ "Cannot instantiate source object!",
+ this );
+ // Unreachable
+ }
+
+
+ // 1) Create new child content.
+
+
+ OUString aType = xSource->isFolder()
+ ? HIERARCHY_FOLDER_CONTENT_TYPE
+ : HIERARCHY_LINK_CONTENT_TYPE;
+ ucb::ContentInfo aContentInfo;
+ aContentInfo.Type = aType;
+ aContentInfo.Attributes = 0;
+
+ // Note: The static cast is okay here, because its sure that
+ // createNewContent always creates a HierarchyContent.
+ rtl::Reference< HierarchyContent > xTarget
+ = static_cast< HierarchyContent * >(
+ createNewContent( aContentInfo ).get() );
+ if ( !xTarget.is() )
+ {
+ uno::Sequence<uno::Any> aArgs(comphelper::InitAnyPropertySequence(
+ {
+ {"Folder", uno::Any(aId)}
+ }));
+ ucbhelper::cancelCommandExecution(
+ ucb::IOErrorCode_CANT_CREATE,
+ aArgs,
+ xEnv,
+ "XContentCreator::createNewContent failed!",
+ this );
+ // Unreachable
+ }
+
+
+ // 2) Copy data from source content to child content.
+
+
+ uno::Sequence< beans::Property > aSourceProps
+ = xSource->getPropertySetInfo( xEnv )->getProperties();
+ sal_Int32 nCount = aSourceProps.getLength();
+
+ if ( nCount )
+ {
+ bool bHadTitle = rInfo.NewTitle.isEmpty();
+
+ // Get all source values.
+ uno::Reference< sdbc::XRow > xRow
+ = xSource->getPropertyValues( aSourceProps );
+
+ uno::Sequence< beans::PropertyValue > aValues( nCount );
+ beans::PropertyValue* pValues = aValues.getArray();
+
+ const beans::Property* pProps = aSourceProps.getConstArray();
+ for ( sal_Int32 n = 0; n < nCount; ++n )
+ {
+ const beans::Property& rProp = pProps[ n ];
+ beans::PropertyValue& rValue = pValues[ n ];
+
+ rValue.Name = rProp.Name;
+ rValue.Handle = rProp.Handle;
+
+ if ( !bHadTitle && rProp.Name == "Title" )
+ {
+ // Set new title instead of original.
+ bHadTitle = true;
+ rValue.Value <<= rInfo.NewTitle;
+ }
+ else
+ rValue.Value = xRow->getObject(
+ n + 1,
+ uno::Reference< container::XNameAccess >() );
+
+ rValue.State = beans::PropertyState_DIRECT_VALUE;
+
+ if ( rProp.Attributes & beans::PropertyAttribute::REMOVABLE )
+ {
+ // Add Additional Core Property.
+ try
+ {
+ xTarget->addProperty( rProp.Name,
+ rProp.Attributes,
+ rValue.Value );
+ }
+ catch ( beans::PropertyExistException const & )
+ {
+ }
+ catch ( beans::IllegalTypeException const & )
+ {
+ }
+ catch ( lang::IllegalArgumentException const & )
+ {
+ }
+ }
+ }
+
+ // Set target values.
+ xTarget->setPropertyValues( aValues, xEnv );
+ }
+
+
+ // 3) Commit (insert) child.
+
+
+ xTarget->insert( rInfo.NameClash, xEnv );
+
+
+ // 4) Transfer (copy) children of source.
+
+
+ if ( xSource->isFolder() )
+ {
+ HierarchyEntry aFolder(
+ m_xContext, m_pProvider, xId->getContentIdentifier() );
+ HierarchyEntry::iterator it;
+
+ while ( aFolder.next( it ) )
+ {
+ const HierarchyEntryData& rResult = *it;
+
+ OUString aChildId = xId->getContentIdentifier();
+ if ( ( aChildId.lastIndexOf( '/' ) + 1 ) != aChildId.getLength() )
+ aChildId += "/";
+
+ aChildId += rResult.getName();
+
+ ucb::TransferInfo aInfo;
+ aInfo.MoveData = false;
+ aInfo.NewTitle.clear();
+ aInfo.SourceURL = aChildId;
+ aInfo.NameClash = rInfo.NameClash;
+
+ // Transfer child to target.
+ xTarget->transfer( aInfo, xEnv );
+ }
+ }
+
+
+ // 5) Destroy source ( when moving only ) .
+
+
+ if ( !rInfo.MoveData )
+ return;
+
+ xSource->destroy( true, xEnv );
+
+ // Remove all persistent data of source and its children.
+ if ( !xSource->removeData() )
+ {
+ uno::Sequence<uno::Any> aArgs(comphelper::InitAnyPropertySequence(
+ {
+ {"Uri", uno::Any(xSource->m_xIdentifier->getContentIdentifier())}
+ }));
+ ucbhelper::cancelCommandExecution(
+ ucb::IOErrorCode_CANT_WRITE,
+ aArgs,
+ xEnv,
+ "Cannot remove persistent data of source object!",
+ this );
+ // Unreachable
+ }
+
+ // Remove own and all children's Additional Core Properties.
+ xSource->removeAdditionalPropertySet();
+}
+
+
+// HierarchyContentProperties Implementation.
+
+
+uno::Sequence< ucb::ContentInfo >
+HierarchyContentProperties::getCreatableContentsInfo() const
+{
+ if ( getIsFolder() )
+ {
+ uno::Sequence< ucb::ContentInfo > aSeq( 2 );
+
+ // Folder.
+ aSeq.getArray()[ 0 ].Type = HIERARCHY_FOLDER_CONTENT_TYPE;
+ aSeq.getArray()[ 0 ].Attributes = ucb::ContentInfoAttribute::KIND_FOLDER;
+
+ uno::Sequence< beans::Property > aFolderProps( 1 );
+ aFolderProps.getArray()[ 0 ] = beans::Property(
+ "Title",
+ -1,
+ cppu::UnoType<OUString>::get(),
+ beans::PropertyAttribute::BOUND );
+ aSeq.getArray()[ 0 ].Properties = aFolderProps;
+
+ // Link.
+ aSeq.getArray()[ 1 ].Type = HIERARCHY_LINK_CONTENT_TYPE;
+ aSeq.getArray()[ 1 ].Attributes = ucb::ContentInfoAttribute::KIND_LINK;
+
+ uno::Sequence< beans::Property > aLinkProps( 2 );
+ aLinkProps.getArray()[ 0 ] = beans::Property(
+ "Title",
+ -1,
+ cppu::UnoType<OUString>::get(),
+ beans::PropertyAttribute::BOUND );
+ aLinkProps.getArray()[ 1 ] = beans::Property(
+ "TargetURL",
+ -1,
+ cppu::UnoType<OUString>::get(),
+ beans::PropertyAttribute::BOUND );
+ aSeq.getArray()[ 1 ].Properties = aLinkProps;
+
+ return aSeq;
+ }
+ else
+ {
+ return uno::Sequence< ucb::ContentInfo >( 0 );
+ }
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/ucb/source/ucp/hierarchy/hierarchycontent.hxx b/ucb/source/ucp/hierarchy/hierarchycontent.hxx
new file mode 100644
index 0000000000..c7b0d773ce
--- /dev/null
+++ b/ucb/source/ucp/hierarchy/hierarchycontent.hxx
@@ -0,0 +1,255 @@
+/* -*- 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:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#pragma once
+
+#include <vector>
+#include <rtl/ref.hxx>
+#include <com/sun/star/ucb/XContentCreator.hpp>
+#include <ucbhelper/contenthelper.hxx>
+#include "hierarchydata.hxx"
+#include "hierarchyprovider.hxx"
+
+namespace com::sun::star::beans {
+ struct Property;
+ struct PropertyValue;
+}
+
+namespace com::sun::star::sdbc {
+ class XRow;
+}
+
+namespace com::sun::star::ucb {
+ struct TransferInfo;
+}
+
+namespace hierarchy_ucp
+{
+
+
+class HierarchyContentProperties
+{
+public:
+ HierarchyContentProperties() {};
+
+ explicit HierarchyContentProperties( const HierarchyEntryData::Type & rType )
+ : m_aData( rType ),
+ m_aContentType( rType == HierarchyEntryData::FOLDER
+ ? HIERARCHY_FOLDER_CONTENT_TYPE
+ : HIERARCHY_LINK_CONTENT_TYPE ) {}
+
+ explicit HierarchyContentProperties( const HierarchyEntryData & rData )
+ : m_aData( rData ),
+ m_aContentType( rData.getType() == HierarchyEntryData::FOLDER
+ ? HIERARCHY_FOLDER_CONTENT_TYPE
+ : HIERARCHY_LINK_CONTENT_TYPE ) {}
+
+ const OUString & getName() const { return m_aData.getName(); }
+ void setName( const OUString & rName ) { m_aData.setName( rName ); };
+
+ const OUString & getTitle() const { return m_aData.getTitle(); }
+ void setTitle( const OUString & rTitle )
+ { m_aData.setTitle( rTitle ); };
+
+ const OUString & getTargetURL() const
+ { return m_aData.getTargetURL(); }
+ void setTargetURL( const OUString & rURL )
+ { m_aData.setTargetURL( rURL ); };
+
+ const OUString & getContentType() const { return m_aContentType; }
+
+ bool getIsFolder() const
+ { return m_aData.getType() == HierarchyEntryData::FOLDER; }
+
+ bool getIsDocument() const { return !getIsFolder(); }
+
+ css::uno::Sequence< css::ucb::ContentInfo >
+ getCreatableContentsInfo() const;
+
+ const HierarchyEntryData & getHierarchyEntryData() const { return m_aData; }
+
+private:
+ HierarchyEntryData m_aData;
+ OUString m_aContentType;
+};
+
+
+class HierarchyContentProvider;
+
+class HierarchyContent : public ::ucbhelper::ContentImplHelper,
+ public css::ucb::XContentCreator
+{
+ enum ContentKind { LINK, FOLDER, ROOT };
+ enum ContentState { TRANSIENT, // created via CreateNewContent,
+ // but did not process "insert" yet
+ PERSISTENT, // processed "insert"
+ DEAD // processed "delete"
+ };
+
+ HierarchyContentProperties m_aProps;
+ ContentKind m_eKind;
+ ContentState m_eState;
+ HierarchyContentProvider* m_pProvider;
+ bool m_bCheckedReadOnly;
+ bool m_bIsReadOnly;
+
+private:
+ HierarchyContent(
+ const css::uno::Reference< css::uno::XComponentContext >& rxContext,
+ HierarchyContentProvider* pProvider,
+ const css::uno::Reference< css::ucb::XContentIdentifier >& Identifier,
+ HierarchyContentProperties aProps );
+ HierarchyContent(
+ const css::uno::Reference< css::uno::XComponentContext >& rxContext,
+ HierarchyContentProvider* pProvider,
+ const css::uno::Reference< css::ucb::XContentIdentifier >& Identifier,
+ const css::ucb::ContentInfo& Info );
+
+ 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;
+
+ static bool hasData(
+ const css::uno::Reference< css::uno::XComponentContext >& rxContext,
+ HierarchyContentProvider* pProvider,
+ const css::uno::Reference< css::ucb::XContentIdentifier >& Identifier );
+ bool hasData(
+ const css::uno::Reference< css::ucb::XContentIdentifier >& Identifier )
+ { return hasData( m_xContext, m_pProvider, Identifier ); }
+ static bool loadData(
+ const css::uno::Reference< css::uno::XComponentContext >& rxContext,
+ HierarchyContentProvider* pProvider,
+ const css::uno::Reference< css::ucb::XContentIdentifier >& Identifier,
+ HierarchyContentProperties& rProps );
+ bool storeData();
+ void renameData( const css::uno::Reference< css::ucb::XContentIdentifier >& xOldId,
+ const css::uno::Reference< css::ucb::XContentIdentifier >& xNewId );
+ bool removeData();
+
+ void setKind( const css::uno::Reference< css::ucb::XContentIdentifier >& Identifier );
+
+ bool isReadOnly();
+
+ bool isFolder() const { return ( m_eKind > LINK ); }
+
+ css::uno::Reference< css::ucb::XContentIdentifier >
+ makeNewIdentifier( const OUString& rTitle );
+
+ typedef rtl::Reference< HierarchyContent > HierarchyContentRef;
+ typedef std::vector< HierarchyContentRef > HierarchyContentRefVector;
+ void queryChildren( HierarchyContentRefVector& rChildren );
+
+ bool exchangeIdentity(
+ const css::uno::Reference< css::ucb::XContentIdentifier >& xNewId );
+
+ css::uno::Reference< css::sdbc::XRow >
+ getPropertyValues( const css::uno::Sequence< css::beans::Property >& rProperties );
+ /// @throws css::uno::Exception
+ 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
+ void insert( sal_Int32 nNameClashResolve,
+ const css::uno::Reference< css::ucb::XCommandEnvironment > & xEnv );
+
+ /// @throws css::uno::Exception
+ void destroy( bool bDeletePhysical,
+ const css::uno::Reference< css::ucb::XCommandEnvironment > & xEnv );
+
+ /// @throws css::uno::Exception
+ void transfer( const css::ucb::TransferInfo& rInfo,
+ const css::uno::Reference< css::ucb::XCommandEnvironment > & xEnv );
+
+public:
+ // Create existing content. Fail, if not already exists.
+ static rtl::Reference<HierarchyContent> create(
+ const css::uno::Reference< css::uno::XComponentContext >& rxContext,
+ HierarchyContentProvider* pProvider,
+ const css::uno::Reference<
+ css::ucb::XContentIdentifier >& Identifier );
+
+ // Create new content. Fail, if already exists.
+ static rtl::Reference<HierarchyContent> create(
+ const css::uno::Reference< css::uno::XComponentContext >& rxContext,
+ HierarchyContentProvider* pProvider,
+ const css::uno::Reference< css::ucb::XContentIdentifier >& Identifier,
+ const css::ucb::ContentInfo& Info );
+
+ virtual ~HierarchyContent() 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 css::uno::Sequence< OUString > SAL_CALL
+ getSupportedServiceNames() override;
+
+ // XContent
+ virtual OUString SAL_CALL
+ getContentType() override;
+ virtual css::uno::Reference< css::ucb::XContentIdentifier > SAL_CALL
+ getIdentifier() override;
+
+ // XCommandProcessor
+ 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;
+
+
+ // Additional interfaces
+
+
+ // XContentCreator
+ 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;
+
+
+ // Non-interface methods.
+
+
+ static css::uno::Reference< css::sdbc::XRow >
+ getPropertyValues( const css::uno::Reference< css::uno::XComponentContext >& rxContext,
+ const css::uno::Sequence< css::beans::Property >& rProperties,
+ const HierarchyContentProperties& rData,
+ HierarchyContentProvider* pProvider,
+ const OUString& rContentId );
+};
+
+} // namespace hierarchy_ucp
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/ucb/source/ucp/hierarchy/hierarchycontentcaps.cxx b/ucb/source/ucp/hierarchy/hierarchycontentcaps.cxx
new file mode 100644
index 0000000000..836a136f62
--- /dev/null
+++ b/ucb/source/ucp/hierarchy/hierarchycontentcaps.cxx
@@ -0,0 +1,681 @@
+/* -*- 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:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+
+/**************************************************************************
+ TODO
+ **************************************************************************
+
+ **************************************************************************
+
+ Props/Commands:
+
+ root folder folder link link
+ (new) (new)
+ ----------------------------------------------------------------
+ ContentType x x x x x
+ IsDocument x x x x x
+ IsFolder x x x x x
+ Title x x x x x
+ TargetURL x x
+ CreatableContentsInfo x x x x x
+
+ getCommandInfo x x x x x
+ getPropertySetInfo x x x x x
+ getPropertyValues x x x x x
+ setPropertyValues x x x x x
+ createNewContent x x
+ insert x x
+ delete x x
+ open x x
+ transfer x x
+
+ *************************************************************************/
+
+#include <com/sun/star/beans/Property.hpp>
+#include <com/sun/star/beans/PropertyAttribute.hpp>
+#include <com/sun/star/beans/PropertyValue.hpp>
+#include <com/sun/star/ucb/CommandInfo.hpp>
+#include <com/sun/star/ucb/OpenCommandArgument2.hpp>
+#include <com/sun/star/ucb/TransferInfo.hpp>
+#include <com/sun/star/uno/Sequence.hxx>
+#include <sal/macros.h>
+#include "hierarchycontent.hxx"
+
+using namespace com::sun::star;
+using namespace hierarchy_ucp;
+
+
+// HierarchyContent implementation.
+
+
+#define MAKEPROPSEQUENCE( a ) \
+ uno::Sequence< beans::Property >( a, SAL_N_ELEMENTS( a ) )
+
+#define MAKECMDSEQUENCE( a ) \
+ uno::Sequence< ucb::CommandInfo >( a, SAL_N_ELEMENTS( a ) )
+
+
+// IMPORTANT: If any property data ( name / type / ... ) are changed, then
+// HierarchyContent::getPropertyValues(...) must be adapted too!
+
+
+// virtual
+uno::Sequence< beans::Property > HierarchyContent::getProperties(
+ const uno::Reference< ucb::XCommandEnvironment > & /*xEnv*/ )
+{
+ osl::Guard< osl::Mutex > aGuard( m_aMutex );
+
+ if ( m_eKind == LINK )
+ {
+
+
+ // Link: Supported properties
+
+
+ if ( isReadOnly() )
+ {
+ static const beans::Property aLinkPropertyInfoTable[] =
+ {
+
+ // Required properties
+
+ beans::Property(
+ "ContentType",
+ -1,
+ cppu::UnoType<OUString>::get(),
+ beans::PropertyAttribute::BOUND
+ | beans::PropertyAttribute::READONLY
+ ),
+ 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::PropertyAttribute::READONLY
+ ),
+
+ // Optional standard properties
+
+ beans::Property(
+ "TargetURL",
+ -1,
+ cppu::UnoType<OUString>::get(),
+ beans::PropertyAttribute::BOUND
+ | beans::PropertyAttribute::READONLY
+ ),
+ beans::Property(
+ "CreatableContentsInfo",
+ -1,
+ cppu::UnoType<uno::Sequence< ucb::ContentInfo >>::get(),
+ beans::PropertyAttribute::BOUND
+ | beans::PropertyAttribute::READONLY )
+
+ // New properties
+
+ };
+ return MAKEPROPSEQUENCE( aLinkPropertyInfoTable );
+ }
+ else
+ {
+ static const beans::Property aLinkPropertyInfoTable[] =
+ {
+
+ // Required properties
+
+ beans::Property(
+ "ContentType",
+ -1,
+ cppu::UnoType<OUString>::get(),
+ beans::PropertyAttribute::BOUND
+ | beans::PropertyAttribute::READONLY
+ ),
+ 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
+ ),
+
+ // Optional standard properties
+
+ beans::Property(
+ "TargetURL",
+ -1,
+ cppu::UnoType<OUString>::get(),
+ beans::PropertyAttribute::BOUND
+ ),
+ beans::Property(
+ "CreatableContentsInfo",
+ -1,
+ cppu::UnoType<uno::Sequence< ucb::ContentInfo >>::get(),
+ beans::PropertyAttribute::BOUND
+ | beans::PropertyAttribute::READONLY )
+
+ // New properties
+
+ };
+ return MAKEPROPSEQUENCE( aLinkPropertyInfoTable );
+ }
+ }
+ else if ( m_eKind == FOLDER )
+ {
+
+
+ // Folder: Supported properties
+
+
+ if ( isReadOnly() )
+ {
+ static const beans::Property aFolderPropertyInfoTable[] =
+ {
+
+ // Required properties
+
+ beans::Property(
+ "ContentType",
+ -1,
+ cppu::UnoType<OUString>::get(),
+ beans::PropertyAttribute::BOUND
+ | beans::PropertyAttribute::READONLY
+ ),
+ 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::PropertyAttribute::READONLY
+ ),
+
+ // Optional standard properties
+
+ beans::Property(
+ "CreatableContentsInfo",
+ -1,
+ cppu::UnoType<uno::Sequence< ucb::ContentInfo >>::get(),
+ beans::PropertyAttribute::BOUND
+ | beans::PropertyAttribute::READONLY )
+
+ // New properties
+
+ };
+ return MAKEPROPSEQUENCE( aFolderPropertyInfoTable );
+ }
+ else
+ {
+ static const beans::Property aFolderPropertyInfoTable[] =
+ {
+
+ // Required properties
+
+ beans::Property(
+ "ContentType",
+ -1,
+ cppu::UnoType<OUString>::get(),
+ beans::PropertyAttribute::BOUND
+ | beans::PropertyAttribute::READONLY
+ ),
+ 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
+ ),
+
+ // Optional standard properties
+
+ beans::Property(
+ "CreatableContentsInfo",
+ -1,
+ cppu::UnoType<uno::Sequence< ucb::ContentInfo >>::get(),
+ beans::PropertyAttribute::BOUND
+ | beans::PropertyAttribute::READONLY )
+
+ // New properties
+
+ };
+ return MAKEPROPSEQUENCE( aFolderPropertyInfoTable );
+ }
+ }
+ else
+ {
+
+
+ // Root Folder: Supported properties
+
+
+ // Currently no difference between read-only/read-write
+ // -> all props are read-only
+
+ static const beans::Property aRootFolderPropertyInfoTable[] =
+ {
+
+ // Required properties
+
+ beans::Property(
+ "ContentType",
+ -1,
+ cppu::UnoType<OUString>::get(),
+ beans::PropertyAttribute::BOUND
+ | beans::PropertyAttribute::READONLY
+ ),
+ 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::PropertyAttribute::READONLY
+ ),
+
+ // Optional standard properties
+
+ beans::Property(
+ "CreatableContentsInfo",
+ -1,
+ cppu::UnoType<uno::Sequence< ucb::ContentInfo >>::get(),
+ beans::PropertyAttribute::BOUND
+ | beans::PropertyAttribute::READONLY )
+
+ // New properties
+
+ };
+ return MAKEPROPSEQUENCE( aRootFolderPropertyInfoTable );
+ }
+}
+
+
+// virtual
+uno::Sequence< ucb::CommandInfo > HierarchyContent::getCommands(
+ const uno::Reference< ucb::XCommandEnvironment > & /*xEnv*/ )
+{
+ osl::Guard< osl::Mutex > aGuard( m_aMutex );
+
+ if ( m_eKind == LINK )
+ {
+
+
+ // Link: Supported commands
+
+
+ if ( isReadOnly() )
+ {
+ static const ucb::CommandInfo aLinkCommandInfoTable[] =
+ {
+
+ // 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
+
+
+ // New commands
+
+ };
+ return MAKECMDSEQUENCE( aLinkCommandInfoTable );
+ }
+ else
+ {
+ static const ucb::CommandInfo aLinkCommandInfoTable[] =
+ {
+
+ // 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<void>::get()
+ )
+
+ // New commands
+
+ };
+ return MAKECMDSEQUENCE( aLinkCommandInfoTable );
+ }
+ }
+ else if ( m_eKind == FOLDER )
+ {
+
+
+ // Folder: Supported commands
+
+
+ if ( isReadOnly() )
+ {
+ static const ucb::CommandInfo aFolderCommandInfoTable[] =
+ {
+
+ // 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()
+ )
+
+ // New commands
+
+ };
+ return MAKECMDSEQUENCE( aFolderCommandInfoTable );
+ }
+ else
+ {
+ static const ucb::CommandInfo aFolderCommandInfoTable[] =
+ {
+
+ // 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<void>::get()
+ ),
+ ucb::CommandInfo(
+ "open",
+ -1,
+ cppu::UnoType<ucb::OpenCommandArgument2>::get()
+ ),
+ ucb::CommandInfo(
+ "transfer",
+ -1,
+ cppu::UnoType<ucb::TransferInfo>::get()
+ ),
+ ucb::CommandInfo(
+ "createNewContent",
+ -1,
+ cppu::UnoType<ucb::ContentInfo>::get()
+ )
+
+ // New commands
+
+ };
+ return MAKECMDSEQUENCE( aFolderCommandInfoTable );
+ }
+ }
+ else
+ {
+
+
+ // Root Folder: Supported commands
+
+
+ if ( isReadOnly() )
+ {
+ static const ucb::CommandInfo aRootFolderCommandInfoTable[] =
+ {
+
+ // 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()
+ )
+
+ // New commands
+
+ };
+ return MAKECMDSEQUENCE( aRootFolderCommandInfoTable );
+ }
+ else
+ {
+ static const ucb::CommandInfo aRootFolderCommandInfoTable[] =
+ {
+
+ // 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()
+ ),
+ ucb::CommandInfo(
+ "transfer",
+ -1,
+ cppu::UnoType<ucb::TransferInfo>::get()
+ ),
+ ucb::CommandInfo(
+ "createNewContent",
+ -1,
+ cppu::UnoType<ucb::ContentInfo>::get()
+ )
+
+ // New commands
+
+ };
+ return MAKECMDSEQUENCE( aRootFolderCommandInfoTable );
+ }
+ }
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/ucb/source/ucp/hierarchy/hierarchydata.cxx b/ucb/source/ucp/hierarchy/hierarchydata.cxx
new file mode 100644
index 0000000000..6b7e0d857e
--- /dev/null
+++ b/ucb/source/ucp/hierarchy/hierarchydata.cxx
@@ -0,0 +1,1097 @@
+/* -*- 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:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+
+/**************************************************************************
+ TODO
+ **************************************************************************
+
+ - HierarchyEntry::move
+ --> Rewrite to use XNamed ( once this is supported by config db api ).
+
+ *************************************************************************/
+#include "hierarchydata.hxx"
+
+#include <comphelper/diagnose_ex.hxx>
+#include <rtl/ustrbuf.hxx>
+#include <com/sun/star/container/XHierarchicalNameAccess.hpp>
+#include <com/sun/star/container/XNameContainer.hpp>
+#include <com/sun/star/container/XNameReplace.hpp>
+#include <com/sun/star/util/XChangesBatch.hpp>
+#include <com/sun/star/util/XOfficeInstallationDirectories.hpp>
+#include <com/sun/star/lang/XSingleServiceFactory.hpp>
+#include <comphelper/propertysequence.hxx>
+#include <utility>
+#include "hierarchyprovider.hxx"
+#include "hierarchyuri.hxx"
+
+using namespace com::sun::star;
+
+namespace hierarchy_ucp
+{
+
+
+static void makeXMLName( std::u16string_view rIn, OUStringBuffer & rBuffer )
+{
+ size_t nCount = rIn.size();
+ for ( size_t n = 0; n < nCount; ++n )
+ {
+ const sal_Unicode c = rIn[ n ];
+ switch ( c )
+ {
+ case '&':
+ rBuffer.append( "&amp;" );
+ break;
+
+ case '"':
+ rBuffer.append( "&quot;" );
+ break;
+
+ case '\'':
+ rBuffer.append( "&apos;" );
+ break;
+
+ case '<':
+ rBuffer.append( "&lt;" );
+ break;
+
+ case '>':
+ rBuffer.append( "&gt;" );
+ break;
+
+ default:
+ rBuffer.append( c );
+ break;
+ }
+ }
+}
+
+
+// HierarchyEntry Implementation.
+
+
+constexpr OUStringLiteral READ_SERVICE_NAME = u"com.sun.star.ucb.HierarchyDataReadAccess";
+constexpr OUString READWRITE_SERVICE_NAME = u"com.sun.star.ucb.HierarchyDataReadWriteAccess"_ustr;
+
+// describe path of cfg entry
+constexpr OUString CFGPROPERTY_NODEPATH = u"nodepath"_ustr;
+
+
+HierarchyEntry::HierarchyEntry(
+ uno::Reference< uno::XComponentContext > xContext,
+ HierarchyContentProvider* pProvider,
+ const OUString& rURL )
+: m_xContext(std::move( xContext )),
+ m_xOfficeInstDirs( pProvider->getOfficeInstallationDirectories() ),
+ m_bTriedToGetRootReadAccess( false )
+{
+ HierarchyUri aUri( rURL );
+ m_aServiceSpecifier = aUri.getService();
+
+ m_xConfigProvider
+ = pProvider->getConfigProvider( m_aServiceSpecifier );
+ m_xRootReadAccess
+ = pProvider->getRootConfigReadNameAccess( m_aServiceSpecifier );
+
+ // Note: do not init m_aPath in init list. createPathFromHierarchyURL
+ // needs m_xContext and m_aMutex.
+ m_aPath = createPathFromHierarchyURL( aUri );
+
+ // Extract language independent name from URL.
+ sal_Int32 nPos = rURL.lastIndexOf( '/' );
+ if ( nPos > HIERARCHY_URL_SCHEME_LENGTH )
+ m_aName = rURL.copy( nPos + 1 );
+ else
+ OSL_FAIL( "HierarchyEntry - Invalid URL!" );
+}
+
+
+bool HierarchyEntry::hasData()
+{
+ uno::Reference< container::XHierarchicalNameAccess > xRootReadAccess
+ = getRootReadAccess();
+
+ OSL_ENSURE( xRootReadAccess.is(), "HierarchyEntry::hasData - No root!" );
+
+ if ( xRootReadAccess.is() )
+ return xRootReadAccess->hasByHierarchicalName( m_aPath );
+
+ return false;
+}
+
+
+bool HierarchyEntry::getData( HierarchyEntryData& rData )
+{
+ try
+ {
+ uno::Reference< container::XHierarchicalNameAccess > xRootReadAccess
+ = getRootReadAccess();
+
+ OSL_ENSURE( xRootReadAccess.is(),
+ "HierarchyEntry::getData - No root!" );
+
+ if ( xRootReadAccess.is() )
+ {
+ OUString aTitlePath = m_aPath + "/Title";
+
+ // Note: Avoid NoSuchElementExceptions, because exceptions are
+ // relatively 'expensive'. Checking for availability of
+ // title value is sufficient here, because if it is
+ // there, the other values will be available too.
+ if ( !xRootReadAccess->hasByHierarchicalName( aTitlePath ) )
+ return false;
+
+ OUString aValue;
+
+ // Get Title value.
+ if ( !( xRootReadAccess->getByHierarchicalName( aTitlePath )
+ >>= aValue ) )
+ {
+ OSL_FAIL( "HierarchyEntry::getData - "
+ "Got no Title value!" );
+ return false;
+ }
+
+ rData.setTitle( aValue );
+
+ // Get TargetURL value.
+ OUString aTargetURLPath = m_aPath + "/TargetURL";
+ if ( !( xRootReadAccess->getByHierarchicalName( aTargetURLPath )
+ >>= aValue ) )
+ {
+ OSL_FAIL( "HierarchyEntry::getData - "
+ "Got no TargetURL value!" );
+ return false;
+ }
+
+ // TargetURL property may contain a reference to the Office
+ // installation directory. To ensure a reloctable office
+ // installation, the path to the office installation directory must
+ // never be stored directly. A placeholder is used instead. Replace
+ // it by actual installation directory.
+ if ( m_xOfficeInstDirs.is() && !aValue.isEmpty() )
+ aValue = m_xOfficeInstDirs->makeAbsoluteURL( aValue );
+ rData.setTargetURL( aValue );
+
+ OUString aTypePath = m_aPath + "/Type";
+ if ( xRootReadAccess->hasByHierarchicalName( aTypePath ) )
+ {
+ // Might not be present since it was introduced long after
+ // Title and TargetURL (#82433#)... So not getting it is
+ // not an error.
+
+ // Get Type value.
+ sal_Int32 nType = 0;
+ if ( xRootReadAccess->getByHierarchicalName( aTypePath )
+ >>= nType )
+ {
+ if ( nType == 0 )
+ {
+ rData.setType( HierarchyEntryData::LINK );
+ }
+ else if ( nType == 1 )
+ {
+ rData.setType( HierarchyEntryData::FOLDER );
+ }
+ else
+ {
+ OSL_FAIL( "HierarchyEntry::getData - "
+ "Unknown Type value!" );
+ return false;
+ }
+ }
+ }
+
+ rData.setName( m_aName );
+ return true;
+ }
+ }
+ catch ( uno::RuntimeException const & )
+ {
+ throw;
+ }
+ catch ( container::NoSuchElementException const & )
+ {
+ // getByHierarchicalName
+
+ TOOLS_WARN_EXCEPTION("ucb.ucp", "");
+ }
+ return false;
+}
+
+
+bool HierarchyEntry::setData( const HierarchyEntryData& rData )
+{
+ try
+ {
+ std::unique_lock aGuard( m_aMutex );
+
+ if ( !m_xConfigProvider.is() )
+ m_xConfigProvider.set(
+ m_xContext->getServiceManager()->createInstanceWithContext(m_aServiceSpecifier, m_xContext),
+ uno::UNO_QUERY );
+
+ if ( m_xConfigProvider.is() )
+ {
+ // Create parent's key. It must exist!
+
+ OUString aParentPath;
+ bool bRoot = true;
+
+ sal_Int32 nPos = m_aPath.lastIndexOf( '/' );
+ if ( nPos != -1 )
+ {
+ // Skip "/Children" segment of the path, too.
+ nPos = m_aPath.lastIndexOf( '/', nPos - 1 );
+
+ OSL_ENSURE( nPos != -1,
+ "HierarchyEntry::setData - Wrong path!" );
+
+ aParentPath += m_aPath.subView( 0, nPos );
+ bRoot = false;
+ }
+
+ uno::Sequence<uno::Any> aArguments(comphelper::InitAnyPropertySequence(
+ {
+ {CFGPROPERTY_NODEPATH, uno::Any(aParentPath)}
+ }));
+
+ uno::Reference< util::XChangesBatch > xBatch(
+ m_xConfigProvider->createInstanceWithArguments(
+ READWRITE_SERVICE_NAME,
+ aArguments ),
+ uno::UNO_QUERY );
+
+ OSL_ENSURE( xBatch.is(),
+ "HierarchyEntry::setData - No batch!" );
+
+ uno::Reference< container::XNameAccess > xParentNameAccess(
+ xBatch, uno::UNO_QUERY );
+
+ OSL_ENSURE( xParentNameAccess.is(),
+ "HierarchyEntry::setData - No name access!" );
+
+ if ( xBatch.is() && xParentNameAccess.is() )
+ {
+ // Try to create own key. It must not exist!
+
+ bool bExists = true;
+ uno::Any aMyKey;
+
+ try
+ {
+ uno::Reference< container::XNameAccess > xNameAccess;
+
+ if ( bRoot )
+ {
+ xNameAccess = xParentNameAccess;
+ }
+ else
+ {
+ xParentNameAccess->getByName("Children") >>= xNameAccess;
+ }
+
+ if ( xNameAccess->hasByName( m_aName ) )
+ aMyKey = xNameAccess->getByName( m_aName );
+ else
+ bExists = false;
+ }
+ catch ( container::NoSuchElementException const & )
+ {
+ bExists = false;
+ }
+
+ uno::Reference< container::XNameReplace > xNameReplace;
+ uno::Reference< container::XNameContainer > xContainer;
+
+ if ( bExists )
+ {
+ // Key exists. Replace values.
+
+ aMyKey >>= xNameReplace;
+
+ OSL_ENSURE( xNameReplace.is(),
+ "HierarchyEntry::setData - No name replace!" );
+ }
+ else
+ {
+ // Key does not exist. Create / fill / insert it.
+
+ uno::Reference< lang::XSingleServiceFactory > xFac;
+
+ if ( bRoot )
+ {
+ // Special handling for children of root,
+ // which is not an entry. It's only a set
+ // of entries.
+ xFac.set( xParentNameAccess, uno::UNO_QUERY );
+ }
+ else
+ {
+ // Append new entry to parents child list,
+ // which is a set of entries.
+ xParentNameAccess->getByName("Children") >>= xFac;
+ }
+
+ OSL_ENSURE( xFac.is(),
+ "HierarchyEntry::setData - No factory!" );
+
+ if ( xFac.is() )
+ {
+ xNameReplace.set( xFac->createInstance(), uno::UNO_QUERY );
+
+ OSL_ENSURE( xNameReplace.is(),
+ "HierarchyEntry::setData - No name replace!" );
+
+ if ( xNameReplace.is() )
+ {
+ xContainer.set( xFac, uno::UNO_QUERY );
+
+ OSL_ENSURE( xContainer.is(),
+ "HierarchyEntry::setData - No container!" );
+ }
+ }
+ }
+
+ if ( xNameReplace.is() )
+ {
+ // Set Title value.
+ xNameReplace->replaceByName(
+ "Title",
+ uno::Any( rData.getTitle() ) );
+
+ // Set TargetURL value.
+
+ // TargetURL property may contain a reference to the Office
+ // installation directory. To ensure a reloctable office
+ // installation, the path to the office installation
+ // directory must never be stored directly. Use a
+ // placeholder instead.
+ OUString aValue( rData.getTargetURL() );
+ if ( m_xOfficeInstDirs.is() && !aValue.isEmpty() )
+ aValue
+ = m_xOfficeInstDirs->makeRelocatableURL( aValue );
+
+ xNameReplace->replaceByName(
+ "TargetURL",
+ uno::Any( aValue ) );
+
+ // Set Type value.
+ sal_Int32 nType
+ = rData.getType() == HierarchyEntryData::LINK ? 0 : 1;
+ xNameReplace->replaceByName(
+ "Type",
+ uno::Any( nType ) );
+
+ if ( xContainer.is() )
+ xContainer->insertByName(
+ m_aName, uno::Any( xNameReplace ) );
+
+ // Commit changes.
+ xBatch->commitChanges();
+ return true;
+ }
+ }
+ }
+ }
+ catch ( lang::IllegalArgumentException const & )
+ {
+ // replaceByName, insertByName
+
+ TOOLS_WARN_EXCEPTION("ucb.ucp", "");
+ }
+ catch ( uno::RuntimeException const & )
+ {
+ throw;
+ }
+ catch ( container::NoSuchElementException const & )
+ {
+ // replaceByName, getByName
+
+ TOOLS_WARN_EXCEPTION("ucb.ucp", "");
+ }
+ catch ( container::ElementExistException const & )
+ {
+ // insertByName
+
+ TOOLS_WARN_EXCEPTION("ucb.ucp", "");
+ }
+ catch ( lang::WrappedTargetException const & )
+ {
+ // replaceByName, insertByName, getByName, commitChanges
+
+ TOOLS_WARN_EXCEPTION("ucb.ucp", "");
+ }
+ catch ( uno::Exception const & )
+ {
+ // createInstance, createInstanceWithArguments
+
+ TOOLS_WARN_EXCEPTION("ucb.ucp", "");
+ }
+
+ return false;
+}
+
+
+bool HierarchyEntry::move(
+ const OUString& rNewURL, const HierarchyEntryData& rData )
+{
+ OUString aNewPath = createPathFromHierarchyURL( HierarchyUri(rNewURL) );
+
+ std::unique_lock aGuard( m_aMutex );
+
+ if ( aNewPath == m_aPath )
+ return true;
+
+ bool bOldRoot = true;
+ uno::Reference< util::XChangesBatch > xOldParentBatch;
+
+ OUString aNewKey;
+ sal_Int32 nURLPos = rNewURL.lastIndexOf( '/' );
+ if ( nURLPos > HIERARCHY_URL_SCHEME_LENGTH )
+ aNewKey = rNewURL.copy( nURLPos + 1 );
+ else
+ {
+ OSL_FAIL( "HierarchyEntry::move - Invalid URL!" );
+ return false;
+ }
+
+ bool bNewRoot = true;
+ uno::Reference< util::XChangesBatch > xNewParentBatch;
+
+ bool bDifferentParents = true;
+
+ try
+ {
+ if ( !m_xConfigProvider.is() )
+ m_xConfigProvider.set(
+ m_xContext->getServiceManager()->createInstanceWithContext(m_aServiceSpecifier, m_xContext),
+ uno::UNO_QUERY );
+
+ if ( !m_xConfigProvider.is() )
+ return false;
+
+ OUString aOldParentPath;
+ sal_Int32 nPos = m_aPath.lastIndexOf( '/' );
+ if ( nPos != -1 )
+ {
+ // Skip "/Children" segment of the path, too.
+ nPos = m_aPath.lastIndexOf( '/', nPos - 1 );
+
+ OSL_ENSURE( nPos != -1, "HierarchyEntry::move - Wrong path!" );
+
+ aOldParentPath += m_aPath.subView( 0, nPos );
+ bOldRoot = false;
+ }
+
+ OUString aNewParentPath;
+ nPos = aNewPath.lastIndexOf( '/' );
+ if ( nPos != -1 )
+ {
+ // Skip "/Children" segment of the path, too.
+ nPos = aNewPath.lastIndexOf( '/', nPos - 1 );
+
+ OSL_ENSURE( nPos != -1, "HierarchyEntry::move - Wrong path!" );
+
+ aNewParentPath += aNewPath.subView( 0, nPos );
+ bNewRoot = false;
+ }
+
+ uno::Sequence<uno::Any> aArguments(comphelper::InitAnyPropertySequence(
+ {
+ {CFGPROPERTY_NODEPATH, uno::Any(aOldParentPath)}
+ }));
+
+ xOldParentBatch.set(
+ m_xConfigProvider->createInstanceWithArguments(
+ READWRITE_SERVICE_NAME,
+ aArguments ),
+ uno::UNO_QUERY );
+
+ OSL_ENSURE( xOldParentBatch.is(), "HierarchyEntry::move - No batch!" );
+
+ if ( !xOldParentBatch.is() )
+ return false;
+
+ if ( aOldParentPath == aNewParentPath )
+ {
+ bDifferentParents = false;
+ xNewParentBatch = xOldParentBatch;
+ }
+ else
+ {
+ bDifferentParents = true;
+
+ uno::Sequence<uno::Any> aArguments2(comphelper::InitAnyPropertySequence(
+ {
+ {CFGPROPERTY_NODEPATH, uno::Any(aNewParentPath)}
+ }));
+
+ xNewParentBatch.set(
+ m_xConfigProvider->createInstanceWithArguments(
+ READWRITE_SERVICE_NAME,
+ aArguments2 ),
+ uno::UNO_QUERY );
+
+ OSL_ENSURE(
+ xNewParentBatch.is(), "HierarchyEntry::move - No batch!" );
+
+ if ( !xNewParentBatch.is() )
+ return false;
+ }
+ }
+ catch ( uno::RuntimeException const & )
+ {
+ throw;
+ }
+ catch ( uno::Exception const & )
+ {
+ // createInstance, createInstanceWithArguments
+
+ TOOLS_WARN_EXCEPTION("ucb.ucp", "");
+ return false;
+ }
+
+
+ // (1) Get entry...
+
+
+ uno::Any aEntry;
+ uno::Reference< container::XNameAccess > xOldParentNameAccess;
+ uno::Reference< container::XNameContainer > xOldNameContainer;
+
+ try
+ {
+ xOldParentNameAccess.set( xOldParentBatch, uno::UNO_QUERY );
+
+ OSL_ENSURE( xOldParentNameAccess.is(),
+ "HierarchyEntry::move - No name access!" );
+
+ if ( !xOldParentNameAccess.is() )
+ return false;
+
+ if ( bOldRoot )
+ {
+ xOldNameContainer.set( xOldParentNameAccess, uno::UNO_QUERY );
+ }
+ else
+ {
+ xOldParentNameAccess->getByName("Children") >>= xOldNameContainer;
+ }
+
+ aEntry = xOldNameContainer->getByName( m_aName );
+ }
+ catch ( container::NoSuchElementException const & )
+ {
+ // getByName
+
+ TOOLS_WARN_EXCEPTION("ucb.ucp", "");
+ return false;
+ }
+ catch ( lang::WrappedTargetException const & )
+ {
+ // getByName
+
+ TOOLS_WARN_EXCEPTION("ucb.ucp", "");
+ return false;
+ }
+
+
+ // (2) Remove entry... Note: Insert BEFORE remove does not work!
+
+
+ try
+ {
+ xOldNameContainer->removeByName( m_aName );
+ xOldParentBatch->commitChanges();
+ }
+ catch ( container::NoSuchElementException const & )
+ {
+ // getByName, removeByName
+
+ TOOLS_WARN_EXCEPTION("ucb.ucp", "");
+ return false;
+ }
+
+
+ // (3) Insert entry at new parent...
+
+
+ try
+ {
+ uno::Reference< container::XNameReplace > xNewNameReplace;
+ aEntry >>= xNewNameReplace;
+
+ OSL_ENSURE( xNewNameReplace.is(),
+ "HierarchyEntry::move - No name replace!" );
+
+ if ( !xNewNameReplace.is() )
+ return false;
+
+ uno::Reference< container::XNameAccess > xNewParentNameAccess;
+ if ( bDifferentParents )
+ xNewParentNameAccess.set( xNewParentBatch, uno::UNO_QUERY );
+ else
+ xNewParentNameAccess = xOldParentNameAccess;
+
+ OSL_ENSURE( xNewParentNameAccess.is(),
+ "HierarchyEntry::move - No name access!" );
+
+ if ( !xNewParentNameAccess.is() )
+ return false;
+
+ uno::Reference< container::XNameContainer > xNewNameContainer;
+ if ( bDifferentParents )
+ {
+ if ( bNewRoot )
+ {
+ xNewNameContainer.set( xNewParentNameAccess, uno::UNO_QUERY );
+ }
+ else
+ {
+ xNewParentNameAccess->getByName("Children") >>= xNewNameContainer;
+ }
+ }
+ else
+ xNewNameContainer = xOldNameContainer;
+
+ if ( !xNewNameContainer.is() )
+ return false;
+
+ xNewNameReplace->replaceByName(
+ "Title",
+ uno::Any( rData.getTitle() ) );
+
+ // TargetURL property may contain a reference to the Office
+ // installation directory. To ensure a reloctable office
+ // installation, the path to the office installation
+ // directory must never be stored directly. Use a placeholder
+ // instead.
+ OUString aValue( rData.getTargetURL() );
+ if ( m_xOfficeInstDirs.is() && !aValue.isEmpty() )
+ aValue = m_xOfficeInstDirs->makeRelocatableURL( aValue );
+ xNewNameReplace->replaceByName(
+ "TargetURL",
+ uno::Any( aValue ) );
+ sal_Int32 nType = rData.getType() == HierarchyEntryData::LINK ? 0 : 1;
+ xNewNameReplace->replaceByName(
+ "Type",
+ uno::Any( nType ) );
+
+ xNewNameContainer->insertByName( aNewKey, aEntry );
+ xNewParentBatch->commitChanges();
+ }
+ catch ( container::NoSuchElementException const & )
+ {
+ // replaceByName, insertByName, getByName
+
+ TOOLS_WARN_EXCEPTION("ucb.ucp", "");
+ return false;
+ }
+ catch ( lang::IllegalArgumentException const & )
+ {
+ // replaceByName, insertByName
+
+ TOOLS_WARN_EXCEPTION("ucb.ucp", "");
+ return false;
+ }
+ catch ( container::ElementExistException const & )
+ {
+ // insertByName
+
+ TOOLS_WARN_EXCEPTION("ucb.ucp", "");
+ return false;
+ }
+ catch ( lang::WrappedTargetException const & )
+ {
+ // replaceByName, insertByName, getByName
+
+ TOOLS_WARN_EXCEPTION("ucb.ucp", "");
+ return false;
+ }
+
+ return true;
+}
+
+
+bool HierarchyEntry::remove()
+{
+ try
+ {
+ std::unique_lock aGuard( m_aMutex );
+
+ if ( !m_xConfigProvider.is() )
+ m_xConfigProvider.set(
+ m_xContext->getServiceManager()->createInstanceWithContext(m_aServiceSpecifier, m_xContext),
+ uno::UNO_QUERY );
+
+ if ( m_xConfigProvider.is() )
+ {
+ // Create parent's key. It must exist!
+
+ OUString aParentPath;
+ bool bRoot = true;
+
+ sal_Int32 nPos = m_aPath.lastIndexOf( '/' );
+ if ( nPos != -1 )
+ {
+ // Skip "/Children" segment of the path, too.
+ nPos = m_aPath.lastIndexOf( '/', nPos - 1 );
+
+ OSL_ENSURE( nPos != -1,
+ "HierarchyEntry::remove - Wrong path!" );
+
+ aParentPath += m_aPath.subView( 0, nPos );
+ bRoot = false;
+ }
+
+ uno::Sequence<uno::Any> aArguments(comphelper::InitAnyPropertySequence(
+ {
+ {CFGPROPERTY_NODEPATH, uno::Any(aParentPath)}
+ }));
+
+ uno::Reference< util::XChangesBatch > xBatch(
+ m_xConfigProvider->createInstanceWithArguments(
+ READWRITE_SERVICE_NAME,
+ aArguments ),
+ uno::UNO_QUERY );
+
+ OSL_ENSURE( xBatch.is(),
+ "HierarchyEntry::remove - No batch!" );
+
+ uno::Reference< container::XNameAccess > xParentNameAccess(
+ xBatch, uno::UNO_QUERY );
+
+ OSL_ENSURE( xParentNameAccess.is(),
+ "HierarchyEntry::remove - No name access!" );
+
+ if ( xBatch.is() && xParentNameAccess.is() )
+ {
+ uno::Reference< container::XNameContainer > xContainer;
+
+ if ( bRoot )
+ {
+ // Special handling for children of root,
+ // which is not an entry. It's only a set
+ // of entries.
+ xContainer.set( xParentNameAccess, uno::UNO_QUERY );
+ }
+ else
+ {
+ // Append new entry to parents child list,
+ // which is a set of entries.
+ xParentNameAccess->getByName("Children") >>= xContainer;
+ }
+
+ OSL_ENSURE( xContainer.is(),
+ "HierarchyEntry::remove - No container!" );
+
+ if ( xContainer.is() )
+ {
+ xContainer->removeByName( m_aName );
+ xBatch->commitChanges();
+ return true;
+ }
+ }
+ }
+ }
+ catch ( uno::RuntimeException const & )
+ {
+ throw;
+ }
+ catch ( container::NoSuchElementException const & )
+ {
+ // getByName, removeByName
+
+ OSL_FAIL(
+ "HierarchyEntry::remove - caught NoSuchElementException!" );
+ }
+ catch ( lang::WrappedTargetException const & )
+ {
+ // getByName, commitChanges
+
+ OSL_FAIL(
+ "HierarchyEntry::remove - caught WrappedTargetException!" );
+ }
+ catch ( uno::Exception const & )
+ {
+ // createInstance, createInstanceWithArguments
+
+ TOOLS_WARN_EXCEPTION("ucb.ucp", "");
+ }
+
+ return false;
+}
+
+
+bool HierarchyEntry::first( iterator & it )
+{
+ if ( it.pos == -1 )
+ {
+ // Init...
+
+ try
+ {
+ uno::Reference< container::XHierarchicalNameAccess >
+ xRootHierNameAccess = getRootReadAccess();
+
+ if ( xRootHierNameAccess.is() )
+ {
+ uno::Reference< container::XNameAccess > xNameAccess;
+
+ if ( !m_aPath.isEmpty() )
+ {
+ OUString aPath = m_aPath + "/Children";
+
+ xRootHierNameAccess->getByHierarchicalName( aPath )
+ >>= xNameAccess;
+ }
+ else
+ xNameAccess.set( xRootHierNameAccess, uno::UNO_QUERY );
+
+ OSL_ENSURE( xNameAccess.is(),
+ "HierarchyEntry::first - No name access!" );
+
+ if ( xNameAccess.is() )
+ it.names = xNameAccess->getElementNames();
+
+ uno::Reference< container::XHierarchicalNameAccess >
+ xHierNameAccess( xNameAccess, uno::UNO_QUERY );
+
+ OSL_ENSURE( xHierNameAccess.is(),
+ "HierarchyEntry::first - No hier. name access!" );
+
+ it.dir = xHierNameAccess;
+
+ it.officeDirs = m_xOfficeInstDirs;
+ }
+ }
+ catch ( uno::RuntimeException const & )
+ {
+ throw;
+ }
+ catch ( container::NoSuchElementException const& )
+ {
+ // getByHierarchicalName
+
+ TOOLS_WARN_EXCEPTION("ucb.ucp", "");
+ }
+ catch ( uno::Exception const & )
+ {
+ TOOLS_WARN_EXCEPTION("ucb.ucp", "");
+ }
+ }
+
+ if ( !it.names.hasElements() )
+ return false;
+
+ it.pos = 0;
+ return true;
+}
+
+
+bool HierarchyEntry::next( iterator& it )
+{
+ if ( it.pos == -1 )
+ return first( it );
+
+ ++it.pos;
+
+ return ( it.pos < it.names.getLength() );
+}
+
+
+OUString HierarchyEntry::createPathFromHierarchyURL(
+ const HierarchyUri& rURI )
+{
+ // Transform path...
+ // folder/subfolder/subsubfolder
+ // --> ['folder']/Children/['subfolder']/Children/['subsubfolder']
+
+ const OUString aPath = rURI.getPath().copy( 1 ); // skip leading slash.
+ sal_Int32 nLen = aPath.getLength();
+
+ if ( nLen )
+ {
+ OUStringBuffer aNewPath( "['" );
+
+ sal_Int32 nStart = 0;
+ sal_Int32 nEnd = aPath.indexOf( '/' );
+
+ do
+ {
+ if ( nEnd == -1 )
+ nEnd = nLen;
+
+ OUString aToken = aPath.copy( nStart, nEnd - nStart );
+ makeXMLName( aToken, aNewPath );
+
+ if ( nEnd != nLen )
+ {
+ aNewPath.append( "']/Children/['" );
+ nStart = nEnd + 1;
+ nEnd = aPath.indexOf( '/', nStart );
+ }
+ else
+ aNewPath.append( "']" );
+ }
+ while ( nEnd != nLen );
+
+ return aNewPath.makeStringAndClear();
+ }
+
+ return aPath;
+}
+
+
+uno::Reference< container::XHierarchicalNameAccess >
+HierarchyEntry::getRootReadAccess()
+{
+ if ( !m_xRootReadAccess.is() )
+ {
+ std::unique_lock aGuard( m_aMutex );
+ if ( !m_xRootReadAccess.is() )
+ {
+ if ( m_bTriedToGetRootReadAccess )
+ {
+ OSL_FAIL( "HierarchyEntry::getRootReadAccess - "
+ "Unable to read any config data! -> #82494#" );
+ return uno::Reference< container::XHierarchicalNameAccess >();
+ }
+
+ try
+ {
+ if ( !m_xConfigProvider.is() )
+ m_xConfigProvider.set(
+ m_xContext->getServiceManager()->createInstanceWithContext(m_aServiceSpecifier, m_xContext),
+ uno::UNO_QUERY );
+
+ if ( m_xConfigProvider.is() )
+ {
+ // Create Root object.
+
+ uno::Sequence<uno::Any> aArguments(comphelper::InitAnyPropertySequence(
+ {
+ {CFGPROPERTY_NODEPATH, uno::Any(OUString())} // root path
+ }));
+
+ m_bTriedToGetRootReadAccess = true;
+
+ m_xRootReadAccess.set(
+ m_xConfigProvider->createInstanceWithArguments(
+ READ_SERVICE_NAME,
+ aArguments ),
+ uno::UNO_QUERY );
+ }
+ }
+ catch ( uno::RuntimeException const & )
+ {
+ throw;
+ }
+ catch ( uno::Exception const & )
+ {
+ // createInstance, createInstanceWithArguments
+
+ TOOLS_WARN_EXCEPTION("ucb.ucp", "");
+ }
+ }
+ }
+ return m_xRootReadAccess;
+}
+
+
+// HierarchyEntry::iterator Implementation.
+
+
+const HierarchyEntryData& HierarchyEntry::iterator::operator*()
+{
+ if ( ( pos != -1 )
+ && ( dir.is() )
+ && ( pos < names.getLength() ) )
+ {
+ try
+ {
+ OUStringBuffer aKey( "['" );
+ makeXMLName( names.getConstArray()[ pos ], aKey );
+ aKey.append( "']" );
+
+ OUString aTitle = aKey.makeStringAndClear();
+ OUString aTargetURL = aTitle;
+ OUString aType = aTitle;
+
+ aTitle += "/Title";
+ aTargetURL += "/TargetURL";
+ aType += "/Type";
+
+ OUString aValue;
+ dir->getByHierarchicalName( aTitle ) >>= aValue;
+ entry.setTitle( aValue );
+
+ dir->getByHierarchicalName( aTargetURL ) >>= aValue;
+
+ // TargetURL property may contain a reference to the Office
+ // installation directory. To ensure a reloctable office
+ // installation, the path to the office installation directory must
+ // never be stored directly. A placeholder is used instead. Replace
+ // it by actual installation directory.
+ if ( officeDirs.is() && !aValue.isEmpty() )
+ aValue = officeDirs->makeAbsoluteURL( aValue );
+ entry.setTargetURL( aValue );
+
+ if ( dir->hasByHierarchicalName( aType ) )
+ {
+ // Might not be present since it was introduced long
+ // after Title and TargetURL (#82433#)... So not getting
+ // it is not an error.
+
+ // Get Type value.
+ sal_Int32 nType = 0;
+ if ( dir->getByHierarchicalName( aType ) >>= nType )
+ {
+ if ( nType == 0 )
+ {
+ entry.setType( HierarchyEntryData::LINK );
+ }
+ else if ( nType == 1 )
+ {
+ entry.setType( HierarchyEntryData::FOLDER );
+ }
+ else
+ {
+ OSL_FAIL( "HierarchyEntry::getData - "
+ "Unknown Type value!" );
+ }
+ }
+ }
+
+ entry.setName(
+ names.getConstArray()[ pos ] );
+ }
+ catch ( container::NoSuchElementException const & )
+ {
+ entry = HierarchyEntryData();
+ }
+ }
+
+ return entry;
+}
+
+} // namespace hierarchy_ucp
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/ucb/source/ucp/hierarchy/hierarchydata.hxx b/ucb/source/ucp/hierarchy/hierarchydata.hxx
new file mode 100644
index 0000000000..63c9a7a0af
--- /dev/null
+++ b/ucb/source/ucp/hierarchy/hierarchydata.hxx
@@ -0,0 +1,134 @@
+/* -*- 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:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#pragma once
+
+#include <rtl/ustring.hxx>
+#include <com/sun/star/lang/XMultiServiceFactory.hpp>
+#include <com/sun/star/uno/XComponentContext.hpp>
+#include <mutex>
+
+namespace com::sun::star {
+ namespace container {
+ class XHierarchicalNameAccess;
+ }
+ namespace util {
+ class XOfficeInstallationDirectories;
+ }
+}
+
+namespace hierarchy_ucp
+{
+
+
+class HierarchyEntryData
+{
+public:
+ enum Type { NONE, LINK, FOLDER };
+
+ HierarchyEntryData() : m_aType( NONE ) {}
+ explicit HierarchyEntryData( const Type & rType ) : m_aType( rType ) {}
+
+ const OUString & getName() const { return m_aName; }
+ void setName( const OUString & rName ) { m_aName = rName; }
+
+ const OUString & getTitle() const { return m_aTitle; }
+ void setTitle( const OUString & rTitle ) { m_aTitle = rTitle; }
+
+ const OUString & getTargetURL() const { return m_aTargetURL; }
+ void setTargetURL( const OUString & rURL ) { m_aTargetURL = rURL; }
+
+ Type getType() const
+ { return ( m_aType != NONE ) ? m_aType
+ : m_aTargetURL.getLength()
+ ? LINK
+ : FOLDER; }
+ void setType( const Type & rType ) { m_aType = rType; }
+
+private:
+ OUString m_aName; // Name (language independent)
+ OUString m_aTitle; // Title (language dependent)
+ OUString m_aTargetURL; // Target URL ( links only )
+ Type m_aType; // Type
+};
+
+
+class HierarchyContentProvider;
+class HierarchyUri;
+
+class HierarchyEntry
+{
+ OUString m_aServiceSpecifier;
+ OUString m_aName;
+ OUString m_aPath;
+ std::mutex m_aMutex;
+ css::uno::Reference< css::uno::XComponentContext > m_xContext;
+ css::uno::Reference< css::lang::XMultiServiceFactory > m_xConfigProvider;
+ css::uno::Reference< css::container::XHierarchicalNameAccess >
+ m_xRootReadAccess;
+ css::uno::Reference< css::util::XOfficeInstallationDirectories >
+ m_xOfficeInstDirs;
+ bool m_bTriedToGetRootReadAccess;
+
+private:
+ static OUString createPathFromHierarchyURL( const HierarchyUri & rURI );
+ css::uno::Reference< css::container::XHierarchicalNameAccess >
+ getRootReadAccess();
+
+public:
+ HierarchyEntry( css::uno::Reference< css::uno::XComponentContext > xContext,
+ HierarchyContentProvider* pProvider,
+ const OUString& rURL );
+
+ bool hasData();
+
+ bool getData( HierarchyEntryData& rData );
+
+ bool setData( const HierarchyEntryData& rData );
+
+ bool move( const OUString& rNewURL,
+ const HierarchyEntryData& rData );
+
+ bool remove();
+
+ // Iteration.
+
+ class iterator
+ {
+ friend class HierarchyEntry;
+
+ public:
+ iterator() : pos( -1 /* before first */ ) {};
+
+ const HierarchyEntryData& operator*();
+ private:
+ HierarchyEntryData entry;
+ css::uno::Reference< css::container::XHierarchicalNameAccess > dir;
+ css::uno::Reference< css::util::XOfficeInstallationDirectories > officeDirs;
+ css::uno::Sequence< OUString> names;
+ sal_Int32 pos;
+ };
+
+ bool first( iterator& it );
+ bool next ( iterator& it );
+};
+
+} // namespace hierarchy_ucp
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/ucb/source/ucp/hierarchy/hierarchydatasource.cxx b/ucb/source/ucp/hierarchy/hierarchydatasource.cxx
new file mode 100644
index 0000000000..58ec6ce57d
--- /dev/null
+++ b/ucb/source/ucp/hierarchy/hierarchydatasource.cxx
@@ -0,0 +1,863 @@
+/* -*- 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:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+
+/**************************************************************************
+ TODO
+ **************************************************************************
+
+ Note: Configuration Management classes do not support XAggregation.
+ So I have to wrap the interesting interfaces manually.
+
+ *************************************************************************/
+#include "hierarchydatasource.hxx"
+#include <osl/diagnose.h>
+
+#include <comphelper/propertyvalue.hxx>
+#include <cppuhelper/queryinterface.hxx>
+#include <cppuhelper/weak.hxx>
+#include <com/sun/star/beans/PropertyValue.hpp>
+#include <com/sun/star/configuration/theDefaultProvider.hpp>
+#include <com/sun/star/container/XHierarchicalNameAccess.hpp>
+#include <com/sun/star/container/XNameContainer.hpp>
+#include <com/sun/star/util/XChangesBatch.hpp>
+#include <com/sun/star/util/XChangesNotifier.hpp>
+#include <com/sun/star/lang/XSingleServiceFactory.hpp>
+#include <o3tl/string_view.hxx>
+#include <ucbhelper/macros.hxx>
+#include <mutex>
+#include <utility>
+
+using namespace com::sun::star;
+using namespace hierarchy_ucp;
+
+
+// describe path of cfg entry
+constexpr OUString CFGPROPERTY_NODEPATH = u"nodepath"_ustr;
+
+constexpr OUString READ_SERVICE_NAME = u"com.sun.star.ucb.HierarchyDataReadAccess"_ustr;
+constexpr OUString READWRITE_SERVICE_NAME = u"com.sun.star.ucb.HierarchyDataReadWriteAccess"_ustr;
+
+constexpr OUString CONFIG_DATA_ROOT_KEY = u"/org.openoffice.ucb.Hierarchy/Root"_ustr;
+
+
+namespace hcp_impl
+{
+
+
+// HierarchyDataReadAccess Implementation.
+
+namespace {
+
+class HierarchyDataAccess : public cppu::OWeakObject,
+ public lang::XServiceInfo,
+ public lang::XTypeProvider,
+ public lang::XComponent,
+ public lang::XSingleServiceFactory,
+ public container::XHierarchicalNameAccess,
+ public container::XNameContainer,
+ public util::XChangesNotifier,
+ public util::XChangesBatch
+{
+ std::mutex m_aMutex;
+ uno::Reference< uno::XInterface > m_xConfigAccess;
+ uno::Reference< lang::XComponent > m_xCfgC;
+ uno::Reference< lang::XSingleServiceFactory > m_xCfgSSF;
+ uno::Reference< container::XHierarchicalNameAccess > m_xCfgHNA;
+ uno::Reference< container::XNameContainer > m_xCfgNC;
+ uno::Reference< container::XNameReplace > m_xCfgNR;
+ uno::Reference< container::XNameAccess > m_xCfgNA;
+ uno::Reference< container::XElementAccess > m_xCfgEA;
+ uno::Reference< util::XChangesNotifier > m_xCfgCN;
+ uno::Reference< util::XChangesBatch > m_xCfgCB;
+ bool m_bReadOnly;
+
+public:
+ HierarchyDataAccess( uno::Reference<
+ uno::XInterface > xConfigAccess,
+ bool bReadOnly );
+
+ // 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;
+
+ // 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;
+
+ // XTypeProvider
+ virtual css::uno::Sequence< sal_Int8 > SAL_CALL getImplementationId() override;
+ virtual css::uno::Sequence< css::uno::Type > SAL_CALL getTypes() override;
+
+ // XComponent
+ virtual void SAL_CALL
+ dispose() override;
+ virtual void SAL_CALL
+ addEventListener( const uno::Reference< lang::XEventListener > & xListener ) override;
+ virtual void SAL_CALL
+ removeEventListener( const uno::Reference<
+ lang::XEventListener > & aListener ) override;
+
+ // XSingleServiceFactory
+ virtual uno::Reference< uno::XInterface > SAL_CALL
+ createInstance() override;
+ virtual uno::Reference< uno::XInterface > SAL_CALL
+ createInstanceWithArguments( const uno::Sequence< uno::Any > & aArguments ) override;
+
+ // XHierarchicalNameAccess
+ virtual uno::Any SAL_CALL
+ getByHierarchicalName( const OUString & aName ) override;
+ virtual sal_Bool SAL_CALL
+ hasByHierarchicalName( const OUString & aName ) override;
+
+ // XNameContainer
+ virtual void SAL_CALL
+ insertByName( const OUString & aName, const uno::Any & aElement ) override;
+ virtual void SAL_CALL
+ removeByName( const OUString & Name ) override;
+
+ // XNameReplace ( base of XNameContainer )
+ virtual void SAL_CALL
+ replaceByName( const OUString & aName, const uno::Any & aElement ) override;
+
+ // XNameAccess ( base of XNameReplace )
+ virtual uno::Any SAL_CALL
+ getByName( const OUString & aName ) override;
+ virtual uno::Sequence< OUString > SAL_CALL
+ getElementNames() override;
+ virtual sal_Bool SAL_CALL
+ hasByName( const OUString & aName ) override;
+
+ // XElementAccess ( base of XNameAccess )
+ virtual uno::Type SAL_CALL
+ getElementType() override;
+ virtual sal_Bool SAL_CALL
+ hasElements() override;
+
+ // XChangesNotifier
+ virtual void SAL_CALL
+ addChangesListener( const uno::Reference<
+ util::XChangesListener > & aListener ) override;
+ virtual void SAL_CALL
+ removeChangesListener( const uno::Reference<
+ util::XChangesListener > & aListener ) override;
+
+ // XChangesBatch
+ virtual void SAL_CALL
+ commitChanges() override;
+ virtual sal_Bool SAL_CALL
+ hasPendingChanges() override;
+ virtual uno::Sequence< util::ElementChange > SAL_CALL
+ getPendingChanges() override;
+private:
+ template<class T>
+ css::uno::Reference<T> ensureOrigInterface(css::uno::Reference<T>& x);
+};
+
+}
+
+} // namespace hcp_impl
+
+using namespace hcp_impl;
+
+
+// HierarchyDataSource Implementation.
+
+
+HierarchyDataSource::HierarchyDataSource(
+ uno::Reference< uno::XComponentContext > xContext )
+: m_xContext(std::move( xContext ))
+{
+}
+
+
+// virtual
+HierarchyDataSource::~HierarchyDataSource()
+{
+}
+
+// XServiceInfo methods.
+OUString SAL_CALL HierarchyDataSource::getImplementationName() \
+{
+ return "com.sun.star.comp.ucb.HierarchyDataSource";
+}
+sal_Bool SAL_CALL HierarchyDataSource::supportsService( const OUString& ServiceName )
+{
+ return cppu::supportsService( this, ServiceName );
+}
+css::uno::Sequence< OUString > HierarchyDataSource::getSupportedServiceNames()
+{
+ return { "com.sun.star.ucb.DefaultHierarchyDataSource", "com.sun.star.ucb.HierarchyDataSource" };
+}
+
+extern "C" SAL_DLLPUBLIC_EXPORT css::uno::XInterface*
+ucb_HierarchyDataSource_get_implementation(
+ css::uno::XComponentContext* context , css::uno::Sequence<css::uno::Any> const&)
+{
+ return cppu::acquire(new HierarchyDataSource(context));
+}
+
+
+// XComponent methods.
+
+
+// virtual
+void SAL_CALL HierarchyDataSource::dispose()
+{
+ std::unique_lock aGuard( m_aMutex );
+
+ if ( m_aDisposeEventListeners.getLength(aGuard) )
+ {
+ lang::EventObject aEvt;
+ aEvt.Source = static_cast< lang::XComponent * >( this );
+ m_aDisposeEventListeners.disposeAndClear( aGuard, aEvt );
+ }
+}
+
+
+// virtual
+void SAL_CALL HierarchyDataSource::addEventListener(
+ const uno::Reference< lang::XEventListener > & Listener )
+{
+ std::unique_lock aGuard( m_aMutex );
+
+ m_aDisposeEventListeners.addInterface( aGuard, Listener );
+}
+
+
+// virtual
+void SAL_CALL HierarchyDataSource::removeEventListener(
+ const uno::Reference< lang::XEventListener > & Listener )
+{
+ std::unique_lock aGuard( m_aMutex );
+
+ m_aDisposeEventListeners.removeInterface( aGuard, Listener );
+}
+
+
+// XMultiServiceFactory methods.
+
+
+// virtual
+uno::Reference< uno::XInterface > SAL_CALL
+HierarchyDataSource::createInstance( const OUString & aServiceSpecifier )
+{
+ // Create view to root node.
+
+ beans::PropertyValue aProp = comphelper::makePropertyValue(CFGPROPERTY_NODEPATH,
+ CONFIG_DATA_ROOT_KEY);
+
+ uno::Sequence< uno::Any > aArguments{ uno::Any(aProp) };
+
+ return createInstanceWithArguments( aServiceSpecifier, aArguments, false );
+}
+
+
+// virtual
+uno::Reference< uno::XInterface > SAL_CALL
+HierarchyDataSource::createInstanceWithArguments(
+ const OUString & ServiceSpecifier,
+ const uno::Sequence< uno::Any > & Arguments )
+{
+ return createInstanceWithArguments( ServiceSpecifier, Arguments, true );
+}
+
+
+// virtual
+uno::Sequence< OUString > SAL_CALL
+HierarchyDataSource::getAvailableServiceNames()
+{
+ return { READ_SERVICE_NAME, READWRITE_SERVICE_NAME };
+}
+
+
+// Non-interface methods
+
+
+uno::Reference< uno::XInterface >
+HierarchyDataSource::createInstanceWithArguments(
+ std::u16string_view ServiceSpecifier,
+ const uno::Sequence< uno::Any > & Arguments,
+ bool bCheckArgs )
+{
+ // Check service specifier.
+ bool bReadOnly = ServiceSpecifier == READ_SERVICE_NAME;
+ bool bReadWrite = !bReadOnly && ServiceSpecifier == READWRITE_SERVICE_NAME;
+
+ if ( !bReadOnly && !bReadWrite )
+ {
+ OSL_FAIL( "HierarchyDataSource::createInstanceWithArguments - "
+ "Unsupported service specifier!" );
+ return uno::Reference< uno::XInterface >();
+ }
+
+ uno::Sequence< uno::Any > aNewArgs( Arguments );
+ auto aNewArgsRange = asNonConstRange(aNewArgs);
+
+ if ( bCheckArgs )
+ {
+ // Check arguments.
+ bool bHasNodePath = false;
+ sal_Int32 nCount = Arguments.getLength();
+ for ( sal_Int32 n = 0; n < nCount; ++n )
+ {
+ beans::PropertyValue aProp;
+ if ( Arguments[ n ] >>= aProp )
+ {
+ if ( aProp.Name == CFGPROPERTY_NODEPATH )
+ {
+ OUString aPath;
+ if ( aProp.Value >>= aPath )
+ {
+ bHasNodePath = true;
+
+ // Create path to data inside the configuration.
+ OUString aConfigPath;
+ if ( !createConfigPath( aPath, aConfigPath ) )
+ {
+ OSL_FAIL( "HierarchyDataSource::"
+ "createInstanceWithArguments - "
+ "Invalid node path!" );
+ return uno::Reference< uno::XInterface >();
+ }
+
+ aProp.Value <<= aConfigPath;
+
+ // Set new path in arguments.
+ aNewArgsRange[ n ] <<= aProp;
+
+ break;
+ }
+ else
+ {
+ OSL_FAIL( "HierarchyDataSource::createInstanceWithArguments - "
+ "Invalid type for property 'nodepath'!" );
+ return uno::Reference< uno::XInterface >();
+ }
+ }
+ }
+ }
+
+ if ( !bHasNodePath )
+ {
+ OSL_FAIL( "HierarchyDataSource::createInstanceWithArguments - "
+ "No 'nodepath' property!" );
+ return uno::Reference< uno::XInterface >();
+ }
+ }
+
+ // Create Configuration Provider.
+ uno::Reference< lang::XMultiServiceFactory > xProv = getConfigProvider();
+ if ( !xProv.is() )
+ return uno::Reference< uno::XInterface >();
+
+ uno::Reference< uno::XInterface > xConfigAccess;
+ try
+ {
+ if ( bReadOnly )
+ {
+ // Create configuration read-only access object.
+ xConfigAccess = xProv->createInstanceWithArguments(
+ "com.sun.star.configuration.ConfigurationAccess",
+ aNewArgs );
+ }
+ else
+ {
+ // Create configuration read-write access object.
+ xConfigAccess = xProv->createInstanceWithArguments(
+ "com.sun.star.configuration.ConfigurationUpdateAccess",
+ aNewArgs );
+ }
+ }
+ catch ( uno::Exception const & )
+ {
+ OSL_FAIL( "HierarchyDataSource::createInstanceWithArguments - "
+ "Cannot instantiate configuration access!" );
+ throw;
+ }
+
+ if ( !xConfigAccess.is() )
+ {
+ OSL_FAIL( "HierarchyDataSource::createInstanceWithArguments - "
+ "Cannot instantiate configuration access!" );
+ return xConfigAccess;
+ }
+
+ return cppu::getXWeak(new HierarchyDataAccess(xConfigAccess, bReadOnly));
+}
+
+
+uno::Reference< lang::XMultiServiceFactory >
+HierarchyDataSource::getConfigProvider()
+{
+ if ( !m_xConfigProvider.is() )
+ {
+ std::unique_lock aGuard( m_aMutex );
+ if ( !m_xConfigProvider.is() )
+ {
+ try
+ {
+ m_xConfigProvider = configuration::theDefaultProvider::get( m_xContext );
+ }
+ catch ( uno::Exception const & )
+ {
+ OSL_FAIL( "HierarchyDataSource::getConfigProvider - "
+ "caught exception!" );
+ }
+ }
+ }
+
+ return m_xConfigProvider;
+}
+
+
+bool HierarchyDataSource::createConfigPath(
+ std::u16string_view rInPath, OUString & rOutPath )
+{
+ if ( !rInPath.empty() )
+ {
+ if ( o3tl::starts_with( rInPath, u"/" ) )
+ {
+ OSL_FAIL( "HierarchyDataSource::createConfigPath - "
+ "Leading slash in node path!" );
+ return false;
+ }
+
+ if ( o3tl::ends_with( rInPath, u"/" ) )
+ {
+ OSL_FAIL( "HierarchyDataSource::createConfigPath - "
+ "Trailing slash in node path!" );
+ return false;
+ }
+
+ rOutPath = CONFIG_DATA_ROOT_KEY + "/" + rInPath;
+ }
+ else
+ {
+ rOutPath = CONFIG_DATA_ROOT_KEY;
+ }
+
+ return true;
+}
+
+
+// HierarchyDataAccess Implementation.
+
+template<class T>
+css::uno::Reference<T> HierarchyDataAccess::ensureOrigInterface(css::uno::Reference<T>& x)
+{
+ if ( x.is() )
+ return x;
+ std::scoped_lock aGuard( m_aMutex );
+ if ( !x.is() )
+ x.set( m_xConfigAccess, uno::UNO_QUERY );
+ return x;
+}
+
+
+HierarchyDataAccess::HierarchyDataAccess( uno::Reference<
+ uno::XInterface > xConfigAccess,
+ bool bReadOnly )
+: m_xConfigAccess(std::move( xConfigAccess )),
+ m_bReadOnly( bReadOnly )
+{
+}
+
+// XInterface methods.
+void SAL_CALL HierarchyDataAccess::acquire()
+ noexcept
+{
+ OWeakObject::acquire();
+}
+
+void SAL_CALL HierarchyDataAccess::release()
+ noexcept
+{
+ OWeakObject::release();
+}
+
+// virtual
+uno::Any SAL_CALL HierarchyDataAccess::queryInterface( const uno::Type & aType )
+{
+ // Interfaces supported in read-only and read-write mode.
+ uno::Any aRet = cppu::queryInterface( aType,
+ static_cast< lang::XTypeProvider * >( this ),
+ static_cast< lang::XServiceInfo * >( this ),
+ static_cast< lang::XComponent * >( this ),
+ static_cast< container::XHierarchicalNameAccess * >( this ),
+ static_cast< container::XNameAccess * >( this ),
+ static_cast< container::XElementAccess * >( this ),
+ static_cast< util::XChangesNotifier * >( this ) );
+
+ // Interfaces supported only in read-write mode.
+ if ( !aRet.hasValue() && !m_bReadOnly )
+ {
+ aRet = cppu::queryInterface( aType,
+ static_cast< lang::XSingleServiceFactory * >( this ),
+ static_cast< container::XNameContainer * >( this ),
+ static_cast< container::XNameReplace * >( this ),
+ static_cast< util::XChangesBatch * >( this ) );
+ }
+
+ return aRet.hasValue() ? aRet : OWeakObject::queryInterface( aType );
+}
+
+
+// XTypeProvider methods.
+
+
+XTYPEPROVIDER_COMMON_IMPL( HierarchyDataAccess );
+
+
+// virtual
+uno::Sequence< uno::Type > SAL_CALL HierarchyDataAccess::getTypes()
+{
+ if ( m_bReadOnly )
+ {
+ static cppu::OTypeCollection s_aReadOnlyTypes(
+ CPPU_TYPE_REF( lang::XTypeProvider ),
+ CPPU_TYPE_REF( lang::XServiceInfo ),
+ CPPU_TYPE_REF( lang::XComponent ),
+ CPPU_TYPE_REF( container::XHierarchicalNameAccess ),
+ CPPU_TYPE_REF( container::XNameAccess ),
+ CPPU_TYPE_REF( util::XChangesNotifier ) );
+
+ return s_aReadOnlyTypes.getTypes();
+ }
+ else
+ {
+ static cppu::OTypeCollection s_aReadWriteTypes(
+ CPPU_TYPE_REF( lang::XTypeProvider ),
+ CPPU_TYPE_REF( lang::XServiceInfo ),
+ CPPU_TYPE_REF( lang::XComponent ),
+ CPPU_TYPE_REF( lang::XSingleServiceFactory ),
+ CPPU_TYPE_REF( container::XHierarchicalNameAccess ),
+ CPPU_TYPE_REF( container::XNameContainer ),
+ CPPU_TYPE_REF( util::XChangesBatch ),
+ CPPU_TYPE_REF( util::XChangesNotifier ) );
+
+ return s_aReadWriteTypes.getTypes();
+ }
+}
+
+
+// XServiceInfo methods.
+
+OUString SAL_CALL HierarchyDataAccess::getImplementationName()
+{
+ return "com.sun.star.comp.ucb.HierarchyDataAccess";
+}
+
+sal_Bool SAL_CALL HierarchyDataAccess::supportsService( const OUString& ServiceName )
+{
+ return cppu::supportsService( this, ServiceName );
+}
+
+css::uno::Sequence< OUString > SAL_CALL HierarchyDataAccess::getSupportedServiceNames()
+{
+ return { READ_SERVICE_NAME, READWRITE_SERVICE_NAME };
+}
+
+
+// XComponent methods.
+
+
+// virtual
+void SAL_CALL HierarchyDataAccess::dispose()
+{
+ uno::Reference< lang::XComponent > xOrig
+ = ensureOrigInterface( m_xCfgC );
+
+ OSL_ENSURE( xOrig.is(),
+ "HierarchyDataAccess : Data source is not an XComponent!" );
+ xOrig->dispose();
+}
+
+
+// virtual
+void SAL_CALL HierarchyDataAccess::addEventListener(
+ const uno::Reference< lang::XEventListener > & xListener )
+{
+ uno::Reference< lang::XComponent > xOrig
+ = ensureOrigInterface( m_xCfgC );
+
+ OSL_ENSURE( xOrig.is(),
+ "HierarchyDataAccess : Data source is not an XComponent!" );
+ xOrig->addEventListener( xListener );
+}
+
+
+// virtual
+void SAL_CALL HierarchyDataAccess::removeEventListener(
+ const uno::Reference< lang::XEventListener > & aListener )
+{
+ uno::Reference< lang::XComponent > xOrig
+ = ensureOrigInterface( m_xCfgC );
+
+ OSL_ENSURE( xOrig.is(),
+ "HierarchyDataAccess : Data source is not an XComponent!" );
+ xOrig->removeEventListener( aListener );
+}
+
+
+// XHierarchicalNameAccess methods.
+
+
+// virtual
+uno::Any SAL_CALL HierarchyDataAccess::getByHierarchicalName(
+ const OUString & aName )
+{
+ uno::Reference< container::XHierarchicalNameAccess > xOrig
+ = ensureOrigInterface( m_xCfgHNA );
+
+ OSL_ENSURE( xOrig.is(),
+ "HierarchyDataAccess : "
+ "Data source is not an XHierarchicalNameAccess!" );
+ return xOrig->getByHierarchicalName( aName );
+}
+
+
+// virtual
+sal_Bool SAL_CALL HierarchyDataAccess::hasByHierarchicalName(
+ const OUString & aName )
+{
+ uno::Reference< container::XHierarchicalNameAccess > xOrig
+ = ensureOrigInterface( m_xCfgHNA );
+
+ OSL_ENSURE( xOrig.is(),
+ "HierarchyDataAccess : "
+ "Data source is not an XHierarchicalNameAccess!" );
+ return xOrig->hasByHierarchicalName( aName );
+}
+
+
+// XNameAccess methods.
+
+
+// virtual
+uno::Any SAL_CALL HierarchyDataAccess::getByName( const OUString & aName )
+{
+ uno::Reference< container::XNameAccess > xOrig
+ = ensureOrigInterface( m_xCfgNA );
+
+ OSL_ENSURE( xOrig.is(),
+ "HierarchyDataAccess : Data source is not an XNameAccess!" );
+ return xOrig->getByName( aName );
+}
+
+
+// virtual
+uno::Sequence< OUString > SAL_CALL HierarchyDataAccess::getElementNames()
+{
+ uno::Reference< container::XNameAccess > xOrig
+ = ensureOrigInterface( m_xCfgNA );
+
+ OSL_ENSURE( xOrig.is(),
+ "HierarchyDataAccess : Data source is not an XNameAccess!" );
+ return xOrig->getElementNames();
+}
+
+
+// virtual
+sal_Bool SAL_CALL HierarchyDataAccess::hasByName( const OUString & aName )
+{
+ uno::Reference< container::XNameAccess > xOrig
+ = ensureOrigInterface( m_xCfgNA );
+
+ OSL_ENSURE( xOrig.is(),
+ "HierarchyDataAccess : Data source is not an XNameAccess!" );
+ return xOrig->hasByName( aName );
+}
+
+
+// XElementAccess methods.
+
+
+// virtual
+uno::Type SAL_CALL HierarchyDataAccess::getElementType()
+{
+ uno::Reference< container::XElementAccess > xOrig
+ = ensureOrigInterface( m_xCfgEA );
+
+ OSL_ENSURE( xOrig.is(),
+ "HierarchyDataAccess : Data source is not an XElementAccess!" );
+ return xOrig->getElementType();
+}
+
+
+// virtual
+sal_Bool SAL_CALL HierarchyDataAccess::hasElements()
+{
+ uno::Reference< container::XElementAccess > xOrig
+ = ensureOrigInterface( m_xCfgEA );
+
+ OSL_ENSURE( xOrig.is(),
+ "HierarchyDataAccess : Data source is not an XElementAccess!" );
+ return xOrig->hasElements();
+}
+
+
+// XChangesNotifier methods.
+
+
+// virtual
+void SAL_CALL HierarchyDataAccess::addChangesListener(
+ const uno::Reference< util::XChangesListener > & aListener )
+{
+ uno::Reference< util::XChangesNotifier > xOrig
+ = ensureOrigInterface( m_xCfgCN );
+
+ OSL_ENSURE( xOrig.is(),
+ "HierarchyDataAccess : Data source is not an XChangesNotifier!" );
+ xOrig->addChangesListener( aListener );
+}
+
+
+// virtual
+void SAL_CALL HierarchyDataAccess::removeChangesListener(
+ const uno::Reference< util::XChangesListener > & aListener )
+{
+ uno::Reference< util::XChangesNotifier > xOrig
+ = ensureOrigInterface( m_xCfgCN );
+
+ OSL_ENSURE( xOrig.is(),
+ "HierarchyDataAccess : Data source is not an XChangesNotifier!" );
+ xOrig->removeChangesListener( aListener );
+}
+
+
+// XSingleServiceFactory methods.
+
+
+// virtual
+uno::Reference< uno::XInterface > SAL_CALL HierarchyDataAccess::createInstance()
+{
+ uno::Reference< lang::XSingleServiceFactory > xOrig
+ = ensureOrigInterface( m_xCfgSSF );
+
+ OSL_ENSURE( xOrig.is(),
+ "HierarchyDataAccess : Data source is not an XSingleServiceFactory!" );
+ return xOrig->createInstance();
+}
+
+
+// virtual
+uno::Reference< uno::XInterface > SAL_CALL
+HierarchyDataAccess::createInstanceWithArguments(
+ const uno::Sequence< uno::Any > & aArguments )
+{
+ uno::Reference< lang::XSingleServiceFactory > xOrig
+ = ensureOrigInterface( m_xCfgSSF );
+
+ OSL_ENSURE( xOrig.is(),
+ "HierarchyDataAccess : Data source is not an XSingleServiceFactory!" );
+ return xOrig->createInstanceWithArguments( aArguments );
+}
+
+
+// XNameContainer methods.
+
+
+// virtual
+void SAL_CALL
+HierarchyDataAccess::insertByName( const OUString & aName,
+ const uno::Any & aElement )
+{
+ uno::Reference< container::XNameContainer > xOrig
+ = ensureOrigInterface( m_xCfgNC );
+
+ OSL_ENSURE( xOrig.is(),
+ "HierarchyDataAccess : Data source is not an XNameContainer!" );
+ xOrig->insertByName( aName, aElement );
+}
+
+
+// virtual
+void SAL_CALL
+HierarchyDataAccess::removeByName( const OUString & Name )
+{
+ uno::Reference< container::XNameContainer > xOrig
+ = ensureOrigInterface( m_xCfgNC );
+
+ OSL_ENSURE( xOrig.is(),
+ "HierarchyDataAccess : Data source is not an XNameContainer!" );
+ xOrig->removeByName( Name );
+}
+
+
+// XNameReplace methods.
+
+
+// virtual
+void SAL_CALL HierarchyDataAccess::replaceByName( const OUString & aName,
+ const uno::Any & aElement )
+{
+ uno::Reference< container::XNameReplace > xOrig
+ = ensureOrigInterface( m_xCfgNR );
+
+ OSL_ENSURE( xOrig.is(),
+ "HierarchyDataAccess : Data source is not an XNameReplace!" );
+ xOrig->replaceByName( aName, aElement );
+}
+
+
+// XChangesBatch methods.
+
+
+// virtual
+void SAL_CALL HierarchyDataAccess::commitChanges()
+{
+ uno::Reference< util::XChangesBatch > xOrig
+ = ensureOrigInterface( m_xCfgCB );
+
+ OSL_ENSURE( xOrig.is(),
+ "HierarchyDataAccess : Data source is not an XChangesBatch!" );
+ xOrig->commitChanges();
+}
+
+
+// virtual
+sal_Bool SAL_CALL HierarchyDataAccess::hasPendingChanges()
+{
+ uno::Reference< util::XChangesBatch > xOrig
+ = ensureOrigInterface( m_xCfgCB );
+
+ OSL_ENSURE( xOrig.is(),
+ "HierarchyDataAccess : Data source is not an XChangesBatch!" );
+ return xOrig->hasPendingChanges();
+}
+
+
+// virtual
+uno::Sequence< util::ElementChange > SAL_CALL
+HierarchyDataAccess::getPendingChanges()
+{
+ uno::Reference< util::XChangesBatch > xOrig
+ = ensureOrigInterface( m_xCfgCB );
+
+ OSL_ENSURE( xOrig.is(),
+ "HierarchyDataAccess : Data source is not an XChangesBatch!" );
+ return xOrig->getPendingChanges();
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/ucb/source/ucp/hierarchy/hierarchydatasource.hxx b/ucb/source/ucp/hierarchy/hierarchydatasource.hxx
new file mode 100644
index 0000000000..b2d1812328
--- /dev/null
+++ b/ucb/source/ucp/hierarchy/hierarchydatasource.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/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#pragma once
+
+#include <com/sun/star/lang/XComponent.hpp>
+#include <com/sun/star/lang/XMultiServiceFactory.hpp>
+#include <com/sun/star/lang/XServiceInfo.hpp>
+#include <com/sun/star/uno/XComponentContext.hpp>
+#include <comphelper/interfacecontainer4.hxx>
+#include <cppuhelper/implbase.hxx>
+#include <memory>
+#include <mutex>
+#include <string_view>
+
+
+namespace hierarchy_ucp {
+
+
+class HierarchyDataSource : public cppu::WeakImplHelper<
+ css::lang::XServiceInfo,
+ css::lang::XComponent,
+ css::lang::XMultiServiceFactory>
+{
+ std::mutex m_aMutex;
+ css::uno::Reference< css::uno::XComponentContext > m_xContext;
+ css::uno::Reference< css::lang::XMultiServiceFactory > m_xConfigProvider;
+ comphelper::OInterfaceContainerHelper4<css::lang::XEventListener> m_aDisposeEventListeners;
+
+public:
+ explicit HierarchyDataSource( css::uno::Reference< css::uno::XComponentContext > xContext );
+ virtual ~HierarchyDataSource() 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;
+
+ // XComponent
+ virtual void SAL_CALL dispose() override;
+ virtual void SAL_CALL addEventListener( const css::uno::Reference< css::lang::XEventListener > & xListener ) override;
+ virtual void SAL_CALL removeEventListener( const css::uno::Reference< css::lang::XEventListener > & aListener ) override;
+
+ // XMultiServiceFactory
+ virtual css::uno::Reference< css::uno::XInterface > SAL_CALL createInstance( const OUString & aServiceSpecifier ) override;
+ virtual css::uno::Reference< css::uno::XInterface > SAL_CALL createInstanceWithArguments( const OUString & ServiceSpecifier,
+ const css::uno::Sequence<
+ css::uno::Any > & Arguments ) override;
+ virtual css::uno::Sequence< OUString > SAL_CALL getAvailableServiceNames() override;
+
+ // Non-Interface methods
+
+private:
+ /// @throws css::uno::Exception
+ css::uno::Reference< css::uno::XInterface > createInstanceWithArguments( std::u16string_view ServiceSpecifier,
+ const css::uno::Sequence<
+ css::uno::Any > & Arguments,
+ bool bCheckArgs );
+
+ css::uno::Reference< css::lang::XMultiServiceFactory > getConfigProvider();
+
+ static bool createConfigPath( std::u16string_view rInPath, OUString & rOutPath );
+};
+
+} // namespace hierarchy_ucp
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/ucb/source/ucp/hierarchy/hierarchydatasupplier.cxx b/ucb/source/ucp/hierarchy/hierarchydatasupplier.cxx
new file mode 100644
index 0000000000..1a2f29f6b3
--- /dev/null
+++ b/ucb/source/ucp/hierarchy/hierarchydatasupplier.cxx
@@ -0,0 +1,369 @@
+/* -*- 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:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+
+/**************************************************************************
+ TODO
+ **************************************************************************
+
+ *************************************************************************/
+
+#include <com/sun/star/ucb/IllegalIdentifierException.hpp>
+#include <com/sun/star/ucb/OpenMode.hpp>
+#include <ucbhelper/contentidentifier.hxx>
+#include "hierarchydatasupplier.hxx"
+#include "hierarchyprovider.hxx"
+#include "hierarchycontent.hxx"
+
+using namespace com::sun::star;
+using namespace hierarchy_ucp;
+
+
+
+HierarchyResultSetDataSupplier::HierarchyResultSetDataSupplier(
+ const uno::Reference< uno::XComponentContext >& rxContext,
+ const rtl::Reference< HierarchyContent >& rContent,
+ sal_Int32 nOpenMode )
+: m_xContent( rContent ), m_xContext( rxContext ),
+ m_aFolder( rxContext,
+ static_cast< HierarchyContentProvider * >(
+ rContent->getProvider().get() ),
+ rContent->getIdentifier()->getContentIdentifier() ),
+ m_nOpenMode( nOpenMode ), m_bCountFinal( false )
+{
+}
+
+
+// virtual
+HierarchyResultSetDataSupplier::~HierarchyResultSetDataSupplier()
+{
+}
+
+
+// virtual
+OUString HierarchyResultSetDataSupplier::queryContentIdentifierString(
+ sal_uInt32 nIndex )
+{
+ std::unique_lock aGuard( m_aMutex );
+ return queryContentIdentifierStringImpl(aGuard, nIndex);
+}
+
+OUString HierarchyResultSetDataSupplier::queryContentIdentifierStringImpl(
+ std::unique_lock<std::mutex>& rGuard,
+ sal_uInt32 nIndex )
+{
+ if ( nIndex < m_aResults.size() )
+ {
+ OUString aId = m_aResults[ nIndex ]->aId;
+ if ( !aId.isEmpty() )
+ {
+ // Already cached.
+ return aId;
+ }
+ }
+
+ if ( getResultImpl( rGuard, nIndex ) )
+ {
+ OUString aId
+ = m_xContent->getIdentifier()->getContentIdentifier();
+
+ if ( ( aId.lastIndexOf( '/' ) + 1 ) != aId.getLength() )
+ aId += "/";
+
+ aId += m_aResults[ nIndex ]->aData.getName();
+
+ m_aResults[ nIndex ]->aId = aId;
+ return aId;
+ }
+ return OUString();
+}
+
+
+// virtual
+uno::Reference< ucb::XContentIdentifier >
+HierarchyResultSetDataSupplier::queryContentIdentifier( sal_uInt32 nIndex )
+{
+ std::unique_lock aGuard( m_aMutex );
+
+ if ( nIndex < m_aResults.size() )
+ {
+ uno::Reference< ucb::XContentIdentifier > xId
+ = m_aResults[ nIndex ]->xId;
+ if ( xId.is() )
+ {
+ // Already cached.
+ return xId;
+ }
+ }
+
+ OUString aId = queryContentIdentifierStringImpl( aGuard, nIndex );
+ if ( !aId.isEmpty() )
+ {
+ uno::Reference< ucb::XContentIdentifier > xId
+ = new ::ucbhelper::ContentIdentifier( aId );
+ m_aResults[ nIndex ]->xId = xId;
+ return xId;
+ }
+ return uno::Reference< ucb::XContentIdentifier >();
+}
+
+
+// virtual
+uno::Reference< ucb::XContent >
+HierarchyResultSetDataSupplier::queryContent( sal_uInt32 nIndex )
+{
+ std::unique_lock aGuard( m_aMutex );
+
+ if ( nIndex < m_aResults.size() )
+ {
+ uno::Reference< ucb::XContent > xContent
+ = m_aResults[ nIndex ]->xContent;
+ if ( xContent.is() )
+ {
+ // Already cached.
+ return xContent;
+ }
+ }
+
+ uno::Reference< ucb::XContentIdentifier > xId
+ = queryContentIdentifier( nIndex );
+ if ( xId.is() )
+ {
+ try
+ {
+ uno::Reference< ucb::XContent > xContent
+ = m_xContent->getProvider()->queryContent( xId );
+ m_aResults[ nIndex ]->xContent = xContent;
+ return xContent;
+
+ }
+ catch ( ucb::IllegalIdentifierException const & )
+ {
+ }
+ }
+ return uno::Reference< ucb::XContent >();
+}
+
+
+// virtual
+bool HierarchyResultSetDataSupplier::getResult( sal_uInt32 nIndex )
+{
+ std::unique_lock aGuard( m_aMutex );
+ return getResultImpl(aGuard, nIndex);
+}
+
+bool HierarchyResultSetDataSupplier::getResultImpl( std::unique_lock<std::mutex>& rGuard, sal_uInt32 nIndex )
+{
+ if ( m_aResults.size() > nIndex )
+ {
+ // Result already present.
+ return true;
+ }
+
+ // Result not (yet) present.
+
+ if ( m_bCountFinal )
+ return false;
+
+ // Try to obtain result...
+
+ sal_uInt32 nOldCount = m_aResults.size();
+ bool bFound = false;
+ sal_uInt32 nPos = nOldCount;
+
+ while ( m_aFolder.next( m_aIterator ) )
+ {
+ const HierarchyEntryData& rResult = *m_aIterator;
+ if ( checkResult( rResult ) )
+ {
+ m_aResults.emplace_back( new ResultListEntry( rResult ) );
+
+ if ( nPos == nIndex )
+ {
+ // Result obtained.
+ bFound = true;
+ break;
+ }
+ }
+ nPos++;
+ }
+
+ if ( !bFound )
+ m_bCountFinal = true;
+
+ rtl::Reference< ::ucbhelper::ResultSet > xResultSet = getResultSet();
+ if ( xResultSet.is() )
+ {
+ // Callbacks follow!
+ rGuard.unlock();
+
+ if ( nOldCount < m_aResults.size() )
+ xResultSet->rowCountChanged(
+ nOldCount, m_aResults.size() );
+
+ if ( m_bCountFinal )
+ xResultSet->rowCountFinal();
+
+ rGuard.lock();
+ }
+
+ return bFound;
+}
+
+
+// virtual
+sal_uInt32 HierarchyResultSetDataSupplier::totalCount()
+{
+ std::unique_lock aGuard( m_aMutex );
+
+ if ( m_bCountFinal )
+ return m_aResults.size();
+
+ sal_uInt32 nOldCount = m_aResults.size();
+
+ while ( m_aFolder.next( m_aIterator ) )
+ {
+ const HierarchyEntryData& rResult = *m_aIterator;
+ if ( checkResult( rResult ) )
+ m_aResults.emplace_back( new ResultListEntry( rResult ) );
+ }
+
+ m_bCountFinal = true;
+
+ rtl::Reference< ::ucbhelper::ResultSet > xResultSet = getResultSet();
+ if ( xResultSet.is() )
+ {
+ // Callbacks follow!
+ aGuard.unlock();
+
+ if ( nOldCount < m_aResults.size() )
+ xResultSet->rowCountChanged(
+ nOldCount, m_aResults.size() );
+
+ xResultSet->rowCountFinal();
+ }
+
+ return m_aResults.size();
+}
+
+
+// virtual
+sal_uInt32 HierarchyResultSetDataSupplier::currentCount()
+{
+ return m_aResults.size();
+}
+
+
+// virtual
+bool HierarchyResultSetDataSupplier::isCountFinal()
+{
+ return m_bCountFinal;
+}
+
+
+// virtual
+uno::Reference< sdbc::XRow >
+HierarchyResultSetDataSupplier::queryPropertyValues( sal_uInt32 nIndex )
+{
+ std::unique_lock aGuard( m_aMutex );
+
+ if ( nIndex < m_aResults.size() )
+ {
+ uno::Reference< sdbc::XRow > xRow
+ = m_aResults[ nIndex ]->xRow;
+ if ( xRow.is() )
+ {
+ // Already cached.
+ return xRow;
+ }
+ }
+
+ if ( getResultImpl( aGuard, nIndex ) )
+ {
+ HierarchyContentProperties aData(
+ m_aResults[ nIndex ]->aData );
+
+ uno::Reference< sdbc::XRow > xRow
+ = HierarchyContent::getPropertyValues(
+ m_xContext,
+ getResultSet()->getProperties(),
+ aData,
+ static_cast< HierarchyContentProvider * >(
+ m_xContent->getProvider().get() ),
+ queryContentIdentifierStringImpl( aGuard, nIndex ) );
+ m_aResults[ nIndex ]->xRow = xRow;
+ return xRow;
+ }
+
+ return uno::Reference< sdbc::XRow >();
+}
+
+
+// virtual
+void HierarchyResultSetDataSupplier::releasePropertyValues( sal_uInt32 nIndex )
+{
+ std::unique_lock aGuard( m_aMutex );
+
+ if ( nIndex < m_aResults.size() )
+ m_aResults[ nIndex ]->xRow.clear();
+}
+
+
+// virtual
+void HierarchyResultSetDataSupplier::close()
+{
+}
+
+
+// virtual
+void HierarchyResultSetDataSupplier::validate()
+{
+}
+
+
+bool HierarchyResultSetDataSupplier::checkResult(
+ const HierarchyEntryData& rResult )
+{
+ switch ( m_nOpenMode )
+ {
+ case ucb::OpenMode::FOLDERS:
+ if ( rResult.getType() == HierarchyEntryData::LINK )
+ {
+ // Entry is a link.
+ return false;
+ }
+ break;
+
+ case ucb::OpenMode::DOCUMENTS:
+ if ( rResult.getType() == HierarchyEntryData::FOLDER )
+ {
+ // Entry is a folder.
+ return false;
+ }
+ break;
+
+ case ucb::OpenMode::ALL:
+ default:
+ break;
+ }
+
+ return true;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/ucb/source/ucp/hierarchy/hierarchydatasupplier.hxx b/ucb/source/ucp/hierarchy/hierarchydatasupplier.hxx
new file mode 100644
index 0000000000..ee416e71c5
--- /dev/null
+++ b/ucb/source/ucp/hierarchy/hierarchydatasupplier.hxx
@@ -0,0 +1,93 @@
+/* -*- 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:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#pragma once
+
+#include <rtl/ref.hxx>
+#include <ucbhelper/resultset.hxx>
+#include <mutex>
+#include <utility>
+#include <vector>
+#include "hierarchydata.hxx"
+
+namespace hierarchy_ucp {
+
+class HierarchyEntryData;
+class HierarchyContent;
+
+class HierarchyResultSetDataSupplier :
+ public ::ucbhelper::ResultSetDataSupplier
+{
+private:
+ bool checkResult( const HierarchyEntryData& rResult );
+
+public:
+ HierarchyResultSetDataSupplier(
+ const css::uno::Reference< css::uno::XComponentContext >& rxContext,
+ const rtl::Reference< HierarchyContent >& rContent,
+ sal_Int32 nOpenMode );
+ virtual ~HierarchyResultSetDataSupplier() override;
+
+ virtual OUString queryContentIdentifierString( sal_uInt32 nIndex ) final 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 ) final 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;
+private:
+ OUString queryContentIdentifierStringImpl( std::unique_lock<std::mutex>&, sal_uInt32 nIndex );
+ bool getResultImpl( std::unique_lock<std::mutex>&, sal_uInt32 nIndex );
+
+ struct ResultListEntry
+ {
+ OUString aId;
+ css::uno::Reference< css::ucb::XContentIdentifier > xId;
+ css::uno::Reference< css::ucb::XContent > xContent;
+ css::uno::Reference< css::sdbc::XRow > xRow;
+ HierarchyEntryData aData;
+
+ explicit ResultListEntry( HierarchyEntryData aEntry ) : aData(std::move( aEntry )) {}
+ };
+ typedef std::vector< std::unique_ptr<ResultListEntry> > ResultList;
+ std::mutex m_aMutex;
+ ResultList m_aResults;
+ rtl::Reference< HierarchyContent > m_xContent;
+ css::uno::Reference< css::uno::XComponentContext > m_xContext;
+ HierarchyEntry m_aFolder;
+ HierarchyEntry::iterator m_aIterator;
+ sal_Int32 m_nOpenMode;
+ bool m_bCountFinal;
+};
+
+} // namespace hierarchy_ucp
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/ucb/source/ucp/hierarchy/hierarchyprovider.cxx b/ucb/source/ucp/hierarchy/hierarchyprovider.cxx
new file mode 100644
index 0000000000..ad019732a8
--- /dev/null
+++ b/ucb/source/ucp/hierarchy/hierarchyprovider.cxx
@@ -0,0 +1,244 @@
+/* -*- 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:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+
+/**************************************************************************
+ TODO
+ **************************************************************************
+
+ - XInitialization::initialize does not work any longer!
+
+ *************************************************************************/
+#include <osl/diagnose.h>
+#include <com/sun/star/beans/PropertyValue.hpp>
+#include <com/sun/star/container/XHierarchicalNameAccess.hpp>
+#include <com/sun/star/ucb/IllegalIdentifierException.hpp>
+#include <com/sun/star/util/theOfficeInstallationDirectories.hpp>
+#include <cppuhelper/queryinterface.hxx>
+#include <cppuhelper/weak.hxx>
+#include <ucbhelper/contentidentifier.hxx>
+#include <ucbhelper/macros.hxx>
+#include "hierarchyprovider.hxx"
+#include "hierarchycontent.hxx"
+#include "hierarchyuri.hxx"
+
+#include "../inc/urihelper.hxx"
+
+using namespace com::sun::star;
+using namespace hierarchy_ucp;
+
+
+// HierarchyContentProvider Implementation.
+
+
+HierarchyContentProvider::HierarchyContentProvider(
+ const uno::Reference< uno::XComponentContext >& rxContext )
+: HierarchyContentProvider_Base( rxContext )
+{
+}
+
+
+// virtual
+HierarchyContentProvider::~HierarchyContentProvider()
+{
+}
+
+// XServiceInfo methods.
+
+OUString SAL_CALL HierarchyContentProvider::getImplementationName() \
+{
+ return "com.sun.star.comp.ucb.HierarchyContentProvider";
+}
+sal_Bool SAL_CALL HierarchyContentProvider::supportsService( const OUString& ServiceName )
+{
+ return cppu::supportsService( this, ServiceName );
+}
+css::uno::Sequence< OUString > HierarchyContentProvider::getSupportedServiceNames()
+{
+ return { "com.sun.star.ucb.HierarchyContentProvider" };
+}
+
+// Service factory implementation.
+
+extern "C" SAL_DLLPUBLIC_EXPORT css::uno::XInterface*
+ucb_HierarchyContentProvider_get_implementation(
+ css::uno::XComponentContext* context , css::uno::Sequence<css::uno::Any> const&)
+{
+ return cppu::acquire(new HierarchyContentProvider(context));
+}
+
+// XContentProvider methods.
+
+
+// virtual
+uno::Reference< ucb::XContent > SAL_CALL
+HierarchyContentProvider::queryContent(
+ const uno::Reference< ucb::XContentIdentifier >& Identifier )
+{
+ HierarchyUri aUri( Identifier->getContentIdentifier() );
+ if ( !aUri.isValid() )
+ throw ucb::IllegalIdentifierException();
+
+ // Encode URL and create new Id. This may "correct" user-typed-in URL's.
+ uno::Reference< ucb::XContentIdentifier > xCanonicId
+ = new ::ucbhelper::ContentIdentifier( ::ucb_impl::urihelper::encodeURI( aUri.getUri() ) );
+ osl::MutexGuard aGuard( m_aMutex );
+
+ // Check, if a content with given id already exists...
+ uno::Reference< ucb::XContent > xContent
+ = queryExistingContent( xCanonicId );
+ if ( xContent.is() )
+ return xContent;
+
+ // Create a new content.
+ xContent = HierarchyContent::create( m_xContext, this, xCanonicId );
+ registerNewContent( xContent );
+
+ if ( xContent.is() && !xContent->getIdentifier().is() )
+ throw ucb::IllegalIdentifierException();
+
+ return xContent;
+}
+
+
+// XInitialization methods.
+
+
+// virtual
+void SAL_CALL HierarchyContentProvider::initialize(
+ const uno::Sequence< uno::Any >& aArguments )
+{
+ if ( aArguments.hasElements() )
+ OSL_FAIL( "HierarchyContentProvider::initialize : not supported!" );
+}
+
+
+// Non-interface methods.
+
+
+uno::Reference< lang::XMultiServiceFactory >
+HierarchyContentProvider::getConfigProvider(
+ const OUString & rServiceSpecifier )
+{
+ osl::MutexGuard aGuard( m_aMutex );
+ ConfigProviderMap::iterator it = m_aConfigProviderMap.find(
+ rServiceSpecifier );
+ if ( it == m_aConfigProviderMap.end() )
+ {
+ try
+ {
+ ConfigProviderMapEntry aEntry;
+ aEntry.xConfigProvider.set(
+ m_xContext->getServiceManager()->createInstanceWithContext(rServiceSpecifier, m_xContext),
+ uno::UNO_QUERY );
+
+ if ( aEntry.xConfigProvider.is() )
+ {
+ m_aConfigProviderMap[ rServiceSpecifier ] = aEntry;
+ return aEntry.xConfigProvider;
+ }
+ }
+ catch ( uno::Exception const & )
+ {
+// OSL_FAIL( // "HierarchyContentProvider::getConfigProvider - "
+// "caught exception!" );
+ }
+
+ OSL_FAIL( "HierarchyContentProvider::getConfigProvider - "
+ "No config provider!" );
+
+ return uno::Reference< lang::XMultiServiceFactory >();
+ }
+
+ return (*it).second.xConfigProvider;
+}
+
+uno::Reference< container::XHierarchicalNameAccess >
+HierarchyContentProvider::getRootConfigReadNameAccess(
+ const OUString & rServiceSpecifier )
+{
+ osl::MutexGuard aGuard( m_aMutex );
+ ConfigProviderMap::iterator it = m_aConfigProviderMap.find(
+ rServiceSpecifier );
+ if (it == m_aConfigProviderMap.end())
+ return uno::Reference< container::XHierarchicalNameAccess >();
+
+ if ( !( (*it).second.xRootReadAccess.is() ) )
+ {
+ if ( (*it).second.bTriedToGetRootReadAccess )
+ {
+ OSL_FAIL( "HierarchyContentProvider::getRootConfigReadNameAccess - "
+ "Unable to read any config data! -> #82494#" );
+ return uno::Reference< container::XHierarchicalNameAccess >();
+ }
+
+ try
+ {
+ uno::Reference< lang::XMultiServiceFactory > xConfigProv
+ = getConfigProvider( rServiceSpecifier );
+
+ if ( xConfigProv.is() )
+ {
+ beans::PropertyValue aProperty;
+ aProperty.Name = "nodepath" ;
+ aProperty.Value <<= OUString(); // root path
+ uno::Sequence< uno::Any > aArguments{ uno::Any(aProperty) };
+
+ (*it).second.bTriedToGetRootReadAccess = true;
+
+ (*it).second.xRootReadAccess.set(
+ xConfigProv->createInstanceWithArguments(
+ "com.sun.star.ucb.HierarchyDataReadAccess",
+ aArguments ),
+ uno::UNO_QUERY );
+ }
+ }
+ catch ( uno::RuntimeException const & )
+ {
+ throw;
+ }
+ catch ( uno::Exception const & )
+ {
+ // createInstance, createInstanceWithArguments
+
+ OSL_FAIL( "HierarchyContentProvider::getRootConfigReadNameAccess - "
+ "caught Exception!" );
+ }
+ }
+
+ return (*it).second.xRootReadAccess;
+}
+
+uno::Reference< util::XOfficeInstallationDirectories >
+HierarchyContentProvider::getOfficeInstallationDirectories()
+{
+ if ( !m_xOfficeInstDirs.is() )
+ {
+ osl::MutexGuard aGuard( m_aMutex );
+ if ( !m_xOfficeInstDirs.is() )
+ {
+ OSL_ENSURE( m_xContext.is(), "No service manager!" );
+
+ m_xOfficeInstDirs = util::theOfficeInstallationDirectories::get(m_xContext);
+ }
+ }
+ return m_xOfficeInstDirs;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/ucb/source/ucp/hierarchy/hierarchyprovider.hxx b/ucb/source/ucp/hierarchy/hierarchyprovider.hxx
new file mode 100644
index 0000000000..f32a2c6925
--- /dev/null
+++ b/ucb/source/ucp/hierarchy/hierarchyprovider.hxx
@@ -0,0 +1,101 @@
+/* -*- 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:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#pragma once
+
+#include <ucbhelper/providerhelper.hxx>
+#include <com/sun/star/lang/XInitialization.hpp>
+#include <com/sun/star/lang/XMultiServiceFactory.hpp>
+#include <unordered_map>
+
+namespace com::sun::star {
+ namespace container {
+ class XHierarchicalNameAccess;
+ }
+ namespace util {
+ class XOfficeInstallationDirectories;
+ }
+}
+
+namespace hierarchy_ucp {
+
+
+#define HIERARCHY_URL_SCHEME \
+ "vnd.sun.star.hier"
+#define HIERARCHY_URL_SCHEME_LENGTH 17
+
+inline constexpr OUString HIERARCHY_FOLDER_CONTENT_TYPE =
+ u"application/" HIERARCHY_URL_SCHEME "-folder"_ustr;
+inline constexpr OUString HIERARCHY_LINK_CONTENT_TYPE =
+ u"application/" HIERARCHY_URL_SCHEME "-link"_ustr;
+
+struct ConfigProviderMapEntry
+{
+ css::uno::Reference< css::lang::XMultiServiceFactory > xConfigProvider;
+ css::uno::Reference< css::container::XHierarchicalNameAccess > xRootReadAccess;
+ bool bTriedToGetRootReadAccess;
+
+ ConfigProviderMapEntry() : bTriedToGetRootReadAccess( false ) {}
+};
+
+typedef std::unordered_map
+<
+ OUString, // service specifier
+ ConfigProviderMapEntry
+>
+ConfigProviderMap;
+
+typedef cppu::ImplInheritanceHelper< ::ucbhelper::ContentProviderImplHelper, css::lang::XInitialization> HierarchyContentProvider_Base;
+class HierarchyContentProvider : public HierarchyContentProvider_Base
+{
+ ConfigProviderMap m_aConfigProviderMap;
+ css::uno::Reference< css::util::XOfficeInstallationDirectories > m_xOfficeInstDirs;
+
+public:
+ explicit HierarchyContentProvider(
+ const css::uno::Reference< css::uno::XComponentContext >& rxContext );
+ virtual ~HierarchyContentProvider() 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;
+
+ // XInitialization
+ virtual void SAL_CALL
+ initialize( const css::uno::Sequence< css::uno::Any >& aArguments ) override;
+
+ // Non-Interface methods
+ css::uno::Reference< css::lang::XMultiServiceFactory >
+ getConfigProvider( const OUString & rServiceSpecifier );
+ css::uno::Reference< css::container::XHierarchicalNameAccess >
+ getRootConfigReadNameAccess( const OUString & rServiceSpecifier );
+
+ // Note: may return an empty reference.
+ css::uno::Reference< css::util::XOfficeInstallationDirectories >
+ getOfficeInstallationDirectories();
+};
+
+} // namespace hierarchy_ucp
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/ucb/source/ucp/hierarchy/hierarchyuri.cxx b/ucb/source/ucp/hierarchy/hierarchyuri.cxx
new file mode 100644
index 0000000000..b637fd85f9
--- /dev/null
+++ b/ucb/source/ucp/hierarchy/hierarchyuri.cxx
@@ -0,0 +1,178 @@
+/* -*- 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:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+
+/**************************************************************************
+ TODO
+ **************************************************************************
+
+ *************************************************************************/
+
+#include "hierarchyuri.hxx"
+
+using namespace hierarchy_ucp;
+
+constexpr OUString HIERARCHY_URL_SCHEME = u"vnd.sun.star.hier"_ustr;
+constexpr OUString DEFAULT_DATA_SOURCE_SERVICE =
+ u"com.sun.star.ucb.DefaultHierarchyDataSource"_ustr;
+
+
+// HierarchyUri Implementation.
+
+
+void HierarchyUri::init() const
+{
+ // Already inited?
+ if ( m_aUri.isEmpty() || !m_aPath.isEmpty() )
+ return;
+
+ // Note: Maybe it's a re-init, setUri only resets m_aPath!
+ m_aService.clear();
+ m_aParentUri.clear();
+
+ // URI must match at least: <scheme>:
+ if ( m_aUri.getLength() < HIERARCHY_URL_SCHEME.getLength() + 1 )
+ {
+ // error, but remember that we did an init().
+ m_aPath = "/";
+ return;
+ }
+
+ // Scheme is case insensitive.
+ OUString aScheme
+ = m_aUri.copy( 0, HIERARCHY_URL_SCHEME.getLength() ).toAsciiLowerCase();
+ if ( aScheme == HIERARCHY_URL_SCHEME )
+ {
+ m_aUri = m_aUri.replaceAt( 0, aScheme.getLength(), aScheme );
+
+ sal_Int32 nPos = 0;
+
+ // If the URI has no service specifier, insert default service.
+ // This is for backward compatibility and for convenience.
+
+ if ( m_aUri.getLength() == HIERARCHY_URL_SCHEME.getLength() + 1 )
+ {
+ // root folder URI without path and service specifier.
+ m_aUri += "//" + DEFAULT_DATA_SOURCE_SERVICE + "/";
+ m_aService = DEFAULT_DATA_SOURCE_SERVICE ;
+
+ nPos = m_aUri.getLength() - 1;
+ }
+ else if ( ( m_aUri.getLength() == HIERARCHY_URL_SCHEME.getLength() + 2 )
+ &&
+ ( m_aUri[ HIERARCHY_URL_SCHEME.getLength() + 1 ] == '/' ) )
+ {
+ // root folder URI without service specifier.
+ m_aUri += "/" + DEFAULT_DATA_SOURCE_SERVICE + "/";
+ m_aService = DEFAULT_DATA_SOURCE_SERVICE;
+
+ nPos = m_aUri.getLength() - 1;
+ }
+ else if ( ( m_aUri.getLength() > HIERARCHY_URL_SCHEME.getLength() + 2 )
+ &&
+ ( m_aUri[ HIERARCHY_URL_SCHEME.getLength() + 2 ] != '/' ) )
+ {
+ // other (no root folder) URI without service specifier.
+ m_aUri = m_aUri.replaceAt(
+ HIERARCHY_URL_SCHEME.getLength() + 2,
+ 0,
+ rtl::Concat2View("/" + DEFAULT_DATA_SOURCE_SERVICE + "/") );
+ m_aService = DEFAULT_DATA_SOURCE_SERVICE;
+
+ nPos
+ = HIERARCHY_URL_SCHEME.getLength() + 3 + m_aService.getLength();
+ }
+ else
+ {
+ // URI with service specifier.
+ sal_Int32 nStart = HIERARCHY_URL_SCHEME.getLength() + 3;
+
+ // Here: - m_aUri has at least the form "<scheme>://"
+ // - nStart points to char after <scheme>:
+
+ // Only <scheme>:// ?
+ if ( nStart == m_aUri.getLength() )
+ {
+ // error, but remember that we did an init().
+ m_aPath = "/";
+ return;
+ }
+
+ // Empty path segments?
+ if ( m_aUri.indexOf("//", nStart) != -1 )
+ {
+ // error, but remember that we did an init().
+ m_aPath = "/";
+ return;
+ }
+
+ sal_Int32 nEnd = m_aUri.indexOf( '/', nStart );
+
+ // Only <scheme>:/// ?
+ if ( nEnd == nStart )
+ {
+ // error, but remember that we did an init().
+ m_aPath = "/";
+ return;
+ }
+
+ if ( nEnd == -1 )
+ {
+ // Trailing slash missing.
+ nEnd = m_aUri.getLength();
+ m_aUri += "/";
+ }
+
+ m_aService = m_aUri.copy( nStart, nEnd - nStart );
+
+ nPos = nEnd;
+ }
+
+ // Here: - m_aUri has at least the form "<scheme>://<service>/"
+ // - m_aService was set
+ // - m_aPath, m_aParentPath, m_aName not yet set
+ // - nPos points to slash after service specifier
+
+ // Remove trailing slash, if not a root folder URI.
+ sal_Int32 nEnd = m_aUri.lastIndexOf( '/' );
+ if ( ( nEnd > nPos ) && ( nEnd == ( m_aUri.getLength() - 1 ) ) )
+ m_aUri = m_aUri.copy( 0, nEnd );
+
+ // Path (includes leading slash)
+ m_aPath = m_aUri.copy( nPos );
+
+ // parent URI + name
+ sal_Int32 nLastSlash = m_aUri.lastIndexOf( '/' );
+ if ( ( nLastSlash != -1 ) &&
+ ( nLastSlash != m_aUri.getLength() - 1 ) ) // root
+ {
+ m_aParentUri = m_aUri.copy( 0, nLastSlash );
+ }
+
+ // success
+ m_bValid = true;
+ }
+ else
+ {
+ // error, but remember that we did an init().
+ m_aPath = "/";
+ }
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/ucb/source/ucp/hierarchy/hierarchyuri.hxx b/ucb/source/ucp/hierarchy/hierarchyuri.hxx
new file mode 100644
index 0000000000..c49730d82f
--- /dev/null
+++ b/ucb/source/ucp/hierarchy/hierarchyuri.hxx
@@ -0,0 +1,67 @@
+/* -*- 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:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#pragma once
+
+#include <rtl/ustring.hxx>
+
+namespace hierarchy_ucp {
+
+
+class HierarchyUri
+{
+ mutable OUString m_aUri;
+ mutable OUString m_aParentUri;
+ mutable OUString m_aService;
+ mutable OUString m_aPath;
+ mutable bool m_bValid;
+
+private:
+ void init() const;
+
+public:
+ explicit HierarchyUri( OUString aUri )
+ : m_aUri( std::move(aUri) ), m_bValid( false ) {}
+
+ bool isValid() const
+ { init(); return m_bValid; }
+
+ const OUString & getUri() const
+ { init(); return m_aUri; }
+
+ const OUString & getParentUri() const
+ { init(); return m_aParentUri; }
+
+ const OUString & getService() const
+ { init(); return m_aService; }
+
+ const OUString & getPath() const
+ { init(); return m_aPath; }
+
+ inline bool isRootFolder() const;
+};
+
+inline bool HierarchyUri::isRootFolder() const
+{
+ init();
+ return m_aPath == "/";
+}
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/ucb/source/ucp/hierarchy/ucphier1.component b/ucb/source/ucp/hierarchy/ucphier1.component
new file mode 100644
index 0000000000..e403857c72
--- /dev/null
+++ b/ucb/source/ucp/hierarchy/ucphier1.component
@@ -0,0 +1,31 @@
+<?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/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-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.ucb.HierarchyContentProvider"
+ constructor="ucb_HierarchyContentProvider_get_implementation" single-instance="true">
+ <service name="com.sun.star.ucb.HierarchyContentProvider"/>
+ </implementation>
+ <implementation name="com.sun.star.comp.ucb.HierarchyDataSource"
+ constructor="ucb_HierarchyDataSource_get_implementation" single-instance="true">
+ <service name="com.sun.star.ucb.DefaultHierarchyDataSource"/>
+ <service name="com.sun.star.ucb.HierarchyDataSource"/>
+ </implementation>
+</component>
diff --git a/ucb/source/ucp/image/ucpimage.component b/ucb/source/ucp/image/ucpimage.component
new file mode 100644
index 0000000000..6dba0ddc21
--- /dev/null
+++ b/ucb/source/ucp/image/ucpimage.component
@@ -0,0 +1,17 @@
+<?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.ucb.ImageContentProvider"
+ constructor="com_sun_star_comp_ucb_ImageContentProvider_get_implementation">
+ <service name="com.sun.star.ucb.ImageContentProvider"/>
+ </implementation>
+</component>
diff --git a/ucb/source/ucp/image/ucpimage.cxx b/ucb/source/ucp/image/ucpimage.cxx
new file mode 100644
index 0000000000..acdb030934
--- /dev/null
+++ b/ucb/source/ucp/image/ucpimage.cxx
@@ -0,0 +1,165 @@
+/* -*- 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 <cassert>
+
+#include <com/sun/star/lang/XServiceInfo.hpp>
+#include <com/sun/star/ucb/IllegalIdentifierException.hpp>
+#include <com/sun/star/ucb/XContentProvider.hpp>
+#include <com/sun/star/uno/XComponentContext.hpp>
+#include <com/sun/star/uri/UriReferenceFactory.hpp>
+#include <comphelper/compbase.hxx>
+#include <cppuhelper/supportsservice.hxx>
+#include <rtl/uri.hxx>
+#include <rtl/ustrbuf.hxx>
+#include <utility>
+#include <vcl/ImageTree.hxx>
+#include <vcl/svapp.hxx>
+#include <ucbhelper/content.hxx>
+
+// A LO-private ("implementation detail") UCP used to access images from help
+// content, with theme fallback and localization support as provided by VCL's
+// ImageTree.
+//
+// The URL scheme is
+//
+// "vnd.libreoffice.image://"<theme><path>["?lang="<language-tag>]
+
+namespace {
+
+class Provider final:
+ public comphelper::WeakComponentImplHelper<
+ css::lang::XServiceInfo, css::ucb::XContentProvider>
+{
+public:
+ explicit Provider(
+ css::uno::Reference<css::uno::XComponentContext> context):
+ context_(std::move(context))
+ {}
+
+private:
+ OUString SAL_CALL getImplementationName() override
+ { return "com.sun.star.comp.ucb.ImageContentProvider"; }
+
+ sal_Bool SAL_CALL supportsService(OUString const & ServiceName) override
+ { return cppu::supportsService(this, ServiceName); }
+
+ css::uno::Sequence<OUString> SAL_CALL getSupportedServiceNames() override
+ {
+ return css::uno::Sequence<OUString>{
+ "com.sun.star.ucb.ImageContentProvider"};
+ }
+
+ css::uno::Reference<css::ucb::XContent> SAL_CALL queryContent(
+ css::uno::Reference<css::ucb::XContentIdentifier> const & Identifier)
+ override
+ {
+ css::uno::Reference<css::uno::XComponentContext> context;
+ {
+ std::unique_lock g(m_aMutex);
+ context = context_;
+ }
+ if (!context.is()) {
+ throw css::lang::DisposedException();
+ }
+ auto url(Identifier->getContentIdentifier());
+ auto uri(css::uri::UriReferenceFactory::create(context)->parse(url));
+ if (!(uri.is()
+ && uri->getScheme().equalsIgnoreAsciiCase(
+ "vnd.libreoffice.image")))
+ {
+ throw css::ucb::IllegalIdentifierException(url);
+ }
+ auto auth(
+ rtl::Uri::decode(
+ uri->getAuthority(), rtl_UriDecodeStrict,
+ RTL_TEXTENCODING_UTF8));
+ if (auth.isEmpty()) {
+ throw css::ucb::IllegalIdentifierException(url);
+ }
+ auto path(uri->getPath());
+ if (path.isEmpty()) {
+ throw css::ucb::IllegalIdentifierException(url);
+ }
+ OUStringBuffer buf;
+ assert(path[0] == '/');
+ for (sal_Int32 i = 1;;) {
+ auto j = path.indexOf('/', i);
+ if (j == -1) {
+ j = path.getLength();
+ }
+ auto seg(
+ rtl::Uri::decode(
+ path.copy(i, j - i), rtl_UriDecodeStrict,
+ RTL_TEXTENCODING_UTF8));
+ if (seg.isEmpty()) {
+ throw css::ucb::IllegalIdentifierException(url);
+ }
+ if (i != 1) {
+ buf.append('/');
+ }
+ buf.append(seg);
+ if (j == path.getLength()) {
+ break;
+ }
+ i = j + 1;
+ }
+ auto decPath(buf.makeStringAndClear());
+ OUString lang;
+ if (uri->hasQuery()) {
+ if (!uri->getQuery().startsWith("lang=", &lang)) {
+ throw css::ucb::IllegalIdentifierException(url);
+ }
+ lang = rtl::Uri::decode(
+ lang, rtl_UriDecodeStrict, RTL_TEXTENCODING_UTF8);
+ if (lang.isEmpty()) {
+ throw css::ucb::IllegalIdentifierException(url);
+ }
+ }
+ OUString newUrl;
+ {
+ SolarMutexGuard g;
+ newUrl = ImageTree::get().getImageUrl(decPath, auth, lang);
+ }
+ ucbhelper::Content content;
+ return
+ ucbhelper::Content::create(
+ newUrl, css::uno::Reference<css::ucb::XCommandEnvironment>(),
+ context, content)
+ ? content.get() : css::uno::Reference<css::ucb::XContent>();
+ }
+
+ sal_Int32 SAL_CALL compareContentIds(
+ css::uno::Reference<css::ucb::XContentIdentifier> const & Id1,
+ css::uno::Reference<css::ucb::XContentIdentifier> const & Id2) override
+ {
+ return Id1->getContentIdentifier().compareTo(
+ Id2->getContentIdentifier());
+ }
+
+ void disposing(std::unique_lock<std::mutex>&) override {
+ context_.clear();
+ }
+
+ css::uno::Reference<css::uno::XComponentContext> context_;
+};
+
+}
+
+extern "C" SAL_DLLPUBLIC_EXPORT css::uno::XInterface *
+com_sun_star_comp_ucb_ImageContentProvider_get_implementation(
+ css::uno::XComponentContext * context,
+ css::uno::Sequence<css::uno::Any> const &)
+{
+ return cppu::acquire(new Provider(context));
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/ucb/source/ucp/inc/urihelper.hxx b/ucb/source/ucp/inc/urihelper.hxx
new file mode 100644
index 0000000000..fa9c7c9181
--- /dev/null
+++ b/ucb/source/ucp/inc/urihelper.hxx
@@ -0,0 +1,107 @@
+/* -*- 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:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#pragma once
+
+#include <rtl/ustring.hxx>
+#include <rtl/ustrbuf.hxx>
+#include <rtl/uri.hxx>
+
+
+namespace ucb_impl::urihelper {
+
+ inline OUString encodeSegment( const OUString & rSegment )
+ {
+ return rtl::Uri::encode( rSegment,
+ rtl_UriCharClassPchar,
+ rtl_UriEncodeIgnoreEscapes,
+ RTL_TEXTENCODING_UTF8 );
+ }
+
+ inline OUString decodeSegment( const OUString& rSegment )
+ {
+ return rtl::Uri::decode( rSegment,
+ rtl_UriDecodeWithCharset,
+ RTL_TEXTENCODING_UTF8 );
+ }
+
+ inline OUString encodeURI( const OUString & rURI )
+ {
+ OUString aFragment;
+ OUString aParams;
+ OUString aURI;
+
+ sal_Int32 nFragment = rURI.lastIndexOf( u'#' );
+ if ( nFragment != -1 )
+ aFragment = rURI.copy( nFragment + 1 );
+
+ sal_Int32 nParams = ( nFragment == -1 )
+ ? rURI.lastIndexOf( u'?' )
+ : rURI.lastIndexOf( u'?', nFragment );
+ if ( nParams != -1 )
+ aParams = ( nFragment == -1 )
+ ? rURI.copy( nParams + 1 )
+ : rURI.copy( nParams + 1, nFragment - nParams - 1 );
+
+ aURI = ( nParams != -1 )
+ ? rURI.copy( 0, nParams )
+ : ( nFragment != -1 )
+ ? rURI.copy( 0, nFragment )
+ : rURI;
+
+ if ( aFragment.getLength() > 1 )
+ aFragment =
+ rtl::Uri::encode( aFragment,
+ rtl_UriCharClassUric,
+ rtl_UriEncodeKeepEscapes, /* #i81690# */
+ RTL_TEXTENCODING_UTF8 );
+
+ if ( aParams.getLength() > 1 )
+ aParams =
+ rtl::Uri::encode( aParams,
+ rtl_UriCharClassUric,
+ rtl_UriEncodeKeepEscapes, /* #i81690# */
+ RTL_TEXTENCODING_UTF8 );
+
+ OUStringBuffer aResult(256);
+ sal_Int32 nIndex = 0;
+ do
+ {
+ aResult.append(
+ rtl::Uri::encode( aURI.getToken( 0, '/', nIndex ),
+ rtl_UriCharClassPchar,
+ rtl_UriEncodeKeepEscapes, /* #i81690# */
+ RTL_TEXTENCODING_UTF8 ) );
+ if ( nIndex >= 0 )
+ aResult.append( u'/' );
+ }
+ while ( nIndex >= 0 );
+
+ if ( !aParams.isEmpty() )
+ aResult.append( u"?" + aParams );
+
+ if ( !aFragment.isEmpty() )
+ aResult.append( u"#" + aFragment );
+
+ return aResult.makeStringAndClear();
+ }
+
+} // namespace
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/ucb/source/ucp/package/pkgcontent.cxx b/ucb/source/ucp/package/pkgcontent.cxx
new file mode 100644
index 0000000000..de7b9c16ed
--- /dev/null
+++ b/ucb/source/ucp/package/pkgcontent.cxx
@@ -0,0 +1,2689 @@
+/* -*- 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:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+
+/**************************************************************************
+ TODO
+ **************************************************************************
+ *************************************************************************/
+#include <sal/config.h>
+
+#include <string_view>
+
+#include <osl/diagnose.h>
+
+#include <rtl/ustring.hxx>
+#include <com/sun/star/beans/IllegalTypeException.hpp>
+#include <com/sun/star/beans/PropertyAttribute.hpp>
+#include <com/sun/star/beans/PropertyExistException.hpp>
+#include <com/sun/star/beans/PropertyState.hpp>
+#include <com/sun/star/container/XEnumerationAccess.hpp>
+#include <com/sun/star/container/XHierarchicalNameAccess.hpp>
+#include <com/sun/star/container/XNameContainer.hpp>
+#include <com/sun/star/container/XNamed.hpp>
+#include <com/sun/star/io/BufferSizeExceededException.hpp>
+#include <com/sun/star/io/NotConnectedException.hpp>
+#include <com/sun/star/io/XActiveDataSink.hpp>
+#include <com/sun/star/io/XOutputStream.hpp>
+#include <com/sun/star/lang/IllegalAccessException.hpp>
+#include <com/sun/star/ucb/ContentInfoAttribute.hpp>
+#include <com/sun/star/ucb/IllegalIdentifierException.hpp>
+#include <com/sun/star/ucb/InsertCommandArgument.hpp>
+#include <com/sun/star/ucb/InteractiveBadTransferURLException.hpp>
+#include <com/sun/star/ucb/MissingInputStreamException.hpp>
+#include <com/sun/star/ucb/NameClash.hpp>
+#include <com/sun/star/ucb/NameClashException.hpp>
+#include <com/sun/star/ucb/OpenCommandArgument2.hpp>
+#include <com/sun/star/ucb/OpenMode.hpp>
+#include <com/sun/star/ucb/TransferInfo.hpp>
+#include <com/sun/star/ucb/UnsupportedCommandException.hpp>
+#include <com/sun/star/ucb/UnsupportedDataSinkException.hpp>
+#include <com/sun/star/ucb/UnsupportedNameClashException.hpp>
+#include <com/sun/star/ucb/UnsupportedOpenModeException.hpp>
+#include <com/sun/star/ucb/XCommandInfo.hpp>
+#include <com/sun/star/ucb/XPersistentPropertySet.hpp>
+#include <com/sun/star/util/XChangesBatch.hpp>
+#include <com/sun/star/lang/XSingleServiceFactory.hpp>
+#include <com/sun/star/uno/Any.hxx>
+#include <com/sun/star/uno/Sequence.hxx>
+#include <comphelper/propertysequence.hxx>
+#include <cppuhelper/queryinterface.hxx>
+#include <ucbhelper/contentidentifier.hxx>
+#include <ucbhelper/propertyvalueset.hxx>
+#include <ucbhelper/cancelcommandexecution.hxx>
+#include <ucbhelper/macros.hxx>
+#include <utility>
+#include "pkgcontent.hxx"
+#include "pkgprovider.hxx"
+#include "pkgresultset.hxx"
+
+#include "../inc/urihelper.hxx"
+
+using namespace com::sun::star;
+using namespace package_ucp;
+
+#define NONE_MODIFIED sal_uInt32( 0x00 )
+#define MEDIATYPE_MODIFIED sal_uInt32( 0x01 )
+#define COMPRESSED_MODIFIED sal_uInt32( 0x02 )
+#define ENCRYPTED_MODIFIED sal_uInt32( 0x04 )
+#define ENCRYPTIONKEY_MODIFIED sal_uInt32( 0x08 )
+
+
+// ContentProperties Implementation.
+
+
+ContentProperties::ContentProperties( const OUString& rContentType )
+: aContentType( rContentType ),
+ nSize( 0 ),
+ bCompressed( true ),
+ bEncrypted( false ),
+ bHasEncryptedEntries( false )
+{
+ bIsFolder = rContentType == PACKAGE_FOLDER_CONTENT_TYPE || rContentType == PACKAGE_ZIP_FOLDER_CONTENT_TYPE;
+ bIsDocument = !bIsFolder;
+
+ OSL_ENSURE( bIsFolder || rContentType == PACKAGE_STREAM_CONTENT_TYPE || rContentType == PACKAGE_ZIP_STREAM_CONTENT_TYPE,
+ "ContentProperties::ContentProperties - Unknown type!" );
+}
+
+
+uno::Sequence< ucb::ContentInfo >
+ContentProperties::getCreatableContentsInfo( PackageUri const & rUri ) const
+{
+ if ( bIsFolder )
+ {
+ uno::Sequence< beans::Property > aProps( 1 );
+ aProps.getArray()[ 0 ] = beans::Property(
+ "Title",
+ -1,
+ cppu::UnoType<OUString>::get(),
+ beans::PropertyAttribute::BOUND );
+
+ uno::Sequence< ucb::ContentInfo > aSeq( 2 );
+
+ // Folder.
+ aSeq.getArray()[ 0 ].Type
+ = Content::getContentType( rUri.getScheme(), true );
+ aSeq.getArray()[ 0 ].Attributes
+ = ucb::ContentInfoAttribute::KIND_FOLDER;
+ aSeq.getArray()[ 0 ].Properties = aProps;
+
+ // Stream.
+ aSeq.getArray()[ 1 ].Type
+ = Content::getContentType( rUri.getScheme(), false );
+ aSeq.getArray()[ 1 ].Attributes
+ = ucb::ContentInfoAttribute::INSERT_WITH_INPUTSTREAM
+ | ucb::ContentInfoAttribute::KIND_DOCUMENT;
+ aSeq.getArray()[ 1 ].Properties = aProps;
+
+ return aSeq;
+ }
+ else
+ {
+ return uno::Sequence< ucb::ContentInfo >( 0 );
+ }
+}
+
+
+// Content Implementation.
+
+
+// static ( "virtual" ctor )
+rtl::Reference<Content> Content::create(
+ const uno::Reference< uno::XComponentContext >& rxContext,
+ ContentProvider* pProvider,
+ const uno::Reference< ucb::XContentIdentifier >& Identifier )
+{
+ OUString aURL = Identifier->getContentIdentifier();
+ PackageUri aURI( aURL );
+ ContentProperties aProps;
+ uno::Reference< container::XHierarchicalNameAccess > xPackage;
+
+ if ( loadData( pProvider, aURI, aProps, xPackage ) )
+ {
+ // resource exists
+
+ sal_Int32 nLastSlash = aURL.lastIndexOf( '/' );
+ if ( ( nLastSlash + 1 ) == aURL.getLength() )
+ {
+ // Client explicitly requested a folder!
+ if ( !aProps.bIsFolder )
+ return nullptr;
+ }
+
+ uno::Reference< ucb::XContentIdentifier > xId
+ = new ::ucbhelper::ContentIdentifier( aURI.getUri() );
+ return new Content( rxContext, pProvider, xId, xPackage, aURI, std::move(aProps) );
+ }
+ else
+ {
+ // resource doesn't exist
+
+ bool bFolder = false;
+
+ // Guess type according to URI.
+ sal_Int32 nLastSlash = aURL.lastIndexOf( '/' );
+ if ( ( nLastSlash + 1 ) == aURL.getLength() )
+ bFolder = true;
+
+ uno::Reference< ucb::XContentIdentifier > xId
+ = new ::ucbhelper::ContentIdentifier( aURI.getUri() );
+
+ ucb::ContentInfo aInfo;
+ if ( bFolder || aURI.isRootFolder() )
+ aInfo.Type = getContentType( aURI.getScheme(), true );
+ else
+ aInfo.Type = getContentType( aURI.getScheme(), false );
+
+ return new Content( rxContext, pProvider, xId, xPackage, aURI, aInfo );
+ }
+}
+
+
+// static ( "virtual" ctor )
+rtl::Reference<Content> Content::create(
+ const uno::Reference< uno::XComponentContext >& rxContext,
+ ContentProvider* pProvider,
+ const uno::Reference< ucb::XContentIdentifier >& Identifier,
+ const ucb::ContentInfo& Info )
+{
+ if ( Info.Type.isEmpty() )
+ return nullptr;
+
+ PackageUri aURI( Identifier->getContentIdentifier() );
+
+ if ( !Info.Type.equalsIgnoreAsciiCase(
+ getContentType( aURI.getScheme(), true ) ) &&
+ !Info.Type.equalsIgnoreAsciiCase(
+ getContentType( aURI.getScheme(), false ) ) )
+ return nullptr;
+
+ uno::Reference< container::XHierarchicalNameAccess > xPackage = pProvider->createPackage( aURI );
+
+ uno::Reference< ucb::XContentIdentifier > xId
+ = new ::ucbhelper::ContentIdentifier( aURI.getUri() );
+ return new Content( rxContext, pProvider, xId, xPackage, std::move(aURI), Info );
+}
+
+
+// static
+OUString Content::getContentType(
+ std::u16string_view aScheme, bool bFolder )
+{
+ return ( OUString::Concat("application/")
+ + aScheme
+ + ( bFolder
+ ? std::u16string_view(u"-folder")
+ : std::u16string_view(u"-stream") ) );
+}
+
+
+Content::Content(
+ const uno::Reference< uno::XComponentContext >& rxContext,
+ ContentProvider* pProvider,
+ const uno::Reference< ucb::XContentIdentifier >& Identifier,
+ uno::Reference< container::XHierarchicalNameAccess > Package,
+ PackageUri aUri,
+ ContentProperties aProps )
+: ContentImplHelper( rxContext, pProvider, Identifier ),
+ m_aUri(std::move( aUri )),
+ m_aProps(std::move( aProps )),
+ m_eState( PERSISTENT ),
+ m_xPackage(std::move( Package )),
+ m_pProvider( pProvider ),
+ m_nModifiedProps( NONE_MODIFIED )
+{
+}
+
+
+Content::Content(
+ const uno::Reference< uno::XComponentContext >& rxContext,
+ ContentProvider* pProvider,
+ const uno::Reference< ucb::XContentIdentifier >& Identifier,
+ uno::Reference< container::XHierarchicalNameAccess > Package,
+ PackageUri aUri,
+ const ucb::ContentInfo& Info )
+ : ContentImplHelper( rxContext, pProvider, Identifier ),
+ m_aUri(std::move( aUri )),
+ m_aProps( Info.Type ),
+ m_eState( TRANSIENT ),
+ m_xPackage(std::move( Package )),
+ m_pProvider( pProvider ),
+ m_nModifiedProps( NONE_MODIFIED )
+{
+}
+
+
+// virtual
+Content::~Content()
+{
+}
+
+
+// XInterface methods.
+
+
+// virtual
+void SAL_CALL Content::acquire()
+ noexcept
+{
+ ContentImplHelper::acquire();
+}
+
+
+// virtual
+void SAL_CALL Content::release()
+ noexcept
+{
+ ContentImplHelper::release();
+}
+
+
+// virtual
+uno::Any SAL_CALL Content::queryInterface( const uno::Type & rType )
+{
+ uno::Any aRet;
+
+ if ( isFolder() )
+ aRet = cppu::queryInterface(
+ rType, static_cast< ucb::XContentCreator * >( this ) );
+
+ return aRet.hasValue() ? aRet : ContentImplHelper::queryInterface( rType );
+}
+
+
+// XTypeProvider methods.
+
+
+XTYPEPROVIDER_COMMON_IMPL( Content );
+
+
+// virtual
+uno::Sequence< uno::Type > SAL_CALL Content::getTypes()
+{
+ if ( isFolder() )
+ {
+ static cppu::OTypeCollection s_aFolderTypes(
+ 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_aFolderTypes.getTypes();
+
+ }
+ else
+ {
+ static cppu::OTypeCollection s_aDocumentTypes(
+ 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_aDocumentTypes.getTypes();
+ }
+}
+
+
+// XServiceInfo methods.
+
+
+// virtual
+OUString SAL_CALL Content::getImplementationName()
+{
+ return "com.sun.star.comp.ucb.PackageContent";
+}
+
+
+// virtual
+uno::Sequence< OUString > SAL_CALL Content::getSupportedServiceNames()
+{
+ return { isFolder()? OUString("com.sun.star.ucb.PackageFolderContent"):OUString("com.sun.star.ucb.PackageStreamContent") } ;
+}
+
+
+// XContent methods.
+
+
+// virtual
+OUString SAL_CALL Content::getContentType()
+{
+ return m_aProps.aContentType;
+}
+
+
+// XCommandProcessor methods.
+
+
+// virtual
+uno::Any SAL_CALL Content::execute(
+ const ucb::Command& aCommand,
+ sal_Int32 /*CommandId*/,
+ const uno::Reference< ucb::XCommandEnvironment >& Environment )
+{
+ uno::Any aRet;
+
+ if ( aCommand.Name == "getPropertyValues" )
+ {
+
+ // getPropertyValues
+
+
+ uno::Sequence< beans::Property > Properties;
+ if ( !( aCommand.Argument >>= Properties ) )
+ {
+ ucbhelper::cancelCommandExecution(
+ uno::Any( lang::IllegalArgumentException(
+ "Wrong argument type!",
+ getXWeak(),
+ -1 ) ),
+ Environment );
+ // Unreachable
+ }
+
+ aRet <<= getPropertyValues( Properties );
+ }
+ else if ( aCommand.Name == "setPropertyValues" )
+ {
+
+ // setPropertyValues
+
+
+ uno::Sequence< beans::PropertyValue > aProperties;
+ if ( !( aCommand.Argument >>= aProperties ) )
+ {
+ ucbhelper::cancelCommandExecution(
+ uno::Any( lang::IllegalArgumentException(
+ "Wrong argument type!",
+ getXWeak(),
+ -1 ) ),
+ Environment );
+ // Unreachable
+ }
+
+ if ( !aProperties.hasElements() )
+ {
+ ucbhelper::cancelCommandExecution(
+ uno::Any( lang::IllegalArgumentException(
+ "No properties!",
+ getXWeak(),
+ -1 ) ),
+ Environment );
+ // Unreachable
+ }
+
+ aRet <<= setPropertyValues( aProperties, Environment );
+ }
+ else if ( aCommand.Name == "getPropertySetInfo" )
+ {
+
+ // getPropertySetInfo
+
+
+ // Note: Implemented by base class.
+ aRet <<= getPropertySetInfo( Environment );
+ }
+ else if ( aCommand.Name == "getCommandInfo" )
+ {
+
+ // getCommandInfo
+
+
+ // Note: Implemented by base class.
+ aRet <<= getCommandInfo( Environment );
+ }
+ else if ( aCommand.Name == "open" )
+ {
+
+ // open
+
+
+ ucb::OpenCommandArgument2 aOpenCommand;
+ if ( !( aCommand.Argument >>= aOpenCommand ) )
+ {
+ ucbhelper::cancelCommandExecution(
+ uno::Any( lang::IllegalArgumentException(
+ "Wrong argument type!",
+ getXWeak(),
+ -1 ) ),
+ Environment );
+ // Unreachable
+ }
+
+ aRet = open( aOpenCommand, Environment );
+ }
+ else if ( !m_aUri.isRootFolder() && aCommand.Name == "insert" )
+ {
+
+ // insert
+
+
+ ucb::InsertCommandArgument aArg;
+ if ( !( aCommand.Argument >>= aArg ) )
+ {
+ ucbhelper::cancelCommandExecution(
+ uno::Any( lang::IllegalArgumentException(
+ "Wrong argument type!",
+ getXWeak(),
+ -1 ) ),
+ Environment );
+ // Unreachable
+ }
+
+ sal_Int32 nNameClash = aArg.ReplaceExisting
+ ? ucb::NameClash::OVERWRITE
+ : ucb::NameClash::ERROR;
+ insert( aArg.Data, nNameClash, Environment );
+ }
+ else if ( !m_aUri.isRootFolder() && aCommand.Name == "delete" )
+ {
+
+ // delete
+
+
+ bool bDeletePhysical = false;
+ aCommand.Argument >>= bDeletePhysical;
+ destroy( bDeletePhysical, Environment );
+
+ // Remove own and all children's persistent data.
+ if ( !removeData() )
+ {
+ uno::Sequence<uno::Any> aArgs(comphelper::InitAnyPropertySequence(
+ {
+ {"Uri", uno::Any(m_xIdentifier->getContentIdentifier())}
+ }));
+ ucbhelper::cancelCommandExecution(
+ ucb::IOErrorCode_CANT_WRITE,
+ aArgs,
+ Environment,
+ "Cannot remove persistent data!",
+ this );
+ // Unreachable
+ }
+
+ // Remove own and all children's Additional Core Properties.
+ removeAdditionalPropertySet();
+ }
+ else if ( aCommand.Name == "transfer" )
+ {
+
+ // transfer
+ // ( Not available at stream objects )
+
+
+ ucb::TransferInfo aInfo;
+ if ( !( aCommand.Argument >>= aInfo ) )
+ {
+ ucbhelper::cancelCommandExecution(
+ uno::Any( lang::IllegalArgumentException(
+ "Wrong argument type!",
+ getXWeak(),
+ -1 ) ),
+ Environment );
+ // Unreachable
+ }
+
+ transfer( aInfo, Environment );
+ }
+ else if ( aCommand.Name == "createNewContent" && isFolder() )
+ {
+
+ // createNewContent
+ // ( Not available at stream objects )
+
+
+ ucb::ContentInfo aInfo;
+ if ( !( aCommand.Argument >>= aInfo ) )
+ {
+ OSL_FAIL( "Wrong argument type!" );
+ ucbhelper::cancelCommandExecution(
+ uno::Any( lang::IllegalArgumentException(
+ "Wrong argument type!",
+ getXWeak(),
+ -1 ) ),
+ Environment );
+ // Unreachable
+ }
+
+ aRet <<= createNewContent( aInfo );
+ }
+ else if ( aCommand.Name == "flush" )
+ {
+
+ // flush
+ // ( Not available at stream objects )
+
+
+ if( !flushData() )
+ {
+ uno::Sequence<uno::Any> aArgs(comphelper::InitAnyPropertySequence(
+ {
+ {"Uri", uno::Any(m_xIdentifier->getContentIdentifier())}
+ }));
+ ucbhelper::cancelCommandExecution(
+ ucb::IOErrorCode_CANT_WRITE,
+ aArgs,
+ Environment,
+ "Cannot write file to disk!",
+ this );
+ // Unreachable
+ }
+ }
+ else
+ {
+
+ // Unsupported command
+
+
+ ucbhelper::cancelCommandExecution(
+ uno::Any( ucb::UnsupportedCommandException(
+ OUString(),
+ getXWeak() ) ),
+ Environment );
+ // Unreachable
+ }
+
+ return aRet;
+}
+
+
+// virtual
+void SAL_CALL Content::abort( sal_Int32 /*CommandId*/ )
+{
+ // @@@ Implement logic to abort running commands, if this makes
+ // sense for your content.
+}
+
+
+// XContentCreator methods.
+
+
+// virtual
+uno::Sequence< ucb::ContentInfo > SAL_CALL
+Content::queryCreatableContentsInfo()
+{
+ return m_aProps.getCreatableContentsInfo( m_aUri );
+}
+
+
+// virtual
+uno::Reference< ucb::XContent > SAL_CALL
+Content::createNewContent( const ucb::ContentInfo& Info )
+{
+ if ( isFolder() )
+ {
+ osl::Guard< osl::Mutex > aGuard( m_aMutex );
+
+ if ( Info.Type.isEmpty() )
+ return uno::Reference< ucb::XContent >();
+
+ if ( !Info.Type.equalsIgnoreAsciiCase(
+ getContentType( m_aUri.getScheme(), true ) ) &&
+ !Info.Type.equalsIgnoreAsciiCase(
+ getContentType( m_aUri.getScheme(), false ) ) )
+ return uno::Reference< ucb::XContent >();
+
+ OUString aURL = m_aUri.getUri() + "/";
+
+ if ( Info.Type.equalsIgnoreAsciiCase(
+ getContentType( m_aUri.getScheme(), true ) ) )
+ aURL += "New_Folder";
+ else
+ aURL += "New_Stream";
+
+ uno::Reference< ucb::XContentIdentifier > xId(
+ new ::ucbhelper::ContentIdentifier( aURL ) );
+
+ return create( m_xContext, m_pProvider, xId, Info );
+ }
+ else
+ {
+ OSL_FAIL( "createNewContent called on non-folder object!" );
+ return uno::Reference< ucb::XContent >();
+ }
+}
+
+
+// Non-interface methods.
+
+
+// virtual
+OUString Content::getParentURL()
+{
+ return m_aUri.getParentUri();
+}
+
+
+// static
+uno::Reference< sdbc::XRow > Content::getPropertyValues(
+ const uno::Reference< uno::XComponentContext >& rxContext,
+ const uno::Sequence< beans::Property >& rProperties,
+ ContentProvider* pProvider,
+ const OUString& rContentId )
+{
+ ContentProperties aData;
+ uno::Reference< container::XHierarchicalNameAccess > xPackage;
+ if ( loadData( pProvider, PackageUri( rContentId ), aData, xPackage ) )
+ {
+ return getPropertyValues( rxContext,
+ rProperties,
+ aData,
+ rtl::Reference<
+ ::ucbhelper::ContentProviderImplHelper >(
+ pProvider ),
+ rContentId );
+ }
+ else
+ {
+ rtl::Reference< ::ucbhelper::PropertyValueSet > xRow
+ = new ::ucbhelper::PropertyValueSet( rxContext );
+
+ for ( const beans::Property& rProp : rProperties )
+ xRow->appendVoid( rProp );
+
+ return xRow;
+ }
+}
+
+
+// static
+uno::Reference< sdbc::XRow > Content::getPropertyValues(
+ const uno::Reference< uno::XComponentContext >& rxContext,
+ const uno::Sequence< beans::Property >& rProperties,
+ const ContentProperties& rData,
+ const rtl::Reference< ::ucbhelper::ContentProviderImplHelper >&
+ rProvider,
+ const OUString& rContentId )
+{
+ // Note: Empty sequence means "get values of all supported properties".
+
+ rtl::Reference< ::ucbhelper::PropertyValueSet > xRow
+ = new ::ucbhelper::PropertyValueSet( rxContext );
+
+ if ( rProperties.hasElements() )
+ {
+ uno::Reference< beans::XPropertySet > xAdditionalPropSet;
+ bool bTriedToGetAdditionalPropSet = false;
+
+ for ( const beans::Property& rProp : rProperties )
+ {
+ // Process Core properties.
+
+ if ( rProp.Name == "ContentType" )
+ {
+ xRow->appendString ( rProp, rData.aContentType );
+ }
+ else if ( rProp.Name == "Title" )
+ {
+ xRow->appendString ( rProp, rData.aTitle );
+ }
+ else if ( rProp.Name == "IsDocument" )
+ {
+ xRow->appendBoolean( rProp, rData.bIsDocument );
+ }
+ else if ( rProp.Name == "IsFolder" )
+ {
+ xRow->appendBoolean( rProp, rData.bIsFolder );
+ }
+ else if ( rProp.Name == "CreatableContentsInfo" )
+ {
+ xRow->appendObject(
+ rProp, uno::Any(
+ rData.getCreatableContentsInfo(
+ PackageUri( rContentId ) ) ) );
+ }
+ else if ( rProp.Name == "MediaType" )
+ {
+ xRow->appendString ( rProp, rData.aMediaType );
+ }
+ else if ( rProp.Name == "Size" )
+ {
+ // Property only available for streams.
+ if ( rData.bIsDocument )
+ xRow->appendLong( rProp, rData.nSize );
+ else
+ xRow->appendVoid( rProp );
+ }
+ else if ( rProp.Name == "Compressed" )
+ {
+ // Property only available for streams.
+ if ( rData.bIsDocument )
+ xRow->appendBoolean( rProp, rData.bCompressed );
+ else
+ xRow->appendVoid( rProp );
+ }
+ else if ( rProp.Name == "Encrypted" )
+ {
+ // Property only available for streams.
+ if ( rData.bIsDocument )
+ xRow->appendBoolean( rProp, rData.bEncrypted );
+ else
+ xRow->appendVoid( rProp );
+ }
+ else if ( rProp.Name == "HasEncryptedEntries" )
+ {
+ // Property only available for root folder.
+ PackageUri aURI( rContentId );
+ if ( aURI.isRootFolder() )
+ xRow->appendBoolean( rProp, rData.bHasEncryptedEntries );
+ else
+ xRow->appendVoid( rProp );
+ }
+ else
+ {
+ // Not a Core Property! Maybe it's an Additional Core Property?!
+
+ if ( !bTriedToGetAdditionalPropSet && !xAdditionalPropSet.is() )
+ {
+ xAdditionalPropSet =
+ rProvider->getAdditionalPropertySet( rContentId,
+ false );
+ bTriedToGetAdditionalPropSet = true;
+ }
+
+ if ( xAdditionalPropSet.is() )
+ {
+ if ( !xRow->appendPropertySetValue(
+ xAdditionalPropSet,
+ rProp ) )
+ {
+ // Append empty entry.
+ xRow->appendVoid( rProp );
+ }
+ }
+ else
+ {
+ // Append empty entry.
+ xRow->appendVoid( rProp );
+ }
+ }
+ }
+ }
+ else
+ {
+ // Append all Core Properties.
+ xRow->appendString (
+ beans::Property(
+ "ContentType",
+ -1,
+ cppu::UnoType<OUString>::get(),
+ beans::PropertyAttribute::BOUND
+ | beans::PropertyAttribute::READONLY ),
+ rData.aContentType );
+ xRow->appendString(
+ beans::Property(
+ "Title",
+ -1,
+ cppu::UnoType<OUString>::get(),
+ beans::PropertyAttribute::BOUND ),
+ rData.aTitle );
+ xRow->appendBoolean(
+ beans::Property(
+ "IsDocument",
+ -1,
+ cppu::UnoType<bool>::get(),
+ beans::PropertyAttribute::BOUND
+ | beans::PropertyAttribute::READONLY ),
+ rData.bIsDocument );
+ xRow->appendBoolean(
+ beans::Property(
+ "IsFolder",
+ -1,
+ cppu::UnoType<bool>::get(),
+ beans::PropertyAttribute::BOUND
+ | beans::PropertyAttribute::READONLY ),
+ rData.bIsFolder );
+ xRow->appendObject(
+ beans::Property(
+ "CreatableContentsInfo",
+ -1,
+ cppu::UnoType<uno::Sequence< ucb::ContentInfo >>::get(),
+ beans::PropertyAttribute::BOUND
+ | beans::PropertyAttribute::READONLY ),
+ uno::Any(
+ rData.getCreatableContentsInfo( PackageUri( rContentId ) ) ) );
+ xRow->appendString(
+ beans::Property(
+ "MediaType",
+ -1,
+ cppu::UnoType<OUString>::get(),
+ beans::PropertyAttribute::BOUND ),
+ rData.aMediaType );
+
+ // Properties only available for streams.
+ if ( rData.bIsDocument )
+ {
+ xRow->appendLong(
+ beans::Property(
+ "Size",
+ -1,
+ cppu::UnoType<sal_Int64>::get(),
+ beans::PropertyAttribute::BOUND
+ | beans::PropertyAttribute::READONLY ),
+ rData.nSize );
+
+ xRow->appendBoolean(
+ beans::Property(
+ "Compressed",
+ -1,
+ cppu::UnoType<bool>::get(),
+ beans::PropertyAttribute::BOUND ),
+ rData.bCompressed );
+
+ xRow->appendBoolean(
+ beans::Property(
+ "Encrypted",
+ -1,
+ cppu::UnoType<bool>::get(),
+ beans::PropertyAttribute::BOUND ),
+ rData.bEncrypted );
+ }
+
+ // Properties only available for root folder.
+ PackageUri aURI( rContentId );
+ if ( aURI.isRootFolder() )
+ {
+ xRow->appendBoolean(
+ beans::Property(
+ "HasEncryptedEntries",
+ -1,
+ cppu::UnoType<bool>::get(),
+ beans::PropertyAttribute::BOUND
+ | beans::PropertyAttribute::READONLY ),
+ rData.bHasEncryptedEntries );
+ }
+
+ // Append all Additional Core Properties.
+
+ uno::Reference< beans::XPropertySet > xSet =
+ rProvider->getAdditionalPropertySet( rContentId, false );
+ xRow->appendPropertySet( xSet );
+ }
+
+ return xRow;
+}
+
+
+uno::Reference< sdbc::XRow > Content::getPropertyValues(
+ const uno::Sequence< beans::Property >& rProperties )
+{
+ osl::Guard< osl::Mutex > aGuard( m_aMutex );
+ return getPropertyValues( m_xContext,
+ rProperties,
+ m_aProps,
+ m_xProvider,
+ m_xIdentifier->getContentIdentifier() );
+}
+
+
+uno::Sequence< uno::Any > Content::setPropertyValues(
+ const uno::Sequence< beans::PropertyValue >& rValues,
+ const uno::Reference< ucb::XCommandEnvironment > & xEnv )
+{
+ osl::ClearableGuard< osl::Mutex > aGuard( m_aMutex );
+
+ uno::Sequence< uno::Any > aRet( rValues.getLength() );
+ auto aRetRange = asNonConstRange(aRet);
+ uno::Sequence< beans::PropertyChangeEvent > aChanges( rValues.getLength() );
+ sal_Int32 nChanged = 0;
+
+ beans::PropertyChangeEvent aEvent;
+ aEvent.Source = getXWeak();
+ aEvent.Further = false;
+// aEvent.PropertyName =
+ aEvent.PropertyHandle = -1;
+// aEvent.OldValue =
+// aEvent.NewValue =
+
+ const beans::PropertyValue* pValues = rValues.getConstArray();
+ sal_Int32 nCount = rValues.getLength();
+
+ uno::Reference< ucb::XPersistentPropertySet > xAdditionalPropSet;
+ bool bTriedToGetAdditionalPropSet = false;
+ bool bExchange = false;
+ bool bStore = false;
+ OUString aNewTitle;
+ sal_Int32 nTitlePos = -1;
+
+ for ( sal_Int32 n = 0; n < nCount; ++n )
+ {
+ const beans::PropertyValue& rValue = pValues[ n ];
+
+ if ( rValue.Name == "ContentType" )
+ {
+ // Read-only property!
+ aRetRange[ n ] <<= lang::IllegalAccessException(
+ "Property is read-only!",
+ getXWeak() );
+ }
+ else if ( rValue.Name == "IsDocument" )
+ {
+ // Read-only property!
+ aRetRange[ n ] <<= lang::IllegalAccessException(
+ "Property is read-only!",
+ getXWeak() );
+ }
+ else if ( rValue.Name == "IsFolder" )
+ {
+ // Read-only property!
+ aRetRange[ n ] <<= lang::IllegalAccessException(
+ "Property is read-only!",
+ getXWeak() );
+ }
+ else if ( rValue.Name == "CreatableContentsInfo" )
+ {
+ // Read-only property!
+ aRetRange[ n ] <<= lang::IllegalAccessException(
+ "Property is read-only!",
+ getXWeak() );
+ }
+ else if ( rValue.Name == "Title" )
+ {
+ if ( m_aUri.isRootFolder() )
+ {
+ // Read-only property!
+ aRetRange[ n ] <<= lang::IllegalAccessException(
+ "Property is read-only!",
+ getXWeak() );
+ }
+ else
+ {
+ OUString aNewValue;
+ if ( rValue.Value >>= aNewValue )
+ {
+ // No empty titles!
+ if ( !aNewValue.isEmpty() )
+ {
+ if ( aNewValue != m_aProps.aTitle )
+ {
+ // modified title -> modified URL -> exchange !
+ if ( m_eState == PERSISTENT )
+ bExchange = true;
+
+ // new value will be set later...
+ aNewTitle = aNewValue;
+
+ // remember position within sequence of values
+ // (for error handling).
+ nTitlePos = n;
+ }
+ }
+ else
+ {
+ aRetRange[ n ] <<=
+ lang::IllegalArgumentException(
+ "Empty title not allowed!",
+ getXWeak(),
+ -1 );
+ }
+ }
+ else
+ {
+ aRetRange[ n ] <<=
+ beans::IllegalTypeException(
+ "Property value has wrong type!",
+ getXWeak() );
+ }
+ }
+ }
+ else if ( rValue.Name == "MediaType" )
+ {
+ OUString aNewValue;
+ if ( rValue.Value >>= aNewValue )
+ {
+ if ( aNewValue != m_aProps.aMediaType )
+ {
+ aEvent.PropertyName = rValue.Name;
+ aEvent.OldValue <<= m_aProps.aMediaType;
+ aEvent.NewValue <<= aNewValue;
+
+ m_aProps.aMediaType = aNewValue;
+ nChanged++;
+ bStore = true;
+ m_nModifiedProps |= MEDIATYPE_MODIFIED;
+ }
+ }
+ else
+ {
+ aRetRange[ n ] <<= beans::IllegalTypeException(
+ "Property value has wrong type!",
+ getXWeak() );
+ }
+ }
+ else if ( rValue.Name == "Size" )
+ {
+ // Read-only property!
+ aRetRange[ n ] <<= lang::IllegalAccessException(
+ "Property is read-only!",
+ getXWeak() );
+ }
+ else if ( rValue.Name == "Compressed" )
+ {
+ // Property only available for streams.
+ if ( m_aProps.bIsDocument )
+ {
+ bool bNewValue;
+ if ( rValue.Value >>= bNewValue )
+ {
+ if ( bNewValue != m_aProps.bCompressed )
+ {
+ aEvent.PropertyName = rValue.Name;
+ aEvent.OldValue <<= m_aProps.bCompressed;
+ aEvent.NewValue <<= bNewValue;
+
+ m_aProps.bCompressed = bNewValue;
+ nChanged++;
+ bStore = true;
+ m_nModifiedProps |= COMPRESSED_MODIFIED;
+ }
+ }
+ else
+ {
+ aRetRange[ n ] <<= beans::IllegalTypeException(
+ "Property value has wrong type!",
+ getXWeak() );
+ }
+ }
+ else
+ {
+ aRetRange[ n ] <<= beans::UnknownPropertyException(
+ "Compressed only supported by streams!",
+ getXWeak() );
+ }
+ }
+ else if ( rValue.Name == "Encrypted" )
+ {
+ // Property only available for streams.
+ if ( m_aProps.bIsDocument )
+ {
+ bool bNewValue;
+ if ( rValue.Value >>= bNewValue )
+ {
+ if ( bNewValue != m_aProps.bEncrypted )
+ {
+ aEvent.PropertyName = rValue.Name;
+ aEvent.OldValue <<= m_aProps.bEncrypted;
+ aEvent.NewValue <<= bNewValue;
+
+ m_aProps.bEncrypted = bNewValue;
+ nChanged++;
+ bStore = true;
+ m_nModifiedProps |= ENCRYPTED_MODIFIED;
+ }
+ }
+ else
+ {
+ aRetRange[ n ] <<= beans::IllegalTypeException(
+ "Property value has wrong type!",
+ getXWeak() );
+ }
+ }
+ else
+ {
+ aRetRange[ n ] <<= beans::UnknownPropertyException(
+ "Encrypted only supported by streams!",
+ getXWeak() );
+ }
+ }
+ else if ( rValue.Name == "HasEncryptedEntries" )
+ {
+ // Read-only property!
+ aRetRange[ n ] <<= lang::IllegalAccessException(
+ "Property is read-only!",
+ getXWeak() );
+ }
+ else if ( rValue.Name == "EncryptionKey" )
+ {
+ // @@@ This is a temporary solution. In the future submitting
+ // the key should be done using an interaction handler!
+
+ // Write-Only property. Only supported by root folder and streams
+ // (all non-root folders of a package have the same encryption key).
+ if ( m_aUri.isRootFolder() || m_aProps.bIsDocument )
+ {
+ uno::Sequence < sal_Int8 > aNewValue;
+ if ( rValue.Value >>= aNewValue )
+ {
+ if ( aNewValue != m_aProps.aEncryptionKey )
+ {
+ aEvent.PropertyName = rValue.Name;
+ aEvent.OldValue <<= m_aProps.aEncryptionKey;
+ aEvent.NewValue <<= aNewValue;
+
+ m_aProps.aEncryptionKey = aNewValue;
+ nChanged++;
+ bStore = true;
+ m_nModifiedProps |= ENCRYPTIONKEY_MODIFIED;
+ }
+ }
+ else
+ {
+ aRetRange[ n ] <<= beans::IllegalTypeException(
+ "Property value has wrong type!",
+ getXWeak() );
+ }
+ }
+ else
+ {
+ aRetRange[ n ] <<= beans::UnknownPropertyException(
+ "EncryptionKey not supported by non-root folder!",
+ getXWeak() );
+ }
+ }
+ else
+ {
+ // Not a Core Property! Maybe it's an Additional Core Property?!
+
+ if ( !bTriedToGetAdditionalPropSet && !xAdditionalPropSet.is() )
+ {
+ xAdditionalPropSet = getAdditionalPropertySet( false );
+ bTriedToGetAdditionalPropSet = true;
+ }
+
+ if ( xAdditionalPropSet.is() )
+ {
+ try
+ {
+ uno::Any aOldValue
+ = xAdditionalPropSet->getPropertyValue( rValue.Name );
+ if ( aOldValue != rValue.Value )
+ {
+ xAdditionalPropSet->setPropertyValue(
+ rValue.Name, rValue.Value );
+
+ aEvent.PropertyName = rValue.Name;
+ aEvent.OldValue = aOldValue;
+ aEvent.NewValue = rValue.Value;
+
+ aChanges.getArray()[ nChanged ] = aEvent;
+ nChanged++;
+ }
+ }
+ catch ( beans::UnknownPropertyException const & e )
+ {
+ aRetRange[ n ] <<= e;
+ }
+ catch ( lang::WrappedTargetException const & e )
+ {
+ aRetRange[ n ] <<= e;
+ }
+ catch ( beans::PropertyVetoException const & e )
+ {
+ aRetRange[ n ] <<= e;
+ }
+ catch ( lang::IllegalArgumentException const & e )
+ {
+ aRetRange[ n ] <<= e;
+ }
+ }
+ else
+ {
+ aRetRange[ n ] <<= uno::Exception(
+ "No property set for storing the value!",
+ getXWeak() );
+ }
+ }
+ }
+
+ if ( bExchange )
+ {
+ uno::Reference< ucb::XContentIdentifier > xOldId = m_xIdentifier;
+
+ // Assemble new content identifier...
+ OUString aNewURL = m_aUri.getParentUri() + "/" +
+ ::ucb_impl::urihelper::encodeSegment( aNewTitle );
+ uno::Reference< ucb::XContentIdentifier > xNewId
+ = new ::ucbhelper::ContentIdentifier( aNewURL );
+
+ aGuard.clear();
+ if ( exchangeIdentity( xNewId ) )
+ {
+ // Adapt persistent data.
+ renameData( xOldId, xNewId );
+
+ // Adapt Additional Core Properties.
+ renameAdditionalPropertySet( xOldId->getContentIdentifier(),
+ xNewId->getContentIdentifier() );
+ }
+ else
+ {
+ // Do not set new title!
+ aNewTitle.clear();
+
+ // Set error .
+ aRetRange[ nTitlePos ] <<= uno::Exception(
+ "Exchange failed!",
+ getXWeak() );
+ }
+ }
+
+ if ( !aNewTitle.isEmpty() )
+ {
+ aEvent.PropertyName = "Title";
+ aEvent.OldValue <<= m_aProps.aTitle;
+ aEvent.NewValue <<= aNewTitle;
+
+ m_aProps.aTitle = aNewTitle;
+
+ aChanges.getArray()[ nChanged ] = aEvent;
+ nChanged++;
+ }
+
+ if ( nChanged > 0 )
+ {
+ // Save changes, if content was already made persistent.
+ if ( ( m_nModifiedProps & ENCRYPTIONKEY_MODIFIED ) ||
+ ( bStore && ( m_eState == PERSISTENT ) ) )
+ {
+ if ( !storeData( uno::Reference< io::XInputStream >() ) )
+ {
+ uno::Sequence<uno::Any> aArgs(comphelper::InitAnyPropertySequence(
+ {
+ {"Uri", uno::Any(m_xIdentifier->getContentIdentifier())}
+ }));
+ ucbhelper::cancelCommandExecution(
+ ucb::IOErrorCode_CANT_WRITE,
+ aArgs,
+ xEnv,
+ "Cannot store persistent data!",
+ this );
+ // Unreachable
+ }
+ }
+
+ aGuard.clear();
+ aChanges.realloc( nChanged );
+ notifyPropertiesChange( aChanges );
+ }
+
+ return aRet;
+}
+
+
+uno::Any Content::open(
+ const ucb::OpenCommandArgument2& rArg,
+ const uno::Reference< ucb::XCommandEnvironment >& xEnv )
+{
+ if ( rArg.Mode == ucb::OpenMode::ALL ||
+ rArg.Mode == ucb::OpenMode::FOLDERS ||
+ rArg.Mode == ucb::OpenMode::DOCUMENTS )
+ {
+
+ // open command for a folder content
+
+
+ uno::Reference< ucb::XDynamicResultSet > xSet
+ = new DynamicResultSet( m_xContext, this, rArg, xEnv );
+ return uno::Any( xSet );
+ }
+ else
+ {
+
+ // open command for a document content
+
+
+ if ( ( rArg.Mode == ucb::OpenMode::DOCUMENT_SHARE_DENY_NONE ) ||
+ ( rArg.Mode == ucb::OpenMode::DOCUMENT_SHARE_DENY_WRITE ) )
+ {
+ // Currently(?) unsupported.
+ ucbhelper::cancelCommandExecution(
+ uno::Any( ucb::UnsupportedOpenModeException(
+ OUString(),
+ getXWeak(),
+ sal_Int16( rArg.Mode ) ) ),
+ xEnv );
+ // Unreachable
+ }
+
+ uno::Reference< io::XOutputStream > xOut( rArg.Sink, uno::UNO_QUERY );
+ if ( xOut.is() )
+ {
+ // PUSH: write data into xOut
+
+ uno::Reference< io::XInputStream > xIn = getInputStream();
+ if ( !xIn.is() )
+ {
+ // No interaction if we are not persistent!
+ uno::Sequence<uno::Any> aArgs(comphelper::InitAnyPropertySequence(
+ {
+ {"Uri", uno::Any(m_xIdentifier->getContentIdentifier())}
+ }));
+ ucbhelper::cancelCommandExecution(
+ ucb::IOErrorCode_CANT_READ,
+ aArgs,
+ m_eState == PERSISTENT
+ ? xEnv
+ : uno::Reference< ucb::XCommandEnvironment >(),
+ "Got no data stream!",
+ this );
+ // Unreachable
+ }
+
+ try
+ {
+ uno::Sequence< sal_Int8 > aBuffer;
+ while (true)
+ {
+ sal_Int32 nRead = xIn->readSomeBytes( aBuffer, 65536 );
+ if (!nRead)
+ break;
+ aBuffer.realloc( nRead );
+ xOut->writeBytes( aBuffer );
+ }
+
+ xOut->closeOutput();
+ }
+ catch ( io::NotConnectedException const & )
+ {
+ // closeOutput, readSomeBytes, writeBytes
+ }
+ catch ( io::BufferSizeExceededException const & )
+ {
+ // closeOutput, readSomeBytes, writeBytes
+ }
+ catch ( io::IOException const & )
+ {
+ // closeOutput, readSomeBytes, writeBytes
+ }
+ }
+ else
+ {
+ uno::Reference< io::XActiveDataSink > xDataSink(
+ rArg.Sink, uno::UNO_QUERY );
+ if ( xDataSink.is() )
+ {
+ // PULL: wait for client read
+
+ uno::Reference< io::XInputStream > xIn = getInputStream();
+ if ( !xIn.is() )
+ {
+ // No interaction if we are not persistent!
+ uno::Sequence<uno::Any> aArgs(comphelper::InitAnyPropertySequence(
+ {
+ {"Uri", uno::Any(m_xIdentifier->getContentIdentifier())}
+ }));
+ ucbhelper::cancelCommandExecution(
+ ucb::IOErrorCode_CANT_READ,
+ aArgs,
+ m_eState == PERSISTENT
+ ? xEnv
+ : uno::Reference<
+ ucb::XCommandEnvironment >(),
+ "Got no data stream!",
+ this );
+ // Unreachable
+ }
+
+ // Done.
+ xDataSink->setInputStream( xIn );
+ }
+ else
+ {
+ // Note: aOpenCommand.Sink may contain an XStream
+ // implementation. Support for this type of
+ // sink is optional...
+ ucbhelper::cancelCommandExecution(
+ uno::Any(
+ ucb::UnsupportedDataSinkException(
+ OUString(),
+ getXWeak(),
+ rArg.Sink ) ),
+ xEnv );
+ // Unreachable
+ }
+ }
+ }
+
+ return uno::Any();
+}
+
+
+void Content::insert(
+ const uno::Reference< io::XInputStream >& xStream,
+ sal_Int32 nNameClashResolve,
+ const uno::Reference< ucb::XCommandEnvironment >& xEnv )
+{
+ osl::ClearableGuard< osl::Mutex > aGuard( m_aMutex );
+
+ // Check, if all required properties were set.
+ if ( isFolder() )
+ {
+ // Required: Title
+
+ if ( m_aProps.aTitle.isEmpty() )
+ m_aProps.aTitle = m_aUri.getName();
+ }
+ else
+ {
+ // Required: rArg.Data
+
+ if ( !xStream.is() )
+ {
+ ucbhelper::cancelCommandExecution(
+ uno::Any( ucb::MissingInputStreamException(
+ OUString(),
+ getXWeak() ) ),
+ xEnv );
+ // Unreachable
+ }
+
+ // Required: Title
+
+ if ( m_aProps.aTitle.isEmpty() )
+ m_aProps.aTitle = m_aUri.getName();
+ }
+
+ OUString aNewURL = m_aUri.getParentUri();
+ if (1 + aNewURL.lastIndexOf('/') != aNewURL.getLength())
+ aNewURL += "/";
+ aNewURL += ::ucb_impl::urihelper::encodeSegment( m_aProps.aTitle );
+ PackageUri aNewUri( aNewURL );
+
+ // Handle possible name clash...
+ switch ( nNameClashResolve )
+ {
+ // fail.
+ case ucb::NameClash::ERROR:
+ if ( hasData( aNewUri ) )
+ {
+ ucbhelper::cancelCommandExecution(
+ uno::Any( ucb::NameClashException(
+ OUString(),
+ getXWeak(),
+ task::InteractionClassification_ERROR,
+ m_aProps.aTitle ) ),
+ xEnv );
+ // Unreachable
+ }
+ break;
+
+ // replace (possibly) existing object.
+ case ucb::NameClash::OVERWRITE:
+ break;
+
+ // "invent" a new valid title.
+ case ucb::NameClash::RENAME:
+ if ( hasData( aNewUri ) )
+ {
+ sal_Int32 nTry = 0;
+
+ do
+ {
+ OUString aNew = aNewUri.getUri() + "_" + OUString::number( ++nTry );
+ aNewUri.setUri( aNew );
+ }
+ while ( hasData( aNewUri ) && ( nTry < 1000 ) );
+
+ if ( nTry == 1000 )
+ {
+ ucbhelper::cancelCommandExecution(
+ uno::Any(
+ ucb::UnsupportedNameClashException(
+ "Unable to resolve name clash!",
+ getXWeak(),
+ nNameClashResolve ) ),
+ xEnv );
+ // Unreachable
+ }
+ else
+ {
+ m_aProps.aTitle += "_";
+ m_aProps.aTitle += OUString::number( nTry );
+ }
+ }
+ break;
+
+ case ucb::NameClash::KEEP: // deprecated
+ case ucb::NameClash::ASK:
+ default:
+ if ( hasData( aNewUri ) )
+ {
+ ucbhelper::cancelCommandExecution(
+ uno::Any(
+ ucb::UnsupportedNameClashException(
+ OUString(),
+ getXWeak(),
+ nNameClashResolve ) ),
+ xEnv );
+ // Unreachable
+ }
+ break;
+ }
+
+ // Identifier changed?
+ bool bNewId = ( m_aUri.getUri() != aNewUri.getUri() );
+
+ if ( bNewId )
+ {
+ m_xIdentifier = new ::ucbhelper::ContentIdentifier( aNewURL );
+ m_aUri = aNewUri;
+ }
+
+ if ( !storeData( xStream ) )
+ {
+ uno::Sequence<uno::Any> aArgs(comphelper::InitAnyPropertySequence(
+ {
+ {"Uri", uno::Any(m_xIdentifier->getContentIdentifier())}
+ }));
+ ucbhelper::cancelCommandExecution(
+ ucb::IOErrorCode_CANT_WRITE,
+ aArgs,
+ xEnv,
+ "Cannot store persistent data!",
+ this );
+ // Unreachable
+ }
+
+ m_eState = PERSISTENT;
+
+ if ( bNewId )
+ {
+ // Take over correct default values from underlying packager...
+ uno::Reference< container::XHierarchicalNameAccess > xXHierarchicalNameAccess;
+ loadData( m_pProvider,
+ m_aUri,
+ m_aProps,
+ xXHierarchicalNameAccess );
+
+ aGuard.clear();
+ inserted();
+ }
+}
+
+
+void Content::destroy(
+ bool bDeletePhysical,
+ const uno::Reference< ucb::XCommandEnvironment >& xEnv )
+{
+ // @@@ take care about bDeletePhysical -> trashcan support
+
+ osl::ClearableGuard< osl::Mutex > aGuard( m_aMutex );
+
+ uno::Reference< ucb::XContent > xThis = this;
+
+ // Persistent?
+ if ( m_eState != PERSISTENT )
+ {
+ ucbhelper::cancelCommandExecution(
+ uno::Any( ucb::UnsupportedCommandException(
+ "Not persistent!",
+ getXWeak() ) ),
+ xEnv );
+ // Unreachable
+ }
+
+ m_eState = DEAD;
+
+ aGuard.clear();
+ deleted();
+
+ if ( isFolder() )
+ {
+ // Process instantiated children...
+
+ ContentRefList aChildren;
+ queryChildren( aChildren );
+
+ for ( auto& rChild : aChildren )
+ {
+ rChild->destroy( bDeletePhysical, xEnv );
+ }
+ }
+}
+
+
+void Content::transfer(
+ const ucb::TransferInfo& rInfo,
+ const uno::Reference< ucb::XCommandEnvironment > & xEnv )
+{
+ osl::ClearableGuard< osl::Mutex > aGuard( m_aMutex );
+
+ // Persistent?
+ if ( m_eState != PERSISTENT )
+ {
+ ucbhelper::cancelCommandExecution(
+ uno::Any( ucb::UnsupportedCommandException(
+ "Not persistent!",
+ getXWeak() ) ),
+ xEnv );
+ // Unreachable
+ }
+
+ // Is source a package content?
+ if ( ( rInfo.SourceURL.isEmpty() ) ||
+ ( rInfo.SourceURL.compareTo(
+ m_aUri.getUri(), PACKAGE_URL_SCHEME_LENGTH + 3 ) != 0 ) )
+ {
+ ucbhelper::cancelCommandExecution(
+ uno::Any( ucb::InteractiveBadTransferURLException(
+ OUString(),
+ getXWeak() ) ),
+ xEnv );
+ // Unreachable
+ }
+
+ // Is source not a parent of me / not me?
+ OUString aId = m_aUri.getParentUri() + "/";
+
+ if ( rInfo.SourceURL.getLength() <= aId.getLength() )
+ {
+ if ( aId.startsWith( rInfo.SourceURL ) )
+ {
+ uno::Sequence<uno::Any> aArgs(comphelper::InitAnyPropertySequence(
+ {
+ {"Uri", uno::Any(rInfo.SourceURL)}
+ }));
+ ucbhelper::cancelCommandExecution(
+ ucb::IOErrorCode_RECURSIVE,
+ aArgs,
+ xEnv,
+ "Target is equal to or is a child of source!",
+ this );
+ // Unreachable
+ }
+ }
+
+
+ // 0) Obtain content object for source.
+
+
+ uno::Reference< ucb::XContentIdentifier > xId
+ = new ::ucbhelper::ContentIdentifier( rInfo.SourceURL );
+
+ // Note: The static cast is okay here, because its sure that
+ // m_xProvider is always the PackageContentProvider.
+ rtl::Reference< Content > xSource;
+
+ try
+ {
+ xSource = static_cast< Content * >(
+ m_xProvider->queryContent( xId ).get() );
+ }
+ catch ( ucb::IllegalIdentifierException const & )
+ {
+ // queryContent
+ }
+
+ if ( !xSource.is() )
+ {
+ uno::Sequence<uno::Any> aArgs(comphelper::InitAnyPropertySequence(
+ {
+ {"Uri", uno::Any(xId->getContentIdentifier())}
+ }));
+ ucbhelper::cancelCommandExecution(
+ ucb::IOErrorCode_CANT_READ,
+ aArgs,
+ xEnv,
+ "Cannot instantiate source object!",
+ this );
+ // Unreachable
+ }
+
+
+ // 1) Create new child content.
+
+
+ OUString aType = xSource->isFolder()
+ ? getContentType( m_aUri.getScheme(), true )
+ : getContentType( m_aUri.getScheme(), false );
+ ucb::ContentInfo aContentInfo;
+ aContentInfo.Type = aType;
+ aContentInfo.Attributes = 0;
+
+ // Note: The static cast is okay here, because its sure that
+ // createNewContent always creates a Content.
+ rtl::Reference< Content > xTarget
+ = static_cast< Content * >( createNewContent( aContentInfo ).get() );
+ if ( !xTarget.is() )
+ {
+ uno::Sequence<uno::Any> aArgs(comphelper::InitAnyPropertySequence(
+ {
+ {"Folder", uno::Any(aId)}
+ }));
+ ucbhelper::cancelCommandExecution(
+ ucb::IOErrorCode_CANT_CREATE,
+ aArgs,
+ xEnv,
+ "XContentCreator::createNewContent failed!",
+ this );
+ // Unreachable
+ }
+
+
+ // 2) Copy data from source content to child content.
+
+
+ uno::Sequence< beans::Property > aSourceProps
+ = xSource->getPropertySetInfo( xEnv )->getProperties();
+ sal_Int32 nCount = aSourceProps.getLength();
+
+ if ( nCount )
+ {
+ bool bHadTitle = rInfo.NewTitle.isEmpty();
+
+ // Get all source values.
+ uno::Reference< sdbc::XRow > xRow
+ = xSource->getPropertyValues( aSourceProps );
+
+ uno::Sequence< beans::PropertyValue > aValues( nCount );
+ beans::PropertyValue* pValues = aValues.getArray();
+
+ const beans::Property* pProps = aSourceProps.getConstArray();
+ for ( sal_Int32 n = 0; n < nCount; ++n )
+ {
+ const beans::Property& rProp = pProps[ n ];
+ beans::PropertyValue& rValue = pValues[ n ];
+
+ rValue.Name = rProp.Name;
+ rValue.Handle = rProp.Handle;
+
+ if ( !bHadTitle && rProp.Name == "Title" )
+ {
+ // Set new title instead of original.
+ bHadTitle = true;
+ rValue.Value <<= rInfo.NewTitle;
+ }
+ else
+ rValue.Value
+ = xRow->getObject( n + 1,
+ uno::Reference<
+ container::XNameAccess >() );
+
+ rValue.State = beans::PropertyState_DIRECT_VALUE;
+
+ if ( rProp.Attributes & beans::PropertyAttribute::REMOVABLE )
+ {
+ // Add Additional Core Property.
+ try
+ {
+ xTarget->addProperty( rProp.Name,
+ rProp.Attributes,
+ rValue.Value );
+ }
+ catch ( beans::PropertyExistException const & )
+ {
+ }
+ catch ( beans::IllegalTypeException const & )
+ {
+ }
+ catch ( lang::IllegalArgumentException const & )
+ {
+ }
+ }
+ }
+
+ // Set target values.
+ xTarget->setPropertyValues( aValues, xEnv );
+ }
+
+
+ // 3) Commit (insert) child.
+
+
+ xTarget->insert( xSource->getInputStream(), rInfo.NameClash, xEnv );
+
+
+ // 4) Transfer (copy) children of source.
+
+
+ if ( xSource->isFolder() )
+ {
+ uno::Reference< container::XEnumeration > xIter
+ = xSource->getIterator();
+ if ( xIter.is() )
+ {
+ while ( xIter->hasMoreElements() )
+ {
+ try
+ {
+ uno::Reference< container::XNamed > xNamed;
+ xIter->nextElement() >>= xNamed;
+
+ if ( !xNamed.is() )
+ {
+ OSL_FAIL( "Content::transfer - Got no XNamed!" );
+ break;
+ }
+
+ OUString aName = xNamed->getName();
+
+ if ( aName.isEmpty() )
+ {
+ OSL_FAIL( "Content::transfer - Empty name!" );
+ break;
+ }
+
+ OUString aChildId = xId->getContentIdentifier();
+ if ( ( aChildId.lastIndexOf( '/' ) + 1 )
+ != aChildId.getLength() )
+ aChildId += "/";
+
+ aChildId += ::ucb_impl::urihelper::encodeSegment( aName );
+
+ ucb::TransferInfo aInfo;
+ aInfo.MoveData = false;
+ aInfo.NewTitle.clear();
+ aInfo.SourceURL = aChildId;
+ aInfo.NameClash = rInfo.NameClash;
+
+ // Transfer child to target.
+ xTarget->transfer( aInfo, xEnv );
+ }
+ catch ( container::NoSuchElementException const & )
+ {
+ }
+ catch ( lang::WrappedTargetException const & )
+ {
+ }
+ }
+ }
+ }
+
+
+ // 5) Destroy source ( when moving only ) .
+
+
+ if ( !rInfo.MoveData )
+ return;
+
+ xSource->destroy( true, xEnv );
+
+ // Remove all persistent data of source and its children.
+ if ( !xSource->removeData() )
+ {
+ uno::Sequence<uno::Any> aArgs(comphelper::InitAnyPropertySequence(
+ {
+ {"Uri", uno::Any(xSource->m_xIdentifier->getContentIdentifier())}
+ }));
+ ucbhelper::cancelCommandExecution(
+ ucb::IOErrorCode_CANT_WRITE,
+ aArgs,
+ xEnv,
+ "Cannot remove persistent data of source object!",
+ this );
+ // Unreachable
+ }
+
+ // Remove own and all children's Additional Core Properties.
+ xSource->removeAdditionalPropertySet();
+}
+
+
+bool Content::exchangeIdentity(
+ const uno::Reference< ucb::XContentIdentifier >& xNewId )
+{
+ if ( !xNewId.is() )
+ return false;
+
+ osl::ClearableGuard< osl::Mutex > aGuard( m_aMutex );
+
+ uno::Reference< ucb::XContent > xThis = this;
+
+ // Already persistent?
+ if ( m_eState != PERSISTENT )
+ {
+ OSL_FAIL( "Content::exchangeIdentity - Not persistent!" );
+ return false;
+ }
+
+ // Exchange own identity.
+
+ // Fail, if a content with given id already exists.
+ PackageUri aNewUri( xNewId->getContentIdentifier() );
+ if ( !hasData( aNewUri ) )
+ {
+ OUString aOldURL = m_xIdentifier->getContentIdentifier();
+
+ aGuard.clear();
+ if ( exchange( xNewId ) )
+ {
+ m_aUri = aNewUri;
+ if ( isFolder() )
+ {
+ // Process instantiated children...
+
+ ContentRefList aChildren;
+ queryChildren( aChildren );
+
+ for ( const auto& rChild : aChildren )
+ {
+ ContentRef xChild = rChild;
+
+ // Create new content identifier for the child...
+ uno::Reference< ucb::XContentIdentifier > xOldChildId
+ = xChild->getIdentifier();
+ OUString aOldChildURL
+ = xOldChildId->getContentIdentifier();
+ OUString aNewChildURL
+ = aOldChildURL.replaceAt(
+ 0,
+ aOldURL.getLength(),
+ xNewId->getContentIdentifier() );
+ uno::Reference< ucb::XContentIdentifier > xNewChildId
+ = new ::ucbhelper::ContentIdentifier( aNewChildURL );
+
+ if ( !xChild->exchangeIdentity( xNewChildId ) )
+ return false;
+ }
+ }
+ return true;
+ }
+ }
+
+ OSL_FAIL( "Content::exchangeIdentity - Panic! Cannot exchange identity!" );
+ return false;
+}
+
+
+void Content::queryChildren( ContentRefList& rChildren )
+{
+ // Obtain a list with a snapshot of all currently instantiated contents
+ // from provider and extract the contents which are direct children
+ // of this content.
+
+ ::ucbhelper::ContentRefList aAllContents;
+ m_xProvider->queryExistingContents( aAllContents );
+
+ OUString aURL = m_xIdentifier->getContentIdentifier();
+
+ OSL_ENSURE( aURL.lastIndexOf( '/' ) != ( aURL.getLength() - 1 ),
+ "Content::queryChildren - Invalid URL!" );
+
+ aURL += "/";
+
+ sal_Int32 nLen = aURL.getLength();
+
+ for ( const auto& rContent : aAllContents )
+ {
+ ::ucbhelper::ContentImplHelperRef xChild = rContent;
+ OUString aChildURL
+ = xChild->getIdentifier()->getContentIdentifier();
+
+ // Is aURL a prefix of aChildURL?
+ if ( ( aChildURL.getLength() > nLen ) &&
+ ( aChildURL.startsWith( aURL ) ) )
+ {
+ if ( aChildURL.indexOf( '/', nLen ) == -1 )
+ {
+ // No further slashes. It's a child!
+ rChildren.emplace_back(
+ static_cast< Content * >( xChild.get() ) );
+ }
+ }
+ }
+}
+
+
+uno::Reference< container::XHierarchicalNameAccess > Content::getPackage(
+ const PackageUri& rURI )
+{
+ osl::Guard< osl::Mutex > aGuard( m_aMutex );
+
+ if ( rURI.getPackage() == m_aUri.getPackage() )
+ {
+ if ( !m_xPackage.is() )
+ m_xPackage = m_pProvider->createPackage( m_aUri );
+
+ return m_xPackage;
+ }
+
+ return m_pProvider->createPackage( rURI );
+}
+
+
+uno::Reference< container::XHierarchicalNameAccess > Content::getPackage()
+{
+ return getPackage( m_aUri );
+}
+
+
+// static
+bool Content::hasData(
+ ContentProvider* pProvider,
+ const PackageUri& rURI,
+ uno::Reference< container::XHierarchicalNameAccess > & rxPackage )
+{
+ rxPackage = pProvider->createPackage( rURI );
+ return rxPackage->hasByHierarchicalName( rURI.getPath() );
+}
+
+
+bool Content::hasData( const PackageUri& rURI )
+{
+ osl::Guard< osl::Mutex > aGuard( m_aMutex );
+
+ uno::Reference< container::XHierarchicalNameAccess > xPackage;
+ if ( rURI.getPackage() == m_aUri.getPackage() )
+ {
+ xPackage = getPackage();
+ return xPackage->hasByHierarchicalName( rURI.getPath() );
+ }
+
+ return hasData( m_pProvider, rURI, xPackage );
+}
+
+
+//static
+bool Content::loadData(
+ ContentProvider* pProvider,
+ const PackageUri& rURI,
+ ContentProperties& rProps,
+ uno::Reference< container::XHierarchicalNameAccess > & rxPackage )
+{
+ rxPackage = pProvider->createPackage( rURI );
+
+ if ( rURI.isRootFolder() )
+ {
+ // Properties available only from package
+ uno::Reference< beans::XPropertySet > xPackagePropSet(
+ rxPackage, uno::UNO_QUERY );
+
+ OSL_ENSURE( xPackagePropSet.is(),
+ "Content::loadData - "
+ "Got no XPropertySet interface from package!" );
+
+ if ( xPackagePropSet.is() )
+ {
+ // HasEncryptedEntries (only available at root folder)
+ try
+ {
+ uno::Any aHasEncryptedEntries
+ = xPackagePropSet->getPropertyValue( "HasEncryptedEntries" );
+ if ( !( aHasEncryptedEntries >>= rProps.bHasEncryptedEntries ) )
+ {
+ OSL_FAIL( "Content::loadData - "
+ "Got no HasEncryptedEntries value!" );
+ return false;
+ }
+ }
+ catch ( beans::UnknownPropertyException const & )
+ {
+ OSL_FAIL( "Content::loadData - "
+ "Got no HasEncryptedEntries value!" );
+ return false;
+ }
+ catch ( lang::WrappedTargetException const & )
+ {
+ OSL_FAIL( "Content::loadData - "
+ "Got no HasEncryptedEntries value!" );
+ return false;
+ }
+ }
+ }
+
+ if ( !rxPackage->hasByHierarchicalName( rURI.getPath() ) )
+ return false;
+
+ try
+ {
+ uno::Any aEntry = rxPackage->getByHierarchicalName( rURI.getPath() );
+ if ( aEntry.hasValue() )
+ {
+ uno::Reference< beans::XPropertySet > xPropSet;
+ aEntry >>= xPropSet;
+
+ if ( !xPropSet.is() )
+ {
+ OSL_FAIL( "Content::loadData - Got no XPropertySet interface!" );
+ return false;
+ }
+
+ // Title
+ rProps.aTitle = rURI.getName();
+
+ // MediaType
+ try
+ {
+ uno::Any aMediaType = xPropSet->getPropertyValue("MediaType");
+ if ( !( aMediaType >>= rProps.aMediaType ) )
+ {
+ OSL_FAIL( "Content::loadData - Got no MediaType value!" );
+ return false;
+ }
+ }
+ catch ( beans::UnknownPropertyException const & )
+ {
+ OSL_FAIL( "Content::loadData - Got no MediaType value!" );
+ return false;
+ }
+ catch ( lang::WrappedTargetException const & )
+ {
+ OSL_FAIL( "Content::loadData - Got no MediaType value!" );
+ return false;
+ }
+
+ uno::Reference< container::XEnumerationAccess > xEnumAccess;
+ aEntry >>= xEnumAccess;
+
+ // ContentType / IsFolder / IsDocument
+ if ( xEnumAccess.is() )
+ {
+ // folder
+ rProps.aContentType = getContentType( rURI.getScheme(), true );
+ rProps.bIsDocument = false;
+ rProps.bIsFolder = true;
+ }
+ else
+ {
+ // stream
+ rProps.aContentType = getContentType( rURI.getScheme(), false );
+ rProps.bIsDocument = true;
+ rProps.bIsFolder = false;
+ }
+
+ if ( rProps.bIsDocument )
+ {
+ // Size ( only available for streams )
+ try
+ {
+ uno::Any aSize = xPropSet->getPropertyValue("Size");
+ if ( !( aSize >>= rProps.nSize ) )
+ {
+ OSL_FAIL( "Content::loadData - Got no Size value!" );
+ return false;
+ }
+ }
+ catch ( beans::UnknownPropertyException const & )
+ {
+ OSL_FAIL( "Content::loadData - Got no Size value!" );
+ return false;
+ }
+ catch ( lang::WrappedTargetException const & )
+ {
+ OSL_FAIL( "Content::loadData - Got no Size value!" );
+ return false;
+ }
+
+ // Compressed ( only available for streams )
+ try
+ {
+ uno::Any aCompressed = xPropSet->getPropertyValue("Compressed");
+ if ( !( aCompressed >>= rProps.bCompressed ) )
+ {
+ OSL_FAIL( "Content::loadData - Got no Compressed value!" );
+ return false;
+ }
+ }
+ catch ( beans::UnknownPropertyException const & )
+ {
+ OSL_FAIL( "Content::loadData - Got no Compressed value!" );
+ return false;
+ }
+ catch ( lang::WrappedTargetException const & )
+ {
+ OSL_FAIL( "Content::loadData - Got no Compressed value!" );
+ return false;
+ }
+
+ // Encrypted ( only available for streams )
+ try
+ {
+ uno::Any aEncrypted = xPropSet->getPropertyValue("Encrypted");
+ if ( !( aEncrypted >>= rProps.bEncrypted ) )
+ {
+ OSL_FAIL( "Content::loadData - Got no Encrypted value!" );
+ return false;
+ }
+ }
+ catch ( beans::UnknownPropertyException const & )
+ {
+ OSL_FAIL( "Content::loadData - Got no Encrypted value!" );
+ return false;
+ }
+ catch ( lang::WrappedTargetException const & )
+ {
+ OSL_FAIL( "Content::loadData - Got no Encrypted value!" );
+ return false;
+ }
+ }
+ return true;
+ }
+ }
+ catch ( container::NoSuchElementException const & )
+ {
+ // getByHierarchicalName
+ }
+
+ return false;
+}
+
+
+void Content::renameData(
+ const uno::Reference< ucb::XContentIdentifier >& xOldId,
+ const uno::Reference< ucb::XContentIdentifier >& xNewId )
+{
+ osl::Guard< osl::Mutex > aGuard( m_aMutex );
+
+ PackageUri aURI( xOldId->getContentIdentifier() );
+ uno::Reference< container::XHierarchicalNameAccess > xNA = getPackage(
+ aURI );
+
+ if ( !xNA->hasByHierarchicalName( aURI.getPath() ) )
+ return;
+
+ try
+ {
+ uno::Any aEntry = xNA->getByHierarchicalName( aURI.getPath() );
+ uno::Reference< container::XNamed > xNamed;
+ aEntry >>= xNamed;
+
+ if ( !xNamed.is() )
+ {
+ OSL_FAIL( "Content::renameData - Got no XNamed interface!" );
+ return;
+ }
+
+ PackageUri aNewURI( xNewId->getContentIdentifier() );
+
+ // No success indicator!? No return value / exceptions specified.
+ xNamed->setName( aNewURI.getName() );
+ }
+ catch ( container::NoSuchElementException const & )
+ {
+ // getByHierarchicalName
+ }
+}
+
+
+bool Content::storeData( const uno::Reference< io::XInputStream >& xStream )
+{
+ osl::Guard< osl::Mutex > aGuard( m_aMutex );
+
+ uno::Reference< container::XHierarchicalNameAccess > xNA = getPackage();
+
+ uno::Reference< beans::XPropertySet > xPackagePropSet(
+ xNA, uno::UNO_QUERY );
+ OSL_ENSURE( xPackagePropSet.is(),
+ "Content::storeData - "
+ "Got no XPropertySet interface from package!" );
+
+ if ( !xPackagePropSet.is() )
+ return false;
+
+ if ( m_nModifiedProps & ENCRYPTIONKEY_MODIFIED )
+ {
+ if ( m_aUri.isRootFolder() )
+ {
+ // Property available only from package and from streams (see below)
+ try
+ {
+ xPackagePropSet->setPropertyValue(
+ "EncryptionKey",
+ uno::Any( m_aProps.aEncryptionKey ) );
+ m_nModifiedProps &= ~ENCRYPTIONKEY_MODIFIED;
+ }
+ catch ( beans::UnknownPropertyException const & )
+ {
+ // setPropertyValue
+ }
+ catch ( beans::PropertyVetoException const & )
+ {
+ // setPropertyValue
+ }
+ catch ( lang::IllegalArgumentException const & )
+ {
+ // setPropertyValue
+ }
+ catch ( lang::WrappedTargetException const & )
+ {
+ // setPropertyValue
+ }
+ }
+ }
+
+ if ( !xNA->hasByHierarchicalName( m_aUri.getPath() ) )
+ {
+// if ( !bCreate )
+// return sal_True;
+
+ try
+ {
+ // Create new resource...
+ uno::Reference< lang::XSingleServiceFactory > xFac(
+ xNA, uno::UNO_QUERY );
+ if ( !xFac.is() )
+ {
+ OSL_FAIL( "Content::storeData - "
+ "Got no XSingleServiceFactory interface!" );
+ return false;
+ }
+
+ uno::Sequence< uno::Any > aArgs{ uno::Any(isFolder()) };
+
+ uno::Reference< uno::XInterface > xNew
+ = xFac->createInstanceWithArguments( aArgs );
+
+ if ( !xNew.is() )
+ {
+ OSL_FAIL( "Content::storeData - createInstance failed!" );
+ return false;
+ }
+
+ PackageUri aParentUri( getParentURL() );
+ uno::Any aEntry
+ = xNA->getByHierarchicalName( aParentUri.getPath() );
+ uno::Reference< container::XNameContainer > xParentContainer;
+ aEntry >>= xParentContainer;
+
+ if ( !xParentContainer.is() )
+ {
+ OSL_FAIL( "Content::storeData - "
+ "Got no XNameContainer interface!" );
+ return false;
+ }
+
+ xParentContainer->insertByName( m_aProps.aTitle,
+ uno::Any( xNew ) );
+ }
+ catch ( lang::IllegalArgumentException const & )
+ {
+ // insertByName
+ OSL_FAIL( "Content::storeData - insertByName failed!" );
+ return false;
+ }
+ catch ( uno::RuntimeException const & )
+ {
+ throw;
+ }
+ catch ( container::ElementExistException const & )
+ {
+ // insertByName
+ OSL_FAIL( "Content::storeData - insertByName failed!" );
+ return false;
+ }
+ catch ( lang::WrappedTargetException const & )
+ {
+ // insertByName
+ OSL_FAIL( "Content::storeData - insertByName failed!" );
+ return false;
+ }
+ catch ( container::NoSuchElementException const & )
+ {
+ // getByHierarchicalName
+ OSL_FAIL( "Content::storeData - getByHierarchicalName failed!" );
+ return false;
+ }
+ catch ( uno::Exception const & )
+ {
+ // createInstanceWithArguments
+ OSL_FAIL( "Content::storeData - Error!" );
+ return false;
+ }
+ }
+
+ if ( !xNA->hasByHierarchicalName( m_aUri.getPath() ) )
+ return false;
+
+ try
+ {
+ uno::Reference< beans::XPropertySet > xPropSet;
+ xNA->getByHierarchicalName( m_aUri.getPath() ) >>= xPropSet;
+
+ if ( !xPropSet.is() )
+ {
+ OSL_FAIL( "Content::storeData - Got no XPropertySet interface!" );
+ return false;
+ }
+
+
+ // Store property values...
+
+
+ if ( m_nModifiedProps & MEDIATYPE_MODIFIED )
+ {
+ xPropSet->setPropertyValue(
+ "MediaType",
+ uno::Any( m_aProps.aMediaType ) );
+ m_nModifiedProps &= ~MEDIATYPE_MODIFIED;
+ }
+
+ if ( m_nModifiedProps & COMPRESSED_MODIFIED )
+ {
+ if ( !isFolder() )
+ xPropSet->setPropertyValue(
+ "Compressed",
+ uno::Any( m_aProps.bCompressed ) );
+
+ m_nModifiedProps &= ~COMPRESSED_MODIFIED;
+ }
+
+ if ( m_nModifiedProps & ENCRYPTED_MODIFIED )
+ {
+ if ( !isFolder() )
+ xPropSet->setPropertyValue(
+ "Encrypted",
+ uno::Any( m_aProps.bEncrypted ) );
+
+ m_nModifiedProps &= ~ENCRYPTED_MODIFIED;
+ }
+
+ if ( m_nModifiedProps & ENCRYPTIONKEY_MODIFIED )
+ {
+ if ( !isFolder() )
+ xPropSet->setPropertyValue(
+ "EncryptionKey",
+ uno::Any( m_aProps.aEncryptionKey ) );
+
+ m_nModifiedProps &= ~ENCRYPTIONKEY_MODIFIED;
+ }
+
+
+ // Store data stream...
+
+
+ if ( xStream.is() && !isFolder() )
+ {
+ uno::Reference< io::XActiveDataSink > xSink(
+ xPropSet, uno::UNO_QUERY );
+
+ if ( !xSink.is() )
+ {
+ OSL_FAIL( "Content::storeData - "
+ "Got no XActiveDataSink interface!" );
+ return false;
+ }
+
+ xSink->setInputStream( xStream );
+ }
+
+ return true;
+ }
+ catch ( container::NoSuchElementException const & )
+ {
+ // getByHierarchicalName
+ }
+ catch ( beans::UnknownPropertyException const & )
+ {
+ // setPropertyValue
+ }
+ catch ( beans::PropertyVetoException const & )
+ {
+ // setPropertyValue
+ }
+ catch ( lang::IllegalArgumentException const & )
+ {
+ // setPropertyValue
+ }
+ catch ( lang::WrappedTargetException const & )
+ {
+ // setPropertyValue
+ }
+
+ OSL_FAIL( "Content::storeData - Error!" );
+ return false;
+}
+
+
+bool Content::removeData()
+{
+ osl::Guard< osl::Mutex > aGuard( m_aMutex );
+
+ uno::Reference< container::XHierarchicalNameAccess > xNA = getPackage();
+
+ PackageUri aParentUri( getParentURL() );
+ if ( !xNA->hasByHierarchicalName( aParentUri.getPath() ) )
+ return false;
+
+ try
+ {
+ uno::Any aEntry = xNA->getByHierarchicalName( aParentUri.getPath() );
+ uno::Reference< container::XNameContainer > xContainer;
+ aEntry >>= xContainer;
+
+ if ( !xContainer.is() )
+ {
+ OSL_FAIL( "Content::removeData - "
+ "Got no XNameContainer interface!" );
+ return false;
+ }
+
+ xContainer->removeByName( m_aUri.getName() );
+ return true;
+ }
+ catch ( container::NoSuchElementException const & )
+ {
+ // getByHierarchicalName, removeByName
+ }
+ catch ( lang::WrappedTargetException const & )
+ {
+ // removeByName
+ }
+
+ OSL_FAIL( "Content::removeData - Error!" );
+ return false;
+}
+
+
+bool Content::flushData()
+{
+ osl::Guard< osl::Mutex > aGuard( m_aMutex );
+
+ // Note: XChangesBatch is only implemented by the package itself, not
+ // by the single entries. Maybe this has to change...
+
+ uno::Reference< container::XHierarchicalNameAccess > xNA = getPackage();
+
+ uno::Reference< util::XChangesBatch > xBatch( xNA, uno::UNO_QUERY );
+ if ( !xBatch.is() )
+ {
+ OSL_FAIL( "Content::flushData - Got no XChangesBatch interface!" );
+ return false;
+ }
+
+ try
+ {
+ xBatch->commitChanges();
+ return true;
+ }
+ catch ( lang::WrappedTargetException const & )
+ {
+ }
+
+ OSL_FAIL( "Content::flushData - Error!" );
+ return false;
+}
+
+
+uno::Reference< io::XInputStream > Content::getInputStream()
+{
+ osl::Guard< osl::Mutex > aGuard( m_aMutex );
+
+ uno::Reference< io::XInputStream > xStream;
+ uno::Reference< container::XHierarchicalNameAccess > xNA = getPackage();
+
+ if ( !xNA->hasByHierarchicalName( m_aUri.getPath() ) )
+ return xStream;
+
+ try
+ {
+ uno::Any aEntry = xNA->getByHierarchicalName( m_aUri.getPath() );
+ uno::Reference< io::XActiveDataSink > xSink;
+ aEntry >>= xSink;
+
+ if ( !xSink.is() )
+ {
+ OSL_FAIL( "Content::getInputStream - "
+ "Got no XActiveDataSink interface!" );
+ return xStream;
+ }
+
+ xStream = xSink->getInputStream();
+
+ OSL_ENSURE( xStream.is(),
+ "Content::getInputStream - Got no stream!" );
+ }
+ catch ( container::NoSuchElementException const & )
+ {
+ // getByHierarchicalName
+ }
+
+ return xStream;
+}
+
+
+uno::Reference< container::XEnumeration > Content::getIterator()
+{
+ osl::Guard< osl::Mutex > aGuard( m_aMutex );
+
+ uno::Reference< container::XEnumeration > xIter;
+ uno::Reference< container::XHierarchicalNameAccess > xNA = getPackage();
+
+ if ( !xNA->hasByHierarchicalName( m_aUri.getPath() ) )
+ return xIter;
+
+ try
+ {
+ uno::Any aEntry = xNA->getByHierarchicalName( m_aUri.getPath() );
+ uno::Reference< container::XEnumerationAccess > xIterFac;
+ aEntry >>= xIterFac;
+
+ if ( !xIterFac.is() )
+ {
+ OSL_FAIL( "Content::getIterator - "
+ "Got no XEnumerationAccess interface!" );
+ return xIter;
+ }
+
+ xIter = xIterFac->createEnumeration();
+
+ OSL_ENSURE( xIter.is(),
+ "Content::getIterator - Got no iterator!" );
+ }
+ catch ( container::NoSuchElementException const & )
+ {
+ // getByHierarchicalName
+ }
+
+ return xIter;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/ucb/source/ucp/package/pkgcontent.hxx b/ucb/source/ucp/package/pkgcontent.hxx
new file mode 100644
index 0000000000..e1eebad50f
--- /dev/null
+++ b/ucb/source/ucp/package/pkgcontent.hxx
@@ -0,0 +1,276 @@
+/* -*- 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:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#pragma once
+
+#include <sal/config.h>
+
+#include <string_view>
+#include <vector>
+#include <rtl/ref.hxx>
+
+#include <com/sun/star/ucb/XContentCreator.hpp>
+#include <ucbhelper/contenthelper.hxx>
+#include "pkguri.hxx"
+
+namespace com::sun::star {
+ namespace beans
+ {
+ struct Property;
+ struct PropertyValue;
+ }
+ namespace container
+ {
+ class XHierarchicalNameAccess;
+ class XEnumeration;
+ }
+ namespace io
+ {
+ class XInputStream;
+ }
+ namespace sdbc
+ {
+ class XRow;
+ }
+ namespace ucb
+ {
+ struct OpenCommandArgument2;
+ struct TransferInfo;
+ }
+}
+
+namespace package_ucp
+{
+
+
+struct ContentProperties
+{
+ OUString aTitle; // Title
+ OUString aContentType; // ContentType
+ bool bIsDocument; // IsDocument
+ bool bIsFolder; // IsFolder
+ OUString aMediaType; // MediaType
+ css::uno::Sequence < sal_Int8 > aEncryptionKey; // EncryptionKey
+ sal_Int64 nSize; // Size
+ bool bCompressed; // Compressed
+ bool bEncrypted; // Encrypted
+ bool bHasEncryptedEntries; // HasEncryptedEntries
+
+ ContentProperties()
+ : bIsDocument( true ), bIsFolder( false ), nSize( 0 ),
+ bCompressed( true ), bEncrypted( false ),
+ bHasEncryptedEntries( false ) {}
+
+ explicit ContentProperties( const OUString& rContentType );
+
+ css::uno::Sequence< css::ucb::ContentInfo >
+ getCreatableContentsInfo( PackageUri const & rUri ) const;
+};
+
+
+class ContentProvider;
+
+class Content : public ::ucbhelper::ContentImplHelper,
+ public css::ucb::XContentCreator
+{
+ enum ContentState { TRANSIENT, // created via CreateNewContent,
+ // but did not process "insert" yet
+ PERSISTENT, // processed "insert"
+ DEAD // processed "delete"
+ };
+
+ PackageUri m_aUri;
+ ContentProperties m_aProps;
+ ContentState m_eState;
+ css::uno::Reference<
+ css::container::XHierarchicalNameAccess > m_xPackage;
+ ContentProvider* m_pProvider;
+ sal_uInt32 m_nModifiedProps;
+
+private:
+ Content( const css::uno::Reference< css::uno::XComponentContext >& rxContext,
+ ContentProvider* pProvider,
+ const css::uno::Reference< css::ucb::XContentIdentifier >& Identifier,
+ css::uno::Reference< css::container::XHierarchicalNameAccess > Package,
+ PackageUri aUri,
+ ContentProperties aProps );
+ Content( const css::uno::Reference< css::uno::XComponentContext >& rxContext,
+ ContentProvider* pProvider,
+ const css::uno::Reference< css::ucb::XContentIdentifier >& Identifier,
+ css::uno::Reference< css::container::XHierarchicalNameAccess > Package,
+ PackageUri aUri,
+ const css::ucb::ContentInfo& Info );
+
+ 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;
+
+ static css::uno::Reference< css::sdbc::XRow >
+ getPropertyValues( const css::uno::Reference< css::uno::XComponentContext >& rxContext,
+ const css::uno::Sequence< css::beans::Property >& rProperties,
+ const ContentProperties& rData,
+ const rtl::Reference< ::ucbhelper::ContentProviderImplHelper >& rProvider,
+ const OUString& rContentId );
+
+ css::uno::Reference< css::sdbc::XRow >
+ getPropertyValues( const css::uno::Sequence< css::beans::Property >& rProperties );
+ /// @throws css::uno::Exception
+ css::uno::Sequence< css::uno::Any >
+ setPropertyValues( const css::uno::Sequence< css::beans::PropertyValue >& rValues,
+ const css::uno::Reference< css::ucb::XCommandEnvironment > & xEnv );
+
+ css::uno::Reference< css::container::XHierarchicalNameAccess >
+ getPackage( const PackageUri& rURI );
+ css::uno::Reference< css::container::XHierarchicalNameAccess >
+ getPackage();
+
+ static bool
+ loadData( ContentProvider* pProvider,
+ const PackageUri& rURI,
+ ContentProperties& rProps,
+ css::uno::Reference< css::container::XHierarchicalNameAccess > & rxPackage );
+ static bool
+ hasData( ContentProvider* pProvider,
+ const PackageUri& rURI,
+ css::uno::Reference< css::container::XHierarchicalNameAccess > & rxPackage );
+
+ bool
+ hasData( const PackageUri& rURI );
+ void
+ renameData( const css::uno::Reference< css::ucb::XContentIdentifier >& xOldId,
+ const css::uno::Reference< css::ucb::XContentIdentifier >& xNewId );
+ bool
+ storeData( const css::uno::Reference< css::io::XInputStream >& xStream );
+ bool
+ removeData();
+
+ bool
+ flushData();
+
+ typedef rtl::Reference< Content > ContentRef;
+ typedef std::vector< ContentRef > ContentRefList;
+ void queryChildren( ContentRefList& rChildren );
+
+ bool
+ exchangeIdentity( const css::uno::Reference<
+ css::ucb::XContentIdentifier >& xNewId );
+
+ /// @throws css::uno::Exception
+ css::uno::Any
+ open( const css::ucb::OpenCommandArgument2& rArg,
+ const css::uno::Reference< css::ucb::XCommandEnvironment > & xEnv );
+
+ /// @throws css::uno::Exception
+ void insert( const css::uno::Reference< css::io::XInputStream >& xStream,
+ sal_Int32 nNameClashResolve,
+ const css::uno::Reference<
+ css::ucb::XCommandEnvironment > & xEnv );
+
+ /// @throws css::uno::Exception
+ void destroy( bool bDeletePhysical,
+ const css::uno::Reference< css::ucb::XCommandEnvironment > & xEnv );
+
+ /// @throws css::uno::Exception
+ void transfer( const css::ucb::TransferInfo& rInfo,
+ const css::uno::Reference< css::ucb::XCommandEnvironment > & xEnv );
+
+ css::uno::Reference< css::io::XInputStream >
+ getInputStream();
+
+ bool isFolder() const { return m_aProps.bIsFolder; }
+
+public:
+ // Create existing content. Fail, if not already exists.
+ static rtl::Reference<Content> create(
+ const css::uno::Reference< css::uno::XComponentContext >& rxContext,
+ ContentProvider* pProvider,
+ const css::uno::Reference< css::ucb::XContentIdentifier >& Identifier );
+
+ // Create new content. Fail, if already exists.
+ static rtl::Reference<Content> create(
+ const css::uno::Reference< css::uno::XComponentContext >& rxContext,
+ ContentProvider* pProvider,
+ const css::uno::Reference< css::ucb::XContentIdentifier >& Identifier,
+ const css::ucb::ContentInfo& Info );
+
+ virtual ~Content() 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 css::uno::Sequence< OUString > SAL_CALL
+ getSupportedServiceNames() override;
+
+ // XContent
+ virtual OUString SAL_CALL
+ getContentType() override;
+
+ // XCommandProcessor
+ 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;
+
+
+ // Additional interfaces
+
+
+ // XContentCreator
+ 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;
+
+
+ // Non-interface methods.
+
+
+ // Called from resultset data supplier.
+ static css::uno::Reference< css::sdbc::XRow >
+ getPropertyValues( const css::uno::Reference< css::uno::XComponentContext >& rxContext,
+ const css::uno::Sequence< css::beans::Property >& rProperties,
+ ContentProvider* pProvider,
+ const OUString& rContentId );
+
+ // Called from resultset data supplier.
+ css::uno::Reference< css::container::XEnumeration >
+ getIterator();
+
+ static OUString
+ getContentType( std::u16string_view aScheme, bool bFolder );
+};
+
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/ucb/source/ucp/package/pkgcontentcaps.cxx b/ucb/source/ucp/package/pkgcontentcaps.cxx
new file mode 100644
index 0000000000..c01201d10a
--- /dev/null
+++ b/ucb/source/ucp/package/pkgcontentcaps.cxx
@@ -0,0 +1,504 @@
+/* -*- 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:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+
+/**************************************************************************
+ TODO
+ **************************************************************************
+
+ Props/Commands:
+
+ rootfolder folder stream
+ ---------------------------------------------
+ ContentType r r r
+ IsDocument r r r
+ IsFolder r r r
+ MediaType (w) (w) w
+ Title r w w
+ Size - - r
+ CreatableContentsInfo r r r
+ Compressed - - w
+ Encrypted - - w
+ HasEncryptedEntries r - -
+
+ getCommandInfo x x x
+ getPropertySetInfo x x x
+ getPropertyValues x x x
+ setPropertyValues x x x
+ insert - x x
+ delete - x x
+ open x x x
+ transfer x x -
+ flush x x -
+ createNewContent x x -
+
+ *************************************************************************/
+#include <com/sun/star/beans/Property.hpp>
+#include <com/sun/star/beans/PropertyAttribute.hpp>
+#include <com/sun/star/beans/PropertyValue.hpp>
+#include <com/sun/star/ucb/CommandInfo.hpp>
+#include <com/sun/star/ucb/OpenCommandArgument2.hpp>
+#include <com/sun/star/ucb/TransferInfo.hpp>
+#include <com/sun/star/uno/Sequence.hxx>
+#include <sal/macros.h>
+#include "pkgcontent.hxx"
+
+using namespace com::sun::star;
+using namespace package_ucp;
+
+
+// Content implementation.
+
+
+#define MAKEPROPSEQUENCE( a ) \
+ uno::Sequence< beans::Property >( a, SAL_N_ELEMENTS( a ) )
+
+#define MAKECMDSEQUENCE( a ) \
+ uno::Sequence< ucb::CommandInfo >( a, SAL_N_ELEMENTS( a ) )
+
+
+// IMPORTANT: If any property data ( name / type / ... ) are changed, then
+// Content::getPropertyValues(...) must be adapted too!
+
+
+// virtual
+uno::Sequence< beans::Property > Content::getProperties(
+ const uno::Reference< ucb::XCommandEnvironment > & /*xEnv*/ )
+{
+ osl::Guard< osl::Mutex > aGuard( m_aMutex );
+
+ if ( isFolder() )
+ {
+ if ( m_aUri.isRootFolder() )
+ {
+
+
+ // Root Folder: Supported properties
+
+
+ static const beans::Property aRootFolderPropertyInfoTable[] =
+ {
+
+ // Required properties
+
+ beans::Property(
+ "ContentType",
+ -1,
+ cppu::UnoType<OUString>::get(),
+ beans::PropertyAttribute::BOUND
+ | beans::PropertyAttribute::READONLY
+ ),
+ 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::PropertyAttribute::READONLY
+ ),
+
+ // Optional standard properties
+
+ beans::Property(
+ "MediaType",
+ -1,
+ cppu::UnoType<OUString>::get(),
+ beans::PropertyAttribute::BOUND
+ ),
+ beans::Property(
+ "CreatableContentsInfo",
+ -1,
+ cppu::UnoType<uno::Sequence< ucb::ContentInfo >>::get(),
+ beans::PropertyAttribute::BOUND
+ | beans::PropertyAttribute::READONLY
+ ),
+
+ // New properties
+
+ beans::Property(
+ "HasEncryptedEntries",
+ -1,
+ cppu::UnoType<bool>::get(),
+ beans::PropertyAttribute::BOUND
+ | beans::PropertyAttribute::READONLY
+ )
+ };
+ return MAKEPROPSEQUENCE( aRootFolderPropertyInfoTable );
+ }
+ else
+ {
+
+
+ // Folder: Supported properties
+
+
+ static const beans::Property aFolderPropertyInfoTable[] =
+ {
+
+ // Required properties
+
+ beans::Property(
+ "ContentType",
+ -1,
+ cppu::UnoType<OUString>::get(),
+ beans::PropertyAttribute::BOUND
+ | beans::PropertyAttribute::READONLY
+ ),
+ 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
+ ),
+
+ // Optional standard properties
+
+ beans::Property(
+ "MediaType",
+ -1,
+ cppu::UnoType<OUString>::get(),
+ beans::PropertyAttribute::BOUND
+ ),
+ beans::Property(
+ "CreatableContentsInfo",
+ -1,
+ cppu::UnoType<uno::Sequence< ucb::ContentInfo >>::get(),
+ beans::PropertyAttribute::BOUND
+ | beans::PropertyAttribute::READONLY
+ )
+
+ // New properties
+
+ };
+ return MAKEPROPSEQUENCE( aFolderPropertyInfoTable );
+ }
+ }
+ else
+ {
+
+
+ // Stream: Supported properties
+
+
+ static const beans::Property aStreamPropertyInfoTable[] =
+ {
+
+ // Required properties
+
+ beans::Property(
+ "ContentType",
+ -1,
+ cppu::UnoType<OUString>::get(),
+ beans::PropertyAttribute::BOUND
+ | beans::PropertyAttribute::READONLY
+ ),
+ 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
+ ),
+
+ // Optional standard properties
+
+ beans::Property(
+ "MediaType",
+ -1,
+ cppu::UnoType<OUString>::get(),
+ beans::PropertyAttribute::BOUND
+ ),
+ 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
+ ),
+
+ // New properties
+
+ beans::Property(
+ "Compressed",
+ -1,
+ cppu::UnoType<bool>::get(),
+ beans::PropertyAttribute::BOUND
+ ),
+ beans::Property(
+ "Encrypted",
+ -1,
+ cppu::UnoType<bool>::get(),
+ beans::PropertyAttribute::BOUND
+ )
+ };
+ return MAKEPROPSEQUENCE( aStreamPropertyInfoTable );
+ }
+}
+
+
+// virtual
+uno::Sequence< ucb::CommandInfo > Content::getCommands(
+ const uno::Reference< ucb::XCommandEnvironment > & /*xEnv*/ )
+{
+ osl::Guard< osl::Mutex > aGuard( m_aMutex );
+
+ if ( isFolder() )
+ {
+ if ( m_aUri.isRootFolder() )
+ {
+
+
+ // Root Folder: Supported commands
+
+
+ static const ucb::CommandInfo aRootFolderCommandInfoTable[] =
+ {
+
+ // 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()
+ ),
+ ucb::CommandInfo(
+ "transfer",
+ -1,
+ cppu::UnoType<ucb::TransferInfo>::get()
+ ),
+ ucb::CommandInfo(
+ "createNewContent",
+ -1,
+ cppu::UnoType<ucb::ContentInfo>::get()
+ ),
+
+ // New commands
+
+ ucb::CommandInfo(
+ "flush",
+ -1,
+ cppu::UnoType<void>::get()
+ )
+ };
+
+ return MAKECMDSEQUENCE( aRootFolderCommandInfoTable );
+ }
+ else
+ {
+
+
+ // Folder: Supported commands
+
+
+ static const ucb::CommandInfo aFolderCommandInfoTable[] =
+ {
+
+ // 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<void>::get()
+ ),
+ ucb::CommandInfo(
+ "open",
+ -1,
+ cppu::UnoType<ucb::OpenCommandArgument2>::get()
+ ),
+ ucb::CommandInfo(
+ "transfer",
+ -1,
+ cppu::UnoType<ucb::TransferInfo>::get()
+ ),
+ ucb::CommandInfo(
+ "createNewContent",
+ -1,
+ cppu::UnoType<ucb::ContentInfo>::get()
+ ),
+
+ // New commands
+
+ ucb::CommandInfo(
+ "flush",
+ -1,
+ cppu::UnoType<void>::get()
+ )
+ };
+
+ return MAKECMDSEQUENCE( aFolderCommandInfoTable );
+ }
+ }
+ else
+ {
+
+
+ // Stream: Supported commands
+
+
+ static const ucb::CommandInfo aStreamCommandInfoTable[] =
+ {
+
+ // 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<void>::get()
+ ),
+ ucb::CommandInfo(
+ "open",
+ -1,
+ cppu::UnoType<ucb::OpenCommandArgument2>::get()
+ )
+
+ // New commands
+
+ };
+
+ return MAKECMDSEQUENCE( aStreamCommandInfoTable );
+ }
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/ucb/source/ucp/package/pkgdatasupplier.cxx b/ucb/source/ucp/package/pkgdatasupplier.cxx
new file mode 100644
index 0000000000..436f7023d3
--- /dev/null
+++ b/ucb/source/ucp/package/pkgdatasupplier.cxx
@@ -0,0 +1,425 @@
+/* -*- 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:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+
+/**************************************************************************
+ TODO
+ **************************************************************************
+
+ *************************************************************************/
+
+#include <osl/diagnose.h>
+#include <com/sun/star/container/XNamed.hpp>
+#include <com/sun/star/ucb/IllegalIdentifierException.hpp>
+#include <com/sun/star/ucb/ResultSetException.hpp>
+#include <ucbhelper/contentidentifier.hxx>
+#include <ucbhelper/providerhelper.hxx>
+#include <utility>
+#include "pkgdatasupplier.hxx"
+#include "pkgcontent.hxx"
+#include "pkgprovider.hxx"
+
+#include "../inc/urihelper.hxx"
+
+using namespace com::sun::star;
+using namespace package_ucp;
+
+// DataSupplier Implementation.
+
+
+DataSupplier::DataSupplier(
+ uno::Reference< uno::XComponentContext > xContext,
+ const rtl::Reference< Content >& rContent )
+: m_xContent( rContent ), m_xContext(std::move( xContext )),
+ m_xFolderEnum( rContent->getIterator() ),
+ m_bCountFinal( !m_xFolderEnum.is() ), m_bThrowException( m_bCountFinal )
+{
+}
+
+
+// virtual
+DataSupplier::~DataSupplier()
+{
+}
+
+
+// virtual
+OUString DataSupplier::queryContentIdentifierString( sal_uInt32 nIndex )
+{
+ std::unique_lock aGuard( m_aMutex );
+ return queryContentIdentifierStringImpl(aGuard, nIndex);
+}
+
+OUString DataSupplier::queryContentIdentifierStringImpl( std::unique_lock<std::mutex>& rGuard, sal_uInt32 nIndex )
+{
+ if ( nIndex < m_aResults.size() )
+ {
+ OUString aId = m_aResults[ nIndex ].aURL;
+ if ( !aId.isEmpty() )
+ {
+ // Already cached.
+ return aId;
+ }
+ }
+
+ if ( getResultImpl( rGuard, nIndex ) )
+ {
+ // Note: getResult fills m_aResults[ nIndex ].aURL.
+ return m_aResults[ nIndex ].aURL;
+ }
+ return OUString();
+}
+
+
+// virtual
+uno::Reference< ucb::XContentIdentifier >
+DataSupplier::queryContentIdentifier( sal_uInt32 nIndex )
+{
+ std::unique_lock aGuard( m_aMutex );
+ return queryContentIdentifierImpl(aGuard, nIndex);
+}
+
+uno::Reference< ucb::XContentIdentifier >
+DataSupplier::queryContentIdentifierImpl( std::unique_lock<std::mutex>& rGuard, sal_uInt32 nIndex )
+{
+ if ( nIndex < m_aResults.size() )
+ {
+ uno::Reference< ucb::XContentIdentifier >& xId
+ = m_aResults[ nIndex ].xId;
+ if ( xId.is() )
+ {
+ // Already cached.
+ return xId;
+ }
+ }
+
+ OUString aId = queryContentIdentifierStringImpl( rGuard, nIndex );
+ if ( !aId.isEmpty() )
+ {
+ uno::Reference< ucb::XContentIdentifier > xId
+ = new ::ucbhelper::ContentIdentifier( aId );
+ m_aResults[ nIndex ].xId = xId;
+ return xId;
+ }
+ return uno::Reference< ucb::XContentIdentifier >();
+}
+
+
+// virtual
+uno::Reference< ucb::XContent > DataSupplier::queryContent(
+ sal_uInt32 nIndex )
+{
+ std::unique_lock aGuard( m_aMutex );
+
+ if ( nIndex < m_aResults.size() )
+ {
+ uno::Reference< ucb::XContent >& xContent
+ = m_aResults[ nIndex ].xContent;
+ if ( xContent.is() )
+ {
+ // Already cached.
+ return xContent;
+ }
+ }
+
+ uno::Reference< ucb::XContentIdentifier > xId
+ = queryContentIdentifierImpl( aGuard, nIndex );
+ if ( xId.is() )
+ {
+ try
+ {
+ uno::Reference< ucb::XContent > xContent
+ = m_xContent->getProvider()->queryContent( xId );
+ m_aResults[ nIndex ].xContent = xContent;
+ return xContent;
+
+ }
+ catch ( ucb::IllegalIdentifierException const & )
+ {
+ }
+ }
+ return uno::Reference< ucb::XContent >();
+}
+
+
+// virtual
+bool DataSupplier::getResult( sal_uInt32 nIndex )
+{
+ std::unique_lock aGuard( m_aMutex );
+ return getResultImpl(aGuard, nIndex);
+}
+
+bool DataSupplier::getResultImpl( std::unique_lock<std::mutex>& rGuard, sal_uInt32 nIndex )
+{
+ if ( m_aResults.size() > nIndex )
+ {
+ // Result already present.
+ return true;
+ }
+
+ // Result not (yet) present.
+
+ if ( m_bCountFinal )
+ return false;
+
+ // Try to obtain result...
+
+ sal_uInt32 nOldCount = m_aResults.size();
+ bool bFound = false;
+ sal_uInt32 nPos = nOldCount;
+
+ while ( m_xFolderEnum->hasMoreElements() )
+ {
+ try
+ {
+ uno::Reference< container::XNamed > xNamed;
+ m_xFolderEnum->nextElement() >>= xNamed;
+
+ if ( !xNamed.is() )
+ {
+ OSL_FAIL( "DataSupplier::getResult - Got no XNamed!" );
+ break;
+ }
+
+ OUString aName = xNamed->getName();
+
+ if ( aName.isEmpty() )
+ {
+ OSL_FAIL( "DataSupplier::getResult - Empty name!" );
+ break;
+ }
+
+ // Assemble URL for child.
+ OUString aURL = assembleChildURL( aName );
+
+ m_aResults.push_back( ResultListEntry( aURL ) );
+
+ if ( nPos == nIndex )
+ {
+ // Result obtained.
+ bFound = true;
+ break;
+ }
+
+ nPos++;
+ }
+ catch ( container::NoSuchElementException const & )
+ {
+ m_bThrowException = true;
+ break;
+ }
+ catch ( lang::WrappedTargetException const & )
+ {
+ m_bThrowException = true;
+ break;
+ }
+ }
+
+ if ( !bFound )
+ m_bCountFinal = true;
+
+ rtl::Reference< ::ucbhelper::ResultSet > xResultSet = getResultSet();
+ if ( xResultSet.is() )
+ {
+ // Callbacks follow!
+ rGuard.unlock();
+
+ if ( nOldCount < m_aResults.size() )
+ xResultSet->rowCountChanged(
+ nOldCount, m_aResults.size() );
+
+ if ( m_bCountFinal )
+ xResultSet->rowCountFinal();
+
+ rGuard.lock();
+ }
+
+ return bFound;
+}
+
+
+// virtual
+sal_uInt32 DataSupplier::totalCount()
+{
+ std::unique_lock aGuard( m_aMutex );
+
+ if ( m_bCountFinal )
+ return m_aResults.size();
+
+ sal_uInt32 nOldCount = m_aResults.size();
+
+ while ( m_xFolderEnum->hasMoreElements() )
+ {
+ try
+ {
+ uno::Reference< container::XNamed > xNamed;
+ m_xFolderEnum->nextElement() >>= xNamed;
+
+ if ( !xNamed.is() )
+ {
+ OSL_FAIL( "DataSupplier::getResult - Got no XNamed!" );
+ break;
+ }
+
+ OUString aName = xNamed->getName();
+
+ if ( aName.isEmpty() )
+ {
+ OSL_FAIL( "DataSupplier::getResult - Empty name!" );
+ break;
+ }
+
+ // Assemble URL for child.
+ OUString aURL = assembleChildURL( aName );
+
+ m_aResults.push_back( ResultListEntry( aURL ) );
+ }
+ catch ( container::NoSuchElementException const & )
+ {
+ m_bThrowException = true;
+ break;
+ }
+ catch ( lang::WrappedTargetException const & )
+ {
+ m_bThrowException = true;
+ break;
+ }
+ }
+
+ m_bCountFinal = true;
+
+ rtl::Reference< ::ucbhelper::ResultSet > xResultSet = getResultSet();
+ if ( xResultSet.is() )
+ {
+ // Callbacks follow!
+ aGuard.unlock();
+
+ if ( nOldCount < m_aResults.size() )
+ xResultSet->rowCountChanged(
+ nOldCount, m_aResults.size() );
+
+ xResultSet->rowCountFinal();
+ }
+
+ return m_aResults.size();
+}
+
+
+// virtual
+sal_uInt32 DataSupplier::currentCount()
+{
+ return m_aResults.size();
+}
+
+
+// virtual
+bool DataSupplier::isCountFinal()
+{
+ return m_bCountFinal;
+}
+
+
+// virtual
+uno::Reference< sdbc::XRow > DataSupplier::queryPropertyValues(
+ sal_uInt32 nIndex )
+{
+ std::unique_lock aGuard( m_aMutex );
+
+ if ( nIndex < m_aResults.size() )
+ {
+ uno::Reference< sdbc::XRow >& xRow = m_aResults[ nIndex ].xRow;
+ if ( xRow.is() )
+ {
+ // Already cached.
+ return xRow;
+ }
+ }
+
+ if ( getResultImpl( aGuard, nIndex ) )
+ {
+ uno::Reference< sdbc::XRow > xRow = Content::getPropertyValues(
+ m_xContext,
+ getResultSet()->getProperties(),
+ static_cast< ContentProvider * >(
+ m_xContent->getProvider().get() ),
+ queryContentIdentifierStringImpl( aGuard, nIndex ) );
+ m_aResults[ nIndex ].xRow = xRow;
+ return xRow;
+ }
+
+ return uno::Reference< sdbc::XRow >();
+}
+
+
+// virtual
+void DataSupplier::releasePropertyValues( sal_uInt32 nIndex )
+{
+ std::unique_lock aGuard( m_aMutex );
+
+ if ( nIndex < m_aResults.size() )
+ m_aResults[ nIndex ].xRow.clear();
+}
+
+
+// virtual
+void DataSupplier::close()
+{
+}
+
+
+// virtual
+void DataSupplier::validate()
+{
+ if ( m_bThrowException )
+ throw ucb::ResultSetException();
+}
+
+
+OUString DataSupplier::assembleChildURL( const OUString& aName )
+{
+ OUString aURL;
+ OUString aContURL
+ = m_xContent->getIdentifier()->getContentIdentifier();
+ sal_Int32 nParam = aContURL.indexOf( '?' );
+ if ( nParam >= 0 )
+ {
+ aURL = aContURL.copy( 0, nParam );
+
+ sal_Int32 nPackageUrlEnd = aURL.lastIndexOf( '/' );
+ if ( nPackageUrlEnd != aURL.getLength() - 1 )
+ aURL += "/";
+
+ aURL += ::ucb_impl::urihelper::encodeSegment( aName ) +
+ aContURL.subView( nParam );
+ }
+ else
+ {
+ aURL = aContURL;
+
+ sal_Int32 nPackageUrlEnd = aURL.lastIndexOf( '/' );
+ if ( nPackageUrlEnd != aURL.getLength() - 1 )
+ aURL += "/";
+
+ aURL += ::ucb_impl::urihelper::encodeSegment( aName );
+ }
+ return aURL;
+}
+
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/ucb/source/ucp/package/pkgdatasupplier.hxx b/ucb/source/ucp/package/pkgdatasupplier.hxx
new file mode 100644
index 0000000000..1872ef26e4
--- /dev/null
+++ b/ucb/source/ucp/package/pkgdatasupplier.hxx
@@ -0,0 +1,87 @@
+/* -*- 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:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#pragma once
+
+#include <rtl/ref.hxx>
+#include <ucbhelper/resultset.hxx>
+#include <com/sun/star/container/XEnumeration.hpp>
+#include <mutex>
+#include <utility>
+#include <vector>
+
+namespace package_ucp {
+
+class Content;
+
+class DataSupplier : public ::ucbhelper::ResultSetDataSupplier
+{
+public:
+ DataSupplier( css::uno::Reference< css::uno::XComponentContext > xContext,
+ const rtl::Reference< Content >& rContent );
+ 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;
+
+ OUString assembleChildURL( const OUString& aName );
+
+private:
+ bool getResultImpl( std::unique_lock<std::mutex>&, sal_uInt32 nIndex );
+ OUString queryContentIdentifierStringImpl( std::unique_lock<std::mutex>&, sal_uInt32 nIndex );
+ css::uno::Reference< css::ucb::XContentIdentifier > queryContentIdentifierImpl( std::unique_lock<std::mutex>&, sal_uInt32 nIndex );
+
+ struct ResultListEntry
+ {
+ OUString aURL;
+ css::uno::Reference< css::ucb::XContentIdentifier > xId;
+ css::uno::Reference< css::ucb::XContent > xContent;
+ css::uno::Reference< css::sdbc::XRow > xRow;
+
+ explicit ResultListEntry(OUString _aURL ) : aURL(std::move( _aURL )) {}
+ };
+ std::mutex m_aMutex;
+ std::vector< ResultListEntry > m_aResults;
+ rtl::Reference< Content > m_xContent;
+ css::uno::Reference< css::uno::XComponentContext > m_xContext;
+ css::uno::Reference< css::container::XEnumeration > m_xFolderEnum;
+ bool m_bCountFinal;
+ bool m_bThrowException;
+};
+
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/ucb/source/ucp/package/pkgprovider.cxx b/ucb/source/ucp/package/pkgprovider.cxx
new file mode 100644
index 0000000000..636ecb3333
--- /dev/null
+++ b/ucb/source/ucp/package/pkgprovider.cxx
@@ -0,0 +1,272 @@
+/* -*- 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:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+
+/**************************************************************************
+ TODO
+ **************************************************************************
+
+ *************************************************************************/
+
+#include <cppuhelper/exc_hlp.hxx>
+#include <cppuhelper/weak.hxx>
+#include <cppuhelper/queryinterface.hxx>
+#include <ucbhelper/contentidentifier.hxx>
+#include <ucbhelper/macros.hxx>
+#include <com/sun/star/container/XHierarchicalNameAccess.hpp>
+#include <com/sun/star/lang/WrappedTargetRuntimeException.hpp>
+#include <com/sun/star/ucb/IllegalIdentifierException.hpp>
+#include <com/sun/star/uno/XComponentContext.hpp>
+#include "pkgprovider.hxx"
+#include "pkgcontent.hxx"
+#include "pkguri.hxx"
+#include <unordered_map>
+#include <utility>
+
+using namespace com::sun::star;
+
+namespace package_ucp
+{
+
+
+
+namespace {
+
+class Package : public cppu::OWeakObject,
+ public container::XHierarchicalNameAccess
+{
+ friend ContentProvider;
+
+ OUString m_aName;
+ uno::Reference< container::XHierarchicalNameAccess > m_xNA;
+ ContentProvider* m_pOwner;
+
+public:
+ Package( OUString aName,
+ uno::Reference< container::XHierarchicalNameAccess > xNA,
+ ContentProvider* pOwner )
+ : m_aName(std::move( aName )), m_xNA(std::move( xNA )), m_pOwner( pOwner ) {}
+ virtual ~Package() override { m_pOwner->removePackage( m_aName ); }
+
+ // XInterface
+ virtual uno::Any SAL_CALL
+ queryInterface( const uno::Type& aType ) override
+ { return m_xNA->queryInterface( aType ); }
+ virtual void SAL_CALL
+ acquire() noexcept override
+ { OWeakObject::acquire(); }
+ virtual void SAL_CALL
+ release() noexcept override
+ { OWeakObject::release(); }
+
+ // XHierarchicalNameAccess
+ virtual uno::Any SAL_CALL
+ getByHierarchicalName( const OUString& aName ) override
+ { return m_xNA->getByHierarchicalName( aName ); }
+ virtual sal_Bool SAL_CALL
+ hasByHierarchicalName( const OUString& aName ) override
+ { return m_xNA->hasByHierarchicalName( aName ); }
+};
+
+}
+
+class Packages : public std::unordered_map<OUString, Package*> {};
+
+}
+
+using namespace package_ucp;
+
+
+// ContentProvider Implementation.
+ContentProvider::ContentProvider(
+ const uno::Reference< uno::XComponentContext >& rxContext )
+: ::ucbhelper::ContentProviderImplHelper( rxContext )
+{
+}
+
+
+// virtual
+ContentProvider::~ContentProvider()
+{
+}
+
+// XInterface methods.
+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< ucb::XContentProvider* >(this)
+ );
+ return aRet.hasValue() ? aRet : OWeakObject::queryInterface( rType );
+}
+
+// XTypeProvider methods.
+
+
+XTYPEPROVIDER_IMPL_3( ContentProvider,
+ lang::XTypeProvider,
+ lang::XServiceInfo,
+ ucb::XContentProvider );
+
+
+// XServiceInfo methods.
+
+OUString
+ContentProvider::getImplementationName()
+{
+ return "com.sun.star.comp.ucb.PackageContentProvider";
+}
+
+sal_Bool
+ContentProvider::supportsService(const OUString& s)
+{
+ return cppu::supportsService(this, s);
+}
+
+css::uno::Sequence< OUString >
+ContentProvider::getSupportedServiceNames()
+{
+ return { "com.sun.star.ucb.PackageContentProvider" };
+}
+
+
+// XContentProvider methods.
+
+
+// virtual
+uno::Reference< ucb::XContent > SAL_CALL ContentProvider::queryContent(
+ const uno::Reference< ucb::XContentIdentifier >& Identifier )
+{
+ if ( !Identifier.is() )
+ return uno::Reference< ucb::XContent >();
+
+ PackageUri aUri( Identifier->getContentIdentifier() );
+ if ( !aUri.isValid() )
+ throw ucb::IllegalIdentifierException();
+
+ // Create a new identifier for the normalized URL returned by
+ // PackageUri::getUri().
+ uno::Reference< ucb::XContentIdentifier > xId = new ::ucbhelper::ContentIdentifier( aUri.getUri() );
+
+ osl::MutexGuard aGuard( m_aMutex );
+
+ // Check, if a content with given id already exists...
+ uno::Reference< ucb::XContent > xContent
+ = queryExistingContent( xId );
+ if ( xContent.is() )
+ return xContent;
+
+ // Create a new content.
+
+ xContent = Content::create( m_xContext, this, Identifier ); // not xId!!!
+ registerNewContent( xContent );
+
+ if ( xContent.is() && !xContent->getIdentifier().is() )
+ throw ucb::IllegalIdentifierException();
+
+ return xContent;
+}
+
+
+// Other methods.
+
+
+uno::Reference< container::XHierarchicalNameAccess >
+ContentProvider::createPackage( const PackageUri & rURI )
+{
+ osl::MutexGuard aGuard( m_aMutex );
+
+ OUString rURL = rURI.getPackage() + rURI.getParam();
+
+ if ( m_pPackages )
+ {
+ Packages::const_iterator it = m_pPackages->find( rURL );
+ if ( it != m_pPackages->end() )
+ {
+ // Already instantiated. Return package.
+ return (*it).second->m_xNA;
+ }
+ }
+ else
+ m_pPackages.reset( new Packages );
+
+ // Create new package...
+ uno::Sequence< uno::Any > aArguments{ uno::Any(rURL) };
+ uno::Reference< container::XHierarchicalNameAccess > xNameAccess;
+ try
+ {
+ xNameAccess.set(
+ m_xContext->getServiceManager()->createInstanceWithArgumentsAndContext(
+ "com.sun.star.packages.comp.ZipPackage",
+ aArguments, m_xContext ),
+ css::uno::UNO_QUERY_THROW );
+ }
+ catch ( uno::RuntimeException const & )
+ {
+ throw;
+ }
+ catch ( uno::Exception const & e )
+ {
+ css::uno::Any anyEx = cppu::getCaughtException();
+ throw css::lang::WrappedTargetRuntimeException(
+ e.Message, e.Context, anyEx);
+ }
+
+ rtl::Reference< Package> xPackage = new Package( rURL, xNameAccess, this );
+ (*m_pPackages)[ rURL ] = xPackage.get();
+ return xPackage;
+}
+
+
+void ContentProvider::removePackage( const OUString & rName )
+{
+ osl::MutexGuard aGuard( m_aMutex );
+
+ if ( m_pPackages )
+ {
+ Packages::iterator it = m_pPackages->find( rName );
+ if ( it != m_pPackages->end() )
+ {
+ m_pPackages->erase( it );
+ return;
+ }
+ }
+}
+
+extern "C" SAL_DLLPUBLIC_EXPORT css::uno::XInterface*
+ucb_package_ContentProvider_get_implementation(
+ css::uno::XComponentContext* context, css::uno::Sequence<css::uno::Any> const&)
+{
+ return cppu::acquire(new ContentProvider(context));
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/ucb/source/ucp/package/pkgprovider.hxx b/ucb/source/ucp/package/pkgprovider.hxx
new file mode 100644
index 0000000000..37584e9734
--- /dev/null
+++ b/ucb/source/ucp/package/pkgprovider.hxx
@@ -0,0 +1,86 @@
+/* -*- 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:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#pragma once
+
+#include <memory>
+#include <ucbhelper/providerhelper.hxx>
+#include "pkguri.hxx"
+
+namespace com::sun::star::container {
+ class XHierarchicalNameAccess;
+}
+
+namespace package_ucp {
+
+
+// UCB Content Type.
+#define PACKAGE_FOLDER_CONTENT_TYPE \
+ "application/" PACKAGE_URL_SCHEME "-folder"
+#define PACKAGE_STREAM_CONTENT_TYPE \
+ "application/" PACKAGE_URL_SCHEME "-stream"
+#define PACKAGE_ZIP_FOLDER_CONTENT_TYPE \
+ "application/" PACKAGE_ZIP_URL_SCHEME "-folder"
+#define PACKAGE_ZIP_STREAM_CONTENT_TYPE \
+ "application/" PACKAGE_ZIP_URL_SCHEME "-stream"
+
+
+class Packages;
+
+class ContentProvider : public ::ucbhelper::ContentProviderImplHelper
+{
+ std::unique_ptr<Packages> m_pPackages;
+
+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;
+
+
+ // Non-interface methods.
+
+
+ css::uno::Reference< css::container::XHierarchicalNameAccess >
+ createPackage( const PackageUri & rParam );
+ void
+ removePackage( const OUString & rName );
+};
+
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/ucb/source/ucp/package/pkgresultset.cxx b/ucb/source/ucp/package/pkgresultset.cxx
new file mode 100644
index 0000000000..967625e9d3
--- /dev/null
+++ b/ucb/source/ucp/package/pkgresultset.cxx
@@ -0,0 +1,79 @@
+/* -*- 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:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+
+/**************************************************************************
+ TODO
+ **************************************************************************
+
+ - This implementation is not a dynamic result set!!! It only implements
+ the necessary interfaces, but never recognizes/notifies changes!!!
+
+ *************************************************************************/
+#include <utility>
+
+#include "pkgdatasupplier.hxx"
+#include "pkgresultset.hxx"
+
+using namespace com::sun::star;
+
+using namespace package_ucp;
+
+
+// DynamicResultSet Implementation.
+
+
+DynamicResultSet::DynamicResultSet(
+ const uno::Reference< uno::XComponentContext >& rxContext,
+ rtl::Reference< Content > xContent,
+ const ucb::OpenCommandArgument2& rCommand,
+ uno::Reference< ucb::XCommandEnvironment > xEnv )
+: ResultSetImplHelper(rxContext, rCommand ),
+ m_xContent(std::move( xContent )),
+ m_xEnv(std::move( xEnv ))
+{
+}
+
+
+// Non-interface methods.
+
+
+void DynamicResultSet::initStatic()
+{
+ m_xResultSet1
+ = new ::ucbhelper::ResultSet( m_xContext,
+ m_aCommand.Properties,
+ new DataSupplier( m_xContext,
+ m_xContent ),
+ m_xEnv );
+}
+
+
+void DynamicResultSet::initDynamic()
+{
+ m_xResultSet1
+ = new ::ucbhelper::ResultSet( m_xContext,
+ m_aCommand.Properties,
+ new DataSupplier( m_xContext,
+ m_xContent ),
+ m_xEnv );
+ m_xResultSet2 = m_xResultSet1;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/ucb/source/ucp/package/pkgresultset.hxx b/ucb/source/ucp/package/pkgresultset.hxx
new file mode 100644
index 0000000000..c87a074233
--- /dev/null
+++ b/ucb/source/ucp/package/pkgresultset.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/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#pragma once
+
+#include <rtl/ref.hxx>
+#include <ucbhelper/resultsethelper.hxx>
+#include "pkgcontent.hxx"
+
+namespace package_ucp {
+
+class DynamicResultSet : public ::ucbhelper::ResultSetImplHelper
+{
+ rtl::Reference< Content > m_xContent;
+ 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,
+ rtl::Reference< Content > xContent,
+ const css::ucb::OpenCommandArgument2& rCommand,
+ css::uno::Reference< css::ucb::XCommandEnvironment > xEnv );
+};
+
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/ucb/source/ucp/package/pkguri.cxx b/ucb/source/ucp/package/pkguri.cxx
new file mode 100644
index 0000000000..b1de54aa47
--- /dev/null
+++ b/ucb/source/ucp/package/pkguri.cxx
@@ -0,0 +1,234 @@
+/* -*- 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:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+
+/**************************************************************************
+ TODO
+ **************************************************************************
+
+ *************************************************************************/
+
+#include <sal/config.h>
+
+#include <string_view>
+
+#include <comphelper/storagehelper.hxx>
+
+#include "../inc/urihelper.hxx"
+
+#include "pkguri.hxx"
+
+using namespace package_ucp;
+
+
+// PackageUri Implementation.
+
+
+static void normalize( OUString& rURL )
+{
+ sal_Int32 nPos = 0;
+ do
+ {
+ nPos = rURL.indexOf( '%', nPos );
+ if ( nPos != -1 )
+ {
+ if ( nPos < ( rURL.getLength() - 2 ) )
+ {
+ OUString aTmp = rURL.copy( nPos + 1, 2 );
+ rURL = rURL.replaceAt( nPos + 1, 2, aTmp.toAsciiUpperCase() );
+ nPos++;
+ }
+ }
+ }
+ while ( nPos != -1 );
+}
+
+
+void PackageUri::init() const
+{
+ // Already inited?
+ if ( m_aUri.isEmpty() || !m_aPath.isEmpty() )
+ return;
+
+ // Note: Maybe it's a re-init, setUri only resets m_aPath!
+ m_aPackage.clear();
+ m_aParentUri.clear();
+ m_aName.clear();
+ m_aParam.clear();
+ m_aScheme.clear();
+
+ // URI must match at least: <scheme>://<non_empty_url_to_file>
+ if ( m_aUri.getLength() < PACKAGE_URL_SCHEME_LENGTH + 4 )
+ {
+ // error, but remember that we did an init().
+ m_aPath = "/";
+ return;
+ }
+
+ // Scheme must be followed by '://'
+ if ( ( m_aUri[ PACKAGE_URL_SCHEME_LENGTH ] != ':' )
+ ||
+ ( m_aUri[ PACKAGE_URL_SCHEME_LENGTH + 1 ] != '/' )
+ ||
+ ( m_aUri[ PACKAGE_URL_SCHEME_LENGTH + 2 ] != '/' ) )
+ {
+ // error, but remember that we did an init().
+ m_aPath = "/";
+ return;
+ }
+
+ OUString aPureUri;
+ sal_Int32 nParam = m_aUri.indexOf( '?' );
+ if( nParam >= 0 )
+ {
+ m_aParam = m_aUri.copy( nParam );
+ aPureUri = m_aUri.copy( 0, nParam );
+ }
+ else
+ aPureUri = m_aUri;
+
+ // Scheme is case insensitive.
+ m_aScheme = aPureUri.copy(
+ 0, PACKAGE_URL_SCHEME_LENGTH ).toAsciiLowerCase();
+
+ if ( m_aScheme == PACKAGE_URL_SCHEME || m_aScheme == PACKAGE_ZIP_URL_SCHEME )
+ {
+ if ( m_aScheme == PACKAGE_ZIP_URL_SCHEME )
+ {
+ m_aParam +=
+ ( !m_aParam.isEmpty()
+ ? std::u16string_view( u"&purezip" )
+ : std::u16string_view( u"?purezip" ) );
+ }
+
+ aPureUri = aPureUri.replaceAt( 0,
+ m_aScheme.getLength(),
+ m_aScheme );
+
+ sal_Int32 nStart = PACKAGE_URL_SCHEME_LENGTH + 3;
+ sal_Int32 nEnd = aPureUri.lastIndexOf( '/' );
+ if ( nEnd == PACKAGE_URL_SCHEME_LENGTH + 3 )
+ {
+ // Only <scheme>:/// - Empty authority
+
+ // error, but remember that we did an init().
+ m_aPath = "/";
+ return;
+ }
+ else if ( nEnd == ( aPureUri.getLength() - 1 ) )
+ {
+ if ( aPureUri[ aPureUri.getLength() - 2 ] == '/' )
+ {
+ // Only <scheme>://// or <scheme>://<something>
+
+ // error, but remember that we did an init().
+ m_aPath = "/";
+ return;
+ }
+
+ // Remove trailing slash.
+ aPureUri = aPureUri.copy( 0, nEnd );
+ }
+
+
+ nEnd = aPureUri.indexOf( '/', nStart );
+ if ( nEnd == -1 )
+ {
+ // root folder.
+
+ OUString aNormPackage = aPureUri.copy( nStart );
+ normalize( aNormPackage );
+
+ aPureUri = aPureUri.replaceAt(
+ nStart, aPureUri.getLength() - nStart, aNormPackage );
+ m_aPackage
+ = ::ucb_impl::urihelper::decodeSegment( aNormPackage );
+ m_aPath = "/";
+ m_aUri = m_aUri.replaceAt( 0,
+ ( nParam >= 0 )
+ ? nParam
+ : m_aUri.getLength(), aPureUri );
+
+ sal_Int32 nLastSlash = m_aPackage.lastIndexOf( '/' );
+ if ( nLastSlash != -1 )
+ m_aName = ::ucb_impl::urihelper::decodeSegment(
+ m_aPackage.copy( nLastSlash + 1 ) );
+ else
+ m_aName
+ = ::ucb_impl::urihelper::decodeSegment( m_aPackage );
+ }
+ else
+ {
+ m_aPath = aPureUri.copy( nEnd + 1 );
+
+ // Unexpected sequences of characters:
+ // - empty path segments
+ // - encoded slashes
+ // - parent folder segments ".."
+ // - current folder segments "."
+ if ( m_aPath.indexOf( "//" ) != -1
+ || m_aPath.indexOf( "%2F" ) != -1
+ || m_aPath.indexOf( "%2f" ) != -1
+ || ::comphelper::OStorageHelper::PathHasSegment( m_aPath, u".." )
+ || ::comphelper::OStorageHelper::PathHasSegment( m_aPath, u"." ) )
+ {
+ // error, but remember that we did an init().
+ m_aPath = "/";
+ return;
+ }
+
+ OUString aNormPackage = aPureUri.copy( nStart, nEnd - nStart );
+ normalize( aNormPackage );
+
+ aPureUri = aPureUri.replaceAt(
+ nStart, nEnd - nStart, aNormPackage );
+ aPureUri = aPureUri.replaceAt(
+ nEnd + 1,
+ aPureUri.getLength() - nEnd - 1,
+ ::ucb_impl::urihelper::encodeURI( m_aPath ) );
+
+ m_aPackage
+ = ::ucb_impl::urihelper::decodeSegment( aNormPackage );
+ m_aPath = ::ucb_impl::urihelper::decodeSegment( m_aPath );
+ m_aUri = m_aUri.replaceAt( 0,
+ ( nParam >= 0 )
+ ? nParam
+ : m_aUri.getLength(), aPureUri );
+
+ sal_Int32 nLastSlash = aPureUri.lastIndexOf( '/' );
+ if ( nLastSlash != -1 )
+ {
+ m_aParentUri = aPureUri.copy( 0, nLastSlash );
+ m_aName = ::ucb_impl::urihelper::decodeSegment(
+ aPureUri.copy( nLastSlash + 1 ) );
+ }
+ }
+
+ // success
+ m_bValid = true;
+ }
+ else
+ {
+ // error, but remember that we did an init().
+ m_aPath = "/";
+ }
+
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/ucb/source/ucp/package/pkguri.hxx b/ucb/source/ucp/package/pkguri.hxx
new file mode 100644
index 0000000000..4583a1afc8
--- /dev/null
+++ b/ucb/source/ucp/package/pkguri.hxx
@@ -0,0 +1,89 @@
+/* -*- 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:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#pragma once
+
+#include <rtl/ustring.hxx>
+#include <utility>
+
+namespace package_ucp {
+
+
+#define PACKAGE_URL_SCHEME "vnd.sun.star.pkg"
+#define PACKAGE_ZIP_URL_SCHEME "vnd.sun.star.zip"
+#define PACKAGE_URL_SCHEME_LENGTH 16
+
+
+class PackageUri
+{
+ mutable OUString m_aUri;
+ mutable OUString m_aParentUri;
+ mutable OUString m_aPackage;
+ mutable OUString m_aPath;
+ mutable OUString m_aName;
+ mutable OUString m_aParam;
+ mutable OUString m_aScheme;
+ mutable bool m_bValid;
+
+private:
+ void init() const;
+
+public:
+ explicit PackageUri( OUString aPackageUri )
+ : m_aUri(std::move( aPackageUri )), m_bValid( false ) {}
+
+ bool isValid() const
+ { init(); return m_bValid; }
+
+ const OUString & getUri() const
+ { init(); return m_aUri; }
+
+ void setUri( const OUString & rPackageUri )
+ { m_aPath.clear(); m_aUri = rPackageUri; m_bValid = false; }
+
+ const OUString & getParentUri() const
+ { init(); return m_aParentUri; }
+
+ const OUString & getPackage() const
+ { init(); return m_aPackage; }
+
+ const OUString & getPath() const
+ { init(); return m_aPath; }
+
+ const OUString & getName() const
+ { init(); return m_aName; }
+
+ const OUString & getParam() const
+ { init(); return m_aParam; }
+
+ const OUString & getScheme() const
+ { init(); return m_aScheme; }
+
+ inline bool isRootFolder() const;
+};
+
+inline bool PackageUri::isRootFolder() const
+{
+ init();
+ return m_aPath == "/";
+}
+
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/ucb/source/ucp/package/ucppkg1.component b/ucb/source/ucp/package/ucppkg1.component
new file mode 100644
index 0000000000..5bbf387a15
--- /dev/null
+++ b/ucb/source/ucp/package/ucppkg1.component
@@ -0,0 +1,26 @@
+<?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/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-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.ucb.PackageContentProvider"
+ constructor="ucb_package_ContentProvider_get_implementation" single-instance="true">
+ <service name="com.sun.star.ucb.PackageContentProvider"/>
+ </implementation>
+</component>
diff --git a/ucb/source/ucp/tdoc/tdoc_content.cxx b/ucb/source/ucp/tdoc/tdoc_content.cxx
new file mode 100644
index 0000000000..606bbcdbbc
--- /dev/null
+++ b/ucb/source/ucp/tdoc/tdoc_content.cxx
@@ -0,0 +1,2843 @@
+/* -*- 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:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+
+/**************************************************************************
+ TODO
+ **************************************************************************
+
+ *************************************************************************/
+
+#include <sal/config.h>
+
+#include <string_view>
+
+#include <o3tl/string_view.hxx>
+#include <comphelper/diagnose_ex.hxx>
+#include <rtl/ustrbuf.hxx>
+#include <com/sun/star/beans/IllegalTypeException.hpp>
+#include <com/sun/star/beans/PropertyAttribute.hpp>
+#include <com/sun/star/beans/XPropertySet.hpp>
+#include <com/sun/star/embed/InvalidStorageException.hpp>
+#include <com/sun/star/embed/StorageWrappedTargetException.hpp>
+#include <com/sun/star/embed/XTransactedObject.hpp>
+#include <com/sun/star/io/BufferSizeExceededException.hpp>
+#include <com/sun/star/io/IOException.hpp>
+#include <com/sun/star/io/NotConnectedException.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/packages/WrongPasswordException.hpp>
+#include <com/sun/star/task/DocumentPasswordRequest.hpp>
+#include <com/sun/star/task/XInteractionPassword.hpp>
+#include <com/sun/star/ucb/CommandFailedException.hpp>
+#include <com/sun/star/ucb/ContentAction.hpp>
+#include <com/sun/star/ucb/ContentInfoAttribute.hpp>
+#include <com/sun/star/ucb/IllegalIdentifierException.hpp>
+#include <com/sun/star/ucb/InsertCommandArgument.hpp>
+#include <com/sun/star/ucb/InteractiveBadTransferURLException.hpp>
+#include <com/sun/star/ucb/MissingInputStreamException.hpp>
+#include <com/sun/star/ucb/NameClash.hpp>
+#include <com/sun/star/ucb/NameClashException.hpp>
+#include <com/sun/star/ucb/OpenCommandArgument2.hpp>
+#include <com/sun/star/ucb/OpenMode.hpp>
+#include <com/sun/star/ucb/TransferInfo.hpp>
+#include <com/sun/star/ucb/UnsupportedCommandException.hpp>
+#include <com/sun/star/ucb/UnsupportedDataSinkException.hpp>
+#include <com/sun/star/ucb/UnsupportedNameClashException.hpp>
+#include <com/sun/star/ucb/UnsupportedOpenModeException.hpp>
+#include <com/sun/star/ucb/XCommandInfo.hpp>
+#include <com/sun/star/ucb/XPersistentPropertySet.hpp>
+
+#include <comphelper/propertysequence.hxx>
+#include <cppuhelper/queryinterface.hxx>
+#include <ucbhelper/cancelcommandexecution.hxx>
+#include <ucbhelper/contentidentifier.hxx>
+#include <ucbhelper/propertyvalueset.hxx>
+#include <ucbhelper/macros.hxx>
+#include <utility>
+
+#include "tdoc_content.hxx"
+#include "tdoc_resultset.hxx"
+#include "tdoc_passwordrequest.hxx"
+
+#include "../inc/urihelper.hxx"
+
+using namespace com::sun::star;
+using namespace tdoc_ucp;
+
+
+static ContentType lcl_getContentType( std::u16string_view rType )
+{
+ if ( rType == TDOC_ROOT_CONTENT_TYPE )
+ return ROOT;
+ else if ( rType == TDOC_DOCUMENT_CONTENT_TYPE )
+ return DOCUMENT;
+ else if ( rType == TDOC_FOLDER_CONTENT_TYPE )
+ return FOLDER;
+ else if ( rType == TDOC_STREAM_CONTENT_TYPE )
+ return STREAM;
+ else
+ {
+ OSL_FAIL( "Content::Content - unsupported content type string" );
+ return STREAM;
+ }
+}
+
+
+// Content Implementation.
+
+
+// static ( "virtual" ctor )
+rtl::Reference<Content> Content::create(
+ const uno::Reference< uno::XComponentContext >& rxContext,
+ ContentProvider* pProvider,
+ const uno::Reference< ucb::XContentIdentifier >& Identifier )
+{
+ // Fail, if resource does not exist.
+ ContentProperties aProps;
+ if ( !Content::loadData( pProvider,
+ Uri( Identifier->getContentIdentifier() ),
+ aProps ) )
+ return nullptr;
+
+ return new Content( rxContext, pProvider, Identifier, std::move(aProps) );
+}
+
+
+// static ( "virtual" ctor )
+rtl::Reference<Content> Content::create(
+ const uno::Reference< uno::XComponentContext >& rxContext,
+ ContentProvider* pProvider,
+ const uno::Reference< ucb::XContentIdentifier >& Identifier,
+ const ucb::ContentInfo& Info )
+{
+ if ( Info.Type.isEmpty() )
+ return nullptr;
+
+ if ( Info.Type != TDOC_FOLDER_CONTENT_TYPE && Info.Type != TDOC_STREAM_CONTENT_TYPE )
+ {
+ OSL_FAIL( "Content::create - unsupported content type!" );
+ return nullptr;
+ }
+
+ return new Content( rxContext, pProvider, Identifier, Info );
+}
+
+
+Content::Content(
+ const uno::Reference< uno::XComponentContext > & rxContext,
+ ContentProvider * pProvider,
+ const uno::Reference< ucb::XContentIdentifier > & Identifier,
+ ContentProperties aProps )
+: ContentImplHelper( rxContext, pProvider, Identifier ),
+ m_aProps(std::move( aProps )),
+ m_eState( PERSISTENT ),
+ m_pProvider( pProvider )
+{
+}
+
+
+// ctor for a content just created via XContentCreator::createNewContent()
+Content::Content(
+ const uno::Reference< uno::XComponentContext >& rxContext,
+ ContentProvider* pProvider,
+ const uno::Reference< ucb::XContentIdentifier >& Identifier,
+ const ucb::ContentInfo& Info )
+ : ContentImplHelper( rxContext, pProvider, Identifier ),
+ m_aProps( lcl_getContentType( Info.Type ), OUString() ), // no Title (yet)
+ m_eState( TRANSIENT ),
+ m_pProvider( pProvider )
+{
+}
+
+
+// virtual
+Content::~Content()
+{
+}
+
+
+// XInterface methods.
+
+
+// virtual
+void SAL_CALL Content::acquire()
+ noexcept
+{
+ ContentImplHelper::acquire();
+}
+
+
+// virtual
+void SAL_CALL Content::release()
+ noexcept
+{
+ ContentImplHelper::release();
+}
+
+
+// virtual
+uno::Any SAL_CALL Content::queryInterface( const uno::Type & rType )
+{
+ uno::Any aRet = ContentImplHelper::queryInterface( rType );
+
+ if ( !aRet.hasValue() )
+ {
+ aRet = cppu::queryInterface(
+ rType, static_cast< ucb::XContentCreator * >( this ) );
+ if ( aRet.hasValue() )
+ {
+ if ( !m_aProps.isContentCreator() )
+ return uno::Any();
+ }
+ }
+
+ return aRet;
+}
+
+
+// XTypeProvider methods.
+
+
+XTYPEPROVIDER_COMMON_IMPL( Content );
+
+
+// virtual
+uno::Sequence< uno::Type > SAL_CALL Content::getTypes()
+{
+ if ( m_aProps.isContentCreator() )
+ {
+ static cppu::OTypeCollection s_aFolderTypes(
+ 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_aFolderTypes.getTypes();
+ }
+ else
+ {
+ static cppu::OTypeCollection s_aDocumentTypes(
+ 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_aDocumentTypes.getTypes();
+ }
+}
+
+
+// XServiceInfo methods.
+
+
+// virtual
+OUString SAL_CALL Content::getImplementationName()
+{
+ return "com.sun.star.comp.ucb.TransientDocumentsContent";
+}
+
+
+// virtual
+uno::Sequence< OUString > SAL_CALL Content::getSupportedServiceNames()
+{
+ osl::Guard< osl::Mutex > aGuard( m_aMutex );
+
+ uno::Sequence< OUString > aSNS( 1 );
+
+ if ( m_aProps.getType() == STREAM )
+ aSNS.getArray()[ 0 ] = "com.sun.star.ucb.TransientDocumentsStreamContent";
+ else if ( m_aProps.getType() == FOLDER )
+ aSNS.getArray()[ 0 ] = "com.sun.star.ucb.TransientDocumentsFolderContent";
+ else if ( m_aProps.getType() == DOCUMENT )
+ aSNS.getArray()[ 0 ] = "com.sun.star.ucb.TransientDocumentsDocumentContent";
+ else
+ aSNS.getArray()[ 0 ] = "com.sun.star.ucb.TransientDocumentsRootContent";
+
+ return aSNS;
+}
+
+
+// XContent methods.
+
+
+// virtual
+OUString SAL_CALL Content::getContentType()
+{
+ osl::Guard< osl::Mutex > aGuard( m_aMutex );
+ return m_aProps.getContentType();
+}
+
+
+// virtual
+uno::Reference< ucb::XContentIdentifier > SAL_CALL
+Content::getIdentifier()
+{
+ {
+ osl::Guard< osl::Mutex > aGuard( m_aMutex );
+
+ // Transient?
+ if ( m_eState == TRANSIENT )
+ {
+ // Transient contents have no identifier.
+ return uno::Reference< ucb::XContentIdentifier >();
+ }
+ }
+ return ContentImplHelper::getIdentifier();
+}
+
+
+// XCommandProcessor methods.
+
+
+// virtual
+uno::Any SAL_CALL Content::execute(
+ const ucb::Command& aCommand,
+ sal_Int32 /*CommandId*/,
+ const uno::Reference< ucb::XCommandEnvironment >& Environment )
+{
+ uno::Any aRet;
+
+ if ( aCommand.Name == "getPropertyValues" )
+ {
+
+ // getPropertyValues
+
+
+ uno::Sequence< beans::Property > Properties;
+ if ( !( aCommand.Argument >>= Properties ) )
+ {
+ ucbhelper::cancelCommandExecution(
+ uno::Any( lang::IllegalArgumentException(
+ "Wrong argument type!",
+ getXWeak(),
+ -1 ) ),
+ Environment );
+ // Unreachable
+ }
+
+ aRet <<= getPropertyValues( Properties );
+ }
+ else if ( aCommand.Name == "setPropertyValues" )
+ {
+
+ // setPropertyValues
+
+
+ uno::Sequence< beans::PropertyValue > aProperties;
+ if ( !( aCommand.Argument >>= aProperties ) )
+ {
+ ucbhelper::cancelCommandExecution(
+ uno::Any( lang::IllegalArgumentException(
+ "Wrong argument type!",
+ getXWeak(),
+ -1 ) ),
+ Environment );
+ // Unreachable
+ }
+
+ if ( !aProperties.hasElements() )
+ {
+ ucbhelper::cancelCommandExecution(
+ uno::Any( lang::IllegalArgumentException(
+ "No properties!",
+ getXWeak(),
+ -1 ) ),
+ Environment );
+ // Unreachable
+ }
+
+ aRet <<= setPropertyValues( aProperties, Environment );
+ }
+ else if ( aCommand.Name == "getPropertySetInfo" )
+ {
+
+ // getPropertySetInfo
+
+
+ aRet <<= getPropertySetInfo( Environment );
+ }
+ else if ( aCommand.Name == "getCommandInfo" )
+ {
+
+ // getCommandInfo
+
+
+ aRet <<= getCommandInfo( Environment );
+ }
+ else if ( aCommand.Name == "open" )
+ {
+
+ // open
+
+
+ ucb::OpenCommandArgument2 aOpenCommand;
+ if ( !( aCommand.Argument >>= aOpenCommand ) )
+ {
+ ucbhelper::cancelCommandExecution(
+ uno::Any( lang::IllegalArgumentException(
+ "Wrong argument type!",
+ getXWeak(),
+ -1 ) ),
+ Environment );
+ // Unreachable
+ }
+
+ aRet = open( aOpenCommand, Environment );
+ }
+ else if ( aCommand.Name == "insert" )
+ {
+
+ // insert ( Supported by folders and streams only )
+
+
+ ContentType eType = m_aProps.getType();
+ if ( ( eType != FOLDER ) && ( eType != STREAM ) )
+ {
+ ucbhelper::cancelCommandExecution(
+ uno::Any( ucb::UnsupportedCommandException(
+ "insert command only supported by "
+ "folders and streams!",
+ getXWeak() ) ),
+ Environment );
+ // Unreachable
+ }
+
+ if ( eType == STREAM )
+ {
+ Uri aUri( m_xIdentifier->getContentIdentifier() );
+ Uri aParentUri( aUri.getParentUri() );
+ if ( aParentUri.isDocument() )
+ {
+ ucbhelper::cancelCommandExecution(
+ uno::Any( ucb::UnsupportedCommandException(
+ "insert command not supported by "
+ "streams that are direct children "
+ "of document root!",
+ getXWeak() ) ),
+ Environment );
+ // Unreachable
+ }
+ }
+
+ ucb::InsertCommandArgument aArg;
+ if ( !( aCommand.Argument >>= aArg ) )
+ {
+ ucbhelper::cancelCommandExecution(
+ uno::Any( lang::IllegalArgumentException(
+ "Wrong argument type!",
+ getXWeak(),
+ -1 ) ),
+ Environment );
+ // Unreachable
+ }
+
+ sal_Int32 nNameClash = aArg.ReplaceExisting
+ ? ucb::NameClash::OVERWRITE
+ : ucb::NameClash::ERROR;
+ insert( aArg.Data, nNameClash, Environment );
+ }
+ else if ( aCommand.Name == "delete" )
+ {
+
+ // delete ( Supported by folders and streams only )
+
+
+ {
+ osl::MutexGuard aGuard( m_aMutex );
+
+ ContentType eType = m_aProps.getType();
+ if ( ( eType != FOLDER ) && ( eType != STREAM ) )
+ {
+ ucbhelper::cancelCommandExecution(
+ uno::Any( ucb::UnsupportedCommandException(
+ "delete command only supported by "
+ "folders and streams!",
+ getXWeak() ) ),
+ Environment );
+ // Unreachable
+ }
+ }
+
+ bool bDeletePhysical = false;
+ aCommand.Argument >>= bDeletePhysical;
+ destroy( bDeletePhysical, Environment );
+
+ // Remove own and all children's persistent data.
+ if ( !removeData() )
+ {
+ uno::Sequence<uno::Any> aArgs(comphelper::InitAnyPropertySequence(
+ {
+ {"Uri", uno::Any(m_xIdentifier->getContentIdentifier())}
+ }));
+ ucbhelper::cancelCommandExecution(
+ ucb::IOErrorCode_CANT_WRITE,
+ aArgs,
+ Environment,
+ "Cannot remove persistent data!",
+ this );
+ // Unreachable
+ }
+
+ // Remove own and all children's Additional Core Properties.
+ removeAdditionalPropertySet();
+ }
+ else if ( aCommand.Name == "transfer" )
+ {
+
+ // transfer ( Supported by document and folders only )
+
+
+ {
+ osl::MutexGuard aGuard( m_aMutex );
+
+ ContentType eType = m_aProps.getType();
+ if ( ( eType != FOLDER ) && ( eType != DOCUMENT ) )
+ {
+ ucbhelper::cancelCommandExecution(
+ uno::Any( ucb::UnsupportedCommandException(
+ "transfer command only supported "
+ "by folders and documents!",
+ getXWeak() ) ),
+ Environment );
+ // Unreachable
+ }
+ }
+
+ ucb::TransferInfo aInfo;
+ if ( !( aCommand.Argument >>= aInfo ) )
+ {
+ OSL_FAIL( "Wrong argument type!" );
+ ucbhelper::cancelCommandExecution(
+ uno::Any( lang::IllegalArgumentException(
+ "Wrong argument type!",
+ getXWeak(),
+ -1 ) ),
+ Environment );
+ // Unreachable
+ }
+
+ transfer( aInfo, Environment );
+ }
+ else if ( aCommand.Name == "createNewContent" )
+ {
+
+ // createNewContent ( Supported by document and folders only )
+
+
+ {
+ osl::MutexGuard aGuard( m_aMutex );
+
+ ContentType eType = m_aProps.getType();
+ if ( ( eType != FOLDER ) && ( eType != DOCUMENT ) )
+ {
+ ucbhelper::cancelCommandExecution(
+ uno::Any( ucb::UnsupportedCommandException(
+ "createNewContent command only "
+ "supported by folders and "
+ "documents!",
+ getXWeak() ) ),
+ Environment );
+ // Unreachable
+ }
+ }
+
+ ucb::ContentInfo aInfo;
+ if ( !( aCommand.Argument >>= aInfo ) )
+ {
+ OSL_FAIL( "Wrong argument type!" );
+ ucbhelper::cancelCommandExecution(
+ uno::Any( lang::IllegalArgumentException(
+ "Wrong argument type!",
+ getXWeak(),
+ -1 ) ),
+ Environment );
+ // Unreachable
+ }
+
+ aRet <<= createNewContent( aInfo );
+ }
+ else
+ {
+
+ // Unsupported command
+
+
+ ucbhelper::cancelCommandExecution(
+ uno::Any( ucb::UnsupportedCommandException(
+ OUString(),
+ getXWeak() ) ),
+ Environment );
+ // Unreachable
+ }
+
+ return aRet;
+}
+
+
+// virtual
+void SAL_CALL Content::abort( sal_Int32 /*CommandId*/ )
+{
+}
+
+
+// XContentCreator methods.
+
+
+// virtual
+uno::Sequence< ucb::ContentInfo > SAL_CALL
+Content::queryCreatableContentsInfo()
+{
+ return m_aProps.getCreatableContentsInfo();
+}
+
+
+// virtual
+uno::Reference< ucb::XContent > SAL_CALL
+Content::createNewContent( const ucb::ContentInfo& Info )
+{
+ if ( m_aProps.isContentCreator() )
+ {
+ osl::Guard< osl::Mutex > aGuard( m_aMutex );
+
+ if ( Info.Type.isEmpty() )
+ return uno::Reference< ucb::XContent >();
+
+ bool bCreateFolder = Info.Type == TDOC_FOLDER_CONTENT_TYPE;
+
+ // streams cannot be created as direct children of document root
+ if ( !bCreateFolder && ( m_aProps.getType() == DOCUMENT ) )
+ {
+ OSL_FAIL( "Content::createNewContent - streams cannot be "
+ "created as direct children of document root!" );
+ return uno::Reference< ucb::XContent >();
+ }
+ if ( !bCreateFolder && Info.Type != TDOC_STREAM_CONTENT_TYPE )
+ {
+ OSL_FAIL( "Content::createNewContent - unsupported type!" );
+ return uno::Reference< ucb::XContent >();
+ }
+
+ OUString aURL = m_xIdentifier->getContentIdentifier();
+
+ OSL_ENSURE( !aURL.isEmpty(),
+ "Content::createNewContent - empty identifier!" );
+
+ if ( ( aURL.lastIndexOf( '/' ) + 1 ) != aURL.getLength() )
+ aURL += "/";
+
+ if ( bCreateFolder )
+ aURL += "New_Folder";
+ else
+ aURL += "New_Stream";
+
+ uno::Reference< ucb::XContentIdentifier > xId
+ = new ::ucbhelper::ContentIdentifier( aURL );
+
+ return create( m_xContext, m_pProvider, xId, Info );
+ }
+ else
+ {
+ OSL_FAIL( "createNewContent called on non-contentcreator object!" );
+ return uno::Reference< ucb::XContent >();
+ }
+}
+
+
+// virtual
+OUString Content::getParentURL()
+{
+ osl::Guard< osl::Mutex > aGuard( m_aMutex );
+ Uri aUri( m_xIdentifier->getContentIdentifier() );
+ return aUri.getParentUri();
+}
+
+
+uno::Reference< ucb::XContentIdentifier >
+Content::makeNewIdentifier( const OUString& rTitle )
+{
+ osl::Guard< osl::Mutex > aGuard( m_aMutex );
+
+ // Assemble new content identifier...
+ Uri aUri( m_xIdentifier->getContentIdentifier() );
+ OUString aNewURL = aUri.getParentUri() + ::ucb_impl::urihelper::encodeSegment( rTitle );
+
+ return
+ uno::Reference< ucb::XContentIdentifier >(
+ new ::ucbhelper::ContentIdentifier( aNewURL ) );
+}
+
+
+void Content::queryChildren( ContentRefList& rChildren )
+{
+ osl::Guard< osl::Mutex > aGuard( m_aMutex );
+
+ // Only folders (root, documents, folders) have children.
+ if ( !m_aProps.getIsFolder() )
+ return;
+
+ // Obtain a list with a snapshot of all currently instantiated contents
+ // from provider and extract the contents which are direct children
+ // of this content.
+
+ ::ucbhelper::ContentRefList aAllContents;
+ m_xProvider->queryExistingContents( aAllContents );
+
+ OUString aURL = m_xIdentifier->getContentIdentifier();
+ sal_Int32 nURLPos = aURL.lastIndexOf( '/' );
+
+ if ( nURLPos != ( aURL.getLength() - 1 ) )
+ {
+ // No trailing slash found. Append.
+ aURL += "/";
+ }
+
+ sal_Int32 nLen = aURL.getLength();
+
+ for ( const auto& rContent : aAllContents )
+ {
+ ::ucbhelper::ContentImplHelperRef xChild = rContent;
+ OUString aChildURL
+ = xChild->getIdentifier()->getContentIdentifier();
+
+ // Is aURL a prefix of aChildURL?
+ if ( ( aChildURL.getLength() > nLen ) &&
+ ( aChildURL.startsWith( aURL ) ) )
+ {
+ sal_Int32 nPos = aChildURL.indexOf( '/', nLen );
+
+ if ( ( nPos == -1 ) ||
+ ( nPos == ( aChildURL.getLength() - 1 ) ) )
+ {
+ // No further slashes / only a final slash. It's a child!
+ rChildren.emplace_back(
+ static_cast< Content * >( xChild.get() ) );
+ }
+ }
+ }
+}
+
+
+bool Content::exchangeIdentity(
+ const uno::Reference< ucb::XContentIdentifier >& xNewId )
+{
+ if ( !xNewId.is() )
+ return false;
+
+ osl::ClearableGuard< osl::Mutex > aGuard( m_aMutex );
+
+ uno::Reference< ucb::XContent > xThis = this;
+
+ // Already persistent?
+ if ( m_eState != PERSISTENT )
+ {
+ OSL_FAIL( "Content::exchangeIdentity - Not persistent!" );
+ return false;
+ }
+
+ // Only folders and streams can be renamed -> exchange identity.
+ ContentType eType = m_aProps.getType();
+ if ( ( eType == ROOT ) || ( eType == DOCUMENT ) )
+ {
+ OSL_FAIL( "Content::exchangeIdentity - "
+ "Not supported by root or document!" );
+ return false;
+ }
+
+ // Exchange own identity.
+
+ // Fail, if a content with given id already exists.
+ if ( !hasData( Uri( xNewId->getContentIdentifier() ) ) )
+ {
+ OUString aOldURL = m_xIdentifier->getContentIdentifier();
+
+ aGuard.clear();
+ if ( exchange( xNewId ) )
+ {
+ if ( eType == FOLDER )
+ {
+ // Process instantiated children...
+
+ ContentRefList aChildren;
+ queryChildren( aChildren );
+
+ for ( const auto& rChild : aChildren )
+ {
+ ContentRef xChild = rChild;
+
+ // Create new content identifier for the child...
+ uno::Reference< ucb::XContentIdentifier > xOldChildId
+ = xChild->getIdentifier();
+ OUString aOldChildURL
+ = xOldChildId->getContentIdentifier();
+ OUString aNewChildURL
+ = aOldChildURL.replaceAt(
+ 0,
+ aOldURL.getLength(),
+ xNewId->getContentIdentifier() );
+ uno::Reference< ucb::XContentIdentifier > xNewChildId
+ = new ::ucbhelper::ContentIdentifier( aNewChildURL );
+
+ if ( !xChild->exchangeIdentity( xNewChildId ) )
+ return false;
+ }
+ }
+ return true;
+ }
+ }
+
+ OSL_FAIL( "Content::exchangeIdentity - "
+ "Panic! Cannot exchange identity!" );
+ return false;
+}
+
+
+// static
+uno::Reference< sdbc::XRow > Content::getPropertyValues(
+ const uno::Reference< uno::XComponentContext >& rxContext,
+ const uno::Sequence< beans::Property >& rProperties,
+ ContentProvider* pProvider,
+ const OUString& rContentId )
+{
+ ContentProperties aData;
+ if ( loadData( pProvider, Uri(rContentId), aData ) )
+ {
+ return getPropertyValues(
+ rxContext, rProperties, aData, pProvider, rContentId );
+ }
+ else
+ {
+ rtl::Reference< ::ucbhelper::PropertyValueSet > xRow
+ = new ::ucbhelper::PropertyValueSet( rxContext );
+
+ for ( const beans::Property& rProp : rProperties )
+ xRow->appendVoid( rProp );
+
+ return xRow;
+ }
+}
+
+
+// static
+uno::Reference< sdbc::XRow > Content::getPropertyValues(
+ const uno::Reference< uno::XComponentContext >& rxContext,
+ const uno::Sequence< beans::Property >& rProperties,
+ const ContentProperties& rData,
+ ContentProvider* pProvider,
+ const OUString& rContentId )
+{
+ // Note: Empty sequence means "get values of all supported properties".
+
+ rtl::Reference< ::ucbhelper::PropertyValueSet > xRow
+ = new ::ucbhelper::PropertyValueSet( rxContext );
+
+ if ( rProperties.hasElements() )
+ {
+ uno::Reference< beans::XPropertySet > xAdditionalPropSet;
+ bool bTriedToGetAdditionalPropSet = false;
+
+ for ( const beans::Property& rProp : rProperties )
+ {
+ // Process Core properties.
+
+ if ( rProp.Name == "ContentType" )
+ {
+ xRow->appendString ( rProp, rData.getContentType() );
+ }
+ else if ( rProp.Name == "Title" )
+ {
+ xRow->appendString ( rProp, rData.getTitle() );
+ }
+ else if ( rProp.Name == "IsDocument" )
+ {
+ xRow->appendBoolean( rProp, rData.getIsDocument() );
+ }
+ else if ( rProp.Name == "IsFolder" )
+ {
+ xRow->appendBoolean( rProp, rData.getIsFolder() );
+ }
+ else if ( rProp.Name == "CreatableContentsInfo" )
+ {
+ xRow->appendObject(
+ rProp, uno::Any( rData.getCreatableContentsInfo() ) );
+ }
+ else if ( rProp.Name == "DateModified" )
+ {
+ // DateModified is only supported by streams.
+ ContentType eType = rData.getType();
+ if ( eType == STREAM )
+ {
+ xRow->appendObject(
+ rProp,
+ uno::Any(
+ pProvider->queryStreamDateModified( rContentId ) ) );
+ }
+ else
+ xRow->appendVoid( rProp );
+ }
+ else if ( rProp.Name == "Storage" )
+ {
+ // Storage is only supported by folders.
+ ContentType eType = rData.getType();
+ if ( eType == FOLDER )
+ xRow->appendObject(
+ rProp,
+ uno::Any(
+ pProvider->queryStorageClone( rContentId ) ) );
+ else
+ xRow->appendVoid( rProp );
+ }
+ else if ( rProp.Name == "DocumentModel" )
+ {
+ // DocumentModel is only supported by documents.
+ ContentType eType = rData.getType();
+ if ( eType == DOCUMENT )
+ xRow->appendObject(
+ rProp,
+ uno::Any(
+ pProvider->queryDocumentModel( rContentId ) ) );
+ else
+ xRow->appendVoid( rProp );
+ }
+ else
+ {
+ // Not a Core Property! Maybe it's an Additional Core Property?!
+
+ if ( !bTriedToGetAdditionalPropSet && !xAdditionalPropSet.is() )
+ {
+ xAdditionalPropSet =
+ pProvider->getAdditionalPropertySet( rContentId,
+ false );
+ bTriedToGetAdditionalPropSet = true;
+ }
+
+ if ( xAdditionalPropSet.is() )
+ {
+ if ( !xRow->appendPropertySetValue(
+ xAdditionalPropSet,
+ rProp ) )
+ {
+ // Append empty entry.
+ xRow->appendVoid( rProp );
+ }
+ }
+ else
+ {
+ // Append empty entry.
+ xRow->appendVoid( rProp );
+ }
+ }
+ }
+ }
+ else
+ {
+ // Append all Core Properties.
+ xRow->appendString (
+ beans::Property( "ContentType",
+ -1,
+ cppu::UnoType<OUString>::get(),
+ beans::PropertyAttribute::BOUND
+ | beans::PropertyAttribute::READONLY ),
+ rData.getContentType() );
+
+ ContentType eType = rData.getType();
+
+ xRow->appendString (
+ beans::Property( "Title",
+ -1,
+ cppu::UnoType<OUString>::get(),
+ // Title is read-only for root and documents.
+ beans::PropertyAttribute::BOUND |
+ ( ( eType == ROOT ) || ( eType == DOCUMENT )
+ ? beans::PropertyAttribute::READONLY
+ : 0 ) ),
+ rData.getTitle() );
+ xRow->appendBoolean(
+ beans::Property( "IsDocument",
+ -1,
+ cppu::UnoType<bool>::get(),
+ beans::PropertyAttribute::BOUND
+ | beans::PropertyAttribute::READONLY ),
+ rData.getIsDocument() );
+ xRow->appendBoolean(
+ beans::Property( "IsFolder",
+ -1,
+ cppu::UnoType<bool>::get(),
+ beans::PropertyAttribute::BOUND
+ | beans::PropertyAttribute::READONLY ),
+ rData.getIsFolder() );
+ xRow->appendObject(
+ beans::Property(
+ "CreatableContentsInfo",
+ -1,
+ cppu::UnoType<uno::Sequence< ucb::ContentInfo >>::get(),
+ beans::PropertyAttribute::BOUND
+ | beans::PropertyAttribute::READONLY ),
+ uno::Any( rData.getCreatableContentsInfo() ) );
+
+ // DateModified is only supported by streams.
+ if ( eType == STREAM )
+ {
+ xRow->appendObject(
+ beans::Property( "DateModified",
+ -1,
+ cppu::UnoType<css::util::DateTime>::get(),
+ beans::PropertyAttribute::BOUND
+ | beans::PropertyAttribute::READONLY ),
+ uno::Any( pProvider->queryStreamDateModified( rContentId ) ) );
+ }
+
+ // Storage is only supported by folders.
+ if ( eType == FOLDER )
+ xRow->appendObject(
+ beans::Property( "Storage",
+ -1,
+ cppu::UnoType<embed::XStorage>::get(),
+ beans::PropertyAttribute::BOUND
+ | beans::PropertyAttribute::READONLY ),
+ uno::Any( pProvider->queryStorageClone( rContentId ) ) );
+
+ // DocumentModel is only supported by documents.
+ if ( eType == DOCUMENT )
+ xRow->appendObject(
+ beans::Property( "DocumentModel",
+ -1,
+ cppu::UnoType<frame::XModel>::get(),
+ beans::PropertyAttribute::BOUND
+ | beans::PropertyAttribute::READONLY ),
+ uno::Any(
+ pProvider->queryDocumentModel( rContentId ) ) );
+
+ // Append all Additional Core Properties.
+
+ uno::Reference< beans::XPropertySet > xSet =
+ pProvider->getAdditionalPropertySet( rContentId, false );
+ xRow->appendPropertySet( xSet );
+ }
+
+ return xRow;
+}
+
+
+uno::Reference< sdbc::XRow > Content::getPropertyValues(
+ const uno::Sequence< beans::Property >& rProperties )
+{
+ osl::Guard< osl::Mutex > aGuard( m_aMutex );
+ return getPropertyValues( m_xContext,
+ rProperties,
+ m_aProps,
+ m_pProvider,
+ m_xIdentifier->getContentIdentifier() );
+}
+
+
+uno::Sequence< uno::Any > Content::setPropertyValues(
+ const uno::Sequence< beans::PropertyValue >& rValues,
+ const uno::Reference< ucb::XCommandEnvironment > & xEnv )
+{
+ osl::ClearableGuard< osl::Mutex > aGuard( m_aMutex );
+
+ uno::Sequence< uno::Any > aRet( rValues.getLength() );
+ auto aRetRange = asNonConstRange(aRet);
+ uno::Sequence< beans::PropertyChangeEvent > aChanges( rValues.getLength() );
+ sal_Int32 nChanged = 0;
+
+ beans::PropertyChangeEvent aEvent;
+ aEvent.Source = getXWeak();
+ aEvent.Further = false;
+ // aEvent.PropertyName =
+ aEvent.PropertyHandle = -1;
+ // aEvent.OldValue =
+ // aEvent.NewValue =
+
+ const beans::PropertyValue* pValues = rValues.getConstArray();
+ sal_Int32 nCount = rValues.getLength();
+
+ uno::Reference< ucb::XPersistentPropertySet > xAdditionalPropSet;
+ bool bTriedToGetAdditionalPropSet = false;
+
+ bool bExchange = false;
+ OUString aOldTitle;
+ sal_Int32 nTitlePos = -1;
+
+ for ( sal_Int32 n = 0; n < nCount; ++n )
+ {
+ const beans::PropertyValue& rValue = pValues[ n ];
+
+ if ( rValue.Name == "ContentType" )
+ {
+ // Read-only property!
+ aRetRange[ n ] <<= lang::IllegalAccessException(
+ "Property is read-only!",
+ getXWeak() );
+ }
+ else if ( rValue.Name == "IsDocument" )
+ {
+ // Read-only property!
+ aRetRange[ n ] <<= lang::IllegalAccessException(
+ "Property is read-only!",
+ getXWeak() );
+ }
+ else if ( rValue.Name == "IsFolder" )
+ {
+ // Read-only property!
+ aRetRange[ n ] <<= lang::IllegalAccessException(
+ "Property is read-only!",
+ getXWeak() );
+ }
+ else if ( rValue.Name == "CreatableContentsInfo" )
+ {
+ // Read-only property!
+ aRetRange[ n ] <<= lang::IllegalAccessException(
+ "Property is read-only!",
+ getXWeak() );
+ }
+ else if ( rValue.Name == "Title" )
+ {
+ // Title is read-only for root and documents.
+ ContentType eType = m_aProps.getType();
+ if ( ( eType == ROOT ) || ( eType == DOCUMENT ) )
+ {
+ aRetRange[ n ] <<= lang::IllegalAccessException(
+ "Property is read-only!",
+ getXWeak() );
+ }
+ else
+ {
+ OUString aNewValue;
+ if ( rValue.Value >>= aNewValue )
+ {
+ // No empty titles!
+ if ( !aNewValue.isEmpty() )
+ {
+ if ( aNewValue != m_aProps.getTitle() )
+ {
+ // modified title -> modified URL -> exchange !
+ if ( m_eState == PERSISTENT )
+ bExchange = true;
+
+ aOldTitle = m_aProps.getTitle();
+ m_aProps.setTitle( aNewValue );
+
+ // property change event will be sent later...
+
+ // remember position within sequence of values
+ // (for error handling).
+ nTitlePos = n;
+ }
+ }
+ else
+ {
+ aRetRange[ n ] <<= lang::IllegalArgumentException(
+ "Empty Title not allowed!",
+ getXWeak(),
+ -1 );
+ }
+ }
+ else
+ {
+ aRetRange[ n ] <<= beans::IllegalTypeException(
+ "Title Property value has wrong type!",
+ getXWeak() );
+ }
+ }
+ }
+ else if ( rValue.Name == "Storage" )
+ {
+ ContentType eType = m_aProps.getType();
+ if ( eType == FOLDER )
+ {
+ aRetRange[ n ] <<= lang::IllegalAccessException(
+ "Property is read-only!",
+ getXWeak() );
+ }
+ else
+ {
+ // Storage is only supported by folders.
+ aRetRange[ n ] <<= beans::UnknownPropertyException(
+ "Storage property only supported by folders",
+ getXWeak() );
+ }
+ }
+ else if ( rValue.Name == "DocumentModel" )
+ {
+ ContentType eType = m_aProps.getType();
+ if ( eType == DOCUMENT )
+ {
+ aRetRange[ n ] <<= lang::IllegalAccessException(
+ "Property is read-only!",
+ getXWeak() );
+ }
+ else
+ {
+ // Storage is only supported by folders.
+ aRetRange[ n ] <<= beans::UnknownPropertyException(
+ "DocumentModel property only supported by documents",
+ getXWeak() );
+ }
+ }
+ else
+ {
+ // Not a Core Property! Maybe it's an Additional Core Property?!
+
+ if ( !bTriedToGetAdditionalPropSet && !xAdditionalPropSet.is() )
+ {
+ xAdditionalPropSet = getAdditionalPropertySet( false );
+ bTriedToGetAdditionalPropSet = true;
+ }
+
+ if ( xAdditionalPropSet.is() )
+ {
+ try
+ {
+ uno::Any aOldValue = xAdditionalPropSet->getPropertyValue(
+ rValue.Name );
+ if ( aOldValue != rValue.Value )
+ {
+ xAdditionalPropSet->setPropertyValue(
+ rValue.Name, rValue.Value );
+
+ aEvent.PropertyName = rValue.Name;
+ aEvent.OldValue = aOldValue;
+ aEvent.NewValue = rValue.Value;
+
+ aChanges.getArray()[ nChanged ] = aEvent;
+ nChanged++;
+ }
+ }
+ catch ( beans::UnknownPropertyException const & e )
+ {
+ aRetRange[ n ] <<= e;
+ }
+ catch ( lang::WrappedTargetException const & e )
+ {
+ aRetRange[ n ] <<= e;
+ }
+ catch ( beans::PropertyVetoException const & e )
+ {
+ aRetRange[ n ] <<= e;
+ }
+ catch ( lang::IllegalArgumentException const & e )
+ {
+ aRetRange[ n ] <<= e;
+ }
+ }
+ else
+ {
+ aRetRange[ n ] <<= uno::Exception(
+ "No property set for storing the value!",
+ getXWeak() );
+ }
+ }
+ }
+
+ if ( bExchange )
+ {
+ uno::Reference< ucb::XContentIdentifier > xOldId
+ = m_xIdentifier;
+ uno::Reference< ucb::XContentIdentifier > xNewId
+ = makeNewIdentifier( m_aProps.getTitle() );
+
+ aGuard.clear();
+ if ( exchangeIdentity( xNewId ) )
+ {
+ // Adapt persistent data.
+ renameData( xOldId, xNewId );
+
+ // Adapt Additional Core Properties.
+ renameAdditionalPropertySet( xOldId->getContentIdentifier(),
+ xNewId->getContentIdentifier() );
+ }
+ else
+ {
+ // Roll-back.
+ m_aProps.setTitle( aOldTitle );
+ aOldTitle.clear();
+
+ // Set error .
+ aRetRange[ nTitlePos ] <<= uno::Exception(
+ "Exchange failed!",
+ getXWeak() );
+ }
+ }
+
+ if ( !aOldTitle.isEmpty() )
+ {
+ aEvent.PropertyName = "Title";
+ aEvent.OldValue <<= aOldTitle;
+ aEvent.NewValue <<= m_aProps.getTitle();
+
+ aChanges.getArray()[ nChanged ] = aEvent;
+ nChanged++;
+ }
+
+ if ( nChanged > 0 )
+ {
+ // Save changes, if content was already made persistent.
+ if ( !bExchange && ( m_eState == PERSISTENT ) )
+ {
+ if ( !storeData( uno::Reference< io::XInputStream >(), xEnv ) )
+ {
+ uno::Sequence<uno::Any> aArgs(comphelper::InitAnyPropertySequence(
+ {
+ {"Uri", uno::Any(m_xIdentifier->getContentIdentifier())}
+ }));
+ ucbhelper::cancelCommandExecution(
+ ucb::IOErrorCode_CANT_WRITE,
+ aArgs,
+ xEnv,
+ "Cannot store persistent data!",
+ this );
+ // Unreachable
+ }
+ }
+
+ aChanges.realloc( nChanged );
+
+ aGuard.clear();
+ notifyPropertiesChange( aChanges );
+ }
+
+ return aRet;
+}
+
+
+uno::Any Content::open(
+ const ucb::OpenCommandArgument2& rArg,
+ const uno::Reference< ucb::XCommandEnvironment >& xEnv )
+{
+ if ( rArg.Mode == ucb::OpenMode::ALL ||
+ rArg.Mode == ucb::OpenMode::FOLDERS ||
+ rArg.Mode == ucb::OpenMode::DOCUMENTS )
+ {
+
+ // open command for a folder content
+
+
+ uno::Reference< ucb::XDynamicResultSet > xSet
+ = new DynamicResultSet( m_xContext, this, rArg );
+ return uno::Any( xSet );
+ }
+ else
+ {
+
+ // open command for a document content
+
+
+ if ( ( rArg.Mode == ucb::OpenMode::DOCUMENT_SHARE_DENY_NONE ) ||
+ ( rArg.Mode == ucb::OpenMode::DOCUMENT_SHARE_DENY_WRITE ) )
+ {
+ // Currently(?) unsupported.
+ ucbhelper::cancelCommandExecution(
+ uno::Any( ucb::UnsupportedOpenModeException(
+ OUString(),
+ getXWeak(),
+ sal_Int16( rArg.Mode ) ) ),
+ xEnv );
+ // Unreachable
+ }
+
+ osl::Guard< osl::Mutex > aGuard( m_aMutex );
+
+ uno::Reference< io::XActiveDataStreamer > xDataStreamer(
+ rArg.Sink, uno::UNO_QUERY );
+ if ( xDataStreamer.is() )
+ {
+ // May throw CommandFailedException, DocumentPasswordRequest!
+ uno::Reference< io::XStream > xStream = getStream( xEnv );
+ if ( !xStream.is() )
+ {
+ // No interaction if we are not persistent!
+ uno::Sequence<uno::Any> aArgs(comphelper::InitAnyPropertySequence(
+ {
+ {"Uri", uno::Any(m_xIdentifier->getContentIdentifier())}
+ }));
+ ucbhelper::cancelCommandExecution(
+ ucb::IOErrorCode_CANT_READ,
+ aArgs,
+ m_eState == PERSISTENT
+ ? xEnv
+ : uno::Reference< ucb::XCommandEnvironment >(),
+ "Got no data stream!",
+ this );
+ // Unreachable
+ }
+
+ // Done.
+ xDataStreamer->setStream( xStream );
+ }
+ else
+ {
+ uno::Reference< io::XOutputStream > xOut( rArg.Sink, uno::UNO_QUERY );
+ if ( xOut.is() )
+ {
+ // PUSH: write data into xOut
+
+ // May throw CommandFailedException, DocumentPasswordRequest!
+ uno::Reference< io::XInputStream > xIn = getInputStream( xEnv );
+ if ( !xIn.is() )
+ {
+ // No interaction if we are not persistent!
+ uno::Sequence<uno::Any> aArgs(comphelper::InitAnyPropertySequence(
+ {
+ {"Uri", uno::Any(m_xIdentifier->getContentIdentifier())}
+ }));
+ ucbhelper::cancelCommandExecution(
+ ucb::IOErrorCode_CANT_READ,
+ aArgs,
+ m_eState == PERSISTENT
+ ? xEnv
+ : uno::Reference< ucb::XCommandEnvironment >(),
+ "Got no data stream!",
+ this );
+ // Unreachable
+ }
+
+ try
+ {
+ uno::Sequence< sal_Int8 > aBuffer;
+
+ while (true)
+ {
+ sal_Int32 nRead = xIn->readSomeBytes( aBuffer, 65536 );
+ if (!nRead)
+ break;
+ aBuffer.realloc( nRead );
+ xOut->writeBytes( aBuffer );
+ }
+
+ xOut->closeOutput();
+ }
+ catch ( io::NotConnectedException const & )
+ {
+ // closeOutput, readSomeBytes, writeBytes
+ }
+ catch ( io::BufferSizeExceededException const & )
+ {
+ // closeOutput, readSomeBytes, writeBytes
+ }
+ catch ( io::IOException const & )
+ {
+ // closeOutput, readSomeBytes, writeBytes
+ }
+ }
+ else
+ {
+ uno::Reference< io::XActiveDataSink > xDataSink(
+ rArg.Sink, uno::UNO_QUERY );
+ if ( xDataSink.is() )
+ {
+ // PULL: wait for client read
+
+ // May throw CommandFailedException, DocumentPasswordRequest!
+ uno::Reference< io::XInputStream > xIn = getInputStream( xEnv );
+ if ( !xIn.is() )
+ {
+ // No interaction if we are not persistent!
+ uno::Sequence<uno::Any> aArgs(comphelper::InitAnyPropertySequence(
+ {
+ {"Uri", uno::Any(m_xIdentifier->getContentIdentifier())}
+ }));
+ ucbhelper::cancelCommandExecution(
+ ucb::IOErrorCode_CANT_READ,
+ aArgs,
+ m_eState == PERSISTENT
+ ? xEnv
+ : uno::Reference<
+ ucb::XCommandEnvironment >(),
+ "Got no data stream!",
+ this );
+ // Unreachable
+ }
+
+ // Done.
+ xDataSink->setInputStream( xIn );
+ }
+ else
+ {
+ ucbhelper::cancelCommandExecution(
+ uno::Any(
+ ucb::UnsupportedDataSinkException(
+ OUString(),
+ getXWeak(),
+ rArg.Sink ) ),
+ xEnv );
+ // Unreachable
+ }
+ }
+ }
+ }
+
+ return uno::Any();
+}
+
+
+void Content::insert( const uno::Reference< io::XInputStream >& xData,
+ sal_Int32 nNameClashResolve,
+ const uno::Reference<
+ ucb::XCommandEnvironment > & xEnv )
+{
+ osl::ClearableGuard< osl::Mutex > aGuard( m_aMutex );
+
+ ContentType eType = m_aProps.getType();
+
+ OSL_ENSURE( ( eType == FOLDER ) || ( eType == STREAM ),
+ "insert command only supported by streams and folders!" );
+
+ Uri aUri( m_xIdentifier->getContentIdentifier() );
+
+#if OSL_DEBUG_LEVEL > 0
+ if ( eType == STREAM )
+ {
+ Uri aParentUri( aUri.getParentUri() );
+ OSL_ENSURE( !aParentUri.isDocument(),
+ "insert command not supported by streams that are direct "
+ "children of document root!" );
+ }
+#endif
+
+ // Check, if all required properties were set.
+ if ( eType == FOLDER )
+ {
+ // Required: Title
+
+ if ( m_aProps.getTitle().isEmpty() )
+ m_aProps.setTitle( aUri.getDecodedName() );
+ }
+ else // stream
+ {
+ // Required: data
+
+ if ( !xData.is() )
+ {
+ ucbhelper::cancelCommandExecution(
+ uno::Any( ucb::MissingInputStreamException(
+ OUString(),
+ getXWeak() ) ),
+ xEnv );
+ // Unreachable
+ }
+
+ // Required: Title
+
+ if ( m_aProps.getTitle().isEmpty() )
+ m_aProps.setTitle( aUri.getDecodedName() );
+ }
+
+ Uri aNewUri( aUri.getParentUri() + m_aProps.getTitle() );
+
+ // Handle possible name clash...
+ switch ( nNameClashResolve )
+ {
+ // fail.
+ case ucb::NameClash::ERROR:
+ if ( hasData( aNewUri ) )
+ {
+ ucbhelper::cancelCommandExecution(
+ uno::Any( ucb::NameClashException(
+ OUString(),
+ getXWeak(),
+ task::InteractionClassification_ERROR,
+ m_aProps.getTitle() ) ),
+ xEnv );
+ // Unreachable
+ }
+ break;
+
+ // replace (possibly) existing object.
+ case ucb::NameClash::OVERWRITE:
+ break;
+
+ // "invent" a new valid title.
+ case ucb::NameClash::RENAME:
+ if ( hasData( aNewUri ) )
+ {
+ sal_Int32 nTry = 0;
+
+ do
+ {
+ aNewUri.setUri( aNewUri.getUri() + "_" + OUString::number(++nTry) );
+ }
+ while ( hasData( aNewUri ) && ( nTry < 1000 ) );
+
+ if ( nTry == 1000 )
+ {
+ ucbhelper::cancelCommandExecution(
+ uno::Any(
+ ucb::UnsupportedNameClashException(
+ "Unable to resolve name clash!",
+ getXWeak(),
+ nNameClashResolve ) ),
+ xEnv );
+ // Unreachable
+ }
+ else
+ {
+ m_aProps.setTitle( m_aProps.getTitle() + "_" + OUString::number( ++nTry ) );
+ }
+ }
+ break;
+
+ case ucb::NameClash::KEEP: // deprecated
+ case ucb::NameClash::ASK:
+ default:
+ if ( hasData( aNewUri ) )
+ {
+ ucbhelper::cancelCommandExecution(
+ uno::Any(
+ ucb::UnsupportedNameClashException(
+ OUString(),
+ getXWeak(),
+ nNameClashResolve ) ),
+ xEnv );
+ // Unreachable
+ }
+ break;
+ }
+
+ // Identifier changed?
+ bool bNewId = ( aUri != aNewUri );
+
+ if ( bNewId )
+ {
+ m_xIdentifier
+ = new ::ucbhelper::ContentIdentifier( aNewUri.getUri() );
+ }
+
+ if ( !storeData( xData, xEnv ) )
+ {
+ uno::Sequence<uno::Any> aArgs(comphelper::InitAnyPropertySequence(
+ {
+ {"Uri", uno::Any(m_xIdentifier->getContentIdentifier())}
+ }));
+ ucbhelper::cancelCommandExecution(
+ ucb::IOErrorCode_CANT_WRITE,
+ aArgs,
+ xEnv,
+ "Cannot store persistent data!",
+ this );
+ // Unreachable
+ }
+
+ m_eState = PERSISTENT;
+
+ if ( bNewId )
+ {
+ //loadData( m_pProvider, m_aUri, m_aProps );
+
+ aGuard.clear();
+ inserted();
+ }
+}
+
+
+void Content::destroy( bool bDeletePhysical,
+ const uno::Reference<
+ ucb::XCommandEnvironment > & xEnv )
+{
+ // @@@ take care about bDeletePhysical -> trashcan support
+
+ osl::ClearableGuard< osl::Mutex > aGuard( m_aMutex );
+
+ ContentType eType = m_aProps.getType();
+
+ OSL_ENSURE( ( eType == FOLDER ) || ( eType == STREAM ),
+ "delete command only supported by streams and folders!" );
+
+ uno::Reference< ucb::XContent > xThis = this;
+
+ // Persistent?
+ if ( m_eState != PERSISTENT )
+ {
+ ucbhelper::cancelCommandExecution(
+ uno::Any( ucb::UnsupportedCommandException(
+ "Not persistent!",
+ getXWeak() ) ),
+ xEnv );
+ // Unreachable
+ }
+
+ m_eState = DEAD;
+
+ aGuard.clear();
+ deleted();
+
+ if ( eType == FOLDER )
+ {
+ // Process instantiated children...
+
+ ContentRefList aChildren;
+ queryChildren( aChildren );
+
+ for ( auto& rChild : aChildren )
+ {
+ rChild->destroy( bDeletePhysical, xEnv );
+ }
+ }
+}
+
+
+void Content::notifyDocumentClosed()
+{
+ osl::ClearableGuard< osl::Mutex > aGuard( m_aMutex );
+
+ m_eState = DEAD;
+
+ // @@@ anything else to reset or such?
+
+ // callback follows!
+ aGuard.clear();
+
+ // Propagate destruction to content event listeners
+ // Remove this from provider's content list.
+ deleted();
+}
+
+
+uno::Reference< ucb::XContent >
+Content::queryChildContent( std::u16string_view rRelativeChildUri )
+{
+ osl::Guard< osl::Mutex > aGuard( m_aMutex );
+
+ const OUString aMyId = getIdentifier()->getContentIdentifier();
+ OUStringBuffer aBuf( aMyId );
+ if ( !aMyId.endsWith("/") )
+ aBuf.append( "/" );
+ if ( !o3tl::starts_with(rRelativeChildUri, u"/") )
+ aBuf.append( rRelativeChildUri );
+ else
+ aBuf.append( rRelativeChildUri.substr(1) );
+
+ uno::Reference< ucb::XContentIdentifier > xChildId
+ = new ::ucbhelper::ContentIdentifier( aBuf.makeStringAndClear() );
+
+ uno::Reference< ucb::XContent > xChild;
+ try
+ {
+ xChild = m_pProvider->queryContent( xChildId );
+ }
+ catch ( ucb::IllegalIdentifierException const & )
+ {
+ // handled below.
+ }
+
+ OSL_ENSURE( xChild.is(),
+ "Content::queryChildContent - unable to create child content!" );
+ return xChild;
+}
+
+
+void Content::notifyChildRemoved( std::u16string_view rRelativeChildUri )
+{
+ osl::ClearableGuard< osl::Mutex > aGuard( m_aMutex );
+
+ // Ugly! Need to create child content object, just to fill event properly.
+ uno::Reference< ucb::XContent > xChild
+ = queryChildContent( rRelativeChildUri );
+
+ if ( !xChild.is() )
+ return;
+
+ // callback follows!
+ aGuard.clear();
+
+ // Notify "REMOVED" event.
+ ucb::ContentEvent aEvt(
+ getXWeak(),
+ ucb::ContentAction::REMOVED,
+ xChild,
+ getIdentifier() );
+ notifyContentEvent( aEvt );
+}
+
+
+void Content::notifyChildInserted( std::u16string_view rRelativeChildUri )
+{
+ osl::ClearableGuard< osl::Mutex > aGuard( m_aMutex );
+
+ // Ugly! Need to create child content object, just to fill event properly.
+ uno::Reference< ucb::XContent > xChild
+ = queryChildContent( rRelativeChildUri );
+
+ if ( !xChild.is() )
+ return;
+
+ // callback follows!
+ aGuard.clear();
+
+ // Notify "INSERTED" event.
+ ucb::ContentEvent aEvt(
+ getXWeak(),
+ ucb::ContentAction::INSERTED,
+ xChild,
+ getIdentifier() );
+ notifyContentEvent( aEvt );
+}
+
+
+void Content::transfer(
+ const ucb::TransferInfo& rInfo,
+ const uno::Reference< ucb::XCommandEnvironment > & xEnv )
+{
+ osl::ClearableGuard< osl::Mutex > aGuard( m_aMutex );
+
+ // Persistent?
+ if ( m_eState != PERSISTENT )
+ {
+ ucbhelper::cancelCommandExecution(
+ uno::Any( ucb::UnsupportedCommandException(
+ "Not persistent!",
+ getXWeak() ) ),
+ xEnv );
+ // Unreachable
+ }
+
+ // Does source URI scheme match? Only vnd.sun.star.tdoc is supported.
+
+ if ( rInfo.SourceURL.getLength() < TDOC_URL_SCHEME_LENGTH + 2 )
+ {
+ // Invalid length (to short).
+ ucbhelper::cancelCommandExecution(
+ uno::Any( ucb::InteractiveBadTransferURLException(
+ OUString(),
+ getXWeak() ) ),
+ xEnv );
+ // Unreachable
+ }
+
+ OUString aScheme
+ = rInfo.SourceURL.copy( 0, TDOC_URL_SCHEME_LENGTH + 2 )
+ .toAsciiLowerCase();
+ if ( aScheme != TDOC_URL_SCHEME ":/" )
+ {
+ // Invalid scheme.
+ ucbhelper::cancelCommandExecution(
+ uno::Any( ucb::InteractiveBadTransferURLException(
+ OUString(),
+ getXWeak() ) ),
+ xEnv );
+ // Unreachable
+ }
+
+ // Does source URI describe a tdoc folder or stream?
+ Uri aSourceUri( rInfo.SourceURL );
+ if ( !aSourceUri.isValid() )
+ {
+ ucbhelper::cancelCommandExecution(
+ uno::Any( lang::IllegalArgumentException(
+ "Invalid source URI! Syntax!",
+ getXWeak(),
+ -1 ) ),
+ xEnv );
+ // Unreachable
+ }
+
+ if ( aSourceUri.isRoot() || aSourceUri.isDocument() )
+ {
+ ucbhelper::cancelCommandExecution(
+ uno::Any( lang::IllegalArgumentException(
+ "Invalid source URI! Must describe a folder or stream!",
+ getXWeak(),
+ -1 ) ),
+ xEnv );
+ // Unreachable
+ }
+
+ // Is source not a parent of me / not me?
+ OUString aId = m_xIdentifier->getContentIdentifier();
+ sal_Int32 nPos = aId.lastIndexOf( '/' );
+ if ( nPos != ( aId.getLength() - 1 ) )
+ {
+ // No trailing slash found. Append.
+ aId += "/";
+ }
+
+ if ( rInfo.SourceURL.getLength() <= aId.getLength() )
+ {
+ if ( aId.startsWith( rInfo.SourceURL ) )
+ {
+ uno::Sequence<uno::Any> aArgs(comphelper::InitAnyPropertySequence(
+ {
+ {"Uri", uno::Any(rInfo.SourceURL)}
+ }));
+ ucbhelper::cancelCommandExecution(
+ ucb::IOErrorCode_RECURSIVE,
+ aArgs,
+ xEnv,
+ "Target is equal to or is a child of source!",
+ this );
+ // Unreachable
+ }
+ }
+
+ if ( m_aProps.getType() == DOCUMENT )
+ {
+ bool bOK = false;
+
+ uno::Reference< embed::XStorage > xStorage
+ = m_pProvider->queryStorage(
+ aSourceUri.getParentUri(), READ_WRITE_NOCREATE );
+ if ( xStorage.is() )
+ {
+ try
+ {
+ if ( xStorage->isStreamElement( aSourceUri.getDecodedName() ) )
+ {
+ ucbhelper::cancelCommandExecution(
+ uno::Any( lang::IllegalArgumentException(
+ "Invalid source URI! "
+ "Streams cannot be created as "
+ "children of document root!",
+ getXWeak(),
+ -1 ) ),
+ xEnv );
+ // Unreachable
+ }
+ bOK = true;
+ }
+ catch ( container::NoSuchElementException const & )
+ {
+ // handled below.
+ }
+ catch ( lang::IllegalArgumentException const & )
+ {
+ // handled below.
+ }
+ catch ( embed::InvalidStorageException const & )
+ {
+ // handled below.
+ }
+ }
+
+ if ( !bOK )
+ {
+ ucbhelper::cancelCommandExecution(
+ uno::Any( lang::IllegalArgumentException(
+ "Invalid source URI! Unable to determine source type!",
+ getXWeak(),
+ -1 ) ),
+ xEnv );
+ // Unreachable
+ }
+ }
+
+
+ // Copy data.
+
+
+ OUString aNewName( !rInfo.NewTitle.isEmpty()
+ ? rInfo.NewTitle
+ : aSourceUri.getDecodedName() );
+
+ if ( !copyData( aSourceUri, aNewName ) )
+ {
+ uno::Sequence<uno::Any> aArgs(comphelper::InitAnyPropertySequence(
+ {
+ {"Uri", uno::Any(rInfo.SourceURL)}
+ }));
+ ucbhelper::cancelCommandExecution(
+ ucb::IOErrorCode_CANT_WRITE,
+ aArgs,
+ xEnv,
+ "Cannot copy data!",
+ this );
+ // Unreachable
+ }
+
+
+ // Copy own and all children's Additional Core Properties.
+
+
+ OUString aTargetUri = m_xIdentifier->getContentIdentifier();
+ if ( ( aTargetUri.lastIndexOf( '/' ) + 1 ) != aTargetUri.getLength() )
+ aTargetUri += "/";
+
+ if ( !rInfo.NewTitle.isEmpty() )
+ aTargetUri += ::ucb_impl::urihelper::encodeSegment( rInfo.NewTitle );
+ else
+ aTargetUri += aSourceUri.getName();
+
+ if ( !copyAdditionalPropertySet( aSourceUri.getUri(), aTargetUri ) )
+ {
+ uno::Sequence<uno::Any> aArgs(comphelper::InitAnyPropertySequence(
+ {
+ {"Uri", uno::Any(rInfo.SourceURL)}
+ }));
+ ucbhelper::cancelCommandExecution(
+ ucb::IOErrorCode_CANT_WRITE,
+ aArgs,
+ xEnv,
+ "Cannot copy additional properties!",
+ this );
+ // Unreachable
+ }
+
+
+ // Propagate new content.
+
+
+ rtl::Reference< Content > xTarget;
+ try
+ {
+ uno::Reference< ucb::XContentIdentifier > xTargetId
+ = new ::ucbhelper::ContentIdentifier( aTargetUri );
+
+ // Note: The static cast is okay here, because its sure that
+ // m_xProvider is always the WebDAVContentProvider.
+ xTarget = static_cast< Content * >(
+ m_pProvider->queryContent( xTargetId ).get() );
+
+ }
+ catch ( ucb::IllegalIdentifierException const & )
+ {
+ // queryContent
+ }
+
+ if ( !xTarget.is() )
+ {
+ uno::Sequence<uno::Any> aArgs(comphelper::InitAnyPropertySequence(
+ {
+ {"Uri", uno::Any(aTargetUri)}
+ }));
+ ucbhelper::cancelCommandExecution(
+ ucb::IOErrorCode_CANT_READ,
+ aArgs,
+ xEnv,
+ "Cannot instantiate target object!",
+ this );
+ // Unreachable
+ }
+
+ // Announce transferred content in its new folder.
+ xTarget->inserted();
+
+
+ // Remove source, if requested
+
+
+ if ( !rInfo.MoveData )
+ return;
+
+ rtl::Reference< Content > xSource;
+ try
+ {
+ uno::Reference< ucb::XContentIdentifier >
+ xSourceId = new ::ucbhelper::ContentIdentifier( rInfo.SourceURL );
+
+ // Note: The static cast is okay here, because its sure
+ // that m_xProvider is always the ContentProvider.
+ xSource = static_cast< Content * >(
+ m_xProvider->queryContent( xSourceId ).get() );
+ }
+ catch ( ucb::IllegalIdentifierException const & )
+ {
+ // queryContent
+ }
+
+ if ( !xSource.is() )
+ {
+ uno::Sequence<uno::Any> aArgs(comphelper::InitAnyPropertySequence(
+ {
+ {"Uri", uno::Any(rInfo.SourceURL)}
+ }));
+ ucbhelper::cancelCommandExecution(
+ ucb::IOErrorCode_CANT_READ,
+ aArgs,
+ xEnv,
+ "Cannot instantiate target object!",
+ this );
+ // Unreachable
+ }
+
+ // Propagate destruction (recursively).
+ xSource->destroy( true, xEnv );
+
+ // Remove all persistent data of source and its children.
+ if ( !xSource->removeData() )
+ {
+ uno::Sequence<uno::Any> aArgs(comphelper::InitAnyPropertySequence(
+ {
+ {"Uri", uno::Any(rInfo.SourceURL)}
+ }));
+ ucbhelper::cancelCommandExecution(
+ ucb::IOErrorCode_CANT_WRITE,
+ aArgs,
+ xEnv,
+ "Cannot remove persistent data of source object!",
+ this );
+ // Unreachable
+ }
+
+ // Remove own and all children's Additional Core Properties.
+ if ( xSource->removeAdditionalPropertySet() )
+ return;
+
+ uno::Sequence<uno::Any> aArgs(comphelper::InitAnyPropertySequence(
+ {
+ {"Uri", uno::Any(rInfo.SourceURL)}
+ }));
+ ucbhelper::cancelCommandExecution(
+ ucb::IOErrorCode_CANT_WRITE,
+ aArgs,
+ xEnv,
+ "Cannot remove additional properties of source object!",
+ this );
+ // Unreachable
+}
+
+
+//static
+bool Content::hasData( ContentProvider const * pProvider, const Uri & rUri )
+{
+ if ( rUri.isRoot() )
+ {
+ return true; // root has no storage
+ }
+ else if ( rUri.isDocument() )
+ {
+ uno::Reference< embed::XStorage > xStorage
+ = pProvider->queryStorage( rUri.getUri(), READ );
+ return xStorage.is();
+ }
+ else
+ {
+ // folder or stream
+
+ // Ask parent storage. In case that rUri describes a stream,
+ // ContentProvider::queryStorage( rUri ) would return null.
+
+ uno::Reference< embed::XStorage > xStorage
+ = pProvider->queryStorage( rUri.getParentUri(), READ );
+
+ if ( !xStorage.is() )
+ return false;
+
+ return xStorage->hasByName( rUri.getDecodedName() );
+ }
+}
+
+
+//static
+bool Content::loadData( ContentProvider const * pProvider,
+ const Uri & rUri,
+ ContentProperties& rProps )
+{
+ if ( rUri.isRoot() ) // root has no storage, but can always be created
+ {
+ rProps
+ = ContentProperties(
+ ROOT, pProvider->queryStorageTitle( rUri.getUri() ) );
+ }
+ else if ( rUri.isDocument() ) // document must have storage
+ {
+ uno::Reference< embed::XStorage > xStorage
+ = pProvider->queryStorage( rUri.getUri(), READ );
+
+ if ( !xStorage.is() )
+ return false;
+
+ rProps
+ = ContentProperties(
+ DOCUMENT, pProvider->queryStorageTitle( rUri.getUri() ) );
+ }
+ else // stream or folder; stream has no storage; folder has storage
+ {
+ uno::Reference< embed::XStorage > xStorage
+ = pProvider->queryStorage( rUri.getParentUri(), READ );
+
+ if ( !xStorage.is() )
+ return false;
+
+ // Check whether exists at all, is stream or folder
+ try
+ {
+ // return: true -> folder
+ // return: false -> stream
+ // NoSuchElementException -> neither folder nor stream
+ bool bIsFolder
+ = xStorage->isStorageElement( rUri.getDecodedName() );
+
+ rProps
+ = ContentProperties(
+ bIsFolder ? FOLDER : STREAM,
+ pProvider->queryStorageTitle( rUri.getUri() ) );
+ }
+ catch ( container::NoSuchElementException const & )
+ {
+ // there is no element with such name
+ //OSL_ENSURE( false, "Caught NoSuchElementException!" );
+ return false;
+ }
+ catch ( lang::IllegalArgumentException const & )
+ {
+ // an illegal argument is provided
+ TOOLS_WARN_EXCEPTION("ucb.ucp", "");
+ return false;
+ }
+ catch ( embed::InvalidStorageException const & )
+ {
+ // this storage is in invalid state for any reason
+ TOOLS_WARN_EXCEPTION("ucb.ucp", "");
+ return false;
+ }
+ }
+ return true;
+}
+
+
+bool Content::storeData( const uno::Reference< io::XInputStream >& xData,
+ const uno::Reference<
+ ucb::XCommandEnvironment >& xEnv )
+{
+ osl::Guard< osl::Mutex > aGuard( m_aMutex );
+
+ ContentType eType = m_aProps.getType();
+ if ( ( eType == ROOT ) || ( eType == DOCUMENT ) )
+ {
+ OSL_FAIL( "storeData not supported by root and documents!" );
+ return false;
+ }
+
+ Uri aUri( m_xIdentifier->getContentIdentifier() );
+
+ if ( eType == FOLDER )
+ {
+ uno::Reference< embed::XStorage > xStorage
+ = m_pProvider->queryStorage( aUri.getUri(), READ_WRITE_CREATE );
+
+ if ( !xStorage.is() )
+ return false;
+
+ uno::Reference< beans::XPropertySet > xPropSet(
+ xStorage, uno::UNO_QUERY );
+ OSL_ENSURE( xPropSet.is(),
+ "Content::storeData - Got no XPropertySet interface!" );
+ if ( !xPropSet.is() )
+ return false;
+
+ try
+ {
+ // According to MBA, if no mediatype is set, folder and all
+ // its contents will be lost on save of the document!!!
+ xPropSet->setPropertyValue(
+ "MediaType",
+ uno::Any(
+ OUString( // @@@ better mediatype
+ "application/binary" ) ) );
+ }
+ catch ( beans::UnknownPropertyException const & )
+ {
+ OSL_FAIL( "Property MediaType not supported!" );
+ return false;
+ }
+ catch ( beans::PropertyVetoException const & )
+ {
+ TOOLS_WARN_EXCEPTION("ucb.ucp", "");
+ return false;
+ }
+ catch ( lang::IllegalArgumentException const & )
+ {
+ TOOLS_WARN_EXCEPTION("ucb.ucp", "");
+ return false;
+ }
+ catch ( lang::WrappedTargetException const & )
+ {
+ TOOLS_WARN_EXCEPTION("ucb.ucp", "");
+ return false;
+ }
+
+ if ( !commitStorage( xStorage ) )
+ return false;
+ }
+ else if ( eType == STREAM )
+ {
+ // stream
+
+ // Important: Parent storage and output stream must be kept alive until
+ // changes have been committed!
+ uno::Reference< embed::XStorage > xStorage
+ = m_pProvider->queryStorage(
+ aUri.getParentUri(), READ_WRITE_CREATE );
+ uno::Reference< io::XOutputStream > xOut;
+
+ if ( !xStorage.is() )
+ return false;
+
+ if ( xData.is() )
+ {
+ // May throw CommandFailedException, DocumentPasswordRequest!
+ xOut = getTruncatedOutputStream( xEnv );
+
+ OSL_ENSURE( xOut.is(), "No target data stream!" );
+
+ try
+ {
+ uno::Sequence< sal_Int8 > aBuffer;
+ while (true)
+ {
+ sal_Int32 nRead = xData->readSomeBytes( aBuffer, 65536 );
+ if (!nRead)
+ break;
+ aBuffer.realloc( nRead );
+ xOut->writeBytes( aBuffer );
+ }
+
+ closeOutputStream( xOut );
+ }
+ catch ( io::NotConnectedException const & )
+ {
+ // readSomeBytes, writeBytes
+ TOOLS_WARN_EXCEPTION("ucb.ucp", "");
+ closeOutputStream( xOut );
+ return false;
+ }
+ catch ( io::BufferSizeExceededException const & )
+ {
+ // readSomeBytes, writeBytes
+ TOOLS_WARN_EXCEPTION("ucb.ucp", "");
+ closeOutputStream( xOut );
+ return false;
+ }
+ catch ( io::IOException const & )
+ {
+ // readSomeBytes, writeBytes
+ TOOLS_WARN_EXCEPTION("ucb.ucp", "");
+ closeOutputStream( xOut );
+ return false;
+ }
+ catch ( ... )
+ {
+ closeOutputStream( xOut );
+ throw;
+ }
+ }
+
+ // Commit changes.
+ if ( !commitStorage( xStorage ) )
+ return false;
+ }
+ else
+ {
+ OSL_FAIL( "Unknown content type!" );
+ return false;
+ }
+ return true;
+}
+
+
+void Content::renameData(
+ const uno::Reference< ucb::XContentIdentifier >& xOldId,
+ const uno::Reference< ucb::XContentIdentifier >& xNewId )
+{
+ osl::Guard< osl::Mutex > aGuard( m_aMutex );
+
+ ContentType eType = m_aProps.getType();
+ if ( ( eType == ROOT ) || ( eType == DOCUMENT ) )
+ {
+ OSL_FAIL( "renameData not supported by root and documents!" );
+ return;
+ }
+
+ Uri aOldUri( xOldId->getContentIdentifier() );
+ uno::Reference< embed::XStorage > xStorage
+ = m_pProvider->queryStorage(
+ aOldUri.getParentUri(), READ_WRITE_NOCREATE );
+
+ if ( !xStorage.is() )
+ return;
+
+ try
+ {
+ Uri aNewUri( xNewId->getContentIdentifier() );
+ xStorage->renameElement(
+ aOldUri.getDecodedName(), aNewUri.getDecodedName() );
+ }
+ catch ( embed::InvalidStorageException const & )
+ {
+ // this storage is in invalid state for any reason
+ TOOLS_WARN_EXCEPTION("ucb.ucp", "");
+ return;
+ }
+ catch ( lang::IllegalArgumentException const & )
+ {
+ // an illegal argument is provided
+ TOOLS_WARN_EXCEPTION("ucb.ucp", "");
+ return;
+ }
+ catch ( container::NoSuchElementException const & )
+ {
+ // there is no element with old name in this storage
+ TOOLS_WARN_EXCEPTION("ucb.ucp", "");
+ return;
+ }
+ catch ( container::ElementExistException const & )
+ {
+ // an element with new name already exists in this storage
+ TOOLS_WARN_EXCEPTION("ucb.ucp", "");
+ return;
+ }
+ catch ( io::IOException const & )
+ {
+ // in case of io errors during renaming
+ TOOLS_WARN_EXCEPTION("ucb.ucp", "");
+ return;
+ }
+ catch ( embed::StorageWrappedTargetException const & )
+ {
+ // wraps other exceptions
+ TOOLS_WARN_EXCEPTION("ucb.ucp", "");
+ return;
+ }
+
+ commitStorage( xStorage );
+}
+
+
+bool Content::removeData()
+{
+ osl::Guard< osl::Mutex > aGuard( m_aMutex );
+
+ ContentType eType = m_aProps.getType();
+ if ( ( eType == ROOT ) || ( eType == DOCUMENT ) )
+ {
+ OSL_FAIL( "removeData not supported by root and documents!" );
+ return false;
+ }
+
+ Uri aUri( m_xIdentifier->getContentIdentifier() );
+ uno::Reference< embed::XStorage > xStorage
+ = m_pProvider->queryStorage(
+ aUri.getParentUri(), READ_WRITE_NOCREATE );
+
+ if ( !xStorage.is() )
+ return false;
+
+ try
+ {
+ xStorage->removeElement( aUri.getDecodedName() );
+ }
+ catch ( embed::InvalidStorageException const & )
+ {
+ // this storage is in invalid state for any reason
+ TOOLS_WARN_EXCEPTION("ucb.ucp", "");
+ return false;
+ }
+ catch ( lang::IllegalArgumentException const & )
+ {
+ // an illegal argument is provided
+ TOOLS_WARN_EXCEPTION("ucb.ucp", "");
+ return false;
+ }
+ catch ( container::NoSuchElementException const & )
+ {
+ // there is no element with this name in this storage
+ TOOLS_WARN_EXCEPTION("ucb.ucp", "");
+ return false;
+ }
+ catch ( io::IOException const & )
+ {
+ // in case of io errors during renaming
+ TOOLS_WARN_EXCEPTION("ucb.ucp", "");
+ return false;
+ }
+ catch ( embed::StorageWrappedTargetException const & )
+ {
+ // wraps other exceptions
+ TOOLS_WARN_EXCEPTION("ucb.ucp", "");
+ return false;
+ }
+
+ return commitStorage( xStorage );
+}
+
+
+bool Content::copyData( const Uri & rSourceUri, const OUString & rNewName )
+{
+ osl::Guard< osl::Mutex > aGuard( m_aMutex );
+
+ ContentType eType = m_aProps.getType();
+ if ( ( eType == ROOT ) || ( eType == STREAM ) )
+ {
+ OSL_FAIL( "copyData not supported by root and streams!" );
+ return false;
+ }
+
+ Uri aDestUri( m_xIdentifier->getContentIdentifier() );
+ uno::Reference< embed::XStorage > xDestStorage
+ = m_pProvider->queryStorage( aDestUri.getUri(), READ_WRITE_NOCREATE );
+
+ if ( !xDestStorage.is() )
+ return false;
+
+ uno::Reference< embed::XStorage > xSourceStorage
+ = m_pProvider->queryStorage( rSourceUri.getParentUri(), READ );
+
+ if ( !xSourceStorage.is() )
+ return false;
+
+ try
+ {
+ xSourceStorage->copyElementTo( rSourceUri.getDecodedName(),
+ xDestStorage,
+ rNewName );
+ }
+ catch ( embed::InvalidStorageException const & )
+ {
+ // this storage is in invalid state for any reason
+ TOOLS_WARN_EXCEPTION("ucb.ucp", "");
+ return false;
+ }
+ catch ( lang::IllegalArgumentException const & )
+ {
+ // an illegal argument is provided
+ TOOLS_WARN_EXCEPTION("ucb.ucp", "");
+ return false;
+ }
+ catch ( container::NoSuchElementException const & )
+ {
+ // there is no element with this name in this storage
+ TOOLS_WARN_EXCEPTION("ucb.ucp", "");
+ return false;
+ }
+ catch ( container::ElementExistException const & )
+ {
+ // there is no element with this name in this storage
+ TOOLS_WARN_EXCEPTION("ucb.ucp", "");
+ return false;
+ }
+ catch ( io::IOException const & )
+ {
+ // in case of io errors during renaming
+ TOOLS_WARN_EXCEPTION("ucb.ucp", "");
+ return false;
+ }
+ catch ( embed::StorageWrappedTargetException const & )
+ {
+ // wraps other exceptions
+ TOOLS_WARN_EXCEPTION("ucb.ucp", "");
+ return false;
+ }
+
+ return commitStorage( xDestStorage );
+}
+
+
+// static
+bool Content::commitStorage( const uno::Reference< embed::XStorage > & xStorage )
+{
+ // Commit changes
+ uno::Reference< embed::XTransactedObject > xTO( xStorage, uno::UNO_QUERY );
+
+ OSL_ENSURE( xTO.is(),
+ "Required interface css.embed.XTransactedObject missing!" );
+ try
+ {
+ xTO->commit();
+ }
+ catch ( io::IOException const & )
+ {
+ TOOLS_WARN_EXCEPTION("ucb.ucp", "");
+ return false;
+ }
+ catch ( lang::WrappedTargetException const & )
+ {
+ TOOLS_WARN_EXCEPTION("ucb.ucp", "");
+ return false;
+ }
+
+ return true;
+}
+
+
+// static
+bool Content::closeOutputStream(
+ const uno::Reference< io::XOutputStream > & xOut )
+{
+ if ( xOut.is() )
+ {
+ try
+ {
+ xOut->closeOutput();
+ return true;
+ }
+ catch ( io::NotConnectedException const & )
+ {
+ TOOLS_WARN_EXCEPTION("ucb.ucp", "");
+ }
+ catch ( io::BufferSizeExceededException const & )
+ {
+ TOOLS_WARN_EXCEPTION("ucb.ucp", "");
+ }
+ catch ( io::IOException const & )
+ {
+ TOOLS_WARN_EXCEPTION("ucb.ucp", "");
+ }
+ }
+ return false;
+}
+
+/// @throws ucb::CommandFailedException
+/// @throws task::DocumentPasswordRequest
+static OUString obtainPassword(
+ const OUString & rName,
+ task::PasswordRequestMode eMode,
+ const uno::Reference< ucb::XCommandEnvironment > & xEnv )
+{
+ rtl::Reference< DocumentPasswordRequest > xRequest
+ = new DocumentPasswordRequest( eMode, rName );
+
+ if ( xEnv.is() )
+ {
+ uno::Reference< task::XInteractionHandler > xIH
+ = xEnv->getInteractionHandler();
+ if ( xIH.is() )
+ {
+ 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() )
+ {
+ throw ucb::CommandFailedException(
+ "Abort requested by Interaction Handler.",
+ uno::Reference< uno::XInterface >(),
+ xRequest->getRequest() );
+ }
+
+ uno::Reference< task::XInteractionPassword > xPassword(
+ xSelection.get(), uno::UNO_QUERY );
+ if ( xPassword.is() )
+ {
+ return xPassword->getPassword();
+ }
+
+ // Unknown selection. Should never happen.
+ throw ucb::CommandFailedException(
+ "Interaction Handler selected unknown continuation!",
+ uno::Reference< uno::XInterface >(),
+ xRequest->getRequest() );
+ }
+ }
+ }
+
+ // No IH or IH did not handle exception.
+ task::DocumentPasswordRequest aRequest;
+ xRequest->getRequest() >>= aRequest;
+ throw aRequest;
+}
+
+
+uno::Reference< io::XInputStream > Content::getInputStream(
+ const uno::Reference< ucb::XCommandEnvironment > & xEnv )
+{
+ OUString aUri;
+ OUString aPassword;
+ bool bPasswordRequested = false;
+
+ {
+ osl::Guard< osl::Mutex > aGuard( m_aMutex );
+
+ OSL_ENSURE( m_aProps.getType() == STREAM,
+ "Content::getInputStream - content is no stream!" );
+
+ aUri = Uri( m_xIdentifier->getContentIdentifier() ).getUri();
+ }
+
+ for ( ;; )
+ {
+ try
+ {
+ osl::Guard< osl::Mutex > aGuard( m_aMutex );
+ return m_pProvider->queryInputStream( aUri, aPassword );
+ }
+ catch ( packages::WrongPasswordException const & )
+ {
+ // Obtain (new) password.
+ aPassword
+ = obtainPassword( aUri, /* @@@ find better title */
+ bPasswordRequested
+ ? task::PasswordRequestMode_PASSWORD_REENTER
+ : task::PasswordRequestMode_PASSWORD_ENTER,
+ xEnv );
+ bPasswordRequested = true;
+ }
+ }
+}
+
+/// @throws ucb::CommandFailedException
+/// @throws task::DocumentPasswordRequest
+/// @throws uno::RuntimeException
+static uno::Reference< io::XOutputStream > lcl_getTruncatedOutputStream(
+ const OUString & rUri,
+ ContentProvider const * pProvider,
+ const uno::Reference< ucb::XCommandEnvironment > & xEnv )
+{
+ OUString aPassword;
+ bool bPasswordRequested = false;
+ for ( ;; )
+ {
+ try
+ {
+ return pProvider->queryOutputStream(
+ rUri, aPassword, true /* truncate */ );
+ }
+ catch ( packages::WrongPasswordException const & )
+ {
+ // Obtain (new) password.
+ aPassword
+ = obtainPassword( rUri, /* @@@ find better title */
+ bPasswordRequested
+ ? task::PasswordRequestMode_PASSWORD_REENTER
+ : task::PasswordRequestMode_PASSWORD_ENTER,
+ xEnv );
+ bPasswordRequested = true;
+ }
+ }
+}
+
+
+uno::Reference< io::XOutputStream > Content::getTruncatedOutputStream(
+ const uno::Reference< ucb::XCommandEnvironment > & xEnv )
+{
+ OSL_ENSURE( m_aProps.getType() == STREAM,
+ "Content::getTruncatedOutputStream - content is no stream!" );
+
+ return lcl_getTruncatedOutputStream(
+ Uri( m_xIdentifier->getContentIdentifier() ).getUri(),
+ m_pProvider,
+ xEnv );
+}
+
+
+uno::Reference< io::XStream > Content::getStream(
+ const uno::Reference< ucb::XCommandEnvironment > & xEnv )
+{
+ osl::Guard< osl::Mutex > aGuard( m_aMutex );
+
+ OSL_ENSURE( m_aProps.getType() == STREAM,
+ "Content::getStream - content is no stream!" );
+
+ OUString aUri( Uri( m_xIdentifier->getContentIdentifier() ).getUri() );
+ OUString aPassword;
+ bool bPasswordRequested = false;
+ for ( ;; )
+ {
+ try
+ {
+ return m_pProvider->queryStream(
+ aUri, aPassword, false /* no truncate */ );
+ }
+ catch ( packages::WrongPasswordException const & )
+ {
+ // Obtain (new) password.
+ aPassword
+ = obtainPassword( aUri, /* @@@ find better title */
+ bPasswordRequested
+ ? task::PasswordRequestMode_PASSWORD_REENTER
+ : task::PasswordRequestMode_PASSWORD_ENTER,
+ xEnv );
+ bPasswordRequested = true;
+ }
+ }
+}
+
+
+// ContentProperties Implementation.
+
+
+uno::Sequence< ucb::ContentInfo >
+ContentProperties::getCreatableContentsInfo() const
+{
+ if ( isContentCreator() )
+ {
+ uno::Sequence< beans::Property > aProps( 1 );
+ aProps.getArray()[ 0 ] = beans::Property(
+ "Title",
+ -1,
+ cppu::UnoType<OUString>::get(),
+ beans::PropertyAttribute::BOUND );
+
+ if ( getType() == DOCUMENT )
+ {
+ // streams cannot be created as direct children of document root
+ uno::Sequence< ucb::ContentInfo > aSeq( 1 );
+
+ // Folder.
+ aSeq.getArray()[ 0 ].Type = TDOC_FOLDER_CONTENT_TYPE;
+ aSeq.getArray()[ 0 ].Attributes = ucb::ContentInfoAttribute::KIND_FOLDER;
+ aSeq.getArray()[ 0 ].Properties = aProps;
+
+ return aSeq;
+ }
+ else
+ {
+ uno::Sequence< ucb::ContentInfo > aSeq( 2 );
+
+ // Folder.
+ aSeq.getArray()[ 0 ].Type = TDOC_FOLDER_CONTENT_TYPE;
+ aSeq.getArray()[ 0 ].Attributes
+ = ucb::ContentInfoAttribute::KIND_FOLDER;
+ aSeq.getArray()[ 0 ].Properties = aProps;
+
+ // Stream.
+ aSeq.getArray()[ 1 ].Type = TDOC_STREAM_CONTENT_TYPE;
+ aSeq.getArray()[ 1 ].Attributes
+ = ucb::ContentInfoAttribute::INSERT_WITH_INPUTSTREAM
+ | ucb::ContentInfoAttribute::KIND_DOCUMENT;
+ aSeq.getArray()[ 1 ].Properties = aProps;
+
+ return aSeq;
+ }
+ }
+ else
+ {
+ OSL_FAIL( "getCreatableContentsInfo called on non-contentcreator "
+ "object!" );
+
+ return uno::Sequence< ucb::ContentInfo >( 0 );
+ }
+}
+
+
+bool ContentProperties::isContentCreator() const
+{
+ return ( getType() == FOLDER ) || ( getType() == DOCUMENT );
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/ucb/source/ucp/tdoc/tdoc_content.hxx b/ucb/source/ucp/tdoc/tdoc_content.hxx
new file mode 100644
index 0000000000..a292877f2e
--- /dev/null
+++ b/ucb/source/ucp/tdoc/tdoc_content.hxx
@@ -0,0 +1,283 @@
+/* -*- 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:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#pragma once
+
+#include <sal/config.h>
+
+#include <string_view>
+
+#include <ucbhelper/contenthelper.hxx>
+#include <com/sun/star/ucb/XContentCreator.hpp>
+#include <utility>
+#include "tdoc_provider.hxx"
+
+namespace com::sun::star {
+ namespace sdbc { class XRow; }
+ namespace io { class XInputStream; class XOutputStream; }
+ namespace beans { struct PropertyValue; }
+ namespace ucb { struct OpenCommandArgument2; struct TransferInfo;
+ struct ContentInfo; }
+}
+
+namespace tdoc_ucp
+{
+
+
+enum ContentType { STREAM, FOLDER, DOCUMENT, ROOT };
+
+class ContentProperties
+{
+public:
+ ContentProperties()
+ : m_eType( STREAM )
+ {}
+
+ ContentProperties( const ContentType & rType, OUString aTitle )
+ : m_eType( rType ),
+ m_aContentType( rType == STREAM
+ ? TDOC_STREAM_CONTENT_TYPE
+ : rType == FOLDER
+ ? TDOC_FOLDER_CONTENT_TYPE
+ : rType == DOCUMENT
+ ? TDOC_DOCUMENT_CONTENT_TYPE
+ : TDOC_ROOT_CONTENT_TYPE ),
+ m_aTitle(std::move( aTitle ))
+ {}
+
+ ContentType getType() const { return m_eType; }
+
+ // Properties
+
+ const OUString & getContentType() const { return m_aContentType; }
+
+ bool getIsFolder() const { return m_eType > STREAM; }
+ bool getIsDocument() const { return !getIsFolder(); }
+
+ const OUString & getTitle() const { return m_aTitle; }
+ void setTitle( const OUString & rTitle ) { m_aTitle = rTitle; }
+
+ css::uno::Sequence< css::ucb::ContentInfo >
+ getCreatableContentsInfo() const;
+
+ bool isContentCreator() const;
+
+private:
+ ContentType m_eType;
+ OUString m_aContentType;
+ OUString m_aTitle;
+};
+
+
+class Content : public ::ucbhelper::ContentImplHelper,
+ public css::ucb::XContentCreator
+{
+ enum ContentState { TRANSIENT, // created via createNewContent,
+ // but did not process "insert" yet
+ PERSISTENT, // processed "insert"
+ DEAD // processed "delete" / document was closed
+ };
+
+ ContentProperties m_aProps;
+ ContentState m_eState;
+ ContentProvider* m_pProvider;
+
+private:
+ Content( const css::uno::Reference< css::uno::XComponentContext >& rxContext,
+ ContentProvider* pProvider,
+ const css::uno::Reference< css::ucb::XContentIdentifier >& Identifier,
+ ContentProperties aProps );
+ Content( const css::uno::Reference< css::uno::XComponentContext >& rxContext,
+ ContentProvider* pProvider,
+ const css::uno::Reference< css::ucb::XContentIdentifier >& Identifier,
+ const css::ucb::ContentInfo& Info );
+
+ 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;
+
+ static bool hasData( ContentProvider const * pProvider, const Uri & rUri );
+ bool hasData( const Uri & rUri ) const { return hasData( m_pProvider, rUri ); }
+
+ static bool loadData( ContentProvider const * pProvider,
+ const Uri & rUri,
+ ContentProperties& rProps );
+ /// @throws css::ucb::CommandFailedException
+ /// @throws css::task::DocumentPasswordRequest
+ /// @throws css::uno::RuntimeException
+ bool storeData( const css::uno::Reference< css::io::XInputStream >& xData,
+ const css::uno::Reference< css::ucb::XCommandEnvironment >& xEnv );
+ void renameData( const css::uno::Reference< css::ucb::XContentIdentifier >& xOldId,
+ const css::uno::Reference< css::ucb::XContentIdentifier >& xNewId );
+ bool removeData();
+
+ bool copyData( const Uri & rSourceUri, const OUString & rNewName );
+
+ css::uno::Reference< css::ucb::XContentIdentifier >
+ makeNewIdentifier( const OUString& rTitle );
+
+ typedef rtl::Reference< Content > ContentRef;
+ typedef std::vector< ContentRef > ContentRefList;
+ void queryChildren( ContentRefList& rChildren );
+
+ bool exchangeIdentity(
+ const css::uno::Reference< css::ucb::XContentIdentifier >& xNewId );
+
+ css::uno::Reference< css::sdbc::XRow >
+ getPropertyValues( const css::uno::Sequence< css::beans::Property >& rProperties );
+ css::uno::Sequence< css::uno::Any >
+ /// @throws css::uno::Exception
+ setPropertyValues(
+ const css::uno::Sequence< css::beans::PropertyValue >& rValues,
+ const css::uno::Reference< css::ucb::XCommandEnvironment > & xEnv );
+
+ /// @throws css::uno::Exception
+ css::uno::Any
+ open( const css::ucb::OpenCommandArgument2& rArg,
+ const css::uno::Reference< css::ucb::XCommandEnvironment >& xEnv );
+
+ /// @throws css::uno::Exception
+ void insert( const css::uno::Reference< css::io::XInputStream >& xData,
+ sal_Int32 nNameClashResolve,
+ const css::uno::Reference< css::ucb::XCommandEnvironment > & xEnv );
+
+ /// @throws css::uno::Exception
+ void destroy( bool bDeletePhysical,
+ const css::uno::Reference< css::ucb::XCommandEnvironment > & xEnv );
+
+ /// @throws css::uno::Exception
+ void transfer( const css::ucb::TransferInfo& rInfo,
+ const css::uno::Reference< css::ucb::XCommandEnvironment > & xEnv );
+
+ static css::uno::Reference< css::sdbc::XRow >
+ getPropertyValues( const css::uno::Reference< css::uno::XComponentContext >& rxContext,
+ const css::uno::Sequence< css::beans::Property >& rProperties,
+ const ContentProperties& rData,
+ ContentProvider* pProvider,
+ const OUString& rContentId );
+
+
+ static bool commitStorage(
+ const css::uno::Reference< css::embed::XStorage > & xStorage );
+
+ static bool closeOutputStream(
+ const css::uno::Reference< css::io::XOutputStream > & xOut );
+
+ /// @throws css::ucb::CommandFailedException
+ /// @throws css::task::DocumentPasswordRequest
+ /// @throws css::uno::RuntimeException
+ css::uno::Reference< css::io::XInputStream >
+ getInputStream( const css::uno::Reference< css::ucb::XCommandEnvironment > & xEnv );
+
+ /// @throws css::ucb::CommandFailedException
+ /// @throws css::task::DocumentPasswordRequest
+ /// @throws css::uno::RuntimeException
+ css::uno::Reference< css::io::XOutputStream >
+ getTruncatedOutputStream(
+ const css::uno::Reference< css::ucb::XCommandEnvironment > & xEnv );
+
+ css::uno::Reference< css::ucb::XContent >
+ queryChildContent( std::u16string_view rRelativeChildUri );
+
+ /// @throws css::ucb::CommandFailedException
+ /// @throws css::task::DocumentPasswordRequest
+ /// @throws css::uno::RuntimeException
+ css::uno::Reference< css::io::XStream >
+ getStream( const css::uno::Reference< css::ucb::XCommandEnvironment > & xEnv );
+
+public:
+ // Create existing content. Fail, if not already exists.
+ static rtl::Reference<Content> create(
+ const css::uno::Reference< css::uno::XComponentContext >& rxContext,
+ ContentProvider* pProvider,
+ const css::uno::Reference< css::ucb::XContentIdentifier >& Identifier );
+
+ // Create new content. Fail, if already exists.
+ static rtl::Reference<Content> create(
+ const css::uno::Reference< css::uno::XComponentContext >& rxContext,
+ ContentProvider* pProvider,
+ const css::uno::Reference< css::ucb::XContentIdentifier >& Identifier,
+ const css::ucb::ContentInfo& Info );
+
+ virtual ~Content() 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 css::uno::Sequence< OUString > SAL_CALL
+ getSupportedServiceNames() override;
+
+ // XContent
+ virtual OUString SAL_CALL
+ getContentType() override;
+ virtual css::uno::Reference< css::ucb::XContentIdentifier > SAL_CALL
+ getIdentifier() override;
+
+ // XCommandProcessor
+ 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;
+
+
+ // Additional interfaces
+
+
+ // XContentCreator
+ 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;
+
+
+ // Non-interface methods.
+
+
+ static css::uno::Reference< css::sdbc::XRow >
+ getPropertyValues( const css::uno::Reference< css::uno::XComponentContext >& rxContext,
+ const css::uno::Sequence< css::beans::Property >& rProperties,
+ ContentProvider* pProvider,
+ const OUString& rContentId );
+
+ void notifyDocumentClosed();
+ void notifyChildRemoved( std::u16string_view rRelativeChildUri );
+ void notifyChildInserted( std::u16string_view rRelativeChildUri );
+
+ rtl::Reference< ContentProvider > getContentProvider() const
+ { return rtl::Reference< ContentProvider >( m_pProvider ); }
+};
+
+} // namespace tdoc_ucp
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/ucb/source/ucp/tdoc/tdoc_contentcaps.cxx b/ucb/source/ucp/tdoc/tdoc_contentcaps.cxx
new file mode 100644
index 0000000000..384bac2f94
--- /dev/null
+++ b/ucb/source/ucp/tdoc/tdoc_contentcaps.cxx
@@ -0,0 +1,631 @@
+/* -*- 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:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+
+/**************************************************************************
+ TODO
+ **************************************************************************
+
+ **************************************************************************
+
+ Props/Commands:
+
+ root document folder folder stream stream
+ (new) (new)
+ ----------------------------------------------------------------
+ ContentType r r r r r r
+ IsDocument r r r r r r
+ IsFolder r r r r r r
+ Title r r w w w w
+ CreatableContentsInfo r r r r r r
+ DateModified - - - - r r
+ Storage - - r r - -
+ DocumentModel - r - - - -
+
+ getCommandInfo x x x x x x
+ getPropertySetInfo x x x x x x
+ getPropertyValues x x x x x x
+ setPropertyValues x x x x x x
+ insert - - x x x(*) x(*)
+ delete - - x - x -
+ open x x x - x -
+ transfer - x x - - -
+ createNewContent - x x - - -
+
+
+ *************************************************************************/
+
+#include <com/sun/star/beans/Property.hpp>
+#include <com/sun/star/beans/PropertyAttribute.hpp>
+#include <com/sun/star/beans/PropertyValue.hpp>
+#include <com/sun/star/embed/XStorage.hpp>
+#include <com/sun/star/frame/XModel.hpp>
+#include <com/sun/star/ucb/CommandInfo.hpp>
+#include <com/sun/star/ucb/OpenCommandArgument2.hpp>
+#include <com/sun/star/ucb/TransferInfo.hpp>
+#include <com/sun/star/util/DateTime.hpp>
+#include <osl/diagnose.h>
+#include <sal/macros.h>
+#include "tdoc_content.hxx"
+
+namespace com::sun::star::embed {
+ class XStorage;
+}
+
+using namespace com::sun::star;
+using namespace tdoc_ucp;
+
+
+// Content implementation.
+
+
+#define MAKEPROPSEQUENCE( a ) \
+ uno::Sequence< beans::Property >( a, SAL_N_ELEMENTS(a) )
+
+#define MAKECMDSEQUENCE( a ) \
+ uno::Sequence< ucb::CommandInfo >( a, SAL_N_ELEMENTS(a) )
+
+
+// IMPORTANT: If any property data ( name / type / ... ) are changed, then
+// Content::getPropertyValues(...) must be adapted too!
+
+
+// virtual
+uno::Sequence< beans::Property > Content::getProperties(
+ const uno::Reference< ucb::XCommandEnvironment > & /*xEnv*/ )
+{
+ osl::Guard< osl::Mutex > aGuard( m_aMutex );
+
+ if ( m_aProps.getType() == STREAM )
+ {
+
+
+ // Stream: Supported properties
+
+
+ static const beans::Property aStreamPropertyInfoTable[] =
+ {
+
+ // Mandatory properties
+
+ beans::Property(
+ "ContentType",
+ -1,
+ cppu::UnoType<OUString>::get(),
+ beans::PropertyAttribute::BOUND
+ | beans::PropertyAttribute::READONLY
+ ),
+ 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
+ ),
+
+ // Optional standard properties
+
+ beans::Property(
+ "CreatableContentsInfo",
+ -1,
+ cppu::UnoType<uno::Sequence< ucb::ContentInfo >>::get(),
+ beans::PropertyAttribute::BOUND
+ | beans::PropertyAttribute::READONLY
+ ),
+ beans::Property(
+ "DateModified",
+ -1,
+ cppu::UnoType<css::util::DateTime>::get(),
+ beans::PropertyAttribute::BOUND
+ | beans::PropertyAttribute::READONLY
+ )
+
+ // New properties
+
+ };
+ return MAKEPROPSEQUENCE( aStreamPropertyInfoTable );
+ }
+ else if ( m_aProps.getType() == FOLDER )
+ {
+
+
+ // Folder: Supported properties
+
+
+ static const beans::Property aFolderPropertyInfoTable[] =
+ {
+
+ // Mandatory properties
+
+ beans::Property(
+ "ContentType",
+ -1,
+ cppu::UnoType<OUString>::get(),
+ beans::PropertyAttribute::BOUND
+ | beans::PropertyAttribute::READONLY
+ ),
+ 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
+ ),
+
+ // Optional standard properties
+
+ beans::Property(
+ "CreatableContentsInfo",
+ -1,
+ cppu::UnoType<uno::Sequence< ucb::ContentInfo >>::get(),
+ beans::PropertyAttribute::BOUND
+ | beans::PropertyAttribute::READONLY
+ ),
+
+ // New properties
+
+ beans::Property(
+ "Storage",
+ -1,
+ cppu::UnoType<embed::XStorage>::get(),
+ beans::PropertyAttribute::BOUND
+ | beans::PropertyAttribute::READONLY
+ )
+ };
+ return MAKEPROPSEQUENCE( aFolderPropertyInfoTable );
+ }
+ else if ( m_aProps.getType() == DOCUMENT )
+ {
+
+
+ // Document: Supported properties
+
+
+ static const beans::Property aDocPropertyInfoTable[] =
+ {
+
+ // Mandatory properties
+
+ beans::Property(
+ "ContentType",
+ -1,
+ cppu::UnoType<OUString>::get(),
+ beans::PropertyAttribute::BOUND
+ | beans::PropertyAttribute::READONLY
+ ),
+ 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::PropertyAttribute::READONLY
+ ),
+
+ // Optional standard properties
+
+ beans::Property(
+ "CreatableContentsInfo",
+ -1,
+ cppu::UnoType<uno::Sequence< ucb::ContentInfo >>::get(),
+ beans::PropertyAttribute::BOUND
+ | beans::PropertyAttribute::READONLY
+ ),
+
+ // New properties
+
+ beans::Property(
+ "DocumentModel",
+ -1,
+ cppu::UnoType<frame::XModel>::get(),
+ beans::PropertyAttribute::BOUND
+ | beans::PropertyAttribute::READONLY
+ )
+ };
+ return MAKEPROPSEQUENCE( aDocPropertyInfoTable );
+ }
+ else
+ {
+
+
+ // Root: Supported properties
+
+
+ OSL_ENSURE( m_aProps.getType() == ROOT, "Wrong content type!" );
+
+ static const beans::Property aRootPropertyInfoTable[] =
+ {
+
+ // Mandatory properties
+
+ beans::Property(
+ "ContentType",
+ -1,
+ cppu::UnoType<OUString>::get(),
+ beans::PropertyAttribute::BOUND
+ | beans::PropertyAttribute::READONLY
+ ),
+ 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::PropertyAttribute::READONLY
+ ),
+
+ // Optional standard properties
+
+ beans::Property(
+ "CreatableContentsInfo",
+ -1,
+ cppu::UnoType<uno::Sequence< ucb::ContentInfo >>::get(),
+ beans::PropertyAttribute::BOUND
+ | beans::PropertyAttribute::READONLY
+ )
+
+ // New properties
+
+ };
+ return MAKEPROPSEQUENCE( aRootPropertyInfoTable );
+ }
+}
+
+
+// virtual
+uno::Sequence< ucb::CommandInfo > Content::getCommands(
+ const uno::Reference< ucb::XCommandEnvironment > & /*xEnv*/ )
+{
+ osl::Guard< osl::Mutex > aGuard( m_aMutex );
+
+ if ( m_aProps.getType() == STREAM )
+ {
+ Uri aUri( m_xIdentifier->getContentIdentifier() );
+ Uri aParentUri( aUri.getParentUri() );
+
+ if ( aParentUri.isDocument() )
+ {
+
+
+ // Stream, that is a child of a document: Supported commands
+
+
+ static const ucb::CommandInfo aStreamCommandInfoTable1[] =
+ {
+
+ // Mandatory 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(
+ "open",
+ -1,
+ cppu::UnoType<ucb::OpenCommandArgument2>::get()
+ )
+
+ // New commands
+
+ };
+ return MAKECMDSEQUENCE( aStreamCommandInfoTable1 );
+ }
+
+
+ // Stream: Supported commands
+
+
+ static const ucb::CommandInfo aStreamCommandInfoTable[] =
+ {
+
+ // Mandatory 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<void>::get()
+ ),
+ ucb::CommandInfo(
+ "open",
+ -1,
+ cppu::UnoType<ucb::OpenCommandArgument2>::get()
+ )
+
+ // New commands
+
+ };
+ return MAKECMDSEQUENCE( aStreamCommandInfoTable );
+ }
+ else if ( m_aProps.getType() == FOLDER )
+ {
+
+
+ // Folder: Supported commands
+
+
+ static const ucb::CommandInfo aFolderCommandInfoTable[] =
+ {
+
+ // Mandatory 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<void>::get()
+ ),
+ ucb::CommandInfo(
+ "open",
+ -1,
+ cppu::UnoType<ucb::OpenCommandArgument2>::get()
+ ),
+ ucb::CommandInfo(
+ "transfer",
+ -1,
+ cppu::UnoType<ucb::TransferInfo>::get()
+ ),
+ ucb::CommandInfo(
+ "createNewContent",
+ -1,
+ cppu::UnoType<ucb::ContentInfo>::get()
+ )
+
+ // New commands
+
+ };
+ return MAKECMDSEQUENCE( aFolderCommandInfoTable );
+ }
+ else if ( m_aProps.getType() == DOCUMENT )
+ {
+
+
+ // Document: Supported commands
+
+
+ static const ucb::CommandInfo aDocCommandInfoTable[] =
+ {
+
+ // Mandatory 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()
+ ),
+ ucb::CommandInfo(
+ "transfer",
+ -1,
+ cppu::UnoType<ucb::TransferInfo>::get()
+ ),
+ ucb::CommandInfo(
+ "createNewContent",
+ -1,
+ cppu::UnoType<ucb::ContentInfo>::get()
+ )
+
+ // New commands
+
+ };
+ return MAKECMDSEQUENCE( aDocCommandInfoTable );
+ }
+ else
+ {
+
+
+ // Root: Supported commands
+
+
+ OSL_ENSURE( m_aProps.getType() == ROOT, "Wrong content type!" );
+
+ static const ucb::CommandInfo aRootCommandInfoTable[] =
+ {
+
+ // Mandatory 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()
+ )
+
+ // New commands
+
+ };
+ return MAKECMDSEQUENCE( aRootCommandInfoTable );
+ }
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/ucb/source/ucp/tdoc/tdoc_datasupplier.cxx b/ucb/source/ucp/tdoc/tdoc_datasupplier.cxx
new file mode 100644
index 0000000000..9930f80d6e
--- /dev/null
+++ b/ucb/source/ucp/tdoc/tdoc_datasupplier.cxx
@@ -0,0 +1,394 @@
+/* -*- 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:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+
+/**************************************************************************
+ TODO
+ **************************************************************************
+
+ *************************************************************************/
+
+#include <optional>
+
+#include <com/sun/star/ucb/IllegalIdentifierException.hpp>
+#include <com/sun/star/ucb/ResultSetException.hpp>
+#include <osl/diagnose.h>
+#include <ucbhelper/contentidentifier.hxx>
+#include <utility>
+
+#include "tdoc_datasupplier.hxx"
+#include "tdoc_content.hxx"
+
+using namespace com::sun::star;
+using namespace tdoc_ucp;
+
+namespace tdoc_ucp
+{
+
+
+// struct ResultListEntry.
+
+namespace {
+
+
+}
+
+// struct DataSupplier_Impl.
+
+
+}
+
+// DataSupplier Implementation.
+ResultSetDataSupplier::ResultSetDataSupplier(
+ uno::Reference< uno::XComponentContext > xContext,
+ rtl::Reference< Content > xContent )
+: m_xContent(std::move( xContent )), m_xContext(std::move( xContext )),
+ m_bCountFinal( false ), m_bThrowException( false )
+{
+}
+
+// virtual
+ResultSetDataSupplier::~ResultSetDataSupplier()
+{
+}
+
+// virtual
+OUString
+ResultSetDataSupplier::queryContentIdentifierString( sal_uInt32 nIndex )
+{
+ std::unique_lock aGuard( m_aMutex );
+ return queryContentIdentifierStringImpl(aGuard, nIndex);
+}
+
+OUString
+ResultSetDataSupplier::queryContentIdentifierStringImpl( std::unique_lock<std::mutex>& /*rGuard*/, sal_uInt32 nIndex )
+{
+ if ( nIndex < m_aResults.size() )
+ {
+ OUString aId = m_aResults[ nIndex ].aURL;
+ if ( !aId.isEmpty() )
+ {
+ // Already cached.
+ return aId;
+ }
+ }
+
+ if ( getResult( nIndex ) )
+ {
+ // Note: getResult fills m_pImpl->m_aResults[ nIndex ]->aURL.
+ return m_aResults[ nIndex ].aURL;
+ }
+ return OUString();
+}
+
+// virtual
+uno::Reference< ucb::XContentIdentifier >
+ResultSetDataSupplier::queryContentIdentifier( sal_uInt32 nIndex )
+{
+ std::unique_lock aGuard( m_aMutex );
+ return queryContentIdentifierImpl(aGuard, nIndex);
+}
+
+uno::Reference< ucb::XContentIdentifier >
+ResultSetDataSupplier::queryContentIdentifierImpl( std::unique_lock<std::mutex>& rGuard, sal_uInt32 nIndex )
+{
+ if ( nIndex < m_aResults.size() )
+ {
+ uno::Reference< ucb::XContentIdentifier > xId
+ = m_aResults[ nIndex ].xId;
+ if ( xId.is() )
+ {
+ // Already cached.
+ return xId;
+ }
+ }
+
+ OUString aId = queryContentIdentifierStringImpl( rGuard, nIndex );
+ if ( !aId.isEmpty() )
+ {
+ uno::Reference< ucb::XContentIdentifier > xId
+ = new ::ucbhelper::ContentIdentifier( aId );
+ m_aResults[ nIndex ].xId = xId;
+ return xId;
+ }
+ return uno::Reference< ucb::XContentIdentifier >();
+}
+
+// virtual
+uno::Reference< ucb::XContent >
+ResultSetDataSupplier::queryContent( sal_uInt32 nIndex )
+{
+ std::unique_lock aGuard( m_aMutex );
+
+ if ( nIndex < m_aResults.size() )
+ {
+ uno::Reference< ucb::XContent > xContent
+ = m_aResults[ nIndex ].xContent;
+ if ( xContent.is() )
+ {
+ // Already cached.
+ return xContent;
+ }
+ }
+
+ uno::Reference< ucb::XContentIdentifier > xId
+ = queryContentIdentifierImpl( aGuard, nIndex );
+ if ( xId.is() )
+ {
+ try
+ {
+ uno::Reference< ucb::XContent > xContent
+ = m_xContent->getProvider()->queryContent( xId );
+ m_aResults[ nIndex ].xContent = xContent;
+ return xContent;
+
+ }
+ catch ( ucb::IllegalIdentifierException const & )
+ {
+ }
+ }
+ return uno::Reference< ucb::XContent >();
+}
+
+// virtual
+bool ResultSetDataSupplier::getResult( sal_uInt32 nIndex )
+{
+ std::unique_lock aGuard( m_aMutex );
+ return getResultImpl(aGuard, nIndex);
+}
+
+bool ResultSetDataSupplier::getResultImpl( std::unique_lock<std::mutex>& rGuard, sal_uInt32 nIndex )
+{
+ if ( m_aResults.size() > nIndex )
+ {
+ // Result already present.
+ return true;
+ }
+
+ // Result not (yet) present.
+
+ if ( m_bCountFinal )
+ return false;
+
+ // Try to obtain result...
+
+ sal_uInt32 nOldCount = m_aResults.size();
+ bool bFound = false;
+
+ if ( queryNamesOfChildren(rGuard) )
+ {
+ for ( sal_uInt32 n = nOldCount;
+ n < sal::static_int_cast<sal_uInt32>(
+ m_xNamesOfChildren->getLength());
+ ++n )
+ {
+ const OUString & rName
+ = m_xNamesOfChildren->getConstArray()[ n ];
+
+ if ( rName.isEmpty() )
+ {
+ OSL_FAIL( "ResultDataSupplier::getResult - Empty name!" );
+ break;
+ }
+
+ // Assemble URL for child.
+ OUString aURL = assembleChildURL( rName );
+
+ m_aResults.emplace_back( aURL );
+
+ if ( n == nIndex )
+ {
+ // Result obtained.
+ bFound = true;
+ break;
+ }
+ }
+ }
+
+ if ( !bFound )
+ m_bCountFinal = true;
+
+ rtl::Reference< ::ucbhelper::ResultSet > xResultSet = getResultSet();
+ if ( xResultSet.is() )
+ {
+ // Callbacks follow!
+ rGuard.unlock();
+
+ if ( nOldCount < m_aResults.size() )
+ xResultSet->rowCountChanged( nOldCount, m_aResults.size() );
+
+ if ( m_bCountFinal )
+ xResultSet->rowCountFinal();
+
+ rGuard.lock();
+ }
+
+ return bFound;
+}
+
+// virtual
+sal_uInt32 ResultSetDataSupplier::totalCount()
+{
+ std::unique_lock aGuard( m_aMutex );
+
+ if ( m_bCountFinal )
+ return m_aResults.size();
+
+ sal_uInt32 nOldCount = m_aResults.size();
+
+ if ( queryNamesOfChildren(aGuard) )
+ {
+ for ( sal_uInt32 n = nOldCount;
+ n < sal::static_int_cast<sal_uInt32>(
+ m_xNamesOfChildren->getLength());
+ ++n )
+ {
+ const OUString & rName
+ = m_xNamesOfChildren->getConstArray()[ n ];
+
+ if ( rName.isEmpty() )
+ {
+ OSL_FAIL( "ResultDataSupplier::getResult - Empty name!" );
+ break;
+ }
+
+ // Assemble URL for child.
+ OUString aURL = assembleChildURL( rName );
+
+ m_aResults.emplace_back( aURL );
+ }
+ }
+
+ m_bCountFinal = true;
+
+ rtl::Reference< ::ucbhelper::ResultSet > xResultSet = getResultSet();
+ if ( xResultSet.is() )
+ {
+ // Callbacks follow!
+ aGuard.unlock();
+
+ if ( nOldCount < m_aResults.size() )
+ xResultSet->rowCountChanged( nOldCount, m_aResults.size() );
+
+ xResultSet->rowCountFinal();
+ }
+
+ return m_aResults.size();
+}
+
+// virtual
+sal_uInt32 ResultSetDataSupplier::currentCount()
+{
+ return m_aResults.size();
+}
+
+// virtual
+bool ResultSetDataSupplier::isCountFinal()
+{
+ return m_bCountFinal;
+}
+
+// virtual
+uno::Reference< sdbc::XRow >
+ResultSetDataSupplier::queryPropertyValues( sal_uInt32 nIndex )
+{
+ std::unique_lock aGuard( m_aMutex );
+
+ if ( nIndex < m_aResults.size() )
+ {
+ uno::Reference< sdbc::XRow > xRow = m_aResults[ nIndex ].xRow;
+ if ( xRow.is() )
+ {
+ // Already cached.
+ return xRow;
+ }
+ }
+
+ if ( getResultImpl( aGuard, nIndex ) )
+ {
+ uno::Reference< sdbc::XRow > xRow = Content::getPropertyValues(
+ m_xContext,
+ getResultSet()->getProperties(),
+ m_xContent->getContentProvider().get(),
+ queryContentIdentifierStringImpl( aGuard, nIndex ) );
+ m_aResults[ nIndex ].xRow = xRow;
+ return xRow;
+ }
+
+ return uno::Reference< sdbc::XRow >();
+}
+
+// virtual
+void ResultSetDataSupplier::releasePropertyValues( sal_uInt32 nIndex )
+{
+ std::unique_lock aGuard( m_aMutex );
+
+ if ( nIndex < m_aResults.size() )
+ m_aResults[ nIndex ].xRow.clear();
+}
+
+// virtual
+void ResultSetDataSupplier::close()
+{
+}
+
+// virtual
+void ResultSetDataSupplier::validate()
+{
+ if ( m_bThrowException )
+ throw ucb::ResultSetException();
+}
+
+bool ResultSetDataSupplier::queryNamesOfChildren(std::unique_lock<std::mutex>& /*rGuard*/)
+{
+ if ( !m_xNamesOfChildren )
+ {
+ uno::Sequence< OUString > aNamesOfChildren;
+
+ if ( !m_xContent->getContentProvider()->queryNamesOfChildren(
+ m_xContent->getIdentifier()->getContentIdentifier(),
+ aNamesOfChildren ) )
+ {
+ OSL_FAIL( "Got no list of children!" );
+ m_bThrowException = true;
+ return false;
+ }
+ else
+ {
+ m_xNamesOfChildren = std::move( aNamesOfChildren );
+ }
+ }
+ return true;
+}
+
+OUString
+ResultSetDataSupplier::assembleChildURL( std::u16string_view aName )
+{
+ OUString aURL
+ = m_xContent->getIdentifier()->getContentIdentifier();
+
+ sal_Int32 nUrlEnd = aURL.lastIndexOf( '/' );
+ if ( nUrlEnd != aURL.getLength() - 1 )
+ aURL += "/";
+
+ aURL += aName;
+ return aURL;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/ucb/source/ucp/tdoc/tdoc_datasupplier.hxx b/ucb/source/ucp/tdoc/tdoc_datasupplier.hxx
new file mode 100644
index 0000000000..802a6dbc0a
--- /dev/null
+++ b/ucb/source/ucp/tdoc/tdoc_datasupplier.hxx
@@ -0,0 +1,94 @@
+/* -*- 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:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#pragma once
+
+#include <rtl/ref.hxx>
+#include <ucbhelper/resultset.hxx>
+#include <mutex>
+#include <optional>
+#include <string_view>
+#include <utility>
+#include <vector>
+
+namespace tdoc_ucp {
+
+struct DataSupplier_Impl;
+class Content;
+
+class ResultSetDataSupplier final : public ::ucbhelper::ResultSetDataSupplier
+{
+ struct ResultListEntry
+ {
+ OUString aURL;
+ css::uno::Reference< css::ucb::XContentIdentifier > xId;
+ css::uno::Reference< css::ucb::XContent > xContent;
+ css::uno::Reference< css::sdbc::XRow > xRow;
+
+ explicit ResultListEntry( OUString _aURL ) : aURL(std::move( _aURL )) {}
+ };
+
+ std::mutex m_aMutex;
+ std::vector< ResultListEntry > m_aResults;
+ rtl::Reference< Content > m_xContent;
+ css::uno::Reference< css::uno::XComponentContext > m_xContext;
+ std::optional<css::uno::Sequence< OUString > > m_xNamesOfChildren;
+ bool m_bCountFinal;
+ bool m_bThrowException;
+
+private:
+ bool queryNamesOfChildren(std::unique_lock<std::mutex>& rGuard);
+ OUString assembleChildURL( std::u16string_view aName );
+
+public:
+ ResultSetDataSupplier(
+ css::uno::Reference< css::uno::XComponentContext > xContext,
+ rtl::Reference< Content > xContent );
+ virtual ~ResultSetDataSupplier() 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;
+
+private:
+ OUString queryContentIdentifierStringImpl( std::unique_lock<std::mutex>& rGuard, sal_uInt32 nIndex );
+ css::uno::Reference< css::ucb::XContentIdentifier >
+ queryContentIdentifierImpl( std::unique_lock<std::mutex>& rGuard, sal_uInt32 nIndex );
+ bool getResultImpl( std::unique_lock<std::mutex>& rGuard, sal_uInt32 nIndex );
+};
+
+} // namespace tdoc_ucp
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/ucb/source/ucp/tdoc/tdoc_docmgr.cxx b/ucb/source/ucp/tdoc/tdoc_docmgr.cxx
new file mode 100644
index 0000000000..39fa5bc828
--- /dev/null
+++ b/ucb/source/ucp/tdoc/tdoc_docmgr.cxx
@@ -0,0 +1,692 @@
+/* -*- 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:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+
+#include <rtl/ref.hxx>
+#include <comphelper/diagnose_ex.hxx>
+#include <sal/log.hxx>
+#include <tools/datetime.hxx>
+
+#include <comphelper/documentinfo.hxx>
+#include <comphelper/namedvaluecollection.hxx>
+#include <comphelper/sequence.hxx>
+
+#include <com/sun/star/awt/XTopWindow.hpp>
+#include <com/sun/star/beans/XPropertySet.hpp>
+#include <com/sun/star/document/XDocumentEventBroadcaster.hpp>
+#include <com/sun/star/document/XStorageBasedDocument.hpp>
+#include <com/sun/star/frame/UnknownModuleException.hpp>
+#include <com/sun/star/frame/theGlobalEventBroadcaster.hpp>
+#include <com/sun/star/frame/ModuleManager.hpp>
+#include <com/sun/star/lang/DisposedException.hpp>
+#include <com/sun/star/lang/NotInitializedException.hpp>
+#include <com/sun/star/util/XCloseBroadcaster.hpp>
+
+#include "tdoc_docmgr.hxx"
+#include "tdoc_provider.hxx"
+
+using namespace com::sun::star;
+using namespace tdoc_ucp;
+
+// OfficeDocumentsCloseListener Implementation.
+
+
+// util::XCloseListener
+
+
+// virtual
+void SAL_CALL OfficeDocumentsManager::OfficeDocumentsCloseListener::queryClosing(
+ const lang::EventObject& /*Source*/, sal_Bool /*GetsOwnership*/ )
+{
+}
+
+
+void SAL_CALL OfficeDocumentsManager::OfficeDocumentsCloseListener::notifyClosing(
+ const lang::EventObject& Source )
+{
+ if (!m_pManager) return; // disposed?
+
+ document::DocumentEvent aDocEvent;
+ aDocEvent.Source = Source.Source;
+ aDocEvent.EventName = "OfficeDocumentsListener::notifyClosing";
+ m_pManager->documentEventOccured( aDocEvent );
+}
+
+
+// lang::XDocumentEventListener (base of util::XCloseListener)
+
+
+// virtual
+void SAL_CALL OfficeDocumentsManager::OfficeDocumentsCloseListener::disposing(
+ const lang::EventObject& /*Source*/ )
+{
+}
+
+
+// OfficeDocumentsManager Implementation.
+
+
+OfficeDocumentsManager::OfficeDocumentsManager(
+ const uno::Reference< uno::XComponentContext > & rxContext,
+ ContentProvider * pDocEventListener )
+: m_xContext( rxContext ),
+ m_xDocEvtNotifier( frame::theGlobalEventBroadcaster::get( rxContext ) ),
+ m_pDocEventListener( pDocEventListener ),
+ m_xDocCloseListener( new OfficeDocumentsCloseListener( this ) )
+{
+ // Order is important (multithreaded environment)
+ uno::Reference< document::XDocumentEventBroadcaster >(
+ m_xDocEvtNotifier, uno::UNO_QUERY_THROW )->addDocumentEventListener( this );
+ buildDocumentsList();
+}
+
+
+// virtual
+OfficeDocumentsManager::~OfficeDocumentsManager()
+{
+ //OSL_ENSURE( m_aDocs.empty(), "document list not empty!" );
+ // no need to assert this: Normal shutdown of LibreOffice could already trigger it, since the order
+ // in which objects are actually released/destroyed upon shutdown is not defined. And when we
+ // arrive *here*, LibreOffice *is* shutting down currently, since we're held by the TDOC provider,
+ // which is disposed upon shutdown.
+ m_xDocCloseListener->Dispose();
+}
+
+
+void OfficeDocumentsManager::destroy()
+{
+ uno::Reference< document::XDocumentEventBroadcaster >(
+ m_xDocEvtNotifier, uno::UNO_QUERY_THROW )->removeDocumentEventListener( this );
+}
+
+
+static OUString
+getDocumentId( const uno::Reference< uno::XInterface > & xDoc )
+{
+ OUString aId;
+
+ // Try to get the UID directly from the document.
+ uno::Reference< beans::XPropertySet > xPropSet( xDoc, uno::UNO_QUERY );
+ if ( xPropSet.is() )
+ {
+ try
+ {
+ uno::Any aValue = xPropSet->getPropertyValue("RuntimeUID");
+ aValue >>= aId;
+ }
+ catch ( beans::UnknownPropertyException const & )
+ {
+ // Not actually an error. Property is optional.
+ }
+ catch ( lang::WrappedTargetException const & )
+ {
+ TOOLS_WARN_EXCEPTION("ucb.ucp", "Caught WrappedTargetException!");
+ }
+ }
+
+ if ( aId.isEmpty() )
+ {
+ // fallback: generate UID from document's this pointer.
+ // normalize the interface pointer first. Else, calls with different
+ // interfaces to the same object (say, XFoo and XBar) will produce
+ // different IDs
+ uno::Reference< uno::XInterface > xNormalizedIFace( xDoc, uno::UNO_QUERY );
+ sal_Int64 nId = reinterpret_cast< sal_Int64 >( xNormalizedIFace.get() );
+ aId = OUString::number( nId );
+ }
+
+ OSL_ENSURE( !aId.isEmpty(), "getDocumentId - Empty id!" );
+ return aId;
+}
+
+
+// document::XDocumentEventListener
+
+
+// virtual
+void SAL_CALL OfficeDocumentsManager::documentEventOccured(
+ const document::DocumentEvent & Event )
+{
+/*
+ Events documentation: OOo Developer's Guide / Writing UNO Components /
+ Integrating Components into OpenOffice.org / Jobs
+*/
+
+ if ( Event.EventName == "OnLoadFinished" // document loaded
+ || Event.EventName == "OnCreate" ) // document created
+ {
+ if ( isOfficeDocument( Event.Source ) )
+ {
+ uno::Reference<frame::XModel> const xModel(
+ Event.Source, uno::UNO_QUERY );
+ OSL_ENSURE( xModel.is(), "Got no frame::XModel!" );
+
+ bool found(false);
+
+ {
+ std::scoped_lock aGuard( m_aMtx );
+
+ found = std::any_of(m_aDocs.begin(), m_aDocs.end(),
+ [&xModel](const DocumentList::value_type& rEntry) { return rEntry.second.xModel == xModel; });
+ }
+
+ if (!found)
+ {
+ // no mutex to avoid deadlocks!
+ // need no lock to access const members, ContentProvider is safe
+
+ // new document
+
+ uno::Reference< document::XStorageBasedDocument >
+ xDoc( Event.Source, uno::UNO_QUERY );
+ OSL_ENSURE( xDoc.is(), "Got no document::XStorageBasedDocument!" );
+
+ uno::Reference< embed::XStorage > xStorage
+ = xDoc->getDocumentStorage();
+ OSL_ENSURE( xStorage.is(), "Got no document storage!" );
+
+ rtl:: OUString aDocId = getDocumentId( Event.Source );
+ rtl:: OUString aTitle = comphelper::DocumentInfo::getDocumentTitle(
+ uno::Reference< frame::XModel >( Event.Source, uno::UNO_QUERY ) );
+
+ {
+ std::scoped_lock g(m_aMtx);
+ m_aDocs[ aDocId ] = StorageInfo( aTitle, xStorage, xModel );
+ }
+
+ uno::Reference< util::XCloseBroadcaster > xCloseBroadcaster(
+ Event.Source, uno::UNO_QUERY );
+ OSL_ENSURE( xCloseBroadcaster.is(),
+ "OnLoadFinished/OnCreate event: got no close broadcaster!" );
+
+ if ( xCloseBroadcaster.is() )
+ xCloseBroadcaster->addCloseListener(m_xDocCloseListener);
+
+ // Propagate document closure.
+ OSL_ENSURE( m_pDocEventListener,
+ "OnLoadFinished/OnCreate event: no owner for insert event propagation!" );
+
+ if ( m_pDocEventListener )
+ m_pDocEventListener->notifyDocumentOpened( aDocId );
+ }
+ }
+ }
+ else if ( Event.EventName == "OfficeDocumentsListener::notifyClosing" )
+ {
+ if ( isOfficeDocument( Event.Source ) )
+ {
+ // Document has been closed (unloaded)
+
+ // Official event "OnUnload" does not work here. Event
+ // gets fired too early. Other OnUnload listeners called after this
+ // listener may still need TDOC access to the document. Remove the
+ // document from TDOC docs list on XCloseListener::notifyClosing.
+ // See OfficeDocumentsManager::OfficeDocumentsListener::notifyClosing.
+
+ uno::Reference< frame::XModel >
+ xModel( Event.Source, uno::UNO_QUERY );
+ OSL_ENSURE( xModel.is(), "Got no frame::XModel!" );
+
+ bool found(false);
+ OUString aDocId;
+
+ {
+ std::scoped_lock aGuard( m_aMtx );
+
+ auto it = std::find_if(m_aDocs.begin(), m_aDocs.end(),
+ [&xModel](const DocumentList::value_type& rEntry) { return rEntry.second.xModel == xModel; });
+ if ( it != m_aDocs.end() )
+ {
+ aDocId = (*it).first;
+ found = true;
+ m_aDocs.erase( it );
+ }
+ }
+
+ OSL_ENSURE( found,
+ "OnUnload event notified for unknown document!" );
+
+ if (found)
+ {
+ // Propagate document closure.
+ OSL_ENSURE( m_pDocEventListener,
+ "OnUnload event: no owner for close event propagation!" );
+ if (m_pDocEventListener)
+ {
+ m_pDocEventListener->notifyDocumentClosed(aDocId);
+ }
+ uno::Reference< util::XCloseBroadcaster > xCloseBroadcaster(
+ Event.Source, uno::UNO_QUERY );
+ OSL_ENSURE( xCloseBroadcaster.is(),
+ "OnUnload event: got no XCloseBroadcaster from XModel" );
+ if ( xCloseBroadcaster.is() )
+ xCloseBroadcaster->removeCloseListener(m_xDocCloseListener);
+ }
+ }
+ }
+ else if ( Event.EventName == "OnSaveDone" )
+ {
+ if ( isOfficeDocument( Event.Source ) )
+ {
+ // Storage gets exchanged while saving.
+ uno::Reference<document::XStorageBasedDocument> const xDoc(
+ Event.Source, uno::UNO_QUERY );
+ OSL_ENSURE( xDoc.is(),
+ "Got no document::XStorageBasedDocument!" );
+ uno::Reference<embed::XStorage> const xStorage(
+ xDoc->getDocumentStorage());
+ OSL_ENSURE( xStorage.is(), "Got no document storage!" );
+
+ uno::Reference< frame::XModel >
+ xModel( Event.Source, uno::UNO_QUERY );
+ OSL_ENSURE( xModel.is(), "Got no frame::XModel!" );
+
+ std::scoped_lock aGuard( m_aMtx );
+
+ DocumentList::iterator it = std::find_if(m_aDocs.begin(), m_aDocs.end(),
+ [&xModel](const DocumentList::value_type& rEntry) { return rEntry.second.xModel == xModel; });
+
+ OSL_ENSURE( it != m_aDocs.end(),
+ "OnSaveDone event notified for unknown document!" );
+ if ( it != m_aDocs.end() )
+ {
+ (*it).second.xStorage = xStorage;
+ }
+ }
+ }
+ else if ( Event.EventName == "OnSaveAsDone" )
+ {
+ if ( isOfficeDocument( Event.Source ) )
+ {
+ // Storage gets exchanged while saving.
+ uno::Reference<document::XStorageBasedDocument> const xDoc(
+ Event.Source, uno::UNO_QUERY );
+ OSL_ENSURE( xDoc.is(),
+ "Got no document::XStorageBasedDocument!" );
+ uno::Reference<embed::XStorage> const xStorage(
+ xDoc->getDocumentStorage());
+ OSL_ENSURE( xStorage.is(), "Got no document storage!" );
+
+ uno::Reference< frame::XModel >
+ xModel( Event.Source, uno::UNO_QUERY );
+ OSL_ENSURE( xModel.is(), "Got no frame::XModel!" );
+
+ OUString const title(comphelper::DocumentInfo::getDocumentTitle(xModel));
+
+ std::scoped_lock aGuard( m_aMtx );
+
+ DocumentList::iterator it = std::find_if(m_aDocs.begin(), m_aDocs.end(),
+ [&xModel](const DocumentList::value_type& rEntry) { return rEntry.second.xModel == xModel; });
+
+ OSL_ENSURE( it != m_aDocs.end(),
+ "OnSaveAsDone event notified for unknown document!" );
+ if ( it != m_aDocs.end() )
+ {
+ (*it).second.xStorage = xStorage;
+
+ // Adjust title.
+ (*it).second.aTitle = title;
+ }
+ }
+ }
+ else if ( Event.EventName == "OnTitleChanged"
+ || Event.EventName == "OnStorageChanged" )
+ {
+ if ( isOfficeDocument( Event.Source ) )
+ {
+ // Storage gets exchanged while saving.
+ uno::Reference<document::XStorageBasedDocument> const xDoc(
+ Event.Source, uno::UNO_QUERY );
+ OSL_ENSURE( xDoc.is(),
+ "Got no document::XStorageBasedDocument!" );
+ uno::Reference<embed::XStorage> const xStorage(
+ xDoc->getDocumentStorage());
+ OSL_ENSURE( xStorage.is(), "Got no document storage!" );
+
+ uno::Reference< frame::XModel >
+ xModel( Event.Source, uno::UNO_QUERY );
+ OSL_ENSURE( xModel.is(), "Got no frame::XModel!" );
+
+ OUString const aTitle(comphelper::DocumentInfo::getDocumentTitle(xModel));
+
+ OUString const aDocId(getDocumentId(Event.Source));
+
+ std::scoped_lock aGuard( m_aMtx );
+
+ DocumentList::iterator it = std::find_if(m_aDocs.begin(), m_aDocs.end(),
+ [&xModel](const DocumentList::value_type& rEntry) { return rEntry.second.xModel == xModel; });
+ if ( it != m_aDocs.end() )
+ {
+ // Adjust title.
+ (*it).second.aTitle = aTitle;
+
+ m_aDocs[ aDocId ] = StorageInfo( aTitle, xStorage, xModel );
+ }
+
+// OSL_ENSURE( it != m_aDocs.end(),
+// "TitleChanged event notified for unknown document!" );
+ // TODO: re-enable this assertion. It has been disabled for now, since it breaks the assertion-free smoketest,
+ // and the fix is more difficult than what can be done now.
+ // The problem is that at the moment, when you close a SFX-based document via API, it will first
+ // fire the notifyClosing event, which will make the OfficeDocumentsManager remove the doc from its list.
+ // Then, it will notify an OnTitleChanged, then an OnUnload. Documents closed via call the notifyClosing
+ // *after* OnUnload and all other On* events.
+ // In agreement with MBA, the implementation for SfxBaseModel::Close should be changed to also send notifyClosing
+ // as last event. When this happens, the assertion here must be enabled, again.
+ }
+ }
+}
+
+// lang::XDocumentEventListener (base of document::XDocumentEventListener)
+
+// virtual
+void SAL_CALL OfficeDocumentsManager::disposing(
+ const lang::EventObject& /*Source*/ )
+{
+}
+
+// Non-interface.
+
+void OfficeDocumentsManager::buildDocumentsList()
+{
+ uno::Reference< container::XEnumeration > xEnum
+ = m_xDocEvtNotifier->createEnumeration();
+
+ while ( xEnum->hasMoreElements() )
+ {
+ uno::Any aValue = xEnum->nextElement();
+ // container::NoSuchElementException
+ // lang::WrappedTargetException
+
+ try
+ {
+ uno::Reference< frame::XModel > xModel;
+ aValue >>= xModel;
+
+ if ( xModel.is() )
+ {
+ if ( isOfficeDocument( xModel ) )
+ {
+ bool found(false);
+
+ {
+ std::scoped_lock aGuard( m_aMtx );
+
+ found = std::any_of(m_aDocs.begin(), m_aDocs.end(),
+ [&xModel](const DocumentList::value_type& rEntry) { return rEntry.second.xModel == xModel; });
+ }
+
+ if (!found)
+ {
+ // new document
+ OUString aDocId = getDocumentId( xModel );
+ OUString aTitle = comphelper::DocumentInfo::getDocumentTitle( xModel );
+
+ uno::Reference< document::XStorageBasedDocument >
+ xDoc( xModel, uno::UNO_QUERY );
+ OSL_ENSURE( xDoc.is(),
+ "Got no document::XStorageBasedDocument!" );
+
+ uno::Reference< embed::XStorage > xStorage
+ = xDoc->getDocumentStorage();
+ OSL_ENSURE( xStorage.is(), "Got no document storage!" );
+
+ {
+ std::scoped_lock aGuard( m_aMtx );
+ m_aDocs[ aDocId ]
+ = StorageInfo( aTitle, xStorage, xModel );
+ }
+
+ uno::Reference< util::XCloseBroadcaster > xCloseBroadcaster(
+ xModel, uno::UNO_QUERY );
+ OSL_ENSURE( xCloseBroadcaster.is(),
+ "buildDocumentsList: got no close broadcaster!" );
+
+ if ( xCloseBroadcaster.is() )
+ xCloseBroadcaster->addCloseListener(m_xDocCloseListener);
+ }
+ }
+ }
+ }
+ catch ( lang::DisposedException const & )
+ {
+ // Note: Due to race conditions the XEnumeration can
+ // contain docs that have already been closed
+ }
+ catch ( lang::NotInitializedException const & )
+ {
+ // Note: Due to race conditions the XEnumeration can
+ // contain docs that are still uninitialized
+ }
+ }
+}
+
+uno::Reference< embed::XStorage >
+OfficeDocumentsManager::queryStorage( const OUString & rDocId )
+{
+ std::scoped_lock aGuard( m_aMtx );
+
+ DocumentList::const_iterator it = m_aDocs.find( rDocId );
+ if ( it == m_aDocs.end() )
+ return uno::Reference< embed::XStorage >();
+
+ return (*it).second.xStorage;
+}
+
+
+OUString OfficeDocumentsManager::queryDocumentId(
+ const uno::Reference< frame::XModel > & xModel )
+{
+ return getDocumentId( xModel );
+}
+
+
+uno::Reference< frame::XModel >
+OfficeDocumentsManager::queryDocumentModel( const OUString & rDocId )
+{
+ std::scoped_lock aGuard( m_aMtx );
+
+ DocumentList::const_iterator it = m_aDocs.find( rDocId );
+ if ( it == m_aDocs.end() )
+ return uno::Reference< frame::XModel >();
+
+ return (*it).second.xModel;
+}
+
+
+uno::Sequence< OUString > OfficeDocumentsManager::queryDocuments()
+{
+ std::scoped_lock aGuard( m_aMtx );
+
+ return comphelper::mapKeysToSequence( m_aDocs );
+}
+
+
+OUString
+OfficeDocumentsManager::queryStorageTitle( const OUString & rDocId )
+{
+ std::scoped_lock aGuard( m_aMtx );
+
+ DocumentList::const_iterator it = m_aDocs.find( rDocId );
+ if ( it == m_aDocs.end() )
+ return OUString();
+
+ return (*it).second.aTitle;
+}
+
+
+css::util::DateTime OfficeDocumentsManager::queryStreamDateModified(OUString const & uri) {
+ std::scoped_lock g(m_aMtx);
+ auto const i1 = m_aDocs.find(Uri(uri).getDocumentId());
+ if (i1 != m_aDocs.end()) {
+ auto const i2 = i1->second.streamDateModified.find(uri);
+ if (i2 != i1->second.streamDateModified.end()) {
+ return i2->second;
+ }
+ }
+ return {};
+}
+
+
+void OfficeDocumentsManager::updateStreamDateModified(OUString const & uri) {
+ std::scoped_lock g(m_aMtx);
+ auto const i = m_aDocs.find(Uri(uri).getDocumentId());
+ if (i == m_aDocs.end()) {
+ SAL_WARN("ucb.ucp.tdoc", "No document info for <" << uri << ">");
+ return;
+ }
+ i->second.streamDateModified[uri] = DateTime(DateTime::SYSTEM).GetUNODateTime();
+}
+
+
+bool OfficeDocumentsManager::isDocumentPreview(
+ const uno::Reference< frame::XModel3 > & xModel )
+{
+ if ( !xModel.is() )
+ return false;
+
+ uno::Sequence<beans::PropertyValue> props = xModel->getArgs2( { "Preview" } );
+ for (const auto & rProp : props)
+ if (rProp.Name == "Preview")
+ {
+ bool bIsPreview = false;
+ rProp.Value >>= bIsPreview;
+ return bIsPreview;
+ }
+ return false;
+}
+
+
+bool OfficeDocumentsManager::isHelpDocument(
+ const uno::Reference< frame::XModel > & xModel )
+{
+ if ( !xModel.is() )
+ return false;
+
+ OUString sURL( xModel->getURL() );
+ return sURL.match( "vnd.sun.star.help://" );
+}
+
+
+bool OfficeDocumentsManager::isWithoutOrInTopLevelFrame(
+ const uno::Reference< frame::XModel > & xModel )
+{
+ if ( !xModel.is() )
+ return false;
+
+ uno::Reference< frame::XController > xController
+ = xModel->getCurrentController();
+ if ( xController.is() )
+ {
+ uno::Reference< frame::XFrame > xFrame
+ = xController->getFrame();
+ if ( xFrame.is() )
+ {
+ // don't use XFrame::isTop here. This nowadays excludes
+ // "sub documents" such as forms embedded in database documents
+ uno::Reference< awt::XTopWindow > xFrameContainer(
+ xFrame->getContainerWindow(), uno::UNO_QUERY );
+ if ( !xFrameContainer.is() )
+ return false;
+ }
+ }
+
+ return true;
+}
+
+
+bool OfficeDocumentsManager::isBasicIDE(
+ const uno::Reference< frame::XModel > & xModel )
+{
+ if ( !m_xModuleMgr.is() )
+ {
+ std::scoped_lock aGuard( m_aMtx );
+ if ( !m_xModuleMgr.is() )
+ {
+ try
+ {
+ m_xModuleMgr = frame::ModuleManager::create( m_xContext );
+ }
+ catch ( uno::Exception const & )
+ {
+ // handled below.
+ }
+
+ OSL_ENSURE( m_xModuleMgr .is(),
+ "Could not instantiate ModuleManager service!" );
+ }
+ }
+
+ if ( m_xModuleMgr.is() )
+ {
+ OUString aModule;
+ try
+ {
+ aModule = m_xModuleMgr->identify( xModel );
+ }
+ catch ( lang::IllegalArgumentException const & )
+ {
+ TOOLS_WARN_EXCEPTION("ucb.ucp", "");
+ }
+ catch ( frame::UnknownModuleException const & )
+ {
+ TOOLS_WARN_EXCEPTION("ucb.ucp", "");
+ }
+
+ if ( !aModule.isEmpty() )
+ {
+ // Filter unwanted items, that are no real documents.
+ if ( aModule == "com.sun.star.script.BasicIDE" )
+ {
+ return true;
+ }
+ }
+ }
+
+ return false;
+}
+
+
+bool OfficeDocumentsManager::isOfficeDocument(
+ const uno::Reference< uno::XInterface > & xDoc )
+{
+ uno::Reference< frame::XModel > xModel( xDoc, uno::UNO_QUERY );
+ uno::Reference< document::XStorageBasedDocument >
+ xStorageBasedDoc( xModel, uno::UNO_QUERY );
+ if ( !xStorageBasedDoc.is() )
+ return false;
+ uno::Reference< frame::XModel3 > xModel3( xDoc, uno::UNO_QUERY );
+ assert(xModel3 && "anything implementing frame:XModel is expected to implement XModel3 as well");
+
+ if ( !isWithoutOrInTopLevelFrame( xModel ) )
+ return false;
+
+ if ( isDocumentPreview( xModel3 ) )
+ return false;
+
+ if ( isHelpDocument( xModel ) )
+ return false;
+
+ if ( isBasicIDE( xModel ) )
+ return false;
+
+ return true;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/ucb/source/ucp/tdoc/tdoc_docmgr.hxx b/ucb/source/ucp/tdoc/tdoc_docmgr.hxx
new file mode 100644
index 0000000000..7115d0fe8e
--- /dev/null
+++ b/ucb/source/ucp/tdoc/tdoc_docmgr.hxx
@@ -0,0 +1,159 @@
+/* -*- 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:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#pragma once
+
+#include <rtl/ref.hxx>
+
+#include <cppuhelper/implbase.hxx>
+
+#include <com/sun/star/document/XDocumentEventListener.hpp>
+#include <com/sun/star/embed/XStorage.hpp>
+#include <com/sun/star/frame/XModel3.hpp>
+#include <com/sun/star/frame/XModuleManager2.hpp>
+#include <com/sun/star/frame/XGlobalEventBroadcaster.hpp>
+#include <com/sun/star/uno/XComponentContext.hpp>
+#include <com/sun/star/util/DateTime.hpp>
+#include <com/sun/star/util/XCloseListener.hpp>
+
+#include <map>
+#include <mutex>
+#include <unordered_map>
+#include <utility>
+
+namespace tdoc_ucp {
+
+ class ContentProvider;
+
+ struct StorageInfo
+ {
+ OUString aTitle;
+ css::uno::Reference< css::embed::XStorage > xStorage;
+ css::uno::Reference< css::frame::XModel > xModel;
+ std::unordered_map<OUString, css::util::DateTime> streamDateModified;
+
+ StorageInfo() {}; // needed for STL map only.
+
+ StorageInfo(
+ OUString _aTitle,
+ css::uno::Reference< css::embed::XStorage > _xStorage,
+ css::uno::Reference< css::frame::XModel > _xModel )
+ : aTitle(std::move( _aTitle )), xStorage(std::move( _xStorage )), xModel(std::move( _xModel )) {}
+ };
+
+
+ typedef std::map< OUString, StorageInfo > DocumentList;
+
+
+ class OfficeDocumentsManager :
+ public cppu::WeakImplHelper< css::document::XDocumentEventListener >
+ {
+ class OfficeDocumentsCloseListener :
+ public cppu::WeakImplHelper< css::util::XCloseListener >
+
+ {
+ public:
+ explicit OfficeDocumentsCloseListener( OfficeDocumentsManager * pMgr )
+ : m_pManager( pMgr ) {}
+
+ // util::XCloseListener
+ virtual void SAL_CALL queryClosing(
+ const css::lang::EventObject& Source,
+ sal_Bool GetsOwnership ) override;
+
+ virtual void SAL_CALL notifyClosing(
+ const css::lang::EventObject& Source ) override;
+
+ // lang::XEventListener (base of util::XCloseListener)
+ virtual void SAL_CALL disposing(
+ const css::lang::EventObject & Source ) override;
+
+ void Dispose() { m_pManager = nullptr; }
+
+ private:
+ OfficeDocumentsManager * m_pManager;
+ };
+
+ public:
+ OfficeDocumentsManager(
+ const css::uno::Reference< css::uno::XComponentContext > & rxContext,
+ ContentProvider * pDocEventListener );
+ virtual ~OfficeDocumentsManager() override;
+
+ void destroy();
+
+ // document::XDocumentEventListener
+ virtual void SAL_CALL documentEventOccured(
+ const css::document::DocumentEvent & Event ) override;
+
+ // lang::XEventListener (base of document::XDocumentEventListener)
+ virtual void SAL_CALL disposing(
+ const css::lang::EventObject & Source ) override;
+
+ // Non-interface
+ css::uno::Reference< css::embed::XStorage >
+ queryStorage( const OUString & rDocId );
+
+ static OUString queryDocumentId(
+ const css::uno::Reference< css::frame::XModel > & xModel );
+
+ css::uno::Reference< css::frame::XModel >
+ queryDocumentModel( const OUString & rDocId );
+
+ css::uno::Sequence< OUString >
+ queryDocuments();
+
+ OUString
+ queryStorageTitle( const OUString & rDocId );
+
+ css::util::DateTime queryStreamDateModified(OUString const & uri);
+
+ void updateStreamDateModified(OUString const & uri);
+
+ private:
+ void buildDocumentsList();
+
+ bool isOfficeDocument(
+ const css::uno::Reference< css::uno::XInterface > & xDoc );
+
+ static bool isDocumentPreview(
+ const css::uno::Reference< css::frame::XModel3 > & xModel );
+
+ static bool isWithoutOrInTopLevelFrame(
+ const css::uno::Reference< css::frame::XModel > & xModel );
+
+ bool
+ isBasicIDE(
+ const css::uno::Reference< css::frame::XModel > & xModel );
+
+ static bool isHelpDocument(
+ const css::uno::Reference< css::frame::XModel > & xModel );
+
+ std::mutex m_aMtx;
+ css::uno::Reference< css::uno::XComponentContext > m_xContext;
+ css::uno::Reference< css::frame::XGlobalEventBroadcaster > m_xDocEvtNotifier;
+ css::uno::Reference< css::frame::XModuleManager2 > m_xModuleMgr;
+ DocumentList m_aDocs;
+ ContentProvider * const m_pDocEventListener;
+ ::rtl::Reference<OfficeDocumentsCloseListener> const m_xDocCloseListener;
+ };
+
+} // namespace tdoc_ucp
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/ucb/source/ucp/tdoc/tdoc_documentcontentfactory.cxx b/ucb/source/ucp/tdoc/tdoc_documentcontentfactory.cxx
new file mode 100644
index 0000000000..5b45df4bb9
--- /dev/null
+++ b/ucb/source/ucp/tdoc/tdoc_documentcontentfactory.cxx
@@ -0,0 +1,115 @@
+/* -*- 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:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+
+/**************************************************************************
+ TODO
+ **************************************************************************
+
+ *************************************************************************/
+
+#include <cppuhelper/supportsservice.hxx>
+#include <cppuhelper/weak.hxx>
+#include <utility>
+
+#include "tdoc_documentcontentfactory.hxx"
+
+using namespace com::sun::star;
+using namespace tdoc_ucp;
+
+
+// DocumentContentFactory Implementation.
+
+
+DocumentContentFactory::DocumentContentFactory(
+ uno::Reference< uno::XComponentContext > xContext )
+: m_xContext(std::move( xContext ))
+{
+}
+
+
+// virtual
+DocumentContentFactory::~DocumentContentFactory()
+{
+}
+
+
+// XServiceInfo methods.
+
+
+// virtual
+OUString SAL_CALL DocumentContentFactory::getImplementationName()
+{
+ return "com.sun.star.comp.ucb.TransientDocumentsDocumentContentFactory";
+}
+
+// virtual
+sal_Bool SAL_CALL
+DocumentContentFactory::supportsService( const OUString& ServiceName )
+{
+ return cppu::supportsService(this, ServiceName);
+}
+
+// virtual
+uno::Sequence< OUString > SAL_CALL
+DocumentContentFactory::getSupportedServiceNames()
+{
+ return { "com.sun.star.frame.TransientDocumentsDocumentContentFactory" };
+}
+
+
+// XTransientDocumentsDocumentContentFactory methods.
+
+
+// virtual
+uno::Reference< ucb::XContent > SAL_CALL
+DocumentContentFactory::createDocumentContent(
+ const uno::Reference< frame::XModel >& Model )
+{
+ uno::Reference< frame::XTransientDocumentsDocumentContentFactory > xDocFac;
+ try
+ {
+ xDocFac.set( m_xContext->getServiceManager()->createInstanceWithContext("com.sun.star.ucb.TransientDocumentsContentProvider", m_xContext),
+ uno::UNO_QUERY );
+ }
+ catch ( uno::Exception const & )
+ {
+ // handled below.
+ }
+
+ if ( xDocFac.is() )
+ return xDocFac->createDocumentContent( Model );
+
+ throw uno::RuntimeException(
+ "Unable to obtain document content factory!",
+ getXWeak() );
+}
+
+
+// Service factory implementation.
+
+extern "C" SAL_DLLPUBLIC_EXPORT css::uno::XInterface*
+ucb_tdoc_DocumentContentFactory_get_implementation(
+ css::uno::XComponentContext* context, css::uno::Sequence<css::uno::Any> const&)
+{
+ return cppu::acquire(new DocumentContentFactory(context));
+}
+
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/ucb/source/ucp/tdoc/tdoc_documentcontentfactory.hxx b/ucb/source/ucp/tdoc/tdoc_documentcontentfactory.hxx
new file mode 100644
index 0000000000..3f6c9d0157
--- /dev/null
+++ b/ucb/source/ucp/tdoc/tdoc_documentcontentfactory.hxx
@@ -0,0 +1,58 @@
+/* -*- 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:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#pragma once
+
+#include <com/sun/star/lang/XServiceInfo.hpp>
+#include <com/sun/star/uno/XComponentContext.hpp>
+#include <com/sun/star/frame/XTransientDocumentsDocumentContentFactory.hpp>
+
+#include <cppuhelper/implbase.hxx>
+
+namespace tdoc_ucp {
+
+class DocumentContentFactory :
+ public cppu::WeakImplHelper<
+ css::frame::XTransientDocumentsDocumentContentFactory,
+ css::lang::XServiceInfo >
+{
+public:
+ explicit DocumentContentFactory( css::uno::Reference< css::uno::XComponentContext > );
+ virtual ~DocumentContentFactory() 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;
+
+ // XTransientDocumentsDocumentContentFactory
+ virtual css::uno::Reference< css::ucb::XContent > SAL_CALL
+ createDocumentContent( const css::uno::Reference< css::frame::XModel >& Model ) override;
+
+private:
+ css::uno::Reference< css::uno::XComponentContext > m_xContext;
+};
+
+} // namespace tdoc_ucp
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/ucb/source/ucp/tdoc/tdoc_passwordrequest.cxx b/ucb/source/ucp/tdoc/tdoc_passwordrequest.cxx
new file mode 100644
index 0000000000..412b39fe8a
--- /dev/null
+++ b/ucb/source/ucp/tdoc/tdoc_passwordrequest.cxx
@@ -0,0 +1,191 @@
+/* -*- 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:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+
+#include <com/sun/star/lang/XTypeProvider.hpp>
+#include <com/sun/star/task/DocumentPasswordRequest.hpp>
+#include <com/sun/star/task/XInteractionPassword.hpp>
+
+#include <cppuhelper/queryinterface.hxx>
+#include <cppuhelper/typeprovider.hxx>
+#include <ucbhelper/interactionrequest.hxx>
+
+#include "tdoc_passwordrequest.hxx"
+
+#include <mutex>
+
+using namespace com::sun::star;
+using namespace tdoc_ucp;
+
+namespace tdoc_ucp
+{
+ namespace {
+
+ class InteractionSupplyPassword :
+ public ucbhelper::InteractionContinuation,
+ public lang::XTypeProvider,
+ public task::XInteractionPassword
+ {
+ public:
+ explicit InteractionSupplyPassword( ucbhelper::InteractionRequest * pRequest )
+ : InteractionContinuation( pRequest ) {}
+
+ // XInterface
+ virtual uno::Any SAL_CALL queryInterface( const uno::Type & rType ) override;
+ virtual void SAL_CALL acquire()
+ noexcept override;
+ virtual void SAL_CALL release()
+ noexcept override;
+
+ // XTypeProvider
+ virtual uno::Sequence< uno::Type > SAL_CALL getTypes() override;
+ virtual uno::Sequence< sal_Int8 > SAL_CALL getImplementationId() override;
+
+ // XInteractionContinuation
+ virtual void SAL_CALL select() override;
+
+ // XInteractionPassword
+ virtual void SAL_CALL setPassword( const OUString & aPasswd ) override;
+ virtual OUString SAL_CALL getPassword() override;
+
+ private:
+ std::mutex m_aMutex;
+ OUString m_aPassword;
+ };
+
+ }
+} // namespace tdoc_ucp
+
+
+// InteractionSupplyPassword Implementation.
+
+
+// XInterface methods.
+
+
+// virtual
+void SAL_CALL InteractionSupplyPassword::acquire()
+ noexcept
+{
+ OWeakObject::acquire();
+}
+
+
+// virtual
+void SAL_CALL InteractionSupplyPassword::release()
+ noexcept
+{
+ OWeakObject::release();
+}
+
+
+// virtual
+uno::Any SAL_CALL
+InteractionSupplyPassword::queryInterface( const uno::Type & rType )
+{
+ uno::Any aRet = cppu::queryInterface( rType,
+ static_cast< lang::XTypeProvider * >( this ),
+ static_cast< task::XInteractionContinuation * >( this ),
+ static_cast< task::XInteractionPassword * >( this ) );
+
+ return aRet.hasValue()
+ ? aRet : InteractionContinuation::queryInterface( rType );
+}
+
+
+// XTypeProvider methods.
+
+
+// virtual
+uno::Sequence< sal_Int8 > SAL_CALL
+InteractionSupplyPassword::getImplementationId()
+{
+ return css::uno::Sequence<sal_Int8>();
+}
+
+
+// virtual
+uno::Sequence< uno::Type > SAL_CALL InteractionSupplyPassword::getTypes()
+{
+ static cppu::OTypeCollection s_aCollection(
+ cppu::UnoType<lang::XTypeProvider>::get(),
+ cppu::UnoType<task::XInteractionPassword>::get() );
+
+ return s_aCollection.getTypes();
+}
+
+
+// XInteractionContinuation methods.
+
+
+// virtual
+void SAL_CALL InteractionSupplyPassword::select()
+{
+ recordSelection();
+}
+
+
+// XInteractionPassword methods.
+
+
+// virtual
+void SAL_CALL
+InteractionSupplyPassword::setPassword( const OUString& aPasswd )
+{
+ std::scoped_lock aGuard( m_aMutex );
+ m_aPassword = aPasswd;
+}
+
+// virtual
+OUString SAL_CALL InteractionSupplyPassword::getPassword()
+{
+ std::scoped_lock aGuard( m_aMutex );
+ return m_aPassword;
+}
+
+
+// DocumentPasswordRequest Implementation.
+
+
+DocumentPasswordRequest::DocumentPasswordRequest(
+ task::PasswordRequestMode eMode,
+ const OUString & rDocumentName )
+{
+ // Fill request...
+ task::DocumentPasswordRequest aRequest;
+// aRequest.Message = // OUString
+// aRequest.Context = // XInterface
+ aRequest.Classification = task::InteractionClassification_ERROR;
+ aRequest.Mode = eMode;
+ aRequest.Name = rDocumentName;
+
+ setRequest( uno::Any( aRequest ) );
+
+ // Fill continuations...
+ uno::Sequence<
+ uno::Reference< task::XInteractionContinuation > > aContinuations{
+ new ucbhelper::InteractionAbort( this ),
+ new ucbhelper::InteractionRetry( this ),
+ new InteractionSupplyPassword( this )
+ };
+
+ setContinuations( aContinuations );
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/ucb/source/ucp/tdoc/tdoc_passwordrequest.hxx b/ucb/source/ucp/tdoc/tdoc_passwordrequest.hxx
new file mode 100644
index 0000000000..d246a5ea4d
--- /dev/null
+++ b/ucb/source/ucp/tdoc/tdoc_passwordrequest.hxx
@@ -0,0 +1,87 @@
+/* -*- 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:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#pragma once
+
+#include <com/sun/star/task/PasswordRequestMode.hpp>
+
+#include <ucbhelper/interactionrequest.hxx>
+
+namespace tdoc_ucp
+{
+/*
+ @usage:
+
+ uno::Reference< ucb::XCommandEnvironment > Environment = ...;
+
+ if ( Environment.is() )
+ {
+ uno::Reference< task::XInteractionHandler > xIH
+ = Environment->getInteractionHandler();
+ if ( xIH.is() )
+ {
+ rtl::Reference< DocumentPasswordRequest > xRequest
+ = new DocumentPasswordRequest(
+ task::PasswordRequestMode_PASSWORD_ENTER,
+ m_xIdentifier->getContentIdentifier() );
+ xIH->handle( xRequest.get() );
+
+ 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() )
+ {
+ // @@@
+ }
+
+ uno::Reference< task::XInteractionRetry > xRetry(
+ xSelection.get(), uno::UNO_QUERY );
+ if ( xRetry.is() )
+ {
+ // @@@
+ }
+
+ uno::Reference< task::XInteractionPassword > xPassword(
+ xSelection.get(), uno::UNO_QUERY );
+ if ( xPassword.is() )
+ {
+ OUString aPassword = xPassword->getPassword();
+
+ // @@@
+ }
+ }
+ }
+ }
+
+ */
+
+class DocumentPasswordRequest : public ucbhelper::InteractionRequest
+{
+public:
+ DocumentPasswordRequest(css::task::PasswordRequestMode eMode, const OUString& rDocumentName);
+};
+
+} // namespace tdoc_ucp
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/ucb/source/ucp/tdoc/tdoc_provider.cxx b/ucb/source/ucp/tdoc/tdoc_provider.cxx
new file mode 100644
index 0000000000..f9aaa13954
--- /dev/null
+++ b/ucb/source/ucp/tdoc/tdoc_provider.cxx
@@ -0,0 +1,574 @@
+/* -*- 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:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+
+/**************************************************************************
+ TODO
+ **************************************************************************
+
+ *************************************************************************/
+
+#include <comphelper/diagnose_ex.hxx>
+
+#include <com/sun/star/embed/InvalidStorageException.hpp>
+#include <com/sun/star/embed/StorageWrappedTargetException.hpp>
+#include <com/sun/star/io/IOException.hpp>
+#include <com/sun/star/ucb/IllegalIdentifierException.hpp>
+#include <cppuhelper/queryinterface.hxx>
+#include <ucbhelper/contentidentifier.hxx>
+#include <ucbhelper/macros.hxx>
+
+#include "tdoc_provider.hxx"
+#include "tdoc_content.hxx"
+#include "tdoc_uri.hxx"
+#include "tdoc_docmgr.hxx"
+#include "tdoc_storage.hxx"
+
+using namespace com::sun::star;
+using namespace tdoc_ucp;
+
+
+// ContentProvider Implementation.
+
+
+ContentProvider::ContentProvider(
+ const uno::Reference< uno::XComponentContext >& rxContext )
+: ContentProvider_Base( rxContext ),
+ m_xDocsMgr( new OfficeDocumentsManager( rxContext, this ) ),
+ m_xStgElemFac( new StorageElementFactory( rxContext, m_xDocsMgr ) )
+{
+}
+
+
+// virtual
+ContentProvider::~ContentProvider()
+{
+ if ( m_xDocsMgr.is() )
+ m_xDocsMgr->destroy();
+}
+
+
+// XServiceInfo methods.
+OUString SAL_CALL ContentProvider::getImplementationName()
+{
+ return "com.sun.star.comp.ucb.TransientDocumentsContentProvider";
+}
+
+sal_Bool SAL_CALL ContentProvider::supportsService( const OUString& ServiceName )
+{
+ return cppu::supportsService( this, ServiceName );
+}
+
+css::uno::Sequence< OUString > SAL_CALL ContentProvider::getSupportedServiceNames()
+{
+ return { "com.sun.star.ucb.TransientDocumentsContentProvider" };
+}
+
+
+// Service factory implementation.
+
+
+extern "C" SAL_DLLPUBLIC_EXPORT css::uno::XInterface*
+ucb_tdoc_ContentProvider_get_implementation(
+ css::uno::XComponentContext* context, css::uno::Sequence<css::uno::Any> const&)
+{
+ return cppu::acquire(new ContentProvider(context));
+}
+
+// XContentProvider methods.
+
+
+// virtual
+uno::Reference< ucb::XContent > SAL_CALL
+ContentProvider::queryContent(
+ const uno::Reference< ucb::XContentIdentifier >& Identifier )
+{
+ Uri aUri( Identifier->getContentIdentifier() );
+ if ( !aUri.isValid() )
+ throw ucb::IllegalIdentifierException(
+ "Invalid URL!",
+ Identifier );
+
+ // Normalize URI.
+ uno::Reference< ucb::XContentIdentifier > xCanonicId
+ = new ::ucbhelper::ContentIdentifier( aUri.getUri() );
+
+ osl::MutexGuard aGuard( m_aMutex );
+
+ // Check, if a content with given id already exists...
+ uno::Reference< ucb::XContent > xContent
+ = queryExistingContent( xCanonicId );
+
+ if ( !xContent.is() )
+ {
+ // Create a new content.
+ xContent = Content::create( m_xContext, this, xCanonicId );
+ registerNewContent( xContent );
+ }
+
+ return xContent;
+}
+
+
+// XTransientDocumentsDocumentContentIdentifierFactory methods.
+
+uno::Reference<ucb::XContentIdentifier> SAL_CALL
+ContentProvider::createDocumentContentIdentifier(
+ uno::Reference<frame::XModel> const& xModel)
+{
+ // model -> id -> content identifier -> queryContent
+ if ( !m_xDocsMgr.is() )
+ {
+ throw lang::IllegalArgumentException(
+ "No Document Manager!",
+ getXWeak(),
+ 1 );
+ }
+
+ OUString aDocId = tdoc_ucp::OfficeDocumentsManager::queryDocumentId(xModel);
+ if ( aDocId.isEmpty() )
+ {
+ throw lang::IllegalArgumentException(
+ "Unable to obtain document id from model!",
+ getXWeak(),
+ 1 );
+ }
+
+ OUString aBuffer = TDOC_URL_SCHEME ":/" + aDocId;
+
+ uno::Reference< ucb::XContentIdentifier > xId
+ = new ::ucbhelper::ContentIdentifier( aBuffer );
+ return xId;
+}
+
+// XTransientDocumentsDocumentContentFactory methods.
+
+uno::Reference< ucb::XContent > SAL_CALL
+ContentProvider::createDocumentContent(
+ uno::Reference<frame::XModel> const& xModel)
+{
+ uno::Reference<ucb::XContentIdentifier> const xId(
+ createDocumentContentIdentifier(xModel));
+
+ osl::MutexGuard aGuard( m_aMutex );
+
+ // Check, if a content with given id already exists...
+ uno::Reference< ucb::XContent > xContent
+ = queryExistingContent( xId );
+
+ if ( !xContent.is() )
+ {
+ // Create a new content.
+ xContent = Content::create( m_xContext, this, xId );
+ }
+
+ if ( xContent.is() )
+ return xContent;
+
+ // no content.
+ throw lang::IllegalArgumentException(
+ "Illegal Content Identifier!",
+ getXWeak(),
+ 1 );
+}
+
+
+// interface OfficeDocumentsEventListener
+
+
+// virtual
+void ContentProvider::notifyDocumentClosed( std::u16string_view rDocId )
+{
+ osl::MutexGuard aGuard( getContentListMutex() );
+
+ ::ucbhelper::ContentRefList aAllContents;
+ queryExistingContents( aAllContents );
+
+ // Notify all content objects related to the closed doc.
+
+ bool bFoundDocumentContent = false;
+ rtl::Reference< Content > xRoot;
+
+ for ( const auto& rContent : aAllContents )
+ {
+ Uri aUri( rContent->getIdentifier()->getContentIdentifier() );
+ OSL_ENSURE( aUri.isValid(),
+ "ContentProvider::notifyDocumentClosed - Invalid URI!" );
+
+ if ( !bFoundDocumentContent )
+ {
+ if ( aUri.isRoot() )
+ {
+ xRoot = static_cast< Content * >( rContent.get() );
+ }
+ else if ( aUri.isDocument() )
+ {
+ if ( aUri.getDocumentId() == rDocId )
+ {
+ bFoundDocumentContent = true;
+
+ // document content will notify removal of child itself;
+ // no need for the root to propagate this.
+ xRoot.clear();
+ }
+ }
+ }
+
+ if ( aUri.getDocumentId() == rDocId )
+ {
+ // Inform content.
+ rtl::Reference< Content > xContent
+ = static_cast< Content * >( rContent.get() );
+
+ xContent->notifyDocumentClosed();
+ }
+ }
+
+ if ( xRoot.is() )
+ {
+ // No document content found for rDocId but root content
+ // instantiated. Root content must announce document removal
+ // to content event listeners.
+ xRoot->notifyChildRemoved( rDocId );
+ }
+}
+
+
+// virtual
+void ContentProvider::notifyDocumentOpened( std::u16string_view rDocId )
+{
+ osl::MutexGuard aGuard( getContentListMutex() );
+
+ ::ucbhelper::ContentRefList aAllContents;
+ queryExistingContents( aAllContents );
+
+ // Find root content. If instantiated let it propagate document insertion.
+
+ for ( const auto& rContent : aAllContents )
+ {
+ Uri aUri( rContent->getIdentifier()->getContentIdentifier() );
+ OSL_ENSURE( aUri.isValid(),
+ "ContentProvider::notifyDocumentOpened - Invalid URI!" );
+
+ if ( aUri.isRoot() )
+ {
+ rtl::Reference< Content > xRoot
+ = static_cast< Content * >( rContent.get() );
+ xRoot->notifyChildInserted( rDocId );
+
+ // Done.
+ break;
+ }
+ }
+}
+
+
+// Non-UNO
+
+
+uno::Reference< embed::XStorage >
+ContentProvider::queryStorage( const OUString & rUri,
+ StorageAccessMode eMode ) const
+{
+ if ( m_xStgElemFac.is() )
+ {
+ try
+ {
+ return m_xStgElemFac->createStorage( rUri, eMode );
+ }
+ catch ( embed::InvalidStorageException const & )
+ {
+ TOOLS_WARN_EXCEPTION("ucb.ucp", "");
+ }
+ catch ( lang::IllegalArgumentException const & )
+ {
+ TOOLS_WARN_EXCEPTION("ucb.ucp", "");
+ }
+ catch ( io::IOException const & )
+ {
+ // Okay to happen, for instance when the storage does not exist.
+ //OSL_ENSURE( false, "Caught IOException!" );
+ }
+ catch ( embed::StorageWrappedTargetException const & )
+ {
+ TOOLS_WARN_EXCEPTION("ucb.ucp", "");
+ }
+ }
+ return uno::Reference< embed::XStorage >();
+}
+
+
+uno::Reference< embed::XStorage >
+ContentProvider::queryStorageClone( const OUString & rUri ) const
+{
+ if ( m_xStgElemFac.is() )
+ {
+ try
+ {
+ Uri aUri( rUri );
+ uno::Reference< embed::XStorage > xParentStorage
+ = m_xStgElemFac->createStorage( aUri.getParentUri(), READ );
+ uno::Reference< embed::XStorage > xStorage
+ = m_xStgElemFac->createTemporaryStorage();
+
+ xParentStorage->copyStorageElementLastCommitTo(
+ aUri.getDecodedName(), xStorage );
+ return xStorage;
+ }
+ catch ( embed::InvalidStorageException const & )
+ {
+ TOOLS_WARN_EXCEPTION("ucb.ucp", "");
+ }
+ catch ( lang::IllegalArgumentException const & )
+ {
+ TOOLS_WARN_EXCEPTION("ucb.ucp", "");
+ }
+ catch ( io::IOException const & )
+ {
+ // Okay to happen, for instance when the storage does not exist.
+ //OSL_ENSURE( false, "Caught IOException!" );
+ }
+ catch ( embed::StorageWrappedTargetException const & )
+ {
+ TOOLS_WARN_EXCEPTION("ucb.ucp", "");
+ }
+ }
+
+ return uno::Reference< embed::XStorage >();
+}
+
+
+uno::Reference< io::XInputStream >
+ContentProvider::queryInputStream( const OUString & rUri,
+ const OUString & rPassword ) const
+{
+ if ( m_xStgElemFac.is() )
+ {
+ try
+ {
+ return m_xStgElemFac->createInputStream( rUri, rPassword );
+ }
+ catch ( embed::InvalidStorageException const & )
+ {
+ TOOLS_WARN_EXCEPTION("ucb.ucp", "");
+ }
+ catch ( lang::IllegalArgumentException const & )
+ {
+ TOOLS_WARN_EXCEPTION("ucb.ucp", "");
+ }
+ catch ( io::IOException const & )
+ {
+ TOOLS_WARN_EXCEPTION("ucb.ucp", "");
+ }
+ catch ( embed::StorageWrappedTargetException const & )
+ {
+ TOOLS_WARN_EXCEPTION("ucb.ucp", "");
+ }
+// catch ( packages::WrongPasswordException const & )
+// {
+// // the key provided is wrong; rethrow; to be handled by caller.
+// throw;
+// }
+ }
+ return uno::Reference< io::XInputStream >();
+}
+
+
+uno::Reference< io::XOutputStream >
+ContentProvider::queryOutputStream( const OUString & rUri,
+ const OUString & rPassword,
+ bool bTruncate ) const
+{
+ if ( m_xStgElemFac.is() )
+ {
+ try
+ {
+ return
+ m_xStgElemFac->createOutputStream( rUri, rPassword, bTruncate );
+ }
+ catch ( embed::InvalidStorageException const & )
+ {
+ TOOLS_WARN_EXCEPTION("ucb.ucp", "");
+ }
+ catch ( lang::IllegalArgumentException const & )
+ {
+ TOOLS_WARN_EXCEPTION("ucb.ucp", "");
+ }
+ catch ( io::IOException const & )
+ {
+ // Okay to happen, for instance when the storage does not exist.
+ //OSL_ENSURE( false, "Caught IOException!" );
+ }
+ catch ( embed::StorageWrappedTargetException const & )
+ {
+ TOOLS_WARN_EXCEPTION("ucb.ucp", "");
+ }
+// catch ( packages::WrongPasswordException const & )
+// {
+// // the key provided is wrong; rethrow; to be handled by caller.
+// throw;
+// }
+ }
+ return uno::Reference< io::XOutputStream >();
+}
+
+
+uno::Reference< io::XStream >
+ContentProvider::queryStream( const OUString & rUri,
+ const OUString & rPassword,
+ bool bTruncate ) const
+{
+ if ( m_xStgElemFac.is() )
+ {
+ try
+ {
+ return m_xStgElemFac->createStream( rUri, rPassword, bTruncate );
+ }
+ catch ( embed::InvalidStorageException const & )
+ {
+ TOOLS_WARN_EXCEPTION("ucb.ucp", "");
+ }
+ catch ( lang::IllegalArgumentException const & )
+ {
+ TOOLS_WARN_EXCEPTION("ucb.ucp", "");
+ }
+ catch ( io::IOException const & )
+ {
+ // Okay to happen, for instance when the storage does not exist.
+ //OSL_ENSURE( false, "Caught IOException!" );
+ }
+ catch ( embed::StorageWrappedTargetException const & )
+ {
+ TOOLS_WARN_EXCEPTION("ucb.ucp", "");
+ }
+// catch ( packages::WrongPasswordException const & )
+// {
+// // the key provided is wrong; rethrow; to be handled by caller.
+// throw;
+// }
+ }
+ return uno::Reference< io::XStream >();
+}
+
+
+bool ContentProvider::queryNamesOfChildren(
+ const OUString & rUri, uno::Sequence< OUString > & rNames ) const
+{
+ Uri aUri( rUri );
+ if ( aUri.isRoot() )
+ {
+ // special handling for root, which has no storage, but children.
+ if ( m_xDocsMgr.is() )
+ {
+ rNames = m_xDocsMgr->queryDocuments();
+ return true;
+ }
+ }
+ else
+ {
+ if ( m_xStgElemFac.is() )
+ {
+ try
+ {
+ uno::Reference< embed::XStorage > xStorage
+ = m_xStgElemFac->createStorage( rUri, READ );
+
+ OSL_ENSURE( xStorage.is(), "Got no Storage!" );
+
+ if ( xStorage.is() )
+ {
+ rNames = xStorage->getElementNames();
+ return true;
+ }
+ }
+ catch ( embed::InvalidStorageException const & )
+ {
+ TOOLS_WARN_EXCEPTION("ucb.ucp", "");
+ }
+ catch ( lang::IllegalArgumentException const & )
+ {
+ TOOLS_WARN_EXCEPTION("ucb.ucp", "");
+ }
+ catch ( io::IOException const & )
+ {
+ // Okay to happen, for instance if the storage does not exist.
+ //OSL_ENSURE( false, "Caught IOException!" );
+ }
+ catch ( embed::StorageWrappedTargetException const & )
+ {
+ TOOLS_WARN_EXCEPTION("ucb.ucp", "");
+ }
+ }
+ }
+ return false;
+}
+
+
+OUString
+ContentProvider::queryStorageTitle( const OUString & rUri ) const
+{
+ OUString aTitle;
+
+ Uri aUri( rUri );
+ if ( aUri.isRoot() )
+ {
+ // always empty.
+ aTitle.clear();
+ }
+ else if ( aUri.isDocument() )
+ {
+ // for documents, title shall not be derived from URL. It shall
+ // be something more 'speaking' than just the document UID.
+ if ( m_xDocsMgr.is() )
+ aTitle = m_xDocsMgr->queryStorageTitle( aUri.getDocumentId() );
+ }
+ else
+ {
+ // derive title from URL
+ aTitle = aUri.getDecodedName();
+ }
+
+ OSL_ENSURE( !aTitle.isEmpty() || aUri.isRoot(),
+ "ContentProvider::queryStorageTitle - empty title!" );
+ return aTitle;
+}
+
+
+uno::Reference< frame::XModel >
+ContentProvider::queryDocumentModel( const OUString & rUri ) const
+{
+ uno::Reference< frame::XModel > xModel;
+
+ if ( m_xDocsMgr.is() )
+ {
+ Uri aUri( rUri );
+ xModel = m_xDocsMgr->queryDocumentModel( aUri.getDocumentId() );
+ }
+
+ OSL_ENSURE( xModel.is(),
+ "ContentProvider::queryDocumentModel - no model!" );
+ return xModel;
+}
+
+
+css::util::DateTime ContentProvider::queryStreamDateModified(OUString const & uri) const {
+ return m_xDocsMgr->queryStreamDateModified(uri);
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/ucb/source/ucp/tdoc/tdoc_provider.hxx b/ucb/source/ucp/tdoc/tdoc_provider.hxx
new file mode 100644
index 0000000000..8a859ac65a
--- /dev/null
+++ b/ucb/source/ucp/tdoc/tdoc_provider.hxx
@@ -0,0 +1,140 @@
+/* -*- 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:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#pragma once
+
+#include <sal/config.h>
+
+#include <string_view>
+
+#include <rtl/ref.hxx>
+#include <com/sun/star/frame/XTransientDocumentsDocumentContentFactory.hpp>
+#include <com/sun/star/frame/XTransientDocumentsDocumentContentIdentifierFactory.hpp>
+#include <ucbhelper/providerhelper.hxx>
+#include "tdoc_uri.hxx"
+#include "tdoc_docmgr.hxx"
+#include "tdoc_storage.hxx"
+
+namespace com::sun::star::embed {
+ class XStorage;
+}
+
+namespace com::sun::star::frame {
+ class XModel;
+}
+
+namespace com::sun::star::util {
+ struct DateTime;
+}
+
+namespace tdoc_ucp {
+
+
+inline constexpr OUString TDOC_ROOT_CONTENT_TYPE =
+ u"application/" TDOC_URL_SCHEME "-root"_ustr;
+inline constexpr OUString TDOC_DOCUMENT_CONTENT_TYPE =
+ u"application/" TDOC_URL_SCHEME "-document"_ustr;
+inline constexpr OUString TDOC_FOLDER_CONTENT_TYPE =
+ u"application/" TDOC_URL_SCHEME "-folder"_ustr;
+inline constexpr OUString TDOC_STREAM_CONTENT_TYPE =
+ u"application/" TDOC_URL_SCHEME "-stream"_ustr;
+
+
+class StorageElementFactory;
+
+typedef cppu::ImplInheritanceHelper<
+ ::ucbhelper::ContentProviderImplHelper,
+ css::frame::XTransientDocumentsDocumentContentIdentifierFactory,
+ css::frame::XTransientDocumentsDocumentContentFactory > ContentProvider_Base;
+class ContentProvider : public ContentProvider_Base
+{
+public:
+ explicit ContentProvider( const css::uno::Reference< css::uno::XComponentContext >& rxContext );
+ virtual ~ContentProvider() 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;
+
+ // XTransientDocumentsDocumentContentIdentifierFactory
+ virtual css::uno::Reference<css::ucb::XContentIdentifier> SAL_CALL
+ createDocumentContentIdentifier(
+ css::uno::Reference<css::frame::XModel> const& xModel) override;
+
+ // XTransientDocumentsDocumentContentFactory
+ virtual css::uno::Reference< css::ucb::XContent > SAL_CALL
+ createDocumentContent( const css::uno::Reference<
+ css::frame::XModel >& Model ) override;
+
+ // Non-UNO interfaces
+ css::uno::Reference< css::embed::XStorage >
+ queryStorage( const OUString & rUri, StorageAccessMode eMode ) const;
+
+ css::uno::Reference< css::embed::XStorage >
+ queryStorageClone( const OUString & rUri ) const;
+
+ /// @throws css::packages::WrongPasswordException
+ /// @throws css::uno::RuntimeException
+ css::uno::Reference< css::io::XInputStream >
+ queryInputStream( const OUString & rUri,
+ const OUString & rPassword ) const;
+
+ /// @throws css::packages::WrongPasswordException
+ /// @throws css::uno::RuntimeException
+ css::uno::Reference< css::io::XOutputStream >
+ queryOutputStream( const OUString & rUri,
+ const OUString & rPassword,
+ bool bTruncate ) const;
+
+ /// @throws css::packages::WrongPasswordException
+ /// @throws css::uno::RuntimeException
+ css::uno::Reference< css::io::XStream >
+ queryStream( const OUString & rUri,
+ const OUString & rPassword,
+ bool bTruncate ) const;
+
+ bool queryNamesOfChildren(
+ const OUString & rUri,
+ css::uno::Sequence< OUString > & rNames ) const;
+
+ // storage properties
+ OUString queryStorageTitle( const OUString & rUri ) const;
+
+ css::uno::Reference< css::frame::XModel >
+ queryDocumentModel( const OUString & rUri ) const;
+
+ css::util::DateTime queryStreamDateModified(OUString const & uri) const;
+
+ // interface OfficeDocumentsEventListener
+ void notifyDocumentOpened( std::u16string_view rDocId );
+ void notifyDocumentClosed( std::u16string_view rDocId );
+
+private:
+ rtl::Reference< OfficeDocumentsManager > m_xDocsMgr;
+ rtl::Reference< StorageElementFactory > m_xStgElemFac;
+};
+
+} // namespace tdoc_ucp
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/ucb/source/ucp/tdoc/tdoc_resultset.cxx b/ucb/source/ucp/tdoc/tdoc_resultset.cxx
new file mode 100644
index 0000000000..4b1b1a8328
--- /dev/null
+++ b/ucb/source/ucp/tdoc/tdoc_resultset.cxx
@@ -0,0 +1,79 @@
+/* -*- 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:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+
+/**************************************************************************
+ TODO
+ **************************************************************************
+
+ - This implementation is not a dynamic result set!!! It only implements
+ the necessary interfaces, but never recognizes/notifies changes!!!
+
+ *************************************************************************/
+
+#include <ucbhelper/resultset.hxx>
+#include <utility>
+
+#include "tdoc_datasupplier.hxx"
+#include "tdoc_resultset.hxx"
+#include "tdoc_content.hxx"
+
+using namespace com::sun::star;
+using namespace tdoc_ucp;
+
+
+// DynamicResultSet Implementation.
+
+
+DynamicResultSet::DynamicResultSet(
+ const uno::Reference< uno::XComponentContext >& rxContext,
+ rtl::Reference< Content > xContent,
+ const ucb::OpenCommandArgument2& rCommand )
+: ResultSetImplHelper( rxContext, rCommand ),
+ m_xContent(std::move( xContent ))
+{
+}
+
+
+// Non-interface methods.
+
+
+void DynamicResultSet::initStatic()
+{
+ m_xResultSet1
+ = new ::ucbhelper::ResultSet(
+ m_xContext,
+ m_aCommand.Properties,
+ new ResultSetDataSupplier( m_xContext,
+ m_xContent ) );
+}
+
+
+void DynamicResultSet::initDynamic()
+{
+ m_xResultSet1
+ = new ::ucbhelper::ResultSet(
+ m_xContext,
+ m_aCommand.Properties,
+ new ResultSetDataSupplier( m_xContext,
+ m_xContent ) );
+ m_xResultSet2 = m_xResultSet1;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/ucb/source/ucp/tdoc/tdoc_resultset.hxx b/ucb/source/ucp/tdoc/tdoc_resultset.hxx
new file mode 100644
index 0000000000..5324dda572
--- /dev/null
+++ b/ucb/source/ucp/tdoc/tdoc_resultset.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/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#pragma once
+
+#include <rtl/ref.hxx>
+#include <ucbhelper/resultsethelper.hxx>
+#include "tdoc_content.hxx"
+
+namespace tdoc_ucp {
+
+class Content;
+
+class DynamicResultSet : public ::ucbhelper::ResultSetImplHelper
+{
+ rtl::Reference< Content > m_xContent;
+
+private:
+ virtual void initStatic() override;
+ virtual void initDynamic() override;
+
+public:
+ DynamicResultSet(
+ const css::uno::Reference< css::uno::XComponentContext >& rxContext,
+ rtl::Reference< Content > xContent,
+ const css::ucb::OpenCommandArgument2& rCommand );
+};
+
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/ucb/source/ucp/tdoc/tdoc_stgelems.cxx b/ucb/source/ucp/tdoc/tdoc_stgelems.cxx
new file mode 100644
index 0000000000..dff9bf5909
--- /dev/null
+++ b/ucb/source/ucp/tdoc/tdoc_stgelems.cxx
@@ -0,0 +1,882 @@
+/* -*- 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:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+
+/**************************************************************************
+ TODO
+ **************************************************************************
+
+ - remove root storage access workaround
+
+ *************************************************************************/
+
+#include <comphelper/diagnose_ex.hxx>
+#include <com/sun/star/io/IOException.hpp>
+#include <com/sun/star/lang/DisposedException.hpp>
+#include <com/sun/star/reflection/ProxyFactory.hpp>
+#include <utility>
+
+#include "tdoc_docmgr.hxx"
+#include "tdoc_uri.hxx"
+
+#include "tdoc_stgelems.hxx"
+
+using namespace com::sun::star;
+using namespace tdoc_ucp;
+
+
+// ParentStorageHolder Implementation.
+
+
+ParentStorageHolder::ParentStorageHolder(
+ uno::Reference< embed::XStorage > xParentStorage,
+ const OUString & rUri )
+: m_xParentStorage(std::move( xParentStorage )),
+ m_bParentIsRootStorage( false )
+{
+ Uri aUri( rUri );
+ if ( aUri.isDocument() )
+ m_bParentIsRootStorage = true;
+}
+
+
+// Storage Implementation.
+
+
+Storage::Storage( const uno::Reference< uno::XComponentContext > & rxContext,
+ rtl::Reference< StorageElementFactory > xFactory,
+ const OUString & rUri,
+ const uno::Reference< embed::XStorage > & xParentStorage,
+ const uno::Reference< embed::XStorage > & xStorageToWrap )
+: ParentStorageHolder( xParentStorage, Uri( rUri ).getParentUri() ),
+ m_xFactory(std::move( xFactory )),
+ m_xWrappedStorage( xStorageToWrap ),
+ m_xWrappedTransObj( xStorageToWrap, uno::UNO_QUERY ), // optional interface
+ m_xWrappedComponent( xStorageToWrap ),
+ m_xWrappedTypeProv( xStorageToWrap, uno::UNO_QUERY ),
+ m_bIsDocumentStorage( Uri( rUri ).isDocument() )
+{
+ OSL_ENSURE( m_xWrappedStorage.is(),
+ "Storage::Storage: No storage to wrap!" );
+
+ OSL_ENSURE( m_xWrappedComponent.is(),
+ "Storage::Storage: No component to wrap!" );
+
+ OSL_ENSURE( m_xWrappedTypeProv.is(),
+ "Storage::Storage: No Type Provider!" );
+
+ // Use proxy factory service to create aggregatable proxy.
+ try
+ {
+ uno::Reference< reflection::XProxyFactory > xProxyFac =
+ reflection::ProxyFactory::create( rxContext );
+ m_xAggProxy = xProxyFac->createProxy( m_xWrappedStorage );
+ }
+ catch ( uno::Exception const & )
+ {
+ TOOLS_WARN_EXCEPTION("ucb.ucp", "");
+ }
+
+ OSL_ENSURE( m_xAggProxy.is(),
+ "Storage::Storage: Wrapped storage cannot be aggregated!" );
+
+ if ( !m_xAggProxy.is() )
+ return;
+
+ osl_atomic_increment( &m_refCount );
+ {
+ // Solaris compiler problem:
+ // Extra block to enforce destruction of temporary object created
+ // in next statement _before_ osl_atomic_decrement is
+ // called. Otherwise 'this' will destroy itself even before ctor
+ // is completed (See impl. of XInterface::release())!
+
+ m_xAggProxy->setDelegator(
+ getXWeak() );
+ }
+ osl_atomic_decrement( &m_refCount );
+}
+
+
+// virtual
+Storage::~Storage()
+{
+ if ( m_xAggProxy.is() )
+ m_xAggProxy->setDelegator( uno::Reference< uno::XInterface >() );
+
+ // Never dispose a document storage. Not owner!
+ if ( m_bIsDocumentStorage )
+ return;
+
+ if ( !m_xWrappedComponent.is() )
+ return;
+
+ // "Auto-dispose"...
+ try
+ {
+ m_xWrappedComponent->dispose();
+ }
+ catch ( lang::DisposedException const & )
+ {
+ // might happen.
+ }
+ catch ( ... )
+ {
+ TOOLS_WARN_EXCEPTION( "ucb", "Storage::~Storage - Caught exception!" );
+ }
+}
+
+
+// uno::XInterface
+
+
+// virtual
+uno::Any SAL_CALL Storage::queryInterface( const uno::Type& aType )
+{
+ // First, try to use interfaces implemented by myself and base class(es)
+ uno::Any aRet = StorageUNOBase::queryInterface( aType );
+
+ if ( aRet.hasValue() )
+ return aRet;
+
+ // Try to use requested interface from aggregated storage
+ return m_xAggProxy->queryAggregation( aType );
+}
+
+
+// virtual
+void SAL_CALL Storage::acquire()
+ noexcept
+{
+ osl_atomic_increment( &m_refCount );
+}
+
+
+// virtual
+void SAL_CALL Storage::release()
+ noexcept
+{
+ //#i120738, Storage::release overrides OWeakObject::release(),
+ //need call OWeakObject::release() to release OWeakObject::m_pWeakConnectionPoint
+
+ if ( m_refCount == 1 )
+ m_xFactory->releaseElement( this );
+
+ //delete this;
+ OWeakObject::release();
+}
+
+
+// lang::XTypeProvider
+
+
+// virtual
+uno::Sequence< uno::Type > SAL_CALL Storage::getTypes()
+{
+ return m_xWrappedTypeProv->getTypes();
+}
+
+
+// virtual
+uno::Sequence< sal_Int8 > SAL_CALL Storage::getImplementationId()
+{
+ return css::uno::Sequence<sal_Int8>();
+}
+
+
+// lang::XComponent (base of embed::XStorage)
+
+
+// virtual
+void SAL_CALL Storage::dispose()
+{
+ m_xWrappedStorage->dispose();
+ m_xWrappedStorage.clear();
+}
+
+
+// virtual
+void SAL_CALL Storage::addEventListener(
+ const uno::Reference< lang::XEventListener >& xListener )
+{
+ m_xWrappedStorage->addEventListener( xListener );
+}
+
+// virtual
+void SAL_CALL Storage::removeEventListener(
+ const uno::Reference< lang::XEventListener >& aListener )
+{
+ m_xWrappedStorage->removeEventListener( aListener );
+}
+
+
+// container::XElementAccess (base of container::XNameAccess)
+
+
+// virtual
+uno::Type SAL_CALL Storage::getElementType()
+{
+ return m_xWrappedStorage->getElementType();
+}
+
+
+// virtual
+sal_Bool SAL_CALL Storage::hasElements()
+{
+ return m_xWrappedStorage->hasElements();
+}
+
+
+// container::XNameAccess (base of embed::XStorage)
+
+
+// virtual
+uno::Any SAL_CALL Storage::getByName( const OUString& aName )
+{
+ return m_xWrappedStorage->getByName( aName );
+}
+
+
+// virtual
+uno::Sequence< OUString > SAL_CALL Storage::getElementNames()
+{
+ return m_xWrappedStorage->getElementNames();
+}
+
+
+// virtual
+sal_Bool SAL_CALL Storage::hasByName( const OUString& aName )
+{
+ return m_xWrappedStorage->hasByName( aName );
+}
+
+
+// embed::XStorage
+
+
+// virtual
+void SAL_CALL Storage::copyToStorage(
+ const uno::Reference< embed::XStorage >& xDest )
+{
+ m_xWrappedStorage->copyToStorage( xDest );
+}
+
+
+// virtual
+uno::Reference< io::XStream > SAL_CALL Storage::openStreamElement(
+ const OUString& aStreamName, sal_Int32 nOpenMode )
+{
+ return m_xWrappedStorage->openStreamElement( aStreamName, nOpenMode );
+}
+
+
+// virtual
+uno::Reference< io::XStream > SAL_CALL Storage::openEncryptedStreamElement(
+ const OUString& aStreamName,
+ sal_Int32 nOpenMode,
+ const OUString& aPassword )
+{
+ return m_xWrappedStorage->openEncryptedStreamElement(
+ aStreamName, nOpenMode, aPassword );
+}
+
+
+// virtual
+uno::Reference< embed::XStorage > SAL_CALL Storage::openStorageElement(
+ const OUString& aStorName, sal_Int32 nOpenMode )
+{
+ return m_xWrappedStorage->openStorageElement( aStorName, nOpenMode );
+}
+
+
+// virtual
+uno::Reference< io::XStream > SAL_CALL Storage::cloneStreamElement(
+ const OUString& aStreamName )
+{
+ return m_xWrappedStorage->cloneStreamElement( aStreamName );
+}
+
+
+// virtual
+uno::Reference< io::XStream > SAL_CALL Storage::cloneEncryptedStreamElement(
+ const OUString& aStreamName,
+ const OUString& aPassword )
+{
+ return m_xWrappedStorage->cloneEncryptedStreamElement( aStreamName,
+ aPassword );
+}
+
+
+// virtual
+void SAL_CALL Storage::copyLastCommitTo(
+ const uno::Reference< embed::XStorage >& xTargetStorage )
+{
+ m_xWrappedStorage->copyLastCommitTo( xTargetStorage );
+}
+
+
+// virtual
+void SAL_CALL Storage::copyStorageElementLastCommitTo(
+ const OUString& aStorName,
+ const uno::Reference< embed::XStorage >& xTargetStorage )
+{
+ m_xWrappedStorage->copyStorageElementLastCommitTo( aStorName, xTargetStorage );
+}
+
+
+// virtual
+sal_Bool SAL_CALL Storage::isStreamElement(
+ const OUString& aElementName )
+{
+ return m_xWrappedStorage->isStreamElement( aElementName );
+}
+
+
+// virtual
+sal_Bool SAL_CALL Storage::isStorageElement(
+ const OUString& aElementName )
+{
+ return m_xWrappedStorage->isStorageElement( aElementName );
+}
+
+
+// virtual
+void SAL_CALL Storage::removeElement( const OUString& aElementName )
+{
+ m_xWrappedStorage->removeElement( aElementName );
+}
+
+
+// virtual
+void SAL_CALL Storage::renameElement( const OUString& aEleName,
+ const OUString& aNewName )
+{
+ m_xWrappedStorage->renameElement( aEleName, aNewName );
+}
+
+
+// virtual
+void SAL_CALL Storage::copyElementTo(
+ const OUString& aElementName,
+ const uno::Reference< embed::XStorage >& xDest,
+ const OUString& aNewName )
+{
+ m_xWrappedStorage->copyElementTo( aElementName, xDest, aNewName );
+}
+
+
+// virtual
+void SAL_CALL Storage::moveElementTo(
+ const OUString& aElementName,
+ const uno::Reference< embed::XStorage >& xDest,
+ const OUString& rNewName )
+{
+ m_xWrappedStorage->moveElementTo( aElementName, xDest, rNewName );
+}
+
+
+// embed::XTransactedObject
+
+
+// virtual
+void SAL_CALL Storage::commit()
+{
+ // Never commit a root storage (-> has no parent)!
+ // Would lead in writing the whole document to disk.
+
+ uno::Reference< embed::XStorage > xParentStorage = getParentStorage();
+ if ( !xParentStorage.is() )
+ return;
+
+ OSL_ENSURE( m_xWrappedTransObj.is(), "No XTransactedObject interface!" );
+
+ if ( !m_xWrappedTransObj.is() )
+ return;
+
+ m_xWrappedTransObj->commit();
+
+ if ( !isParentARootStorage() )
+ {
+ uno::Reference< embed::XTransactedObject > xParentTA(
+ xParentStorage, uno::UNO_QUERY );
+ OSL_ENSURE( xParentTA.is(), "No XTransactedObject interface!" );
+
+ if ( xParentTA.is() )
+ xParentTA->commit();
+ }
+}
+
+
+// virtual
+void SAL_CALL Storage::revert()
+{
+ uno::Reference< embed::XStorage > xParentStorage = getParentStorage();
+ if ( !xParentStorage.is() )
+ return;
+
+ OSL_ENSURE( m_xWrappedTransObj.is(), "No XTransactedObject interface!" );
+
+ if ( !m_xWrappedTransObj.is() )
+ return;
+
+ m_xWrappedTransObj->revert();
+
+ if ( !isParentARootStorage() )
+ {
+ uno::Reference< embed::XTransactedObject > xParentTA(
+ xParentStorage, uno::UNO_QUERY );
+ OSL_ENSURE( xParentTA.is(), "No XTransactedObject interface!" );
+
+ if ( xParentTA.is() )
+ xParentTA->revert();
+ }
+}
+
+
+// OutputStream Implementation.
+
+
+OutputStream::OutputStream(
+ const uno::Reference< uno::XComponentContext > & rxContext,
+ const OUString & rUri,
+ const uno::Reference< embed::XStorage > & xParentStorage,
+ const uno::Reference< io::XOutputStream > & xStreamToWrap )
+: ParentStorageHolder( xParentStorage, Uri( rUri ).getParentUri() ),
+ m_xWrappedStream( xStreamToWrap ),
+ m_xWrappedComponent( xStreamToWrap, uno::UNO_QUERY ),
+ m_xWrappedTypeProv( xStreamToWrap, uno::UNO_QUERY )
+{
+ OSL_ENSURE( m_xWrappedStream.is(),
+ "OutputStream::OutputStream: No stream to wrap!" );
+
+ OSL_ENSURE( m_xWrappedComponent.is(),
+ "OutputStream::OutputStream: No component to wrap!" );
+
+ OSL_ENSURE( m_xWrappedTypeProv.is(),
+ "OutputStream::OutputStream: No Type Provider!" );
+
+ // Use proxy factory service to create aggregatable proxy.
+ try
+ {
+ uno::Reference< reflection::XProxyFactory > xProxyFac =
+ reflection::ProxyFactory::create( rxContext );
+ m_xAggProxy = xProxyFac->createProxy( m_xWrappedStream );
+ }
+ catch ( uno::Exception const & )
+ {
+ TOOLS_WARN_EXCEPTION("ucb.ucp", "");
+ }
+
+ OSL_ENSURE( m_xAggProxy.is(),
+ "OutputStream::OutputStream: Wrapped stream cannot be aggregated!" );
+
+ if ( !m_xAggProxy.is() )
+ return;
+
+ osl_atomic_increment( &m_refCount );
+ {
+ // Solaris compiler problem:
+ // Extra block to enforce destruction of temporary object created
+ // in next statement _before_ osl_atomic_decrement is
+ // called. Otherwise 'this' will destroy itself even before ctor
+ // is completed (See impl. of XInterface::release())!
+
+ m_xAggProxy->setDelegator(
+ getXWeak() );
+ }
+ osl_atomic_decrement( &m_refCount );
+}
+
+
+// virtual
+OutputStream::~OutputStream()
+{
+ if ( m_xAggProxy.is() )
+ m_xAggProxy->setDelegator( uno::Reference< uno::XInterface >() );
+}
+
+
+// uno::XInterface
+
+
+// virtual
+uno::Any SAL_CALL OutputStream::queryInterface( const uno::Type& aType )
+{
+ uno::Any aRet = OutputStreamUNOBase::queryInterface( aType );
+
+ if ( aRet.hasValue() )
+ return aRet;
+
+ if ( m_xAggProxy.is() )
+ return m_xAggProxy->queryAggregation( aType );
+ else
+ return uno::Any();
+}
+
+
+// lang::XTypeProvider
+
+
+// virtual
+uno::Sequence< uno::Type > SAL_CALL OutputStream::getTypes()
+{
+ return m_xWrappedTypeProv->getTypes();
+}
+
+
+// virtual
+uno::Sequence< sal_Int8 > SAL_CALL OutputStream::getImplementationId()
+{
+ return css::uno::Sequence<sal_Int8>();
+}
+
+
+// io::XOutputStream
+
+
+// virtual
+void SAL_CALL
+OutputStream::writeBytes( const uno::Sequence< sal_Int8 >& aData )
+{
+ m_xWrappedStream->writeBytes( aData );
+}
+
+
+// virtual
+void SAL_CALL
+OutputStream::flush()
+{
+ m_xWrappedStream->flush();
+}
+
+
+// virtual
+void SAL_CALL
+OutputStream::closeOutput( )
+{
+ m_xWrappedStream->closeOutput();
+
+ // Release parent storage.
+ // Now, that the stream is closed/disposed it is not needed any longer.
+ setParentStorage( uno::Reference< embed::XStorage >() );
+}
+
+
+// lang::XComponent
+
+
+// virtual
+void SAL_CALL
+OutputStream::dispose()
+{
+ m_xWrappedComponent->dispose();
+
+ // Release parent storage.
+ // Now, that the stream is closed/disposed it is not needed any longer.
+ setParentStorage( uno::Reference< embed::XStorage >() );
+}
+
+
+// virtual
+void SAL_CALL
+OutputStream::addEventListener(
+ const uno::Reference< lang::XEventListener >& xListener )
+{
+ m_xWrappedComponent->addEventListener( xListener );
+}
+
+
+// virtual
+void SAL_CALL
+OutputStream::removeEventListener(
+ const uno::Reference< lang::XEventListener >& aListener )
+{
+ m_xWrappedComponent->removeEventListener( aListener );
+}
+
+
+// Stream Implementation.
+
+
+Stream::Stream(
+ const uno::Reference< uno::XComponentContext > & rxContext,
+ rtl::Reference<OfficeDocumentsManager> const & docsMgr,
+ const OUString & rUri,
+ const uno::Reference< embed::XStorage > & xParentStorage,
+ const uno::Reference< io::XStream > & xStreamToWrap )
+: ParentStorageHolder( xParentStorage, Uri( rUri ).getParentUri() ),
+ m_docsMgr(docsMgr),
+ m_uri(rUri),
+ m_xWrappedStream( xStreamToWrap ),
+ m_xWrappedOutputStream( xStreamToWrap->getOutputStream() ), // might be empty
+ m_xWrappedTruncate( m_xWrappedOutputStream, uno::UNO_QUERY ), // might be empty
+ m_xWrappedInputStream( xStreamToWrap->getInputStream() ),
+ m_xWrappedComponent( xStreamToWrap, uno::UNO_QUERY ),
+ m_xWrappedTypeProv( xStreamToWrap, uno::UNO_QUERY )
+{
+ OSL_ENSURE( m_xWrappedStream.is(),
+ "OutputStream::OutputStream: No stream to wrap!" );
+
+ OSL_ENSURE( m_xWrappedComponent.is(),
+ "OutputStream::OutputStream: No component to wrap!" );
+
+ OSL_ENSURE( m_xWrappedTypeProv.is(),
+ "OutputStream::OutputStream: No Type Provider!" );
+
+ // Use proxy factory service to create aggregatable proxy.
+ try
+ {
+ uno::Reference< reflection::XProxyFactory > xProxyFac =
+ reflection::ProxyFactory::create( rxContext );
+ m_xAggProxy = xProxyFac->createProxy( m_xWrappedStream );
+ }
+ catch ( uno::Exception const & )
+ {
+ TOOLS_WARN_EXCEPTION("ucb.ucp", "");
+ }
+
+ OSL_ENSURE( m_xAggProxy.is(),
+ "OutputStream::OutputStream: Wrapped stream cannot be aggregated!" );
+
+ if ( !m_xAggProxy.is() )
+ return;
+
+ osl_atomic_increment( &m_refCount );
+ {
+ // Solaris compiler problem:
+ // Extra block to enforce destruction of temporary object created
+ // in next statement _before_ osl_atomic_decrement is
+ // called. Otherwise 'this' will destroy itself even before ctor
+ // is completed (See impl. of XInterface::release())!
+
+ m_xAggProxy->setDelegator(
+ getXWeak() );
+ }
+ osl_atomic_decrement( &m_refCount );
+}
+
+
+// virtual
+Stream::~Stream()
+{
+ if ( m_xAggProxy.is() )
+ m_xAggProxy->setDelegator( uno::Reference< uno::XInterface >() );
+}
+
+
+// uno::XInterface
+
+
+// virtual
+uno::Any SAL_CALL Stream::queryInterface( const uno::Type& aType )
+{
+ uno::Any aRet = StreamUNOBase::queryInterface( aType );
+
+ if ( aRet.hasValue() )
+ return aRet;
+
+ if ( m_xAggProxy.is() )
+ return m_xAggProxy->queryAggregation( aType );
+ else
+ return uno::Any();
+}
+
+
+// lang::XTypeProvider
+
+
+// virtual
+uno::Sequence< uno::Type > SAL_CALL Stream::getTypes()
+{
+ return m_xWrappedTypeProv->getTypes();
+}
+
+
+// virtual
+uno::Sequence< sal_Int8 > SAL_CALL Stream::getImplementationId()
+{
+ return css::uno::Sequence<sal_Int8>();
+}
+
+
+// io::XStream.
+
+
+// virtual
+uno::Reference< io::XInputStream > SAL_CALL Stream::getInputStream()
+{
+ return uno::Reference< io::XInputStream >( this );
+}
+
+
+// virtual
+uno::Reference< io::XOutputStream > SAL_CALL Stream::getOutputStream()
+{
+ return uno::Reference< io::XOutputStream >( this );
+}
+
+
+// io::XOutputStream.
+
+
+// virtual
+void SAL_CALL Stream::writeBytes( const uno::Sequence< sal_Int8 >& aData )
+{
+ if ( m_xWrappedOutputStream.is() )
+ {
+ m_xWrappedOutputStream->writeBytes( aData );
+ commitChanges();
+ }
+}
+
+
+// virtual
+void SAL_CALL Stream::flush()
+{
+ if ( m_xWrappedOutputStream.is() )
+ {
+ m_xWrappedOutputStream->flush();
+ commitChanges();
+ }
+}
+
+
+// virtual
+void SAL_CALL Stream::closeOutput()
+{
+ if ( m_xWrappedOutputStream.is() )
+ {
+ m_xWrappedOutputStream->closeOutput();
+ commitChanges();
+ }
+
+ // Release parent storage.
+ // Now, that the stream is closed/disposed it is not needed any longer.
+ setParentStorage( uno::Reference< embed::XStorage >() );
+}
+
+
+// io::XTruncate.
+
+
+// virtual
+void SAL_CALL Stream::truncate()
+{
+ if ( m_xWrappedTruncate.is() )
+ {
+ m_xWrappedTruncate->truncate();
+ commitChanges();
+ }
+}
+
+
+// io::XInputStream.
+
+
+// virtual
+sal_Int32 SAL_CALL Stream::readBytes( uno::Sequence< sal_Int8 >& aData,
+ sal_Int32 nBytesToRead )
+{
+ return m_xWrappedInputStream->readBytes( aData, nBytesToRead );
+}
+
+
+// virtual
+sal_Int32 SAL_CALL Stream::readSomeBytes( uno::Sequence< sal_Int8 >& aData,
+ sal_Int32 nMaxBytesToRead )
+{
+ return m_xWrappedInputStream->readSomeBytes( aData, nMaxBytesToRead );
+}
+
+
+// virtual
+void SAL_CALL Stream::skipBytes( sal_Int32 nBytesToSkip )
+{
+ m_xWrappedInputStream->skipBytes( nBytesToSkip );
+}
+
+
+// virtual
+sal_Int32 SAL_CALL Stream::available()
+{
+ return m_xWrappedInputStream->available();
+}
+
+
+// virtual
+void SAL_CALL Stream::closeInput()
+{
+ m_xWrappedInputStream->closeInput();
+}
+
+
+// lang::XComponent
+
+
+// virtual
+void SAL_CALL Stream::dispose()
+{
+ m_xWrappedComponent->dispose();
+
+ // Release parent storage.
+ // Now, that the stream is closed/disposed it is not needed any longer.
+ setParentStorage( uno::Reference< embed::XStorage >() );
+}
+
+
+// virtual
+void SAL_CALL Stream::addEventListener(
+ const uno::Reference< lang::XEventListener >& xListener )
+{
+ m_xWrappedComponent->addEventListener( xListener );
+}
+
+
+// virtual
+void SAL_CALL Stream::removeEventListener(
+ const uno::Reference< lang::XEventListener >& aListener )
+{
+ m_xWrappedComponent->removeEventListener( aListener );
+}
+
+
+// Non-UNO
+
+
+void Stream::commitChanges()
+{
+ uno::Reference< embed::XTransactedObject >
+ xParentTA( getParentStorage(), uno::UNO_QUERY );
+ OSL_ENSURE( xParentTA.is(), "No XTransactedObject interface!" );
+
+ if ( xParentTA.is() )
+ {
+ try
+ {
+ xParentTA->commit();
+ }
+ catch ( lang::WrappedTargetException const & )
+ {
+ throw io::IOException(); // @@@
+ }
+ }
+ m_docsMgr->updateStreamDateModified(m_uri);
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/ucb/source/ucp/tdoc/tdoc_stgelems.hxx b/ucb/source/ucp/tdoc/tdoc_stgelems.hxx
new file mode 100644
index 0000000000..6229923f22
--- /dev/null
+++ b/ucb/source/ucp/tdoc/tdoc_stgelems.hxx
@@ -0,0 +1,341 @@
+/* -*- 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:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#pragma once
+
+#include <rtl/ref.hxx>
+
+#include <cppuhelper/implbase.hxx>
+
+#include <com/sun/star/embed/XStorage.hpp>
+#include <com/sun/star/embed/XTransactedObject.hpp>
+#include <com/sun/star/io/XOutputStream.hpp>
+#include <com/sun/star/io/XStream.hpp>
+#include <com/sun/star/io/XTruncate.hpp>
+#include <com/sun/star/lang/XComponent.hpp>
+#include <com/sun/star/uno/XAggregation.hpp>
+
+#include "tdoc_storage.hxx"
+
+#include <mutex>
+
+namespace tdoc_ucp {
+
+class OfficeDocumentsManager;
+
+class ParentStorageHolder
+{
+public:
+ ParentStorageHolder(
+ css::uno::Reference< css::embed::XStorage > xParentStorage,
+ const OUString & rUri );
+
+ bool isParentARootStorage() const
+ { return m_bParentIsRootStorage; }
+ const css::uno::Reference< css::embed::XStorage >&
+ getParentStorage() const
+ { return m_xParentStorage; }
+ void setParentStorage( const css::uno::Reference< css::embed::XStorage > & xStg )
+ {
+ std::scoped_lock aGuard( m_aMutex );
+ m_xParentStorage = xStg;
+ }
+
+private:
+ std::mutex m_aMutex;
+ css::uno::Reference< css::embed::XStorage > m_xParentStorage;
+ bool m_bParentIsRootStorage;
+};
+
+
+typedef
+ cppu::WeakImplHelper<
+ css::embed::XStorage,
+ css::embed::XTransactedObject > StorageUNOBase;
+
+class Storage : public StorageUNOBase, public ParentStorageHolder
+{
+public:
+ Storage(
+ const css::uno::Reference< css::uno::XComponentContext > & rxContext,
+ rtl::Reference< StorageElementFactory > xFactory,
+ const OUString & rUri,
+ const css::uno::Reference< css::embed::XStorage > & xParentStorage,
+ const css::uno::Reference< css::embed::XStorage > & xStorageToWrap );
+ virtual ~Storage() override;
+
+ // XInterface
+ virtual css::uno::Any SAL_CALL queryInterface(
+ const css::uno::Type& aType ) override;
+ virtual void SAL_CALL acquire()
+ noexcept override;
+ virtual void SAL_CALL release()
+ noexcept override;
+
+ // XTypeProvider (implemented by base, but needs to be overridden for
+ // delegating to aggregate)
+ virtual css::uno::Sequence< css::uno::Type > SAL_CALL
+ getTypes() override;
+ virtual css::uno::Sequence< sal_Int8 > SAL_CALL
+ getImplementationId() override;
+
+ // XComponent ( one of XStorage bases )
+ virtual void SAL_CALL
+ dispose() override;
+ virtual void SAL_CALL
+ addEventListener( const css::uno::Reference< css::lang::XEventListener > & xListener ) override;
+ virtual void SAL_CALL
+ removeEventListener( const css::uno::Reference< css::lang::XEventListener >& aListener ) override;
+
+ // XNameAccess ( one of XStorage bases )
+ virtual css::uno::Any SAL_CALL
+ getByName( const OUString& aName ) override;
+ virtual css::uno::Sequence< OUString > SAL_CALL
+ getElementNames() override;
+ virtual sal_Bool SAL_CALL
+ hasByName( const OUString& aName ) override;
+
+ // XElementAccess (base of XNameAccess)
+ virtual css::uno::Type SAL_CALL
+ getElementType() override;
+ virtual sal_Bool SAL_CALL
+ hasElements() override;
+
+ // XStorage
+ virtual void SAL_CALL
+ copyToStorage( const css::uno::Reference< css::embed::XStorage >& xDest ) override;
+ virtual css::uno::Reference< css::io::XStream > SAL_CALL
+ openStreamElement( const OUString& aStreamName,
+ sal_Int32 nOpenMode ) override;
+ virtual css::uno::Reference< css::io::XStream > SAL_CALL
+ openEncryptedStreamElement( const OUString& aStreamName,
+ sal_Int32 nOpenMode,
+ const OUString& aPassword ) override;
+ virtual css::uno::Reference< css::embed::XStorage > SAL_CALL
+ openStorageElement( const OUString& aStorName,
+ sal_Int32 nOpenMode ) override;
+ virtual css::uno::Reference< css::io::XStream > SAL_CALL
+ cloneStreamElement( const OUString& aStreamName ) override;
+ virtual css::uno::Reference< css::io::XStream > SAL_CALL
+ cloneEncryptedStreamElement( const OUString& aStreamName,
+ const OUString& aPassword ) override;
+ virtual void SAL_CALL
+ copyLastCommitTo( const css::uno::Reference<
+ css::embed::XStorage >& xTargetStorage ) override;
+ virtual void SAL_CALL
+ copyStorageElementLastCommitTo( const OUString& aStorName,
+ const css::uno::Reference<
+ css::embed::XStorage > &
+ xTargetStorage ) override;
+ virtual sal_Bool SAL_CALL
+ isStreamElement( const OUString& aElementName ) override;
+ virtual sal_Bool SAL_CALL
+ isStorageElement( const OUString& aElementName ) override;
+ virtual void SAL_CALL
+ removeElement( const OUString& aElementName ) override;
+ virtual void SAL_CALL
+ renameElement( const OUString& aEleName,
+ const OUString& aNewName ) override;
+ virtual void SAL_CALL
+ copyElementTo( const OUString& aElementName,
+ const css::uno::Reference< css::embed::XStorage >& xDest,
+ const OUString& aNewName ) override;
+ virtual void SAL_CALL
+ moveElementTo( const OUString& aElementName,
+ const css::uno::Reference< css::embed::XStorage >& xDest,
+ const OUString& rNewName ) override;
+
+ // XTransactedObject
+ virtual void SAL_CALL commit() override;
+ virtual void SAL_CALL revert() override;
+
+private:
+ rtl::Reference< StorageElementFactory > m_xFactory;
+ css::uno::Reference< css::uno::XAggregation > m_xAggProxy;
+ css::uno::Reference< css::embed::XStorage > m_xWrappedStorage;
+ css::uno::Reference< css::embed::XTransactedObject > m_xWrappedTransObj;
+ css::uno::Reference< css::lang::XComponent > m_xWrappedComponent;
+ css::uno::Reference< css::lang::XTypeProvider > m_xWrappedTypeProv;
+ bool m_bIsDocumentStorage;
+
+ StorageElementFactory::StorageMap::iterator m_aContainerIt;
+
+ friend class StorageElementFactory;
+};
+
+
+typedef
+ cppu::WeakImplHelper<
+ css::io::XOutputStream,
+ css::lang::XComponent > OutputStreamUNOBase;
+
+class OutputStream : public OutputStreamUNOBase, public ParentStorageHolder
+{
+public:
+ OutputStream(
+ const css::uno::Reference< css::uno::XComponentContext > & rxContext,
+ const OUString & rUri,
+ const css::uno::Reference< css::embed::XStorage > & xParentStorage,
+ const css::uno::Reference< css::io::XOutputStream > & xStreamToWrap );
+ virtual ~OutputStream() override;
+
+ // XInterface
+ virtual css::uno::Any SAL_CALL
+ queryInterface( const css::uno::Type& aType ) override;
+
+ // XTypeProvider (implemented by base, but needs to be overridden for
+ // delegating to aggregate)
+ virtual css::uno::Sequence< css::uno::Type > SAL_CALL
+ getTypes() override;
+ virtual css::uno::Sequence< sal_Int8 > SAL_CALL
+ getImplementationId() override;
+
+ // XOutputStream
+ virtual void SAL_CALL
+ writeBytes( const css::uno::Sequence< sal_Int8 >& aData ) override;
+ virtual void SAL_CALL
+ flush( ) override;
+ // Note: We need to intercept this one.
+ virtual void SAL_CALL
+ closeOutput( ) override;
+
+ // XComponent
+ // Note: We need to intercept this one.
+ virtual void SAL_CALL
+ dispose() override;
+ virtual void SAL_CALL
+ addEventListener( const css::uno::Reference< css::lang::XEventListener >& xListener ) override;
+ virtual void SAL_CALL
+ removeEventListener( const css::uno::Reference< css::lang::XEventListener >& aListener ) override;
+
+private:
+ css::uno::Reference<
+ css::uno::XAggregation > m_xAggProxy;
+ css::uno::Reference<
+ css::io::XOutputStream > m_xWrappedStream;
+ css::uno::Reference<
+ css::lang::XComponent > m_xWrappedComponent;
+ css::uno::Reference<
+ css::lang::XTypeProvider > m_xWrappedTypeProv;
+};
+
+
+typedef cppu::WeakImplHelper< css::io::XStream,
+ css::io::XOutputStream,
+ css::io::XTruncate,
+ css::io::XInputStream,
+ css::lang::XComponent >
+ StreamUNOBase;
+
+class Stream : public StreamUNOBase, public ParentStorageHolder
+{
+public:
+ Stream(
+ const css::uno::Reference< css::uno::XComponentContext > & rxContext,
+ rtl::Reference<OfficeDocumentsManager> const & docsMgr,
+ const OUString & rUri,
+ const css::uno::Reference< css::embed::XStorage > & xParentStorage,
+ const css::uno::Reference< css::io::XStream > & xStreamToWrap );
+
+ virtual ~Stream() override;
+
+ // XInterface
+ virtual css::uno::Any SAL_CALL
+ queryInterface( const css::uno::Type& aType ) override;
+
+ // XTypeProvider (implemented by base, but needs to be overridden for
+ // delegating to aggregate)
+ virtual css::uno::Sequence< css::uno::Type > SAL_CALL
+ getTypes() override;
+ virtual css::uno::Sequence< sal_Int8 > SAL_CALL
+ getImplementationId() override;
+
+ // XStream
+ virtual css::uno::Reference< css::io::XInputStream > SAL_CALL
+ getInputStream() override;
+
+ virtual css::uno::Reference< css::io::XOutputStream > SAL_CALL
+ getOutputStream() override;
+
+ // XOutputStream
+ 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;
+
+ // XTruncate
+ virtual void SAL_CALL
+ truncate() override;
+
+ // XInputStream
+ 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;
+
+ // XComponent
+ // Note: We need to intercept this one.
+ virtual void SAL_CALL
+ dispose() override;
+ virtual void SAL_CALL
+ addEventListener( const css::uno::Reference< css::lang::XEventListener >& xListener ) override;
+ virtual void SAL_CALL
+ removeEventListener( const css::uno::Reference< css::lang::XEventListener >& aListener ) override;
+
+private:
+ /// @throws css::io::IOException
+ void commitChanges();
+
+ rtl::Reference<OfficeDocumentsManager> m_docsMgr;
+ OUString m_uri;
+ css::uno::Reference<
+ css::uno::XAggregation > m_xAggProxy;
+ css::uno::Reference<
+ css::io::XStream > m_xWrappedStream;
+ css::uno::Reference<
+ css::io::XOutputStream > m_xWrappedOutputStream;
+ css::uno::Reference<
+ css::io::XTruncate > m_xWrappedTruncate;
+ css::uno::Reference<
+ css::io::XInputStream > m_xWrappedInputStream;
+ css::uno::Reference<
+ css::lang::XComponent > m_xWrappedComponent;
+ css::uno::Reference<
+ css::lang::XTypeProvider > m_xWrappedTypeProv;
+};
+
+} // namespace tdoc_ucp
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/ucb/source/ucp/tdoc/tdoc_storage.cxx b/ucb/source/ucp/tdoc/tdoc_storage.cxx
new file mode 100644
index 0000000000..fe307a5bfa
--- /dev/null
+++ b/ucb/source/ucp/tdoc/tdoc_storage.cxx
@@ -0,0 +1,618 @@
+/* -*- 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:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include <com/sun/star/beans/XPropertySet.hpp>
+#include <com/sun/star/embed/ElementModes.hpp>
+#include <com/sun/star/embed/InvalidStorageException.hpp>
+#include <com/sun/star/embed/StorageFactory.hpp>
+#include <com/sun/star/embed/StorageWrappedTargetException.hpp>
+#include <com/sun/star/io/IOException.hpp>
+#include <com/sun/star/packages/NoEncryptionException.hpp>
+#include <cppuhelper/exc_hlp.hxx>
+#include <utility>
+#include <osl/diagnose.h>
+
+#include "tdoc_uri.hxx"
+#include "tdoc_docmgr.hxx"
+#include "tdoc_stgelems.hxx"
+
+#include "tdoc_storage.hxx"
+
+using namespace com::sun::star;
+using namespace tdoc_ucp;
+
+
+// StorageElementFactory Implementation.
+
+
+StorageElementFactory::StorageElementFactory(
+ uno::Reference< uno::XComponentContext > xContext,
+ rtl::Reference< OfficeDocumentsManager > xDocsMgr )
+: m_xDocsMgr(std::move( xDocsMgr )),
+ m_xContext(std::move( xContext ))
+{
+}
+
+
+StorageElementFactory::~StorageElementFactory()
+{
+ OSL_ENSURE( m_aMap.empty(),
+ "StorageElementFactory::~StorageElementFactory - Dangling storages!" );
+}
+
+
+uno::Reference< embed::XStorage >
+StorageElementFactory::createTemporaryStorage()
+{
+ uno::Reference< embed::XStorage > xStorage;
+ uno::Reference< lang::XSingleServiceFactory > xStorageFac;
+ if ( m_xContext.is() )
+ {
+ xStorageFac = embed::StorageFactory::create( m_xContext );
+ }
+
+ OSL_ENSURE( xStorageFac.is(), "Can't create storage factory!" );
+ if ( xStorageFac.is() )
+ xStorage.set( xStorageFac->createInstance(), uno::UNO_QUERY );
+
+ if ( !xStorage.is() )
+ throw uno::RuntimeException();
+
+ return xStorage;
+}
+
+
+uno::Reference< embed::XStorage >
+StorageElementFactory::createStorage( const OUString & rUri,
+ StorageAccessMode eMode )
+{
+ osl::MutexGuard aGuard( m_aMutex );
+
+ if ( ( eMode != READ ) &&
+ ( eMode != READ_WRITE_NOCREATE ) &&
+ ( eMode != READ_WRITE_CREATE ) )
+ throw lang::IllegalArgumentException(
+ "Invalid open mode!",
+ uno::Reference< uno::XInterface >(),
+ sal_Int16( 2 ) );
+
+ Uri aUri( rUri );
+ if ( aUri.isRoot() )
+ {
+ throw lang::IllegalArgumentException(
+ "Root never has a storage!",
+ uno::Reference< uno::XInterface >(),
+ sal_Int16( 1 ) );
+ }
+
+ OUString aUriKey
+ ( rUri.endsWith("/")
+ ? rUri.copy( 0, rUri.getLength() - 1 )
+ : rUri );
+
+ StorageMap::iterator aIt ( m_aMap.begin() );
+ StorageMap::iterator aEnd( m_aMap.end() );
+
+ while ( aIt != aEnd )
+ {
+ if ( (*aIt).first.first == aUriKey )
+ {
+ // URI matches. Now, check open mode.
+ bool bMatch = true;
+ switch ( eMode )
+ {
+ case READ:
+ // No need to check; storage is at least readable.
+ bMatch = true;
+ break;
+
+ case READ_WRITE_NOCREATE:
+ case READ_WRITE_CREATE:
+ // If found storage is writable, it can be used.
+ // If not, a new one must be created.
+ bMatch = (*aIt).first.second;
+ break;
+ }
+
+ if ( bMatch )
+ break;
+ }
+ ++aIt;
+ }
+
+ if ( aIt == aEnd )
+ {
+ uno::Reference< embed::XStorage > xParentStorage;
+
+ // documents never have a parent storage.
+ if ( !aUri.isDocument() )
+ {
+ xParentStorage = queryParentStorage( aUriKey, eMode );
+
+ if ( !xParentStorage.is() )
+ {
+ // requested to create new storage, but failed?
+ OSL_ENSURE( eMode != READ_WRITE_CREATE,
+ "Unable to create parent storage!" );
+ return xParentStorage;
+ }
+ }
+
+ uno::Reference< embed::XStorage > xStorage
+ = queryStorage( xParentStorage, aUriKey, eMode );
+
+ if ( !xStorage.is() )
+ {
+ // requested to create new storage, but failed?
+ OSL_ENSURE( eMode != READ_WRITE_CREATE,
+ "Unable to create storage!" );
+ return xStorage;
+ }
+
+ bool bWritable = ( ( eMode == READ_WRITE_NOCREATE )
+ || ( eMode == READ_WRITE_CREATE ) );
+
+ rtl::Reference< Storage > xElement(
+ new Storage( m_xContext, this, aUriKey, xParentStorage, xStorage ) );
+
+ aIt = m_aMap.emplace(
+ std::pair< OUString, bool >( aUriKey, bWritable ),
+ xElement.get() ).first;
+
+ aIt->second->m_aContainerIt = aIt;
+ return aIt->second;
+ }
+ else if ( osl_atomic_increment( &aIt->second->m_refCount ) > 1 )
+ {
+ uno::Reference< embed::XStorage > xElement( aIt->second );
+ osl_atomic_decrement( &aIt->second->m_refCount );
+ return xElement;
+ }
+ else
+ {
+ osl_atomic_decrement( &aIt->second->m_refCount );
+ aIt->second->m_aContainerIt = m_aMap.end();
+
+ uno::Reference< embed::XStorage > xParentStorage;
+
+ // documents never have a parent storage.
+ if ( !aUri.isDocument() )
+ {
+ xParentStorage = queryParentStorage( aUriKey, eMode );
+
+ if ( !xParentStorage.is() )
+ {
+ // requested to create new storage, but failed?
+ OSL_ENSURE( eMode != READ_WRITE_CREATE,
+ "Unable to create parent storage!" );
+ return xParentStorage;
+ }
+ }
+
+ uno::Reference< embed::XStorage > xStorage
+ = queryStorage( xParentStorage, aUriKey, eMode );
+
+ if ( !xStorage.is() )
+ {
+ // requested to create new storage, but failed?
+ OSL_ENSURE( eMode != READ_WRITE_CREATE,
+ "Unable to create storage!" );
+ return xStorage;
+ }
+
+ rtl::Reference<Storage> pNewStorage = new Storage( m_xContext, this, aUriKey, xParentStorage, xStorage );
+ aIt->second = pNewStorage.get();
+ aIt->second->m_aContainerIt = aIt;
+ return pNewStorage;
+ }
+}
+
+
+uno::Reference< io::XInputStream >
+StorageElementFactory::createInputStream( const OUString & rUri,
+ const OUString & rPassword )
+{
+ osl::MutexGuard aGuard( m_aMutex );
+
+ uno::Reference< embed::XStorage > xParentStorage
+ = queryParentStorage( rUri, READ );
+
+ // Each stream must have a parent storage.
+ if ( !xParentStorage.is() )
+ return uno::Reference< io::XInputStream >();
+
+ uno::Reference< io::XStream > xStream
+ = queryStream( xParentStorage, rUri, rPassword, READ, false );
+
+ if ( !xStream.is() )
+ return uno::Reference< io::XInputStream >();
+
+ return xStream->getInputStream();
+}
+
+
+uno::Reference< io::XOutputStream >
+StorageElementFactory::createOutputStream( const OUString & rUri,
+ const OUString & rPassword,
+ bool bTruncate )
+{
+ osl::MutexGuard aGuard( m_aMutex );
+
+ uno::Reference< embed::XStorage > xParentStorage
+ = queryParentStorage( rUri, READ_WRITE_CREATE );
+
+ // Each stream must have a parent storage.
+ if ( !xParentStorage.is() )
+ {
+ OSL_FAIL( "StorageElementFactory::createOutputStream - "
+ "Unable to create parent storage!" );
+ return uno::Reference< io::XOutputStream >();
+ }
+
+ uno::Reference< io::XStream > xStream
+ = queryStream(
+ xParentStorage, rUri, rPassword, READ_WRITE_CREATE, bTruncate );
+
+ if ( !xStream.is() )
+ {
+ OSL_FAIL( "StorageElementFactory::createOutputStream - "
+ "Unable to create stream!" );
+ return uno::Reference< io::XOutputStream >();
+ }
+
+ // Note: We need a wrapper to hold a reference to the parent storage to
+ // ensure that nobody else owns it at the moment we want to commit
+ // our changes. (There can be only one writable instance at a time
+ // and even no writable instance if there is already another
+ // read-only instance!)
+ return uno::Reference< io::XOutputStream >(
+ new OutputStream( m_xContext, rUri, xParentStorage, xStream->getOutputStream() ) );
+}
+
+
+uno::Reference< io::XStream >
+StorageElementFactory::createStream( const OUString & rUri,
+ const OUString & rPassword,
+ bool bTruncate )
+{
+ osl::MutexGuard aGuard( m_aMutex );
+
+ uno::Reference< embed::XStorage > xParentStorage
+ = queryParentStorage( rUri, READ_WRITE_CREATE );
+
+ // Each stream must have a parent storage.
+ if ( !xParentStorage.is() )
+ {
+ OSL_FAIL( "StorageElementFactory::createStream - "
+ "Unable to create parent storage!" );
+ return uno::Reference< io::XStream >();
+ }
+
+ uno::Reference< io::XStream > xStream
+ = queryStream(
+ xParentStorage, rUri, rPassword, READ_WRITE_NOCREATE, bTruncate );
+
+ if ( !xStream.is() )
+ {
+ OSL_FAIL( "StorageElementFactory::createStream - "
+ "Unable to create stream!" );
+ return uno::Reference< io::XStream >();
+ }
+
+ return uno::Reference< io::XStream >(
+ new Stream( m_xContext, m_xDocsMgr, rUri, xParentStorage, xStream ) );
+}
+
+
+void StorageElementFactory::releaseElement( Storage const * pElement )
+{
+ OSL_ASSERT( pElement );
+ osl::MutexGuard aGuard( m_aMutex );
+ if ( pElement->m_aContainerIt != m_aMap.end() )
+ m_aMap.erase( pElement->m_aContainerIt );
+}
+
+
+// Non-UNO interface
+
+
+uno::Reference< embed::XStorage > StorageElementFactory::queryParentStorage(
+ const OUString & rUri, StorageAccessMode eMode )
+{
+ uno::Reference< embed::XStorage > xParentStorage;
+
+ Uri aUri( rUri );
+ Uri aParentUri( aUri.getParentUri() );
+ if ( !aParentUri.isRoot() )
+ {
+ xParentStorage = createStorage( aUri.getParentUri(), eMode );
+ OSL_ENSURE( xParentStorage.is()
+ // requested to create new storage, but failed?
+ || ( eMode != READ_WRITE_CREATE ),
+ "StorageElementFactory::queryParentStorage - No storage!" );
+ }
+ return xParentStorage;
+}
+
+
+uno::Reference< embed::XStorage > StorageElementFactory::queryStorage(
+ const uno::Reference< embed::XStorage > & xParentStorage,
+ const OUString & rUri,
+ StorageAccessMode eMode )
+{
+ uno::Reference< embed::XStorage > xStorage;
+
+ Uri aUri( rUri );
+
+ if ( !xParentStorage.is() )
+ {
+ // document storage
+
+ xStorage = m_xDocsMgr->queryStorage( aUri.getDocumentId() );
+
+ if ( !xStorage.is() )
+ {
+ if ( eMode == READ_WRITE_CREATE )
+ throw lang::IllegalArgumentException(
+ "Invalid open mode: document storages cannot be created!",
+ uno::Reference< uno::XInterface >(),
+ sal_Int16( 2 ) );
+ else
+ throw embed::InvalidStorageException(
+ "Invalid document id!",
+ uno::Reference< uno::XInterface >() );
+ }
+
+ // match xStorage's open mode against requested open mode
+
+ uno::Reference< beans::XPropertySet > xPropSet(
+ xStorage, uno::UNO_QUERY );
+ OSL_ENSURE( xPropSet.is(),
+ "StorageElementFactory::queryStorage - "
+ "No XPropertySet interface!" );
+ try
+ {
+ uno::Any aPropValue = xPropSet->getPropertyValue("OpenMode");
+
+ sal_Int32 nOpenMode = 0;
+ if ( aPropValue >>= nOpenMode )
+ {
+ switch ( eMode )
+ {
+ case READ:
+ if ( !( nOpenMode & embed::ElementModes::READ ) )
+ {
+ // document opened, but not readable.
+ throw embed::InvalidStorageException(
+ "Storage is open, but not readable!" );
+ }
+ // storage okay
+ break;
+
+ case READ_WRITE_NOCREATE:
+ case READ_WRITE_CREATE:
+ if ( !( nOpenMode & embed::ElementModes::WRITE ) )
+ {
+ // document opened, but not writable.
+ throw embed::InvalidStorageException(
+ "Storage is open, but not writable!" );
+ }
+ // storage okay
+ break;
+ }
+ }
+ else
+ {
+ OSL_FAIL(
+ "Bug! Value of property OpenMode has wrong type!" );
+
+ throw uno::RuntimeException(
+ "Bug! Value of property OpenMode has wrong type!" );
+ }
+ }
+ catch ( beans::UnknownPropertyException const & )
+ {
+ css::uno::Any anyEx = cppu::getCaughtException();
+ OSL_FAIL( "Property OpenMode not supported!" );
+
+ throw embed::StorageWrappedTargetException(
+ "Bug! Value of property OpenMode has wrong type!",
+ uno::Reference< uno::XInterface >(),
+ anyEx );
+ }
+ catch ( lang::WrappedTargetException const & )
+ {
+ css::uno::Any anyEx = cppu::getCaughtException();
+ OSL_FAIL( "Caught WrappedTargetException!" );
+
+ throw embed::StorageWrappedTargetException(
+ "WrappedTargetException during getPropertyValue!",
+ uno::Reference< uno::XInterface >(),
+ anyEx );
+ }
+ }
+ else
+ {
+ // sub storage
+
+ const OUString & rName = aUri.getDecodedName();
+
+ if ( eMode == READ )
+ {
+ try
+ {
+ sal_Int32 const nOpenMode = embed::ElementModes::READ
+ | embed::ElementModes::NOCREATE;
+ xStorage
+ = xParentStorage->openStorageElement( rName, nOpenMode );
+ }
+ catch ( io::IOException const & )
+ {
+ // Another chance: Try to clone storage.
+ xStorage = createTemporaryStorage();
+ xParentStorage->copyStorageElementLastCommitTo( rName,
+ xStorage );
+ }
+ }
+ else
+ {
+ sal_Int32 nOpenMode = embed::ElementModes::READWRITE;
+ if ( eMode == READ_WRITE_NOCREATE )
+ nOpenMode |= embed::ElementModes::NOCREATE;
+
+ xStorage = xParentStorage->openStorageElement( rName, nOpenMode );
+ }
+ }
+
+ OSL_ENSURE( xStorage.is() || ( eMode != READ_WRITE_CREATE ),
+ "StorageElementFactory::queryStorage - No storage!" );
+ return xStorage;
+}
+
+
+uno::Reference< io::XStream >
+StorageElementFactory::queryStream(
+ const uno::Reference< embed::XStorage > & xParentStorage,
+ const OUString & rUri,
+ const OUString & rPassword,
+ StorageAccessMode eMode,
+ bool bTruncate )
+{
+ osl::MutexGuard aGuard( m_aMutex );
+
+ if ( !xParentStorage.is() )
+ {
+ throw lang::IllegalArgumentException(
+ "No parent storage!",
+ uno::Reference< uno::XInterface >(),
+ sal_Int16( 2 ) );
+ }
+
+ Uri aUri( rUri );
+ if ( aUri.isRoot() )
+ {
+ throw lang::IllegalArgumentException(
+ "Root never is a stream!",
+ uno::Reference< uno::XInterface >(),
+ sal_Int16( 2 ) );
+ }
+ else if ( aUri.isDocument() )
+ {
+ throw lang::IllegalArgumentException(
+ "A document never is a stream!",
+ uno::Reference< uno::XInterface >(),
+ sal_Int16( 2 ) );
+ }
+
+ sal_Int32 nOpenMode;
+ switch ( eMode )
+ {
+ case READ:
+ nOpenMode = embed::ElementModes::READ
+ | embed::ElementModes::NOCREATE
+ | embed::ElementModes::SEEKABLE;
+ break;
+
+ case READ_WRITE_NOCREATE:
+ nOpenMode = embed::ElementModes::READWRITE
+ | embed::ElementModes::NOCREATE
+ | embed::ElementModes::SEEKABLE;
+
+ if ( bTruncate )
+ nOpenMode |= embed::ElementModes::TRUNCATE;
+
+ break;
+
+ case READ_WRITE_CREATE:
+ nOpenMode = embed::ElementModes::READWRITE
+ | embed::ElementModes::SEEKABLE;
+
+ if ( bTruncate )
+ nOpenMode |= embed::ElementModes::TRUNCATE;
+
+ break;
+
+ default:
+ OSL_FAIL( "StorageElementFactory::queryStream : Unknown open mode!" );
+
+ throw embed::InvalidStorageException(
+ "Unknown open mode!",
+ uno::Reference< uno::XInterface >() );
+ }
+
+ // No object re-usage mechanism; streams are seekable => not stateless.
+
+ uno::Reference< io::XStream > xStream;
+ if ( !rPassword.isEmpty() )
+ {
+ if ( eMode == READ )
+ {
+ try
+ {
+ xStream = xParentStorage->cloneEncryptedStreamElement(
+ aUri.getDecodedName(),
+ rPassword );
+ }
+ catch ( packages::NoEncryptionException const & )
+ {
+ xStream
+ = xParentStorage->cloneStreamElement( aUri.getDecodedName() );
+ }
+ }
+ else
+ {
+ try
+ {
+ xStream = xParentStorage->openEncryptedStreamElement(
+ aUri.getDecodedName(),
+ nOpenMode,
+ rPassword );
+ }
+ catch ( packages::NoEncryptionException const & )
+ {
+ xStream
+ = xParentStorage->openStreamElement( aUri.getDecodedName(),
+ nOpenMode );
+ }
+ }
+ }
+ else
+ {
+ if ( eMode == READ )
+ {
+ xStream = xParentStorage->cloneStreamElement( aUri.getDecodedName() );
+ }
+ else
+ {
+ xStream = xParentStorage->openStreamElement( aUri.getDecodedName(),
+ nOpenMode );
+ }
+ }
+
+ if ( !xStream.is() )
+ {
+ throw embed::InvalidStorageException(
+ "No stream!",
+ uno::Reference< uno::XInterface >() );
+ }
+
+ return xStream;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/ucb/source/ucp/tdoc/tdoc_storage.hxx b/ucb/source/ucp/tdoc/tdoc_storage.hxx
new file mode 100644
index 0000000000..08b247a68b
--- /dev/null
+++ b/ucb/source/ucp/tdoc/tdoc_storage.hxx
@@ -0,0 +1,162 @@
+/* -*- 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:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#pragma once
+
+#include <map>
+
+#include <osl/mutex.hxx>
+#include <rtl/ref.hxx>
+#include <salhelper/simplereferenceobject.hxx>
+
+#include <com/sun/star/embed/XStorage.hpp>
+#include <com/sun/star/uno/XComponentContext.hpp>
+
+namespace tdoc_ucp {
+
+ enum StorageAccessMode
+ {
+ READ, // Note: might be writable as well
+ READ_WRITE_NOCREATE,
+ READ_WRITE_CREATE
+ };
+
+ class Storage;
+ class OfficeDocumentsManager;
+
+ class StorageElementFactory : public salhelper::SimpleReferenceObject
+ {
+ public:
+ StorageElementFactory(
+ css::uno::Reference< css::uno::XComponentContext > xContext,
+ rtl::Reference< OfficeDocumentsManager > xDocsMgr );
+ virtual ~StorageElementFactory() override;
+
+ /// @throws css::uno::Exception
+ /// @throws css::uno::RuntimeException
+ css::uno::Reference< css::embed::XStorage >
+ createTemporaryStorage();
+
+ /// @throws css::embed::InvalidStorageException
+ /// @throws css::lang::IllegalArgumentException
+ /// @throws css::io::IOException
+ /// @throws css::embed::StorageWrappedTargetException
+ /// @throws css::uno::RuntimeException
+ css::uno::Reference< css::embed::XStorage >
+ createStorage( const OUString & rUri, StorageAccessMode eMode );
+
+ /// @throws css::embed::InvalidStorageException
+ /// @throws css::lang::IllegalArgumentException
+ /// @throws css::io::IOException
+ /// @throws css::embed::StorageWrappedTargetException
+ /// @throws css::packages::WrongPasswordException
+ /// @throws css::uno::RuntimeException
+ css::uno::Reference< css::io::XInputStream >
+ createInputStream( const OUString & rUri,
+ const OUString & rPassword );
+
+ /// @throws css::embed::InvalidStorageException
+ /// @throws css::lang::IllegalArgumentException
+ /// @throws css::io::IOException
+ /// @throws css::embed::StorageWrappedTargetException
+ /// @throws css::packages::WrongPasswordException
+ /// @throws css::uno::RuntimeException
+ css::uno::Reference< css::io::XOutputStream >
+ createOutputStream( const OUString & rUri,
+ const OUString & rPassword,
+ bool bTruncate );
+
+ /// @throws css::embed::InvalidStorageException
+ /// @throws css::lang::IllegalArgumentException
+ /// @throws css::io::IOException
+ /// @throws css::embed::StorageWrappedTargetException
+ /// @throws css::packages::WrongPasswordException
+ /// @throws css::uno::RuntimeException
+ css::uno::Reference< css::io::XStream >
+ createStream( const OUString & rUri,
+ const OUString & rPassword,
+ bool bTruncate );
+
+ private:
+ friend class Storage;
+
+ void releaseElement( Storage const * pElement );
+
+ /// @throws css::embed::InvalidStorageException
+ /// @throws css::lang::IllegalArgumentException
+ /// @throws css::io::IOException
+ /// @throws css::embed::StorageWrappedTargetException
+ /// @throws css::uno::RuntimeException
+ css::uno::Reference< css::embed::XStorage >
+ queryParentStorage( const OUString & rUri,
+ StorageAccessMode eMode );
+
+ /// @throws css::embed::InvalidStorageException
+ /// @throws css::lang::IllegalArgumentException
+ /// @throws css::io::IOException
+ /// @throws css::embed::StorageWrappedTargetException
+ /// @throws css::uno::RuntimeException
+ css::uno::Reference< css::embed::XStorage >
+ queryStorage( const css::uno::Reference<
+ css::embed::XStorage > & xParentStorage,
+ const OUString & rUri,
+ StorageAccessMode eMode );
+
+ /// @throws css::embed::InvalidStorageException
+ /// @throws css::lang::IllegalArgumentException
+ /// @throws css::io::IOException
+ /// @throws css::embed::StorageWrappedTargetException
+ /// @throws css::packages::WrongPasswordException
+ /// @throws css::uno::RuntimeException
+ css::uno::Reference< css::io::XStream >
+ queryStream( const css::uno::Reference<
+ css::embed::XStorage > & xParentStorage,
+ const OUString & rUri,
+ const OUString & rPassword,
+ StorageAccessMode eMode,
+ bool bTruncate /* ignored for read-only streams */ );
+
+ struct ltstrbool
+ {
+ bool operator()(
+ const std::pair< OUString, bool > & s1,
+ const std::pair< OUString, bool > & s2 ) const
+ {
+ if ( s1.first < s2.first )
+ return true;
+ else if ( s1.first == s2.first )
+ return ( !s1.second && s2.second );
+ else
+ return false;
+ }
+ };
+
+ // key: pair< storageuri, iswritable >
+ typedef std::map<
+ std::pair< OUString, bool >, Storage *, ltstrbool > StorageMap;
+
+ StorageMap m_aMap;
+ osl::Mutex m_aMutex;
+ rtl::Reference< OfficeDocumentsManager > m_xDocsMgr;
+ css::uno::Reference< css::uno::XComponentContext > m_xContext;
+ };
+
+} // namespace tdoc_ucp
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/ucb/source/ucp/tdoc/tdoc_uri.cxx b/ucb/source/ucp/tdoc/tdoc_uri.cxx
new file mode 100644
index 0000000000..592977ea03
--- /dev/null
+++ b/ucb/source/ucp/tdoc/tdoc_uri.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/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+
+/**************************************************************************
+ TODO
+ **************************************************************************
+
+ *************************************************************************/
+
+#include "../inc/urihelper.hxx"
+
+#include "tdoc_uri.hxx"
+
+using namespace tdoc_ucp;
+
+
+// Uri Implementation.
+
+
+void Uri::init() const
+{
+ // Already inited?
+ if ( m_eState != UNKNOWN )
+ return;
+
+ m_eState = INVALID;
+
+ // Check for proper length: must be at least length of <scheme>:/
+ if ( m_aUri.getLength() < TDOC_URL_SCHEME_LENGTH + 2 )
+ {
+ // Invalid length (to short).
+ return;
+ }
+
+ // Check for proper scheme. (Scheme is case insensitive.)
+ OUString aScheme
+ = m_aUri.copy( 0, TDOC_URL_SCHEME_LENGTH ).toAsciiLowerCase();
+ if ( aScheme != TDOC_URL_SCHEME )
+ {
+ // Invalid scheme.
+ return;
+ }
+
+ // Remember normalized scheme string.
+ m_aUri = m_aUri.replaceAt( 0, aScheme.getLength(), aScheme );
+
+ if ( m_aUri[ TDOC_URL_SCHEME_LENGTH ] != ':' )
+ {
+ // Invalid (no ':' after <scheme>).
+ return;
+ }
+
+ if ( m_aUri[ TDOC_URL_SCHEME_LENGTH + 1 ] != '/' )
+ {
+ // Invalid (no '/' after <scheme>:).
+ return;
+ }
+
+ m_aPath = m_aUri.copy( TDOC_URL_SCHEME_LENGTH + 1 );
+
+ // Note: There must be at least one slash; see above.
+ sal_Int32 nLastSlash = m_aUri.lastIndexOf( '/' );
+ bool bTrailingSlash = false;
+ if ( nLastSlash == m_aUri.getLength() - 1 )
+ {
+ // ignore trailing slash
+ bTrailingSlash = true;
+ nLastSlash = m_aUri.lastIndexOf( '/', nLastSlash );
+ }
+
+ if ( nLastSlash != -1 ) // -1 is valid for the root folder
+ {
+ m_aParentUri = m_aUri.copy( 0, nLastSlash + 1 );
+
+ if ( bTrailingSlash )
+ m_aName = m_aUri.copy( nLastSlash + 1,
+ m_aUri.getLength() - nLastSlash - 2 );
+ else
+ m_aName = m_aUri.copy( nLastSlash + 1 );
+
+ m_aDecodedName = ::ucb_impl::urihelper::decodeSegment( m_aName );
+
+ sal_Int32 nSlash = m_aPath.indexOf( '/', 1 );
+ if ( nSlash == -1 )
+ m_aDocId = m_aPath.copy( 1 );
+ else
+ m_aDocId = m_aPath.copy( 1, nSlash - 1 );
+ }
+
+ m_eState = VALID;
+
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/ucb/source/ucp/tdoc/tdoc_uri.hxx b/ucb/source/ucp/tdoc/tdoc_uri.hxx
new file mode 100644
index 0000000000..fcf36d354f
--- /dev/null
+++ b/ucb/source/ucp/tdoc/tdoc_uri.hxx
@@ -0,0 +1,108 @@
+/* -*- 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:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#pragma once
+
+#include <rtl/ustring.hxx>
+#include <utility>
+
+namespace tdoc_ucp {
+
+
+#define TDOC_URL_SCHEME "vnd.sun.star.tdoc"
+#define TDOC_URL_SCHEME_LENGTH 17
+
+
+class Uri
+{
+ enum State { UNKNOWN, INVALID, VALID };
+
+ mutable OUString m_aUri;
+ mutable OUString m_aParentUri;
+ mutable OUString m_aPath;
+ mutable OUString m_aDocId;
+ mutable OUString m_aName;
+ mutable OUString m_aDecodedName;
+ mutable State m_eState;
+
+private:
+ void init() const;
+
+public:
+ explicit Uri( OUString aUri )
+ : m_aUri(std::move( aUri )), m_eState( UNKNOWN ) {}
+
+ bool operator== ( const Uri & rOther ) const
+ { init(); return m_aUri == rOther.m_aUri; }
+
+ bool operator!= ( const Uri & rOther ) const
+ { return !operator==( rOther ); }
+
+ bool isValid() const
+ { init(); return m_eState == VALID; }
+
+ const OUString & getUri() const
+ { init(); return m_aUri; }
+
+ inline void setUri( const OUString & rUri );
+
+ const OUString & getParentUri() const
+ { init(); return m_aParentUri; }
+
+ const OUString & getDocumentId() const
+ { init(); return m_aDocId; }
+
+ const OUString & getName() const
+ { init(); return m_aName; }
+
+ const OUString & getDecodedName() const
+ { init(); return m_aDecodedName; }
+
+ inline bool isRoot() const;
+
+ inline bool isDocument() const;
+};
+
+inline void Uri::setUri( const OUString & rUri )
+{
+ m_eState = UNKNOWN;
+ m_aUri = rUri;
+ m_aParentUri.clear();
+ m_aDocId.clear();
+ m_aPath.clear();
+ m_aName.clear();
+ m_aDecodedName.clear();
+}
+
+inline bool Uri::isRoot() const
+{
+ init();
+ return ( m_aPath.getLength() == 1 );
+}
+
+inline bool Uri::isDocument() const
+{
+ init();
+ return ( ( !m_aDocId.isEmpty() ) /* not root */
+ && ( m_aPath.subView( m_aDocId.getLength() + 1 ).size() < 2 ) );
+}
+
+} // namespace tdoc_ucp
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/ucb/source/ucp/tdoc/ucptdoc1.component b/ucb/source/ucp/tdoc/ucptdoc1.component
new file mode 100644
index 0000000000..0ba43e2669
--- /dev/null
+++ b/ucb/source/ucp/tdoc/ucptdoc1.component
@@ -0,0 +1,30 @@
+<?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/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-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.ucb.TransientDocumentsContentProvider"
+ constructor="ucb_tdoc_ContentProvider_get_implementation">
+ <service name="com.sun.star.ucb.TransientDocumentsContentProvider"/>
+ </implementation>
+ <implementation name="com.sun.star.comp.ucb.TransientDocumentsDocumentContentFactory"
+ constructor="ucb_tdoc_DocumentContentFactory_get_implementation" single-instance="true">
+ <service name="com.sun.star.frame.TransientDocumentsDocumentContentFactory"/>
+ </implementation>
+</component>
diff --git a/ucb/source/ucp/webdav-curl/ContentProperties.cxx b/ucb/source/ucp/webdav-curl/ContentProperties.cxx
new file mode 100644
index 0000000000..c89710a1ea
--- /dev/null
+++ b/ucb/source/ucp/webdav-curl/ContentProperties.cxx
@@ -0,0 +1,566 @@
+/* -*- 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:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include <memory>
+#include <com/sun/star/util/DateTime.hpp>
+#include "CurlUri.hxx"
+#include "DAVResource.hxx"
+#include "DAVProperties.hxx"
+#include "DateTimeHelper.hxx"
+#include "webdavprovider.hxx"
+#include "ContentProperties.hxx"
+#include <o3tl/string_view.hxx>
+
+using namespace com::sun::star;
+using namespace http_dav_ucp;
+
+/*
+=============================================================================
+
+ Property Mapping
+
+=============================================================================
+HTTP (entity header) WebDAV (property) UCB (property)
+=============================================================================
+
+Allow
+Content-Encoding
+Content-Language getcontentlanguage
+Content-Length getcontentlength Size
+Content-Location
+Content-MD5
+Content-Range
+Content-Type getcontenttype MediaType
+Expires
+Last-Modified getlastmodified DateModified
+ creationdate DateCreated
+ resourcetype IsFolder,IsDocument,ContentType
+ displayname
+ETag (actually getetag
+a response header )
+ lockdiscovery
+ supportedlock
+ source
+ Title (always taken from URI)
+
+=============================================================================
+
+Important: HTTP headers will not be mapped to DAV properties; only to UCB
+ properties. (Content-Length,Content-Type,Last-Modified)
+*/
+
+
+// ContentProperties Implementation.
+
+
+// static member!
+uno::Any ContentProperties::m_aEmptyAny;
+
+ContentProperties::ContentProperties( const DAVResource& rResource )
+: m_xProps( new PropertyValueMap ),
+ m_bTrailingSlash( false )
+{
+ assert(!rResource.uri.isEmpty() &&
+ "ContentProperties ctor - Empty resource URI!");
+
+ // Title
+ try
+ {
+ CurlUri const aURI( rResource.uri );
+ m_aEscapedTitle = aURI.GetPathBaseName();
+
+ (*m_xProps)[ OUString( "Title" ) ]
+ = PropertyValue(
+ uno::Any( aURI.GetPathBaseNameUnescaped() ), true );
+ }
+ catch ( DAVException const & )
+ {
+ (*m_xProps)[ OUString( "Title" ) ]
+ = PropertyValue(
+ uno::Any(
+ OUString( "*** unknown ***" ) ),
+ true );
+ }
+
+ for ( const auto& rProp : rResource.properties )
+ {
+ addProperty( rProp );
+ }
+
+ if ( rResource.uri.endsWith("/") )
+ m_bTrailingSlash = true;
+}
+
+
+ContentProperties::ContentProperties(
+ const OUString & rTitle, bool bFolder )
+: m_xProps( new PropertyValueMap ),
+ m_bTrailingSlash( false )
+{
+ (*m_xProps)[ OUString( "Title" ) ]
+ = PropertyValue( uno::Any( rTitle ), true );
+ (*m_xProps)[ OUString( "IsFolder" ) ]
+ = PropertyValue( uno::Any( bFolder ), true );
+ (*m_xProps)[ OUString( "IsDocument" ) ]
+ = PropertyValue( uno::Any( bool( !bFolder ) ), true );
+}
+
+
+ContentProperties::ContentProperties( const OUString & rTitle )
+: m_xProps( new PropertyValueMap ),
+ m_bTrailingSlash( false )
+{
+ (*m_xProps)[ OUString( "Title" ) ]
+ = PropertyValue( uno::Any( rTitle ), true );
+}
+
+
+ContentProperties::ContentProperties()
+: m_xProps( new PropertyValueMap ),
+ m_bTrailingSlash( false )
+{
+}
+
+
+ContentProperties::ContentProperties( const ContentProperties & rOther )
+: m_aEscapedTitle( rOther.m_aEscapedTitle ),
+ m_xProps( rOther.m_xProps
+ ? new PropertyValueMap( *rOther.m_xProps )
+ : new PropertyValueMap ),
+ m_bTrailingSlash( rOther.m_bTrailingSlash )
+{
+}
+
+
+bool ContentProperties::contains( const OUString & rName ) const
+{
+ if ( get( rName ) )
+ return true;
+ else
+ return false;
+}
+
+
+const uno::Any & ContentProperties::getValue(
+ const OUString & rName ) const
+{
+ const PropertyValue * pProp = get( rName );
+ if ( pProp )
+ return pProp->value();
+ else
+ return m_aEmptyAny;
+}
+
+
+const PropertyValue * ContentProperties::get(
+ const OUString & rName ) const
+{
+ PropertyValueMap::const_iterator it = m_xProps->find( rName );
+ const PropertyValueMap::const_iterator end = m_xProps->end();
+
+ if ( it == end )
+ {
+ it = std::find_if(m_xProps->cbegin(), end,
+ [&rName](const PropertyValueMap::value_type& rEntry) {
+ return rEntry.first.equalsIgnoreAsciiCase( rName );
+ });
+ if ( it != end )
+ return &(*it).second;
+
+ return nullptr;
+ }
+ else
+ return &(*it).second;
+}
+
+
+// static
+void ContentProperties::UCBNamesToDAVNames(
+ const uno::Sequence< beans::Property > & rProps,
+ std::vector< OUString > & propertyNames )
+{
+
+ // Assemble list of DAV properties to obtain from server.
+ // Append DAV properties needed to obtain requested UCB props.
+
+
+ // DAV UCB
+ // creationdate <- DateCreated
+ // getlastmodified <- DateModified
+ // getcontenttype <- MediaType
+ // getcontentlength <- Size
+ // resourcetype <- IsFolder, IsDocument, ContentType
+ // (taken from URI) <- Title
+
+ bool bCreationDate = false;
+ bool bLastModified = false;
+ bool bContentType = false;
+ bool bContentLength = false;
+ bool bResourceType = false;
+
+ sal_Int32 nCount = rProps.getLength();
+ for ( sal_Int32 n = 0; n < nCount; ++n )
+ {
+ const beans::Property & rProp = rProps[ n ];
+
+ if ( rProp.Name == "Title" )
+ {
+ // Title is always obtained from resource's URI.
+ continue;
+ }
+ else if ( rProp.Name == "DateCreated" ||
+ ( rProp.Name == DAVProperties::CREATIONDATE ) )
+ {
+ if ( !bCreationDate )
+ {
+ propertyNames.push_back( DAVProperties::CREATIONDATE );
+ bCreationDate = true;
+ }
+ }
+ else if ( rProp.Name == "DateModified" ||
+ ( rProp.Name == DAVProperties::GETLASTMODIFIED ) )
+ {
+ if ( !bLastModified )
+ {
+ propertyNames.push_back(
+ DAVProperties::GETLASTMODIFIED );
+ bLastModified = true;
+ }
+ }
+ else if ( rProp.Name == "MediaType" ||
+ ( rProp.Name == DAVProperties::GETCONTENTTYPE ) )
+ {
+ if ( !bContentType )
+ {
+ propertyNames.push_back(
+ DAVProperties::GETCONTENTTYPE );
+ bContentType = true;
+ }
+ }
+ else if ( rProp.Name == "Size" ||
+ ( rProp.Name == DAVProperties::GETCONTENTLENGTH ) )
+ {
+ if ( !bContentLength )
+ {
+ propertyNames.push_back(
+ DAVProperties::GETCONTENTLENGTH );
+ bContentLength = true;
+ }
+ }
+ else if ( rProp.Name == "ContentType" ||
+ rProp.Name == "IsDocument" ||
+ rProp.Name == "IsFolder" ||
+ ( rProp.Name == DAVProperties::RESOURCETYPE ) )
+ {
+ if ( !bResourceType )
+ {
+ propertyNames.push_back( DAVProperties::RESOURCETYPE );
+ bResourceType = true;
+ }
+ }
+ else
+ {
+ propertyNames.push_back( rProp.Name );
+ }
+ }
+}
+
+
+// static
+void ContentProperties::UCBNamesToHTTPNames(
+ const uno::Sequence< beans::Property > & rProps,
+ std::vector< OUString > & propertyNames )
+{
+
+ // Assemble list of HTTP header names to obtain from server.
+ // Append HTTP headers needed to obtain requested UCB props.
+
+
+ // HTTP UCB
+ // Last-Modified <- DateModified
+ // Content-Type <- MediaType
+ // Content-Length <- Size
+
+ sal_Int32 nCount = rProps.getLength();
+ for ( sal_Int32 n = 0; n < nCount; ++n )
+ {
+ const beans::Property & rProp = rProps[ n ];
+
+ if ( rProp.Name == "DateModified" )
+ {
+ propertyNames.push_back( OUString( "Last-Modified" ) );
+ }
+ else if ( rProp.Name == "MediaType" )
+ {
+ propertyNames.push_back( OUString( "Content-Type" ) );
+ }
+ else if ( rProp.Name == "Size" )
+ {
+ propertyNames.push_back( OUString( "Content-Length" ) );
+ }
+ else
+ {
+ propertyNames.push_back( rProp.Name );
+ }
+ }
+}
+
+
+bool ContentProperties::containsAllNames(
+ const uno::Sequence< beans::Property >& rProps,
+ std::vector< OUString > & rNamesNotContained ) const
+{
+ rNamesNotContained.clear();
+
+ sal_Int32 nCount = rProps.getLength();
+ for ( sal_Int32 n = 0; n < nCount; ++n )
+ {
+ const OUString & rName = rProps[ n ].Name;
+ if ( !contains( rName ) )
+ {
+ // Not found.
+ rNamesNotContained.push_back( rName );
+ }
+ }
+
+ return ( rNamesNotContained.size() == 0 );
+}
+
+
+void ContentProperties::addProperties(
+ const std::vector< OUString > & rProps,
+ const ContentProperties & rContentProps )
+{
+ for ( const OUString & rName : rProps )
+ {
+ if ( !contains( rName ) ) // ignore duplicates
+ {
+ const PropertyValue * pProp = rContentProps.get( rName );
+ if ( pProp )
+ {
+ // Add it.
+ addProperty( rName, pProp->value(), pProp->isCaseSensitive() );
+ }
+ else
+ {
+ addProperty( rName, uno::Any(), false );
+ }
+ }
+ }
+}
+
+void ContentProperties::addProperty( const DAVPropertyValue & rProp )
+{
+ addProperty( rProp.Name, rProp.Value, rProp.IsCaseSensitive );
+}
+
+
+void ContentProperties::addProperty( const OUString & rName,
+ const css::uno::Any & rValue,
+ bool bIsCaseSensitive )
+{
+ if ( rName == DAVProperties::CREATIONDATE )
+ {
+ // Map DAV:creationdate to UCP:DateCreated
+ OUString aValue;
+ rValue >>= aValue;
+ util::DateTime aDate;
+ DateTimeHelper::convert( aValue, aDate );
+
+ (*m_xProps)[ OUString( "DateCreated" ) ]
+ = PropertyValue( uno::Any( aDate ), true );
+ }
+ // else if ( rName.equals( DAVProperties::DISPLAYNAME ) )
+ // {
+ // }
+ // else if ( rName.equals( DAVProperties::GETCONTENTLANGUAGE ) )
+ // {
+ // }
+ else if ( rName == DAVProperties::GETCONTENTLENGTH )
+ {
+ // Map DAV:getcontentlength to UCP:Size
+ OUString aValue;
+ rValue >>= aValue;
+
+ (*m_xProps)[ OUString( "Size" ) ]
+ = PropertyValue( uno::Any( aValue.toInt64() ), true );
+ }
+ else if ( rName.equalsIgnoreAsciiCase( "Content-Length" ) )
+ {
+ // Do NOT map Content-Length entity header to DAV:getcontentlength!
+ // Only DAV resources have this property.
+
+ // Map Content-Length entity header to UCP:Size
+ OUString aValue;
+ rValue >>= aValue;
+
+ (*m_xProps)[ OUString( "Size" ) ]
+ = PropertyValue( uno::Any( aValue.toInt64() ), true );
+ }
+ else if ( rName == DAVProperties::GETCONTENTTYPE )
+ {
+ // Map DAV:getcontenttype to UCP:MediaType (1:1)
+ (*m_xProps)[ OUString( "MediaType" ) ]
+ = PropertyValue( rValue, true );
+ }
+ else if ( rName.equalsIgnoreAsciiCase( "Content-Type" ) )
+ {
+ // Do NOT map Content-Type entity header to DAV:getcontenttype!
+ // Only DAV resources have this property.
+
+ // Map DAV:getcontenttype to UCP:MediaType (1:1)
+ (*m_xProps)[ OUString( "MediaType" ) ]
+ = PropertyValue( rValue, true );
+ }
+ // else if ( rName.equals( DAVProperties::GETETAG ) )
+ // {
+ // }
+ else if ( rName == DAVProperties::GETLASTMODIFIED )
+ {
+ // Map the DAV:getlastmodified entity header to UCP:DateModified
+ OUString aValue;
+ rValue >>= aValue;
+ util::DateTime aDate;
+ DateTimeHelper::convert( aValue, aDate );
+
+ (*m_xProps)[ OUString( "DateModified" ) ]
+ = PropertyValue( uno::Any( aDate ), true );
+ }
+ else if ( rName.equalsIgnoreAsciiCase( "Last-Modified" ) )
+ {
+ // Do not map Last-Modified entity header to DAV:getlastmodified!
+ // Only DAV resources have this property.
+
+ // Map the Last-Modified entity header to UCP:DateModified
+ OUString aValue;
+ rValue >>= aValue;
+ util::DateTime aDate;
+ DateTimeHelper::convert( aValue, aDate );
+
+ (*m_xProps)[ OUString( "DateModified" ) ]
+ = PropertyValue( uno::Any( aDate ), true );
+ }
+ // else if ( rName.equals( DAVProperties::LOCKDISCOVERY ) )
+ // {
+ // }
+ else if ( rName == DAVProperties::RESOURCETYPE )
+ {
+ OUString aValue;
+ rValue >>= aValue;
+
+ // Map DAV:resourcetype to UCP:IsFolder, UCP:IsDocument, UCP:ContentType
+ bool bFolder =
+ aValue.equalsIgnoreAsciiCase( "collection" );
+
+ (*m_xProps)[ OUString( "IsFolder" ) ]
+ = PropertyValue( uno::Any( bFolder ), true );
+ (*m_xProps)[ OUString( "IsDocument" ) ]
+ = PropertyValue( uno::Any( bool( !bFolder ) ), true );
+ (*m_xProps)[ OUString( "ContentType" ) ]
+ = PropertyValue( uno::Any( bFolder
+ ? WEBDAV_COLLECTION_TYPE
+ : WEBDAV_CONTENT_TYPE ), true );
+ }
+ // else if ( rName.equals( DAVProperties::SUPPORTEDLOCK ) )
+ // {
+ // }
+
+ // Save property.
+ (*m_xProps)[ rName ] = PropertyValue( rValue, bIsCaseSensitive );
+}
+
+
+// CachableContentProperties Implementation.
+
+
+namespace
+{
+ bool isCachable( std::u16string_view rName, bool isCaseSensitive )
+ {
+ const OUString aNonCachableProps [] =
+ {
+ DAVProperties::LOCKDISCOVERY,
+
+ DAVProperties::GETETAG,
+ OUString( "ETag" ),
+
+ OUString( "DateModified" ),
+ OUString( "Last-Modified" ),
+ DAVProperties::GETLASTMODIFIED,
+
+ OUString( "Size" ),
+ OUString( "Content-Length" ),
+ DAVProperties::GETCONTENTLENGTH,
+
+ OUString( "Date" )
+ };
+
+ for ( sal_uInt32 n = 0;
+ n < ( sizeof( aNonCachableProps )
+ / sizeof( aNonCachableProps[ 0 ] ) );
+ ++n )
+ {
+ if ( isCaseSensitive )
+ {
+ if ( rName == aNonCachableProps[ n ] )
+ return false;
+ }
+ else
+ if ( o3tl::equalsIgnoreAsciiCase( rName, aNonCachableProps[ n ] ) )
+ return false;
+ }
+ return true;
+ }
+
+} // namespace
+
+
+CachableContentProperties::CachableContentProperties(
+ const ContentProperties & rProps )
+{
+ addProperties( rProps );
+}
+
+
+void CachableContentProperties::addProperties(
+ const ContentProperties & rProps )
+{
+ const std::unique_ptr< PropertyValueMap > & props = rProps.getProperties();
+
+ for ( const auto& rProp : *props )
+ {
+ if ( isCachable( rProp.first, rProp.second.isCaseSensitive() ) )
+ m_aProps.addProperty( rProp.first,
+ rProp.second.value(),
+ rProp.second.isCaseSensitive() );
+ }
+}
+
+
+void CachableContentProperties::addProperties(
+ const std::vector< DAVPropertyValue > & rProps )
+{
+ for ( const auto& rProp : rProps )
+ {
+ if ( isCachable( rProp.Name, rProp.IsCaseSensitive ) )
+ m_aProps.addProperty( rProp );
+ }
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/ucb/source/ucp/webdav-curl/ContentProperties.hxx b/ucb/source/ucp/webdav-curl/ContentProperties.hxx
new file mode 100644
index 0000000000..376c77d3c6
--- /dev/null
+++ b/ucb/source/ucp/webdav-curl/ContentProperties.hxx
@@ -0,0 +1,174 @@
+/* -*- 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:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+
+#pragma once
+
+#include <memory>
+#include <unordered_map>
+#include <utility>
+#include <vector>
+#include <rtl/ustring.hxx>
+#include <com/sun/star/uno/Any.hxx>
+#include <com/sun/star/uno/Sequence.hxx>
+#include "DAVResource.hxx"
+
+namespace com::sun::star::beans {
+ struct Property;
+}
+
+namespace http_dav_ucp
+{
+
+struct DAVResource;
+
+// PropertyValueMap.
+class PropertyValue
+{
+private:
+ css::uno::Any m_aValue;
+ bool m_bIsCaseSensitive;
+
+public:
+ PropertyValue()
+ : m_bIsCaseSensitive( true ) {}
+
+ explicit PropertyValue( css::uno::Any aValue,
+ bool bIsCaseSensitive )
+ : m_aValue(std::move( aValue)),
+ m_bIsCaseSensitive( bIsCaseSensitive ) {}
+
+ bool isCaseSensitive() const { return m_bIsCaseSensitive; }
+ const css::uno::Any & value() const { return m_aValue; }
+
+};
+
+typedef std::unordered_map< OUString, PropertyValue > PropertyValueMap;
+
+class ContentProperties
+{
+public:
+ ContentProperties();
+
+ explicit ContentProperties( const DAVResource& rResource );
+
+ // Mini props for transient contents.
+ ContentProperties( const OUString & rTitle, bool bFolder );
+
+ // Micro props for non-existing contents.
+ explicit ContentProperties( const OUString & rTitle );
+
+ ContentProperties( const ContentProperties & rOther );
+
+ bool contains( const OUString & rName ) const;
+
+ const css::uno::Any& getValue( const OUString & rName ) const;
+
+ // Maps the UCB property names contained in rProps with their DAV property
+ // counterparts, if possible. All unmappable properties will be included
+ // unchanged in resulting vector.
+ // The vector filled by this method can directly be handed over to
+ // DAVResourceAccess::PROPFIND. The result from PROPFIND
+ // (vector< DAVResource >) can be used to create a ContentProperties
+ // instance which can map DAV properties back to UCB properties.
+ static void UCBNamesToDAVNames( const css::uno::Sequence< css::beans::Property > & rProps,
+ std::vector< OUString > & resources );
+
+ // Maps the UCB property names contained in rProps with their HTTP header
+ // counterparts, if possible. All unmappable properties will be included
+ // unchanged in resulting vector.
+ // The vector filled by this method can directly be handed over to
+ // DAVResourceAccess::HEAD. The result from HEAD (vector< DAVResource >)
+ // can be used to create a ContentProperties instance which can map header
+ // names back to UCB properties.
+ static void UCBNamesToHTTPNames( const css::uno::Sequence< css::beans::Property > & rProps,
+ std::vector< OUString > & resources );
+
+ // return true, if all properties contained in rProps are contained in
+ // this ContentProperties instance. Otherwise, false will be returned.
+ // rNamesNotContained contain the missing names.
+ bool containsAllNames(
+ const css::uno::Sequence< css::beans::Property >& rProps,
+ std::vector< OUString > & rNamesNotContained ) const;
+
+ // adds all properties described by rProps that are actually contained in
+ // rContentProps to this instance. In case of duplicates the value
+ // already contained in this will left unchanged.
+ void addProperties( const std::vector< OUString > & rProps,
+ const ContentProperties & rContentProps );
+
+ // overwrites probably existing entry.
+ void addProperty( const OUString & rName,
+ const css::uno::Any & rValue,
+ bool bIsCaseSensitive );
+
+ // overwrites probably existing entry.
+ void addProperty( const DAVPropertyValue & rProp );
+
+ bool isTrailingSlash() const { return m_bTrailingSlash; }
+
+ const OUString & getEscapedTitle() const { return m_aEscapedTitle; }
+
+ // Not good to expose implementation details, but this is actually an
+ // internal class.
+ const std::unique_ptr< PropertyValueMap > & getProperties() const
+ { return m_xProps; }
+
+private:
+ OUString m_aEscapedTitle;
+ std::unique_ptr< PropertyValueMap > m_xProps;
+ bool m_bTrailingSlash;
+
+ static css::uno::Any m_aEmptyAny;
+
+ ContentProperties & operator=( const ContentProperties & ); // n.i.
+
+ const PropertyValue * get( const OUString & rName ) const;
+};
+
+class CachableContentProperties
+{
+private:
+ ContentProperties m_aProps;
+
+ CachableContentProperties & operator=( const CachableContentProperties & ); // n.i.
+ CachableContentProperties( const CachableContentProperties & ); // n.i.
+
+public:
+ explicit CachableContentProperties( const ContentProperties & rProps );
+
+ void addProperties( const ContentProperties & rProps );
+
+ void addProperties( const std::vector< DAVPropertyValue > & rProps );
+
+ bool containsAllNames(
+ const css::uno::Sequence< css::beans::Property >& rProps,
+ std::vector< OUString > & rNamesNotContained ) const
+ { return m_aProps.containsAllNames( rProps, rNamesNotContained ); }
+
+ const css::uno::Any &
+ getValue( const OUString & rName ) const
+ { return m_aProps.getValue( rName ); }
+
+ operator const ContentProperties & () const { return m_aProps; }
+};
+
+} // namespace http_dav_ucp
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/ucb/source/ucp/webdav-curl/CurlSession.cxx b/ucb/source/ucp/webdav-curl/CurlSession.cxx
new file mode 100644
index 0000000000..a54e9d1add
--- /dev/null
+++ b/ucb/source/ucp/webdav-curl/CurlSession.cxx
@@ -0,0 +1,2442 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4; fill-column: 100 -*- */
+/*
+ * 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 "CurlSession.hxx"
+
+#include "SerfLockStore.hxx"
+#include "DAVProperties.hxx"
+#include "UCBDeadPropertyValue.hxx"
+#include "webdavresponseparser.hxx"
+
+#include <comphelper/attributelist.hxx>
+#include <comphelper/lok.hxx>
+#include <comphelper/scopeguard.hxx>
+#include <comphelper/string.hxx>
+
+#include <o3tl/safeint.hxx>
+#include <o3tl/string_view.hxx>
+
+#include <officecfg/Inet.hxx>
+#include <officecfg/Office/Security.hxx>
+
+#include <com/sun/star/beans/NamedValue.hpp>
+#include <com/sun/star/io/Pipe.hpp>
+#include <com/sun/star/io/SequenceInputStream.hpp>
+#include <com/sun/star/io/SequenceOutputStream.hpp>
+#include <com/sun/star/xml/sax/Writer.hpp>
+
+#include <osl/time.h>
+#include <sal/log.hxx>
+#include <rtl/uri.hxx>
+#include <rtl/strbuf.hxx>
+#include <rtl/ustrbuf.hxx>
+#include <systools/curlinit.hxx>
+#include <config_version.h>
+
+#include <map>
+#include <optional>
+#include <tuple>
+#include <utility>
+
+using namespace ::com::sun::star;
+
+namespace
+{
+/// globals container
+struct Init
+{
+ /// note: LockStore has its own mutex and calls CurlSession from its thread
+ /// so don't call LockStore with m_Mutex held to prevent deadlock.
+ ::http_dav_ucp::SerfLockStore LockStore;
+
+ Init()
+ {
+ if (curl_global_init(CURL_GLOBAL_ALL) != CURLE_OK)
+ {
+ assert(!"curl_global_init failed");
+ }
+ }
+ // do not call curl_global_cleanup() - this is not the only client of curl
+};
+Init g_Init;
+
+struct ResponseHeaders
+{
+ ::std::vector<::std::pair<::std::vector<OString>, ::std::optional<long>>> HeaderFields;
+ CURL* pCurl;
+ ResponseHeaders(CURL* const i_pCurl)
+ : pCurl(i_pCurl)
+ {
+ }
+};
+
+auto GetResponseCode(ResponseHeaders const& rHeaders) -> ::std::optional<long>
+{
+ return (rHeaders.HeaderFields.empty()) ? ::std::optional<long>{}
+ : rHeaders.HeaderFields.back().second;
+}
+
+struct DownloadTarget
+{
+ uno::Reference<io::XOutputStream> xOutStream;
+ ResponseHeaders const& rHeaders;
+ DownloadTarget(uno::Reference<io::XOutputStream> i_xOutStream,
+ ResponseHeaders const& i_rHeaders)
+ : xOutStream(std::move(i_xOutStream))
+ , rHeaders(i_rHeaders)
+ {
+ }
+};
+
+struct UploadSource
+{
+ uno::Sequence<sal_Int8> const& rInData;
+ size_t nPosition;
+ UploadSource(uno::Sequence<sal_Int8> const& i_rInData)
+ : rInData(i_rInData)
+ , nPosition(0)
+ {
+ }
+};
+
+auto GetErrorString(CURLcode const rc, char const* const pErrorBuffer = nullptr) -> OString
+{
+ char const* const pMessage( // static fallback
+ (pErrorBuffer && pErrorBuffer[0] != '\0') ? pErrorBuffer : curl_easy_strerror(rc));
+ return OString::Concat("(") + OString::number(sal_Int32(rc)) + ") " + pMessage;
+}
+
+auto GetErrorStringMulti(CURLMcode const mc) -> OString
+{
+ return OString::Concat("(") + OString::number(sal_Int32(mc)) + ") " + curl_multi_strerror(mc);
+}
+
+/// represent an option to be passed to curl_easy_setopt()
+struct CurlOption
+{
+ CURLoption const Option;
+ enum class Type
+ {
+ Pointer,
+ Long,
+ CurlOffT
+ };
+ Type const Tag;
+ union {
+ void const* const pValue;
+ long /*const*/ lValue;
+ curl_off_t /*const*/ cValue;
+ };
+ char const* const pExceptionString;
+
+ CurlOption(CURLoption const i_Option, void const* const i_Value,
+ char const* const i_pExceptionString)
+ : Option(i_Option)
+ , Tag(Type::Pointer)
+ , pValue(i_Value)
+ , pExceptionString(i_pExceptionString)
+ {
+ }
+ // Depending on platform, curl_off_t may be "long" or a larger type
+ // so cannot use overloading to distinguish these cases.
+ CurlOption(CURLoption const i_Option, curl_off_t const i_Value,
+ char const* const i_pExceptionString, Type const type = Type::Long)
+ : Option(i_Option)
+ , Tag(type)
+ , pExceptionString(i_pExceptionString)
+ {
+ static_assert(sizeof(long) <= sizeof(curl_off_t));
+ switch (type)
+ {
+ case Type::Long:
+ lValue = i_Value;
+ break;
+ case Type::CurlOffT:
+ cValue = i_Value;
+ break;
+ default:
+ assert(false);
+ }
+ }
+};
+
+// NOBODY will prevent logging the response body in ProcessRequest() exception
+// handler, so only use it if logging is disabled
+const CurlOption g_NoBody{ CURLOPT_NOBODY,
+ sal_detail_log_report(SAL_DETAIL_LOG_LEVEL_INFO, "ucb.ucp.webdav.curl")
+ == SAL_DETAIL_LOG_ACTION_IGNORE
+ ? 1L
+ : 0L,
+ nullptr };
+
+/// combined guard class to ensure things are released in correct order,
+/// particularly in ProcessRequest() error handling
+class Guard
+{
+private:
+ /// mutex *first* because m_oGuard requires it
+ ::std::unique_lock<::std::mutex> m_Lock;
+ ::std::vector<CurlOption> const m_Options;
+ ::http_dav_ucp::CurlUri const& m_rURI;
+ CURL* const m_pCurl;
+
+public:
+ explicit Guard(::std::mutex& rMutex, ::std::vector<CurlOption> aOptions,
+ ::http_dav_ucp::CurlUri const& rURI, CURL* const pCurl)
+ : m_Lock(rMutex, ::std::defer_lock)
+ , m_Options(std::move(aOptions))
+ , m_rURI(rURI)
+ , m_pCurl(pCurl)
+ {
+ Acquire();
+ }
+ ~Guard()
+ {
+ if (m_Lock.owns_lock())
+ {
+ Release();
+ }
+ }
+
+ void Acquire()
+ {
+ assert(!m_Lock.owns_lock());
+ m_Lock.lock();
+ for (auto const& it : m_Options)
+ {
+ CURLcode rc(CURL_LAST); // warning C4701
+ if (it.Tag == CurlOption::Type::Pointer)
+ {
+ rc = curl_easy_setopt(m_pCurl, it.Option, it.pValue);
+ }
+ else if (it.Tag == CurlOption::Type::Long)
+ {
+ rc = curl_easy_setopt(m_pCurl, it.Option, it.lValue);
+ }
+ else if (it.Tag == CurlOption::Type::CurlOffT)
+ {
+ rc = curl_easy_setopt(m_pCurl, it.Option, it.cValue);
+ }
+ else
+ {
+ assert(false);
+ }
+ if (it.pExceptionString != nullptr)
+ {
+ if (rc != CURLE_OK)
+ {
+ SAL_WARN("ucb.ucp.webdav.curl",
+ "set " << it.pExceptionString << " failed: " << GetErrorString(rc));
+ throw ::http_dav_ucp::DAVException(
+ ::http_dav_ucp::DAVException::DAV_SESSION_CREATE,
+ ::http_dav_ucp::ConnectionEndPointString(m_rURI.GetHost(),
+ m_rURI.GetPort()));
+ }
+ }
+ else // many of the options cannot fail
+ {
+ assert(rc == CURLE_OK);
+ }
+ }
+ }
+ void Release()
+ {
+ assert(m_Lock.owns_lock());
+ for (auto const& it : m_Options)
+ {
+ CURLcode rc(CURL_LAST); // warning C4701
+ if (it.Tag == CurlOption::Type::Pointer)
+ {
+ rc = curl_easy_setopt(m_pCurl, it.Option, nullptr);
+ }
+ else if (it.Tag == CurlOption::Type::Long)
+ {
+ rc = curl_easy_setopt(m_pCurl, it.Option, 0L);
+ }
+ else if (it.Tag == CurlOption::Type::CurlOffT)
+ {
+ rc = curl_easy_setopt(m_pCurl, it.Option, curl_off_t(-1));
+ }
+ else
+ {
+ assert(false);
+ }
+ assert(rc == CURLE_OK);
+ (void)rc;
+ }
+ m_Lock.unlock();
+ }
+};
+
+} // namespace
+
+namespace http_dav_ucp
+{
+// libcurl callbacks:
+
+static int debug_callback(CURL* handle, curl_infotype type, char* data, size_t size,
+ void* /*userdata*/)
+{
+ char const* pType(nullptr);
+ switch (type)
+ {
+ case CURLINFO_TEXT:
+ SAL_INFO("ucb.ucp.webdav.curl", "debug log: " << handle << ": " << data);
+ return 0;
+ case CURLINFO_HEADER_IN:
+ SAL_INFO("ucb.ucp.webdav.curl",
+ "CURLINFO_HEADER_IN: " << handle << ": " << OString(data, size));
+ return 0;
+ case CURLINFO_HEADER_OUT:
+ {
+ // unlike IN, this is all headers in one call
+ OString tmp(data, size);
+ sal_Int32 const start(tmp.indexOf("Authorization: "));
+ if (start != -1)
+ {
+ sal_Int32 const end(tmp.indexOf("\r\n", start));
+ assert(end != -1);
+ sal_Int32 const len(SAL_N_ELEMENTS("Authorization: ") - 1);
+ tmp = tmp.replaceAt(
+ start + len, end - start - len,
+ Concat2View(OString::number(end - start - len) + " bytes redacted"));
+ }
+ SAL_INFO("ucb.ucp.webdav.curl", "CURLINFO_HEADER_OUT: " << handle << ": " << tmp);
+ return 0;
+ }
+ case CURLINFO_DATA_IN:
+ pType = "CURLINFO_DATA_IN";
+ break;
+ case CURLINFO_DATA_OUT:
+ pType = "CURLINFO_DATA_OUT";
+ break;
+ case CURLINFO_SSL_DATA_IN:
+ pType = "CURLINFO_SSL_DATA_IN";
+ break;
+ case CURLINFO_SSL_DATA_OUT:
+ pType = "CURLINFO_SSL_DATA_OUT";
+ break;
+ default:
+ SAL_WARN("ucb.ucp.webdav.curl", "unexpected debug log type");
+ return 0;
+ }
+ SAL_INFO("ucb.ucp.webdav.curl", "debug log: " << handle << ": " << pType << " " << size);
+ return 0;
+}
+
+static size_t write_callback(char* const ptr, size_t const size, size_t const nmemb,
+ void* const userdata)
+{
+ auto* const pTarget(static_cast<DownloadTarget*>(userdata));
+ if (!pTarget) // looks like ~every request may have a response body
+ {
+ return nmemb;
+ }
+ assert(size == 1); // says the man page
+ (void)size;
+ assert(pTarget->xOutStream.is());
+ auto const oResponseCode(GetResponseCode(pTarget->rHeaders));
+ if (!oResponseCode)
+ {
+ return 0; // that is an error
+ }
+ // always write, for exception handler in ProcessRequest()
+ // if (200 <= *oResponseCode && *oResponseCode < 300)
+ {
+ try
+ {
+ uno::Sequence<sal_Int8> const data(reinterpret_cast<sal_Int8*>(ptr), nmemb);
+ pTarget->xOutStream->writeBytes(data);
+ }
+ catch (...)
+ {
+ SAL_WARN("ucb.ucp.webdav.curl", "exception in write_callback");
+ return 0; // error
+ }
+ }
+ // else: ignore the body? CurlSession will check the status eventually
+ return nmemb;
+}
+
+static size_t read_callback(char* const buffer, size_t const size, size_t const nitems,
+ void* const userdata)
+{
+ auto* const pSource(static_cast<UploadSource*>(userdata));
+ assert(pSource);
+ size_t const nBytes(size * nitems);
+ size_t nRet(0);
+ try
+ {
+ assert(pSource->nPosition <= o3tl::make_unsigned(pSource->rInData.getLength()));
+ nRet = ::std::min<size_t>(pSource->rInData.getLength() - pSource->nPosition, nBytes);
+ ::std::memcpy(buffer, pSource->rInData.getConstArray() + pSource->nPosition, nRet);
+ pSource->nPosition += nRet;
+ }
+ catch (...)
+ {
+ SAL_WARN("ucb.ucp.webdav.curl", "exception in read_callback");
+ return CURL_READFUNC_ABORT; // error
+ }
+ return nRet;
+}
+
+static size_t header_callback(char* const buffer, size_t const size, size_t const nitems,
+ void* const userdata)
+{
+ auto* const pHeaders(static_cast<ResponseHeaders*>(userdata));
+ assert(pHeaders);
+#if 0
+ if (!pHeaders) // TODO maybe not needed in every request? not sure
+ {
+ return nitems;
+ }
+#endif
+ assert(size == 1); // says the man page
+ (void)size;
+ try
+ {
+ if (nitems <= 2)
+ {
+ // end of header, body follows...
+ if (pHeaders->HeaderFields.empty())
+ {
+ SAL_WARN("ucb.ucp.webdav.curl", "header_callback: empty header?");
+ return 0; // error
+ }
+ // unfortunately there's no separate callback when the body begins,
+ // so have to manually retrieve the status code here
+ long statusCode(SC_NONE);
+ auto rc = curl_easy_getinfo(pHeaders->pCurl, CURLINFO_RESPONSE_CODE, &statusCode);
+ assert(rc == CURLE_OK);
+ (void)rc;
+ // always put the current response code here - wasn't necessarily in this header
+ pHeaders->HeaderFields.back().second.emplace(statusCode);
+ }
+ else if (buffer[0] == ' ' || buffer[0] == '\t') // folded header field?
+ {
+ size_t i(0);
+ do
+ {
+ ++i;
+ } while (i == ' ' || i == '\t');
+ if (pHeaders->HeaderFields.empty() || pHeaders->HeaderFields.back().second
+ || pHeaders->HeaderFields.back().first.empty())
+ {
+ SAL_WARN("ucb.ucp.webdav.curl",
+ "header_callback: folded header field without start");
+ return 0; // error
+ }
+ pHeaders->HeaderFields.back().first.back()
+ += OString::Concat(" ") + ::std::string_view(&buffer[i], nitems - i);
+ }
+ else
+ {
+ if (pHeaders->HeaderFields.empty() || pHeaders->HeaderFields.back().second)
+ {
+ pHeaders->HeaderFields.emplace_back();
+ }
+ pHeaders->HeaderFields.back().first.emplace_back(OString(buffer, nitems));
+ }
+ }
+ catch (...)
+ {
+ SAL_WARN("ucb.ucp.webdav.curl", "exception in header_callback");
+ return 0; // error
+ }
+ return nitems;
+}
+
+static auto ProcessHeaders(::std::vector<OString> const& rHeaders) -> ::std::map<OUString, OUString>
+{
+ ::std::map<OUString, OUString> ret;
+ for (OString const& rLine : rHeaders)
+ {
+ OString line;
+ if (!rLine.endsWith("\r\n", &line))
+ {
+ SAL_WARN("ucb.ucp.webdav.curl", "invalid header field (no CRLF)");
+ continue;
+ }
+ if (line.startsWith("HTTP/") // first line
+ || line.isEmpty()) // last line
+ {
+ continue;
+ }
+ auto const nColon(line.indexOf(':'));
+ if (nColon == -1)
+ {
+ {
+ SAL_WARN("ucb.ucp.webdav.curl", "invalid header field (no :)");
+ }
+ continue;
+ }
+ if (nColon == 0)
+ {
+ SAL_WARN("ucb.ucp.webdav.curl", "invalid header field (empty name)");
+ continue;
+ }
+ // case insensitive; must be ASCII
+ auto const name(::rtl::OStringToOUString(line.copy(0, nColon).toAsciiLowerCase(),
+ RTL_TEXTENCODING_ASCII_US));
+ sal_Int32 nStart(nColon + 1);
+ while (nStart < line.getLength() && (line[nStart] == ' ' || line[nStart] == '\t'))
+ {
+ ++nStart;
+ }
+ sal_Int32 nEnd(line.getLength());
+ while (nStart < nEnd && (line[nEnd - 1] == ' ' || line[nEnd - 1] == '\t'))
+ {
+ --nEnd;
+ }
+ // RFC 7230 says that only ASCII works reliably anyway (neon also did this)
+ auto const value(::rtl::OStringToOUString(line.subView(nStart, nEnd - nStart),
+ RTL_TEXTENCODING_ASCII_US));
+ auto const it(ret.find(name));
+ if (it != ret.end())
+ {
+ it->second = it->second + "," + value;
+ }
+ else
+ {
+ ret[name] = value;
+ }
+ }
+ return ret;
+}
+
+static auto ExtractRequestedHeaders(
+ ResponseHeaders const& rHeaders,
+ ::std::pair<::std::vector<OUString> const&, DAVResource&> const* const pRequestedHeaders)
+ -> void
+{
+ ::std::map<OUString, OUString> const headerMap(
+ ProcessHeaders(rHeaders.HeaderFields.back().first));
+ if (pRequestedHeaders)
+ {
+ for (OUString const& rHeader : pRequestedHeaders->first)
+ {
+ auto const it(headerMap.find(rHeader.toAsciiLowerCase()));
+ if (it != headerMap.end())
+ {
+ DAVPropertyValue value;
+ value.IsCaseSensitive = false;
+ value.Name = it->first;
+ value.Value <<= it->second;
+ pRequestedHeaders->second.properties.push_back(value);
+ }
+ }
+ }
+}
+
+// this appears to be the only way to get the "realm" from libcurl
+static auto ExtractRealm(ResponseHeaders const& rHeaders, char const* const pAuthHeaderName)
+ -> ::std::optional<OUString>
+{
+ ::std::map<OUString, OUString> const headerMap(
+ ProcessHeaders(rHeaders.HeaderFields.back().first));
+ auto const it(headerMap.find(OUString::createFromAscii(pAuthHeaderName).toAsciiLowerCase()));
+ if (it == headerMap.end())
+ {
+ SAL_WARN("ucb.ucp.webdav.curl", "cannot find auth header");
+ return {};
+ }
+ // It may be possible that the header contains multiple methods each with
+ // a different realm - extract only the first one bc the downstream API
+ // only supports one anyway.
+ // case insensitive!
+ auto i(it->second.toAsciiLowerCase().indexOf("realm="));
+ // is optional
+ if (i == -1)
+ {
+ SAL_INFO("ucb.ucp.webdav.curl", "auth header has no realm");
+ return {};
+ }
+ // no whitespace allowed before or after =
+ i += ::std::strlen("realm=");
+ if (it->second.getLength() < i + 2 || it->second[i] != '\"')
+ {
+ SAL_WARN("ucb.ucp.webdav.curl", "no realm value");
+ return {};
+ }
+ ++i;
+ OUStringBuffer buf;
+ while (i < it->second.getLength() && it->second[i] != '\"')
+ {
+ if (it->second[i] == '\\') // quoted-pair escape
+ {
+ ++i;
+ if (it->second.getLength() <= i)
+ {
+ SAL_WARN("ucb.ucp.webdav.curl", "unterminated quoted-pair");
+ return {};
+ }
+ }
+ buf.append(it->second[i]);
+ ++i;
+ }
+ if (it->second.getLength() <= i)
+ {
+ SAL_WARN("ucb.ucp.webdav.curl", "unterminated realm");
+ return {};
+ }
+ return buf.makeStringAndClear();
+}
+
+CurlSession::CurlSession(uno::Reference<uno::XComponentContext> xContext,
+ ::rtl::Reference<DAVSessionFactory> const& rpFactory, OUString const& rURI,
+ uno::Sequence<beans::NamedValue> const& rFlags,
+ ::ucbhelper::InternetProxyDecider const& rProxyDecider)
+ : DAVSession(rpFactory)
+ , m_xContext(std::move(xContext))
+ , m_Flags(rFlags)
+ , m_URI(rURI)
+ , m_Proxy(rProxyDecider.getProxy(m_URI.GetScheme(), m_URI.GetHost(), m_URI.GetPort()))
+{
+ assert(m_URI.GetScheme() == "http" || m_URI.GetScheme() == "https");
+ m_pCurlMulti.reset(curl_multi_init());
+ if (!m_pCurlMulti)
+ {
+ SAL_WARN("ucb.ucp.webdav.curl", "curl_multi_init failed");
+ throw DAVException(DAVException::DAV_SESSION_CREATE,
+ ConnectionEndPointString(m_URI.GetHost(), m_URI.GetPort()));
+ }
+ m_pCurl.reset(curl_easy_init());
+ if (!m_pCurl)
+ {
+ SAL_WARN("ucb.ucp.webdav.curl", "curl_easy_init failed");
+ throw DAVException(DAVException::DAV_SESSION_CREATE,
+ ConnectionEndPointString(m_URI.GetHost(), m_URI.GetPort()));
+ }
+ m_ErrorBuffer[0] = '\0';
+ // this supposedly gives the highest quality error reporting
+ auto rc = curl_easy_setopt(m_pCurl.get(), CURLOPT_ERRORBUFFER, m_ErrorBuffer);
+ assert(rc == CURLE_OK);
+#if 1
+ // just for debugging...
+ rc = curl_easy_setopt(m_pCurl.get(), CURLOPT_DEBUGFUNCTION, debug_callback);
+ assert(rc == CURLE_OK);
+#endif
+ rc = curl_easy_setopt(m_pCurl.get(), CURLOPT_VERBOSE, 1L);
+ assert(rc == CURLE_OK);
+ // accept any encoding supported by libcurl
+ rc = curl_easy_setopt(m_pCurl.get(), CURLOPT_ACCEPT_ENCODING, "");
+ if (rc != CURLE_OK)
+ {
+ SAL_WARN("ucb.ucp.webdav.curl", "CURLOPT_ACCEPT_ENCODING failed: " << GetErrorString(rc));
+ throw DAVException(DAVException::DAV_SESSION_CREATE,
+ ConnectionEndPointString(m_URI.GetHost(), m_URI.GetPort()));
+ }
+ auto const connectTimeout(officecfg::Inet::Settings::ConnectTimeout::get());
+ // default is 300s
+ rc = curl_easy_setopt(m_pCurl.get(), CURLOPT_CONNECTTIMEOUT,
+ ::std::max<long>(2L, ::std::min<long>(connectTimeout, 180L)));
+ if (rc != CURLE_OK)
+ {
+ SAL_WARN("ucb.ucp.webdav.curl", "CURLOPT_CONNECTTIMEOUT failed: " << GetErrorString(rc));
+ throw DAVException(DAVException::DAV_SESSION_CREATE,
+ ConnectionEndPointString(m_URI.GetHost(), m_URI.GetPort()));
+ }
+ auto const readTimeout(officecfg::Inet::Settings::ReadTimeout::get());
+ m_nReadTimeout = ::std::max<int>(20, ::std::min<long>(readTimeout, 180)) * 1000;
+ // default is infinite
+ rc = curl_easy_setopt(m_pCurl.get(), CURLOPT_TIMEOUT, 300L);
+ if (rc != CURLE_OK)
+ {
+ SAL_WARN("ucb.ucp.webdav.curl", "CURLOPT_TIMEOUT failed: " << GetErrorString(rc));
+ throw DAVException(DAVException::DAV_SESSION_CREATE,
+ ConnectionEndPointString(m_URI.GetHost(), m_URI.GetPort()));
+ }
+ rc = curl_easy_setopt(m_pCurl.get(), CURLOPT_WRITEFUNCTION, &write_callback);
+ assert(rc == CURLE_OK);
+ rc = curl_easy_setopt(m_pCurl.get(), CURLOPT_READFUNCTION, &read_callback);
+ assert(rc == CURLE_OK);
+ rc = curl_easy_setopt(m_pCurl.get(), CURLOPT_HEADERFUNCTION, &header_callback);
+ assert(rc == CURLE_OK);
+ ::InitCurl_easy(m_pCurl.get());
+ if (officecfg::Office::Security::Net::AllowInsecureProtocols::get())
+ {
+ // tdf#149921 by default, with schannel (WNT) connection fails if revocation
+ // lists cannot be checked; try to limit the checking to when revocation
+ // lists can actually be retrieved (usually not the case for self-signed CA)
+#if CURL_AT_LEAST_VERSION(7, 70, 0)
+ rc = curl_easy_setopt(m_pCurl.get(), CURLOPT_SSL_OPTIONS, CURLSSLOPT_REVOKE_BEST_EFFORT);
+ assert(rc == CURLE_OK);
+ rc = curl_easy_setopt(m_pCurl.get(), CURLOPT_PROXY_SSL_OPTIONS,
+ CURLSSLOPT_REVOKE_BEST_EFFORT);
+ assert(rc == CURLE_OK);
+#endif
+ }
+ // set this initially, may be overwritten during authentication
+ rc = curl_easy_setopt(m_pCurl.get(), CURLOPT_HTTPAUTH, CURLAUTH_ANY);
+ assert(rc == CURLE_OK); // ANY is always available
+ // always set CURLOPT_PROXY to suppress proxy detection in libcurl
+ OString const utf8Proxy(OUStringToOString(m_Proxy, RTL_TEXTENCODING_UTF8));
+ rc = curl_easy_setopt(m_pCurl.get(), CURLOPT_PROXY, utf8Proxy.getStr());
+ if (rc != CURLE_OK)
+ {
+ SAL_WARN("ucb.ucp.webdav.curl", "CURLOPT_PROXY failed: " << GetErrorString(rc));
+ throw DAVException(DAVException::DAV_SESSION_CREATE, m_Proxy);
+ }
+ if (!m_Proxy.isEmpty())
+ {
+ // set this initially, may be overwritten during authentication
+ rc = curl_easy_setopt(m_pCurl.get(), CURLOPT_PROXYAUTH, CURLAUTH_ANY);
+ assert(rc == CURLE_OK); // ANY is always available
+ }
+ auto const it(::std::find_if(m_Flags.begin(), m_Flags.end(),
+ [](auto const& rFlag) { return rFlag.Name == "KeepAlive"; }));
+ if (it != m_Flags.end() && it->Value.get<bool>())
+ {
+ // neon would close the connection from ne_end_request(), this seems
+ // to be the equivalent and not CURLOPT_TCP_KEEPALIVE
+ rc = curl_easy_setopt(m_pCurl.get(), CURLOPT_FORBID_REUSE, 1L);
+ assert(rc == CURLE_OK);
+ }
+ // If WOPI-like host has self-signed certificate, it's not possible to insert images
+ // to the document, so here is a compromise. The user has already accepted the self
+ // signed certificate in the browser, when we get here.
+ if (comphelper::LibreOfficeKit::isActive())
+ {
+ rc = curl_easy_setopt(m_pCurl.get(), CURLOPT_SSL_VERIFYPEER, 0L);
+ assert(rc == CURLE_OK);
+ }
+}
+
+CurlSession::~CurlSession() {}
+
+auto CurlSession::CanUse(OUString const& rURI, uno::Sequence<beans::NamedValue> const& rFlags)
+ -> bool
+{
+ try
+ {
+ CurlUri const uri(rURI);
+
+ return m_URI.GetScheme() == uri.GetScheme() && m_URI.GetHost() == uri.GetHost()
+ && m_URI.GetPort() == uri.GetPort() && m_Flags == rFlags;
+ }
+ catch (DAVException const&)
+ {
+ return false;
+ }
+}
+
+auto CurlSession::UsesProxy() -> bool
+{
+ assert(m_URI.GetScheme() == "http" || m_URI.GetScheme() == "https");
+ return !m_Proxy.isEmpty();
+}
+
+auto CurlSession::abort() -> void
+{
+ // note: abort() was a no-op since OOo 3.2 and before that it crashed.
+ bool expected(false);
+ // it would be pointless to lock m_Mutex here as the other thread holds it
+ if (m_AbortFlag.compare_exchange_strong(expected, true))
+ {
+ // This function looks safe to call without m_Mutex as long as the
+ // m_pCurlMulti handle is not destroyed, and the caller must own a ref
+ // to this object which keeps it alive; it should cause poll to return.
+ curl_multi_wakeup(m_pCurlMulti.get());
+ }
+}
+
+/// this is just a bunch of static member functions called from CurlSession
+struct CurlProcessor
+{
+ static auto URIReferenceToURI(CurlSession& rSession, std::u16string_view rURIReference)
+ -> CurlUri;
+
+ static auto ProcessRequestImpl(
+ CurlSession& rSession, CurlUri const& rURI, OUString const& rMethod,
+ curl_slist* pRequestHeaderList, uno::Reference<io::XOutputStream> const* pxOutStream,
+ uno::Sequence<sal_Int8> const* pInData,
+ ::std::pair<::std::vector<OUString> const&, DAVResource&> const* pRequestedHeaders,
+ ResponseHeaders& rHeaders) -> void;
+
+ static auto ProcessRequest(
+ CurlSession& rSession, CurlUri const& rURI, OUString const& rMethod,
+ ::std::vector<CurlOption> const& rOptions, DAVRequestEnvironment const* pEnv,
+ ::std::unique_ptr<curl_slist, deleter_from_fn<curl_slist, curl_slist_free_all>>
+ pRequestHeaderList,
+ uno::Reference<io::XOutputStream> const* pxOutStream,
+ uno::Reference<io::XInputStream> const* pxInStream,
+ ::std::pair<::std::vector<OUString> const&, DAVResource&> const* pRequestedHeaders) -> void;
+
+ static auto
+ PropFind(CurlSession& rSession, CurlUri const& rURI, Depth depth,
+ ::std::tuple<::std::vector<OUString> const&, ::std::vector<DAVResource>* const,
+ ::std::vector<ucb::Lock>* const> const* o_pRequestedProperties,
+ ::std::vector<DAVResourceInfo>* const o_pResourceInfos,
+ DAVRequestEnvironment const& rEnv) -> void;
+
+ static auto MoveOrCopy(CurlSession& rSession, std::u16string_view rSourceURIReference,
+ ::std::u16string_view rDestinationURI, DAVRequestEnvironment const& rEnv,
+ bool isOverwrite, char const* pMethod) -> void;
+
+ static auto Lock(CurlSession& rSession, CurlUri const& rURI, DAVRequestEnvironment const* pEnv,
+ ::std::unique_ptr<curl_slist, deleter_from_fn<curl_slist, curl_slist_free_all>>
+ pRequestHeaderList,
+ uno::Reference<io::XInputStream> const* pxInStream)
+ -> ::std::vector<::std::pair<ucb::Lock, sal_Int32>>;
+
+ static auto Unlock(CurlSession& rSession, CurlUri const& rURI,
+ DAVRequestEnvironment const* pEnv) -> void;
+};
+
+auto CurlProcessor::URIReferenceToURI(CurlSession& rSession, std::u16string_view rURIReference)
+ -> CurlUri
+{
+ // No need to acquire rSession.m_Mutex because accessed members are const.
+ if (rSession.UsesProxy())
+ // very odd, but see DAVResourceAccess::getRequestURI() :-/
+ {
+ assert(o3tl::starts_with(rURIReference, u"http://")
+ || o3tl::starts_with(rURIReference, u"https://"));
+ return CurlUri(rURIReference);
+ }
+ else
+ {
+ assert(o3tl::starts_with(rURIReference, u"/"));
+ return rSession.m_URI.CloneWithRelativeRefPathAbsolute(rURIReference);
+ }
+}
+
+/// main function to initiate libcurl requests
+auto CurlProcessor::ProcessRequestImpl(
+ CurlSession& rSession, CurlUri const& rURI, OUString const& rMethod,
+ curl_slist* const pRequestHeaderList,
+ uno::Reference<io::XOutputStream> const* const pxOutStream,
+ uno::Sequence<sal_Int8> const* const pInData,
+ ::std::pair<::std::vector<OUString> const&, DAVResource&> const* const pRequestedHeaders,
+ ResponseHeaders& rHeaders) -> void
+{
+ ::comphelper::ScopeGuard const g([&]() {
+ auto rc = curl_easy_setopt(rSession.m_pCurl.get(), CURLOPT_HEADERDATA, nullptr);
+ assert(rc == CURLE_OK);
+ (void)rc;
+ if (pxOutStream)
+ {
+ rc = curl_easy_setopt(rSession.m_pCurl.get(), CURLOPT_WRITEDATA, nullptr);
+ assert(rc == CURLE_OK);
+ }
+ if (pInData)
+ {
+ rc = curl_easy_setopt(rSession.m_pCurl.get(), CURLOPT_READDATA, nullptr);
+ assert(rc == CURLE_OK);
+ rc = curl_easy_setopt(rSession.m_pCurl.get(), CURLOPT_UPLOAD, 0L);
+ assert(rc == CURLE_OK);
+ }
+ if (pRequestHeaderList)
+ {
+ rc = curl_easy_setopt(rSession.m_pCurl.get(), CURLOPT_HTTPHEADER, nullptr);
+ assert(rc == CURLE_OK);
+ }
+ });
+
+ if (pRequestHeaderList)
+ {
+ auto rc = curl_easy_setopt(rSession.m_pCurl.get(), CURLOPT_HTTPHEADER, pRequestHeaderList);
+ assert(rc == CURLE_OK);
+ (void)rc;
+ }
+
+ auto rc = curl_easy_setopt(rSession.m_pCurl.get(), CURLOPT_CURLU, rURI.GetCURLU());
+ assert(rc == CURLE_OK); // can't fail since 7.63.0
+
+ rc = curl_easy_setopt(rSession.m_pCurl.get(), CURLOPT_HEADERDATA, &rHeaders);
+ assert(rc == CURLE_OK);
+ ::std::optional<DownloadTarget> oDownloadTarget;
+ if (pxOutStream)
+ {
+ oDownloadTarget.emplace(*pxOutStream, rHeaders);
+ rc = curl_easy_setopt(rSession.m_pCurl.get(), CURLOPT_WRITEDATA, &*oDownloadTarget);
+ assert(rc == CURLE_OK);
+ }
+ ::std::optional<UploadSource> oUploadSource;
+ if (pInData)
+ {
+ oUploadSource.emplace(*pInData);
+ rc = curl_easy_setopt(rSession.m_pCurl.get(), CURLOPT_READDATA, &*oUploadSource);
+ assert(rc == CURLE_OK);
+ }
+ rSession.m_ErrorBuffer[0] = '\0';
+
+ // note: easy handle must be added for *every* transfer!
+ // otherwise it gets stuck in MSTATE_MSGSENT forever after 1st transfer
+ auto mc = curl_multi_add_handle(rSession.m_pCurlMulti.get(), rSession.m_pCurl.get());
+ if (mc != CURLM_OK)
+ {
+ SAL_WARN("ucb.ucp.webdav.curl",
+ "curl_multi_add_handle failed: " << GetErrorStringMulti(mc));
+ throw DAVException(
+ DAVException::DAV_SESSION_CREATE,
+ ConnectionEndPointString(rSession.m_URI.GetHost(), rSession.m_URI.GetPort()));
+ }
+ ::comphelper::ScopeGuard const gg([&]() {
+ mc = curl_multi_remove_handle(rSession.m_pCurlMulti.get(), rSession.m_pCurl.get());
+ if (mc != CURLM_OK)
+ {
+ SAL_WARN("ucb.ucp.webdav.curl",
+ "curl_multi_remove_handle failed: " << GetErrorStringMulti(mc));
+ }
+ });
+
+ // this is where libcurl actually does something
+ rc = CURL_LAST; // clear current value
+ int nRunning;
+ do
+ {
+ mc = curl_multi_perform(rSession.m_pCurlMulti.get(), &nRunning);
+ if (mc != CURLM_OK)
+ {
+ SAL_WARN("ucb.ucp.webdav.curl",
+ "curl_multi_perform failed: " << GetErrorStringMulti(mc));
+ throw DAVException(
+ DAVException::DAV_HTTP_CONNECT,
+ ConnectionEndPointString(rSession.m_URI.GetHost(), rSession.m_URI.GetPort()));
+ }
+ if (nRunning == 0)
+ { // short request like HEAD on loopback could be done in first call
+ break;
+ }
+ int nFDs;
+ mc = curl_multi_poll(rSession.m_pCurlMulti.get(), nullptr, 0, rSession.m_nReadTimeout,
+ &nFDs);
+ if (mc != CURLM_OK)
+ {
+ SAL_WARN("ucb.ucp.webdav.curl", "curl_multi_poll failed: " << GetErrorStringMulti(mc));
+ throw DAVException(
+ DAVException::DAV_HTTP_CONNECT,
+ ConnectionEndPointString(rSession.m_URI.GetHost(), rSession.m_URI.GetPort()));
+ }
+ if (rSession.m_AbortFlag.load())
+ { // flag was set by abort() -> not sure what exception to throw?
+ throw DAVException(DAVException::DAV_HTTP_ERROR, "abort() was called", 0);
+ }
+ } while (nRunning != 0);
+ // there should be exactly 1 CURLMsg now, but the interface is
+ // extensible so future libcurl versions could yield additional things
+ do
+ {
+ CURLMsg const* const pMsg = curl_multi_info_read(rSession.m_pCurlMulti.get(), &nRunning);
+ if (pMsg && pMsg->msg == CURLMSG_DONE)
+ {
+ assert(pMsg->easy_handle == rSession.m_pCurl.get());
+ rc = pMsg->data.result;
+ }
+ else
+ {
+ SAL_WARN("ucb.ucp.webdav.curl", "curl_multi_info_read unexpected result");
+ }
+ } while (nRunning != 0);
+
+ // error handling part 1: libcurl errors
+ if (rc != CURLE_OK)
+ {
+ // TODO: is there any value in extracting CURLINFO_OS_ERRNO
+ SAL_WARN("ucb.ucp.webdav.curl",
+ "curl_easy_perform failed: " << GetErrorString(rc, rSession.m_ErrorBuffer));
+ switch (rc)
+ {
+ case CURLE_UNSUPPORTED_PROTOCOL:
+ throw DAVException(DAVException::DAV_UNSUPPORTED);
+ case CURLE_COULDNT_RESOLVE_PROXY:
+ throw DAVException(DAVException::DAV_HTTP_LOOKUP, rSession.m_Proxy);
+ case CURLE_COULDNT_RESOLVE_HOST:
+ throw DAVException(
+ DAVException::DAV_HTTP_LOOKUP,
+ ConnectionEndPointString(rSession.m_URI.GetHost(), rSession.m_URI.GetPort()));
+ case CURLE_COULDNT_CONNECT:
+ case CURLE_SSL_CONNECT_ERROR:
+ case CURLE_SSL_CERTPROBLEM:
+ case CURLE_SSL_CIPHER:
+ case CURLE_PEER_FAILED_VERIFICATION:
+ case CURLE_SSL_ISSUER_ERROR:
+ case CURLE_SSL_PINNEDPUBKEYNOTMATCH:
+ case CURLE_SSL_INVALIDCERTSTATUS:
+ case CURLE_FAILED_INIT:
+#if CURL_AT_LEAST_VERSION(7, 69, 0)
+ case CURLE_QUIC_CONNECT_ERROR:
+#endif
+ throw DAVException(
+ DAVException::DAV_HTTP_CONNECT,
+ ConnectionEndPointString(rSession.m_URI.GetHost(), rSession.m_URI.GetPort()));
+ case CURLE_REMOTE_ACCESS_DENIED:
+ case CURLE_LOGIN_DENIED:
+ case CURLE_AUTH_ERROR:
+ throw DAVException(
+ DAVException::DAV_HTTP_AUTH, // probably?
+ ConnectionEndPointString(rSession.m_URI.GetHost(), rSession.m_URI.GetPort()));
+ case CURLE_WRITE_ERROR:
+ case CURLE_READ_ERROR: // error returned from our callbacks
+ case CURLE_OUT_OF_MEMORY:
+ case CURLE_ABORTED_BY_CALLBACK:
+ case CURLE_BAD_FUNCTION_ARGUMENT:
+ case CURLE_SEND_ERROR:
+ case CURLE_RECV_ERROR:
+ case CURLE_SSL_CACERT_BADFILE:
+ case CURLE_SSL_CRL_BADFILE:
+ case CURLE_RECURSIVE_API_CALL:
+ throw DAVException(
+ DAVException::DAV_HTTP_FAILED,
+ ConnectionEndPointString(rSession.m_URI.GetHost(), rSession.m_URI.GetPort()));
+ case CURLE_OPERATION_TIMEDOUT:
+ throw DAVException(
+ DAVException::DAV_HTTP_TIMEOUT,
+ ConnectionEndPointString(rSession.m_URI.GetHost(), rSession.m_URI.GetPort()));
+ default: // lots of generic errors
+ throw DAVException(DAVException::DAV_HTTP_ERROR, "", 0);
+ }
+ }
+ // error handling part 2: HTTP status codes
+ long statusCode(SC_NONE);
+ rc = curl_easy_getinfo(rSession.m_pCurl.get(), CURLINFO_RESPONSE_CODE, &statusCode);
+ assert(rc == CURLE_OK);
+ assert(statusCode != SC_NONE); // ??? should be error returned from perform?
+ SAL_INFO("ucb.ucp.webdav.curl", "HTTP status code: " << statusCode);
+ if (statusCode < 300)
+ {
+ // neon did this regardless of status or even error, which seems odd
+ ExtractRequestedHeaders(rHeaders, pRequestedHeaders);
+ }
+ else
+ {
+ // create message containing the HTTP method and response status line
+ OUString statusLine("\n" + rMethod + "\n=>\n");
+ if (!rHeaders.HeaderFields.empty() && !rHeaders.HeaderFields.back().first.empty()
+ && rHeaders.HeaderFields.back().first.front().startsWith("HTTP"))
+ {
+ statusLine += ::rtl::OStringToOUString(
+ ::o3tl::trim(rHeaders.HeaderFields.back().first.front()),
+ RTL_TEXTENCODING_ASCII_US);
+ }
+ switch (statusCode)
+ {
+ case SC_REQUEST_TIMEOUT:
+ {
+ throw DAVException(
+ DAVException::DAV_HTTP_TIMEOUT,
+ ConnectionEndPointString(rSession.m_URI.GetHost(), rSession.m_URI.GetPort()));
+ break;
+ }
+ case SC_MOVED_PERMANENTLY:
+ case SC_MOVED_TEMPORARILY:
+ case SC_SEE_OTHER:
+ case SC_TEMPORARY_REDIRECT:
+ {
+ // could also use CURLOPT_FOLLOWLOCATION but apparently the
+ // upper layer wants to know about redirects?
+ char* pRedirectURL(nullptr);
+ rc = curl_easy_getinfo(rSession.m_pCurl.get(), CURLINFO_REDIRECT_URL,
+ &pRedirectURL);
+ assert(rc == CURLE_OK);
+ if (pRedirectURL)
+ {
+ // Sharepoint 2016 workaround: contains unencoded U+0020
+ OUString const redirectURL(::rtl::Uri::encode(
+ pRedirectURL
+ ? OUString(pRedirectURL, strlen(pRedirectURL), RTL_TEXTENCODING_UTF8)
+ : OUString(),
+ rtl_UriCharClassUric, rtl_UriEncodeKeepEscapes, RTL_TEXTENCODING_UTF8));
+
+ throw DAVException(DAVException::DAV_HTTP_REDIRECT, redirectURL);
+ }
+ [[fallthrough]];
+ }
+ default:
+ throw DAVException(DAVException::DAV_HTTP_ERROR, statusLine, statusCode);
+ }
+ }
+
+ if (pxOutStream)
+ {
+ (*pxOutStream)->closeOutput(); // signal EOF
+ }
+}
+
+static auto TryRemoveExpiredLockToken(CurlSession& rSession, CurlUri const& rURI,
+ DAVRequestEnvironment const* const pEnv) -> bool
+{
+ if (!pEnv)
+ {
+ // caller was a NonInteractive_*LOCK function anyway, its caller is LockStore
+ return false;
+ }
+ OUString const* const pToken(g_Init.LockStore.getLockTokenForURI(rURI.GetURI(), nullptr));
+ if (!pToken)
+ {
+ return false;
+ }
+ try
+ {
+ // determine validity of existing lock via lockdiscovery request
+ ::std::vector<OUString> const propertyNames{ DAVProperties::LOCKDISCOVERY };
+ ::std::vector<ucb::Lock> locks;
+ ::std::tuple<::std::vector<OUString> const&, ::std::vector<DAVResource>* const,
+ ::std::vector<ucb::Lock>* const> const args(propertyNames, nullptr, &locks);
+
+ CurlProcessor::PropFind(rSession, rURI, DAVZERO, &args, nullptr, *pEnv);
+
+ // https://datatracker.ietf.org/doc/html/rfc4918#section-15.8
+ // The response MAY not contain tokens, but hopefully it
+ // will if client is properly authenticated.
+ if (::std::any_of(locks.begin(), locks.end(), [pToken](ucb::Lock const& rLock) {
+ return ::std::any_of(
+ rLock.LockTokens.begin(), rLock.LockTokens.end(),
+ [pToken](OUString const& rToken) { return *pToken == rToken; });
+ }))
+ {
+ return false; // still have the lock
+ }
+
+ SAL_INFO("ucb.ucp.webdav.curl",
+ "lock token expired, removing: " << rURI.GetURI() << " " << *pToken);
+ g_Init.LockStore.removeLock(rURI.GetURI());
+ return true;
+ }
+ catch (DAVException const&)
+ {
+ return false; // ignore, the caller already has a better exception
+ }
+}
+
+auto CurlProcessor::ProcessRequest(
+ CurlSession& rSession, CurlUri const& rURI, OUString const& rMethod,
+ ::std::vector<CurlOption> const& rOptions, DAVRequestEnvironment const* const pEnv,
+ ::std::unique_ptr<curl_slist, deleter_from_fn<curl_slist, curl_slist_free_all>>
+ pRequestHeaderList,
+ uno::Reference<io::XOutputStream> const* const pxOutStream,
+ uno::Reference<io::XInputStream> const* const pxInStream,
+ ::std::pair<::std::vector<OUString> const&, DAVResource&> const* const pRequestedHeaders)
+ -> void
+{
+ if (pEnv)
+ { // add custom request headers passed by caller
+ for (auto const& rHeader : pEnv->m_aRequestHeaders)
+ {
+ OString const utf8Header(
+ OUStringToOString(rHeader.first, RTL_TEXTENCODING_ASCII_US) + ": "
+ + OUStringToOString(rHeader.second, RTL_TEXTENCODING_ASCII_US));
+ pRequestHeaderList.reset(
+ curl_slist_append(pRequestHeaderList.release(), utf8Header.getStr()));
+ if (!pRequestHeaderList)
+ {
+ throw uno::RuntimeException("curl_slist_append failed");
+ }
+ }
+ }
+
+ uno::Sequence<sal_Int8> data;
+ if (pxInStream)
+ {
+ uno::Reference<io::XSeekable> const xSeekable(*pxInStream, uno::UNO_QUERY);
+ if (xSeekable.is())
+ {
+ auto const len(xSeekable->getLength() - xSeekable->getPosition());
+ if ((**pxInStream).readBytes(data, len) != len)
+ {
+ throw uno::RuntimeException("short readBytes");
+ }
+ }
+ else
+ {
+ ::std::vector<uno::Sequence<sal_Int8>> bufs;
+ bool isDone(false);
+ do
+ {
+ bufs.emplace_back();
+ isDone = (**pxInStream).readSomeBytes(bufs.back(), 65536) == 0;
+ } while (!isDone);
+ sal_Int32 nSize(0);
+ for (auto const& rBuf : bufs)
+ {
+ if (o3tl::checked_add(nSize, rBuf.getLength(), nSize))
+ {
+ throw std::bad_alloc(); // too large for Sequence
+ }
+ }
+ data.realloc(nSize);
+ size_t nCopied(0);
+ for (auto const& rBuf : bufs)
+ {
+ ::std::memcpy(data.getArray() + nCopied, rBuf.getConstArray(), rBuf.getLength());
+ nCopied += rBuf.getLength(); // can't overflow
+ }
+ }
+ }
+
+ // Clear flag before transfer starts; only a transfer started before
+ // calling abort() will be aborted, not one started later.
+ rSession.m_AbortFlag.store(false);
+
+ Guard guard(rSession.m_Mutex, rOptions, rURI, rSession.m_pCurl.get());
+
+ // authentication data may be in the URI, or requested via XInteractionHandler
+ struct Auth
+ {
+ OUString UserName;
+ OUString PassWord;
+ decltype(CURLAUTH_ANY) AuthMask; ///< allowed auth methods
+ Auth(OUString aUserName, OUString aPassword, decltype(CURLAUTH_ANY) const & rAuthMask)
+ : UserName(std::move(aUserName))
+ , PassWord(std::move(aPassword))
+ , AuthMask(rAuthMask)
+ {
+ }
+ };
+ ::std::optional<Auth> oAuth;
+ ::std::optional<Auth> oAuthProxy;
+ if (pEnv && !rSession.m_isAuthenticatedProxy && !rSession.m_Proxy.isEmpty())
+ {
+ try
+ {
+ // the hope is that this must be a URI
+ CurlUri const uri(rSession.m_Proxy);
+ if (!uri.GetUser().isEmpty() || !uri.GetPassword().isEmpty())
+ {
+ oAuthProxy.emplace(uri.GetUser(), uri.GetPassword(), CURLAUTH_ANY);
+ }
+ }
+ catch (DAVException&)
+ {
+ // ignore any parsing failure here
+ }
+ }
+ decltype(CURLAUTH_ANY) const authSystem(CURLAUTH_NEGOTIATE | CURLAUTH_NTLM | CURLAUTH_NTLM_WB);
+ if (pRequestedHeaders || (pEnv && !rSession.m_isAuthenticated))
+ {
+ // m_aRequestURI *may* be a path or *may* be URI - wtf
+ // TODO: why is there this m_aRequestURI and also rURIReference argument?
+ // ... only caller is DAVResourceAccess - always identical except MOVE/COPY
+ // which doesn't work if it's just a URI reference so let's just use
+ // rURIReference via rURI instead
+#if 0
+ CurlUri const uri(pEnv->m_aRequestURI);
+#endif
+ // note: due to parsing bug pwd didn't work in previous webdav ucps
+ if (pEnv && !rSession.m_isAuthenticated
+ && (!rURI.GetUser().isEmpty() || !rURI.GetPassword().isEmpty()))
+ {
+ oAuth.emplace(rURI.GetUser(), rURI.GetPassword(), CURLAUTH_ANY);
+ }
+ if (pRequestedHeaders)
+ {
+ // note: Previously this would be the rURIReference directly but
+ // that ends up in CurlUri anyway and curl is unhappy.
+ // But it looks like all consumers of this .uri are interested
+ // only in the path, so it shouldn't make a difference to give
+ // the entire URI when the caller extracts the path anyway.
+ pRequestedHeaders->second.uri = rURI.GetURI();
+ pRequestedHeaders->second.properties.clear();
+ }
+ }
+ bool isRetry(false);
+ bool isFallbackHTTP10(false);
+ int nAuthRequests(0);
+ int nAuthRequestsProxy(0);
+
+ // libcurl does not have an authentication callback so handle auth
+ // related status codes and requesting credentials via this loop
+ do
+ {
+ isRetry = false;
+
+ // re-check m_isAuthenticated flags every time, could have been set
+ // by re-entrant call
+ if (oAuth && !rSession.m_isAuthenticated)
+ {
+ OString const utf8UserName(OUStringToOString(oAuth->UserName, RTL_TEXTENCODING_UTF8));
+ auto rc
+ = curl_easy_setopt(rSession.m_pCurl.get(), CURLOPT_USERNAME, utf8UserName.getStr());
+ if (rc != CURLE_OK)
+ {
+ SAL_WARN("ucb.ucp.webdav.curl", "CURLOPT_USERNAME failed: " << GetErrorString(rc));
+ throw DAVException(DAVException::DAV_INVALID_ARG);
+ }
+ OString const utf8PassWord(OUStringToOString(oAuth->PassWord, RTL_TEXTENCODING_UTF8));
+ rc = curl_easy_setopt(rSession.m_pCurl.get(), CURLOPT_PASSWORD, utf8PassWord.getStr());
+ if (rc != CURLE_OK)
+ {
+ SAL_WARN("ucb.ucp.webdav.curl", "CURLOPT_PASSWORD failed: " << GetErrorString(rc));
+ throw DAVException(DAVException::DAV_INVALID_ARG);
+ }
+ rc = curl_easy_setopt(rSession.m_pCurl.get(), CURLOPT_HTTPAUTH, oAuth->AuthMask);
+ assert(
+ rc
+ == CURLE_OK); // it shouldn't be possible to reduce auth to 0 via the authSystem masks
+ }
+
+ if (oAuthProxy && !rSession.m_isAuthenticatedProxy)
+ {
+ OString const utf8UserName(
+ OUStringToOString(oAuthProxy->UserName, RTL_TEXTENCODING_UTF8));
+ auto rc = curl_easy_setopt(rSession.m_pCurl.get(), CURLOPT_PROXYUSERNAME,
+ utf8UserName.getStr());
+ if (rc != CURLE_OK)
+ {
+ SAL_WARN("ucb.ucp.webdav.curl",
+ "CURLOPT_PROXYUSERNAME failed: " << GetErrorString(rc));
+ throw DAVException(DAVException::DAV_INVALID_ARG);
+ }
+ OString const utf8PassWord(
+ OUStringToOString(oAuthProxy->PassWord, RTL_TEXTENCODING_UTF8));
+ rc = curl_easy_setopt(rSession.m_pCurl.get(), CURLOPT_PROXYPASSWORD,
+ utf8PassWord.getStr());
+ if (rc != CURLE_OK)
+ {
+ SAL_WARN("ucb.ucp.webdav.curl",
+ "CURLOPT_PROXYPASSWORD failed: " << GetErrorString(rc));
+ throw DAVException(DAVException::DAV_INVALID_ARG);
+ }
+ rc = curl_easy_setopt(rSession.m_pCurl.get(), CURLOPT_PROXYAUTH, oAuthProxy->AuthMask);
+ assert(
+ rc
+ == CURLE_OK); // it shouldn't be possible to reduce auth to 0 via the authSystem masks
+ }
+
+ ResponseHeaders headers(rSession.m_pCurl.get());
+ // always pass a stream for debug logging, buffer the result body
+ uno::Reference<io::XSequenceOutputStream> const xSeqOutStream(
+ io::SequenceOutputStream::create(rSession.m_xContext));
+ uno::Reference<io::XOutputStream> const xTempOutStream(xSeqOutStream);
+ assert(xTempOutStream.is());
+
+ try
+ {
+ ProcessRequestImpl(rSession, rURI, rMethod, pRequestHeaderList.get(), &xTempOutStream,
+ pxInStream ? &data : nullptr, pRequestedHeaders, headers);
+ if (pxOutStream)
+ { // only copy to result stream if transfer was successful
+ (*pxOutStream)->writeBytes(xSeqOutStream->getWrittenBytes());
+ (*pxOutStream)->closeOutput(); // signal EOF
+ }
+ }
+ catch (DAVException const& rException)
+ {
+ // log start of request body if there was any
+ auto const bytes(xSeqOutStream->getWrittenBytes());
+ auto const len(::std::min<sal_Int32>(bytes.getLength(), 10000));
+ SAL_INFO("ucb.ucp.webdav.curl",
+ "DAVException; (first) " << len << " bytes of data received:");
+ if (0 < len)
+ {
+ OStringBuffer buf(len);
+ for (sal_Int32 i = 0; i < len; ++i)
+ {
+ if (bytes[i] < 0x20) // also if negative
+ {
+ static char const hexDigit[16] = { '0', '1', '2', '3', '4', '5', '6', '7',
+ '8', '9', 'A', 'B', 'C', 'D', 'E', 'F' };
+ buf.append(OString::Concat("\\x")
+ + OStringChar(hexDigit[static_cast<sal_uInt8>(bytes[i]) >> 4])
+ + OStringChar(hexDigit[bytes[i] & 0x0F]));
+ }
+ else
+ {
+ buf.append(static_cast<char>(bytes[i]));
+ }
+ }
+ SAL_INFO("ucb.ucp.webdav.curl", buf.makeStringAndClear());
+ }
+
+ // error handling part 3: special HTTP status codes
+ // that require unlocking m_Mutex to handle
+ if (rException.getError() == DAVException::DAV_HTTP_ERROR)
+ {
+ auto const statusCode(rException.getStatus());
+ switch (statusCode)
+ {
+ case SC_LOCKED:
+ {
+ guard.Release(); // release m_Mutex before accessing LockStore
+ if (g_Init.LockStore.getLockTokenForURI(rURI.GetURI(), nullptr))
+ {
+ throw DAVException(DAVException::DAV_LOCKED_SELF);
+ }
+ else // locked by third party
+ {
+ throw DAVException(DAVException::DAV_LOCKED);
+ }
+ break;
+ }
+ case SC_PRECONDITION_FAILED:
+ case SC_BAD_REQUEST:
+ {
+ guard.Release(); // release m_Mutex before accessing LockStore
+ // Not obvious but apparently these codes may indicate
+ // the expiration of a lock.
+ // Initiate a new request *outside* ProcessRequestImpl
+ // *after* rGuard.unlock() to avoid messing up m_pCurl state.
+ if (TryRemoveExpiredLockToken(rSession, rURI, pEnv))
+ {
+ throw DAVException(DAVException::DAV_LOCK_EXPIRED);
+ }
+ break;
+ }
+ case SC_UNAUTHORIZED:
+ case SC_PROXY_AUTHENTICATION_REQUIRED:
+ {
+ (statusCode != SC_PROXY_AUTHENTICATION_REQUIRED
+ ? rSession.m_isAuthenticated
+ : rSession.m_isAuthenticatedProxy)
+ = false; // any auth data in m_pCurl is invalid
+ auto& rnAuthRequests(statusCode == SC_UNAUTHORIZED ? nAuthRequests
+ : nAuthRequestsProxy);
+ if (rnAuthRequests == 10)
+ {
+ SAL_INFO("ucb.ucp.webdav.curl", "aborting authentication after "
+ << rnAuthRequests << " attempts");
+ }
+ else if (pEnv && pEnv->m_xAuthListener)
+ {
+ ::std::optional<OUString> const oRealm(ExtractRealm(
+ headers, statusCode == SC_UNAUTHORIZED ? "WWW-Authenticate"
+ : "Proxy-Authenticate"));
+
+ ::std::optional<Auth>& roAuth(
+ statusCode == SC_UNAUTHORIZED ? oAuth : oAuthProxy);
+ OUString userName(roAuth ? roAuth->UserName : OUString());
+ OUString passWord(roAuth ? roAuth->PassWord : OUString());
+ long authAvail(0);
+ auto const rc = curl_easy_getinfo(rSession.m_pCurl.get(),
+ statusCode == SC_UNAUTHORIZED
+ ? CURLINFO_HTTPAUTH_AVAIL
+ : CURLINFO_PROXYAUTH_AVAIL,
+ &authAvail);
+ assert(rc == CURLE_OK);
+ (void)rc;
+ // only allow SystemCredentials once - the
+ // PasswordContainer may have stored it in the
+ // Config (TrySystemCredentialsFirst or
+ // AuthenticateUsingSystemCredentials) and then it
+ // will always force its use no matter how hopeless
+ bool const isSystemCredSupported((authAvail & authSystem) != 0
+ && rnAuthRequests == 0);
+ ++rnAuthRequests;
+
+ // Ask user via XInteractionHandler.
+ // Warning: This likely runs an event loop which may
+ // end up calling back into this instance, so all
+ // changes to m_pCurl must be undone now and
+ // restored after return.
+ guard.Release();
+
+ auto const ret = pEnv->m_xAuthListener->authenticate(
+ oRealm ? *oRealm : "",
+ statusCode == SC_UNAUTHORIZED ? rSession.m_URI.GetHost()
+ : rSession.m_Proxy,
+ userName, passWord, isSystemCredSupported);
+
+ if (ret == 0)
+ {
+ // NTLM may either use a password requested
+ // from the user, or from the system via SSPI
+ // so i guess it should not be disabled here
+ // regardless of the state of the system auth
+ // checkbox, particularly since SSPI is only
+ // available on WNT.
+ // Additionally, "Negotiate" has a "legacy"
+ // mode that is actually just NTLM according to
+ // https://curl.se/rfc/ntlm.html#ntlmHttpAuthentication
+ // so there's nothing in authSystem that can be
+ // disabled here.
+ roAuth.emplace(userName, passWord,
+ ((userName.isEmpty() && passWord.isEmpty())
+ ? (authAvail & authSystem)
+ : authAvail));
+ isRetry = true;
+ // Acquire is only necessary in case of success.
+ guard.Acquire();
+ break; // break out of switch
+ }
+ // else: throw
+ }
+ SAL_INFO("ucb.ucp.webdav.curl", "no auth credentials provided");
+ throw DAVException(DAVException::DAV_HTTP_NOAUTH,
+ ConnectionEndPointString(rSession.m_URI.GetHost(),
+ rSession.m_URI.GetPort()));
+ break;
+ }
+ }
+ }
+ else if (rException.getError() == DAVException::DAV_UNSUPPORTED)
+ {
+ // tdf#152493 libcurl can't handle "Transfer-Encoding: chunked"
+ // in HTTP/1.1 100 Continue response.
+ // workaround: if HTTP/1.1 didn't work, try HTTP/1.0
+ // (but fallback only once - to prevent infinite loop)
+ if (isFallbackHTTP10)
+ {
+ throw DAVException(DAVException::DAV_HTTP_ERROR);
+ }
+ isFallbackHTTP10 = true;
+ // note: this is not reset - future requests to this URI use it!
+ auto rc = curl_easy_setopt(rSession.m_pCurl.get(), CURLOPT_HTTP_VERSION,
+ CURL_HTTP_VERSION_1_0);
+ if (rc != CURLE_OK)
+ {
+ throw DAVException(DAVException::DAV_HTTP_ERROR);
+ }
+ SAL_INFO("ucb.ucp.webdav.curl", "attempting fallback to HTTP/1.0");
+ isRetry = true;
+ }
+ if (!isRetry)
+ {
+ throw; // everything else: re-throw
+ }
+ }
+ } while (isRetry);
+
+ if (oAuth)
+ {
+ // assume this worked, leave auth data as stored in m_pCurl
+ rSession.m_isAuthenticated = true;
+ }
+ if (oAuthProxy)
+ {
+ // assume this worked, leave auth data as stored in m_pCurl
+ rSession.m_isAuthenticatedProxy = true;
+ }
+}
+
+auto CurlSession::OPTIONS(OUString const& rURIReference,
+
+ DAVOptions& rOptions, DAVRequestEnvironment const& rEnv) -> void
+{
+ SAL_INFO("ucb.ucp.webdav.curl", "OPTIONS: " << rURIReference);
+
+ rOptions.init();
+
+ CurlUri const uri(CurlProcessor::URIReferenceToURI(*this, rURIReference));
+
+ ::std::vector<OUString> const headerNames{ "allow", "dav" };
+ DAVResource result;
+ ::std::pair<::std::vector<OUString> const&, DAVResource&> const headers(headerNames, result);
+
+ ::std::vector<CurlOption> const options{
+ g_NoBody, { CURLOPT_CUSTOMREQUEST, "OPTIONS", "CURLOPT_CUSTOMREQUEST" }
+ };
+
+ CurlProcessor::ProcessRequest(*this, uri, "OPTIONS", options, &rEnv, nullptr, nullptr, nullptr,
+ &headers);
+
+ for (auto const& it : result.properties)
+ {
+ OUString value;
+ it.Value >>= value;
+ SAL_INFO("ucb.ucp.webdav.curl", "OPTIONS: header: " << it.Name << ": " << value);
+ if (it.Name.equalsIgnoreAsciiCase("allow"))
+ {
+ rOptions.setAllowedMethods(value);
+ }
+ else if (it.Name.equalsIgnoreAsciiCase("dav"))
+ {
+ // see <http://tools.ietf.org/html/rfc4918#section-10.1>,
+ // <http://tools.ietf.org/html/rfc4918#section-18>,
+ // and <http://tools.ietf.org/html/rfc7230#section-3.2>
+ // we detect the class (1, 2 and 3), other elements (token, URL)
+ // are not used for now
+ auto const list(::comphelper::string::convertCommaSeparated(value));
+ for (OUString const& v : list)
+ {
+ if (v == "1")
+ {
+ rOptions.setClass1();
+ }
+ else if (v == "2")
+ {
+ rOptions.setClass2();
+ }
+ else if (v == "3")
+ {
+ rOptions.setClass3();
+ }
+ }
+ }
+ }
+ if (rOptions.isClass2() || rOptions.isClass3())
+ {
+ if (g_Init.LockStore.getLockTokenForURI(uri.GetURI(), nullptr))
+ {
+ rOptions.setLocked();
+ }
+ }
+}
+
+auto CurlProcessor::PropFind(
+ CurlSession& rSession, CurlUri const& rURI, Depth const nDepth,
+ ::std::tuple<::std::vector<OUString> const&, ::std::vector<DAVResource>* const,
+ ::std::vector<ucb::Lock>* const> const* const o_pRequestedProperties,
+ ::std::vector<DAVResourceInfo>* const o_pResourceInfos, DAVRequestEnvironment const& rEnv)
+ -> void
+{
+ assert((o_pRequestedProperties != nullptr) != (o_pResourceInfos != nullptr));
+ assert((o_pRequestedProperties == nullptr)
+ || (::std::get<1>(*o_pRequestedProperties) != nullptr)
+ != (::std::get<2>(*o_pRequestedProperties) != nullptr));
+
+ ::std::unique_ptr<curl_slist, deleter_from_fn<curl_slist, curl_slist_free_all>> pList;
+ pList.reset(curl_slist_append(pList.release(), "Content-Type: application/xml"));
+ if (!pList)
+ {
+ throw uno::RuntimeException("curl_slist_append failed");
+ }
+ OString depth;
+ switch (nDepth)
+ {
+ case DAVZERO:
+ depth = "Depth: 0"_ostr;
+ break;
+ case DAVONE:
+ depth = "Depth: 1"_ostr;
+ break;
+ case DAVINFINITY:
+ depth = "Depth: infinity"_ostr;
+ break;
+ default:
+ assert(false);
+ }
+ pList.reset(curl_slist_append(pList.release(), depth.getStr()));
+ if (!pList)
+ {
+ throw uno::RuntimeException("curl_slist_append failed");
+ }
+
+ uno::Reference<io::XSequenceOutputStream> const xSeqOutStream(
+ io::SequenceOutputStream::create(rSession.m_xContext));
+ uno::Reference<io::XOutputStream> const xRequestOutStream(xSeqOutStream);
+ assert(xRequestOutStream.is());
+
+ uno::Reference<xml::sax::XWriter> const xWriter(xml::sax::Writer::create(rSession.m_xContext));
+ xWriter->setOutputStream(xRequestOutStream);
+ xWriter->startDocument();
+ rtl::Reference<::comphelper::AttributeList> const pAttrList(new ::comphelper::AttributeList);
+ pAttrList->AddAttribute("xmlns", "DAV:");
+ xWriter->startElement("propfind", pAttrList);
+ if (o_pResourceInfos)
+ {
+ xWriter->startElement("propname", nullptr);
+ xWriter->endElement("propname");
+ }
+ else
+ {
+ if (::std::get<0>(*o_pRequestedProperties).empty())
+ {
+ xWriter->startElement("allprop", nullptr);
+ xWriter->endElement("allprop");
+ }
+ else
+ {
+ xWriter->startElement("prop", nullptr);
+ for (OUString const& rName : ::std::get<0>(*o_pRequestedProperties))
+ {
+ SerfPropName name;
+ DAVProperties::createSerfPropName(rName, name);
+ pAttrList->Clear();
+ pAttrList->AddAttribute("xmlns", OUString::createFromAscii(name.nspace));
+ xWriter->startElement(OUString::createFromAscii(name.name), pAttrList);
+ xWriter->endElement(OUString::createFromAscii(name.name));
+ }
+ xWriter->endElement("prop");
+ }
+ }
+ xWriter->endElement("propfind");
+ xWriter->endDocument();
+
+ uno::Reference<io::XInputStream> const xRequestInStream(
+ io::SequenceInputStream::createStreamFromSequence(rSession.m_xContext,
+ xSeqOutStream->getWrittenBytes()));
+ assert(xRequestInStream.is());
+
+ curl_off_t const len(xSeqOutStream->getWrittenBytes().getLength());
+
+ ::std::vector<CurlOption> const options{
+ { CURLOPT_UPLOAD, 1L, nullptr },
+ { CURLOPT_CUSTOMREQUEST, "PROPFIND", "CURLOPT_CUSTOMREQUEST" },
+ // note: Sharepoint cannot handle "Transfer-Encoding: chunked"
+ { CURLOPT_INFILESIZE_LARGE, len, nullptr, CurlOption::Type::CurlOffT }
+ };
+
+ // stream for response
+ uno::Reference<io::XInputStream> const xResponseInStream(io::Pipe::create(rSession.m_xContext));
+ uno::Reference<io::XOutputStream> const xResponseOutStream(xResponseInStream, uno::UNO_QUERY);
+ assert(xResponseInStream.is());
+ assert(xResponseOutStream.is());
+
+ CurlProcessor::ProcessRequest(rSession, rURI, "PROPFIND", options, &rEnv, ::std::move(pList),
+ &xResponseOutStream, &xRequestInStream, nullptr);
+
+ if (o_pResourceInfos)
+ {
+ *o_pResourceInfos = parseWebDAVPropNameResponse(xResponseInStream);
+ }
+ else
+ {
+ if (::std::get<1>(*o_pRequestedProperties) != nullptr)
+ {
+ *::std::get<1>(*o_pRequestedProperties)
+ = parseWebDAVPropFindResponse(xResponseInStream);
+ for (DAVResource& it : *::std::get<1>(*o_pRequestedProperties))
+ {
+ // caller will give these uris to CurlUri so can't be relative
+ if (it.uri.startsWith("/"))
+ {
+ try
+ {
+ it.uri = rSession.m_URI.CloneWithRelativeRefPathAbsolute(it.uri).GetURI();
+ }
+ catch (DAVException const&)
+ {
+ SAL_INFO("ucb.ucp.webdav.curl",
+ "PROPFIND: exception parsing uri " << it.uri);
+ }
+ }
+ }
+ }
+ else
+ {
+ *::std::get<2>(*o_pRequestedProperties) = parseWebDAVLockResponse(xResponseInStream);
+ }
+ }
+}
+
+// DAV methods
+auto CurlSession::PROPFIND(OUString const& rURIReference, Depth const depth,
+ ::std::vector<OUString> const& rPropertyNames,
+ ::std::vector<DAVResource>& o_rResources,
+ DAVRequestEnvironment const& rEnv) -> void
+{
+ SAL_INFO("ucb.ucp.webdav.curl", "PROPFIND: " << rURIReference << " " << depth);
+
+ CurlUri const uri(CurlProcessor::URIReferenceToURI(*this, rURIReference));
+
+ ::std::tuple<::std::vector<OUString> const&, ::std::vector<DAVResource>* const,
+ ::std::vector<ucb::Lock>* const> const args(rPropertyNames, &o_rResources,
+ nullptr);
+ return CurlProcessor::PropFind(*this, uri, depth, &args, nullptr, rEnv);
+}
+
+auto CurlSession::PROPFIND(OUString const& rURIReference, Depth const depth,
+ ::std::vector<DAVResourceInfo>& o_rResourceInfos,
+ DAVRequestEnvironment const& rEnv) -> void
+{
+ SAL_INFO("ucb.ucp.webdav.curl", "PROPFIND: " << rURIReference << " " << depth);
+
+ CurlUri const uri(CurlProcessor::URIReferenceToURI(*this, rURIReference));
+
+ return CurlProcessor::PropFind(*this, uri, depth, nullptr, &o_rResourceInfos, rEnv);
+}
+
+auto CurlSession::PROPPATCH(OUString const& rURIReference,
+ ::std::vector<ProppatchValue> const& rValues,
+ DAVRequestEnvironment const& rEnv) -> void
+{
+ SAL_INFO("ucb.ucp.webdav.curl", "PROPPATCH: " << rURIReference);
+
+ CurlUri const uri(CurlProcessor::URIReferenceToURI(*this, rURIReference));
+
+ // TODO: either set CURLOPT_INFILESIZE_LARGE or chunked?
+ ::std::unique_ptr<curl_slist, deleter_from_fn<curl_slist, curl_slist_free_all>> pList;
+ pList.reset(curl_slist_append(pList.release(), "Content-Type: application/xml"));
+ if (!pList)
+ {
+ throw uno::RuntimeException("curl_slist_append failed");
+ }
+
+ // generate XML document for PROPPATCH
+ uno::Reference<io::XSequenceOutputStream> const xSeqOutStream(
+ io::SequenceOutputStream::create(m_xContext));
+ uno::Reference<io::XOutputStream> const xRequestOutStream(xSeqOutStream);
+ assert(xRequestOutStream.is());
+ uno::Reference<xml::sax::XWriter> const xWriter(xml::sax::Writer::create(m_xContext));
+ xWriter->setOutputStream(xRequestOutStream);
+ xWriter->startDocument();
+ rtl::Reference<::comphelper::AttributeList> const pAttrList(new ::comphelper::AttributeList);
+ pAttrList->AddAttribute("xmlns", "DAV:");
+ xWriter->startElement("propertyupdate", pAttrList);
+ for (ProppatchValue const& rPropValue : rValues)
+ {
+ assert(rPropValue.operation == PROPSET || rPropValue.operation == PROPREMOVE);
+ OUString const operation((rPropValue.operation == PROPSET) ? OUString("set")
+ : OUString("remove"));
+ xWriter->startElement(operation, nullptr);
+ xWriter->startElement("prop", nullptr);
+ SerfPropName name;
+ DAVProperties::createSerfPropName(rPropValue.name, name);
+ pAttrList->Clear();
+ pAttrList->AddAttribute("xmlns", OUString::createFromAscii(name.nspace));
+ xWriter->startElement(OUString::createFromAscii(name.name), pAttrList);
+ if (rPropValue.operation == PROPSET)
+ {
+ if (DAVProperties::isUCBDeadProperty(name))
+ {
+ ::std::optional<::std::pair<OUString, OUString>> const oProp(
+ UCBDeadPropertyValue::toXML(rPropValue.value));
+ if (oProp)
+ {
+ xWriter->startElement("ucbprop", nullptr);
+ xWriter->startElement("type", nullptr);
+ xWriter->characters(oProp->first);
+ xWriter->endElement("type");
+ xWriter->startElement("value", nullptr);
+ xWriter->characters(oProp->second);
+ xWriter->endElement("value");
+ xWriter->endElement("ucbprop");
+ }
+ }
+ else
+ {
+ OUString value;
+ rPropValue.value >>= value;
+ xWriter->characters(value);
+ }
+ }
+ xWriter->endElement(OUString::createFromAscii(name.name));
+ xWriter->endElement("prop");
+ xWriter->endElement(operation);
+ }
+ xWriter->endElement("propertyupdate");
+ xWriter->endDocument();
+
+ uno::Reference<io::XInputStream> const xRequestInStream(
+ io::SequenceInputStream::createStreamFromSequence(m_xContext,
+ xSeqOutStream->getWrittenBytes()));
+ assert(xRequestInStream.is());
+
+ curl_off_t const len(xSeqOutStream->getWrittenBytes().getLength());
+
+ ::std::vector<CurlOption> const options{
+ { CURLOPT_UPLOAD, 1L, nullptr },
+ { CURLOPT_CUSTOMREQUEST, "PROPPATCH", "CURLOPT_CUSTOMREQUEST" },
+ // note: Sharepoint cannot handle "Transfer-Encoding: chunked"
+ { CURLOPT_INFILESIZE_LARGE, len, nullptr, CurlOption::Type::CurlOffT }
+ };
+
+ CurlProcessor::ProcessRequest(*this, uri, "PROPPATCH", options, &rEnv, ::std::move(pList),
+ nullptr, &xRequestInStream, nullptr);
+}
+
+auto CurlSession::HEAD(OUString const& rURIReference, ::std::vector<OUString> const& rHeaderNames,
+ DAVResource& io_rResource, DAVRequestEnvironment const& rEnv) -> void
+{
+ SAL_INFO("ucb.ucp.webdav.curl", "HEAD: " << rURIReference);
+
+ CurlUri const uri(CurlProcessor::URIReferenceToURI(*this, rURIReference));
+
+ ::std::vector<CurlOption> const options{ g_NoBody };
+
+ ::std::pair<::std::vector<OUString> const&, DAVResource&> const headers(rHeaderNames,
+ io_rResource);
+
+ CurlProcessor::ProcessRequest(*this, uri, "HEAD", options, &rEnv, nullptr, nullptr, nullptr,
+ &headers);
+}
+
+auto CurlSession::GET(OUString const& rURIReference, DAVRequestEnvironment const& rEnv)
+ -> uno::Reference<io::XInputStream>
+{
+ SAL_INFO("ucb.ucp.webdav.curl", "GET: " << rURIReference);
+
+ CurlUri const uri(CurlProcessor::URIReferenceToURI(*this, rURIReference));
+
+ // could use either com.sun.star.io.Pipe or com.sun.star.io.SequenceInputStream?
+ // Pipe can just write into its XOuputStream, which is simpler.
+ // Both resize exponentially, so performance should be fine.
+ // However, Pipe doesn't implement XSeekable, which is required by filters.
+
+ uno::Reference<io::XSequenceOutputStream> const xSeqOutStream(
+ io::SequenceOutputStream::create(m_xContext));
+ uno::Reference<io::XOutputStream> const xResponseOutStream(xSeqOutStream);
+ assert(xResponseOutStream.is());
+
+ ::std::vector<CurlOption> const options{ { CURLOPT_HTTPGET, 1L, nullptr } };
+
+ CurlProcessor::ProcessRequest(*this, uri, "GET", options, &rEnv, nullptr, &xResponseOutStream,
+ nullptr, nullptr);
+
+ uno::Reference<io::XInputStream> const xResponseInStream(
+ io::SequenceInputStream::createStreamFromSequence(m_xContext,
+ xSeqOutStream->getWrittenBytes()));
+ assert(xResponseInStream.is());
+
+ return xResponseInStream;
+}
+
+auto CurlSession::GET(OUString const& rURIReference, uno::Reference<io::XOutputStream>& rxOutStream,
+ DAVRequestEnvironment const& rEnv) -> void
+{
+ SAL_INFO("ucb.ucp.webdav.curl", "GET: " << rURIReference);
+
+ CurlUri const uri(CurlProcessor::URIReferenceToURI(*this, rURIReference));
+
+ ::std::vector<CurlOption> const options{ { CURLOPT_HTTPGET, 1L, nullptr } };
+
+ CurlProcessor::ProcessRequest(*this, uri, "GET", options, &rEnv, nullptr, &rxOutStream, nullptr,
+ nullptr);
+}
+
+auto CurlSession::GET(OUString const& rURIReference, ::std::vector<OUString> const& rHeaderNames,
+ DAVResource& io_rResource, DAVRequestEnvironment const& rEnv)
+ -> uno::Reference<io::XInputStream>
+{
+ SAL_INFO("ucb.ucp.webdav.curl", "GET: " << rURIReference);
+
+ CurlUri const uri(CurlProcessor::URIReferenceToURI(*this, rURIReference));
+
+ ::std::vector<CurlOption> const options{ { CURLOPT_HTTPGET, 1L, nullptr } };
+
+ uno::Reference<io::XSequenceOutputStream> const xSeqOutStream(
+ io::SequenceOutputStream::create(m_xContext));
+ uno::Reference<io::XOutputStream> const xResponseOutStream(xSeqOutStream);
+ assert(xResponseOutStream.is());
+
+ ::std::pair<::std::vector<OUString> const&, DAVResource&> const headers(rHeaderNames,
+ io_rResource);
+
+ CurlProcessor::ProcessRequest(*this, uri, "GET", options, &rEnv, nullptr, &xResponseOutStream,
+ nullptr, &headers);
+
+ uno::Reference<io::XInputStream> const xResponseInStream(
+ io::SequenceInputStream::createStreamFromSequence(m_xContext,
+ xSeqOutStream->getWrittenBytes()));
+ assert(xResponseInStream.is());
+
+ return xResponseInStream;
+}
+
+auto CurlSession::GET(OUString const& rURIReference, uno::Reference<io::XOutputStream>& rxOutStream,
+ ::std::vector<OUString> const& rHeaderNames, DAVResource& io_rResource,
+ DAVRequestEnvironment const& rEnv) -> void
+{
+ SAL_INFO("ucb.ucp.webdav.curl", "GET: " << rURIReference);
+
+ CurlUri const uri(CurlProcessor::URIReferenceToURI(*this, rURIReference));
+
+ ::std::vector<CurlOption> const options{ { CURLOPT_HTTPGET, 1L, nullptr } };
+
+ ::std::pair<::std::vector<OUString> const&, DAVResource&> const headers(rHeaderNames,
+ io_rResource);
+
+ CurlProcessor::ProcessRequest(*this, uri, "GET", options, &rEnv, nullptr, &rxOutStream, nullptr,
+ &headers);
+}
+
+auto CurlSession::PUT(OUString const& rURIReference,
+ uno::Reference<io::XInputStream> const& rxInStream,
+ DAVRequestEnvironment const& rEnv) -> void
+{
+ SAL_INFO("ucb.ucp.webdav.curl", "PUT: " << rURIReference);
+
+ CurlUri const uri(CurlProcessor::URIReferenceToURI(*this, rURIReference));
+
+ // NextCloud silently fails with chunked encoding
+ uno::Reference<io::XSeekable> const xSeekable(rxInStream, uno::UNO_QUERY);
+ if (!xSeekable.is())
+ {
+ throw uno::RuntimeException("TODO: not seekable");
+ }
+ curl_off_t const len(xSeekable->getLength() - xSeekable->getPosition());
+
+ ::std::unique_ptr<curl_slist, deleter_from_fn<curl_slist, curl_slist_free_all>> pList;
+ OUString const* const pToken(g_Init.LockStore.getLockTokenForURI(uri.GetURI(), nullptr));
+ if (pToken)
+ {
+ OString const utf8If("If: "
+ // disabled as Sharepoint 2013 workaround, it accepts only
+ // "No-Tag-List", see fed2984281a85a5a2f308841ec810f218c75f2ab
+#if 0
+ "<" + OUStringToOString(rURIReference, RTL_TEXTENCODING_ASCII_US)
+ + "> "
+#endif
+ "(<"
+ + OUStringToOString(*pToken, RTL_TEXTENCODING_ASCII_US) + ">)");
+ pList.reset(curl_slist_append(pList.release(), utf8If.getStr()));
+ if (!pList)
+ {
+ throw uno::RuntimeException("curl_slist_append failed");
+ }
+ }
+
+ // lock m_Mutex after accessing global LockStore to avoid deadlock
+
+ // note: Nextcloud 20 cannot handle "Transfer-Encoding: chunked"
+ ::std::vector<CurlOption> const options{
+ { CURLOPT_UPLOAD, 1L, nullptr }, // libcurl won't upload without setting this
+ { CURLOPT_INFILESIZE_LARGE, len, nullptr, CurlOption::Type::CurlOffT }
+ };
+
+ CurlProcessor::ProcessRequest(*this, uri, "PUT", options, &rEnv, ::std::move(pList), nullptr,
+ &rxInStream, nullptr);
+}
+
+auto CurlSession::POST(OUString const& rURIReference, OUString const& rContentType,
+ OUString const& rReferer, uno::Reference<io::XInputStream> const& rxInStream,
+ DAVRequestEnvironment const& rEnv) -> uno::Reference<io::XInputStream>
+{
+ SAL_INFO("ucb.ucp.webdav.curl", "POST: " << rURIReference);
+
+ CurlUri const uri(CurlProcessor::URIReferenceToURI(*this, rURIReference));
+
+ // TODO: either set CURLOPT_POSTFIELDSIZE_LARGE or chunked?
+ ::std::unique_ptr<curl_slist, deleter_from_fn<curl_slist, curl_slist_free_all>> pList(
+ curl_slist_append(nullptr, "Transfer-Encoding: chunked"));
+ if (!pList)
+ {
+ throw uno::RuntimeException("curl_slist_append failed");
+ }
+ OString const utf8ContentType("Content-Type: "
+ + OUStringToOString(rContentType, RTL_TEXTENCODING_ASCII_US));
+ pList.reset(curl_slist_append(pList.release(), utf8ContentType.getStr()));
+ if (!pList)
+ {
+ throw uno::RuntimeException("curl_slist_append failed");
+ }
+ OString const utf8Referer("Referer: " + OUStringToOString(rReferer, RTL_TEXTENCODING_ASCII_US));
+ pList.reset(curl_slist_append(pList.release(), utf8Referer.getStr()));
+ if (!pList)
+ {
+ throw uno::RuntimeException("curl_slist_append failed");
+ }
+
+ ::std::vector<CurlOption> const options{ { CURLOPT_POST, 1L, nullptr } };
+
+ uno::Reference<io::XSequenceOutputStream> const xSeqOutStream(
+ io::SequenceOutputStream::create(m_xContext));
+ uno::Reference<io::XOutputStream> const xResponseOutStream(xSeqOutStream);
+ assert(xResponseOutStream.is());
+
+ CurlProcessor::ProcessRequest(*this, uri, "POST", options, &rEnv, ::std::move(pList),
+ &xResponseOutStream, &rxInStream, nullptr);
+
+ uno::Reference<io::XInputStream> const xResponseInStream(
+ io::SequenceInputStream::createStreamFromSequence(m_xContext,
+ xSeqOutStream->getWrittenBytes()));
+ assert(xResponseInStream.is());
+
+ return xResponseInStream;
+}
+
+auto CurlSession::POST(OUString const& rURIReference, OUString const& rContentType,
+ OUString const& rReferer, uno::Reference<io::XInputStream> const& rxInStream,
+ uno::Reference<io::XOutputStream>& rxOutStream,
+ DAVRequestEnvironment const& rEnv) -> void
+{
+ SAL_INFO("ucb.ucp.webdav.curl", "POST: " << rURIReference);
+
+ CurlUri const uri(CurlProcessor::URIReferenceToURI(*this, rURIReference));
+
+ // TODO: either set CURLOPT_POSTFIELDSIZE_LARGE or chunked?
+ ::std::unique_ptr<curl_slist, deleter_from_fn<curl_slist, curl_slist_free_all>> pList(
+ curl_slist_append(nullptr, "Transfer-Encoding: chunked"));
+ if (!pList)
+ {
+ throw uno::RuntimeException("curl_slist_append failed");
+ }
+ OString const utf8ContentType("Content-Type: "
+ + OUStringToOString(rContentType, RTL_TEXTENCODING_ASCII_US));
+ pList.reset(curl_slist_append(pList.release(), utf8ContentType.getStr()));
+ if (!pList)
+ {
+ throw uno::RuntimeException("curl_slist_append failed");
+ }
+ OString const utf8Referer("Referer: " + OUStringToOString(rReferer, RTL_TEXTENCODING_ASCII_US));
+ pList.reset(curl_slist_append(pList.release(), utf8Referer.getStr()));
+ if (!pList)
+ {
+ throw uno::RuntimeException("curl_slist_append failed");
+ }
+
+ ::std::vector<CurlOption> const options{ { CURLOPT_POST, 1L, nullptr } };
+
+ CurlProcessor::ProcessRequest(*this, uri, "POST", options, &rEnv, ::std::move(pList),
+ &rxOutStream, &rxInStream, nullptr);
+}
+
+auto CurlSession::MKCOL(OUString const& rURIReference, DAVRequestEnvironment const& rEnv) -> void
+{
+ SAL_INFO("ucb.ucp.webdav.curl", "MKCOL: " << rURIReference);
+
+ CurlUri const uri(CurlProcessor::URIReferenceToURI(*this, rURIReference));
+
+ ::std::vector<CurlOption> const options{
+ g_NoBody, { CURLOPT_CUSTOMREQUEST, "MKCOL", "CURLOPT_CUSTOMREQUEST" }
+ };
+
+ CurlProcessor::ProcessRequest(*this, uri, "MKCOL", options, &rEnv, nullptr, nullptr, nullptr,
+ nullptr);
+}
+
+auto CurlProcessor::MoveOrCopy(CurlSession& rSession, std::u16string_view rSourceURIReference,
+ ::std::u16string_view const rDestinationURI,
+ DAVRequestEnvironment const& rEnv, bool const isOverwrite,
+ char const* const pMethod) -> void
+{
+ CurlUri const uriSource(CurlProcessor::URIReferenceToURI(rSession, rSourceURIReference));
+
+ OString const utf8Destination("Destination: "
+ + OUStringToOString(rDestinationURI, RTL_TEXTENCODING_ASCII_US));
+ ::std::unique_ptr<curl_slist, deleter_from_fn<curl_slist, curl_slist_free_all>> pList(
+ curl_slist_append(nullptr, utf8Destination.getStr()));
+ if (!pList)
+ {
+ throw uno::RuntimeException("curl_slist_append failed");
+ }
+ OString const utf8Overwrite(OString::Concat("Overwrite: ") + (isOverwrite ? "T" : "F"));
+ pList.reset(curl_slist_append(pList.release(), utf8Overwrite.getStr()));
+ if (!pList)
+ {
+ throw uno::RuntimeException("curl_slist_append failed");
+ }
+
+ ::std::vector<CurlOption> const options{
+ g_NoBody, { CURLOPT_CUSTOMREQUEST, pMethod, "CURLOPT_CUSTOMREQUEST" }
+ };
+
+ CurlProcessor::ProcessRequest(rSession, uriSource, OUString::createFromAscii(pMethod), options,
+ &rEnv, ::std::move(pList), nullptr, nullptr, nullptr);
+}
+
+auto CurlSession::COPY(OUString const& rSourceURIReference, OUString const& rDestinationURI,
+ DAVRequestEnvironment const& rEnv, bool const isOverwrite) -> void
+{
+ SAL_INFO("ucb.ucp.webdav.curl", "COPY: " << rSourceURIReference);
+
+ return CurlProcessor::MoveOrCopy(*this, rSourceURIReference, rDestinationURI, rEnv, isOverwrite,
+ "COPY");
+}
+
+auto CurlSession::MOVE(OUString const& rSourceURIReference, OUString const& rDestinationURI,
+ DAVRequestEnvironment const& rEnv, bool const isOverwrite) -> void
+{
+ SAL_INFO("ucb.ucp.webdav.curl", "MOVE: " << rSourceURIReference);
+
+ return CurlProcessor::MoveOrCopy(*this, rSourceURIReference, rDestinationURI, rEnv, isOverwrite,
+ "MOVE");
+}
+
+auto CurlSession::DESTROY(OUString const& rURIReference, DAVRequestEnvironment const& rEnv) -> void
+{
+ SAL_INFO("ucb.ucp.webdav.curl", "DESTROY: " << rURIReference);
+
+ CurlUri const uri(CurlProcessor::URIReferenceToURI(*this, rURIReference));
+
+ ::std::vector<CurlOption> const options{
+ g_NoBody, { CURLOPT_CUSTOMREQUEST, "DELETE", "CURLOPT_CUSTOMREQUEST" }
+ };
+
+ CurlProcessor::ProcessRequest(*this, uri, "DESTROY", options, &rEnv, nullptr, nullptr, nullptr,
+ nullptr);
+}
+
+auto CurlProcessor::Lock(
+ CurlSession& rSession, CurlUri const& rURI, DAVRequestEnvironment const* const pEnv,
+ ::std::unique_ptr<curl_slist, deleter_from_fn<curl_slist, curl_slist_free_all>>
+ pRequestHeaderList,
+ uno::Reference<io::XInputStream> const* const pxRequestInStream)
+ -> ::std::vector<::std::pair<ucb::Lock, sal_Int32>>
+{
+ curl_off_t len(0);
+ if (pxRequestInStream)
+ {
+ uno::Reference<io::XSeekable> const xSeekable(*pxRequestInStream, uno::UNO_QUERY);
+ assert(xSeekable.is());
+ len = xSeekable->getLength();
+ }
+
+ ::std::vector<CurlOption> const options{
+ { CURLOPT_UPLOAD, 1L, nullptr },
+ { CURLOPT_CUSTOMREQUEST, "LOCK", "CURLOPT_CUSTOMREQUEST" },
+ // note: Sharepoint cannot handle "Transfer-Encoding: chunked"
+ { CURLOPT_INFILESIZE_LARGE, len, nullptr, CurlOption::Type::CurlOffT }
+ };
+
+ // stream for response
+ uno::Reference<io::XInputStream> const xResponseInStream(io::Pipe::create(rSession.m_xContext));
+ uno::Reference<io::XOutputStream> const xResponseOutStream(xResponseInStream, uno::UNO_QUERY);
+ assert(xResponseInStream.is());
+ assert(xResponseOutStream.is());
+
+ TimeValue startTime;
+ osl_getSystemTime(&startTime);
+
+ CurlProcessor::ProcessRequest(rSession, rURI, "LOCK", options, pEnv,
+ ::std::move(pRequestHeaderList), &xResponseOutStream,
+ pxRequestInStream, nullptr);
+
+ ::std::vector<ucb::Lock> const acquiredLocks(parseWebDAVLockResponse(xResponseInStream));
+ SAL_WARN_IF(acquiredLocks.empty(), "ucb.ucp.webdav.curl",
+ "could not get LOCK for " << rURI.GetURI());
+
+ TimeValue endTime;
+ osl_getSystemTime(&endTime);
+ auto const elapsedSeconds(endTime.Seconds - startTime.Seconds);
+
+ // determine expiration time (seconds from endTime) for each acquired lock
+ ::std::vector<::std::pair<ucb::Lock, sal_Int32>> ret;
+ ret.reserve(acquiredLocks.size());
+ for (auto const& rLock : acquiredLocks)
+ {
+ sal_Int32 lockExpirationTimeSeconds;
+ if (rLock.Timeout == -1)
+ {
+ lockExpirationTimeSeconds = -1;
+ }
+ else if (rLock.Timeout <= elapsedSeconds)
+ {
+ SAL_WARN("ucb.ucp.webdav.curl",
+ "LOCK timeout already expired when receiving LOCK response for "
+ << rURI.GetURI());
+ lockExpirationTimeSeconds = 0;
+ }
+ else
+ {
+ lockExpirationTimeSeconds = startTime.Seconds + rLock.Timeout;
+ }
+ ret.emplace_back(rLock, lockExpirationTimeSeconds);
+ }
+
+ return ret;
+}
+
+auto CurlSession::LOCK(OUString const& rURIReference, ucb::Lock /*const*/& rLock,
+ DAVRequestEnvironment const& rEnv) -> void
+{
+ SAL_INFO("ucb.ucp.webdav.curl", "LOCK: " << rURIReference);
+
+ CurlUri const uri(CurlProcessor::URIReferenceToURI(*this, rURIReference));
+
+ if (g_Init.LockStore.getLockTokenForURI(uri.GetURI(), &rLock))
+ {
+ // already have a lock that covers the requirement
+ // TODO: maybe use DAV:lockdiscovery to ensure it's valid
+ return;
+ }
+
+ // note: no m_Mutex lock needed here, only in CurlProcessor::Lock()
+
+ // generate XML document for acquiring new LOCK
+ uno::Reference<io::XSequenceOutputStream> const xSeqOutStream(
+ io::SequenceOutputStream::create(m_xContext));
+ uno::Reference<io::XOutputStream> const xRequestOutStream(xSeqOutStream);
+ assert(xRequestOutStream.is());
+ uno::Reference<xml::sax::XWriter> const xWriter(xml::sax::Writer::create(m_xContext));
+ xWriter->setOutputStream(xRequestOutStream);
+ xWriter->startDocument();
+ rtl::Reference<::comphelper::AttributeList> const pAttrList(new ::comphelper::AttributeList);
+ pAttrList->AddAttribute("xmlns", "DAV:");
+ xWriter->startElement("lockinfo", pAttrList);
+ xWriter->startElement("lockscope", nullptr);
+ switch (rLock.Scope)
+ {
+ case ucb::LockScope_EXCLUSIVE:
+ xWriter->startElement("exclusive", nullptr);
+ xWriter->endElement("exclusive");
+ break;
+ case ucb::LockScope_SHARED:
+ xWriter->startElement("shared", nullptr);
+ xWriter->endElement("shared");
+ break;
+ default:
+ assert(false);
+ }
+ xWriter->endElement("lockscope");
+ xWriter->startElement("locktype", nullptr);
+ xWriter->startElement("write", nullptr);
+ xWriter->endElement("write");
+ xWriter->endElement("locktype");
+ OUString owner;
+ if ((rLock.Owner >>= owner) && !owner.isEmpty())
+ {
+ xWriter->startElement("owner", nullptr);
+ xWriter->characters(owner);
+ xWriter->endElement("owner");
+ }
+ xWriter->endElement("lockinfo");
+ xWriter->endDocument();
+
+ uno::Reference<io::XInputStream> const xRequestInStream(
+ io::SequenceInputStream::createStreamFromSequence(m_xContext,
+ xSeqOutStream->getWrittenBytes()));
+ assert(xRequestInStream.is());
+
+ ::std::unique_ptr<curl_slist, deleter_from_fn<curl_slist, curl_slist_free_all>> pList;
+ pList.reset(curl_slist_append(pList.release(), "Content-Type: application/xml"));
+ if (!pList)
+ {
+ throw uno::RuntimeException("curl_slist_append failed");
+ }
+ OString depth;
+ switch (rLock.Depth)
+ {
+ case ucb::LockDepth_ZERO:
+ depth = "Depth: 0"_ostr;
+ break;
+ case ucb::LockDepth_ONE:
+ depth = "Depth: 1"_ostr;
+ break;
+ case ucb::LockDepth_INFINITY:
+ depth = "Depth: infinity"_ostr;
+ break;
+ default:
+ assert(false);
+ }
+ pList.reset(curl_slist_append(pList.release(), depth.getStr()));
+ if (!pList)
+ {
+ throw uno::RuntimeException("curl_slist_append failed");
+ }
+ OString timeout;
+ switch (rLock.Timeout)
+ {
+ case -1:
+ timeout = "Timeout: Infinite"_ostr;
+ break;
+ case 0:
+ timeout = "Timeout: Second-180"_ostr;
+ break;
+ default:
+ timeout = "Timeout: Second-" + OString::number(rLock.Timeout);
+ assert(0 < rLock.Timeout);
+ break;
+ }
+ pList.reset(curl_slist_append(pList.release(), timeout.getStr()));
+ if (!pList)
+ {
+ throw uno::RuntimeException("curl_slist_append failed");
+ }
+
+ auto const acquiredLocks
+ = CurlProcessor::Lock(*this, uri, &rEnv, ::std::move(pList), &xRequestInStream);
+
+ for (auto const& rAcquiredLock : acquiredLocks)
+ {
+ g_Init.LockStore.addLock(uri.GetURI(), rAcquiredLock.first,
+ rAcquiredLock.first.LockTokens[0], this, rAcquiredLock.second);
+ SAL_INFO("ucb.ucp.webdav.curl", "created LOCK for " << rURIReference);
+ }
+}
+
+auto CurlProcessor::Unlock(CurlSession& rSession, CurlUri const& rURI,
+ DAVRequestEnvironment const* const pEnv) -> void
+{
+ OUString const* const pToken(g_Init.LockStore.getLockTokenForURI(rURI.GetURI(), nullptr));
+ if (!pToken)
+ {
+ SAL_WARN("ucb.ucp.webdav.curl", "attempt to unlock but not locked");
+ throw DAVException(DAVException::DAV_NOT_LOCKED);
+ }
+ OString const utf8LockToken("Lock-Token: <"
+ + OUStringToOString(*pToken, RTL_TEXTENCODING_ASCII_US) + ">");
+ ::std::unique_ptr<curl_slist, deleter_from_fn<curl_slist, curl_slist_free_all>> pList(
+ curl_slist_append(nullptr, utf8LockToken.getStr()));
+ if (!pList)
+ {
+ throw uno::RuntimeException("curl_slist_append failed");
+ }
+
+ ::std::vector<CurlOption> const options{ { CURLOPT_CUSTOMREQUEST, "UNLOCK",
+ "CURLOPT_CUSTOMREQUEST" } };
+
+ CurlProcessor::ProcessRequest(rSession, rURI, "UNLOCK", options, pEnv, ::std::move(pList),
+ nullptr, nullptr, nullptr);
+}
+
+auto CurlSession::UNLOCK(OUString const& rURIReference, DAVRequestEnvironment const& rEnv) -> void
+{
+ SAL_INFO("ucb.ucp.webdav.curl", "UNLOCK: " << rURIReference);
+
+ // note: no m_Mutex lock needed here, only in CurlProcessor::Unlock()
+
+ CurlUri const uri(CurlProcessor::URIReferenceToURI(*this, rURIReference));
+
+ CurlProcessor::Unlock(*this, uri, &rEnv);
+
+ g_Init.LockStore.removeLock(uri.GetURI());
+}
+
+auto CurlSession::NonInteractive_LOCK(OUString const& rURI, ::std::u16string_view const rLockToken,
+ sal_Int32& o_rLastChanceToSendRefreshRequest,
+ bool& o_rIsAuthFailed) -> bool
+{
+ SAL_INFO("ucb.ucp.webdav.curl", "NonInteractive_LOCK: " << rURI);
+
+ // note: no m_Mutex lock needed here, only in CurlProcessor::Lock()
+
+ try
+ {
+ CurlUri const uri(rURI);
+ ::std::unique_ptr<curl_slist, deleter_from_fn<curl_slist, curl_slist_free_all>> pList(
+ curl_slist_append(nullptr, "Timeout: Second-180"));
+
+ assert(!rLockToken.empty()); // LockStore is the caller
+ OString const utf8If("If: (<" + OUStringToOString(rLockToken, RTL_TEXTENCODING_ASCII_US)
+ + ">)");
+ pList.reset(curl_slist_append(pList.release(), utf8If.getStr()));
+ if (!pList)
+ {
+ throw uno::RuntimeException("curl_slist_append failed");
+ }
+
+ auto const acquiredLocks
+ = CurlProcessor::Lock(*this, uri, nullptr, ::std::move(pList), nullptr);
+
+ SAL_WARN_IF(1 < acquiredLocks.size(), "ucb.ucp.webdav.curl",
+ "multiple locks acquired on refresh for " << rURI);
+ if (!acquiredLocks.empty())
+ {
+ o_rLastChanceToSendRefreshRequest = acquiredLocks.begin()->second;
+ }
+ SAL_INFO("ucb.ucp.webdav.curl", "NonInteractive_LOCK succeeded on " << rURI);
+ return true;
+ }
+ catch (DAVException const& rException)
+ {
+ SAL_INFO("ucb.ucp.webdav.curl", "NonInteractive_LOCK failed on " << rURI);
+ switch (rException.getError())
+ {
+ case DAVException::DAV_HTTP_AUTH:
+ case DAVException::DAV_HTTP_NOAUTH:
+ o_rIsAuthFailed = true;
+ break;
+ default:
+ break;
+ }
+ return false;
+ }
+ catch (...)
+ {
+ SAL_INFO("ucb.ucp.webdav.curl", "NonInteractive_LOCK failed on " << rURI);
+ return false;
+ }
+}
+
+auto CurlSession::NonInteractive_UNLOCK(OUString const& rURI) -> void
+{
+ SAL_INFO("ucb.ucp.webdav.curl", "NonInteractive_UNLOCK: " << rURI);
+
+ // note: no m_Mutex lock needed here, only in CurlProcessor::Unlock()
+
+ try
+ {
+ CurlUri const uri(rURI);
+
+ CurlProcessor::Unlock(*this, uri, nullptr);
+
+ // the only caller is the dtor of the LockStore, don't call remove!
+ SAL_INFO("ucb.ucp.webdav.curl", "NonInteractive_UNLOCK succeeded on " << rURI);
+ }
+ catch (...)
+ {
+ SAL_INFO("ucb.ucp.webdav.curl", "NonInteractive_UNLOCK failed on " << rURI);
+ }
+}
+
+} // namespace http_dav_ucp
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/ucb/source/ucp/webdav-curl/CurlSession.hxx b/ucb/source/ucp/webdav-curl/CurlSession.hxx
new file mode 100644
index 0000000000..2b71412878
--- /dev/null
+++ b/ucb/source/ucp/webdav-curl/CurlSession.hxx
@@ -0,0 +1,145 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4; fill-column: 100 -*- */
+/*
+ * 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 "DAVSession.hxx"
+#include "CurlUri.hxx"
+
+#include <curl/curl.h>
+
+#include <atomic>
+#include <mutex>
+
+namespace http_dav_ucp
+{
+/// implementation of libcurl HTTP/DAV back-end
+class CurlSession : public DAVSession
+{
+private:
+ /// mutex required to access all other non-const members
+ ::std::mutex m_Mutex;
+ css::uno::Reference<css::uno::XComponentContext> const m_xContext;
+ /// flags may be passed to constructor, e.g. "KeepAlive"
+ css::uno::Sequence<css::beans::NamedValue> const m_Flags;
+ CurlUri const m_URI;
+ /// buffer for libcurl detailed error messages
+ char m_ErrorBuffer[CURL_ERROR_SIZE];
+ /// proxy is used if non-empty
+ OUString const m_Proxy;
+ /// once authentication was successful, rely on m_pCurl's data
+ bool m_isAuthenticated = false;
+ bool m_isAuthenticatedProxy = false;
+ /// read timeout in milliseconds (connection timeout is stored in m_pCurl)
+ int m_nReadTimeout = 0;
+ /// flag to signal abort to transferring thread
+ ::std::atomic<bool> m_AbortFlag = false;
+
+ /// libcurl multi handle
+ ::std::unique_ptr<CURLM, deleter_from_fn<CURLM, curl_multi_cleanup>> m_pCurlMulti;
+ /// libcurl easy handle
+ ::std::unique_ptr<CURL, deleter_from_fn<CURL, curl_easy_cleanup>> m_pCurl;
+
+ // this class exists just to hide the implementation details in cxx file
+ friend struct CurlProcessor;
+
+public:
+ explicit CurlSession(css::uno::Reference<css::uno::XComponentContext> xContext,
+ ::rtl::Reference<DAVSessionFactory> const& rpFactory, OUString const& rURI,
+ css::uno::Sequence<css::beans::NamedValue> const& rFlags,
+ ::ucbhelper::InternetProxyDecider const& rProxyDecider);
+ virtual ~CurlSession() override;
+
+ virtual auto CanUse(OUString const& rURI,
+ css::uno::Sequence<css::beans::NamedValue> const& rFlags) -> bool override;
+
+ virtual auto UsesProxy() -> bool override;
+
+ // DAV methods
+ virtual auto OPTIONS(OUString const& rURIReference, DAVOptions& rOptions,
+ DAVRequestEnvironment const& rEnv) -> void override;
+
+ virtual auto PROPFIND(OUString const& rURIReference, Depth depth,
+ ::std::vector<OUString> const& rPropertyNames,
+ ::std::vector<DAVResource>& o_rResources,
+ DAVRequestEnvironment const& rEnv) -> void override;
+
+ virtual auto PROPFIND(OUString const& rURIReference, Depth depth,
+ ::std::vector<DAVResourceInfo>& o_rResourceInfos,
+ DAVRequestEnvironment const& rEnv) -> void override;
+
+ virtual auto PROPPATCH(OUString const& rURIReference,
+ ::std::vector<ProppatchValue> const& rValues,
+ DAVRequestEnvironment const& rEnv) -> void override;
+
+ virtual auto HEAD(OUString const& rURIReference, ::std::vector<OUString> const& rHeaderNames,
+ DAVResource& io_rResource, DAVRequestEnvironment const& rEnv)
+ -> void override;
+
+ virtual auto GET(OUString const& rURIReference, DAVRequestEnvironment const& rEnv)
+ -> css::uno::Reference<css::io::XInputStream> override;
+
+ virtual auto GET(OUString const& rURIReference,
+ css::uno::Reference<css::io::XOutputStream>& rxOutStream,
+ DAVRequestEnvironment const& rEnv) -> void override;
+
+ virtual auto GET(OUString const& rURIReference, ::std::vector<OUString> const& rHeaderNames,
+ DAVResource& io_rResource, DAVRequestEnvironment const& rEnv)
+ -> css::uno::Reference<css::io::XInputStream> override;
+
+ virtual auto GET(OUString const& rURIReference,
+ css::uno::Reference<css::io::XOutputStream>& rxOutStream,
+ ::std::vector<OUString> const& rHeaderNames, DAVResource& io_rResource,
+ DAVRequestEnvironment const& rEnv) -> void override;
+
+ virtual auto PUT(OUString const& rURIReference,
+ css::uno::Reference<css::io::XInputStream> const& rxInStream,
+ DAVRequestEnvironment const& rEnv) -> void override;
+
+ virtual auto POST(OUString const& rURIReference, OUString const& rContentType,
+ OUString const& rReferer,
+ css::uno::Reference<css::io::XInputStream> const& rxInStream,
+ DAVRequestEnvironment const& rEnv)
+ -> css::uno::Reference<css::io::XInputStream> override;
+
+ virtual auto POST(OUString const& rURIReference, OUString const& rContentType,
+ OUString const& rReferer,
+ css::uno::Reference<css::io::XInputStream> const& rxInStream,
+ css::uno::Reference<css::io::XOutputStream>& rxOutStream,
+ DAVRequestEnvironment const& rEnv) -> void override;
+
+ virtual auto MKCOL(OUString const& rURIReference, DAVRequestEnvironment const& rEnv)
+ -> void override;
+
+ virtual auto COPY(OUString const& rSourceURIReference, OUString const& rDestinationURI,
+ DAVRequestEnvironment const& rEnv, bool isOverwrite = false) -> void override;
+
+ virtual auto MOVE(OUString const& rSourceURIReference, OUString const& rDestinationURI,
+ DAVRequestEnvironment const& rEnv, bool isOverwrite = false) -> void override;
+
+ virtual auto DESTROY(OUString const& rURIReference, DAVRequestEnvironment const& rEnv)
+ -> void override;
+
+ virtual auto LOCK(OUString const& rURIReference, css::ucb::Lock /*const*/& rLock,
+ DAVRequestEnvironment const& rEnv) -> void override;
+
+ virtual auto UNLOCK(OUString const& rURIReference, DAVRequestEnvironment const& rEnv)
+ -> void override;
+
+ virtual auto abort() -> void override;
+
+ auto NonInteractive_LOCK(OUString const& rURI, ::std::u16string_view rLockToken,
+ sal_Int32& o_rLastChanceToSendRefreshRequest, bool& o_rIsAuthFailed)
+ -> bool;
+ auto NonInteractive_UNLOCK(OUString const& rURI) -> void;
+};
+
+} // namespace http_dav_ucp
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/ucb/source/ucp/webdav-curl/CurlUri.cxx b/ucb/source/ucp/webdav-curl/CurlUri.cxx
new file mode 100644
index 0000000000..3ee218d5ac
--- /dev/null
+++ b/ucb/source/ucp/webdav-curl/CurlUri.cxx
@@ -0,0 +1,330 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4; fill-column: 100 -*- */
+/*
+ * 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:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include "CurlUri.hxx"
+
+#include <sal/log.hxx>
+#include <rtl/uri.hxx>
+#include <rtl/ustrbuf.hxx>
+
+#include <optional>
+
+namespace http_dav_ucp
+{
+const auto DEFAULT_HTTP_PORT = 80;
+const auto DEFAULT_HTTPS_PORT = 443;
+
+static ::std::optional<OUString> GetURLComponent(CURLU& rURI, CURLUPart const what,
+ CURLUcode const expected,
+ unsigned int const flags = 0)
+{
+ char* pPart(nullptr);
+ auto uc = curl_url_get(&rURI, what, &pPart, flags);
+ if (expected != CURLUE_OK && uc == expected)
+ {
+ return ::std::optional<OUString>();
+ }
+ if (uc != CURLUE_OK)
+ {
+ SAL_WARN("ucb.ucp.webdav.curl", "curl_url_get failed: " << what << " " << uc);
+ throw DAVException(DAVException::DAV_INVALID_ARG);
+ }
+ assert(pPart);
+ CurlUniquePtr<char> pPart2(pPart);
+ return ::rtl::OStringToOUString(pPart, RTL_TEXTENCODING_UTF8);
+}
+
+void CurlUri::Init()
+{
+ // looks like the result should be the same as the old calculateURI()
+ auto const oURI(GetURLComponent(*m_pUrl, CURLUPART_URL, CURLUE_OK, CURLU_NO_DEFAULT_PORT));
+ assert(oURI);
+ m_URI = *oURI;
+
+ auto const oScheme(GetURLComponent(*m_pUrl, CURLUPART_SCHEME, CURLUE_NO_SCHEME));
+ if (oScheme)
+ {
+ m_Scheme = *oScheme;
+ }
+ auto const oUser(GetURLComponent(*m_pUrl, CURLUPART_USER, CURLUE_NO_USER));
+ if (oUser)
+ {
+ m_User = *oUser;
+ }
+ auto const oPassWord(GetURLComponent(*m_pUrl, CURLUPART_PASSWORD, CURLUE_NO_PASSWORD));
+ if (oPassWord)
+ {
+ m_Password = *oPassWord;
+ }
+ auto const oHost(GetURLComponent(*m_pUrl, CURLUPART_HOST, CURLUE_NO_HOST));
+ if (oHost)
+ {
+ m_Host = *oHost;
+ }
+ // DAV schemes always have port but Content::transfer() is called with
+ // arbitrary URLs so use CURLUE_NO_PORT
+ auto const oPort(GetURLComponent(*m_pUrl, CURLUPART_PORT, CURLUE_NO_PORT, CURLU_DEFAULT_PORT));
+ if (oPort)
+ {
+ m_nPort = oPort->toInt32();
+ }
+
+ auto const oPath(GetURLComponent(*m_pUrl, CURLUPART_PATH, CURLUE_OK));
+ assert(oPath);
+ m_Path = *oPath;
+
+ // note: this used to be added to m_Path because before 2007, ne_uri path contained query/fragment as well :-/
+ auto const oQuery(GetURLComponent(*m_pUrl, CURLUPART_QUERY, CURLUE_NO_QUERY));
+ if (oQuery)
+ {
+ m_QueryAndFragment += "?" + *oQuery;
+ }
+ auto const oFragment(GetURLComponent(*m_pUrl, CURLUPART_FRAGMENT, CURLUE_NO_FRAGMENT));
+ if (oFragment)
+ {
+ m_QueryAndFragment += "#" + *oFragment;
+ }
+}
+
+CurlUri::CurlUri(::std::u16string_view const rURI)
+{
+ // note: in the old implementation, the rURI would be URI-encoded again
+ // here, apparently because it could actually be an IRI (RFC 3987) and
+ // neon didn't support that - not clear if this is a good idea
+
+ m_pUrl.reset(curl_url());
+ if (!m_pUrl)
+ {
+ throw ::std::bad_alloc();
+ }
+
+ // use curl to parse the URI, to get a consistent interpretation
+ if (rURI.find(u'\0') != std::u16string_view::npos)
+ {
+ throw DAVException(DAVException::DAV_INVALID_ARG);
+ }
+ OString const utf8URI(OUStringToOString(rURI, RTL_TEXTENCODING_UTF8));
+ auto uc = curl_url_set(m_pUrl.get(), CURLUPART_URL, utf8URI.getStr(), 0);
+ if (uc != CURLUE_OK)
+ {
+ SAL_WARN("ucb.ucp.webdav.curl", "curl_url_set failed: " << uc);
+ throw DAVException(DAVException::DAV_INVALID_ARG);
+ }
+
+ Init();
+}
+
+CurlUri::CurlUri(CURLU /*const*/& rUrl)
+ : m_pUrl(curl_url_dup(&rUrl))
+{
+ if (!m_pUrl)
+ {
+ throw ::std::bad_alloc();
+ }
+
+ Init();
+}
+
+CurlUri::CurlUri(CurlUri const& rOther)
+ : m_pUrl(curl_url_dup(rOther.m_pUrl.get()))
+ , m_URI(rOther.m_URI)
+ , m_Scheme(rOther.m_Scheme)
+ , m_User(rOther.m_User)
+ , m_Password(rOther.m_Password)
+ , m_Host(rOther.m_Host)
+ , m_nPort(rOther.m_nPort)
+ , m_Path(rOther.m_Path)
+ , m_QueryAndFragment(rOther.m_QueryAndFragment)
+{
+ assert(rOther.m_pUrl);
+ if (!m_pUrl)
+ {
+ throw ::std::bad_alloc();
+ }
+}
+
+void CurlUri::operator=(CurlUri const& rOther)
+{
+ assert(rOther.m_pUrl);
+ m_pUrl.reset(curl_url_dup(rOther.m_pUrl.get()));
+ if (!m_pUrl)
+ {
+ throw ::std::bad_alloc();
+ }
+ m_URI = rOther.m_URI;
+ m_Scheme = rOther.m_Scheme;
+ m_User = rOther.m_User;
+ m_Password = rOther.m_Password;
+ m_Host = rOther.m_Host;
+ m_nPort = rOther.m_nPort;
+ m_Path = rOther.m_Path;
+ m_QueryAndFragment = rOther.m_QueryAndFragment;
+}
+
+bool CurlUri::operator==(CurlUri const& rOther) const { return m_URI == rOther.m_URI; }
+
+OUString CurlUri::GetPathBaseName() const
+{
+ sal_Int32 nPos = m_Path.lastIndexOf('/');
+ sal_Int32 nTrail = 0;
+ if (nPos == m_Path.getLength() - 1)
+ {
+ // Trailing slash found. Skip.
+ nTrail = 1;
+ nPos = m_Path.lastIndexOf('/', nPos);
+ }
+ if (nPos == -1)
+ {
+ return "/";
+ }
+ return m_Path.copy(nPos + 1, m_Path.getLength() - nPos - 1 - nTrail);
+}
+
+OUString CurlUri::GetPathBaseNameUnescaped() const { return DecodeURI(GetPathBaseName()); }
+
+void CurlUri::SetScheme(::std::u16string_view const rScheme)
+{
+ OString const utf8URI(OUStringToOString(rScheme, RTL_TEXTENCODING_UTF8));
+ auto uc = curl_url_set(m_pUrl.get(), CURLUPART_SCHEME, utf8URI.getStr(), 0);
+ if (uc != CURLUE_OK)
+ {
+ SAL_WARN("ucb.ucp.webdav.curl", "curl_url_set failed: " << uc);
+ throw DAVException(DAVException::DAV_INVALID_ARG);
+ }
+ auto const oURI(GetURLComponent(*m_pUrl, CURLUPART_URL, CURLUE_OK, CURLU_NO_DEFAULT_PORT));
+ assert(oURI);
+ m_URI = *oURI;
+ auto const oScheme(GetURLComponent(*m_pUrl, CURLUPART_SCHEME, CURLUE_NO_SCHEME));
+ if (oScheme)
+ {
+ m_Scheme = *oScheme;
+ }
+}
+
+void CurlUri::AppendPath(::std::u16string_view const rPath)
+{
+ OUStringBuffer path(m_Path);
+ if (path.lastIndexOf('/') != path.getLength() - 1)
+ {
+ path.append("/");
+ }
+ path.append(rPath);
+ OString const utf8Path(OUStringToOString(path, RTL_TEXTENCODING_UTF8));
+ auto uc = curl_url_set(m_pUrl.get(), CURLUPART_PATH, utf8Path.getStr(), 0);
+ if (uc != CURLUE_OK)
+ {
+ SAL_WARN("ucb.ucp.webdav.curl", "curl_url_set failed: " << uc);
+ throw DAVException(DAVException::DAV_INVALID_ARG);
+ }
+ auto const oURI(GetURLComponent(*m_pUrl, CURLUPART_URL, CURLUE_OK, CURLU_NO_DEFAULT_PORT));
+ assert(oURI);
+ m_URI = *oURI;
+ auto const oPath(GetURLComponent(*m_pUrl, CURLUPART_PATH, CURLUE_OK));
+ assert(oPath);
+ m_Path = *oPath;
+}
+
+CurlUri CurlUri::CloneWithRelativeRefPathAbsolute(std::u16string_view rRelativeRef) const
+{
+ ::std::unique_ptr<CURLU, deleter_from_fn<CURLU, curl_url_cleanup>> pUrl(
+ curl_url_dup(m_pUrl.get()));
+ size_t indexEnd(rRelativeRef.size());
+ auto const indexQuery(rRelativeRef.find('?'));
+ auto const indexFragment(rRelativeRef.find('#'));
+ CURLUcode uc;
+ if (indexFragment != std::u16string_view::npos)
+ {
+ std::u16string_view const fragment(rRelativeRef.substr(indexFragment + 1));
+ indexEnd = indexFragment;
+ OString const utf8Fragment(OUStringToOString(fragment, RTL_TEXTENCODING_UTF8));
+ uc = curl_url_set(pUrl.get(), CURLUPART_FRAGMENT, utf8Fragment.getStr(), 0);
+ }
+ else
+ {
+ uc = curl_url_set(pUrl.get(), CURLUPART_FRAGMENT, nullptr, 0);
+ }
+ if (uc != CURLUE_OK)
+ {
+ SAL_WARN("ucb.ucp.webdav.curl", "curl_url_set failed: " << uc);
+ throw DAVException(DAVException::DAV_INVALID_ARG);
+ }
+ if (indexQuery != std::u16string_view::npos
+ && (indexFragment == std::u16string_view::npos || indexQuery < indexFragment))
+ {
+ std::u16string_view const query(
+ rRelativeRef.substr(indexQuery + 1, indexEnd - indexQuery - 1));
+ indexEnd = indexQuery;
+ OString const utf8Query(OUStringToOString(query, RTL_TEXTENCODING_UTF8));
+ uc = curl_url_set(pUrl.get(), CURLUPART_QUERY, utf8Query.getStr(), 0);
+ }
+ else
+ {
+ uc = curl_url_set(pUrl.get(), CURLUPART_QUERY, nullptr, 0);
+ }
+ if (uc != CURLUE_OK)
+ {
+ SAL_WARN("ucb.ucp.webdav.curl", "curl_url_set failed: " << uc);
+ throw DAVException(DAVException::DAV_INVALID_ARG);
+ }
+ std::u16string_view const path(rRelativeRef.substr(0, indexEnd));
+ OString const utf8Path(OUStringToOString(path, RTL_TEXTENCODING_UTF8));
+ uc = curl_url_set(pUrl.get(), CURLUPART_PATH, utf8Path.getStr(), 0);
+ if (uc != CURLUE_OK)
+ {
+ SAL_WARN("ucb.ucp.webdav.curl", "curl_url_set failed: " << uc);
+ throw DAVException(DAVException::DAV_INVALID_ARG);
+ }
+ return CurlUri(*pUrl.release());
+}
+
+OUString EncodeSegment(OUString const& rSegment)
+{
+ return rtl::Uri::encode(rSegment, rtl_UriCharClassPchar, rtl_UriEncodeIgnoreEscapes,
+ RTL_TEXTENCODING_UTF8);
+}
+
+OUString DecodeURI(OUString const& rURI)
+{
+ return rtl::Uri::decode(rURI, rtl_UriDecodeWithCharset, RTL_TEXTENCODING_UTF8);
+}
+
+OUString ConnectionEndPointString(std::u16string_view rHostName, sal_uInt16 const nPort)
+{
+ OUStringBuffer aBuf;
+
+ // Is host a numeric IPv6 address?
+ if ((rHostName.find(':') != std::u16string_view::npos) && (rHostName[0] != '['))
+ {
+ aBuf.append(OUString::Concat("[") + rHostName + "]");
+ }
+ else
+ {
+ aBuf.append(rHostName);
+ }
+
+ if ((nPort != DEFAULT_HTTP_PORT) && (nPort != DEFAULT_HTTPS_PORT))
+ {
+ aBuf.append(":" + OUString::number(sal_Int32(nPort)));
+ }
+ return aBuf.makeStringAndClear();
+}
+
+} // namespace http_dav_ucp
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab cinoptions=b1,g0,N-s cinkeys+=0=break: */
diff --git a/ucb/source/ucp/webdav-curl/CurlUri.hxx b/ucb/source/ucp/webdav-curl/CurlUri.hxx
new file mode 100644
index 0000000000..70a4ffb55c
--- /dev/null
+++ b/ucb/source/ucp/webdav-curl/CurlUri.hxx
@@ -0,0 +1,94 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4; fill-column: 100 -*- */
+/*
+ * 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:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#pragma once
+
+#include <curl/curl.h>
+
+#include <memory>
+
+#include <rtl/ustring.hxx>
+
+#include "DAVException.hxx"
+
+namespace http_dav_ucp
+{
+template <typename T, auto fn> struct deleter_from_fn
+{
+ void operator()(T* p) const { fn(p); }
+};
+template <typename T> using CurlUniquePtr = ::std::unique_ptr<T, deleter_from_fn<T, curl_free>>;
+
+class CurlUri
+{
+private:
+ /// native curl representation of parsed URI
+ ::std::unique_ptr<CURLU, deleter_from_fn<CURLU, curl_url_cleanup>> m_pUrl;
+ /// duplicate state for quick access to some components
+ OUString m_URI;
+ OUString m_Scheme;
+ OUString m_User;
+ OUString m_Password;
+ OUString m_Host;
+ sal_uInt16 m_nPort = 0;
+ OUString m_Path;
+ OUString m_QueryAndFragment;
+
+ void Init();
+
+public:
+ CurlUri(CurlUri const& rUri);
+ CurlUri(CURLU /*const*/& rUrl);
+ void operator=(CurlUri const& rOther);
+
+ /// @throws DAVException
+ explicit CurlUri(::std::u16string_view rURI);
+
+ bool operator==(CurlUri const& rOther) const;
+
+ CURLU const* GetCURLU() const { return m_pUrl.get(); }
+ OUString const& GetURI() const { return m_URI; }
+ OUString const& GetScheme() const { return m_Scheme; }
+ OUString const& GetUser() const { return m_User; }
+ OUString const& GetPassword() const { return m_Password; }
+ OUString const& GetHost() const { return m_Host; }
+ sal_uInt16 GetPort() const { return m_nPort; }
+ OUString const& GetPath() const { return m_Path; }
+ OUString GetRelativeReference() const { return m_Path + m_QueryAndFragment; }
+
+ OUString GetPathBaseName() const;
+
+ OUString GetPathBaseNameUnescaped() const;
+
+ /// @throws DAVException
+ void SetScheme(::std::u16string_view rScheme);
+ /// @throws DAVException
+ void AppendPath(::std::u16string_view rPath);
+ /// @param matches: relative-ref = path-absolute [ "?" query ] [ "#" fragment ]
+ /// @throws DAVException
+ CurlUri CloneWithRelativeRefPathAbsolute(std::u16string_view rRelativeRef) const;
+};
+
+OUString EncodeSegment(OUString const& rSegment);
+OUString DecodeURI(OUString const& rURI);
+OUString ConnectionEndPointString(std::u16string_view rHost, sal_uInt16 nPort);
+
+} // namespace http_dav_ucp
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab cinoptions=b1,g0,N-s cinkeys+=0=break: */
diff --git a/ucb/source/ucp/webdav-curl/DAVAuthListener.hxx b/ucb/source/ucp/webdav-curl/DAVAuthListener.hxx
new file mode 100644
index 0000000000..c3d643bc7d
--- /dev/null
+++ b/ucb/source/ucp/webdav-curl/DAVAuthListener.hxx
@@ -0,0 +1,43 @@
+/* -*- 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:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+
+#pragma once
+
+#include <salhelper/simplereferenceobject.hxx>
+#include <rtl/ustring.hxx>
+
+namespace http_dav_ucp
+{
+
+class DAVAuthListener : public salhelper::SimpleReferenceObject
+{
+ public:
+ virtual int authenticate(
+ const OUString & inRealm,
+ const OUString & inHostName,
+ OUString & inoutUserName,
+ OUString & outPassWord,
+ bool bCanUseSystemCredentials,
+ bool bUsePreviousCredentials = true ) = 0;
+};
+
+} // namespace http_dav_ucp
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/ucb/source/ucp/webdav-curl/DAVAuthListenerImpl.hxx b/ucb/source/ucp/webdav-curl/DAVAuthListenerImpl.hxx
new file mode 100644
index 0000000000..700c7c4c61
--- /dev/null
+++ b/ucb/source/ucp/webdav-curl/DAVAuthListenerImpl.hxx
@@ -0,0 +1,62 @@
+/* -*- 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:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+
+#pragma once
+
+#include "DAVAuthListener.hxx"
+#include <com/sun/star/ucb/XCommandEnvironment.hpp>
+#include <utility>
+
+
+namespace http_dav_ucp
+{
+
+
+
+
+ class DAVAuthListener_Impl : public DAVAuthListener
+ {
+ public:
+
+ DAVAuthListener_Impl(
+ css::uno::Reference<css::ucb::XCommandEnvironment> xEnv,
+ OUString inURL )
+ : m_xEnv(std::move( xEnv )), m_aURL(std::move( inURL ))
+ {
+ }
+
+ virtual int authenticate( const OUString & inRealm,
+ const OUString & inHostName,
+ OUString & inoutUserName,
+ OUString & outPassWord,
+ bool bCanUseSystemCredentials,
+ bool bUsePreviousCredentials = true ) override;
+ private:
+
+ const css::uno::Reference< css::ucb::XCommandEnvironment > m_xEnv;
+ const OUString m_aURL;
+
+ OUString m_aPrevPassword;
+ OUString m_aPrevUsername;
+ };
+
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/ucb/source/ucp/webdav-curl/DAVException.hxx b/ucb/source/ucp/webdav-curl/DAVException.hxx
new file mode 100644
index 0000000000..759e43f25f
--- /dev/null
+++ b/ucb/source/ucp/webdav-curl/DAVException.hxx
@@ -0,0 +1,176 @@
+/* -*- 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:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+
+#pragma once
+
+#include <rtl/ustring.hxx>
+#include <utility>
+
+namespace http_dav_ucp
+{
+
+
+// HTTP/WebDAV status codes
+
+
+const sal_uInt16 SC_NONE = 0;
+
+// 1xx (Informational - no errors)
+const sal_uInt16 SC_CONTINUE = 100;
+const sal_uInt16 SC_SWITCHING_PROTOCOLS = 101;
+// DAV extensions
+const sal_uInt16 SC_PROCESSING = 102;
+
+//2xx (Successful - no errors)
+const sal_uInt16 SC_OK = 200;
+const sal_uInt16 SC_CREATED = 201;
+const sal_uInt16 SC_ACCEPTED = 202;
+const sal_uInt16 SC_NON_AUTHORITATIVE_INFORMATION = 203;
+const sal_uInt16 SC_NO_CONTENT = 204;
+const sal_uInt16 SC_RESET_CONTENT = 205;
+const sal_uInt16 SC_PARTIAL_CONTENT = 206;
+// DAV extensions
+const sal_uInt16 SC_MULTISTATUS = 207;
+
+//3xx (Redirection)
+const sal_uInt16 SC_MULTIPLE_CHOICES = 300;
+const sal_uInt16 SC_MOVED_PERMANENTLY = 301;
+const sal_uInt16 SC_MOVED_TEMPORARILY = 302;
+const sal_uInt16 SC_SEE_OTHER = 303;
+const sal_uInt16 SC_NOT_MODIFIED = 304;
+const sal_uInt16 SC_USE_PROXY = 305;
+const sal_uInt16 SC_TEMPORARY_REDIRECT = 307;
+
+//4xx (Client error)
+const sal_uInt16 SC_BAD_REQUEST = 400;
+const sal_uInt16 SC_UNAUTHORIZED = 401;
+const sal_uInt16 SC_PAYMENT_REQUIRED = 402;
+const sal_uInt16 SC_FORBIDDEN = 403;
+const sal_uInt16 SC_NOT_FOUND = 404;
+const sal_uInt16 SC_METHOD_NOT_ALLOWED = 405;
+const sal_uInt16 SC_NOT_ACCEPTABLE = 406;
+const sal_uInt16 SC_PROXY_AUTHENTICATION_REQUIRED = 407;
+const sal_uInt16 SC_REQUEST_TIMEOUT = 408;
+const sal_uInt16 SC_CONFLICT = 409;
+const sal_uInt16 SC_GONE = 410;
+const sal_uInt16 SC_LENGTH_REQUIRED = 411;
+const sal_uInt16 SC_PRECONDITION_FAILED = 412;
+const sal_uInt16 SC_REQUEST_ENTITY_TOO_LARGE = 413;
+const sal_uInt16 SC_REQUEST_URI_TOO_LONG = 414;
+const sal_uInt16 SC_UNSUPPORTED_MEDIA_TYPE = 415;
+const sal_uInt16 SC_REQUESTED_RANGE_NOT_SATISFIABLE = 416;
+const sal_uInt16 SC_EXPECTATION_FAILED = 417;
+// DAV extensions
+const sal_uInt16 SC_UNPROCESSABLE_ENTITY = 422;
+const sal_uInt16 SC_LOCKED = 423;
+const sal_uInt16 SC_FAILED_DEPENDENCY = 424;
+
+//5xx (Server error, general <https://tools.ietf.org/html/rfc7231#section-6.6>)
+const sal_uInt16 SC_INTERNAL_SERVER_ERROR = 500;
+const sal_uInt16 SC_NOT_IMPLEMENTED = 501;
+const sal_uInt16 SC_BAD_GATEWAY = 502;
+const sal_uInt16 SC_SERVICE_UNAVAILABLE = 503;
+const sal_uInt16 SC_GATEWAY_TIMEOUT = 504;
+const sal_uInt16 SC_HTTP_VERSION_NOT_SUPPORTED = 505;
+// DAV extensions (<https://tools.ietf.org/html/rfc4918#section-11>)
+const sal_uInt16 SC_INSUFFICIENT_STORAGE = 507;
+
+// unofficial status codes only used internally by LO
+// used to cache the connection time out event
+const sal_uInt16 USC_CONNECTION_TIMED_OUT = 908;
+ // name resolution failed
+const sal_uInt16 USC_LOOKUP_FAILED = 909;
+const sal_uInt16 USC_AUTH_FAILED = 910;
+const sal_uInt16 USC_AUTHPROXY_FAILED = 911;
+
+
+
+class DAVException : public std::exception
+{
+ public:
+ enum ExceptionCode {
+ DAV_HTTP_ERROR = 0, // Generic error,
+ // mData = server error message,
+ // mStatusCode = HTTP status code
+ DAV_HTTP_LOOKUP, // Name lookup failed,
+ // mData = server[:port]
+ DAV_HTTP_NOAUTH, // No User authentication data provided - e.g., user aborts corresponding dialog
+ // mData = server[:port]
+ DAV_HTTP_AUTH, // User authentication failed on server,
+ // mData = server[:port]
+ DAV_HTTP_AUTHPROXY, // User authentication failed on proxy,
+ // mData = proxy server[:port]
+ DAV_HTTP_CONNECT, // Could not connect to server,
+ // mData = server[:port]
+ DAV_HTTP_TIMEOUT, // Connection timed out
+ // mData = server[:port]
+ DAV_HTTP_FAILED, // The precondition failed
+ // mData = server[:port]
+ DAV_HTTP_RETRY, // Retry request
+ // mData = server[:port]
+ DAV_HTTP_REDIRECT, // Request was redirected,
+ // mData = new URL
+ DAV_SESSION_CREATE, // session creation error,
+ // mData = server[:port]
+ DAV_INVALID_ARG, // invalid argument
+ DAV_UNSUPPORTED, // internal to CurlSession
+
+ DAV_LOCK_EXPIRED, // DAV lock expired
+
+ DAV_NOT_LOCKED, // not locked
+
+ DAV_LOCKED_SELF, // locked by this OOo session
+
+ DAV_LOCKED // locked by third party
+ };
+
+ private:
+ ExceptionCode mExceptionCode;
+ OUString mData;
+ sal_uInt16 mStatusCode;
+
+ public:
+ explicit DAVException( ExceptionCode inExceptionCode )
+ : mExceptionCode( inExceptionCode )
+ , mData()
+ , mStatusCode( SC_NONE )
+ {};
+ DAVException( ExceptionCode inExceptionCode,
+ OUString aData )
+ : mExceptionCode( inExceptionCode )
+ , mData(std::move( aData ))
+ , mStatusCode( SC_NONE )
+ {};
+ DAVException( ExceptionCode inExceptionCode,
+ OUString aData,
+ sal_uInt16 nStatusCode )
+ : mExceptionCode( inExceptionCode )
+ , mData(std::move( aData ))
+ , mStatusCode( nStatusCode )
+ {};
+
+ const ExceptionCode & getError() const { return mExceptionCode; }
+ const OUString & getData() const { return mData; }
+ sal_uInt16 getStatus() const { return mStatusCode; }
+};
+
+} // namespace http_dav_ucp
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/ucb/source/ucp/webdav-curl/DAVProperties.cxx b/ucb/source/ucp/webdav-curl/DAVProperties.cxx
new file mode 100644
index 0000000000..bd0be1a862
--- /dev/null
+++ b/ucb/source/ucp/webdav-curl/DAVProperties.cxx
@@ -0,0 +1,204 @@
+/* -*- 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:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include "DAVProperties.hxx"
+#include <rtl/ustrbuf.hxx>
+#include <o3tl/string_view.hxx>
+#include <string.h>
+
+using namespace http_dav_ucp;
+
+
+// static
+void DAVProperties::createSerfPropName( ::std::u16string_view const rFullName,
+ SerfPropName & rName )
+{
+ if (o3tl::starts_with(rFullName, u"DAV:"))
+ {
+ rName.nspace = "DAV:";
+ rName.name
+ = strdup( OUStringToOString(
+ rFullName.substr(RTL_CONSTASCII_LENGTH("DAV:")),
+ RTL_TEXTENCODING_UTF8 ).getStr() );
+ }
+ else if (o3tl::starts_with(rFullName, u"http://apache.org/dav/props/"))
+ {
+ rName.nspace = "http://apache.org/dav/props/";
+ rName.name
+ = strdup( OUStringToOString(
+ rFullName.substr(
+ RTL_CONSTASCII_LENGTH(
+ "http://apache.org/dav/props/" ) ),
+ RTL_TEXTENCODING_UTF8 ).getStr() );
+ }
+ else if (o3tl::starts_with(rFullName, u"http://ucb.openoffice.org/dav/props/"))
+ {
+ rName.nspace = "http://ucb.openoffice.org/dav/props/";
+ rName.name
+ = strdup( OUStringToOString(
+ rFullName.substr(
+ RTL_CONSTASCII_LENGTH(
+ "http://ucb.openoffice.org/dav/props/" ) ),
+ RTL_TEXTENCODING_UTF8 ).getStr() );
+ }
+ else if (o3tl::starts_with(rFullName, u"<prop:"))
+ {
+ // Support for 3rd party namespaces/props
+
+ OString aFullName
+ = OUStringToOString( rFullName, RTL_TEXTENCODING_UTF8 );
+
+ // Format: <prop:the_propname xmlns:prop="the_namespace">
+
+ sal_Int32 nStart = RTL_CONSTASCII_LENGTH( "<prop:" );
+ sal_Int32 nLen = aFullName.indexOf( ' ' ) - nStart;
+ rName.name = strdup( aFullName.copy( nStart, nLen ).getStr() );
+
+ nStart = aFullName.indexOf( '=', nStart + nLen ) + 2; // after ="
+ nLen = aFullName.getLength() - RTL_CONSTASCII_LENGTH( "\">" ) - nStart;
+ rName.nspace = strdup( aFullName.copy( nStart, nLen ).getStr() );
+ }
+ else
+ {
+ // this must not be a URI - WebDAVResponseParser must have converted it
+ // to the "<prop:" form above
+ assert(rFullName.find(':') == ::std::u16string_view::npos);
+ // Add our namespace to our own properties.
+ rName.nspace = "http://ucb.openoffice.org/dav/props/";
+ rName.name
+ = strdup( OUStringToOString( rFullName,
+ RTL_TEXTENCODING_UTF8 ).getStr() );
+ }
+}
+
+
+// static
+void DAVProperties::createUCBPropName( const char * nspace,
+ const char * name,
+ OUString & rFullName )
+{
+ OUString aNameSpace
+ = OStringToOUString( nspace, RTL_TEXTENCODING_UTF8 );
+ OUString aName
+ = OStringToOUString( name, RTL_TEXTENCODING_UTF8 );
+
+ if ( !aNameSpace.getLength() )
+ {
+ // Some servers send XML without proper namespaces. Assume "DAV:"
+ // in this case, if name is a well-known dav property name.
+ // Although this is not 100% correct, it solves many problems.
+
+ if (o3tl::equalsIgnoreAsciiCase(aName, std::u16string_view(DAVProperties::RESOURCETYPE).substr(4)) ||
+ o3tl::equalsIgnoreAsciiCase(aName, std::u16string_view(DAVProperties::SUPPORTEDLOCK).substr(4)) ||
+ o3tl::equalsIgnoreAsciiCase(aName, std::u16string_view(DAVProperties::LOCKDISCOVERY).substr(4)) ||
+ o3tl::equalsIgnoreAsciiCase(aName, std::u16string_view(DAVProperties::CREATIONDATE).substr(4)) ||
+ o3tl::equalsIgnoreAsciiCase(aName, std::u16string_view(DAVProperties::DISPLAYNAME).substr(4)) ||
+ o3tl::equalsIgnoreAsciiCase(aName, std::u16string_view(DAVProperties::GETCONTENTLANGUAGE).substr(4)) ||
+ o3tl::equalsIgnoreAsciiCase(aName, std::u16string_view(DAVProperties::GETCONTENTLENGTH).substr(4)) ||
+ o3tl::equalsIgnoreAsciiCase(aName, std::u16string_view(DAVProperties::GETCONTENTTYPE).substr(4)) ||
+ o3tl::equalsIgnoreAsciiCase(aName, std::u16string_view(DAVProperties::GETETAG).substr(4)) ||
+ o3tl::equalsIgnoreAsciiCase(aName, std::u16string_view(DAVProperties::GETLASTMODIFIED).substr(4)))
+ {
+ aNameSpace = "DAV:";
+ }
+ }
+
+ // Note: Concatenating strings BEFORE comparing against known namespaces
+ // is important. See RFC 2815 ( 23.4.2 Meaning of Qualified Names ).
+ rFullName = aNameSpace;
+ rFullName += aName;
+
+ if ( rFullName.startsWith( "DAV:" ) )
+ {
+ // Okay, Just concat strings.
+ }
+ else if ( rFullName.startsWith( "http://apache.org/dav/props/" ) )
+ {
+ // Okay, Just concat strings.
+ }
+ else if ( rFullName.startsWith( "http://ucb.openoffice.org/dav/props/" ) )
+ {
+ // Remove namespace from our own properties.
+ rFullName = rFullName.copy(
+ RTL_CONSTASCII_LENGTH(
+ "http://ucb.openoffice.org/dav/props/" ) );
+ }
+ else
+ {
+ // Create property name that encodes, namespace and name ( XML ).
+ rFullName = "<prop:";
+ rFullName += aName;
+ rFullName += " xmlns:prop=\"";
+ rFullName += aNameSpace;
+ rFullName += "\">";
+ }
+}
+
+
+// static
+bool DAVProperties::isUCBDeadProperty( const SerfPropName & rName )
+{
+ return ( rName.nspace &&
+ ( rtl_str_compareIgnoreAsciiCase(
+ rName.nspace, "http://ucb.openoffice.org/dav/props/" )
+ == 0 ) );
+}
+
+bool DAVProperties::isUCBSpecialProperty(std::u16string_view rFullName, OUString& rParsedName)
+{
+ size_t nLen = rFullName.size();
+ if ( nLen <= 0 ||
+ !o3tl::starts_with(rFullName, u"<prop:" ) ||
+ !o3tl::starts_with(rFullName, u"\">" ) )
+ return false;
+
+ sal_Int32 nStart = RTL_CONSTASCII_LENGTH( "<prop:" );
+ size_t nEnd = rFullName.find( ' ', nStart );
+ if ( nEnd == std::u16string_view::npos )
+ return false;
+
+ std::u16string_view sPropName = rFullName.substr( nStart, nEnd - nStart );
+ if ( sPropName.empty() )
+ return false;
+
+ // TODO skip whitespaces?
+ ++nEnd;
+ if ( !o3tl::starts_with(rFullName.substr(nEnd), u"xmlns:prop=\"" ) )
+ return false;
+
+ nStart = nEnd + RTL_CONSTASCII_LENGTH( "xmlns:prop=\"" );
+ nEnd = rFullName.find( '"', nStart );
+ if ( nEnd != nLen - RTL_CONSTASCII_LENGTH( "\">" ) )
+ return false;
+
+ std::u16string_view sNamesp = rFullName.substr( nStart, nEnd - nStart );
+ nLen = sNamesp.size();
+ if ( !nLen )
+ return false;
+
+ OUStringBuffer aBuff( sNamesp );
+ if ( sNamesp[nLen - 1] != '/' )
+ aBuff.append( '/' );
+ aBuff.append( sPropName );
+ rParsedName = aBuff.makeStringAndClear();
+
+ return rParsedName.getLength();
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/ucb/source/ucp/webdav-curl/DAVProperties.hxx b/ucb/source/ucp/webdav-curl/DAVProperties.hxx
new file mode 100644
index 0000000000..8466692612
--- /dev/null
+++ b/ucb/source/ucp/webdav-curl/DAVProperties.hxx
@@ -0,0 +1,57 @@
+/* -*- 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:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+
+#pragma once
+
+#include <rtl/ustring.hxx>
+
+namespace http_dav_ucp
+{
+
+typedef struct { const char *nspace, *name; } SerfPropName;
+
+struct DAVProperties
+{
+ static constexpr OUString CREATIONDATE = u"DAV:creationdate"_ustr;
+ static constexpr OUString DISPLAYNAME = u"DAV:displayname"_ustr;
+ static constexpr OUString GETCONTENTLANGUAGE = u"DAV:getcontentlanguage"_ustr;
+ static constexpr OUString GETCONTENTLENGTH = u"DAV:getcontentlength"_ustr;
+ static constexpr OUString GETCONTENTTYPE = u"DAV:getcontenttype"_ustr;
+ static constexpr OUString GETETAG = u"DAV:getetag"_ustr;
+ static constexpr OUString GETLASTMODIFIED = u"DAV:getlastmodified"_ustr;
+ static constexpr OUString LOCKDISCOVERY = u"DAV:lockdiscovery"_ustr;
+ static constexpr OUString RESOURCETYPE = u"DAV:resourcetype"_ustr;
+ static constexpr OUString SUPPORTEDLOCK = u"DAV:supportedlock"_ustr;
+ static constexpr OUString EXECUTABLE = u"http://apache.org/dav/props/executable"_ustr;
+
+ static void createSerfPropName( ::std::u16string_view rFullName,
+ SerfPropName & rName );
+ static void createUCBPropName ( const char * nspace,
+ const char * name,
+ OUString & rFullName );
+
+ static bool isUCBDeadProperty( const SerfPropName & rName );
+ static bool isUCBSpecialProperty( std::u16string_view rFullName,
+ OUString & rParsedName );
+};
+
+} // namespace http_dav_ucp
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/ucb/source/ucp/webdav-curl/DAVRequestEnvironment.hxx b/ucb/source/ucp/webdav-curl/DAVRequestEnvironment.hxx
new file mode 100644
index 0000000000..3bde76ed33
--- /dev/null
+++ b/ucb/source/ucp/webdav-curl/DAVRequestEnvironment.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:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+
+#pragma once
+
+#include <utility>
+#include <vector>
+#include <rtl/ref.hxx>
+#include "DAVAuthListener.hxx"
+
+namespace http_dav_ucp
+{
+ typedef std::pair< OUString, OUString > DAVRequestHeader;
+ typedef std::vector< DAVRequestHeader > DAVRequestHeaders;
+
+struct DAVRequestEnvironment
+{
+ rtl::Reference< DAVAuthListener > m_xAuthListener;
+ DAVRequestHeaders m_aRequestHeaders;
+
+ DAVRequestEnvironment( rtl::Reference< DAVAuthListener > xListener,
+ DAVRequestHeaders aRequestHeaders)
+ : m_xAuthListener(std::move( xListener )),
+ m_aRequestHeaders(std::move( aRequestHeaders )) {}
+};
+
+} // namespace http_dav_ucp
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/ucb/source/ucp/webdav-curl/DAVResource.hxx b/ucb/source/ucp/webdav-curl/DAVResource.hxx
new file mode 100644
index 0000000000..93cdd743f1
--- /dev/null
+++ b/ucb/source/ucp/webdav-curl/DAVResource.hxx
@@ -0,0 +1,58 @@
+/* -*- 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:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+
+#pragma once
+
+#include <vector>
+
+#include <rtl/ustring.hxx>
+#include <com/sun/star/uno/Any.hxx>
+
+namespace http_dav_ucp
+{
+
+struct DAVPropertyValue
+{
+ OUString Name;
+ css::uno::Any Value;
+ bool IsCaseSensitive;
+
+ DAVPropertyValue() : IsCaseSensitive( true ) {}
+};
+
+struct DAVResource
+{
+ OUString uri;
+ std::vector< DAVPropertyValue > properties;
+};
+
+struct DAVResourceInfo
+{
+ std::vector < OUString > properties;
+
+ bool operator==( const struct DAVResourceInfo& a ) const
+ {
+ return (properties == a.properties );
+ }
+};
+
+} // namespace http_dav_ucp
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/ucb/source/ucp/webdav-curl/DAVResourceAccess.cxx b/ucb/source/ucp/webdav-curl/DAVResourceAccess.cxx
new file mode 100644
index 0000000000..c1b775c08f
--- /dev/null
+++ b/ucb/source/ucp/webdav-curl/DAVResourceAccess.cxx
@@ -0,0 +1,1182 @@
+/* -*- 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:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+
+#include <com/sun/star/task/XInteractionAbort.hpp>
+#include <com/sun/star/ucb/XWebDAVCommandEnvironment.hpp>
+
+#include <ucbhelper/simpleauthenticationrequest.hxx>
+#include <comphelper/seekableinput.hxx>
+
+#include "DAVAuthListenerImpl.hxx"
+#include "DAVResourceAccess.hxx"
+#include "webdavprovider.hxx"
+
+#include <officecfg/Office/Security.hxx>
+
+#include <com/sun/star/lang/IllegalArgumentException.hpp>
+#include <com/sun/star/io/IOException.hpp>
+#include <utility>
+
+using namespace http_dav_ucp;
+using namespace com::sun::star;
+
+
+// DAVAuthListener_Impl Implementation.
+
+
+// virtual
+int DAVAuthListener_Impl::authenticate(
+ const OUString & inRealm,
+ const OUString & inHostName,
+ OUString & inoutUserName,
+ OUString & outPassWord,
+ bool bCanUseSystemCredentials,
+ bool bUsePreviousCredentials )
+{
+ if ( m_xEnv.is() )
+ {
+ uno::Reference< task::XInteractionHandler > xIH
+ = m_xEnv->getInteractionHandler();
+
+ if ( xIH.is() )
+ {
+ // Providing previously retrieved credentials will cause the password
+ // container to reject these. Thus, the credential input dialog will be shown again.
+ // #102871# - Supply username and password from previous try.
+ // Password container service depends on this!
+ if ( inoutUserName.isEmpty() && bUsePreviousCredentials )
+ inoutUserName = m_aPrevUsername;
+
+ if ( outPassWord.isEmpty() && bUsePreviousCredentials )
+ outPassWord = m_aPrevPassword;
+
+ rtl::Reference< ucbhelper::SimpleAuthenticationRequest > xRequest
+ = new ucbhelper::SimpleAuthenticationRequest(
+ m_aURL, inHostName, inRealm, inoutUserName,
+ outPassWord,
+ bCanUseSystemCredentials );
+ 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();
+
+ bool bUseSystemCredentials = false;
+
+ if ( bCanUseSystemCredentials )
+ bUseSystemCredentials
+ = xSupp->getUseSystemCredentials();
+
+ if ( bUseSystemCredentials )
+ {
+ // This is the (strange) way to tell neon to use
+ // system credentials.
+ inoutUserName.clear();
+ outPassWord.clear();
+ }
+ else
+ {
+ inoutUserName = xSupp->getUserName();
+ outPassWord = xSupp->getPassword();
+ }
+
+ // #102871# - Remember username and password.
+ m_aPrevUsername = inoutUserName;
+ m_aPrevPassword = outPassWord;
+
+ // go on.
+ return 0;
+ }
+ }
+ }
+ }
+ // Abort.
+ return -1;
+}
+
+
+// DAVResourceAccess Implementation.
+
+constexpr size_t g_nRedirectLimit = 5;
+
+DAVResourceAccess::DAVResourceAccess(
+ uno::Reference< uno::XComponentContext > xContext,
+ rtl::Reference< DAVSessionFactory > xSessionFactory,
+ OUString aURL )
+: m_aURL(std::move( aURL )),
+ m_xSessionFactory(std::move( xSessionFactory )),
+ m_xContext(std::move( xContext ))
+{
+}
+
+
+DAVResourceAccess::DAVResourceAccess( const DAVResourceAccess & rOther )
+: m_aURL( rOther.m_aURL ),
+ m_aPath( rOther.m_aPath ),
+ m_aFlags( rOther.m_aFlags ),
+ m_xSession( rOther.m_xSession ),
+ m_xSessionFactory( rOther.m_xSessionFactory ),
+ m_xContext( rOther.m_xContext ),
+ m_aRedirectURIs( rOther.m_aRedirectURIs )
+{
+}
+
+
+DAVResourceAccess & DAVResourceAccess::operator=(
+ const DAVResourceAccess & rOther )
+{
+ m_aURL = rOther.m_aURL;
+ m_aPath = rOther.m_aPath;
+ m_aFlags = rOther.m_aFlags;
+ m_xSession = rOther.m_xSession;
+ m_xSessionFactory = rOther.m_xSessionFactory;
+ m_xContext = rOther.m_xContext;
+ m_aRedirectURIs = rOther.m_aRedirectURIs;
+
+ return *this;
+}
+
+void DAVResourceAccess::OPTIONS(
+ DAVOptions & rOptions,
+ const uno::Reference< ucb::XCommandEnvironment > & xEnv )
+{
+ initialize();
+
+ int errorCount = 0;
+ bool bRetry;
+ do
+ {
+ bRetry = false;
+ try
+ {
+ DAVRequestHeaders aHeaders;
+
+ getUserRequestHeaders( xEnv,
+ getRequestURI(),
+ ucb::WebDAVHTTPMethod_OPTIONS,
+ aHeaders );
+
+ m_xSession->OPTIONS( getRequestURI(),
+ rOptions,
+ DAVRequestEnvironment(
+ new DAVAuthListener_Impl( xEnv, m_aURL ),
+ aHeaders ) );
+ }
+ catch (DAVException const& e)
+ {
+ errorCount++;
+ bRetry = handleException( e, errorCount );
+ if ( !bRetry )
+ throw;
+ }
+ }
+ while ( bRetry );
+}
+
+void DAVResourceAccess::PROPFIND(
+ const Depth nDepth,
+ const std::vector< OUString > & rPropertyNames,
+ std::vector< DAVResource > & rResources,
+ const uno::Reference< ucb::XCommandEnvironment > & xEnv )
+{
+ initialize();
+
+ int errorCount = 0;
+ bool bRetry;
+ do
+ {
+ bRetry = false;
+ try
+ {
+ DAVRequestHeaders aHeaders;
+
+ getUserRequestHeaders( xEnv,
+ getRequestURI(),
+ ucb::WebDAVHTTPMethod_PROPFIND,
+ aHeaders );
+
+ m_xSession->PROPFIND( getRequestURI(),
+ nDepth,
+ rPropertyNames,
+ rResources,
+ DAVRequestEnvironment(
+ new DAVAuthListener_Impl( xEnv, m_aURL ),
+ aHeaders ) );
+ }
+ catch (DAVException const& e)
+ {
+ errorCount++;
+ bRetry = handleException( e, errorCount );
+ if ( !bRetry )
+ throw;
+ }
+ }
+ while ( bRetry );
+}
+
+
+void DAVResourceAccess::PROPFIND(
+ const Depth nDepth,
+ std::vector< DAVResourceInfo > & rResInfo,
+ const uno::Reference< ucb::XCommandEnvironment > & xEnv )
+{
+ initialize();
+
+ int errorCount = 0;
+ bool bRetry;
+ do
+ {
+ bRetry = false;
+ try
+ {
+ DAVRequestHeaders aHeaders;
+ getUserRequestHeaders( xEnv,
+ getRequestURI(),
+ ucb::WebDAVHTTPMethod_PROPFIND,
+ aHeaders );
+
+ m_xSession->PROPFIND( getRequestURI(),
+ nDepth,
+ rResInfo,
+ DAVRequestEnvironment(
+ new DAVAuthListener_Impl( xEnv, m_aURL ),
+ aHeaders ) ) ;
+ }
+ catch (DAVException const& e)
+ {
+ errorCount++;
+ bRetry = handleException( e, errorCount );
+ if ( !bRetry )
+ throw;
+ }
+ }
+ while ( bRetry );
+}
+
+
+void DAVResourceAccess::PROPPATCH(
+ const std::vector< ProppatchValue >& rValues,
+ const uno::Reference< ucb::XCommandEnvironment >& xEnv )
+{
+ initialize();
+
+ int errorCount = 0;
+ bool bRetry;
+ do
+ {
+ bRetry = false;
+ try
+ {
+ DAVRequestHeaders aHeaders;
+ getUserRequestHeaders( xEnv,
+ getRequestURI(),
+ ucb::WebDAVHTTPMethod_PROPPATCH,
+ aHeaders );
+
+ m_xSession->PROPPATCH( getRequestURI(),
+ rValues,
+ DAVRequestEnvironment(
+ new DAVAuthListener_Impl( xEnv, m_aURL ),
+ aHeaders ) );
+ }
+ catch (DAVException const& e)
+ {
+ errorCount++;
+ bRetry = handleException( e, errorCount );
+ if ( !bRetry )
+ throw;
+ }
+ }
+ while ( bRetry );
+}
+
+
+void DAVResourceAccess::HEAD(
+ const std::vector< OUString > & rHeaderNames,
+ DAVResource & rResource,
+ const uno::Reference< ucb::XCommandEnvironment >& xEnv )
+{
+ initialize();
+
+ int errorCount = 0;
+ bool bRetry;
+ do
+ {
+ bRetry = false;
+ try
+ {
+ DAVRequestHeaders aHeaders;
+ getUserRequestHeaders( xEnv,
+ getRequestURI(),
+ ucb::WebDAVHTTPMethod_HEAD,
+ aHeaders );
+
+ m_xSession->HEAD( getRequestURI(),
+ rHeaderNames,
+ rResource,
+ DAVRequestEnvironment(
+ new DAVAuthListener_Impl( xEnv, m_aURL ),
+ aHeaders ) );
+ }
+ catch (DAVException const& e)
+ {
+ errorCount++;
+ bRetry = handleException( e, errorCount );
+ if ( !bRetry )
+ throw;
+ }
+ }
+ while ( bRetry );
+}
+
+
+uno::Reference< io::XInputStream > DAVResourceAccess::GET(
+ const uno::Reference< ucb::XCommandEnvironment > & xEnv )
+{
+ initialize();
+
+ uno::Reference< io::XInputStream > xStream;
+ int errorCount = 0;
+ bool bRetry;
+ do
+ {
+ bRetry = false;
+ try
+ {
+ DAVRequestHeaders aHeaders;
+ getUserRequestHeaders( xEnv,
+ getRequestURI(),
+ ucb::WebDAVHTTPMethod_GET,
+ aHeaders );
+
+ xStream = m_xSession->GET( getRequestURI(),
+ DAVRequestEnvironment(
+ new DAVAuthListener_Impl(
+ xEnv, m_aURL ),
+ aHeaders ) );
+ }
+ catch (DAVException const& e)
+ {
+ errorCount++;
+ bRetry = handleException( e, errorCount );
+ if ( !bRetry )
+ throw;
+ }
+ }
+ while ( bRetry );
+
+ return xStream;
+}
+
+
+void DAVResourceAccess::GET(
+ uno::Reference< io::XOutputStream > & rStream,
+ const uno::Reference< ucb::XCommandEnvironment > & xEnv )
+{
+ initialize();
+
+ int errorCount = 0;
+ bool bRetry;
+ do
+ {
+ bRetry = false;
+ try
+ {
+ DAVRequestHeaders aHeaders;
+ getUserRequestHeaders( xEnv,
+ getRequestURI(),
+ ucb::WebDAVHTTPMethod_GET,
+ aHeaders );
+
+ m_xSession->GET( getRequestURI(),
+ rStream,
+ DAVRequestEnvironment(
+ new DAVAuthListener_Impl( xEnv, m_aURL ),
+ aHeaders ) );
+ }
+ catch (DAVException const& e)
+ {
+ errorCount++;
+ bRetry = handleException( e, errorCount );
+ if ( !bRetry )
+ throw;
+ }
+ }
+ while ( bRetry );
+}
+
+
+uno::Reference< io::XInputStream > DAVResourceAccess::GET(
+ const std::vector< OUString > & rHeaderNames,
+ DAVResource & rResource,
+ const uno::Reference< ucb::XCommandEnvironment > & xEnv )
+{
+ initialize();
+
+ uno::Reference< io::XInputStream > xStream;
+ int errorCount = 0;
+ bool bRetry;
+ do
+ {
+ bRetry = false;
+ try
+ {
+ DAVRequestHeaders aHeaders;
+ getUserRequestHeaders( xEnv,
+ getRequestURI(),
+ ucb::WebDAVHTTPMethod_GET,
+ aHeaders );
+
+ xStream = m_xSession->GET( getRequestURI(),
+ rHeaderNames,
+ rResource,
+ DAVRequestEnvironment(
+ new DAVAuthListener_Impl(
+ xEnv, m_aURL ),
+ aHeaders ) );
+ }
+ catch (DAVException const& e)
+ {
+ errorCount++;
+ bRetry = handleException( e, errorCount );
+ if ( !bRetry )
+ throw;
+ }
+ }
+ while ( bRetry );
+
+ return xStream;
+}
+
+
+// used as HEAD substitute when HEAD is not implemented on server
+void DAVResourceAccess::GET0(
+ DAVRequestHeaders &rRequestHeaders,
+ const std::vector< OUString > & rHeaderNames,
+ DAVResource & rResource,
+ const uno::Reference< ucb::XCommandEnvironment > & xEnv )
+{
+ initialize();
+
+ int errorCount = 0;
+ bool bRetry;
+ do
+ {
+ bRetry = false;
+ try
+ {
+ getUserRequestHeaders( xEnv,
+ getRequestURI(),
+ ucb::WebDAVHTTPMethod_GET,
+ rRequestHeaders );
+
+ m_xSession->GET( getRequestURI(),
+ rHeaderNames,
+ rResource,
+ DAVRequestEnvironment(
+ new DAVAuthListener_Impl(
+ xEnv, m_aURL ),
+ rRequestHeaders ) );
+ }
+ catch (DAVException const& e)
+ {
+ errorCount++;
+ bRetry = handleException( e, errorCount );
+ if ( !bRetry )
+ throw;
+ }
+ }
+ while ( bRetry );
+}
+
+
+void DAVResourceAccess::GET(
+ uno::Reference< io::XOutputStream > & rStream,
+ const std::vector< OUString > & rHeaderNames,
+ DAVResource & rResource,
+ const uno::Reference< ucb::XCommandEnvironment > & xEnv )
+{
+ initialize();
+
+ bool bRetry;
+ int errorCount = 0;
+ do
+ {
+ bRetry = false;
+ try
+ {
+ DAVRequestHeaders aHeaders;
+ getUserRequestHeaders( xEnv,
+ getRequestURI(),
+ ucb::WebDAVHTTPMethod_GET,
+ aHeaders );
+
+ m_xSession->GET( getRequestURI(),
+ rStream,
+ rHeaderNames,
+ rResource,
+ DAVRequestEnvironment(
+ new DAVAuthListener_Impl( xEnv, m_aURL ),
+ aHeaders ) );
+ }
+ catch (DAVException const& e)
+ {
+ errorCount++;
+ bRetry = handleException( e, errorCount );
+ if ( !bRetry )
+ throw;
+ }
+ }
+ while ( bRetry );
+}
+
+
+void DAVResourceAccess::abort()
+{
+ // seems pointless to call initialize() here, but prepare for nullptr
+ decltype(m_xSession) xSession;
+ {
+ osl::Guard<osl::Mutex> const g(m_aMutex);
+ xSession = m_xSession;
+ }
+ if (xSession.is())
+ {
+ xSession->abort();
+ }
+}
+
+
+namespace {
+
+ /// @throws DAVException
+ void resetInputStream( const uno::Reference< io::XInputStream > & rStream )
+ {
+ try
+ {
+ uno::Reference< io::XSeekable > xSeekable(
+ rStream, uno::UNO_QUERY );
+ if ( xSeekable.is() )
+ {
+ xSeekable->seek( 0 );
+ return;
+ }
+ }
+ catch ( lang::IllegalArgumentException const & )
+ {
+ }
+ catch ( io::IOException const & )
+ {
+ }
+
+ throw DAVException( DAVException::DAV_INVALID_ARG );
+ }
+
+} // namespace
+
+
+void DAVResourceAccess::PUT(
+ const uno::Reference< io::XInputStream > & rStream,
+ const uno::Reference< ucb::XCommandEnvironment > & xEnv )
+{
+ initialize();
+
+ // Make stream seekable, if it not. Needed, if request must be retried.
+ uno::Reference< io::XInputStream > xSeekableStream
+ = comphelper::OSeekableInputWrapper::CheckSeekableCanWrap(
+ rStream, m_xContext );
+
+ int errorCount = 0;
+ bool bRetry = false;
+ do
+ {
+ if ( bRetry )
+ resetInputStream( xSeekableStream );
+
+ bRetry = false;
+ try
+ {
+ DAVRequestHeaders aHeaders;
+ getUserRequestHeaders( xEnv,
+ getRequestURI(),
+ ucb::WebDAVHTTPMethod_PUT,
+ aHeaders );
+
+ m_xSession->PUT( getRequestURI(),
+ xSeekableStream,
+ DAVRequestEnvironment(
+ new DAVAuthListener_Impl( xEnv, m_aURL ),
+ aHeaders ) );
+ }
+ catch (DAVException const& e)
+ {
+ errorCount++;
+ bRetry = handleException( e, errorCount );
+ if ( !bRetry )
+ throw;
+ }
+ }
+ while ( bRetry );
+}
+
+
+uno::Reference< io::XInputStream > DAVResourceAccess::POST(
+ const OUString & rContentType,
+ const OUString & rReferer,
+ const uno::Reference< io::XInputStream > & rInputStream,
+ const uno::Reference< ucb::XCommandEnvironment >& xEnv )
+{
+ initialize();
+
+ // Make stream seekable, if it not. Needed, if request must be retried.
+ uno::Reference< io::XInputStream > xSeekableStream
+ = comphelper::OSeekableInputWrapper::CheckSeekableCanWrap(
+ rInputStream, m_xContext );
+
+ uno::Reference< io::XInputStream > xStream;
+ int errorCount = 0;
+ bool bRetry = false;
+ do
+ {
+ if ( bRetry )
+ {
+ resetInputStream( xSeekableStream );
+ bRetry = false;
+ }
+
+ try
+ {
+ DAVRequestHeaders aHeaders;
+ getUserRequestHeaders( xEnv,
+ getRequestURI(),
+ ucb::WebDAVHTTPMethod_POST,
+ aHeaders );
+
+ xStream = m_xSession->POST( getRequestURI(),
+ rContentType,
+ rReferer,
+ xSeekableStream,
+ DAVRequestEnvironment(
+ new DAVAuthListener_Impl(
+ xEnv, m_aURL ),
+ aHeaders ) );
+ }
+ catch (DAVException const& e)
+ {
+ errorCount++;
+ bRetry = handleException( e, errorCount );
+ if ( !bRetry )
+ throw;
+
+ if ( e.getError() == DAVException::DAV_HTTP_REDIRECT )
+ {
+ // #i74980# - Upon POST redirect, do a GET.
+ return GET( xEnv );
+ }
+ }
+ }
+ while ( bRetry );
+
+ return xStream;
+}
+
+
+void DAVResourceAccess::POST(
+ const OUString & rContentType,
+ const OUString & rReferer,
+ const uno::Reference< io::XInputStream > & rInputStream,
+ uno::Reference< io::XOutputStream > & rOutputStream,
+ const uno::Reference< ucb::XCommandEnvironment >& xEnv )
+{
+ initialize();
+
+ // Make stream seekable, if it not. Needed, if request must be retried.
+ uno::Reference< io::XInputStream > xSeekableStream
+ = comphelper::OSeekableInputWrapper::CheckSeekableCanWrap(
+ rInputStream, m_xContext );
+
+ int errorCount = 0;
+ bool bRetry = false;
+ do
+ {
+ if ( bRetry )
+ {
+ resetInputStream( xSeekableStream );
+ bRetry = false;
+ }
+
+ try
+ {
+ DAVRequestHeaders aHeaders;
+ getUserRequestHeaders( xEnv,
+ getRequestURI(),
+ ucb::WebDAVHTTPMethod_POST,
+ aHeaders );
+
+ m_xSession->POST( getRequestURI(),
+ rContentType,
+ rReferer,
+ xSeekableStream,
+ rOutputStream,
+ DAVRequestEnvironment(
+ new DAVAuthListener_Impl( xEnv, m_aURL ),
+ aHeaders ) );
+ }
+ catch (DAVException const& e)
+ {
+ errorCount++;
+ bRetry = handleException( e, errorCount );
+ if ( !bRetry )
+ throw;
+
+ if ( e.getError() == DAVException::DAV_HTTP_REDIRECT )
+ {
+ // #i74980# - Upon POST redirect, do a GET.
+ GET( rOutputStream, xEnv );
+ return;
+ }
+ }
+ }
+ while ( bRetry );
+}
+
+
+void DAVResourceAccess::MKCOL(
+ const uno::Reference< ucb::XCommandEnvironment > & xEnv )
+{
+ initialize();
+
+ int errorCount = 0;
+ bool bRetry;
+ do
+ {
+ bRetry = false;
+ try
+ {
+ DAVRequestHeaders aHeaders;
+ getUserRequestHeaders( xEnv,
+ getRequestURI(),
+ ucb::WebDAVHTTPMethod_MKCOL,
+ aHeaders );
+
+ m_xSession->MKCOL( getRequestURI(),
+ DAVRequestEnvironment(
+ new DAVAuthListener_Impl( xEnv, m_aURL ),
+ aHeaders ) );
+ }
+ catch (DAVException const& e)
+ {
+ errorCount++;
+ bRetry = handleException( e, errorCount );
+ if ( !bRetry )
+ throw;
+ }
+ }
+ while ( bRetry );
+}
+
+
+void DAVResourceAccess::COPY(
+ const OUString & rSourcePath,
+ const OUString & rDestinationURI,
+ bool bOverwrite,
+ const uno::Reference< ucb::XCommandEnvironment > & xEnv )
+{
+ initialize();
+
+ int errorCount = 0;
+ bool bRetry;
+ do
+ {
+ bRetry = false;
+ try
+ {
+ DAVRequestHeaders aHeaders;
+ getUserRequestHeaders( xEnv,
+ getRequestURI(),
+ ucb::WebDAVHTTPMethod_COPY,
+ aHeaders );
+
+ m_xSession->COPY( rSourcePath,
+ rDestinationURI,
+ DAVRequestEnvironment(
+ new DAVAuthListener_Impl( xEnv, m_aURL ),
+ aHeaders ),
+ bOverwrite );
+ }
+ catch (DAVException const& e)
+ {
+ errorCount++;
+ bRetry = handleException( e, errorCount );
+ if ( !bRetry )
+ throw;
+ }
+ }
+ while ( bRetry );
+}
+
+
+void DAVResourceAccess::MOVE(
+ const OUString & rSourcePath,
+ const OUString & rDestinationURI,
+ bool bOverwrite,
+ const uno::Reference< ucb::XCommandEnvironment > & xEnv )
+{
+ initialize();
+
+ int errorCount = 0;
+ bool bRetry;
+ do
+ {
+ bRetry = false;
+ try
+ {
+ DAVRequestHeaders aHeaders;
+ getUserRequestHeaders( xEnv,
+ getRequestURI(),
+ ucb::WebDAVHTTPMethod_MOVE,
+ aHeaders );
+
+ m_xSession->MOVE( rSourcePath,
+ rDestinationURI,
+ DAVRequestEnvironment(
+ new DAVAuthListener_Impl( xEnv, m_aURL ),
+ aHeaders ),
+ bOverwrite );
+ }
+ catch (DAVException const& e)
+ {
+ errorCount++;
+ bRetry = handleException( e, errorCount );
+ if ( !bRetry )
+ throw;
+ }
+ }
+ while ( bRetry );
+}
+
+
+void DAVResourceAccess::DESTROY(
+ const uno::Reference< ucb::XCommandEnvironment > & xEnv )
+{
+ initialize();
+
+ int errorCount = 0;
+ bool bRetry;
+ do
+ {
+ bRetry = false;
+ try
+ {
+ DAVRequestHeaders aHeaders;
+ getUserRequestHeaders( xEnv,
+ getRequestURI(),
+ ucb::WebDAVHTTPMethod_DELETE,
+ aHeaders );
+
+ m_xSession->DESTROY( getRequestURI(),
+ DAVRequestEnvironment(
+ new DAVAuthListener_Impl( xEnv, m_aURL ),
+ aHeaders ) );
+ }
+ catch (DAVException const& e)
+ {
+ errorCount++;
+ bRetry = handleException( e, errorCount );
+ if ( !bRetry )
+ throw;
+ }
+ }
+ while ( bRetry );
+}
+
+
+// set new lock.
+void DAVResourceAccess::LOCK(
+ ucb::Lock & inLock,
+ const uno::Reference< ucb::XCommandEnvironment > & xEnv )
+{
+ initialize();
+
+ int errorCount = 0;
+ bool bRetry;
+ do
+ {
+ bRetry = false;
+ try
+ {
+ DAVRequestHeaders aHeaders;
+ getUserRequestHeaders( xEnv,
+ getRequestURI(),
+ ucb::WebDAVHTTPMethod_LOCK,
+ aHeaders );
+
+ m_xSession->LOCK( getRequestURI(),
+ inLock,
+ DAVRequestEnvironment(
+ new DAVAuthListener_Impl( xEnv, m_aURL ),
+ aHeaders ) );
+ }
+ catch (DAVException const& e)
+ {
+ errorCount++;
+ bRetry = handleException( e, errorCount );
+ if ( !bRetry )
+ throw;
+ }
+ }
+ while ( bRetry );
+}
+
+void DAVResourceAccess::UNLOCK(
+ const uno::Reference< ucb::XCommandEnvironment > & xEnv )
+{
+ initialize();
+
+ int errorCount = 0;
+ bool bRetry;
+ do
+ {
+ bRetry = false;
+ try
+ {
+ DAVRequestHeaders aHeaders;
+ getUserRequestHeaders( xEnv,
+ getRequestURI(),
+ ucb::WebDAVHTTPMethod_UNLOCK,
+ aHeaders );
+
+ m_xSession->UNLOCK( getRequestURI(),
+ DAVRequestEnvironment(
+ new DAVAuthListener_Impl( xEnv, m_aURL ),
+ aHeaders ) );
+ }
+ catch (DAVException const& e)
+ {
+ errorCount++;
+ bRetry = handleException( e, errorCount );
+ if ( !bRetry )
+ throw;
+ }
+ }
+ while ( bRetry );
+}
+
+void DAVResourceAccess::setFlags( const uno::Sequence< beans::NamedValue >& rFlags )
+{
+ osl::Guard< osl::Mutex > aGuard( m_aMutex );
+ m_aFlags = rFlags;
+}
+
+void DAVResourceAccess::setURL( const OUString & rNewURL )
+{
+ osl::Guard< osl::Mutex > aGuard( m_aMutex );
+ m_aURL = rNewURL;
+ m_aPath.clear(); // Next initialize() will create new session.
+}
+
+
+// init dav session and path
+void DAVResourceAccess::initialize()
+{
+ osl::Guard< osl::Mutex > aGuard( m_aMutex );
+ if ( m_aPath.isEmpty() )
+ {
+ CurlUri aURI(m_aURL);
+ assert(aURI.GetScheme() == HTTP_URL_SCHEME || aURI.GetScheme() == HTTPS_URL_SCHEME);
+ if (aURI.GetScheme() == HTTP_URL_SCHEME)
+ {
+ if (!officecfg::Office::Security::Net::AllowInsecureProtocols::get())
+ {
+ // "http" not allowed -> immediately redirect to "https",
+ // better than showing confusing error to user
+ aURI.SetScheme(HTTPS_URL_SCHEME);
+ }
+ }
+ OUString aPath( aURI.GetRelativeReference() );
+
+ /* #134089# - Check URI */
+ if ( aPath.isEmpty() )
+ throw DAVException( DAVException::DAV_INVALID_ARG );
+
+ /* #134089# - Check URI */
+ if ( aURI.GetHost().isEmpty() )
+ throw DAVException( DAVException::DAV_INVALID_ARG );
+
+ if ( !m_xSession.is() || !m_xSession->CanUse( m_aURL, m_aFlags ) )
+ {
+ m_xSession.clear();
+
+ // create new webdav session
+ m_xSession = m_xSessionFactory->createDAVSession(aURI.GetURI(), m_aFlags, m_xContext);
+
+ if ( !m_xSession.is() )
+ return;
+ }
+
+ // Own URI is needed to redirect cycle detection.
+ m_aRedirectURIs.push_back( aURI );
+
+ // Success.
+ m_aPath = aPath;
+
+ // Not only the path has to be encoded
+ m_aURL = aURI.GetURI();
+ }
+}
+
+
+const OUString & DAVResourceAccess::getRequestURI() const
+{
+ assert(m_xSession.is() &&
+ "DAVResourceAccess::getRequestURI - Not initialized!");
+
+ // In case a proxy is used we have to use the absolute URI for a request.
+ if ( m_xSession->UsesProxy() )
+ return m_aURL;
+
+ return m_aPath;
+}
+
+
+// static
+void DAVResourceAccess::getUserRequestHeaders(
+ const uno::Reference< ucb::XCommandEnvironment > & xEnv,
+ const OUString & rURI,
+ ucb::WebDAVHTTPMethod eMethod,
+ DAVRequestHeaders & rRequestHeaders )
+{
+ if ( !xEnv.is() )
+ return;
+
+ uno::Reference< ucb::XWebDAVCommandEnvironment > xDAVEnv(
+ xEnv, uno::UNO_QUERY );
+
+ if ( !xDAVEnv.is() )
+ return;
+
+ uno::Sequence< beans::StringPair > aRequestHeaders
+ = xDAVEnv->getUserRequestHeaders( rURI, eMethod );
+
+ for ( sal_Int32 n = 0; n < aRequestHeaders.getLength(); ++n )
+ {
+ rRequestHeaders.push_back(
+ DAVRequestHeader( aRequestHeaders[ n ].First,
+ aRequestHeaders[ n ].Second ) );
+ }
+}
+
+// This function member implements the control on cyclical redirections
+bool DAVResourceAccess::detectRedirectCycle(
+ ::std::u16string_view const rRedirectURL)
+{
+ osl::Guard< osl::Mutex > aGuard( m_aMutex );
+
+ CurlUri const aUri( rRedirectURL );
+
+ // Check for maximum number of redirections
+ // according to <https://tools.ietf.org/html/rfc7231#section-6.4>.
+ // A practical limit may be 5, due to earlier specifications:
+ // <https://tools.ietf.org/html/rfc2068#section-10.3>
+ // it can be raised keeping in mind the added net activity.
+ if( g_nRedirectLimit <= m_aRedirectURIs.size() )
+ return true;
+
+ // try to detect a cyclical redirection
+ return std::any_of(m_aRedirectURIs.begin(), m_aRedirectURIs.end(),
+ [&aUri](const CurlUri& rUri) { return aUri == rUri; });
+}
+
+
+void DAVResourceAccess::resetUri()
+{
+ osl::Guard< osl::Mutex > aGuard( m_aMutex );
+ if ( ! m_aRedirectURIs.empty() )
+ {
+ auto const it = m_aRedirectURIs.begin();
+
+ CurlUri const aUri( *it );
+ m_aRedirectURIs.clear();
+ setURL ( aUri.GetURI() );
+ initialize();
+ }
+}
+
+
+bool DAVResourceAccess::handleException(DAVException const& e, int const errorCount)
+{
+ switch ( e.getError() )
+ {
+ case DAVException::DAV_HTTP_REDIRECT:
+ if ( !detectRedirectCycle( e.getData() ) )
+ {
+ // set new URL and path.
+ setURL( e.getData() );
+ initialize();
+ return true;
+ }
+ return false;
+ // i#67048 copy & paste images doesn't display. This bug refers
+ // to an old OOo problem about getting resources from sites with a bad connection.
+ // If we have a bad connection try again. Up to three times.
+ case DAVException::DAV_HTTP_ERROR:
+ // retry up to three times, if not a client-side error (4xx error codes)
+ if ( e.getStatus() < SC_BAD_REQUEST && errorCount < 3 )
+ return true;
+ // check the server side errors
+ switch( e.getStatus() )
+ {
+ // the HTTP server side response status codes that can be retried
+ // [Serf TODO? i#119036] case SC_REQUEST_ENTITY_TOO_LARGE:
+ case SC_BAD_GATEWAY: // retry, can be an excessive load
+ case SC_GATEWAY_TIMEOUT: // retry, may be we get lucky
+ case SC_SERVICE_UNAVAILABLE: // retry, the service may become available
+ case SC_INSUFFICIENT_STORAGE: // space may be freed, retry
+ {
+ if ( errorCount < 3 )
+ return true;
+ else
+ return false;
+ }
+ break;
+ // all the other HTTP server response status codes are NOT retry
+ default:
+ return false;
+ }
+ break;
+ // if connection has said retry then retry!
+ case DAVException::DAV_HTTP_RETRY:
+ return true;
+ // <--
+ default:
+ return false; // Abort
+ }
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/ucb/source/ucp/webdav-curl/DAVResourceAccess.hxx b/ucb/source/ucp/webdav-curl/DAVResourceAccess.hxx
new file mode 100644
index 0000000000..5d5ea07bdd
--- /dev/null
+++ b/ucb/source/ucp/webdav-curl/DAVResourceAccess.hxx
@@ -0,0 +1,219 @@
+/* -*- 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:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+
+#pragma once
+
+#include <vector>
+#include <rtl/ustring.hxx>
+#include <rtl/ref.hxx>
+#include <osl/mutex.hxx>
+#include <com/sun/star/io/XInputStream.hpp>
+#include <com/sun/star/io/XOutputStream.hpp>
+#include <com/sun/star/beans/NamedValue.hpp>
+#include <com/sun/star/ucb/Lock.hpp>
+#include <com/sun/star/ucb/XCommandEnvironment.hpp>
+#include <com/sun/star/ucb/WebDAVHTTPMethod.hpp>
+#include "DAVAuthListener.hxx"
+#include "DAVException.hxx"
+#include "DAVSession.hxx"
+#include "DAVResource.hxx"
+#include "DAVTypes.hxx"
+#include "CurlUri.hxx"
+
+namespace http_dav_ucp
+{
+
+class DAVSessionFactory;
+
+class DAVResourceAccess
+{
+ osl::Mutex m_aMutex;
+ OUString m_aURL;
+ OUString m_aPath;
+ ::com::sun::star::uno::Sequence< ::com::sun::star::beans::NamedValue > m_aFlags;
+ rtl::Reference< DAVSession > m_xSession;
+ rtl::Reference< DAVSessionFactory > m_xSessionFactory;
+ css::uno::Reference< css::uno::XComponentContext > m_xContext;
+ std::vector<CurlUri> m_aRedirectURIs;
+
+public:
+ DAVResourceAccess() = default;
+ DAVResourceAccess( css::uno::Reference< css::uno::XComponentContext > xContext,
+ rtl::Reference< DAVSessionFactory > xSessionFactory,
+ OUString aURL );
+ DAVResourceAccess( const DAVResourceAccess & rOther );
+
+ DAVResourceAccess & operator=( const DAVResourceAccess & rOther );
+
+ /// @throws DAVException
+ void setFlags( const ::com::sun::star::uno::Sequence< ::com::sun::star::beans::NamedValue >& rFlags );
+
+ /// @throws DAVException
+ void setURL( const OUString & rNewURL );
+
+ void resetUri();
+
+ const OUString & getURL() const { return m_aURL; }
+
+ const rtl::Reference< DAVSessionFactory > & getSessionFactory() const
+ { return m_xSessionFactory; }
+
+ // DAV methods
+
+ /// @throws DAVException
+ void
+ OPTIONS(
+ DAVOptions & rOptions,
+ const css::uno::Reference< css::ucb::XCommandEnvironment > & xEnv );
+
+ // allprop & named
+ /// @throws DAVException
+ void
+ PROPFIND( const Depth nDepth,
+ const std::vector< OUString > & rPropertyNames,
+ std::vector< DAVResource > & rResources,
+ const css::uno::Reference< css::ucb::XCommandEnvironment > & xEnv );
+
+ // propnames
+ /// @throws DAVException
+ void
+ PROPFIND( const Depth nDepth,
+ std::vector< DAVResourceInfo > & rResInfo,
+ const css::uno::Reference< css::ucb::XCommandEnvironment > & xEnv );
+
+ /// @throws DAVException
+ void
+ PROPPATCH( const std::vector< ProppatchValue > & rValues,
+ const css::uno::Reference< css::ucb::XCommandEnvironment >& xEnv );
+
+ /// @throws DAVException
+ void
+ HEAD( const std::vector< OUString > & rHeaderNames, // empty == 'all'
+ DAVResource & rResource,
+ const css::uno::Reference< css::ucb::XCommandEnvironment >& xEnv );
+
+ /// @throws DAVException
+ css::uno::Reference< css::io::XInputStream >
+ GET( const css::uno::Reference< css::ucb::XCommandEnvironment > & xEnv );
+
+ /// @throws DAVException
+ void
+ GET( css::uno::Reference< css::io::XOutputStream > & rStream,
+ const css::uno::Reference< css::ucb::XCommandEnvironment > & xEnv );
+
+ /// @throws DAVException
+ css::uno::Reference< css::io::XInputStream >
+ GET( const std::vector< OUString > & rHeaderNames, // empty == 'all'
+ DAVResource & rResource,
+ const css::uno::Reference< css::ucb::XCommandEnvironment > & xEnv );
+
+ /// used as HEAD substitute when HEAD is not implemented on server
+ /// @throws DAVException
+ void
+ GET0( DAVRequestHeaders & rRequestHeaders,
+ const std::vector< OUString > & rHeaderNames, // empty == 'all'
+ DAVResource & rResource,
+ const css::uno::Reference< css::ucb::XCommandEnvironment > & xEnv );
+
+ /// @throws DAVException
+ void
+ GET( css::uno::Reference< css::io::XOutputStream > & rStream,
+ const std::vector< OUString > & rHeaderNames, // empty == 'all'
+ DAVResource & rResource,
+ const css::uno::Reference< css::ucb::XCommandEnvironment > & xEnv );
+
+ /// @throws DAVException
+ void
+ PUT( const css::uno::Reference< css::io::XInputStream > & rStream,
+ const css::uno::Reference< css::ucb::XCommandEnvironment > & xEnv );
+
+ /// @throws DAVException
+ css::uno::Reference< css::io::XInputStream >
+ POST( const OUString & rContentType,
+ const OUString & rReferer,
+ const css::uno::Reference< css::io::XInputStream > & rInputStream,
+ const css::uno::Reference< css::ucb::XCommandEnvironment >& xEnv );
+
+ /// @throws DAVException
+ void
+ POST( const OUString & rContentType,
+ const OUString & rReferer,
+ const css::uno::Reference< css::io::XInputStream > & rInputStream,
+ css::uno::Reference< css::io::XOutputStream > & rOutputStream,
+ const css::uno::Reference< css::ucb::XCommandEnvironment >& xEnv );
+
+ /// @throws DAVException
+ void
+ MKCOL( const css::uno::Reference< css::ucb::XCommandEnvironment > & xEnv );
+
+ /// @throws DAVException
+ void
+ COPY( const OUString & rSourcePath,
+ const OUString & rDestinationURI,
+ bool bOverwrite,
+ const css::uno::Reference< css::ucb::XCommandEnvironment > & xEnv );
+
+ /// @throws DAVException
+ void
+ MOVE( const OUString & rSourcePath,
+ const OUString & rDestinationURI,
+ bool bOverwrite,
+ const css::uno::Reference< css::ucb::XCommandEnvironment > & xEnv );
+
+ /// @throws DAVException
+ void
+ DESTROY( const css::uno::Reference< css::ucb::XCommandEnvironment > & xEnv );
+
+ // set new lock.
+ /// @throws DAVException
+ void
+ LOCK( css::ucb::Lock & inLock,
+ const css::uno::Reference< css::ucb::XCommandEnvironment > & xEnv );
+
+ /// @throws DAVException
+ void
+ UNLOCK( const css::uno::Reference< css::ucb::XCommandEnvironment > & xEnv );
+
+ /// @throws DAVException
+ void
+ abort();
+
+ // helper
+ static void
+ getUserRequestHeaders(
+ const css::uno::Reference< css::ucb::XCommandEnvironment > & xEnv,
+ const OUString & rURI,
+ css::ucb::WebDAVHTTPMethod eMethod,
+ DAVRequestHeaders & rRequestHeaders );
+
+ /// @throws DAVException
+ bool handleException(DAVException const& e, int errorCount);
+
+private:
+ const OUString & getRequestURI() const;
+ /// @throws DAVException
+ bool detectRedirectCycle(::std::u16string_view rRedirectURL);
+ /// @throws DAVException
+ void initialize();
+};
+
+} // namespace http_dav_ucp
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/ucb/source/ucp/webdav-curl/DAVSession.hxx b/ucb/source/ucp/webdav-curl/DAVSession.hxx
new file mode 100644
index 0000000000..b73ceb5613
--- /dev/null
+++ b/ucb/source/ucp/webdav-curl/DAVSession.hxx
@@ -0,0 +1,196 @@
+/* -*- 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:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+
+#pragma once
+
+#include <memory>
+#include <rtl/ustring.hxx>
+#include <com/sun/star/io/XInputStream.hpp>
+#include <com/sun/star/io/XOutputStream.hpp>
+#include <utility>
+#include "DAVResource.hxx"
+#include "DAVSessionFactory.hxx"
+#include "DAVTypes.hxx"
+#include "DAVRequestEnvironment.hxx"
+
+namespace com { namespace sun { namespace star { namespace beans {
+ struct NamedValue;
+} } } }
+
+namespace com::sun::star::ucb {
+ struct Lock;
+}
+
+namespace http_dav_ucp
+{
+
+class DAVAuthListener;
+
+class DAVSession
+{
+public:
+ void acquire()
+ {
+ osl_atomic_increment( &m_nRefCount );
+ }
+
+ void release()
+ {
+ if ( osl_atomic_decrement( &m_nRefCount ) == 0 )
+ {
+ m_xFactory->releaseElement( this );
+ delete this;
+ }
+ }
+
+ virtual bool CanUse( const OUString & rURI,
+ const ::com::sun::star::uno::Sequence<::com::sun::star::beans::NamedValue>& rFlags ) = 0;
+
+ virtual bool UsesProxy() = 0;
+
+ // DAV methods
+
+ virtual void OPTIONS( const OUString & inPath,
+ DAVOptions & rOptions,
+ const DAVRequestEnvironment & rEnv ) = 0;
+
+ // allprop & named
+ /// @throws DAVException
+ virtual void PROPFIND( const OUString & inPath,
+ const Depth inDepth,
+ const std::vector< OUString > & inPropertyNames,
+ std::vector< DAVResource > & ioResources,
+ const DAVRequestEnvironment & rEnv ) = 0;
+
+ // propnames
+ /// @throws DAVException
+ virtual void PROPFIND( const OUString & inPath,
+ const Depth inDepth,
+ std::vector< DAVResourceInfo > & ioResInfo,
+ const DAVRequestEnvironment & rEnv ) = 0;
+
+ /// @throws DAVException
+ virtual void PROPPATCH( const OUString & inPath,
+ const std::vector< ProppatchValue > & inValues,
+ const DAVRequestEnvironment & rEnv ) = 0;
+
+ /// @throws DAVException
+ virtual void HEAD( const OUString & inPath,
+ const std::vector< OUString > & inHeaderNames,
+ DAVResource & ioResource,
+ const DAVRequestEnvironment & rEnv ) = 0;
+
+ /// @throws DAVException
+ virtual css::uno::Reference< css::io::XInputStream >
+ GET( const OUString & inPath,
+ const DAVRequestEnvironment & rEnv ) = 0;
+
+ /// @throws DAVException
+ virtual void GET( const OUString & inPath,
+ css::uno::Reference< css::io::XOutputStream >& o,
+ const DAVRequestEnvironment & rEnv ) = 0;
+
+ /// @throws DAVException
+ virtual css::uno::Reference< css::io::XInputStream >
+ GET( const OUString & inPath,
+ const std::vector< OUString > & inHeaderNames,
+ DAVResource & ioResource,
+ const DAVRequestEnvironment & rEnv ) = 0;
+
+ /// @throws DAVException
+ virtual void
+ GET( const OUString & inPath,
+ css::uno::Reference< css::io::XOutputStream >& o,
+ const std::vector< OUString > & inHeaderNames,
+ DAVResource & ioResource,
+ const DAVRequestEnvironment & rEnv ) = 0;
+
+ /// @throws DAVException
+ virtual void PUT( const OUString & inPath,
+ const css::uno::Reference< css::io::XInputStream >& s,
+ const DAVRequestEnvironment & rEnv ) = 0;
+
+ /// @throws DAVException
+ virtual css::uno::Reference< css::io::XInputStream >
+ POST( const OUString & inPath,
+ const OUString & rContentType,
+ const OUString & rReferer,
+ const css::uno::Reference< css::io::XInputStream > & inInputStream,
+ const DAVRequestEnvironment & rEnv ) = 0;
+
+ /// @throws DAVException
+ virtual void POST( const OUString & inPath,
+ const OUString & rContentType,
+ const OUString & rReferer,
+ const css::uno::Reference< css::io::XInputStream > & inInputStream,
+ css::uno::Reference< css::io::XOutputStream > & oOutputStream,
+ const DAVRequestEnvironment & rEnv ) = 0;
+
+ /// @throws DAVException
+ virtual void MKCOL( const OUString & inPath,
+ const DAVRequestEnvironment & rEnv ) = 0;
+
+ /// @throws DAVException
+ virtual void COPY( const OUString & inSource,
+ const OUString & inDestination,
+ const DAVRequestEnvironment & rEnv,
+ bool inOverwrite = false ) = 0;
+
+ /// @throws DAVException
+ virtual void MOVE( const OUString & inSource,
+ const OUString & inDestination,
+ const DAVRequestEnvironment & rEnv,
+ bool inOverwrite = false ) = 0;
+
+ /// @throws DAVException
+ virtual void DESTROY( const OUString & inPath,
+ const DAVRequestEnvironment & rEnv ) = 0;
+
+ // set new lock.
+ /// @throws DAVException
+ virtual void LOCK( const OUString & inPath,
+ css::ucb::Lock & inLock,
+ const DAVRequestEnvironment & rEnv ) = 0;
+
+ /// @throws DAVException
+ virtual void UNLOCK( const OUString & inPath,
+ const DAVRequestEnvironment & rEnv ) = 0;
+
+ /// @throws DAVException
+ virtual void abort() = 0;
+
+protected:
+ explicit DAVSession( rtl::Reference< DAVSessionFactory > xFactory )
+ : m_xFactory(std::move( xFactory )), m_nRefCount( 0 ) {}
+
+ virtual ~DAVSession() {}
+
+private:
+ rtl::Reference< DAVSessionFactory > m_xFactory;
+ DAVSessionFactory::Map::iterator m_aContainerIt;
+ oslInterlockedCount m_nRefCount;
+
+ friend class DAVSessionFactory;
+ friend struct std::default_delete< DAVSession >;
+};
+
+} // namespace http_dav_ucp
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/ucb/source/ucp/webdav-curl/DAVSessionFactory.cxx b/ucb/source/ucp/webdav-curl/DAVSessionFactory.cxx
new file mode 100644
index 0000000000..910e7f04b9
--- /dev/null
+++ b/ucb/source/ucp/webdav-curl/DAVSessionFactory.cxx
@@ -0,0 +1,81 @@
+/* -*- 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:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include <memory>
+#include "DAVSessionFactory.hxx"
+#include "CurlSession.hxx"
+#include "CurlUri.hxx"
+
+using namespace http_dav_ucp;
+using namespace com::sun::star;
+
+DAVSessionFactory::~DAVSessionFactory()
+{
+}
+
+rtl::Reference< DAVSession > DAVSessionFactory::createDAVSession(
+ const OUString & inUri,
+ const uno::Sequence< beans::NamedValue >& rFlags,
+ const uno::Reference< uno::XComponentContext > & rxContext )
+{
+ std::unique_lock aGuard( m_aMutex );
+
+ if (!m_xProxyDecider)
+ m_xProxyDecider.reset( new ucbhelper::InternetProxyDecider( rxContext ) );
+
+ Map::iterator aIt = std::find_if(m_aMap.begin(), m_aMap.end(),
+ [&inUri, &rFlags](const Map::value_type& rEntry) { return rEntry.second->CanUse( inUri, rFlags ); });
+
+ if ( aIt == m_aMap.end() )
+ {
+ rtl::Reference< CurlSession > xElement(
+ new CurlSession(rxContext, this, inUri, rFlags, *m_xProxyDecider) );
+
+ aIt = m_aMap.emplace( inUri, xElement.get() ).first;
+
+ aIt->second->m_aContainerIt = aIt;
+ return aIt->second;
+ }
+ else if ( osl_atomic_increment( &aIt->second->m_nRefCount ) > 1 )
+ {
+ rtl::Reference< DAVSession > xElement( aIt->second );
+ osl_atomic_decrement( &aIt->second->m_nRefCount );
+ return xElement;
+ }
+ else
+ {
+ osl_atomic_decrement( &aIt->second->m_nRefCount );
+ aIt->second->m_aContainerIt = m_aMap.end();
+
+ rtl::Reference< CurlSession > xNewStorage = new CurlSession(rxContext, this, inUri, rFlags, *m_xProxyDecider);
+ aIt->second = xNewStorage.get();
+ aIt->second->m_aContainerIt = aIt;
+ return xNewStorage;
+ }
+}
+
+void DAVSessionFactory::releaseElement( const DAVSession * pElement )
+{
+ assert( pElement );
+ std::unique_lock aGuard( m_aMutex );
+ if ( pElement->m_aContainerIt != m_aMap.end() )
+ m_aMap.erase( pElement->m_aContainerIt );
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/ucb/source/ucp/webdav-curl/DAVSessionFactory.hxx b/ucb/source/ucp/webdav-curl/DAVSessionFactory.hxx
new file mode 100644
index 0000000000..2699035ebb
--- /dev/null
+++ b/ucb/source/ucp/webdav-curl/DAVSessionFactory.hxx
@@ -0,0 +1,74 @@
+/* -*- 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:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#pragma once
+
+#ifdef min
+#undef min // GNU libstdc++ <memory> includes <limit> which defines methods called min...
+#endif
+#include <map>
+#include <memory>
+#include <mutex>
+#include <salhelper/simplereferenceobject.hxx>
+#include <rtl/ref.hxx>
+#include <com/sun/star/uno/Reference.hxx>
+#include <ucbhelper/proxydecider.hxx>
+
+using namespace com::sun::star;
+
+namespace com::sun::star::beans
+{
+struct NamedValue;
+}
+
+namespace com::sun::star::lang
+{
+class XMultiServiceFactory;
+}
+
+namespace http_dav_ucp
+{
+class DAVSession;
+
+class DAVSessionFactory : public salhelper::SimpleReferenceObject
+{
+public:
+ virtual ~DAVSessionFactory() override;
+
+ /// @throws DAVException
+ rtl::Reference<DAVSession> createDAVSession(
+ const OUString& inUri,
+ const ::com::sun::star::uno::Sequence<::com::sun::star::beans::NamedValue>& rFlags,
+ const css::uno::Reference<css::uno::XComponentContext>& rxContext);
+
+private:
+ typedef std::map<OUString, DAVSession*> Map;
+
+ Map m_aMap;
+ std::mutex m_aMutex;
+ std::unique_ptr<ucbhelper::InternetProxyDecider> m_xProxyDecider;
+
+ void releaseElement(const DAVSession* pElement);
+
+ friend class DAVSession;
+};
+
+} // namespace http_dav_ucp
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/ucb/source/ucp/webdav-curl/DAVTypes.cxx b/ucb/source/ucp/webdav-curl/DAVTypes.cxx
new file mode 100644
index 0000000000..e41c5426e9
--- /dev/null
+++ b/ucb/source/ucp/webdav-curl/DAVTypes.cxx
@@ -0,0 +1,207 @@
+/* -*- 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 "DAVTypes.hxx"
+
+#include "CurlUri.hxx"
+#include "../inc/urihelper.hxx"
+
+#include <osl/time.h>
+
+
+using namespace http_dav_ucp;
+using namespace com::sun::star;
+
+// DAVOptions implementation
+
+DAVOptions::DAVOptions() :
+ m_isClass1( false ),
+ m_isClass2( false ),
+ m_isClass3( false ),
+ m_isHeadAllowed( true ),
+ m_isLocked( false ),
+ m_aAllowedMethods(),
+ m_nStaleTime( 0 ),
+ m_nRequestedTimeLife( 0 ),
+ m_sURL(),
+ m_sRedirectedURL(),
+ m_nHttpResponseStatusCode( 0 ),
+ m_sHttpResponseStatusText()
+{
+}
+
+DAVOptions::DAVOptions( const DAVOptions & rOther ) :
+ m_isClass1( rOther.m_isClass1 ),
+ m_isClass2( rOther.m_isClass2 ),
+ m_isClass3( rOther.m_isClass3 ),
+ m_isHeadAllowed( rOther.m_isHeadAllowed ),
+ m_isLocked( rOther.m_isLocked ),
+ m_aAllowedMethods( rOther.m_aAllowedMethods ),
+ m_nStaleTime( rOther.m_nStaleTime ),
+ m_nRequestedTimeLife( rOther.m_nRequestedTimeLife ),
+ m_sURL( rOther.m_sURL ),
+ m_sRedirectedURL( rOther.m_sRedirectedURL),
+ m_nHttpResponseStatusCode( rOther.m_nHttpResponseStatusCode ),
+ m_sHttpResponseStatusText( rOther.m_sHttpResponseStatusText )
+{
+}
+
+DAVOptions::~DAVOptions()
+{
+}
+
+DAVOptions & DAVOptions::operator=( const DAVOptions& rOpts )
+{
+ m_isClass1 = rOpts.m_isClass1;
+ m_isClass2 = rOpts.m_isClass2;
+ m_isClass3 = rOpts.m_isClass3;
+ m_isLocked = rOpts.m_isLocked;
+ m_isHeadAllowed = rOpts.m_isHeadAllowed;
+ m_aAllowedMethods = rOpts.m_aAllowedMethods;
+ m_nStaleTime = rOpts.m_nStaleTime;
+ m_nRequestedTimeLife = rOpts.m_nRequestedTimeLife;
+ m_sURL = rOpts.m_sURL;
+ m_sRedirectedURL = rOpts.m_sRedirectedURL;
+ m_nHttpResponseStatusCode = rOpts.m_nHttpResponseStatusCode;
+ m_sHttpResponseStatusText = rOpts.m_sHttpResponseStatusText;
+ return *this;
+}
+
+bool DAVOptions::operator==( const DAVOptions& rOpts ) const
+{
+ return
+ m_isClass1 == rOpts.m_isClass1 &&
+ m_isClass2 == rOpts.m_isClass2 &&
+ m_isClass3 == rOpts.m_isClass3 &&
+ m_isLocked == rOpts.m_isLocked &&
+ m_isHeadAllowed == rOpts.m_isHeadAllowed &&
+ m_aAllowedMethods == rOpts.m_aAllowedMethods &&
+ m_nStaleTime == rOpts.m_nStaleTime &&
+ m_nRequestedTimeLife == rOpts.m_nRequestedTimeLife &&
+ m_sURL == rOpts.m_sURL &&
+ m_sRedirectedURL == rOpts.m_sRedirectedURL &&
+ m_nHttpResponseStatusCode == rOpts.m_nHttpResponseStatusCode &&
+ m_sHttpResponseStatusText == rOpts.m_sHttpResponseStatusText;
+}
+
+
+// DAVOptionsCache implementation
+
+DAVOptionsCache::DAVOptionsCache()
+{
+}
+
+DAVOptionsCache::~DAVOptionsCache()
+{
+}
+
+bool DAVOptionsCache::getDAVOptions( const OUString & rURL, DAVOptions & rDAVOptions )
+{
+ std::unique_lock aGuard( m_aMutex );
+ OUString aEncodedUrl( ucb_impl::urihelper::encodeURI( DecodeURI(rURL) ) );
+ normalizeURLLastChar( aEncodedUrl );
+
+ // search the URL in the static map
+ DAVOptionsMap::iterator it;
+ it = m_aTheCache.find( aEncodedUrl );
+ if ( it == m_aTheCache.end() )
+ return false;
+ else
+ {
+ // check if the capabilities are stale, before restoring
+ TimeValue t1;
+ osl_getSystemTime( &t1 );
+ if ( (*it).second.getStaleTime() < t1.Seconds )
+ {
+ // if stale, remove from cache, do not restore
+ m_aTheCache.erase( it );
+ return false;
+ // return false instead
+ }
+ rDAVOptions = (*it).second;
+ return true;
+ }
+}
+
+void DAVOptionsCache::removeDAVOptions( const OUString & rURL )
+{
+ std::unique_lock aGuard( m_aMutex );
+ OUString aEncodedUrl( ucb_impl::urihelper::encodeURI( DecodeURI(rURL) ) );
+ normalizeURLLastChar( aEncodedUrl );
+
+ DAVOptionsMap::iterator it;
+ it = m_aTheCache.find( aEncodedUrl );
+ if ( it != m_aTheCache.end() )
+ {
+ m_aTheCache.erase( it );
+ }
+}
+
+void DAVOptionsCache::addDAVOptions( DAVOptions & rDAVOptions, const sal_uInt32 nLifeTime )
+{
+ std::unique_lock aGuard( m_aMutex );
+ OUString aURL( rDAVOptions.getURL() );
+
+ OUString aEncodedUrl( ucb_impl::urihelper::encodeURI( DecodeURI(aURL) ) );
+ normalizeURLLastChar( aEncodedUrl );
+ rDAVOptions.setURL( aEncodedUrl );
+
+// unchanged, it may be used to access a server
+ OUString aRedirURL( rDAVOptions.getRedirectedURL() );
+ rDAVOptions.setRedirectedURL( aRedirURL );
+
+ // check if already cached
+ DAVOptionsMap::iterator it;
+ it = m_aTheCache.find( aEncodedUrl );
+ if ( it != m_aTheCache.end() )
+ { // already in cache, check LifeTime
+ if ( (*it).second.getRequestedTimeLife() == nLifeTime )
+ return; // same lifetime, do nothing
+
+ // tdf#153642 keep cached Class1 bit at aDAVOptionsException to avoid of
+ // losing the ability to resave the document within the lifetime because
+ // of disabled DAV detection in getResourceType()
+ if ((*it).second.isClass1())
+ {
+ rDAVOptions.setClass1( (*it).second.isClass1() );
+ }
+ }
+ // not in cache, add it
+ TimeValue t1;
+ osl_getSystemTime( &t1 );
+ rDAVOptions.setStaleTime( t1.Seconds + nLifeTime );
+
+ m_aTheCache[ aEncodedUrl ] = rDAVOptions;
+}
+
+void DAVOptionsCache::setHeadAllowed( const OUString & rURL, const bool HeadAllowed )
+{
+ std::unique_lock aGuard( m_aMutex );
+ OUString aEncodedUrl( ucb_impl::urihelper::encodeURI( DecodeURI(rURL) ) );
+ normalizeURLLastChar( aEncodedUrl );
+
+ DAVOptionsMap::iterator it;
+ it = m_aTheCache.find( aEncodedUrl );
+ if ( it != m_aTheCache.end() )
+ {
+ // first check for stale
+ TimeValue t1;
+ osl_getSystemTime( &t1 );
+ if( (*it).second.getStaleTime() < t1.Seconds )
+ {
+ m_aTheCache.erase( it );
+ return;
+ }
+ // check if the resource was present on server
+ (*it).second.setHeadAllowed( HeadAllowed );
+ }
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab cinoptions=b1,g0,N-s cinkeys+=0=break: */
diff --git a/ucb/source/ucp/webdav-curl/DAVTypes.hxx b/ucb/source/ucp/webdav-curl/DAVTypes.hxx
new file mode 100644
index 0000000000..e0f2e85603
--- /dev/null
+++ b/ucb/source/ucp/webdav-curl/DAVTypes.hxx
@@ -0,0 +1,200 @@
+/* -*- 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:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+
+#pragma once
+
+#include <memory>
+#include <map>
+#include <mutex>
+#include <rtl/ustring.hxx>
+#include <com/sun/star/uno/Any.hxx>
+
+namespace http_dav_ucp
+{
+/* Excerpt from RFC 4918
+ <https://tools.ietf.org/html/rfc4918#section-18>
+
+ 18.1 Class 1
+
+ A class 1 compliant resource MUST meet all "MUST" requirements in all
+ sections of this document.
+
+ Class 1 compliant resources MUST return, at minimum, the value "1" in
+ the DAV header on all responses to the OPTIONS method.
+
+ 18.2 Class 2
+
+ A class 2 compliant resource MUST meet all class 1 requirements and
+ support the LOCK method, the DAV:supportedlock property, the DAV:
+ lockdiscovery property, the Time-Out response header and the Lock-
+ Token request header. A class 2 compliant resource SHOULD also
+ support the Timeout request header and the 'owner' XML element.
+
+ Class 2 compliant resources MUST return, at minimum, the values "1"
+ and "2" in the DAV header on all responses to the OPTIONS method.
+
+ 18.3. Class 3
+
+ A resource can explicitly advertise its support for the revisions to
+ [RFC2518] made in this document. Class 1 MUST be supported as well.
+ Class 2 MAY be supported. Advertising class 3 support in addition to
+ class 1 and 2 means that the server supports all the requirements in
+ this specification. Advertising class 3 and class 1 support, but not
+ class 2, means that the server supports all the requirements in this
+ specification except possibly those that involve locking support.
+
+*/
+
+ class DAVOptions
+ {
+ private:
+ bool m_isClass1;
+ bool m_isClass2;
+ bool m_isClass3;
+ /// for server that do not implement it
+ bool m_isHeadAllowed;
+ /// Internally used to maintain the locked state of the resource, only if it's a Class 2 resource
+ bool m_isLocked;
+ /// contains the methods allowed on this resource
+ OUString m_aAllowedMethods;
+
+ /// target time when this capability becomes stale
+ sal_uInt32 m_nStaleTime;
+ sal_uInt32 m_nRequestedTimeLife;
+ OUString m_sURL;
+ OUString m_sRedirectedURL;
+
+ /// The cached HTT response status code. It's 0 if the code was dealt with and there is no need to cache it
+ sal_uInt16 m_nHttpResponseStatusCode;
+ /// The cached string with the server returned HTTP response status code string, corresponds to m_nHttpResponseStatusCode.
+ OUString m_sHttpResponseStatusText;
+
+ public:
+ DAVOptions();
+
+ DAVOptions( const DAVOptions & rOther );
+
+ ~DAVOptions();
+
+ bool isClass1() const { return m_isClass1; };
+ void setClass1( bool Class1 = true ) { m_isClass1 = Class1; };
+
+ bool isClass2() const { return m_isClass2; };
+ void setClass2( bool Class2 = true ) { m_isClass2 = Class2; };
+
+ bool isClass3() const { return m_isClass3; };
+ void setClass3( bool Class3 = true ) { m_isClass3 = Class3; };
+
+ bool isHeadAllowed() const { return m_isHeadAllowed; };
+ void setHeadAllowed( bool HeadAllowed = true ) { m_isHeadAllowed = HeadAllowed; };
+
+ sal_uInt32 getStaleTime() const { return m_nStaleTime ; };
+ void setStaleTime( const sal_uInt32 nStaleTime ) { m_nStaleTime = nStaleTime; };
+
+ sal_uInt32 getRequestedTimeLife() const { return m_nRequestedTimeLife; };
+ void setRequestedTimeLife( const sal_uInt32 nRequestedTimeLife ) { m_nRequestedTimeLife = nRequestedTimeLife; };
+
+ const OUString & getURL() const { return m_sURL; };
+ void setURL( const OUString & sURL ) { m_sURL = sURL; };
+
+ const OUString & getRedirectedURL() const { return m_sRedirectedURL; };
+ void setRedirectedURL( const OUString & sRedirectedURL ) { m_sRedirectedURL = sRedirectedURL; };
+
+ void setAllowedMethods( const OUString & aAllowedMethods ) { m_aAllowedMethods = aAllowedMethods; } ;
+ const OUString & getAllowedMethods() const { return m_aAllowedMethods; } ;
+ bool isLockAllowed() const { return ( m_aAllowedMethods.indexOf( "LOCK" ) != -1 ); };
+
+ void setLocked( bool locked = true ) { m_isLocked = locked; } ;
+ bool isLocked() const { return m_isLocked; };
+
+ sal_uInt16 getHttpResponseStatusCode() const { return m_nHttpResponseStatusCode; };
+ void setHttpResponseStatusCode( const sal_uInt16 nHttpResponseStatusCode ) { m_nHttpResponseStatusCode = nHttpResponseStatusCode; };
+
+ const OUString & getHttpResponseStatusText() const { return m_sHttpResponseStatusText; };
+ void setHttpResponseStatusText( const OUString & rHttpResponseStatusText ) { m_sHttpResponseStatusText = rHttpResponseStatusText; };
+
+ void init() {
+ m_isClass1 = false;
+ m_isClass2 = false;
+ m_isClass3 = false;
+ m_isHeadAllowed = true;
+ m_isLocked = false;
+ m_aAllowedMethods.clear();
+ m_nStaleTime = 0;
+ m_nRequestedTimeLife = 0;
+ m_sURL.clear();
+ m_sRedirectedURL.clear();
+ m_nHttpResponseStatusCode = 0;
+ m_sHttpResponseStatusText.clear();
+ };
+
+ DAVOptions & operator=( const DAVOptions& rOpts );
+ bool operator==( const DAVOptions& rOpts ) const;
+
+ };
+
+ // TODO: the OUString key element in std::map needs to be changed with a URI representation
+ // along with a specific compare (std::less) implementation, as suggested in
+ // <https://tools.ietf.org/html/rfc3986#section-6>, to find by URI and not by string comparison
+ typedef std::map< OUString, DAVOptions,
+ std::less< OUString > > DAVOptionsMap;
+
+ class DAVOptionsCache
+ {
+ DAVOptionsMap m_aTheCache;
+ std::mutex m_aMutex;
+ public:
+ explicit DAVOptionsCache();
+ ~DAVOptionsCache();
+
+ bool getDAVOptions( const OUString & rURL, DAVOptions & rDAVOptions );
+ void removeDAVOptions( const OUString & rURL );
+ void addDAVOptions( DAVOptions & rDAVOptions, const sal_uInt32 nLifeTime );
+
+ void setHeadAllowed( const OUString & rURL, bool HeadAllowed = true );
+
+ private:
+
+ /// remove the last '/' in aUrl, if it exists
+ static void normalizeURLLastChar( OUString& aUrl ) {
+ if ( aUrl.getLength() > 1 &&
+ ( ( aUrl.lastIndexOf( '/' ) + 1 ) == aUrl.getLength() ) )
+ aUrl = aUrl.copy(0, aUrl.getLength() - 1 );
+ };
+ };
+
+ enum Depth { DAVZERO = 0, DAVONE = 1, DAVINFINITY = -1 };
+
+ enum ProppatchOperation { PROPSET = 0, PROPREMOVE = 1 };
+
+ struct ProppatchValue
+ {
+ ProppatchOperation const operation;
+ OUString const name;
+ css::uno::Any const value;
+
+ ProppatchValue( const ProppatchOperation o,
+ OUString n,
+ css::uno::Any v )
+ : operation( o ), name( std::move(n) ), value( std::move(v) ) {}
+ };
+} // namespace http_dav_ucp
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/ucb/source/ucp/webdav-curl/DateTimeHelper.cxx b/ucb/source/ucp/webdav-curl/DateTimeHelper.cxx
new file mode 100644
index 0000000000..6725b3c6fc
--- /dev/null
+++ b/ucb/source/ucp/webdav-curl/DateTimeHelper.cxx
@@ -0,0 +1,258 @@
+/* -*- 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:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include <osl/time.h>
+#include <com/sun/star/util/DateTime.hpp>
+#include "DateTimeHelper.hxx"
+
+using namespace com::sun::star::util;
+
+using namespace http_dav_ucp;
+
+bool DateTimeHelper::ISO8601_To_DateTime (std::u16string_view s,
+ DateTime& dateTime)
+{
+ OString aDT = OUStringToOString(s, RTL_TEXTENCODING_ASCII_US);
+
+ int year, month, day, hours, minutes, off_hours, off_minutes, fix;
+ double seconds;
+
+ // 2001-01-01T12:30:00Z
+ int n = sscanf( aDT.getStr(), "%04d-%02d-%02dT%02d:%02d:%lfZ",
+ &year, &month, &day, &hours, &minutes, &seconds );
+ if ( n == 6 )
+ {
+ fix = 0;
+ }
+ else
+ {
+ // 2001-01-01T12:30:00+03:30
+ n = sscanf( aDT.getStr(), "%04d-%02d-%02dT%02d:%02d:%lf+%02d:%02d",
+ &year, &month, &day, &hours, &minutes, &seconds,
+ &off_hours, &off_minutes );
+ if ( n == 8 )
+ {
+ fix = - off_hours * 3600 - off_minutes * 60;
+ }
+ else
+ {
+ // 2001-01-01T12:30:00-03:30
+ n = sscanf( aDT.getStr(), "%04d-%02d-%02dT%02d:%02d:%lf-%02d:%02d",
+ &year, &month, &day, &hours, &minutes, &seconds,
+ &off_hours, &off_minutes );
+ if ( n == 8 )
+ {
+ fix = off_hours * 3600 + off_minutes * 60;
+ }
+ else
+ {
+ return false;
+ }
+ }
+ }
+
+ // Convert to local time...
+
+ oslDateTime aDateTime;
+ aDateTime.NanoSeconds = 0;
+ aDateTime.Seconds = sal::static_int_cast< sal_uInt16 >(seconds); // 0-59
+ aDateTime.Minutes = sal::static_int_cast< sal_uInt16 >(minutes); // 0-59
+ aDateTime.Hours = sal::static_int_cast< sal_uInt16 >(hours); // 0-23
+ aDateTime.Day = sal::static_int_cast< sal_uInt16 >(day); // 1-31
+ aDateTime.DayOfWeek = 0; // 0-6, 0 = Sunday
+ aDateTime.Month = sal::static_int_cast< sal_uInt16 >(month); // 1-12
+ aDateTime.Year = sal::static_int_cast< sal_Int16 >(year);
+
+ TimeValue aTimeValue;
+ if ( osl_getTimeValueFromDateTime( &aDateTime, &aTimeValue ) )
+ {
+ aTimeValue.Seconds += fix;
+
+ if ( osl_getLocalTimeFromSystemTime( &aTimeValue, &aTimeValue ) )
+ {
+ if ( osl_getDateTimeFromTimeValue( &aTimeValue, &aDateTime ) )
+ {
+ dateTime.Year = aDateTime.Year;
+ dateTime.Month = aDateTime.Month;
+ dateTime.Day = aDateTime.Day;
+ dateTime.Hours = aDateTime.Hours;
+ dateTime.Minutes = aDateTime.Minutes;
+ dateTime.Seconds = aDateTime.Seconds;
+
+ return true;
+ }
+ }
+ }
+
+ return false;
+}
+
+/*
+sal_Int32 DateTimeHelper::convertDayToInt (const OUString& day)
+{
+ if (day.equalsAscii("Sun"))
+ return 0;
+ else if (day.equalsAscii("Mon"))
+ return 1;
+ else if (day.equalsAscii("Tue"))
+ return 2;
+ else if (day.equalsAscii("Wed"))
+ return 3;
+ else if (day.equalsAscii("Thu"))
+ return 4;
+ else if (day.equalsAscii("Fri"))
+ return 5;
+ else if (day.equalsAscii("Sat"))
+ return 6;
+ else
+ return -1;
+}
+*/
+
+sal_Int32 DateTimeHelper::convertMonthToInt(std::u16string_view month)
+{
+ if (month == u"Jan")
+ return 1;
+ else if (month == u"Feb")
+ return 2;
+ else if (month == u"Mar")
+ return 3;
+ else if (month == u"Apr")
+ return 4;
+ else if (month == u"May")
+ return 5;
+ else if (month == u"Jun")
+ return 6;
+ else if (month == u"Jul")
+ return 7;
+ else if (month == u"Aug")
+ return 8;
+ else if (month == u"Sep")
+ return 9;
+ else if (month == u"Oct")
+ return 10;
+ else if (month == u"Nov")
+ return 11;
+ else if (month == u"Dec")
+ return 12;
+ else
+ return 0;
+}
+
+bool DateTimeHelper::RFC2068_To_DateTime (std::u16string_view s,
+ DateTime& dateTime)
+{
+ int year;
+ int day;
+ int hours;
+ int minutes;
+ int seconds;
+ char string_month[3 + 1];
+ char string_day[3 + 1];
+
+ size_t found = s.find(',');
+ if (found != std::u16string_view::npos)
+ {
+ OString aDT = OUStringToOString(s, RTL_TEXTENCODING_ASCII_US);
+
+ // RFC 1123
+ found = sscanf (aDT.getStr(), "%3s, %2d %3s %4d %2d:%2d:%2d GMT",
+ string_day, &day, string_month, &year, &hours, &minutes, &seconds);
+ if (found != 7)
+ {
+ // RFC 1036
+ found = sscanf (aDT.getStr(), "%3s, %2d-%3s-%2d %2d:%2d:%2d GMT",
+ string_day, &day, string_month, &year, &hours, &minutes, &seconds);
+ }
+ found = (found == 7) ? 1 : 0;
+ }
+ else
+ {
+ OString aDT = OUStringToOString(s, RTL_TEXTENCODING_ASCII_US);
+
+ // ANSI C's asctime () format
+ found = sscanf (aDT.getStr(), "%3s %3s %d %2d:%2d:%2d %4d",
+ string_day, string_month,
+ &day, &hours, &minutes, &seconds, &year);
+ found = (found == 7) ? 1 : 0;
+ }
+
+ if (found)
+ {
+ found = 0;
+
+ int month = DateTimeHelper::convertMonthToInt (
+ OUString::createFromAscii (string_month));
+ if (month)
+ {
+ // Convert to local time...
+
+ oslDateTime aDateTime;
+ aDateTime.NanoSeconds = 0;
+ aDateTime.Seconds = sal::static_int_cast< sal_uInt16 >(seconds);
+ // 0-59
+ aDateTime.Minutes = sal::static_int_cast< sal_uInt16 >(minutes);
+ // 0-59
+ aDateTime.Hours = sal::static_int_cast< sal_uInt16 >(hours);
+ // 0-23
+ aDateTime.Day = sal::static_int_cast< sal_uInt16 >(day);
+ // 1-31
+ aDateTime.DayOfWeek = 0; //dayofweek; // 0-6, 0 = Sunday
+ aDateTime.Month = sal::static_int_cast< sal_uInt16 >(month);
+ // 1-12
+ aDateTime.Year = sal::static_int_cast< sal_Int16 >(year);
+
+ TimeValue aTimeValue;
+ if ( osl_getTimeValueFromDateTime( &aDateTime,
+ &aTimeValue ) )
+ {
+ if ( osl_getLocalTimeFromSystemTime( &aTimeValue,
+ &aTimeValue ) )
+ {
+ if ( osl_getDateTimeFromTimeValue( &aTimeValue,
+ &aDateTime ) )
+ {
+ dateTime.Year = aDateTime.Year;
+ dateTime.Month = aDateTime.Month;
+ dateTime.Day = aDateTime.Day;
+ dateTime.Hours = aDateTime.Hours;
+ dateTime.Minutes = aDateTime.Minutes;
+ dateTime.Seconds = aDateTime.Seconds;
+
+ found = 1;
+ }
+ }
+ }
+ }
+ }
+
+ return found;
+}
+
+bool DateTimeHelper::convert (std::u16string_view s, DateTime& dateTime)
+{
+ if (ISO8601_To_DateTime (s, dateTime))
+ return true;
+ else if (RFC2068_To_DateTime (s, dateTime))
+ return true;
+ else
+ return false;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/ucb/source/ucp/webdav-curl/DateTimeHelper.hxx b/ucb/source/ucp/webdav-curl/DateTimeHelper.hxx
new file mode 100644
index 0000000000..3578441274
--- /dev/null
+++ b/ucb/source/ucp/webdav-curl/DateTimeHelper.hxx
@@ -0,0 +1,55 @@
+/* -*- 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:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+
+#pragma once
+
+#include <sal/types.h>
+#include <rtl/ustring.hxx>
+
+namespace com::sun::star::util {
+ struct DateTime;
+}
+
+namespace rtl {
+ class OUString;
+}
+
+namespace http_dav_ucp
+{
+
+class DateTimeHelper
+{
+private:
+ static sal_Int32 convertMonthToInt(std::u16string_view month);
+
+ static bool ISO8601_To_DateTime (std::u16string_view,
+ css::util::DateTime& );
+
+ static bool RFC2068_To_DateTime (std::u16string_view,
+ css::util::DateTime& );
+
+public:
+ static bool convert (std::u16string_view,
+ css::util::DateTime& );
+};
+
+} // namespace http_dav_ucp
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/ucb/source/ucp/webdav-curl/PropertyMap.hxx b/ucb/source/ucp/webdav-curl/PropertyMap.hxx
new file mode 100644
index 0000000000..e38ca5c447
--- /dev/null
+++ b/ucb/source/ucp/webdav-curl/PropertyMap.hxx
@@ -0,0 +1,55 @@
+/* -*- 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:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+
+#pragma once
+
+#include <com/sun/star/beans/Property.hpp>
+#include <unordered_set>
+
+namespace http_dav_ucp {
+
+struct equalPropertyName
+{
+ bool operator()( const css::beans::Property & p1,
+ const css::beans::Property & p2 ) const
+ {
+ return p1.Name == p2.Name;
+ }
+};
+
+struct hashPropertyName
+{
+ size_t operator()( const css::beans::Property & p ) const
+ {
+ return p.Name.hashCode();
+ }
+};
+
+typedef std::unordered_set
+<
+ css::beans::Property,
+ hashPropertyName,
+ equalPropertyName
+>
+PropertyMap;
+
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/ucb/source/ucp/webdav-curl/PropfindCache.cxx b/ucb/source/ucp/webdav-curl/PropfindCache.cxx
new file mode 100644
index 0000000000..da8499e152
--- /dev/null
+++ b/ucb/source/ucp/webdav-curl/PropfindCache.cxx
@@ -0,0 +1,93 @@
+/* -*- 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 <osl/time.h>
+
+#include <utility>
+#include "PropfindCache.hxx"
+
+namespace http_dav_ucp
+{
+
+ // PropertyNames implementation
+
+ PropertyNames::PropertyNames() :
+ m_nStaleTime( 0 ),
+ m_sURL(),
+ m_aPropertiesNames()
+ {
+ }
+
+ PropertyNames::PropertyNames( OUString aURL ) :
+ m_nStaleTime( 0 ),
+ m_sURL(std::move( aURL )),
+ m_aPropertiesNames()
+ {
+ }
+
+ //PropertyNamesCache implementation
+
+ PropertyNamesCache::PropertyNamesCache()
+ {
+ }
+
+ PropertyNamesCache::~PropertyNamesCache()
+ {
+ }
+
+ bool PropertyNamesCache::getCachedPropertyNames( const OUString& rURL, PropertyNames& rCacheElement )
+ {
+ // search the URL in the static map
+ std::unique_lock aGuard( m_aMutex );
+ PropNameCache::const_iterator it;
+ it = m_aTheCache.find( rURL );
+ if ( it == m_aTheCache.end() )
+ return false;
+ else
+ {
+ // check if the element is stale, before restoring
+ TimeValue t1;
+ osl_getSystemTime( &t1 );
+ if ( (*it).second.getStaleTime() < t1.Seconds )
+ {
+ // if stale, remove from cache, do not restore
+ m_aTheCache.erase( it );
+ return false;
+ // return false instead
+ }
+ rCacheElement = (*it).second;
+ return true;
+ }
+ }
+
+ void PropertyNamesCache::removeCachedPropertyNames( const OUString& rURL )
+ {
+ std::unique_lock aGuard( m_aMutex );
+ PropNameCache::const_iterator it;
+ it = m_aTheCache.find( rURL );
+ if ( it != m_aTheCache.end() )
+ {
+ m_aTheCache.erase( it );
+ }
+ }
+
+ void PropertyNamesCache::addCachePropertyNames( PropertyNames& rCacheElement, const sal_uInt32 nLifeTime )
+ {
+ std::unique_lock aGuard( m_aMutex );
+ OUString aURL( rCacheElement.getURL() );
+ TimeValue t1;
+ osl_getSystemTime( &t1 );
+ rCacheElement.setStaleTime( t1.Seconds + nLifeTime );
+
+ m_aTheCache[ aURL ] = rCacheElement;
+ }
+
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab cinoptions=b1,g0,N-s cinkeys+=0=break: */
diff --git a/ucb/source/ucp/webdav-curl/PropfindCache.hxx b/ucb/source/ucp/webdav-curl/PropfindCache.hxx
new file mode 100644
index 0000000000..e900c8ffdd
--- /dev/null
+++ b/ucb/source/ucp/webdav-curl/PropfindCache.hxx
@@ -0,0 +1,80 @@
+/* -*- 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/.
+ */
+
+#ifndef INCLUDED_UCB_SOURCE_UCP_WEBDAV_NEON_PROPFINDCACHE_HXX
+#define INCLUDED_UCB_SOURCE_UCP_WEBDAV_NEON_PROPFINDCACHE_HXX
+
+#include <sal/types.h>
+#include <rtl/ustring.hxx>
+#include <mutex>
+#include <map>
+#include <vector>
+
+#include "DAVResource.hxx"
+
+namespace http_dav_ucp
+{
+ // A property names cache mechanism, URL driven.
+ // It is used to cache the property names received
+ // from the WebDAV server, to minimize the need of
+ // net transactions (e.g. PROPFIND).
+ // The cache lifetime should be short
+ // just to remove the annoying slowness when
+ // typing text or moving cursor around when the
+ // net link is slow.
+
+ // Define the properties cache element
+ class PropertyNames
+ {
+ /// target time when this element becomes stale
+ sal_uInt32 m_nStaleTime;
+ OUString m_sURL;
+ // the property name list received from WebDAV server
+ std::vector< DAVResourceInfo > m_aPropertiesNames;
+
+ public:
+ PropertyNames();
+ explicit PropertyNames( OUString aURL );
+
+ sal_uInt32 getStaleTime() const { return m_nStaleTime; };
+ void setStaleTime( const sal_uInt32 nStaleTime ) { m_nStaleTime = nStaleTime; };
+
+ OUString& getURL() { return m_sURL; };
+
+ const std::vector< DAVResourceInfo >& getPropertiesNames() { return m_aPropertiesNames; };
+ void setPropertiesNames( const std::vector< DAVResourceInfo >& aPropertiesNames ) { m_aPropertiesNames = aPropertiesNames; };
+ };
+
+ // Define the PropertyNames cache
+ // TODO: the OUString key element in std::map needs to be changed with a URI representation
+ // with a specific compare (std::less) implementation, this last one implementing
+ // as suggested in <https://tools.ietf.org/html/rfc3986#section-6>.
+ // To find by URI and not by string equality.
+ typedef std::map< OUString, PropertyNames,
+ std::less< OUString > >PropNameCache;
+
+ class PropertyNamesCache
+ {
+ PropNameCache m_aTheCache;
+ std::mutex m_aMutex;
+
+ public:
+ PropertyNamesCache();
+ ~PropertyNamesCache();
+
+ bool getCachedPropertyNames( const OUString& URL, PropertyNames& rCacheElement );
+ void removeCachedPropertyNames( const OUString& URL );
+ void addCachePropertyNames( PropertyNames& rCacheElement, const sal_uInt32 nLifeTime );
+ };
+
+}
+
+#endif
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab cinoptions=b1,g0,N-s cinkeys+=0=break: */
diff --git a/ucb/source/ucp/webdav-curl/SerfLockStore.cxx b/ucb/source/ucp/webdav-curl/SerfLockStore.cxx
new file mode 100644
index 0000000000..6d7b89e9e6
--- /dev/null
+++ b/ucb/source/ucp/webdav-curl/SerfLockStore.cxx
@@ -0,0 +1,255 @@
+/* -*- 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:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include <chrono>
+#include <rtl/ustring.hxx>
+#include <sal/log.hxx>
+#include <osl/time.h>
+#include <osl/thread.hxx>
+#include <salhelper/thread.hxx>
+
+#include <com/sun/star/ucb/LockScope.hpp>
+#include <thread>
+
+#include "CurlSession.hxx"
+#include "SerfLockStore.hxx"
+
+using namespace http_dav_ucp;
+
+namespace http_dav_ucp {
+
+class TickerThread : public salhelper::Thread
+{
+ bool m_bFinish;
+ SerfLockStore & m_rLockStore;
+
+public:
+
+ explicit TickerThread( SerfLockStore & rLockStore )
+ : Thread( "WebDavTickerThread" ), m_bFinish( false ),
+ m_rLockStore( rLockStore ) {}
+
+ void finish() { m_bFinish = true; }
+
+private:
+
+ virtual void execute();
+};
+
+} // namespace http_dav_ucp
+
+
+void TickerThread::execute()
+{
+ osl_setThreadName("http_dav_ucp::TickerThread");
+
+ SAL_INFO("ucb.ucp.webdav", "TickerThread: start." );
+
+ // we have to go through the loop more often to be able to finish ~quickly
+ const int nNth = 25;
+
+ int nCount = nNth;
+ while ( !m_bFinish )
+ {
+ if ( nCount-- <= 0 )
+ {
+ m_rLockStore.refreshLocks();
+ nCount = nNth;
+ }
+
+ std::this_thread::sleep_for( std::chrono::milliseconds(1000/25) );
+ }
+
+ SAL_INFO("ucb.ucp.webdav", "TickerThread: stop." );
+}
+
+
+SerfLockStore::SerfLockStore()
+{
+}
+
+
+SerfLockStore::~SerfLockStore()
+{
+ std::unique_lock aGuard(m_aMutex);
+ stopTicker(aGuard);
+ aGuard.lock(); // actually no threads should even try to access members now
+
+ // release active locks, if any.
+ SAL_WARN_IF( !m_aLockInfoMap.empty(), "ucb.ucp.webdav",
+ "SerfLockStore::~SerfLockStore - Releasing active locks!" );
+
+ for ( auto& rLockInfo : m_aLockInfoMap )
+ {
+ rLockInfo.second.m_xSession->NonInteractive_UNLOCK(rLockInfo.first);
+ }
+}
+
+void SerfLockStore::startTicker()
+{
+ std::unique_lock aGuard( m_aMutex );
+
+ if ( !m_pTickerThread.is() )
+ {
+ m_pTickerThread = new TickerThread( *this );
+ m_pTickerThread->launch();
+ }
+}
+
+
+void SerfLockStore::stopTicker(std::unique_lock<std::mutex> & rGuard)
+{
+ rtl::Reference<TickerThread> pTickerThread;
+
+ if (m_pTickerThread.is())
+ {
+ m_pTickerThread->finish(); // needs mutex
+ // the TickerThread may run refreshLocks() at most once after this
+ pTickerThread = m_pTickerThread;
+ m_pTickerThread.clear();
+ }
+
+ rGuard.unlock();
+
+ if (pTickerThread.is() && pTickerThread->getIdentifier() != osl::Thread::getCurrentIdentifier())
+ {
+ pTickerThread->join(); // without m_aMutex locked (to prevent deadlock)
+ }
+}
+
+OUString const*
+SerfLockStore::getLockTokenForURI(OUString const& rURI, css::ucb::Lock const*const pLock)
+{
+ assert(rURI.startsWith("http://") || rURI.startsWith("https://"));
+
+ std::unique_lock aGuard( m_aMutex );
+
+ auto const it(m_aLockInfoMap.find(rURI));
+
+ if (it == m_aLockInfoMap.end())
+ {
+ return nullptr;
+ }
+ if (!pLock) // any lock will do
+ {
+ return &it->second.m_sToken;
+ }
+ // 0: EXCLUSIVE 1: SHARED
+ if (it->second.m_Lock.Scope == ucb::LockScope_SHARED && pLock->Scope == ucb::LockScope_EXCLUSIVE)
+ {
+ return nullptr;
+ }
+ assert(it->second.m_Lock.Type == pLock->Type); // only WRITE possible
+ if (it->second.m_Lock.Depth < pLock->Depth)
+ {
+ return nullptr;
+ }
+ // Only own locks are expected in the lock store, but depending on the
+ // server it->second.m_Lock.Owner may contain the string this UCP passed in
+ // the LOCK request, or a user identifier generated by the server (happens
+ // with Sharepoint), so just ignore it here.
+ // ignore Timeout ?
+ return &it->second.m_sToken;
+}
+
+void SerfLockStore::addLock( const OUString& rURI,
+ ucb::Lock const& rLock,
+ const OUString& sToken,
+ rtl::Reference<CurlSession> const & xSession,
+ sal_Int32 nLastChanceToSendRefreshRequest )
+{
+ assert(rURI.startsWith("http://") || rURI.startsWith("https://"));
+ {
+ std::unique_lock aGuard( m_aMutex );
+
+ m_aLockInfoMap[ rURI ]
+ = LockInfo(sToken, rLock, xSession, nLastChanceToSendRefreshRequest);
+ }
+
+ startTicker();
+}
+
+
+void SerfLockStore::removeLock(const OUString& rURI)
+{
+ std::unique_lock aGuard( m_aMutex );
+
+ removeLockImpl(aGuard, rURI);
+}
+
+void SerfLockStore::removeLockImpl(std::unique_lock<std::mutex> & rGuard, const OUString& rURI)
+{
+ assert(rURI.startsWith("http://") || rURI.startsWith("https://"));
+
+ m_aLockInfoMap.erase(rURI);
+
+ if ( m_aLockInfoMap.empty() )
+ {
+ stopTicker(rGuard);
+ }
+}
+
+void SerfLockStore::refreshLocks()
+{
+ std::unique_lock aGuard( m_aMutex );
+
+ ::std::vector<OUString> authFailedLocks;
+
+ for ( auto& rLockInfo : m_aLockInfoMap )
+ {
+ LockInfo & rInfo = rLockInfo.second;
+ if ( rInfo.m_nLastChanceToSendRefreshRequest != -1 )
+ {
+ // 30 seconds or less remaining until lock expires?
+ TimeValue t1;
+ osl_getSystemTime( &t1 );
+ if ( rInfo.m_nLastChanceToSendRefreshRequest - 30
+ <= sal_Int32( t1.Seconds ) )
+ {
+ // refresh the lock.
+ sal_Int32 nlastChanceToSendRefreshRequest = -1;
+ bool isAuthFailed(false);
+ if (rInfo.m_xSession->NonInteractive_LOCK(
+ rLockInfo.first, rLockInfo.second.m_sToken,
+ nlastChanceToSendRefreshRequest,
+ isAuthFailed))
+ {
+ rInfo.m_nLastChanceToSendRefreshRequest
+ = nlastChanceToSendRefreshRequest;
+ }
+ else
+ {
+ if (isAuthFailed)
+ {
+ authFailedLocks.push_back(rLockInfo.first);
+ }
+ // refresh failed. stop auto-refresh.
+ rInfo.m_nLastChanceToSendRefreshRequest = -1;
+ }
+ }
+ }
+ }
+
+ for (auto const& rLock : authFailedLocks)
+ {
+ removeLockImpl(aGuard, rLock);
+ }
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/ucb/source/ucp/webdav-curl/SerfLockStore.hxx b/ucb/source/ucp/webdav-curl/SerfLockStore.hxx
new file mode 100644
index 0000000000..8b54901e1f
--- /dev/null
+++ b/ucb/source/ucp/webdav-curl/SerfLockStore.hxx
@@ -0,0 +1,92 @@
+/* -*- 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:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+
+#pragma once
+
+#include <map>
+#include <mutex>
+#include <rtl/ref.hxx>
+#include <rtl/ustring.hxx>
+#include <com/sun/star/ucb/Lock.hpp>
+#include <utility>
+
+#include "CurlSession.hxx"
+
+namespace http_dav_ucp
+{
+
+class TickerThread;
+
+struct LockInfo
+{
+ OUString m_sToken;
+ css::ucb::Lock m_Lock;
+ rtl::Reference<CurlSession> m_xSession;
+ sal_Int32 m_nLastChanceToSendRefreshRequest;
+
+ LockInfo()
+ : m_nLastChanceToSendRefreshRequest( -1 ) {}
+
+ LockInfo( OUString sToken,
+ css::ucb::Lock aLock,
+ rtl::Reference<CurlSession> xSession,
+ sal_Int32 nLastChanceToSendRefreshRequest )
+ : m_sToken(std::move(sToken))
+ , m_Lock(std::move(aLock))
+ , m_xSession(std::move(xSession))
+ , m_nLastChanceToSendRefreshRequest(nLastChanceToSendRefreshRequest)
+ {}
+};
+
+typedef std::map< OUString, LockInfo > LockInfoMap;
+
+class SerfLockStore
+{
+ std::mutex m_aMutex;
+ rtl::Reference< TickerThread > m_pTickerThread;
+ LockInfoMap m_aLockInfoMap;
+
+public:
+ SerfLockStore();
+ ~SerfLockStore();
+
+ OUString const* getLockTokenForURI(OUString const& rURI, css::ucb::Lock const* pLock);
+
+ void addLock( const OUString& rURI,
+ css::ucb::Lock const& rLock,
+ const OUString& sToken,
+ rtl::Reference<CurlSession> const & xSession,
+ // time in seconds since Jan 1 1970
+ // -1: infinite lock, no refresh
+ sal_Int32 nLastChanceToSendRefreshRequest );
+
+ void removeLock(const OUString& rURI);
+
+ void refreshLocks();
+
+private:
+ void removeLockImpl(std::unique_lock<std::mutex> & rGuard, const OUString& rURI);
+ void startTicker();
+ void stopTicker(std::unique_lock<std::mutex> & rGuard);
+};
+
+} // namespace http_dav_ucp
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/ucb/source/ucp/webdav-curl/UCBDeadPropertyValue.cxx b/ucb/source/ucp/webdav-curl/UCBDeadPropertyValue.cxx
new file mode 100644
index 0000000000..94acb74c24
--- /dev/null
+++ b/ucb/source/ucp/webdav-curl/UCBDeadPropertyValue.cxx
@@ -0,0 +1,220 @@
+/* -*- 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:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include <sal/log.hxx>
+#include <o3tl/string_view.hxx>
+#include "UCBDeadPropertyValue.hxx"
+
+using namespace http_dav_ucp;
+using namespace ::com::sun::star;
+
+
+// static
+constexpr OUString aTypeString = u"string"_ustr;
+constexpr OUString aTypeLong = u"long"_ustr;
+constexpr OUString aTypeShort = u"short"_ustr;
+constexpr OUString aTypeBoolean = u"boolean"_ustr;
+constexpr OUString aTypeChar = u"char"_ustr;
+constexpr OUString aTypeByte = u"byte"_ustr;
+constexpr OUString aTypeHyper = u"hyper"_ustr;
+constexpr OUString aTypeFloat = u"float"_ustr;
+constexpr OUString aTypeDouble = u"double"_ustr;
+
+// static
+bool UCBDeadPropertyValue::supportsType( const uno::Type & rType )
+{
+ if ( ( rType != cppu::UnoType<OUString>::get() )
+ &&
+ ( rType != cppu::UnoType<sal_Int32>::get() )
+ &&
+ ( rType != cppu::UnoType<sal_Int16>::get() )
+ &&
+ ( rType != cppu::UnoType<bool>::get() )
+ &&
+ ( rType != cppu::UnoType<cppu::UnoCharType>::get() )
+ &&
+ ( rType != cppu::UnoType<sal_Int8>::get() )
+ &&
+ ( rType != cppu::UnoType<sal_Int64>::get() )
+ &&
+ ( rType != cppu::UnoType<float>::get() )
+ &&
+ ( rType != cppu::UnoType<double>::get() ) )
+ {
+ return false;
+ }
+
+ return true;
+}
+
+
+// static
+bool UCBDeadPropertyValue::createFromXML(std::u16string_view rType,
+ OUString const& rValue,
+ uno::Any & rOutData)
+{
+ bool success = true;
+
+ if (o3tl::equalsIgnoreAsciiCase(rType, aTypeString))
+ {
+ rOutData <<= rValue;
+ }
+ else if (o3tl::equalsIgnoreAsciiCase(rType, aTypeLong))
+ {
+ rOutData <<= rValue.toInt32();
+ }
+ else if (o3tl::equalsIgnoreAsciiCase(rType, aTypeShort))
+ {
+ rOutData <<= sal_Int16( rValue.toInt32() );
+ }
+ else if (o3tl::equalsIgnoreAsciiCase(rType, aTypeBoolean))
+ {
+ if (rValue.equalsIgnoreAsciiCase(u"true"))
+ {
+ rOutData <<= true;
+ }
+ else
+ {
+ rOutData <<= false;
+ }
+ }
+ else if (o3tl::equalsIgnoreAsciiCase(rType, aTypeChar))
+ {
+ rOutData <<= rValue.toChar();
+ }
+ else if (o3tl::equalsIgnoreAsciiCase(rType, aTypeByte))
+ {
+ rOutData <<= sal_Int8( rValue.toChar() );
+ }
+ else if (o3tl::equalsIgnoreAsciiCase(rType, aTypeHyper))
+ {
+ rOutData <<= rValue.toInt64();
+ }
+ else if (o3tl::equalsIgnoreAsciiCase(rType, aTypeFloat))
+ {
+ rOutData <<= rValue.toFloat();
+ }
+ else if (o3tl::equalsIgnoreAsciiCase(rType, aTypeDouble))
+ {
+ rOutData <<= rValue.toDouble();
+ }
+ else
+ {
+ SAL_WARN( "ucb.ucp.webdav",
+ "UCBDeadPropertyValue::createFromXML - "
+ "Unsupported property type!" );
+ success = false;
+ }
+ return success;
+}
+
+// static
+::std::optional<::std::pair<OUString, OUString>>
+UCBDeadPropertyValue::toXML(const uno::Any & rInData)
+{
+ // <ucbprop><type>the_type</type><value>the_value</value></ucbprop>
+
+ // Check property type. Extract type and value as string.
+
+ const uno::Type& rType = rInData.getValueType();
+ OUString aStringValue;
+ OUString aStringType;
+
+ if ( rType == cppu::UnoType<OUString>::get() )
+ {
+ // string
+ rInData >>= aStringValue;
+ aStringType = aTypeString;
+ }
+ else if ( rType == cppu::UnoType<sal_Int32>::get() )
+ {
+ // long
+ sal_Int32 nValue = 0;
+ rInData >>= nValue;
+ aStringValue = OUString::number( nValue );
+ aStringType = aTypeLong;
+ }
+ else if ( rType == cppu::UnoType<sal_Int16>::get() )
+ {
+ // short
+ sal_Int32 nValue = 0;
+ rInData >>= nValue;
+ aStringValue = OUString::number( nValue );
+ aStringType = aTypeShort;
+ }
+ else if ( rType == cppu::UnoType<bool>::get() )
+ {
+ // boolean
+ bool bValue = false;
+ rInData >>= bValue;
+ aStringValue = OUString::boolean( bValue );
+ aStringType = aTypeBoolean;
+ }
+ else if ( rType == cppu::UnoType<cppu::UnoCharType>::get() )
+ {
+ // char
+ sal_Unicode cValue = 0;
+ rInData >>= cValue;
+ aStringValue = OUString( cValue );
+ aStringType = aTypeChar;
+ }
+ else if ( rType == cppu::UnoType<sal_Int8>::get() )
+ {
+ // byte
+ sal_Int8 nValue = 0;
+ rInData >>= nValue;
+ aStringValue = OUString( sal_Unicode( nValue ) );
+ aStringType = aTypeByte;
+ }
+ else if ( rType == cppu::UnoType<sal_Int64>::get() )
+ {
+ // hyper
+ sal_Int64 nValue = 0;
+ rInData >>= nValue;
+ aStringValue = OUString::number( nValue );
+ aStringType = aTypeHyper;
+ }
+ else if ( rType == cppu::UnoType<float>::get() )
+ {
+ // float
+ float nValue = 0;
+ rInData >>= nValue;
+ aStringValue = OUString::number( nValue );
+ aStringType = aTypeFloat;
+ }
+ else if ( rType == cppu::UnoType<double>::get() )
+ {
+ // double
+ double nValue = 0;
+ rInData >>= nValue;
+ aStringValue = OUString::number( nValue );
+ aStringType = aTypeDouble;
+ }
+ else
+ {
+ SAL_WARN( "ucb.ucp.webdav",
+ "UCBDeadPropertyValue::toXML - "
+ "Unsupported property type!" );
+ return {};
+ }
+
+ return { { aStringType, aStringValue } };
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/ucb/source/ucp/webdav-curl/UCBDeadPropertyValue.hxx b/ucb/source/ucp/webdav-curl/UCBDeadPropertyValue.hxx
new file mode 100644
index 0000000000..12574e0a95
--- /dev/null
+++ b/ucb/source/ucp/webdav-curl/UCBDeadPropertyValue.hxx
@@ -0,0 +1,45 @@
+/* -*- 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:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+
+#pragma once
+
+#include <optional>
+#include <utility>
+
+#include <com/sun/star/uno/Any.hxx>
+
+namespace http_dav_ucp
+{
+
+class UCBDeadPropertyValue
+{
+public:
+ static bool supportsType( const css::uno::Type & rType );
+
+ static bool createFromXML(std::u16string_view rType,
+ OUString const& rValue,
+ css::uno::Any & rOutData);
+ static ::std::optional<::std::pair<OUString, OUString>>
+ toXML(const css::uno::Any & rInData);
+};
+
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/ucb/source/ucp/webdav-curl/ucpdav1.component b/ucb/source/ucp/webdav-curl/ucpdav1.component
new file mode 100644
index 0000000000..bb16e3b397
--- /dev/null
+++ b/ucb/source/ucp/webdav-curl/ucpdav1.component
@@ -0,0 +1,26 @@
+<?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/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-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.WebDAVContentProvider"
+ constructor="ucb_webdav_ContentProvider_get_implementation">
+ <service name="com.sun.star.ucb.WebDAVContentProvider"/>
+ </implementation>
+</component>
diff --git a/ucb/source/ucp/webdav-curl/webdavcontent.cxx b/ucb/source/ucp/webdav-curl/webdavcontent.cxx
new file mode 100644
index 0000000000..2111263bf4
--- /dev/null
+++ b/ucb/source/ucp/webdav-curl/webdavcontent.cxx
@@ -0,0 +1,4305 @@
+/* -*- 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:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include <memory>
+
+#include <cppuhelper/queryinterface.hxx>
+#include <rtl/uri.hxx>
+#include <sal/log.hxx>
+#include <officecfg/Office/Common.hxx>
+#include <officecfg/Inet.hxx>
+#include <ucbhelper/contentidentifier.hxx>
+#include <ucbhelper/macros.hxx>
+#include <ucbhelper/propertyvalueset.hxx>
+#include <ucbhelper/simpleinteractionrequest.hxx>
+#include <ucbhelper/cancelcommandexecution.hxx>
+#include <svl/lockfilecommon.hxx>
+
+#include <com/sun/star/beans/IllegalTypeException.hpp>
+#include <com/sun/star/beans/NotRemoveableException.hpp>
+#include <com/sun/star/beans/PropertyAttribute.hpp>
+#include <com/sun/star/beans/PropertyExistException.hpp>
+#include <com/sun/star/beans/PropertySetInfoChange.hpp>
+#include <com/sun/star/beans/PropertySetInfoChangeEvent.hpp>
+#include <com/sun/star/beans/PropertyValue.hpp>
+#include <com/sun/star/io/XActiveDataSink.hpp>
+#include <com/sun/star/io/XOutputStream.hpp>
+#include <com/sun/star/lang/IllegalAccessException.hpp>
+#include <com/sun/star/lang/IllegalArgumentException.hpp>
+#include <com/sun/star/sdbc/SQLException.hpp>
+#include <com/sun/star/task/PasswordContainerInteractionHandler.hpp>
+#include <com/sun/star/ucb/CommandEnvironment.hpp>
+#include <com/sun/star/ucb/CommandFailedException.hpp>
+#include <com/sun/star/ucb/ContentInfoAttribute.hpp>
+#include <com/sun/star/ucb/IllegalIdentifierException.hpp>
+#include <com/sun/star/ucb/InsertCommandArgument.hpp>
+#include <com/sun/star/ucb/InteractiveBadTransferURLException.hpp>
+#include <com/sun/star/ucb/InteractiveAugmentedIOException.hpp>
+#include <com/sun/star/ucb/InteractiveLockingLockedException.hpp>
+#include <com/sun/star/ucb/InteractiveLockingLockExpiredException.hpp>
+#include <com/sun/star/ucb/InteractiveLockingNotLockedException.hpp>
+#include <com/sun/star/ucb/InteractiveNetworkConnectException.hpp>
+#include <com/sun/star/ucb/InteractiveNetworkGeneralException.hpp>
+#include <com/sun/star/ucb/InteractiveNetworkReadException.hpp>
+#include <com/sun/star/ucb/InteractiveNetworkResolveNameException.hpp>
+#include <com/sun/star/ucb/InteractiveNetworkWriteException.hpp>
+#include <com/sun/star/ucb/MissingInputStreamException.hpp>
+#include <com/sun/star/ucb/MissingPropertiesException.hpp>
+#include <com/sun/star/ucb/NameClash.hpp>
+#include <com/sun/star/ucb/NameClashException.hpp>
+#include <com/sun/star/ucb/OpenCommandArgument3.hpp>
+#include <com/sun/star/ucb/OpenMode.hpp>
+#include <com/sun/star/ucb/PostCommandArgument2.hpp>
+#include <com/sun/star/ucb/PropertyCommandArgument.hpp>
+#include <com/sun/star/ucb/TransferInfo.hpp>
+#include <com/sun/star/ucb/UnsupportedCommandException.hpp>
+#include <com/sun/star/ucb/UnsupportedDataSinkException.hpp>
+#include <com/sun/star/ucb/UnsupportedNameClashException.hpp>
+#include <com/sun/star/ucb/UnsupportedOpenModeException.hpp>
+#include <com/sun/star/ucb/XCommandInfo.hpp>
+#include <com/sun/star/ucb/XPersistentPropertySet.hpp>
+#include <com/sun/star/uno/XComponentContext.hpp>
+
+#include "webdavcontent.hxx"
+#include "webdavprovider.hxx"
+#include "webdavresultset.hxx"
+#include "ContentProperties.hxx"
+#include "CurlUri.hxx"
+#include "UCBDeadPropertyValue.hxx"
+#include "DAVException.hxx"
+#include "DAVProperties.hxx"
+
+using namespace com::sun::star;
+using namespace http_dav_ucp;
+
+namespace
+{
+void lcl_sendPartialGETRequest( bool &bError,
+ DAVException &aLastException,
+ const std::vector< OUString >& rProps,
+ std::vector< OUString > &aHeaderNames,
+ const std::unique_ptr< DAVResourceAccess > &xResAccess,
+ std::unique_ptr< ContentProperties > &xProps,
+ const uno::Reference< ucb::XCommandEnvironment >& xEnv )
+{
+ DAVResource aResource;
+ DAVRequestHeaders aPartialGet;
+ aPartialGet.push_back(
+ DAVRequestHeader(
+ OUString( "Range" ), // see <https://tools.ietf.org/html/rfc7233#section-3.1>
+ OUString( "bytes=0-0" )));
+
+ bool bIsRequestSize = std::any_of(aHeaderNames.begin(), aHeaderNames.end(),
+ [](const OUString& rHeaderName) { return rHeaderName == "Content-Length"; });
+
+ if ( bIsRequestSize )
+ {
+ // we need to know if the server accepts range requests for a resource
+ // and the range unit it uses
+ aHeaderNames.push_back( OUString( "Accept-Ranges" ) ); // see <https://tools.ietf.org/html/rfc7233#section-2.3>
+ aHeaderNames.push_back( OUString( "Content-Range" ) ); // see <https://tools.ietf.org/html/rfc7233#section-4.2>
+ }
+ try
+ {
+ xResAccess->GET0( aPartialGet, aHeaderNames, aResource, xEnv );
+ bError = false;
+
+ if ( bIsRequestSize )
+ {
+ // the ContentProperties maps "Content-Length" to the UCB "Size" property
+ // This would have an unrealistic value of 1 byte because we did only a partial GET
+ // Solution: if "Content-Range" is present, map it with UCB "Size" property
+ OUString aAcceptRanges, aContentRange, aContentLength;
+ std::vector< DAVPropertyValue > &aResponseProps = aResource.properties;
+ for ( const auto& rResponseProp : aResponseProps )
+ {
+ if ( rResponseProp.Name == "Accept-Ranges" )
+ rResponseProp.Value >>= aAcceptRanges;
+ else if ( rResponseProp.Name == "Content-Range" )
+ rResponseProp.Value >>= aContentRange;
+ else if ( rResponseProp.Name == "Content-Length" )
+ rResponseProp.Value >>= aContentLength;
+ }
+
+ sal_Int64 nSize = 1;
+ if ( aContentLength.getLength() )
+ {
+ nSize = aContentLength.toInt64();
+ }
+
+ // according to http://tools.ietf.org/html/rfc2616#section-3.12
+ // the only range unit defined is "bytes" and implementations
+ // MAY ignore ranges specified using other units.
+ if ( nSize == 1 &&
+ aContentRange.getLength() &&
+ aAcceptRanges == "bytes" )
+ {
+ // Parse the Content-Range to get the size
+ // vid. http://tools.ietf.org/html/rfc2616#section-14.16
+ // Content-Range: <range unit> <bytes range>/<size>
+ sal_Int32 nSlash = aContentRange.lastIndexOf( '/' );
+ if ( nSlash != -1 )
+ {
+ OUString aSize = aContentRange.copy( nSlash + 1 );
+ // "*" means that the instance-length is unknown at the time when the response was generated
+ if ( aSize != "*" )
+ {
+ auto it = std::find_if(aResponseProps.begin(), aResponseProps.end(),
+ [](const DAVPropertyValue& rProp) { return rProp.Name == "Content-Length"; });
+ if (it != aResponseProps.end())
+ {
+ it->Value <<= aSize;
+ }
+ }
+ }
+ }
+ }
+
+ if (xProps)
+ xProps->addProperties(
+ rProps,
+ ContentProperties( aResource ) );
+ else
+ xProps.reset ( new ContentProperties( aResource ) );
+ }
+ catch ( DAVException const & ex )
+ {
+ aLastException = ex;
+ }
+}
+}
+
+// Static value, to manage a simple OPTIONS cache
+// Key is the URL, element is the DAVOptions resulting from an OPTIONS call.
+// Cached DAVOptions have a lifetime that depends on the errors received or not received
+// and on the value of received options.
+static DAVOptionsCache aStaticDAVOptionsCache;
+
+
+// Content Implementation.
+
+
+// ctr for content on an existing webdav resource
+Content::Content(
+ const uno::Reference< uno::XComponentContext >& rxContext,
+ ContentProvider* pProvider,
+ const uno::Reference< ucb::XContentIdentifier >& Identifier,
+ rtl::Reference< DAVSessionFactory > const & rSessionFactory )
+: ContentImplHelper( rxContext, pProvider, Identifier ),
+ m_eResourceType( UNKNOWN ),
+ m_eResourceTypeForLocks( UNKNOWN ),
+ m_pProvider( pProvider ),
+ m_bTransient( false ),
+ m_bCollection( false ),
+ m_bDidGetOrHead( false )
+{
+ try
+ {
+ initOptsCacheLifeTime();
+ m_xResAccess.reset( new DAVResourceAccess(
+ rxContext,
+ rSessionFactory,
+ Identifier->getContentIdentifier() ) );
+
+ CurlUri const aURI( Identifier->getContentIdentifier() );
+ m_aEscapedTitle = aURI.GetPathBaseName();
+ }
+ catch ( DAVException const & )
+ {
+ throw ucb::ContentCreationException();
+ }
+}
+
+
+// ctr for content on a non-existing webdav resource
+Content::Content(
+ const uno::Reference< uno::XComponentContext >& rxContext,
+ ContentProvider* pProvider,
+ const uno::Reference< ucb::XContentIdentifier >& Identifier,
+ rtl::Reference< DAVSessionFactory > const & rSessionFactory,
+ bool isCollection )
+: ContentImplHelper( rxContext, pProvider, Identifier ),
+ m_eResourceType( UNKNOWN ),
+ m_eResourceTypeForLocks( UNKNOWN ),
+ m_pProvider( pProvider ),
+ m_bTransient( true ),
+ m_bCollection( isCollection ),
+ m_bDidGetOrHead( false )
+{
+ try
+ {
+ initOptsCacheLifeTime();
+ m_xResAccess.reset( new DAVResourceAccess(
+ rxContext, rSessionFactory, Identifier->getContentIdentifier() ) );
+ }
+ catch ( DAVException const & )
+ {
+ throw ucb::ContentCreationException();
+ }
+
+ // Do not set m_aEscapedTitle here! Content::insert relays on this!!!
+}
+
+
+// virtual
+Content::~Content()
+{
+}
+
+
+// XInterface methods.
+
+
+// virtual
+void SAL_CALL Content::acquire() noexcept
+{
+ ContentImplHelper::acquire();
+}
+
+
+// virtual
+void SAL_CALL Content::release() noexcept
+{
+ ContentImplHelper::release();
+}
+
+
+// virtual
+uno::Any SAL_CALL Content::queryInterface( const uno::Type & rType )
+{
+ // Note: isFolder may require network activities! So call it only
+ // if it is really necessary!!!
+ uno::Any aRet = cppu::queryInterface(
+ rType,
+ static_cast< ucb::XContentCreator * >( this ) );
+ if ( aRet.hasValue() )
+ {
+ try
+ {
+ uno::Reference< task::XInteractionHandler > xIH(
+ task::PasswordContainerInteractionHandler::create(m_xContext) );
+
+ // Supply a command env to isFolder() that contains an interaction
+ // handler that uses the password container service to obtain
+ // credentials without displaying a password gui.
+
+ uno::Reference< ucb::XCommandEnvironment > xCmdEnv(
+ ucb::CommandEnvironment::create(
+ m_xContext,
+ xIH,
+ uno::Reference< ucb::XProgressHandler >() ) );
+
+ return isFolder( xCmdEnv ) ? aRet : uno::Any();
+ }
+ catch ( uno::RuntimeException const & )
+ {
+ throw;
+ }
+ catch ( uno::Exception const & )
+ {
+ return uno::Any();
+ }
+ }
+ return aRet.hasValue() ? aRet : ContentImplHelper::queryInterface( rType );
+}
+
+
+// XTypeProvider methods.
+
+
+XTYPEPROVIDER_COMMON_IMPL( Content );
+
+
+// virtual
+uno::Sequence< uno::Type > SAL_CALL Content::getTypes()
+{
+ bool bFolder = false;
+ try
+ {
+ bFolder
+ = isFolder( uno::Reference< ucb::XCommandEnvironment >() );
+ }
+ catch ( uno::RuntimeException const & )
+ {
+ throw;
+ }
+ catch ( uno::Exception const & )
+ {
+ }
+
+ if ( bFolder )
+ {
+ static cppu::OTypeCollection s_aFolderTypes(
+ 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_aFolderTypes.getTypes();
+ }
+ else
+ {
+ static cppu::OTypeCollection s_aDocumentTypes(
+ 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_aDocumentTypes.getTypes();
+ }
+}
+
+
+// XServiceInfo methods.
+
+
+// virtual
+OUString SAL_CALL Content::getImplementationName()
+{
+ return "com.sun.star.comp.ucb.WebDAVContent";
+}
+
+
+// virtual
+uno::Sequence< OUString > SAL_CALL Content::getSupportedServiceNames()
+{
+ uno::Sequence<OUString> aSNS { WEBDAV_CONTENT_SERVICE_NAME };
+ return aSNS;
+}
+
+
+// XContent methods.
+
+
+// virtual
+OUString SAL_CALL Content::getContentType()
+{
+ bool bFolder = false;
+ try
+ {
+ bFolder
+ = isFolder( uno::Reference< ucb::XCommandEnvironment >() );
+ }
+ catch ( uno::RuntimeException const & )
+ {
+ throw;
+ }
+ catch ( uno::Exception const & )
+ {
+ }
+
+ if ( bFolder )
+ return WEBDAV_COLLECTION_TYPE;
+
+ return WEBDAV_CONTENT_TYPE;
+}
+
+
+// XCommandProcessor methods.
+
+
+// virtual
+uno::Any SAL_CALL Content::execute(
+ const ucb::Command& aCommand,
+ sal_Int32 /*CommandId*/,
+ const uno::Reference< ucb::XCommandEnvironment >& Environment )
+{
+ SAL_INFO("ucb.ucp.webdav", ">>>>> Content::execute: start: command: " << aCommand.Name
+ << ", env: " << (Environment.is() ? "present" : "missing") );
+
+ uno::Any aRet;
+
+ if ( aCommand.Name == "getPropertyValues" )
+ {
+
+ // getPropertyValues
+
+
+ uno::Sequence< beans::Property > Properties;
+ if ( !( aCommand.Argument >>= Properties ) )
+ {
+ ucbhelper::cancelCommandExecution(
+ uno::Any( lang::IllegalArgumentException(
+ "Wrong argument type!",
+ getXWeak(),
+ -1 ) ),
+ Environment );
+ // Unreachable
+ }
+
+ aRet <<= getPropertyValues( Properties, Environment );
+ }
+ else if ( aCommand.Name == "setPropertyValues" )
+ {
+
+ // setPropertyValues
+
+
+ uno::Sequence< beans::PropertyValue > aProperties;
+ if ( !( aCommand.Argument >>= aProperties ) )
+ {
+ ucbhelper::cancelCommandExecution(
+ uno::Any( lang::IllegalArgumentException(
+ "Wrong argument type!",
+ getXWeak(),
+ -1 ) ),
+ Environment );
+ // Unreachable
+ }
+
+ if ( !aProperties.getLength() )
+ {
+ ucbhelper::cancelCommandExecution(
+ uno::Any( lang::IllegalArgumentException(
+ "No properties!",
+ getXWeak(),
+ -1 ) ),
+ Environment );
+ // Unreachable
+ }
+
+ aRet <<= setPropertyValues( aProperties, Environment );
+ }
+ else if ( aCommand.Name == "getPropertySetInfo" )
+ {
+
+ // getPropertySetInfo
+
+
+ // Note: Implemented by base class.
+ aRet <<= getPropertySetInfo( Environment,
+ false /* don't cache data */ );
+ }
+ else if ( aCommand.Name == "getCommandInfo" )
+ {
+
+ // getCommandInfo
+
+
+ // Note: Implemented by base class.
+ aRet <<= getCommandInfo( Environment, false );
+ }
+ else if ( aCommand.Name == "open" )
+ {
+
+ // open
+
+
+ ucb::OpenCommandArgument3 aOpenCommand;
+ ucb::OpenCommandArgument2 aTmp;
+ if ( !( aCommand.Argument >>= aTmp ) )
+ {
+ ucbhelper::cancelCommandExecution(
+ uno::Any( lang::IllegalArgumentException(
+ "Wrong argument type!",
+ getXWeak(),
+ -1 ) ),
+ Environment );
+ // Unreachable
+ }
+ if ( !( aCommand.Argument >>= aOpenCommand ) )
+ {
+ // compat mode, extract Arg2 info into newer structure
+ aOpenCommand.Mode = aTmp.Mode;
+ aOpenCommand.Priority = aTmp.Priority;
+ aOpenCommand.Sink = aTmp.Sink;
+ aOpenCommand.Properties = aTmp.Properties;
+ aOpenCommand.SortingInfo = aTmp.SortingInfo;
+ }
+
+ aRet = open( aOpenCommand, Environment );
+
+ }
+ else if ( aCommand.Name == "insert" )
+ {
+
+ // insert
+
+
+ ucb::InsertCommandArgument arg;
+ if ( !( aCommand.Argument >>= arg ) )
+ {
+ ucbhelper::cancelCommandExecution(
+ uno::Any( lang::IllegalArgumentException(
+ "Wrong argument type!",
+ getXWeak(),
+ -1 ) ),
+ Environment );
+ // Unreachable
+ }
+
+ insert( arg.Data, arg.ReplaceExisting, Environment );
+ }
+ else if ( aCommand.Name == "delete" )
+ {
+
+ // delete
+
+
+ bool bDeletePhysical = false;
+ aCommand.Argument >>= bDeletePhysical;
+
+// KSO: Ignore parameter and destroy the content, if you don't support
+// putting objects into trashcan. ( Since we do not have a trash can
+// service yet (src603), you actually have no other choice. )
+// if ( bDeletePhysical )
+// {
+ try
+ {
+ std::unique_ptr< DAVResourceAccess > xResAccess;
+ {
+ osl::Guard< osl::Mutex > aGuard( m_aMutex );
+ xResAccess.reset( new DAVResourceAccess( *m_xResAccess ) );
+ }
+ aStaticDAVOptionsCache.removeDAVOptions( xResAccess->getURL() );
+ // clean cached value of PROPFIND property names
+ removeCachedPropertyNames( xResAccess->getURL() );
+ xResAccess->DESTROY( Environment );
+ {
+ osl::Guard< osl::Mutex > aGuard( m_aMutex );
+ m_xResAccess.reset( new DAVResourceAccess( *xResAccess ) );
+ }
+ }
+ catch ( DAVException const & e )
+ {
+ cancelCommandExecution( e, Environment, true );
+ // Unreachable
+ }
+// }
+
+ // Propagate destruction.
+ destroy( bDeletePhysical );
+
+ // Remove own and all children's Additional Core Properties.
+ removeAdditionalPropertySet();
+ }
+ else if ( aCommand.Name == "transfer" && isFolder( Environment ) )
+ {
+
+ // transfer
+ // ( Not available at documents )
+
+
+ ucb::TransferInfo transferArgs;
+ if ( !( aCommand.Argument >>= transferArgs ) )
+ {
+ ucbhelper::cancelCommandExecution(
+ uno::Any( lang::IllegalArgumentException(
+ "Wrong argument type!",
+ getXWeak(),
+ -1 ) ),
+ Environment );
+ // Unreachable
+ }
+
+ transfer( transferArgs, Environment );
+ }
+ else if ( aCommand.Name == "post" )
+ {
+
+ // post
+
+
+ ucb::PostCommandArgument2 aArg;
+ if ( !( aCommand.Argument >>= aArg ) )
+ {
+ ucbhelper::cancelCommandExecution(
+ uno::Any( lang::IllegalArgumentException(
+ "Wrong argument type!",
+ getXWeak(),
+ -1 ) ),
+ Environment );
+ // Unreachable
+ }
+
+ post( aArg, Environment );
+ }
+ else if ( aCommand.Name == "lock" )
+ {
+
+ // lock
+
+ ResourceType eType = resourceTypeForLocks( Environment );
+ // when the resource is not yet present the lock is used to create it
+ // see: http://tools.ietf.org/html/rfc4918#section-7.3
+ // If the resource doesn't exists and the lock is not enabled (DAV with
+ // no lock or a simple web) the error will be dealt with inside lock() method
+ if ( eType == NOT_FOUND ||
+ eType == DAV )
+ {
+ lock( Environment );
+ if ( eType == NOT_FOUND )
+ {
+ m_eResourceType = UNKNOWN; // lock may have created it, need to check again
+ m_eResourceTypeForLocks = UNKNOWN;
+ }
+ }
+ }
+ else if ( aCommand.Name == "unlock" )
+ {
+
+ // unlock
+ // do not check for a DAV resource
+ // the lock store will be checked before sending
+ unlock( Environment );
+ }
+ else if ( aCommand.Name == "createNewContent" &&
+ isFolder( Environment ) )
+ {
+
+ // createNewContent
+
+
+ ucb::ContentInfo aArg;
+ if ( !( aCommand.Argument >>= aArg ) )
+ {
+ ucbhelper::cancelCommandExecution(
+ uno::Any( lang::IllegalArgumentException(
+ "Wrong argument type!",
+ getXWeak(),
+ -1 ) ),
+ Environment );
+ // Unreachable
+ }
+
+ aRet <<= createNewContent( aArg );
+ }
+ else if ( aCommand.Name == "addProperty" )
+ {
+ ucb::PropertyCommandArgument aPropArg;
+ if ( !( aCommand.Argument >>= aPropArg ))
+ {
+ ucbhelper::cancelCommandExecution(
+ uno::Any( lang::IllegalArgumentException(
+ "Wrong argument type!",
+ getXWeak(),
+ -1 ) ),
+ Environment );
+ }
+
+ // TODO when/if XPropertyContainer is removed,
+ // the command execution can be canceled in addProperty
+ try
+ {
+ addProperty( aPropArg, Environment );
+ }
+ catch ( const beans::PropertyExistException &e )
+ {
+ ucbhelper::cancelCommandExecution( uno::Any( e ), Environment );
+ }
+ catch ( const beans::IllegalTypeException&e )
+ {
+ ucbhelper::cancelCommandExecution( uno::Any( e ), Environment );
+ }
+ catch ( const lang::IllegalArgumentException&e )
+ {
+ ucbhelper::cancelCommandExecution( uno::Any( e ), Environment );
+ }
+ }
+ else if ( aCommand.Name == "removeProperty" )
+ {
+ OUString sPropName;
+ if ( !( aCommand.Argument >>= sPropName ) )
+ {
+ ucbhelper::cancelCommandExecution(
+ uno::Any( lang::IllegalArgumentException(
+ "Wrong argument type!",
+ getXWeak(),
+ -1 ) ),
+ Environment );
+ }
+
+ // TODO when/if XPropertyContainer is removed,
+ // the command execution can be canceled in removeProperty
+ try
+ {
+ removeProperty( sPropName, Environment );
+ }
+ catch( const beans::UnknownPropertyException &e )
+ {
+ ucbhelper::cancelCommandExecution( uno::Any( e ), Environment );
+ }
+ catch( const beans::NotRemoveableException &e )
+ {
+ ucbhelper::cancelCommandExecution( uno::Any( e ), Environment );
+ }
+ }
+ else
+ {
+
+ // Unsupported command
+
+
+ ucbhelper::cancelCommandExecution(
+ uno::Any( ucb::UnsupportedCommandException(
+ aCommand.Name,
+ getXWeak() ) ),
+ Environment );
+ // Unreachable
+ }
+
+ SAL_INFO("ucb.ucp.webdav", "<<<<< Content::execute: end: command: " << aCommand.Name);
+
+ return aRet;
+}
+
+
+// virtual
+void SAL_CALL Content::abort( sal_Int32 /*CommandId*/ )
+{
+ try
+ {
+ std::unique_ptr< DAVResourceAccess > xResAccess;
+ {
+ osl::MutexGuard aGuard( m_aMutex );
+ xResAccess.reset( new DAVResourceAccess( *m_xResAccess ) );
+ }
+ xResAccess->abort();
+ {
+ osl::Guard< osl::Mutex > aGuard( m_aMutex );
+ m_xResAccess.reset( new DAVResourceAccess( *xResAccess ) );
+ }
+ }
+ catch ( DAVException const & )
+ {
+ // abort failed!
+ }
+}
+
+
+// XPropertyContainer methods.
+
+
+void Content::addProperty( const css::ucb::PropertyCommandArgument &aCmdArg,
+ const uno::Reference< ucb::XCommandEnvironment >& xEnv )
+{
+// if ( m_bTransient )
+// @@@ ???
+ const beans::Property aProperty = aCmdArg.Property;
+ const uno::Any aDefaultValue = aCmdArg.DefaultValue;
+
+ // check property Name
+ if ( !aProperty.Name.getLength() )
+ throw lang::IllegalArgumentException(
+ "\"addProperty\" with empty Property.Name",
+ getXWeak(),
+ -1 );
+
+ // Check property type.
+ if ( !UCBDeadPropertyValue::supportsType( aProperty.Type ) )
+ throw beans::IllegalTypeException(
+ "\"addProperty\" unsupported Property.Type",
+ getXWeak() );
+
+ // check default value
+ if ( aDefaultValue.hasValue() && aDefaultValue.getValueType() != aProperty.Type )
+ throw beans::IllegalTypeException(
+ "\"addProperty\" DefaultValue does not match Property.Type",
+ getXWeak() );
+
+
+ // Make sure a property with the requested name does not already
+ // exist in dynamic and static(!) properties.
+
+
+ // Take into account special properties with custom namespace
+ // using <prop:the_propname xmlns:prop="the_namespace">
+ OUString aSpecialName;
+ bool bIsSpecial = DAVProperties::isUCBSpecialProperty( aProperty.Name, aSpecialName );
+
+ // Note: This requires network access!
+ if ( getPropertySetInfo( xEnv, false /* don't cache data */ )
+ ->hasPropertyByName( bIsSpecial ? aSpecialName : aProperty.Name ) )
+ {
+ // Property does already exist.
+ throw beans::PropertyExistException();
+ }
+
+
+ // Add a new dynamic property.
+
+
+ ProppatchValue aValue( PROPSET, aProperty.Name, aDefaultValue );
+
+ std::vector< ProppatchValue > aProppatchValues;
+ aProppatchValues.push_back( aValue );
+
+ try
+ {
+ // Set property value at server.
+ std::unique_ptr< DAVResourceAccess > xResAccess;
+ {
+ osl::Guard< osl::Mutex > aGuard( m_aMutex );
+ xResAccess.reset( new DAVResourceAccess( *m_xResAccess ) );
+ }
+ aStaticDAVOptionsCache.removeDAVOptions( xResAccess->getURL() );
+ // clean cached value of PROPFIND property names
+ // PROPPATCH can change them
+ removeCachedPropertyNames( xResAccess->getURL() );
+ xResAccess->PROPPATCH( aProppatchValues, xEnv );
+ {
+ osl::Guard< osl::Mutex > aGuard( m_aMutex );
+ m_xResAccess.reset( new DAVResourceAccess( *xResAccess ) );
+ }
+
+ // Notify propertyset info change listeners.
+ beans::PropertySetInfoChangeEvent evt(
+ getXWeak(),
+ bIsSpecial ? aSpecialName : aProperty.Name,
+ -1, // No handle available
+ beans::PropertySetInfoChange::PROPERTY_INSERTED );
+ notifyPropertySetInfoChange( evt );
+ }
+ catch ( DAVException const & e )
+ {
+ if ( e.getStatus() == SC_FORBIDDEN )
+ {
+ // Support for setting arbitrary dead properties is optional!
+
+ // Store property locally.
+ ContentImplHelper::addProperty( bIsSpecial ? aSpecialName : aProperty.Name,
+ aProperty.Attributes,
+ aDefaultValue );
+ }
+ else
+ {
+ if ( shouldAccessNetworkAfterException( e ) )
+ {
+ try
+ {
+ const ResourceType eType = getResourceType( xEnv );
+ switch ( eType )
+ {
+ case UNKNOWN:
+ case DAV:
+ throw lang::IllegalArgumentException();
+
+ case NON_DAV:
+ // Store property locally.
+ ContentImplHelper::addProperty( bIsSpecial ? aSpecialName : aProperty.Name,
+ aProperty.Attributes,
+ aDefaultValue );
+ break;
+
+ default:
+ SAL_WARN( "ucb.ucp.webdav",
+ "Content::addProperty - "
+ "Unsupported resource type!" );
+ break;
+ }
+ }
+ catch ( uno::Exception const & )
+ {
+ SAL_WARN( "ucb.ucp.webdav",
+ "Content::addProperty - "
+ "Unable to determine resource type!" );
+ }
+ }
+ else
+ {
+ SAL_WARN( "ucb.ucp.webdav",
+ "Content::addProperty - "
+ "Unable to determine resource type!" );
+ }
+ }
+ }
+}
+
+void Content::removeProperty( const OUString& Name,
+ const uno::Reference< ucb::XCommandEnvironment >& xEnv )
+{
+#if 0
+ // @@@ REMOVABLE at the moment not properly set in the PropSetInfo
+ try
+ {
+ beans::Property aProp
+ = getPropertySetInfo( xEnv, false /* don't cache data */ )
+ ->getPropertyByName( Name );
+
+ if ( !( aProp.Attributes & beans::PropertyAttribute::REMOVABLE ) )
+ {
+ // Not removable!
+ throw beans::NotRemoveableException();
+ }
+ }
+ catch ( beans::UnknownPropertyException const & )
+ {
+ //SAL_WARN( "ucb.ucp.webdav", "removeProperty - Unknown property!" );
+ throw;
+ }
+#endif
+
+ // Try to remove property from server.
+ try
+ {
+ std::vector< ProppatchValue > aProppatchValues;
+ ProppatchValue aValue( PROPREMOVE, Name, uno::Any() );
+ aProppatchValues.push_back( aValue );
+
+ // Remove property value from server.
+ std::unique_ptr< DAVResourceAccess > xResAccess;
+ {
+ osl::Guard< osl::Mutex > aGuard( m_aMutex );
+ xResAccess.reset( new DAVResourceAccess( *m_xResAccess ) );
+ }
+ aStaticDAVOptionsCache.removeDAVOptions( xResAccess->getURL() );
+ // clean cached value of PROPFIND property names
+ // PROPPATCH can change them
+ removeCachedPropertyNames( xResAccess->getURL() );
+ xResAccess->PROPPATCH( aProppatchValues, xEnv );
+ {
+ osl::Guard< osl::Mutex > aGuard( m_aMutex );
+ m_xResAccess.reset( new DAVResourceAccess( *xResAccess ) );
+ }
+
+ // Notify propertyset info change listeners.
+ beans::PropertySetInfoChangeEvent evt(
+ getXWeak(),
+ Name,
+ -1, // No handle available
+ beans::PropertySetInfoChange::PROPERTY_REMOVED );
+ notifyPropertySetInfoChange( evt );
+ }
+ catch ( DAVException const & e )
+ {
+ if ( e.getStatus() == SC_FORBIDDEN )
+ {
+ // Support for setting arbitrary dead properties is optional!
+
+ // Try to remove property from local store.
+ ContentImplHelper::removeProperty( Name );
+ }
+ else
+ {
+ if ( shouldAccessNetworkAfterException( e ) )
+ {
+ try
+ {
+ const ResourceType eType = getResourceType( xEnv );
+ switch ( eType )
+ {
+ case UNKNOWN:
+ case DAV:
+ throw beans::UnknownPropertyException(Name);
+
+ case NON_DAV:
+ // Try to remove property from local store.
+ ContentImplHelper::removeProperty( Name );
+ break;
+
+ default:
+ SAL_WARN( "ucb.ucp.webdav",
+ "Content::removeProperty - "
+ "Unsupported resource type!" );
+ break;
+ }
+ }
+ catch ( uno::Exception const & )
+ {
+ SAL_WARN( "ucb.ucp.webdav",
+ "Content::removeProperty - "
+ "Unable to determine resource type!" );
+ }
+ }
+ else
+ {
+ SAL_WARN( "ucb.ucp.webdav",
+ "Content::removeProperty - "
+ "Unable to determine resource type!" );
+// throw beans::UnknownPropertyException();
+ }
+ }
+ }
+}
+
+// virtual
+void SAL_CALL Content::addProperty( const OUString& Name,
+ sal_Int16 Attributes,
+ const uno::Any& DefaultValue )
+{
+ beans::Property aProperty;
+ aProperty.Name = Name;
+ aProperty.Type = DefaultValue.getValueType();
+ aProperty.Attributes = Attributes;
+ aProperty.Handle = -1;
+
+ addProperty( ucb::PropertyCommandArgument( aProperty, DefaultValue ),
+ uno::Reference< ucb::XCommandEnvironment >());
+}
+
+// virtual
+void SAL_CALL Content::removeProperty( const OUString& Name )
+{
+ removeProperty( Name,
+ uno::Reference< ucb::XCommandEnvironment >() );
+}
+
+
+// XContentCreator methods.
+
+
+// virtual
+uno::Sequence< ucb::ContentInfo > SAL_CALL
+Content::queryCreatableContentsInfo()
+{
+ osl::Guard< osl::Mutex > aGuard( m_aMutex );
+
+ uno::Sequence< ucb::ContentInfo > aSeq( 2 );
+
+ // document.
+ aSeq.getArray()[ 0 ].Type = WEBDAV_CONTENT_TYPE;
+ aSeq.getArray()[ 0 ].Attributes
+ = ucb::ContentInfoAttribute::INSERT_WITH_INPUTSTREAM
+ | ucb::ContentInfoAttribute::KIND_DOCUMENT;
+
+ beans::Property aProp;
+ m_pProvider->getProperty( "Title", aProp );
+
+ uno::Sequence< beans::Property > aDocProps( 1 );
+ aDocProps.getArray()[ 0 ] = aProp;
+ aSeq.getArray()[ 0 ].Properties = aDocProps;
+
+ // folder.
+ aSeq.getArray()[ 1 ].Type = WEBDAV_COLLECTION_TYPE;
+ aSeq.getArray()[ 1 ].Attributes
+ = ucb::ContentInfoAttribute::KIND_FOLDER;
+
+ uno::Sequence< beans::Property > aFolderProps( 1 );
+ aFolderProps.getArray()[ 0 ] = aProp;
+ aSeq.getArray()[ 1 ].Properties = aFolderProps;
+ return aSeq;
+}
+
+
+// virtual
+uno::Reference< ucb::XContent > SAL_CALL
+Content::createNewContent( const ucb::ContentInfo& Info )
+{
+ osl::Guard< osl::Mutex > aGuard( m_aMutex );
+
+ if ( !Info.Type.getLength() )
+ return uno::Reference< ucb::XContent >();
+
+ if ( ( Info.Type != WEBDAV_COLLECTION_TYPE )
+ &&
+ ( Info.Type != WEBDAV_CONTENT_TYPE ) )
+ return uno::Reference< ucb::XContent >();
+
+ OUString aURL = m_xIdentifier->getContentIdentifier();
+
+ SAL_WARN_IF( aURL.isEmpty(), "ucb.ucp.webdav",
+ "WebdavContent::createNewContent - empty identifier!" );
+
+ if ( ( aURL.lastIndexOf( '/' ) + 1 ) != aURL.getLength() )
+ aURL += "/";
+
+ bool isCollection;
+ if ( Info.Type == WEBDAV_COLLECTION_TYPE )
+ {
+ aURL += "New_Collection";
+ isCollection = true;
+ }
+ else
+ {
+ aURL += "New_Content";
+ isCollection = false;
+ }
+
+ uno::Reference< ucb::XContentIdentifier > xId(
+ new ::ucbhelper::ContentIdentifier( aURL ) );
+
+ // create the local content
+ try
+ {
+ return new ::http_dav_ucp::Content( m_xContext,
+ m_pProvider,
+ xId,
+ m_xResAccess->getSessionFactory(),
+ isCollection );
+ }
+ catch ( ucb::ContentCreationException & )
+ {
+ return uno::Reference< ucb::XContent >();
+ }
+}
+
+
+// virtual
+OUString Content::getParentURL()
+{
+ // <scheme>:// -> ""
+ // <scheme>://foo -> ""
+ // <scheme>://foo/ -> ""
+ // <scheme>://foo/bar -> <scheme>://foo/
+ // <scheme>://foo/bar/ -> <scheme>://foo/
+ // <scheme>://foo/bar/abc -> <scheme>://foo/bar/
+
+ OUString aURL = m_xIdentifier->getContentIdentifier();
+
+ sal_Int32 nPos = aURL.lastIndexOf( '/' );
+ if ( nPos == ( aURL.getLength() - 1 ) )
+ {
+ // Trailing slash found. Skip.
+ nPos = aURL.lastIndexOf( '/', nPos );
+ }
+
+ sal_Int32 nPos1 = aURL.lastIndexOf( '/', nPos );
+ if ( nPos1 != -1 )
+ nPos1 = aURL.lastIndexOf( '/', nPos1 );
+
+ if ( nPos1 == -1 )
+ return OUString();
+
+ return aURL.copy( 0, nPos + 1 );
+}
+
+
+// Non-interface methods.
+
+
+// static
+uno::Reference< sdbc::XRow > Content::getPropertyValues(
+ const uno::Reference< uno::XComponentContext >& rxContext,
+ const uno::Sequence< beans::Property >& rProperties,
+ const ContentProperties& rData,
+ const rtl::Reference< ::ucbhelper::ContentProviderImplHelper >& rProvider,
+ const OUString& rContentId )
+{
+ // Note: Empty sequence means "get values of all supported properties".
+
+ rtl::Reference< ::ucbhelper::PropertyValueSet > xRow
+ = new ::ucbhelper::PropertyValueSet( rxContext );
+
+ sal_Int32 nCount = rProperties.getLength();
+ if ( nCount )
+ {
+ uno::Reference< beans::XPropertySet > xAdditionalPropSet;
+ bool bTriedToGetAdditionalPropSet = false;
+
+ const beans::Property* pProps = rProperties.getConstArray();
+ for ( sal_Int32 n = 0; n < nCount; ++n )
+ {
+ const beans::Property& rProp = pProps[ n ];
+
+ // Process standard UCB, DAV and HTTP properties.
+ const uno::Any & rValue = rData.getValue( rProp.Name );
+ if ( rValue.hasValue() )
+ {
+ xRow->appendObject( rProp, rValue );
+ }
+ else
+ {
+ // Process local Additional Properties.
+ if ( !bTriedToGetAdditionalPropSet && !xAdditionalPropSet.is() )
+ {
+ xAdditionalPropSet =
+ rProvider->getAdditionalPropertySet( rContentId,
+ false );
+ bTriedToGetAdditionalPropSet = true;
+ }
+
+ if ( !xAdditionalPropSet.is() ||
+ !xRow->appendPropertySetValue(
+ xAdditionalPropSet, rProp ) )
+ {
+ // Append empty entry.
+ xRow->appendVoid( rProp );
+ }
+ }
+ }
+ }
+ else
+ {
+ // Append all standard UCB, DAV and HTTP properties.
+
+ const std::unique_ptr< PropertyValueMap > & xProps = rData.getProperties();
+
+ ContentProvider * pProvider
+ = static_cast< ContentProvider * >( rProvider.get() );
+ beans::Property aProp;
+
+ for ( const auto& rProp : *xProps )
+ {
+ if ( pProvider->getProperty( rProp.first, aProp ) )
+ xRow->appendObject( aProp, rProp.second.value() );
+ }
+
+ // Append all local Additional Properties.
+ uno::Reference< beans::XPropertySet > xSet =
+ rProvider->getAdditionalPropertySet( rContentId, false );
+ xRow->appendPropertySet( xSet );
+ }
+
+ return uno::Reference<sdbc::XRow>(xRow);
+}
+
+namespace {
+void GetPropsUsingHeadRequest(DAVResource& resource,
+ const std::unique_ptr< DAVResourceAccess >& xResAccess,
+ const std::vector< OUString >& aHTTPNames,
+ const uno::Reference< ucb::XCommandEnvironment >& xEnv)
+{
+ if (!aHTTPNames.empty())
+ {
+ DAVOptions aDAVOptions;
+ OUString aTargetURL = xResAccess->getURL();
+ // retrieve the cached options if any
+ aStaticDAVOptionsCache.getDAVOptions(aTargetURL, aDAVOptions);
+
+ // clean cached value of PROPFIND property names
+ // PROPPATCH can change them
+ Content::removeCachedPropertyNames(aTargetURL);
+ // test if HEAD allowed, if not, throw, should be caught immediately
+ // SC_GONE used internally by us, see comment in Content::getPropertyValues
+ // in the catch scope
+ if (aDAVOptions.getHttpResponseStatusCode() != SC_GONE &&
+ !aDAVOptions.isHeadAllowed())
+ {
+ throw DAVException(DAVException::DAV_HTTP_ERROR, "405 Not Implemented", SC_METHOD_NOT_ALLOWED);
+ }
+ // if HEAD is enabled on this site
+ // check if there is a relevant HTTP response status code cached
+ if (aDAVOptions.getHttpResponseStatusCode() != SC_NONE)
+ {
+ // throws exception as if there was a server error, a DAV exception
+ throw DAVException(DAVException::DAV_HTTP_ERROR,
+ aDAVOptions.getHttpResponseStatusText(),
+ aDAVOptions.getHttpResponseStatusCode());
+ // Unreachable
+ }
+
+ xResAccess->HEAD(aHTTPNames, resource, xEnv);
+ }
+}
+}
+
+uno::Reference< sdbc::XRow > Content::getPropertyValues(
+ const uno::Sequence< beans::Property >& rProperties,
+ const uno::Reference< ucb::XCommandEnvironment >& xEnv )
+{
+ std::unique_ptr< ContentProperties > xProps;
+ std::unique_ptr< ContentProperties > xCachedProps;
+ std::unique_ptr< DAVResourceAccess > xResAccess;
+ OUString aUnescapedTitle;
+ bool bHasAll = false;
+ uno::Reference< uno::XComponentContext > xContext;
+ uno::Reference< ucb::XContentIdentifier > xIdentifier;
+ rtl::Reference< ::ucbhelper::ContentProviderImplHelper > xProvider;
+
+ {
+ osl::Guard< osl::Mutex > aGuard( m_aMutex );
+
+ aUnescapedTitle = DecodeURI(m_aEscapedTitle);
+ xContext.set( m_xContext );
+ xIdentifier.set( m_xIdentifier );
+ xProvider = m_xProvider;
+ xResAccess.reset( new DAVResourceAccess( *m_xResAccess ) );
+
+ // First, ask cache...
+ if (m_xCachedProps)
+ {
+ xCachedProps.reset( new ContentProperties( *m_xCachedProps ) );
+
+ std::vector< OUString > aMissingProps;
+ if ( xCachedProps->containsAllNames( rProperties, aMissingProps ) )
+ {
+ // All properties are already in cache! No server access needed.
+ bHasAll = true;
+ }
+
+ // use the cached ContentProperties instance
+ xProps.reset( new ContentProperties( *xCachedProps ) );
+ }
+ }
+
+ bool bNetworkAccessAllowed = true;
+
+ if ( !m_bTransient && !bHasAll )
+ {
+ // Obtain values from server...
+
+
+ // First, identify whether resource is DAV or not
+ const ResourceType eType = getResourceType(
+ xEnv, xResAccess, &bNetworkAccessAllowed );
+
+ if ( eType == DAV )
+ {
+ // cache lookup... getResourceType may fill the props cache via
+ // PROPFIND!
+ if (m_xCachedProps)
+ {
+ xCachedProps.reset(
+ new ContentProperties( *m_xCachedProps ) );
+
+ std::vector< OUString > aMissingProps;
+ if ( xCachedProps->containsAllNames(
+ rProperties, aMissingProps ) )
+ {
+ // All properties are already in cache! No server access
+ // needed.
+ bHasAll = true;
+ }
+
+ // use the cached ContentProperties instance
+ xProps.reset( new ContentProperties( *xCachedProps ) );
+ }
+
+ if ( !bHasAll )
+ {
+ // Only DAV resources support PROPFIND
+ std::vector< OUString > aPropNames;
+
+ uno::Sequence< beans::Property > aProperties(rProperties);
+
+ if ( aProperties.getLength() > 0 )
+ ContentProperties::UCBNamesToDAVNames(
+ aProperties, aPropNames );
+
+ if ( !aPropNames.empty() )
+ {
+ std::vector< DAVResource > resources;
+ try
+ {
+ xResAccess->PROPFIND(
+ DAVZERO, aPropNames, resources, xEnv );
+
+ if ( 1 == resources.size() )
+ {
+#if defined SAL_LOG_INFO
+ {//debug
+ // print received resources
+ std::vector< DAVPropertyValue >::const_iterator it = resources[0].properties.begin();
+ std::vector< DAVPropertyValue >::const_iterator end = resources[0].properties.end();
+ while ( it != end )
+ {
+ OUString aPropValue;
+ bool bValue;
+ uno::Sequence< ucb::LockEntry > aSupportedLocks;
+ if( (*it).Value >>= aPropValue )
+ SAL_INFO( "ucb.ucp.webdav", "PROPFIND (getPropertyValues) - returned property: " << (*it).Name << ":" << aPropValue );
+ else if( (*it).Value >>= bValue )
+ SAL_INFO( "ucb.ucp.webdav", "PROPFIND (getPropertyValues) - returned property: " << (*it).Name << ":" <<
+ ( bValue ? "true" : "false" ) );
+ else if( (*it).Value >>= aSupportedLocks )
+ {
+ SAL_INFO( "ucb.ucp.webdav", "PROPFIND (getPropertyValues) - returned property: " << (*it).Name << ":" );
+ for ( sal_Int32 n = 0; n < aSupportedLocks.getLength(); ++n )
+ {
+ SAL_INFO( "ucb.ucp.webdav"," scope: "
+ << (aSupportedLocks[n].Scope == ucb::LockScope_SHARED ? "shared" : "exclusive")
+ << ", type: "
+ << (aSupportedLocks[n].Type != ucb::LockType_WRITE ? "" : "write") );
+ }
+ }
+ ++it;
+ }
+ }
+#endif
+ if (xProps)
+ xProps->addProperties(
+ aPropNames,
+ ContentProperties( resources[ 0 ] ));
+ else
+ xProps.reset(
+ new ContentProperties( resources[ 0 ] ) );
+ }
+ }
+ catch ( DAVException const & e )
+ {
+ bNetworkAccessAllowed = bNetworkAccessAllowed
+ && shouldAccessNetworkAfterException( e );
+
+ if ( !bNetworkAccessAllowed )
+ {
+ cancelCommandExecution( e, xEnv );
+ // unreachable
+ }
+ }
+ }
+ }
+ }
+
+ if ( bNetworkAccessAllowed )
+ {
+ // All properties obtained already?
+ std::vector< OUString > aMissingProps;
+ if ( !( xProps
+ && xProps->containsAllNames(rProperties, aMissingProps))
+ // i#121922 for non-DAV, uncacheable properties must be fetched
+ // regardless of m_bDidGetOrHead.
+ // But SharePoint may do weird things on HEAD so for DAV
+ // only do this if required.
+ && (eType != DAV || !m_bDidGetOrHead))
+ {
+ // Possibly the missing props can be obtained using a HEAD
+ // request.
+
+ std::vector< OUString > aHeaderNames;
+ ContentProperties::UCBNamesToHTTPNames(
+ rProperties,
+ aHeaderNames );
+
+ if( eType != DAV )
+ {
+ // in case of not DAV PROFIND (previously in program flow) failed
+ // so we need to add the only prop that's common
+ // to DAV and NON_DAV: MediaType, that maps to Content-Type
+ aHeaderNames.push_back( "Content-Type" );
+ }
+
+ if (!aHeaderNames.empty()) try
+ {
+ DAVResource resource;
+ GetPropsUsingHeadRequest(resource, xResAccess, aHeaderNames, xEnv);
+ m_bDidGetOrHead = true;
+
+ if (xProps)
+ xProps->addProperties(
+ aMissingProps,
+ ContentProperties( resource ) );
+ else
+ xProps.reset ( new ContentProperties( resource ) );
+
+ if (m_eResourceType == NON_DAV)
+ xProps->addProperties(aMissingProps,
+ ContentProperties(
+ aUnescapedTitle,
+ false));
+ }
+ catch ( DAVException const & e )
+ {
+ // non "general-purpose servers" may not support HEAD requests
+ // see http://www.w3.org/Protocols/rfc2616/rfc2616-sec5.html#sec5.1.1
+ // In this case, perform a partial GET only to get the header info
+ // vid. http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.35
+ // WARNING if the server does not support partial GETs,
+ // the GET will transfer the whole content
+ bool bError = true;
+ DAVException aLastException = e;
+ OUString aTargetURL = xResAccess->getURL();
+
+ if ( e.getError() == DAVException::DAV_HTTP_ERROR )
+ {
+ // According to the spec. the origin server SHOULD return
+ // * 405 (Method Not Allowed):
+ // the method is known but not allowed for the requested resource
+ // * 501 (Not Implemented):
+ // the method is unrecognized or not implemented
+ // * 404 (SC_NOT_FOUND)
+ // is for google-code server and for MS IIS 10.0 Web server
+ // when only GET is enabled
+ if ( aLastException.getStatus() == SC_NOT_IMPLEMENTED ||
+ aLastException.getStatus() == SC_METHOD_NOT_ALLOWED ||
+ aLastException.getStatus() == SC_NOT_FOUND )
+ {
+ SAL_WARN( "ucb.ucp.webdav", "HEAD probably not implemented: fall back to a partial GET" );
+ aStaticDAVOptionsCache.setHeadAllowed( aTargetURL, false );
+ lcl_sendPartialGETRequest( bError,
+ aLastException,
+ aMissingProps,
+ aHeaderNames,
+ xResAccess,
+ xProps,
+ xEnv );
+ m_bDidGetOrHead = !bError;
+ }
+ }
+
+ if ( bError )
+ {
+ DAVOptions aDAVOptionsException;
+
+ aDAVOptionsException.setURL( aTargetURL );
+ // check if the error was SC_NOT_FOUND, meaning that the
+ // GET fall back didn't succeeded and the element is really missing
+ // we will consider the resource SC_GONE (410) for some time
+ // we use SC_GONE because has the same meaning of SC_NOT_FOUND (404)
+ // see:
+ // <https://tools.ietf.org/html/rfc7231#section-6.5.9> (retrieved 2016-10-09)
+ // apparently it's not used to mark the missing HEAD method (so far...)
+ sal_uInt16 ResponseStatusCode =
+ ( aLastException.getStatus() == SC_NOT_FOUND ) ?
+ SC_GONE :
+ aLastException.getStatus();
+ aDAVOptionsException.setHttpResponseStatusCode( ResponseStatusCode );
+ aDAVOptionsException.setHttpResponseStatusText( aLastException.getData() );
+ aStaticDAVOptionsCache.addDAVOptions( aDAVOptionsException,
+ m_nOptsCacheLifeNotFound );
+
+ if ( !shouldAccessNetworkAfterException( aLastException ) )
+ {
+ cancelCommandExecution( aLastException, xEnv );
+ // unreachable
+ }
+ }
+ }
+ }
+ }
+
+ // might trigger HTTP redirect.
+ // Therefore, title must be updated here.
+ CurlUri const aUri( xResAccess->getURL() );
+ aUnescapedTitle = aUri.GetPathBaseNameUnescaped();
+
+ if ( eType == UNKNOWN )
+ {
+ xProps.reset( new ContentProperties( aUnescapedTitle ) );
+ }
+
+ // For DAV resources we only know the Title, for non-DAV
+ // resources we additionally know that it is a document.
+
+ else if ( eType == DAV )
+ {
+ if (!xProps)
+ xProps.reset(new ContentProperties(aUnescapedTitle));
+ else
+ xProps->addProperty("Title", uno::Any(aUnescapedTitle), true);
+ }
+ else
+ {
+ if (!xProps)
+ xProps.reset( new ContentProperties( aUnescapedTitle, false ) );
+ else
+ xProps->addProperty(
+ "Title",
+ uno::Any( aUnescapedTitle ),
+ true );
+
+ xProps->addProperty(
+ "IsFolder",
+ uno::Any( false ),
+ true );
+ xProps->addProperty(
+ "IsDocument",
+ uno::Any( true ),
+ true );
+ xProps->addProperty(
+ "ContentType",
+ uno::Any( WEBDAV_CONTENT_TYPE ),
+ true );
+ }
+ }
+ else
+ {
+ // No server access for just created (not yet committed) objects.
+ // Only a minimal set of properties supported at this stage.
+ if (m_bTransient)
+ xProps.reset( new ContentProperties( aUnescapedTitle,
+ m_bCollection ) );
+ }
+
+ // Add a default for the properties requested but not found.
+ // Determine still missing properties, add a default.
+ // Some client function doesn't expect a void uno::Any,
+ // but instead wants some sort of default.
+ std::vector< OUString > aMissingProps;
+ if ( !xProps->containsAllNames(
+ rProperties, aMissingProps ) )
+ {
+ //
+ for ( std::vector< rtl::OUString >::const_iterator it = aMissingProps.begin();
+ it != aMissingProps.end(); ++it )
+ {
+ // For the time being only a couple of properties need to be added
+ if ( (*it) == "DateModified" || (*it) == "DateCreated" )
+ {
+ util::DateTime aDate;
+ xProps->addProperty(
+ (*it),
+ uno::Any( aDate ),
+ true );
+ }
+ else if (bNetworkAccessAllowed) // don't set these if connection failed
+ {
+ // If WebDAV didn't return the resource type, assume default
+ // This happens e.g. for lists exported by SharePoint
+ if ((*it) == "IsFolder")
+ {
+ xProps->addProperty(
+ (*it),
+ uno::Any( false ),
+ true );
+ }
+ else if ((*it) == "IsDocument")
+ {
+ xProps->addProperty(
+ (*it),
+ uno::Any( true ),
+ true );
+ }
+ }
+ }
+ }
+
+ sal_Int32 nCount = rProperties.getLength();
+ for ( sal_Int32 n = 0; n < nCount; ++n )
+ {
+ const OUString rName = rProperties[ n ].Name;
+ if ( rName == "BaseURI" )
+ {
+ // Add BaseURI property, if requested.
+ xProps->addProperty(
+ "BaseURI",
+ uno::Any( getBaseURI( xResAccess ) ),
+ true );
+ }
+ else if ( rName == "CreatableContentsInfo" )
+ {
+ // Add CreatableContentsInfo property, if requested.
+ bool bFolder = false;
+ xProps->getValue( "IsFolder" )
+ >>= bFolder;
+ xProps->addProperty(
+ "CreatableContentsInfo",
+ uno::Any( bFolder
+ ? queryCreatableContentsInfo()
+ : uno::Sequence< ucb::ContentInfo >() ),
+ true );
+ }
+ }
+
+ uno::Reference< sdbc::XRow > xResultRow
+ = getPropertyValues( xContext,
+ rProperties,
+ *xProps,
+ xProvider,
+ xIdentifier->getContentIdentifier() );
+
+ {
+ osl::Guard< osl::Mutex > aGuard( m_aMutex );
+
+ if (!m_xCachedProps)
+ m_xCachedProps.reset( new CachableContentProperties( *xProps ) );
+ else
+ m_xCachedProps->addProperties( *xProps );
+
+ m_xResAccess.reset( new DAVResourceAccess( *xResAccess ) );
+ m_aEscapedTitle = EncodeSegment(aUnescapedTitle);
+ }
+
+ return xResultRow;
+}
+
+
+uno::Sequence< uno::Any > Content::setPropertyValues(
+ const uno::Sequence< beans::PropertyValue >& rValues,
+ const uno::Reference< ucb::XCommandEnvironment >& xEnv )
+{
+ uno::Reference< ucb::XContentIdentifier > xIdentifier;
+ rtl::Reference< ContentProvider > xProvider;
+ bool bTransient;
+ std::unique_ptr< DAVResourceAccess > xResAccess;
+
+ {
+ osl::Guard< osl::Mutex > aGuard( m_aMutex );
+
+ xProvider.set( m_pProvider );
+ xIdentifier.set( m_xIdentifier );
+ bTransient = m_bTransient;
+ xResAccess.reset( new DAVResourceAccess( *m_xResAccess ) );
+ }
+
+ uno::Sequence< uno::Any > aRet( rValues.getLength() );
+ auto aRetRange = asNonConstRange(aRet);
+ uno::Sequence< beans::PropertyChangeEvent > aChanges( rValues.getLength() );
+ sal_Int32 nChanged = 0;
+
+ beans::PropertyChangeEvent aEvent;
+ aEvent.Source = getXWeak();
+ aEvent.Further = false;
+ // aEvent.PropertyName =
+ aEvent.PropertyHandle = -1;
+ // aEvent.OldValue =
+ // aEvent.NewValue =
+
+ std::vector< ProppatchValue > aProppatchValues;
+ std::vector< sal_Int32 > aProppatchPropsPositions;
+
+ uno::Reference< ucb::XPersistentPropertySet > xAdditionalPropSet;
+ bool bTriedToGetAdditionalPropSet = false;
+
+ bool bExchange = false;
+ OUString aNewTitle;
+ OUString aOldTitle;
+ sal_Int32 nTitlePos = -1;
+
+ uno::Reference< beans::XPropertySetInfo > xInfo;
+
+ const beans::PropertyValue* pValues = rValues.getConstArray();
+ sal_Int32 nCount = rValues.getLength();
+ for ( sal_Int32 n = 0; n < nCount; ++n )
+ {
+ const beans::PropertyValue& rValue = pValues[ n ];
+ const OUString & rName = rValue.Name;
+
+ beans::Property aTmpProp;
+ xProvider->getProperty( rName, aTmpProp );
+
+ if ( aTmpProp.Attributes & beans::PropertyAttribute::READONLY )
+ {
+ // Read-only property!
+ aRetRange[ n ] <<= lang::IllegalAccessException(
+ "Property is read-only!",
+ getXWeak() );
+ continue;
+ }
+
+
+ // Mandatory props.
+
+
+ if ( rName == "ContentType" )
+ {
+ // Read-only property!
+ aRetRange[ n ] <<= lang::IllegalAccessException(
+ "Property is read-only!",
+ getXWeak() );
+ }
+ else if ( rName == "IsDocument" )
+ {
+ // Read-only property!
+ aRetRange[ n ] <<= lang::IllegalAccessException(
+ "Property is read-only!",
+ getXWeak() );
+ }
+ else if ( rName == "IsFolder" )
+ {
+ // Read-only property!
+ aRetRange[ n ] <<= lang::IllegalAccessException(
+ "Property is read-only!",
+ getXWeak() );
+ }
+ else if ( rName == "Title" )
+ {
+ OUString aNewValue;
+ if ( rValue.Value >>= aNewValue )
+ {
+ // No empty titles!
+ if ( aNewValue.getLength() > 0 )
+ {
+ try
+ {
+ CurlUri const aURI(xIdentifier->getContentIdentifier());
+ aOldTitle = aURI.GetPathBaseNameUnescaped();
+
+ if ( aNewValue != aOldTitle )
+ {
+ // modified title -> modified URL -> exchange !
+ if ( !bTransient )
+ bExchange = true;
+
+ // new value will be set later...
+ aNewTitle = aNewValue;
+
+ // remember position within sequence of values (for
+ // error handling).
+ nTitlePos = n;
+ }
+ }
+ catch ( DAVException const & )
+ {
+ aRetRange[ n ] <<= lang::IllegalArgumentException(
+ "Invalid content identifier!",
+ getXWeak(),
+ -1 );
+ }
+ }
+ else
+ {
+ aRetRange[ n ] <<= lang::IllegalArgumentException(
+ "Empty title not allowed!",
+ getXWeak(),
+ -1 );
+ }
+ }
+ else
+ {
+ aRetRange[ n ] <<= beans::IllegalTypeException(
+ "Property value has wrong type!",
+ getXWeak() );
+ }
+ }
+ else
+ {
+
+ // Optional props.
+
+
+ OUString aSpecialName;
+ bool bIsSpecial = DAVProperties::isUCBSpecialProperty( rName, aSpecialName );
+
+ if ( !xInfo.is() )
+ xInfo = getPropertySetInfo( xEnv,
+ false /* don't cache data */ );
+
+ if ( !xInfo->hasPropertyByName( bIsSpecial ? aSpecialName : rName ) )
+ {
+ // Check, whether property exists. Skip otherwise.
+ // PROPPATCH::set would add the property automatically, which
+ // is not allowed for "setPropertyValues" command!
+ aRetRange[ n ] <<= beans::UnknownPropertyException(
+ "Property is unknown!",
+ getXWeak() );
+ continue;
+ }
+
+ if ( rName == "Size" )
+ {
+ // Read-only property!
+ aRetRange[ n ] <<= lang::IllegalAccessException(
+ "Property is read-only!",
+ getXWeak() );
+ }
+ else if ( rName == "DateCreated" )
+ {
+ // Read-only property!
+ aRetRange[ n ] <<= lang::IllegalAccessException(
+ "Property is read-only!",
+ getXWeak() );
+ }
+ else if ( rName == "DateModified" )
+ {
+ // Read-only property!
+ aRetRange[ n ] <<= lang::IllegalAccessException(
+ "Property is read-only!",
+ getXWeak() );
+ }
+ else if ( rName == "MediaType" )
+ {
+ // Read-only property!
+ // (but could be writable, if 'getcontenttype' would be)
+ aRetRange[ n ] <<= lang::IllegalAccessException(
+ "Property is read-only!",
+ getXWeak() );
+ }
+ if ( rName == "CreatableContentsInfo" )
+ {
+ // Read-only property!
+ aRetRange[ n ] <<= lang::IllegalAccessException(
+ "Property is read-only!",
+ getXWeak() );
+ }
+ else
+ {
+ if ( getResourceType( xEnv, xResAccess ) == DAV )
+ {
+ // Property value will be set on server.
+ ProppatchValue aValue( PROPSET, rName, rValue.Value );
+ aProppatchValues.push_back( aValue );
+
+ // remember position within sequence of values (for
+ // error handling).
+ aProppatchPropsPositions.push_back( n );
+ }
+ else
+ {
+ // Property value will be stored in local property store.
+ if ( !bTriedToGetAdditionalPropSet &&
+ !xAdditionalPropSet.is() )
+ {
+ xAdditionalPropSet
+ = getAdditionalPropertySet( false );
+ bTriedToGetAdditionalPropSet = true;
+ }
+
+ if ( xAdditionalPropSet.is() )
+ {
+ try
+ {
+ uno::Any aOldValue
+ = xAdditionalPropSet->getPropertyValue( rName );
+ if ( aOldValue != rValue.Value )
+ {
+ xAdditionalPropSet->setPropertyValue(
+ rName, rValue.Value );
+
+ aEvent.PropertyName = rName;
+ aEvent.OldValue = aOldValue;
+ aEvent.NewValue = rValue.Value;
+
+ aChanges.getArray()[ nChanged ] = aEvent;
+ nChanged++;
+ }
+ }
+ catch ( beans::UnknownPropertyException const & e )
+ {
+ aRetRange[ n ] <<= e;
+ }
+ catch ( lang::WrappedTargetException const & e )
+ {
+ aRetRange[ n ] <<= e;
+ }
+ catch ( beans::PropertyVetoException const & e )
+ {
+ aRetRange[ n ] <<= e;
+ }
+ catch ( lang::IllegalArgumentException const & e )
+ {
+ aRetRange[ n ] <<= e;
+ }
+ }
+ else
+ {
+ aRetRange[ n ] <<= uno::Exception(
+ "No property set for storing the value!",
+ getXWeak() );
+ }
+ }
+ }
+ }
+ } // for
+
+ if ( !bTransient && (!aProppatchValues.empty()) )
+ {
+ try
+ {
+ // clean cached value of PROPFIND property names
+ // PROPPATCH can change them
+ removeCachedPropertyNames( xResAccess->getURL() );
+ // Set property values at server.
+ aStaticDAVOptionsCache.removeDAVOptions( xResAccess->getURL() );
+ xResAccess->PROPPATCH( aProppatchValues, xEnv );
+
+ for ( const auto& rProppatchValue : aProppatchValues )
+ {
+ aEvent.PropertyName = rProppatchValue.name;
+ aEvent.OldValue = uno::Any(); // @@@ too expensive to obtain!
+ aEvent.NewValue = rProppatchValue.value;
+
+ aChanges.getArray()[ nChanged ] = aEvent;
+ nChanged++;
+ }
+ }
+ catch ( DAVException const & e )
+ {
+// SAL_WARN( "ucb.ucp.webdav",
+// "Content::setPropertyValues - PROPPATCH failed!" );
+
+#if 1
+ cancelCommandExecution( e, xEnv );
+ // unreachable
+#else
+ // Note: PROPPATCH either sets ALL property values OR NOTHING.
+
+ std::vector< sal_Int32 >::const_iterator it
+ = aProppatchPropsPositions.begin();
+ std::vector< sal_Int32 >::const_iterator end
+ = aProppatchPropsPositions.end();
+
+ while ( it != end )
+ {
+ // Set error.
+ aRetRange[ (*it) ] <<= MapDAVException( e, true );
+ ++it;
+ }
+#endif
+ }
+ }
+
+ if ( bExchange )
+ {
+ // Assemble new content identifier...
+
+ OUString aNewURL = getParentURL();
+ if ( aNewURL.lastIndexOf( '/' ) != ( aNewURL.getLength() - 1 ) )
+ aNewURL += "/";
+
+ aNewURL += EncodeSegment(aNewTitle);
+
+ uno::Reference< ucb::XContentIdentifier > xNewId
+ = new ::ucbhelper::ContentIdentifier( aNewURL );
+ uno::Reference< ucb::XContentIdentifier > xOldId = xIdentifier;
+
+ try
+ {
+ CurlUri const sourceURI( xOldId->getContentIdentifier() );
+ CurlUri targetURI( xNewId->getContentIdentifier() );
+
+ targetURI.SetScheme( sourceURI.GetScheme() );
+
+ // clean cached value of PROPFIND property names
+ removeCachedPropertyNames( sourceURI.GetURI() );
+ removeCachedPropertyNames( targetURI.GetURI() );
+ aStaticDAVOptionsCache.removeDAVOptions( sourceURI.GetURI() );
+ aStaticDAVOptionsCache.removeDAVOptions( targetURI.GetURI() );
+ xResAccess->MOVE(
+ sourceURI.GetRelativeReference(), targetURI.GetURI(), false, xEnv );
+
+ // @@@ Should check for resources that could not be moved
+ // (due to source access or target overwrite) and send
+ // this information through the interaction handler.
+
+ // @@@ Existing content should be checked to see if it needs
+ // to be deleted at the source
+
+ // @@@ Existing content should be checked to see if it has
+ // been overwritten at the target
+
+ if ( exchangeIdentity( xNewId ) )
+ {
+ xResAccess->setURL( aNewURL );
+
+// DAV resources store all additional props on server!
+// // Adapt Additional Core Properties.
+// renameAdditionalPropertySet( xOldId->getContentIdentifier(),
+// xNewId->getContentIdentifier(),
+// true );
+ }
+ else
+ {
+ // Do not set new title!
+ aNewTitle.clear();
+
+ // Set error .
+ aRetRange[ nTitlePos ] <<= uno::Exception(
+ "Exchange failed!",
+ getXWeak() );
+ }
+ }
+ catch ( DAVException const & e )
+ {
+ // Do not set new title!
+ aNewTitle.clear();
+
+ // Set error .
+ aRetRange[ nTitlePos ] = MapDAVException( e, true );
+ }
+ }
+
+ if ( aNewTitle.getLength() )
+ {
+ osl::Guard< osl::Mutex > aGuard( m_aMutex );
+
+ aEvent.PropertyName = "Title";
+ aEvent.OldValue <<= aOldTitle;
+ aEvent.NewValue <<= aNewTitle;
+
+ m_aEscapedTitle = EncodeSegment(aNewTitle);
+
+ aChanges.getArray()[ nChanged ] = aEvent;
+ nChanged++;
+ }
+
+ if ( nChanged > 0 )
+ {
+ aChanges.realloc( nChanged );
+ notifyPropertiesChange( aChanges );
+ }
+
+ {
+ osl::Guard< osl::Mutex > aGuard( m_aMutex );
+ m_xResAccess.reset( new DAVResourceAccess( *xResAccess ) );
+ }
+
+ return aRet;
+}
+
+
+uno::Any Content::open(
+ const ucb::OpenCommandArgument3 & rArg,
+ const uno::Reference< ucb::XCommandEnvironment > & xEnv )
+{
+ uno::Any aRet;
+
+ bool bOpenFolder = ( ( rArg.Mode == ucb::OpenMode::ALL ) ||
+ ( rArg.Mode == ucb::OpenMode::FOLDERS ) ||
+ ( rArg.Mode == ucb::OpenMode::DOCUMENTS ) );
+ if ( bOpenFolder )
+ {
+ if ( isFolder( xEnv ) )
+ {
+ // Open collection.
+
+ uno::Reference< ucb::XDynamicResultSet > xSet
+ = new DynamicResultSet( m_xContext, this, rArg, xEnv );
+ aRet <<= xSet;
+ }
+ else
+ {
+ // Error: Not a folder!
+
+ ucbhelper::cancelCommandExecution(
+ uno::Any(
+ lang::IllegalArgumentException(
+ "Non-folder resource cannot be opened as folder! Wrong Open Mode!",
+ getXWeak(),
+ -1 ) ),
+ xEnv );
+ // Unreachable
+ }
+ }
+
+ if ( rArg.Sink.is() )
+ {
+ // Open document.
+
+ if ( ( rArg.Mode == ucb::OpenMode::DOCUMENT_SHARE_DENY_NONE ) ||
+ ( rArg.Mode == ucb::OpenMode::DOCUMENT_SHARE_DENY_WRITE ) )
+ {
+ // Currently(?) unsupported.
+ ucbhelper::cancelCommandExecution(
+ uno::Any(
+ ucb::UnsupportedOpenModeException(
+ OUString(),
+ getXWeak(),
+ sal_Int16( rArg.Mode ) ) ),
+ xEnv );
+ // Unreachable
+ }
+
+ uno::Reference< io::XOutputStream > xOut( rArg.Sink, uno::UNO_QUERY );
+ if ( xOut.is() )
+ {
+ // PUSH: write data
+ try
+ {
+ std::unique_ptr< DAVResourceAccess > xResAccess;
+
+ {
+ osl::MutexGuard aGuard( m_aMutex );
+
+ xResAccess.reset(
+ new DAVResourceAccess( *m_xResAccess ) );
+ }
+
+ xResAccess->setFlags( rArg.OpeningFlags );
+ DAVResource aResource;
+ std::vector< OUString > aHeaders;
+
+ removeCachedPropertyNames( xResAccess->getURL() );
+ xResAccess->GET( xOut, aHeaders, aResource, xEnv );
+ m_bDidGetOrHead = true;
+
+ {
+ osl::MutexGuard aGuard( m_aMutex );
+
+ // cache headers.
+ if (!m_xCachedProps)
+ m_xCachedProps.reset(
+ new CachableContentProperties( ContentProperties( aResource ) ) );
+ else
+ m_xCachedProps->addProperties( ContentProperties( aResource ) );
+
+ m_xResAccess.reset(
+ new DAVResourceAccess( *xResAccess ) );
+ }
+ }
+ catch ( DAVException const & e )
+ {
+ cancelCommandExecution( e, xEnv );
+ // Unreachable
+ }
+ }
+ else
+ {
+ uno::Reference< io::XActiveDataSink > xDataSink( rArg.Sink, uno::UNO_QUERY );
+ if ( xDataSink.is() )
+ {
+ // PULL: wait for client read
+ OUString aTargetURL = m_xIdentifier->getContentIdentifier();
+ try
+ {
+ std::unique_ptr< DAVResourceAccess > xResAccess;
+ {
+ osl::MutexGuard aGuard( m_aMutex );
+
+ xResAccess.reset(
+ new DAVResourceAccess( *m_xResAccess ) );
+ }
+ xResAccess->setFlags( rArg.OpeningFlags );
+
+ // fill inputstream sync; return if all data present
+ DAVResource aResource;
+ std::vector< OUString > aHeaders;
+
+ aTargetURL = xResAccess->getURL();
+ removeCachedPropertyNames( aTargetURL );
+ // check if the resource was present on the server
+ // first update it, if necessary
+ // if the open is called directly, without the default open sequence,
+ // e.g. the one used when opening a file looking for properties
+ // first this call will have no effect, since OPTIONS would have already been called
+ // as a consequence of getPropertyValues()
+ DAVOptions aDAVOptions;
+ getResourceOptions( xEnv, aDAVOptions, xResAccess );
+
+ if (aDAVOptions.getHttpResponseStatusCode() != SC_NONE
+ // tdf#148426 fall back to GET in case of 500
+ && aDAVOptions.getHttpResponseStatusCode() != SC_INTERNAL_SERVER_ERROR)
+ {
+ // throws exception as if there was a server error, a DAV exception
+ throw DAVException( DAVException::DAV_HTTP_ERROR,
+ aDAVOptions.getHttpResponseStatusText(),
+ aDAVOptions.getHttpResponseStatusCode() );
+ }
+ uno::Reference< io::XInputStream > xIn
+ = xResAccess->GET( aHeaders, aResource, xEnv );
+ m_bDidGetOrHead = true;
+
+ {
+ osl::MutexGuard aGuard( m_aMutex );
+
+ // cache headers.
+ if (!m_xCachedProps)
+ m_xCachedProps.reset(
+ new CachableContentProperties( ContentProperties( aResource ) ) );
+ else
+ m_xCachedProps->addProperties(
+ aResource.properties );
+
+ m_xResAccess.reset(
+ new DAVResourceAccess( *xResAccess ) );
+ }
+
+ xDataSink->setInputStream( xIn );
+ }
+ catch ( DAVException const & e )
+ {
+ //TODO cache the http error if not yet cached
+ cancelCommandExecution( e, xEnv );
+ // Unreachable
+ }
+ }
+ else
+ {
+ // Note: aOpenCommand.Sink may contain an XStream
+ // implementation. Support for this type of
+ // sink is optional...
+ ucbhelper::cancelCommandExecution(
+ uno::Any(
+ ucb::UnsupportedDataSinkException(
+ OUString(),
+ getXWeak(),
+ rArg.Sink ) ),
+ xEnv );
+ // Unreachable
+ }
+ }
+ }
+
+ return aRet;
+}
+
+
+void Content::post(
+ const ucb::PostCommandArgument2 & rArg,
+ const uno::Reference< ucb::XCommandEnvironment > & xEnv )
+{
+ uno::Reference< io::XActiveDataSink > xSink( rArg.Sink, uno::UNO_QUERY );
+ if ( xSink.is() )
+ {
+ try
+ {
+ std::unique_ptr< DAVResourceAccess > xResAccess;
+ {
+ osl::MutexGuard aGuard( m_aMutex );
+ xResAccess.reset(
+ new DAVResourceAccess( *m_xResAccess ) );
+ }
+
+ removeCachedPropertyNames( xResAccess->getURL() );
+ uno::Reference< io::XInputStream > xResult
+ = xResAccess->POST( rArg.MediaType,
+ rArg.Referer,
+ rArg.Source,
+ xEnv );
+
+ {
+ osl::MutexGuard aGuard( m_aMutex );
+ m_xResAccess.reset(
+ new DAVResourceAccess( *xResAccess ) );
+ }
+
+ xSink->setInputStream( xResult );
+ }
+ catch ( DAVException const & e )
+ {
+ cancelCommandExecution( e, xEnv, true );
+ // Unreachable
+ }
+ }
+ else
+ {
+ uno::Reference< io::XOutputStream > xResult( rArg.Sink, uno::UNO_QUERY );
+ if ( xResult.is() )
+ {
+ try
+ {
+ std::unique_ptr< DAVResourceAccess > xResAccess;
+ {
+ osl::MutexGuard aGuard( m_aMutex );
+ xResAccess.reset(
+ new DAVResourceAccess( *m_xResAccess ) );
+ }
+
+ removeCachedPropertyNames( xResAccess->getURL() );
+ xResAccess->POST( rArg.MediaType,
+ rArg.Referer,
+ rArg.Source,
+ xResult,
+ xEnv );
+
+ {
+ osl::MutexGuard aGuard( m_aMutex );
+ m_xResAccess.reset(
+ new DAVResourceAccess( *xResAccess ) );
+ }
+ }
+ catch ( DAVException const & e )
+ {
+ cancelCommandExecution( e, xEnv, true );
+ // Unreachable
+ }
+ }
+ else
+ {
+ ucbhelper::cancelCommandExecution(
+ uno::Any(
+ ucb::UnsupportedDataSinkException(
+ OUString(),
+ getXWeak(),
+ rArg.Sink ) ),
+ xEnv );
+ // Unreachable
+ }
+ }
+}
+
+
+void Content::queryChildren( ContentRefList& rChildren )
+{
+ // Obtain a list with a snapshot of all currently instantiated contents
+ // from provider and extract the contents which are direct children
+ // of this content.
+
+ ::ucbhelper::ContentRefList aAllContents;
+ m_xProvider->queryExistingContents( aAllContents );
+
+ OUString aURL = m_xIdentifier->getContentIdentifier();
+ sal_Int32 nURLPos = aURL.lastIndexOf( '/' );
+
+ if ( nURLPos != ( aURL.getLength() - 1 ) )
+ {
+ // No trailing slash found. Append.
+ aURL += "/";
+ }
+
+ sal_Int32 nLen = aURL.getLength();
+
+ for ( const auto& rChild : aAllContents )
+ {
+ ::ucbhelper::ContentImplHelperRef xChild = rChild;
+ OUString aChildURL
+ = xChild->getIdentifier()->getContentIdentifier();
+
+ // Is aURL a prefix of aChildURL?
+ if ( ( aChildURL.getLength() > nLen ) &&
+ ( aChildURL.startsWith( aURL ) ) )
+ {
+ sal_Int32 nPos = nLen;
+ nPos = aChildURL.indexOf( '/', nPos );
+
+ if ( ( nPos == -1 ) ||
+ ( nPos == ( aChildURL.getLength() - 1 ) ) )
+ {
+ // No further slashes / only a final slash. It's a child!
+ rChildren.push_back(
+ ::http_dav_ucp::Content::ContentRef(
+ static_cast< ::http_dav_ucp::Content * >(
+ xChild.get() ) ) );
+ }
+ }
+ }
+}
+
+
+void Content::insert(
+ const uno::Reference< io::XInputStream > & xInputStream,
+ bool bReplaceExisting,
+ const uno::Reference< ucb::XCommandEnvironment >& Environment )
+{
+ bool bTransient, bCollection;
+ OUString aEscapedTitle;
+ std::unique_ptr< DAVResourceAccess > xResAccess;
+
+ {
+ osl::Guard< osl::Mutex > aGuard( m_aMutex );
+
+ bTransient = m_bTransient;
+ bCollection = m_bCollection;
+ aEscapedTitle = m_aEscapedTitle;
+ xResAccess.reset( new DAVResourceAccess( *m_xResAccess ) );
+ }
+
+ // Check, if all required properties are present.
+
+ if ( aEscapedTitle.isEmpty() )
+ {
+ SAL_WARN( "ucb.ucp.webdav", "Content::insert - Title missing!" );
+
+ uno::Sequence<OUString> aProps { "Title" };
+ ucbhelper::cancelCommandExecution(
+ uno::Any( ucb::MissingPropertiesException(
+ OUString(),
+ getXWeak(),
+ aProps ) ),
+ Environment );
+ // Unreachable
+ }
+
+ if ( !bReplaceExisting )
+ {
+ /* [RFC 2616] - HTTP
+
+ The PUT method requests that the enclosed entity be stored under the
+ supplied Request-URI. If the Request-URI refers to an already
+ existing resource, the enclosed entity SHOULD be considered as a
+ modified version of the one residing on the origin server.
+ */
+
+ /* [RFC 2518] - WebDAV
+
+ MKCOL creates a new collection resource at the location specified by
+ the Request-URI. If the resource identified by the Request-URI is
+ non-null then the MKCOL MUST fail.
+ */
+
+ // ==> Complain on PUT, continue on MKCOL.
+ if ( !bTransient || !bCollection )
+ {
+#undef ERROR
+ ucb::UnsupportedNameClashException aEx(
+ "Unable to write without overwrite!",
+ getXWeak(),
+ ucb::NameClash::ERROR );
+
+ uno::Reference< task::XInteractionHandler > xIH;
+
+ if ( Environment.is() )
+ xIH = Environment->getInteractionHandler();
+
+ if ( xIH.is() )
+ {
+ uno::Any aExAsAny( aEx );
+
+ rtl::Reference< ucbhelper::SimpleInteractionRequest > xRequest
+ = new ucbhelper::SimpleInteractionRequest(
+ aExAsAny,
+ ContinuationFlags::Approve
+ | ContinuationFlags::Disapprove );
+ xIH->handle( xRequest );
+
+ const ContinuationFlags nResp = xRequest->getResponse();
+
+ switch ( nResp )
+ {
+ case ContinuationFlags::NONE:
+ // Not handled; throw.
+ throw aEx;
+// break;
+
+ case ContinuationFlags::Approve:
+ // Continue -> Overwrite.
+ bReplaceExisting = true;
+ break;
+
+ case ContinuationFlags::Disapprove:
+ // Abort.
+ throw ucb::CommandFailedException(
+ OUString(),
+ uno::Reference< uno::XInterface >(),
+ aExAsAny );
+// break;
+
+ default:
+ SAL_WARN( "ucb.ucp.webdav",
+ "Content::insert - "
+ "Unknown interaction selection!" );
+ throw ucb::CommandFailedException(
+ "Unknown interaction selection!",
+ uno::Reference< uno::XInterface >(),
+ aExAsAny );
+// break;
+ }
+ }
+ else
+ {
+ // No IH; throw.
+ throw aEx;
+ }
+ }
+ }
+
+ if ( bTransient )
+ {
+ // Assemble new content identifier...
+ OUString aURL = getParentURL();
+ if ( aURL.lastIndexOf( '/' ) != ( aURL.getLength() - 1 ) )
+ aURL += "/";
+
+ aURL += aEscapedTitle;
+
+ try
+ {
+ xResAccess->setURL( aURL );
+
+ if ( bCollection )
+ {
+ aStaticDAVOptionsCache.removeDAVOptions( xResAccess->getURL() );
+ removeCachedPropertyNames( xResAccess->getURL() );
+ xResAccess->MKCOL( Environment );
+ }
+ else
+ {
+ // remove options from cache, PUT may change it
+ // it will be refreshed when needed
+ aStaticDAVOptionsCache.removeDAVOptions( xResAccess->getURL() );
+ removeCachedPropertyNames( xResAccess->getURL() );
+ xResAccess->PUT( xInputStream, Environment );
+ // clean cached value of PROPFIND properties names
+ }
+ // no error , set the resourcetype to unknown type
+ // the resource may have transitioned from NOT FOUND or UNKNOWN to something else
+ // depending on the server behaviour
+ // this will force a recheck of the resource type
+ m_eResourceType = UNKNOWN;
+ m_eResourceTypeForLocks = UNKNOWN;
+ }
+ catch ( DAVException const & except )
+ {
+ if ( bCollection )
+ {
+ if ( except.getStatus() == SC_METHOD_NOT_ALLOWED )
+ {
+ // [RFC 2518] - WebDAV
+ // 405 (Method Not Allowed) - MKCOL can only be
+ // executed on a deleted/non-existent resource.
+
+ if ( bReplaceExisting )
+ {
+ // Destroy old resource.
+ try
+ {
+ removeCachedPropertyNames( xResAccess->getURL() );
+ xResAccess->DESTROY( Environment );
+ }
+ catch ( DAVException const & e )
+ {
+ cancelCommandExecution( e, Environment, true );
+ // Unreachable
+ }
+
+ // Insert (recursion!).
+ insert( xInputStream, bReplaceExisting, Environment );
+
+ {
+ osl::Guard< osl::Mutex > aGuard( m_aMutex );
+ m_xResAccess.reset(
+ new DAVResourceAccess( *xResAccess ) );
+ }
+
+ // Success!
+ return;
+ }
+ else
+ {
+ OUString aTitle;
+ try
+ {
+ CurlUri const aURI( aURL );
+ aTitle = aURI.GetPathBaseNameUnescaped();
+ }
+ catch ( DAVException const & )
+ {
+ }
+
+ ucbhelper::cancelCommandExecution(
+ uno::Any(
+ ucb::NameClashException(
+ OUString(),
+ getXWeak(),
+ task::InteractionClassification_ERROR,
+ aTitle ) ),
+ Environment );
+ // Unreachable
+ }
+ }
+ }
+
+ cancelCommandExecution( except, Environment, true );
+ // Unreachable
+ }
+
+ {
+ osl::Guard< osl::Mutex > aGuard( m_aMutex );
+ m_xIdentifier
+ = new ::ucbhelper::ContentIdentifier( aURL );
+ }
+
+ inserted();
+
+ {
+ osl::Guard< osl::Mutex > aGuard( m_aMutex );
+ m_bTransient = false;
+ }
+ }
+ else
+ {
+ if ( !xInputStream.is() )
+ {
+ ucbhelper::cancelCommandExecution(
+ uno::Any(
+ ucb::MissingInputStreamException(
+ OUString(),
+ getXWeak() ) ),
+ Environment );
+ // Unreachable
+ }
+
+ // save the URL since it may change due to redirection
+ OUString aTargetUrl = xResAccess->getURL();
+ try
+ {
+ removeCachedPropertyNames( xResAccess->getURL() );
+ // remove options from cache, PUT may change it
+ // it will be refreshed when needed
+ aStaticDAVOptionsCache.removeDAVOptions( aTargetUrl );
+ xResAccess->PUT( xInputStream, Environment );
+ }
+ catch ( DAVException const & e )
+ {
+ cancelCommandExecution( e, Environment, true );
+ // Unreachable
+ }
+ }
+
+ {
+ osl::Guard< osl::Mutex > aGuard( m_aMutex );
+ m_xResAccess.reset( new DAVResourceAccess( *xResAccess ) );
+ }
+}
+
+
+void Content::transfer(
+ const ucb::TransferInfo & rArgs,
+ const uno::Reference< ucb::XCommandEnvironment >& Environment )
+{
+ uno::Reference< uno::XComponentContext > xContext;
+ uno::Reference< ucb::XContentIdentifier > xIdentifier;
+ uno::Reference< ucb::XContentProvider > xProvider;
+ std::unique_ptr< DAVResourceAccess > xResAccess;
+
+ {
+ osl::Guard< osl::Mutex > aGuard( m_aMutex );
+
+ xContext.set( m_xContext );
+ xIdentifier.set( m_xIdentifier );
+ xProvider.set( m_xProvider );
+ xResAccess.reset( new DAVResourceAccess( *m_xResAccess ) );
+ }
+
+ OUString aTargetURI;
+ try
+ {
+ CurlUri sourceURI( rArgs.SourceURL );
+ CurlUri targetURI( xIdentifier->getContentIdentifier() );
+
+ aTargetURI = targetURI.GetPathBaseNameUnescaped();
+
+ // Check source's and target's URL scheme
+
+ OUString aScheme = sourceURI.GetScheme().toAsciiLowerCase();
+ if ( aScheme == VNDSUNSTARWEBDAV_URL_SCHEME)
+ {
+ sourceURI.SetScheme( HTTP_URL_SCHEME );
+ }
+ else if ( aScheme == VNDSUNSTARWEBDAVS_URL_SCHEME)
+ {
+ sourceURI.SetScheme( HTTPS_URL_SCHEME );
+ }
+ else if ( aScheme == DAV_URL_SCHEME )
+ {
+ sourceURI.SetScheme( HTTP_URL_SCHEME );
+ }
+ else if ( aScheme == DAVS_URL_SCHEME )
+ {
+ sourceURI.SetScheme( HTTPS_URL_SCHEME );
+ }
+ else if (aScheme == WEBDAV_URL_SCHEME)
+ {
+ sourceURI.SetScheme(HTTP_URL_SCHEME);
+ }
+ else if (aScheme == WEBDAVS_URL_SCHEME)
+ {
+ sourceURI.SetScheme(HTTPS_URL_SCHEME);
+ }
+ else
+ {
+ if ( aScheme != HTTP_URL_SCHEME && aScheme != HTTPS_URL_SCHEME )
+ {
+ ucbhelper::cancelCommandExecution(
+ uno::Any(
+ ucb::InteractiveBadTransferURLException(
+ "Unsupported URL scheme!",
+ getXWeak() ) ),
+ Environment );
+ // Unreachable
+ }
+ }
+
+ aScheme = targetURI.GetScheme().toAsciiLowerCase();
+ if ( aScheme == VNDSUNSTARWEBDAV_URL_SCHEME)
+ targetURI.SetScheme( HTTP_URL_SCHEME );
+ else if ( aScheme == VNDSUNSTARWEBDAVS_URL_SCHEME)
+ targetURI.SetScheme( HTTPS_URL_SCHEME );
+ else if ( aScheme == DAV_URL_SCHEME )
+ targetURI.SetScheme( HTTP_URL_SCHEME );
+ else if ( aScheme == DAVS_URL_SCHEME )
+ targetURI.SetScheme( HTTPS_URL_SCHEME );
+ else if (aScheme == WEBDAV_URL_SCHEME)
+ targetURI.SetScheme(HTTP_URL_SCHEME);
+ else if (aScheme == WEBDAVS_URL_SCHEME)
+ targetURI.SetScheme(HTTPS_URL_SCHEME);
+
+ // @@@ This implementation of 'transfer' only works
+ // if the source and target are located at same host.
+ // (Neon does not support cross-server copy/move)
+
+ // Check for same host
+
+ if ( sourceURI.GetHost().getLength() &&
+ ( sourceURI.GetHost() != targetURI.GetHost() ) )
+ {
+ ucbhelper::cancelCommandExecution(
+ uno::Any( ucb::InteractiveBadTransferURLException(
+ "Different hosts!",
+ getXWeak() ) ),
+ Environment );
+ // Unreachable
+ }
+
+ OUString aTitle = rArgs.NewTitle;
+
+ if ( aTitle.isEmpty() )
+ aTitle = sourceURI.GetPathBaseNameUnescaped();
+
+ if ( aTitle == "/" )
+ {
+ // kso: ???
+ aTitle.clear();
+ }
+
+ targetURI.AppendPath( aTitle );
+
+ OUString aTargetURL = xIdentifier->getContentIdentifier();
+ if ( ( aTargetURL.lastIndexOf( '/' ) + 1 )
+ != aTargetURL.getLength() )
+ aTargetURL += "/";
+
+ aTargetURL += aTitle;
+
+ uno::Reference< ucb::XContentIdentifier > xTargetId
+ = new ::ucbhelper::ContentIdentifier( aTargetURL );
+
+ DAVResourceAccess aSourceAccess( xContext,
+ xResAccess->getSessionFactory(),
+ sourceURI.GetURI() );
+
+ if ( rArgs.MoveData )
+ {
+ uno::Reference< ucb::XContentIdentifier > xId
+ = new ::ucbhelper::ContentIdentifier( rArgs.SourceURL );
+
+ // Note: The static cast is okay here, because its sure that
+ // xProvider is always the WebDAVContentProvider.
+ rtl::Reference< Content > xSource
+ = static_cast< Content * >(
+ xProvider->queryContent( xId ).get() );
+
+ // [RFC 2518] - WebDAV
+ // If a resource exists at the destination and the Overwrite
+ // header is "T" then prior to performing the move the server
+ // MUST perform a DELETE with "Depth: infinity" on the
+ // destination resource. If the Overwrite header is set to
+ // "F" then the operation will fail.
+
+ aStaticDAVOptionsCache.removeDAVOptions( sourceURI.GetURI() );
+ aStaticDAVOptionsCache.removeDAVOptions( targetURI.GetURI() );
+ aSourceAccess.MOVE( sourceURI.GetRelativeReference(),
+ targetURI.GetURI(),
+ rArgs.NameClash
+ == ucb::NameClash::OVERWRITE,
+ Environment );
+
+ if ( xSource.is() )
+ {
+ // Propagate destruction to listeners.
+ xSource->destroy( true );
+ }
+
+// DAV resources store all additional props on server!
+// // Rename own and all children's Additional Core Properties.
+// renameAdditionalPropertySet( xId->getContentIdentifier(),
+// xTargetId->getContentIdentifier(),
+// true );
+ }
+ else
+ {
+ // [RFC 2518] - WebDAV
+ // If a resource exists at the destination and the Overwrite
+ // header is "T" then prior to performing the copy the server
+ // MUST perform a DELETE with "Depth: infinity" on the
+ // destination resource. If the Overwrite header is set to
+ // "F" then the operation will fail.
+
+ aStaticDAVOptionsCache.removeDAVOptions( sourceURI.GetURI() );
+ aStaticDAVOptionsCache.removeDAVOptions( targetURI.GetURI() );
+ aSourceAccess.COPY( sourceURI.GetRelativeReference(),
+ targetURI.GetURI(),
+ rArgs.NameClash
+ == ucb::NameClash::OVERWRITE,
+ Environment );
+
+// DAV resources store all additional props on server!
+// // Copy own and all children's Additional Core Properties.
+// copyAdditionalPropertySet( xId->getContentIdentifier(),
+// xTargetId->getContentIdentifier(),
+// true );
+ }
+
+ // Note: The static cast is okay here, because its sure that
+ // xProvider is always the WebDAVContentProvider.
+ rtl::Reference< Content > xTarget
+ = static_cast< Content * >(
+ xProvider->queryContent( xTargetId ).get() );
+
+ // Announce transferred content in its new folder.
+ xTarget->inserted();
+ }
+ catch ( ucb::IllegalIdentifierException const & )
+ {
+ // queryContent
+ }
+ catch ( DAVException const & e )
+ {
+ // [RFC 2518] - WebDAV
+ // 412 (Precondition Failed) - The server was unable to maintain
+ // the liveness of the properties listed in the propertybehavior
+ // XML element or the Overwrite header is "F" and the state of
+ // the destination resource is non-null.
+
+ if ( e.getStatus() == SC_PRECONDITION_FAILED )
+ {
+ switch ( rArgs.NameClash )
+ {
+ case 0/*ucb::NameClash::ERROR*/:
+ {
+ ucbhelper::cancelCommandExecution(
+ uno::Any(
+ ucb::NameClashException(
+ OUString(),
+ getXWeak(),
+ task::InteractionClassification_ERROR,
+ aTargetURI ) ),
+ Environment );
+ // Unreachable
+ }
+ [[fallthrough]];
+
+ case ucb::NameClash::OVERWRITE:
+ break;
+
+ case ucb::NameClash::KEEP: // deprecated
+ case ucb::NameClash::RENAME:
+ case ucb::NameClash::ASK:
+ default:
+ {
+ ucbhelper::cancelCommandExecution(
+ uno::Any(
+ ucb::UnsupportedNameClashException(
+ OUString(),
+ getXWeak(),
+ rArgs.NameClash ) ),
+ Environment );
+ // Unreachable
+ }
+ }
+ }
+
+ cancelCommandExecution( e, Environment, true );
+ // Unreachable
+ }
+
+ {
+ osl::Guard< osl::Mutex > aGuard( m_aMutex );
+ m_xResAccess.reset( new DAVResourceAccess( *xResAccess ) );
+ }
+}
+
+
+void Content::destroy( bool bDeletePhysical )
+{
+ // @@@ take care about bDeletePhysical -> trashcan support
+
+ uno::Reference< ucb::XContent > xThis = this;
+
+ deleted();
+
+ osl::Guard< osl::Mutex > aGuard( m_aMutex );
+
+ // Process instantiated children...
+
+ ::http_dav_ucp::Content::ContentRefList aChildren;
+ queryChildren( aChildren );
+
+ for ( auto& rChild : aChildren )
+ {
+ rChild->destroy( bDeletePhysical );
+ }
+}
+
+// returns the resource type, to be checked for locks
+Content::ResourceType Content::resourceTypeForLocks(
+ const uno::Reference< ucb::XCommandEnvironment >& Environment,
+ const std::unique_ptr< DAVResourceAccess > & rResAccess)
+{
+ ResourceType eResourceTypeForLocks = UNKNOWN;
+ {
+ osl::MutexGuard g(m_aMutex);
+ //check if cache contains what we need, usually the first PROPFIND on the URI has supported lock
+ if (m_xCachedProps)
+ {
+ uno::Sequence< ucb::LockEntry > aSupportedLocks;
+ if ( m_xCachedProps->getValue( DAVProperties::SUPPORTEDLOCK )
+ >>= aSupportedLocks ) //get the cached value for supportedlock
+ {
+ for ( sal_Int32 n = 0; n < aSupportedLocks.getLength(); ++n )
+ {
+ if ( aSupportedLocks[ n ].Scope
+ == ucb::LockScope_EXCLUSIVE &&
+ aSupportedLocks[ n ].Type
+ == ucb::LockType_WRITE )
+ eResourceTypeForLocks = DAV;
+ }
+ }
+ }
+ }
+
+ const OUString & rURL = m_xIdentifier->getContentIdentifier();
+
+ if ( eResourceTypeForLocks == UNKNOWN )
+ {
+ // resource type for lock/unlock operations still unknown, need to ask the server
+
+ //{
+ DAVOptions aDAVOptions;
+ getResourceOptions( Environment, aDAVOptions, rResAccess );
+ if( aDAVOptions.isClass1() ||
+ aDAVOptions.isClass2() ||
+ aDAVOptions.isClass3() )
+ {
+ // this is at least a DAV, lock to be confirmed
+ // class 2 is needed for full lock support
+ // see
+ // <https://tools.ietf.org/html/rfc4918#section-18.2>
+ eResourceTypeForLocks = DAV_NOLOCK;
+ if( aDAVOptions.isClass2() )
+ {
+ // ok, possible lock, check for it
+ try
+ {
+ // we need only DAV:supportedlock
+ std::vector< DAVResource > resources;
+ std::vector< OUString > aPropNames;
+ uno::Sequence< beans::Property > aProperties( 1 );
+ aProperties.getArray()[ 0 ].Name = DAVProperties::SUPPORTEDLOCK;
+
+ ContentProperties::UCBNamesToDAVNames( aProperties, aPropNames );
+ rResAccess->PROPFIND( DAVZERO, aPropNames, resources, Environment );
+
+ bool wasSupportedlockFound = false;
+
+ // only one resource should be returned
+ if ( resources.size() == 1 )
+ {
+ // we may have received a bunch of other properties
+ // (some servers seems to do so)
+ // but we need only supported lock for this check
+ // all returned properties are in
+ // resources.properties[n].Name/.Value
+
+ std::vector< DAVPropertyValue >::iterator it;
+
+ for ( it = resources[0].properties.begin();
+ it != resources[0].properties.end(); ++it)
+ {
+ if ( (*it).Name == DAVProperties::SUPPORTEDLOCK )
+ {
+ wasSupportedlockFound = true;
+ uno::Sequence< ucb::LockEntry > aSupportedLocks;
+ if ( (*it).Value >>= aSupportedLocks )
+ {
+ for ( sal_Int32 n = 0; n < aSupportedLocks.getLength(); ++n )
+ {
+ // TODO: if the lock type is changed from 'exclusive write' to 'shared write'
+ // e.g. to implement 'Calc shared file feature', the ucb::LockScope_EXCLUSIVE
+ // value should be checked as well, adaptation the code may be needed
+ if ( aSupportedLocks[ n ].Scope == ucb::LockScope_EXCLUSIVE &&
+ aSupportedLocks[ n ].Type == ucb::LockType_WRITE )
+ {
+ // requested locking mode is supported
+ eResourceTypeForLocks = DAV;
+ SAL_INFO( "ucb.ucp.webdav", "resourceTypeForLocks - URL: <"
+ << m_xIdentifier->getContentIdentifier() << ">, DAV lock/unlock supported");
+ break;
+ }
+ }
+ break;
+ }
+ }
+ }
+ }
+ else
+ {
+ // PROPFIND failed; check if HEAD contains Content-Disposition: attachment (RFC1806, HTTP/1.1 19.5.1),
+ // which supposedly means no lock for the resource (happens e.g. with SharePoint exported lists)
+ OUString sContentDisposition;
+ // First, check cached properties
+ if (m_xCachedProps)
+ {
+ if ((m_xCachedProps->getValue("Content-Disposition") >>= sContentDisposition)
+ && sContentDisposition.startsWithIgnoreAsciiCase("attachment"))
+ {
+ eResourceTypeForLocks = DAV_NOLOCK;
+ wasSupportedlockFound = true;
+ }
+ }
+ // If no data in cache, try HEAD request
+ if (sContentDisposition.isEmpty() && !m_bDidGetOrHead) try
+ {
+ DAVResource resource;
+ GetPropsUsingHeadRequest(resource, rResAccess, {"Content-Disposition"}, Environment);
+ m_bDidGetOrHead = true;
+ for (const auto& it : resource.properties)
+ {
+ if (it.Name.equalsIgnoreAsciiCase("Content-Disposition"))
+ {
+ if ((it.Value >>= sContentDisposition) && sContentDisposition.equalsIgnoreAsciiCase("attachment"))
+ {
+ eResourceTypeForLocks = DAV_NOLOCK;
+ wasSupportedlockFound = true;
+ }
+ break;
+ }
+ }
+ }
+ catch (...){}
+ }
+ // check if this is still only a DAV_NOLOCK
+ // a fallback for resources that do not have DAVProperties::SUPPORTEDLOCK property
+ // we check for the returned OPTION if LOCK is allowed on the resource
+ if ( !wasSupportedlockFound && eResourceTypeForLocks == DAV_NOLOCK )
+ {
+ SAL_INFO( "ucb.ucp.webdav", "This WebDAV server has no supportedlock property, check for allowed LOCK method in OPTIONS" );
+ // ATTENTION: if the lock type is changed from 'exclusive write' to 'shared write'
+ // e.g. to implement 'Calc shared file feature' on WebDAV directly, and we arrive to this fallback
+ // and the LOCK is allowed, we should assume that only exclusive write lock is available
+ // this is just a reminder...
+ if ( aDAVOptions.isLockAllowed() )
+ eResourceTypeForLocks = DAV;
+ }
+ }
+ catch ( DAVException const & e )
+ {
+ rResAccess->resetUri();
+ //grab the error code
+ switch( e.getStatus() )
+ {
+ case SC_NOT_FOUND:
+ SAL_WARN( "ucb.ucp.webdav", "resourceTypeForLocks() - URL: <"
+ << m_xIdentifier->getContentIdentifier() << "> was not found. ");
+ eResourceTypeForLocks = NOT_FOUND;
+ break;
+ // some servers returns SC_FORBIDDEN, instead
+ // the meaning of SC_FORBIDDEN is, according to <http://tools.ietf.org/html/rfc7231#section-6.5.3>:
+ // The 403 (Forbidden) status code indicates that the server understood
+ // the request but refuses to authorize it
+ case SC_FORBIDDEN:
+ // Errors SC_NOT_IMPLEMENTED and SC_METHOD_NOT_ALLOWED are
+ // part of base http 1.1 RFCs
+ case SC_NOT_IMPLEMENTED: // <http://tools.ietf.org/html/rfc7231#section-6.6.2>
+ case SC_METHOD_NOT_ALLOWED: // <http://tools.ietf.org/html/rfc7231#section-6.5.5>
+ // they all mean the resource is NON_DAV
+ SAL_WARN( "ucb.ucp.webdav", "resourceTypeForLocks() DAVException (SC_FORBIDDEN, SC_NOT_IMPLEMENTED or SC_METHOD_NOT_ALLOWED) - URL: <"
+ << m_xIdentifier->getContentIdentifier() << ">, DAV ExceptionCode: " << e.getError() << ", HTTP error: " << e.getStatus() );
+ eResourceTypeForLocks = NON_DAV;
+ break;
+ default:
+ //fallthrough
+ SAL_WARN( "ucb.ucp.webdav", "resourceTypeForLocks() DAVException - URL: <"
+ << m_xIdentifier->getContentIdentifier() << ">, DAV ExceptionCode: " << e.getError() << ", HTTP error: " << e.getStatus() );
+ eResourceTypeForLocks = UNKNOWN;
+ }
+ }
+ }
+ }
+ else
+ eResourceTypeForLocks = NON_DAV;
+
+ //}
+ }
+ osl::MutexGuard g(m_aMutex);
+ if (m_eResourceTypeForLocks == UNKNOWN)
+ {
+ m_eResourceTypeForLocks = eResourceTypeForLocks;
+ }
+ else
+ {
+ SAL_WARN_IF(
+ eResourceTypeForLocks != m_eResourceTypeForLocks, "ucb.ucp.webdav",
+ "different resource types for <" << rURL << ">: "
+ << +eResourceTypeForLocks << " vs. " << +m_eResourceTypeForLocks);
+ }
+ SAL_INFO( "ucb.ucp.webdav", "resourceTypeForLocks() - URL: <"
+ << m_xIdentifier->getContentIdentifier() << ">, m_eResourceTypeForLocks: " << m_eResourceTypeForLocks );
+ return m_eResourceTypeForLocks;
+}
+
+Content::ResourceType Content::resourceTypeForLocks(
+ const uno::Reference< ucb::XCommandEnvironment >& Environment )
+{
+ std::unique_ptr< DAVResourceAccess > xResAccess;
+ {
+ osl::MutexGuard aGuard( m_aMutex );
+ xResAccess.reset( new DAVResourceAccess( *m_xResAccess ) );
+ }
+ Content::ResourceType ret = resourceTypeForLocks( Environment, xResAccess );
+ {
+ osl::Guard< osl::Mutex > aGuard( m_aMutex );
+ m_xResAccess.reset( new DAVResourceAccess( *xResAccess ) );
+ }
+ return ret;
+}
+
+void Content::lock(
+ const uno::Reference< ucb::XCommandEnvironment >& Environment )
+{
+// prepare aURL to be used in exception, see below
+ OUString aURL;
+ if ( m_bTransient )
+ {
+ aURL = getParentURL();
+ if ( aURL.lastIndexOf('/') != ( aURL.getLength() - 1 ) )
+ aURL += "/";
+
+ aURL += m_aEscapedTitle;
+ }
+ else
+ {
+ aURL = m_xIdentifier->getContentIdentifier();
+ }
+
+ try
+ {
+ std::unique_ptr< DAVResourceAccess > xResAccess;
+ {
+ osl::Guard< osl::Mutex > aGuard( m_aMutex );
+ xResAccess.reset( new DAVResourceAccess( *m_xResAccess ) );
+ }
+
+ uno::Any aOwnerAny;
+ OUString const user(officecfg::Office::Common::Save::Document::UseUserData::get()
+ ? " - " + ::svt::LockFileCommon::GetOOOUserName()
+ : OUString());
+ aOwnerAny <<= OUString("LibreOffice" + user);
+
+ ucb::Lock aLock(
+ ucb::LockScope_EXCLUSIVE,
+ ucb::LockType_WRITE,
+ ucb::LockDepth_ZERO,
+ aOwnerAny,
+ 180, // lock timeout in secs
+ //-1, // infinite lock
+ uno::Sequence< OUString >() );
+
+ // OPTIONS may change as a consequence of the lock operation
+ aStaticDAVOptionsCache.removeDAVOptions( xResAccess->getURL() );
+ removeCachedPropertyNames( xResAccess->getURL() );
+ xResAccess->LOCK( aLock, Environment );
+
+ {
+ osl::Guard< osl::Mutex > aGuard( m_aMutex );
+ m_xResAccess.reset( new DAVResourceAccess( *xResAccess ) );
+ }
+ }
+ catch ( DAVException const & e )
+ {
+ // check if the exception thrown is 'already locked'
+ // this exception is mapped directly to the ucb correct one, without
+ // going into the cancelCommandExecution() user interaction
+ // this exception should be managed by the issuer of 'lock' command
+ switch( e.getError() )
+ {
+ case DAVException::DAV_LOCKED:
+ {
+ SAL_WARN( "ucb.ucp.webdav", "lock(): resource already locked - URL: <"
+ << m_xIdentifier->getContentIdentifier() << ">");
+ throw
+ ucb::InteractiveLockingLockedException(
+ "Locked!",
+ getXWeak(),
+ task::InteractionClassification_ERROR,
+ aURL,
+ false );
+ }
+ break;
+ case DAVException::DAV_HTTP_NOAUTH:
+ case DAVException::DAV_HTTP_AUTH:
+ {
+ SAL_WARN( "ucb.ucp.webdav", "lock(): DAVException Authentication error - URL: <"
+ << m_xIdentifier->getContentIdentifier() << ">" );
+ // DAVException::DAV_HTTP_AUTH exception can mean:
+ // - interaction handler for credential management not present (happens, depending
+ // on the LO framework processing)
+ // - the remote site is a WebDAV with special configuration: read/only for read operations
+ // and read/write for write operations, the user is not allowed to lock/write and
+ // she cancelled the credentials request.
+ // this is not actually an error, but the exception is sent directly from here, avoiding the automatic
+ // management that takes part in cancelCommandExecution() below
+ // Unfortunately there is no InteractiveNetwork*Exception available to signal this
+ // since it mostly happens on read/only part of webdav, this appears to be the most correct exception available
+ throw
+ ucb::InteractiveNetworkWriteException(
+ "Authentication error while trying to lock! Write only WebDAV perhaps?",
+ getXWeak(),
+ task::InteractionClassification_ERROR,
+ e.getData() );
+ }
+ break;
+ case DAVException::DAV_HTTP_ERROR:
+ //grab the error code
+ switch( e.getStatus() )
+ {
+ // The 'case SC_NOT_FOUND' just below tries to solve a problem in eXo Platform
+ // WebDAV connector which apparently fail on resource first creation
+ // rfc4918 section-7.3 (see link below)
+ case SC_NOT_FOUND: // <http://tools.ietf.org/html/rfc7231#section-6.5.4>
+ // The 'case SC_PRECONDITION_FAILED' just below tries to solve a problem
+ // in SharePoint when locking the resource on first creation fails due to this:
+ // <https://msdn.microsoft.com/en-us/library/jj575265%28v=office.12%29.aspx#id15>
+ // (retrieved on 2015-08-14)
+ case SC_PRECONDITION_FAILED: // <http://tools.ietf.org/html/rfc7232#section-4.2>
+ // Errors SC_NOT_IMPLEMENTED and SC_METHOD_NOT_ALLOWED are
+ // part of base http 1.1 RFCs
+ case SC_NOT_IMPLEMENTED: // <http://tools.ietf.org/html/rfc7231#section-6.6.2>
+ case SC_METHOD_NOT_ALLOWED: // <http://tools.ietf.org/html/rfc7231#section-6.5.5>
+ SAL_WARN( "ucb.ucp.webdav", "lock() DAVException (SC_NOT_FOUND, SC_PRECONDITION_FAILED, SC_NOT_IMPLEMENTED or SC_METHOD_NOT_ALLOWED) - URL: <"
+ << m_xIdentifier->getContentIdentifier() << ">, DAV ExceptionCode: " << e.getError() << ", HTTP error: " << e.getStatus() );
+ // act as nothing happened
+ // that's because when a resource is first created
+ // the lock is sent before the put, so the resource
+ // is actually created by LOCK, locking it before
+ // the first PUT, but if LOCK is not supported
+ // (simple web or DAV with lock disabled) we end with one of these http
+ // errors.
+ // These same errors may be reported when the LOCK on an unmapped
+ // (i.e. non existent) resource is not implemented.
+ // Detailed specification in:
+ // <http://tools.ietf.org/html/rfc4918#section-7.3>
+ return;
+ default:
+ //fallthrough
+ ;
+ }
+ break;
+ case DAVException::DAV_LOCKED_SELF:
+ // we already hold the lock and it is in our internal lockstore
+ // just return as if the lock was successful
+ return;
+ default:
+ //fallthrough
+ ;
+ }
+
+ SAL_WARN( "ucb.ucp.webdav","lock() DAVException - URL: <"
+ << m_xIdentifier->getContentIdentifier() << ">, DAV ExceptionCode: " << e.getError() << ", HTTP error: " << e.getStatus() );
+ cancelCommandExecution( e, Environment, false );
+ // Unreachable
+ }
+}
+
+
+void Content::unlock(
+ const uno::Reference< ucb::XCommandEnvironment >& Environment )
+{
+
+ try
+ {
+ std::unique_ptr< DAVResourceAccess > xResAccess;
+ {
+ osl::Guard< osl::Mutex > aGuard( m_aMutex );
+ xResAccess.reset( new DAVResourceAccess( *m_xResAccess ) );
+ }
+
+ // check if the target URL is a Class1 DAV
+ DAVOptions aDAVOptions;
+ getResourceOptions( Environment, aDAVOptions, xResAccess );
+
+ // at least class one is needed
+ if( aDAVOptions.isClass1() )
+ {
+ // remove options from cache, unlock may change it
+ // it will be refreshed when needed
+ aStaticDAVOptionsCache.removeDAVOptions( xResAccess->getURL() );
+ // clean cached value of PROPFIND properties names
+ removeCachedPropertyNames( xResAccess->getURL() );
+ xResAccess->UNLOCK( Environment );
+ }
+
+ {
+ osl::Guard< osl::Mutex > aGuard( m_aMutex );
+ m_xResAccess.reset( new DAVResourceAccess( *xResAccess ) );
+ }
+ }
+ catch ( DAVException const & e )
+ {
+ switch( e.getError() )
+ {
+ case DAVException::DAV_NOT_LOCKED:
+ SAL_WARN( "ucb.ucp.webdav", "unlock(): DAVException::DAV_NOT_LOCKED - URL: <"
+ << m_xIdentifier->getContentIdentifier() << ">");
+ // means that we don't own any lock on this resource
+ // intercepted here to remove a confusing indication to the user
+ // unfortunately this happens in some WebDAV server configuration
+ // acting as WebDAV and having lock/unlock enabled only
+ // for authorized user.
+ return;
+ case DAVException::DAV_HTTP_ERROR:
+ //grab the error code
+ switch( e.getStatus() )
+ {
+ // Errors SC_NOT_IMPLEMENTED and SC_METHOD_NOT_ALLOWED are
+ // part of base http 1.1 RFCs
+ case SC_NOT_IMPLEMENTED: // <http://tools.ietf.org/html/rfc7231#section-6.6.2>
+ case SC_METHOD_NOT_ALLOWED: // <http://tools.ietf.org/html/rfc7231#section-6.5.5>
+ SAL_WARN( "ucb.ucp.webdav", "unlock() DAVException (SC_NOT_IMPLEMENTED or SC_METHOD_NOT_ALLOWED) - URL: <"
+ << m_xIdentifier->getContentIdentifier() << ">, DAV ExceptionCode: " << e.getError() << ", HTTP error: " << e.getStatus() );
+ return;
+ default:
+ //fallthrough
+ ;
+ }
+ break;
+ default:
+ //fallthrough
+ ;
+ }
+ SAL_WARN( "ucb.ucp.webdav","unlock() DAVException - URL: <"
+ << m_xIdentifier->getContentIdentifier() << ">, DAV ExceptionCode: " << e.getError() << ", HTTP error: " << e.getStatus() );
+ cancelCommandExecution( e, Environment, false );
+ // Unreachable
+ }
+}
+
+
+bool Content::exchangeIdentity(
+ const uno::Reference< ucb::XContentIdentifier >& xNewId )
+{
+ if ( !xNewId.is() )
+ return false;
+
+ osl::ClearableGuard< osl::Mutex > aGuard( m_aMutex );
+
+ uno::Reference< ucb::XContent > xThis = this;
+
+ // Already persistent?
+ if ( m_bTransient )
+ {
+ SAL_WARN( "ucb.ucp.webdav", "Content::exchangeIdentity - Not persistent!" );
+ return false;
+ }
+
+ // Exchange own identity.
+
+ // Fail, if a content with given id already exists.
+// if ( !hasData( xNewId ) )
+ {
+ OUString aOldURL = m_xIdentifier->getContentIdentifier();
+
+ aGuard.clear();
+ if ( exchange( xNewId ) )
+ {
+ // Process instantiated children...
+
+ ContentRefList aChildren;
+ queryChildren( aChildren );
+
+ for ( const auto& rChild : aChildren )
+ {
+ ContentRef xChild = rChild;
+
+ // Create new content identifier for the child...
+ uno::Reference< ucb::XContentIdentifier >
+ xOldChildId = xChild->getIdentifier();
+ OUString aOldChildURL
+ = xOldChildId->getContentIdentifier();
+ OUString aNewChildURL
+ = aOldChildURL.replaceAt(
+ 0,
+ aOldURL.getLength(),
+ xNewId->getContentIdentifier() );
+ uno::Reference< ucb::XContentIdentifier > xNewChildId
+ = new ::ucbhelper::ContentIdentifier( aNewChildURL );
+
+ if ( !xChild->exchangeIdentity( xNewChildId ) )
+ return false;
+ }
+ return true;
+ }
+ }
+
+ SAL_WARN( "ucb.ucp.webdav",
+ "Content::exchangeIdentity - "
+ "Panic! Cannot exchange identity!" );
+ return false;
+}
+
+
+bool Content::isFolder(
+ const uno::Reference< ucb::XCommandEnvironment >& xEnv )
+{
+ {
+ osl::MutexGuard aGuard( m_aMutex );
+
+ if ( m_bTransient )
+ return m_bCollection;
+ }
+
+ uno::Sequence< beans::Property > aProperties( 1 );
+ auto pProperties = aProperties.getArray();
+ pProperties[ 0 ].Name = "IsFolder";
+ pProperties[ 0 ].Handle = -1;
+ uno::Reference< sdbc::XRow > xRow( getPropertyValues( aProperties, xEnv ) );
+ if ( xRow.is() )
+ {
+ try
+ {
+ return xRow->getBoolean( 1 );
+ }
+ catch ( sdbc::SQLException const & )
+ {
+ }
+ }
+
+ return false;
+}
+
+
+uno::Any Content::MapDAVException( const DAVException & e, bool bWrite )
+{
+ // Map DAVException...
+ uno::Any aException;
+
+ OUString aURL;
+ if ( m_bTransient )
+ {
+ aURL = getParentURL();
+ if ( aURL.lastIndexOf( '/' ) != ( aURL.getLength() - 1 ) )
+ aURL += "/";
+
+ aURL += m_aEscapedTitle;
+ }
+ else
+ {
+ aURL = m_xIdentifier->getContentIdentifier();
+ }
+
+ switch ( e.getStatus() )
+ {
+ case SC_NOT_FOUND:
+ {
+ uno::Sequence<uno::Any> aArgs{ uno::Any(beans::PropertyValue(
+ "Uri", -1, uno::Any(aURL), beans::PropertyState_DIRECT_VALUE)) };
+
+ aException <<=
+ ucb::InteractiveAugmentedIOException(
+ "Not found!",
+ getXWeak(),
+ task::InteractionClassification_ERROR,
+ ucb::IOErrorCode_NOT_EXISTING,
+ aArgs );
+ return aException;
+ }
+ default:
+ break;
+ }
+
+ switch ( e.getError() )
+ {
+ case DAVException::DAV_HTTP_ERROR:
+ {
+ if ( bWrite )
+ aException <<=
+ ucb::InteractiveNetworkWriteException(
+ e.getData(),
+ getXWeak(),
+ task::InteractionClassification_ERROR,
+ e.getData() );
+ else
+ aException <<=
+ ucb::InteractiveNetworkReadException(
+ e.getData(),
+ getXWeak(),
+ task::InteractionClassification_ERROR,
+ e.getData() );
+ break;
+ }
+
+ case DAVException::DAV_HTTP_LOOKUP:
+ aException <<=
+ ucb::InteractiveNetworkResolveNameException(
+ OUString(),
+ getXWeak(),
+ task::InteractionClassification_ERROR,
+ e.getData() );
+ break;
+
+// @@@ No matching InteractiveNetwork*Exception
+// case DAVException::DAV_HTTP_AUTH:
+// break;
+
+// @@@ No matching InteractiveNetwork*Exception
+// case DAVException::DAV_HTTP_AUTHPROXY:
+// break;
+
+ case DAVException::DAV_HTTP_TIMEOUT:
+ case DAVException::DAV_HTTP_CONNECT:
+ aException <<=
+ ucb::InteractiveNetworkConnectException(
+ OUString(),
+ getXWeak(),
+ task::InteractionClassification_ERROR,
+ e.getData() );
+ break;
+
+// @@@ No matching InteractiveNetwork*Exception
+// case DAVException::DAV_HTTP_REDIRECT:
+// break;
+
+// @@@ No matching InteractiveNetwork*Exception
+// case DAVException::DAV_SESSION_CREATE:
+// break;
+
+ case DAVException::DAV_INVALID_ARG:
+ aException <<=
+ lang::IllegalArgumentException(
+ OUString(),
+ getXWeak(),
+ -1 );
+ break;
+
+ case DAVException::DAV_LOCKED:
+#if 1
+ aException <<=
+ ucb::InteractiveLockingLockedException(
+ "Locked!",
+ getXWeak(),
+ task::InteractionClassification_ERROR,
+ aURL,
+ false ); // not SelfOwned
+#else
+ {
+ uno::Sequence< uno::Any > aArgs( 1 );
+ aArgs[ 0 ] <<= beans::PropertyValue(
+ OUString("Uri"), -1,
+ uno::makeAny(aURL),
+ beans::PropertyState_DIRECT_VALUE);
+
+ aException <<=
+ ucb::InteractiveAugmentedIOException(
+ OUString( "Locked!" ),
+ getXWeak(),
+ task::InteractionClassification_ERROR,
+ ucb::IOErrorCode_LOCKING_VIOLATION,
+ aArgs );
+ }
+#endif
+ break;
+
+ case DAVException::DAV_LOCKED_SELF:
+ aException <<=
+ ucb::InteractiveLockingLockedException(
+ "Locked (self)!",
+ getXWeak(),
+ task::InteractionClassification_ERROR,
+ aURL,
+ true ); // SelfOwned
+ break;
+
+ case DAVException::DAV_NOT_LOCKED:
+ aException <<=
+ ucb::InteractiveLockingNotLockedException(
+ "Not locked!",
+ getXWeak(),
+ task::InteractionClassification_ERROR,
+ aURL );
+ break;
+
+ case DAVException::DAV_LOCK_EXPIRED:
+ aException <<=
+ ucb::InteractiveLockingLockExpiredException(
+ "Lock expired!",
+ getXWeak(),
+ task::InteractionClassification_ERROR,
+ aURL );
+ break;
+
+ default:
+ aException <<=
+ ucb::InteractiveNetworkGeneralException(
+ OUString(),
+ getXWeak(),
+ task::InteractionClassification_ERROR );
+ break;
+ }
+
+ return aException;
+}
+
+
+// static
+bool Content::shouldAccessNetworkAfterException( const DAVException & e )
+{
+ if ( ( e.getStatus() == SC_NOT_FOUND ) ||
+ ( e.getStatus() == SC_GONE ) ||
+ ( e.getError() == DAVException::DAV_HTTP_TIMEOUT ) ||
+ ( e.getError() == DAVException::DAV_HTTP_LOOKUP ) ||
+ ( e.getError() == DAVException::DAV_HTTP_CONNECT ) ||
+ ( e.getError() == DAVException::DAV_HTTP_NOAUTH ) ||
+ ( e.getError() == DAVException::DAV_HTTP_AUTH ) ||
+ ( e.getError() == DAVException::DAV_HTTP_AUTHPROXY ) )
+ return false;
+
+ return true;
+}
+
+
+void Content::cancelCommandExecution(
+ const DAVException & e,
+ const uno::Reference< ucb::XCommandEnvironment > & xEnv,
+ bool bWrite /* = false */ )
+{
+ ucbhelper::cancelCommandExecution( MapDAVException( e, bWrite ), xEnv );
+ // Unreachable
+}
+
+
+OUString
+Content::getBaseURI( const std::unique_ptr< DAVResourceAccess > & rResAccess )
+{
+ osl::Guard< osl::Mutex > aGuard( m_aMutex );
+
+ // First, try to obtain value of response header "Content-Location".
+ if (m_xCachedProps)
+ {
+ OUString aLocation;
+ m_xCachedProps->getValue( "Content-Location" ) >>= aLocation;
+ if ( aLocation.getLength() )
+ {
+ try
+ {
+ // Do not use m_xIdentifier->getContentIdentifier() because it
+ // for example does not reflect redirects applied to requests
+ // done using the original URI but m_xResAccess' URI does.
+ return rtl::Uri::convertRelToAbs( rResAccess->getURL(),
+ aLocation );
+ }
+ catch ( rtl::MalformedUriException const & )
+ {
+ }
+ }
+ }
+
+ return rResAccess->getURL();
+}
+
+// resource type is the type of the WebDAV resource
+Content::ResourceType Content::getResourceType(
+ const uno::Reference< ucb::XCommandEnvironment >& xEnv,
+ const std::unique_ptr< DAVResourceAccess > & rResAccess,
+ bool * networkAccessAllowed )
+{
+ {
+ osl::MutexGuard g(m_aMutex);
+ if (m_eResourceType != UNKNOWN) {
+ return m_eResourceType;
+ }
+ }
+
+ ResourceType eResourceType = UNKNOWN;
+ DAVOptions aDAVOptions;
+
+ {
+ getResourceOptions( xEnv, aDAVOptions, rResAccess, networkAccessAllowed );
+
+ // at least class one is needed
+ if( aDAVOptions.isClass1() )
+ {
+ try
+ {
+ // Try to fetch some frequently used property value, e.g. those
+ // used when loading documents... along with identifying whether
+ // this is a DAV resource.
+ std::vector< DAVResource > resources;
+ std::vector< OUString > aPropNames;
+ uno::Sequence< beans::Property > aProperties( 5 );
+ auto pProperties = aProperties.getArray();
+ pProperties[ 0 ].Name = "IsFolder";
+ pProperties[ 1 ].Name = "IsDocument";
+ pProperties[ 2 ].Name = "IsReadOnly";
+ pProperties[ 3 ].Name = "MediaType";
+ pProperties[ 4 ].Name = DAVProperties::SUPPORTEDLOCK;
+
+ ContentProperties::UCBNamesToDAVNames( aProperties, aPropNames );
+
+ rResAccess->PROPFIND( DAVZERO, aPropNames, resources, xEnv );
+
+ if ( resources.size() == 1 )
+ {
+#if defined SAL_LOG_INFO
+ {//debug
+ // print received resources
+ std::vector< DAVPropertyValue >::const_iterator it = resources[0].properties.begin();
+ std::vector< DAVPropertyValue >::const_iterator end = resources[0].properties.end();
+ while ( it != end )
+ {
+ OUString aPropValue;
+ bool bValue;
+ uno::Sequence< ucb::LockEntry > aSupportedLocks;
+ if((*it).Value >>= aPropValue )
+ SAL_INFO( "ucb.ucp.webdav", "PROPFIND (getResourceType) - ret'd prop: " << (*it).Name << ":" << aPropValue );
+ else if( (*it).Value >>= bValue )
+ SAL_INFO( "ucb.ucp.webdav", "PROPFIND (getResourceType) - ret'd prop: " << (*it).Name << ":" <<
+ ( bValue ? "true" : "false" ) );
+ else if( (*it).Value >>= aSupportedLocks )
+ {
+ SAL_INFO( "ucb.ucp.webdav", "PROPFIND (getResourceType) - ret'd prop: " << (*it).Name << ":" );
+ for ( sal_Int32 n = 0; n < aSupportedLocks.getLength(); ++n )
+ {
+ SAL_INFO( "ucb.ucp.webdav","PROPFIND (getResourceType) - supportedlock[" << n <<"]: scope: "
+ << (aSupportedLocks[n].Scope == ucb::LockScope_SHARED ? "shared" : "exclusive")
+ << ", type: "
+ << (aSupportedLocks[n].Type != ucb::LockType_WRITE ? "" : "write") );
+ }
+ }
+ ++it;
+ }
+ }
+#endif
+ osl::MutexGuard g(m_aMutex);
+ m_xCachedProps.reset(
+ new CachableContentProperties( ContentProperties( resources[ 0 ] ) ) );
+ m_xCachedProps->containsAllNames(
+ aProperties, m_aFailedPropNames );
+ }
+ eResourceType = DAV;
+ }
+ catch ( DAVException const & e )
+ {
+ rResAccess->resetUri();
+
+ SAL_WARN( "ucb.ucp.webdav", "Content::getResourceType returned errors, DAV ExceptionCode: " << e.getError() << ", HTTP error: " << e.getStatus() );
+
+ if ( e.getStatus() == SC_METHOD_NOT_ALLOWED )
+ {
+ // Status SC_METHOD_NOT_ALLOWED is a safe indicator that the
+ // resource is NON_DAV
+ eResourceType = NON_DAV;
+ }
+ else if (networkAccessAllowed != nullptr)
+ {
+ *networkAccessAllowed = *networkAccessAllowed
+ && shouldAccessNetworkAfterException(e);
+ }
+ if ( e.getStatus() == SC_NOT_FOUND )
+ {
+ // arrives here if OPTIONS is still cached for a resource previously available
+ // operate on the OPTIONS cache:
+ // if OPTIONS was not found, do nothing
+ // else OPTIONS returned on a resource not existent (example a server that allows lock on null resource) set
+ // not found and adjust lifetime accordingly
+ DAVOptions aDAVOptionsInner;
+ if (aStaticDAVOptionsCache.getDAVOptions(rResAccess->getURL(), aDAVOptionsInner))
+ {
+ // TODO? get redirected url
+ aDAVOptionsInner.setHttpResponseStatusCode( e.getStatus() );
+ aDAVOptionsInner.setHttpResponseStatusText( e.getData() );
+ aStaticDAVOptionsCache.addDAVOptions( aDAVOptionsInner,
+ m_nOptsCacheLifeNotFound );
+ }
+ }
+ // if the two net events below happen, something
+ // is going on to the connection so break the command flow
+ if ( ( e.getError() == DAVException::DAV_HTTP_TIMEOUT ) ||
+ ( e.getError() == DAVException::DAV_HTTP_CONNECT ) )
+ {
+ cancelCommandExecution( e, xEnv );
+ // unreachable
+ }
+
+ // cancel command execution is case that no user authentication data has been provided.
+ if ( e.getError() == DAVException::DAV_HTTP_NOAUTH )
+ {
+ cancelCommandExecution( e, uno::Reference< ucb::XCommandEnvironment >() );
+ }
+ }
+ }
+ else
+ {
+ rResAccess->resetUri();
+
+ // first check if the cached error can be mapped to DAVException::DAV_HTTP_TIMEOUT or mapped to DAVException::DAV_HTTP_CONNECT
+ if (aDAVOptions.getHttpResponseStatusCode() == USC_CONNECTION_TIMED_OUT
+ // can't get any reliable info without auth => cancel request
+ || aDAVOptions.getHttpResponseStatusCode() == USC_AUTH_FAILED
+ || aDAVOptions.getHttpResponseStatusCode() == USC_AUTHPROXY_FAILED)
+ {
+ // behave same as DAVException::DAV_HTTP_TIMEOUT or DAVException::DAV_HTTP_CONNECT was thrown
+ try
+ {
+ // extract host name and connection port
+ CurlUri theUri( rResAccess->getURL() );
+ OUString aHostName = theUri.GetHost();
+ sal_Int32 nPort = theUri.GetPort();
+ DAVException::ExceptionCode e{};
+ switch (aDAVOptions.getHttpResponseStatusCode())
+ {
+ case USC_CONNECTION_TIMED_OUT:
+ e = DAVException::DAV_HTTP_TIMEOUT;
+ break;
+ case USC_AUTH_FAILED:
+ e = DAVException::DAV_HTTP_AUTH;
+ break;
+ case USC_AUTHPROXY_FAILED:
+ e = DAVException::DAV_HTTP_AUTHPROXY;
+ break;
+ default:
+ assert(false);
+ }
+ throw DAVException( e,
+ ConnectionEndPointString(aHostName, nPort) );
+ }
+ catch ( DAVException& exp )
+ {
+ cancelCommandExecution( exp, xEnv );
+ }
+ }
+
+ if ( aDAVOptions.getHttpResponseStatusCode() != SC_NOT_FOUND &&
+ aDAVOptions.getHttpResponseStatusCode() != SC_GONE ) // the cached OPTIONS can have SC_GONE
+ {
+ eResourceType = NON_DAV;
+ }
+ else
+ {
+ //resource doesn't exist
+ if ( networkAccessAllowed != nullptr )
+ *networkAccessAllowed = false;
+ }
+ }
+ }
+
+ osl::MutexGuard g(m_aMutex);
+ if (m_eResourceType == UNKNOWN) {
+ m_eResourceType = eResourceType;
+ } else {
+ SAL_WARN_IF(
+ eResourceType != m_eResourceType, "ucb.ucp.webdav",
+ "different resource types for <" << rResAccess->getURL() << ">: "
+ << +eResourceType << " vs. " << +m_eResourceType);
+ }
+ SAL_INFO( "ucb.ucp.webdav", "m_eResourceType for <" << rResAccess->getURL() << ">: " << m_eResourceType );
+ return m_eResourceType;
+}
+
+
+Content::ResourceType Content::getResourceType(
+ const uno::Reference< ucb::XCommandEnvironment >& xEnv )
+{
+ std::unique_ptr< DAVResourceAccess > xResAccess;
+ {
+ osl::MutexGuard aGuard( m_aMutex );
+ xResAccess.reset( new DAVResourceAccess( *m_xResAccess ) );
+ }
+ const Content::ResourceType & ret = getResourceType( xEnv, xResAccess );
+ {
+ osl::Guard< osl::Mutex > aGuard( m_aMutex );
+ m_xResAccess.reset( new DAVResourceAccess( *xResAccess ) );
+ }
+ return ret;
+}
+
+
+void Content::initOptsCacheLifeTime()
+{
+ // see description in
+ // officecfg/registry/schema/org/openoffice/Inet.xcs
+ // for use of these field values.
+ sal_uInt32 nAtime;
+ nAtime = officecfg::Inet::Settings::OptsCacheLifeImplWeb::get();
+ m_nOptsCacheLifeImplWeb = std::max( sal_uInt32( 0 ),
+ std::min( nAtime, sal_uInt32( 3600 ) ) );
+
+ nAtime = officecfg::Inet::Settings::OptsCacheLifeDAV::get();
+ m_nOptsCacheLifeDAV = std::max( sal_uInt32( 0 ),
+ std::min( nAtime, sal_uInt32( 3600 ) ) );
+
+ nAtime = officecfg::Inet::Settings::OptsCacheLifeDAVLocked::get();
+ m_nOptsCacheLifeDAVLocked = std::max( sal_uInt32( 0 ),
+ std::min( nAtime, sal_uInt32( 3600 ) ) );
+
+ nAtime = officecfg::Inet::Settings::OptsCacheLifeNotImpl::get();
+ m_nOptsCacheLifeNotImpl = std::max( sal_uInt32( 0 ),
+ std::min( nAtime, sal_uInt32( 43200 ) ) );
+
+ nAtime = officecfg::Inet::Settings::OptsCacheLifeNotFound::get();
+ m_nOptsCacheLifeNotFound = std::max( sal_uInt32( 0 ),
+ std::min( nAtime, sal_uInt32( 30 ) ) );
+}
+
+
+void Content::getResourceOptions(
+ const css::uno::Reference< css::ucb::XCommandEnvironment >& xEnv,
+ DAVOptions& rDAVOptions,
+ const std::unique_ptr< DAVResourceAccess > & rResAccess,
+ bool * networkAccessAllowed )
+{
+ OUString aRedirURL;
+ OUString aTargetURL = rResAccess->getURL();
+ DAVOptions aDAVOptions;
+ // first check if in cache, if not, then send method to server
+ if ( !aStaticDAVOptionsCache.getDAVOptions( aTargetURL, aDAVOptions ) )
+ {
+ try
+ {
+ rResAccess->OPTIONS( aDAVOptions, xEnv );
+ // IMPORTANT:the correctly implemented server will answer without errors, even if the resource is not present
+ sal_uInt32 nLifeTime = ( aDAVOptions.isClass1() ||
+ aDAVOptions.isClass2() ||
+ aDAVOptions.isClass3() ) ?
+ m_nOptsCacheLifeDAV : // a WebDAV site
+ m_nOptsCacheLifeImplWeb; // a site implementing OPTIONS but
+ // it's not DAV
+ // if resource is locked, will use a
+ // different lifetime
+ if( aDAVOptions.isLocked() )
+ nLifeTime = m_nOptsCacheLifeDAVLocked;
+
+ // check if redirected
+ aRedirURL = rResAccess->getURL();
+ if( aRedirURL == aTargetURL)
+ { // no redirection
+ aRedirURL.clear();
+ }
+ // cache this URL's option
+ aDAVOptions.setURL( aTargetURL );
+ aDAVOptions.setRedirectedURL( aRedirURL );
+ aStaticDAVOptionsCache.addDAVOptions( aDAVOptions,
+ nLifeTime );
+ }
+ catch ( DAVException const & e )
+ {
+ // first, remove from cache, will be added if needed, depending on the error received
+ aStaticDAVOptionsCache.removeDAVOptions( aTargetURL );
+ rResAccess->resetUri();
+
+ aDAVOptions.setURL( aTargetURL );
+ aDAVOptions.setRedirectedURL( aRedirURL );
+ switch( e.getError() )
+ {
+ case DAVException::DAV_HTTP_TIMEOUT:
+ case DAVException::DAV_HTTP_CONNECT:
+ {
+ // something bad happened to the connection
+ // not same as not found, this instead happens when the server doesn't exist or doesn't answer at all
+ // probably a new bit stating 'timed out' should be added to opts var?
+ // in any case abort the command
+ SAL_WARN( "ucb.ucp.webdav", "OPTIONS - DAVException: DAV_HTTP_TIMEOUT or DAV_HTTP_CONNECT for URL <" << m_xIdentifier->getContentIdentifier() << ">" );
+ // cache the internal unofficial status code
+
+ aDAVOptions.setHttpResponseStatusCode( USC_CONNECTION_TIMED_OUT );
+ // used only internally, so the text doesn't really matter..
+ aStaticDAVOptionsCache.addDAVOptions( aDAVOptions,
+ m_nOptsCacheLifeNotFound );
+ if ( networkAccessAllowed != nullptr )
+ {
+ *networkAccessAllowed = *networkAccessAllowed
+ && shouldAccessNetworkAfterException(e);
+ }
+ }
+ break;
+ case DAVException::DAV_HTTP_LOOKUP:
+ {
+ SAL_WARN( "ucb.ucp.webdav", "OPTIONS - DAVException: DAV_HTTP_LOOKUP for URL <" << m_xIdentifier->getContentIdentifier() << ">" );
+ aDAVOptions.setHttpResponseStatusCode( USC_LOOKUP_FAILED );
+ // used only internally, so the text doesn't really matter..
+ aStaticDAVOptionsCache.addDAVOptions( aDAVOptions,
+ m_nOptsCacheLifeNotFound );
+ if ( networkAccessAllowed != nullptr )
+ {
+ *networkAccessAllowed = *networkAccessAllowed
+ && shouldAccessNetworkAfterException(e);
+ }
+ }
+ break;
+ case DAVException::DAV_HTTP_NOAUTH:
+ case DAVException::DAV_HTTP_AUTH:
+ {
+ SAL_WARN( "ucb.ucp.webdav", "OPTIONS - DAVException: DAV_HTTP_AUTH for URL <" << m_xIdentifier->getContentIdentifier() << ">" );
+ // - the remote site is a WebDAV with special configuration: read/only for read operations
+ // and read/write for write operations, the user is not allowed to lock/write and
+ // she cancelled the credentials request.
+ // this is not actually an error, it means only that for current user this is a standard web,
+ // though possibly DAV enabled
+ aDAVOptions.setHttpResponseStatusCode( USC_AUTH_FAILED );
+ // used only internally, so the text doesn't really matter..
+ if (xEnv && xEnv->getInteractionHandler())
+ { // only cache if there actually was a chance to request auth
+ aStaticDAVOptionsCache.addDAVOptions( aDAVOptions,
+ m_nOptsCacheLifeNotFound );
+ }
+ if ( networkAccessAllowed != nullptr )
+ {
+ *networkAccessAllowed = *networkAccessAllowed
+ && shouldAccessNetworkAfterException(e);
+ }
+ }
+ break;
+ case DAVException::DAV_HTTP_AUTHPROXY:
+ {
+ SAL_WARN( "ucb.ucp.webdav", "OPTIONS - DAVException: DAV_HTTP_AUTHPROXY for URL <" << m_xIdentifier->getContentIdentifier() << ">" );
+ aDAVOptions.setHttpResponseStatusCode( USC_AUTHPROXY_FAILED );
+ // used only internally, so the text doesn't really matter..
+ aStaticDAVOptionsCache.addDAVOptions( aDAVOptions,
+ m_nOptsCacheLifeNotFound );
+ if ( networkAccessAllowed != nullptr )
+ {
+ *networkAccessAllowed = *networkAccessAllowed
+ && shouldAccessNetworkAfterException(e);
+ }
+ }
+ break;
+ case DAVException::DAV_HTTP_ERROR:
+ {
+ switch( e.getStatus() )
+ {
+ case SC_FORBIDDEN:
+ {
+ SAL_WARN( "ucb.ucp.webdav","OPTIONS - SC_FORBIDDEN for URL <" << m_xIdentifier->getContentIdentifier() << ">" );
+ // cache it, so OPTIONS won't be called again, this URL does not support it
+ aStaticDAVOptionsCache.addDAVOptions( aDAVOptions,
+ m_nOptsCacheLifeNotImpl );
+ }
+ break;
+ case SC_BAD_REQUEST:
+ case SC_INTERNAL_SERVER_ERROR:
+ {
+ SAL_WARN( "ucb.ucp.webdav","OPTIONS - SC_BAD_REQUEST or SC_INTERNAL_SERVER_ERROR for URL <" << m_xIdentifier->getContentIdentifier() << ">, HTTP error: "<< e.getStatus()
+ << ", '" << e.getData() << "'" );
+ // cache it, so OPTIONS won't be called again, this URL detect some problem while answering the method
+ aDAVOptions.setHttpResponseStatusCode( e.getStatus() );
+ aDAVOptions.setHttpResponseStatusText( e.getData() );
+ aStaticDAVOptionsCache.addDAVOptions( aDAVOptions,
+ m_nOptsCacheLifeNotFound );
+ }
+ break;
+ case SC_NOT_IMPLEMENTED:
+ case SC_METHOD_NOT_ALLOWED:
+ {
+ // OPTIONS method must be implemented in DAV
+ // resource is NON_DAV, or not advertising it
+ SAL_WARN( "ucb.ucp.webdav","OPTIONS - SC_NOT_IMPLEMENTED or SC_METHOD_NOT_ALLOWED for URL <" << m_xIdentifier->getContentIdentifier() << ">, HTTP error: "<< e.getStatus()
+ << ", '" << e.getData() << "'" );
+ // cache it, so OPTIONS won't be called again, this URL does not support it
+ aStaticDAVOptionsCache.addDAVOptions( aDAVOptions,
+ m_nOptsCacheLifeNotImpl );
+ }
+ break;
+ case SC_NOT_FOUND:
+ {
+ // Apparently on IIS 10.0, if you disabled OPTIONS method, this error is the one reported,
+ // instead of SC_NOT_IMPLEMENTED or SC_METHOD_NOT_ALLOWED.
+ // So check if this is an available resource, or a real 'Not Found' event.
+ sal_uInt32 nLifeTime = m_nOptsCacheLifeNotFound;
+ if( isResourceAvailable( xEnv, rResAccess, aDAVOptions ) )
+ {
+ SAL_WARN( "ucb.ucp.webdav", "OPTIONS - Got an SC_NOT_FOUND, but the URL <" << m_xIdentifier->getContentIdentifier() << "> resource exists" );
+ nLifeTime = m_nOptsCacheLifeNotImpl;
+ }
+ else
+ {
+ SAL_WARN( "ucb.ucp.webdav", "OPTIONS - SC_NOT_FOUND for URL <" << m_xIdentifier->getContentIdentifier() << ">" );
+ if ( networkAccessAllowed != nullptr )
+ {
+ *networkAccessAllowed = *networkAccessAllowed
+ && shouldAccessNetworkAfterException(e);
+ }
+ }
+ aStaticDAVOptionsCache.addDAVOptions( aDAVOptions,
+ nLifeTime );
+ }
+ break;
+ default:
+ {
+ SAL_WARN( "ucb.ucp.webdav", "OPTIONS - DAV_HTTP_ERROR, for URL <" << m_xIdentifier->getContentIdentifier() << ">, HTTP error: "<< e.getStatus()
+ << ", '" << e.getData() << "'" );
+ aDAVOptions.setHttpResponseStatusCode( e.getStatus() );
+ aDAVOptions.setHttpResponseStatusText( e.getData() );
+ // cache it, so OPTIONS won't be called again, this URL does not support it
+ aStaticDAVOptionsCache.addDAVOptions( aDAVOptions,
+ m_nOptsCacheLifeNotImpl );
+ }
+ break;
+ }
+ }
+ break;
+ // The 'DAVException::DAV_HTTP_REDIRECT' means we reached the maximum
+ // number of redirections, consider the resource type as UNKNOWN
+ // possibly a normal web site, not DAV
+ case DAVException::DAV_HTTP_REDIRECT:
+ default:
+ {
+ SAL_WARN( "ucb.ucp.webdav","OPTIONS - General DAVException (or max DAV_HTTP_REDIRECT reached) for URL <" << m_xIdentifier->getContentIdentifier() << ">, DAV ExceptionCode: "
+ << e.getError() << ", HTTP error: "<< e.getStatus() );
+ aStaticDAVOptionsCache.addDAVOptions( aDAVOptions,
+ m_nOptsCacheLifeNotImpl );
+ }
+ break;
+ }
+ }
+ }
+ else
+ {
+ // check current response status code, perhaps we need to set networkAccessAllowed
+ sal_uInt16 CachedResponseStatusCode = aDAVOptions.getHttpResponseStatusCode();
+ if ( networkAccessAllowed != nullptr &&
+ ( ( CachedResponseStatusCode == SC_NOT_FOUND ) ||
+ ( CachedResponseStatusCode == SC_GONE ) ||
+ ( CachedResponseStatusCode == USC_CONNECTION_TIMED_OUT ) ||
+ ( CachedResponseStatusCode == USC_LOOKUP_FAILED ) ||
+ ( CachedResponseStatusCode == USC_AUTH_FAILED ) ||
+ ( CachedResponseStatusCode == USC_AUTHPROXY_FAILED )
+ )
+ )
+ {
+ *networkAccessAllowed = *networkAccessAllowed && false;
+ }
+ }
+ rDAVOptions = aDAVOptions;
+}
+
+//static
+bool Content::isResourceAvailable( const css::uno::Reference< css::ucb::XCommandEnvironment >& xEnv,
+ const std::unique_ptr< DAVResourceAccess > & rResAccess,
+ DAVOptions& rDAVOptions )
+{
+ std::vector< rtl::OUString > aHeaderNames;
+ DAVResource aResource;
+
+ try
+ {
+ // To check for the physical URL resource availability, first
+ // try using a simple HEAD command
+ // if HEAD is successful, set element found.
+ rResAccess->HEAD( aHeaderNames, aResource, xEnv );
+ rDAVOptions.setHttpResponseStatusCode( 0 );
+ rDAVOptions.setHttpResponseStatusText("");
+ return true;
+ }
+ catch ( DAVException const & e )
+ {
+ if ( e.getError() == DAVException::DAV_HTTP_ERROR )
+ {
+ if ( e.getStatus() == SC_NOT_IMPLEMENTED ||
+ e.getStatus() == SC_METHOD_NOT_ALLOWED ||
+ e.getStatus() == SC_NOT_FOUND )
+ {
+ SAL_WARN( "ucb.ucp.webdav", "HEAD probably not implemented: fall back to a partial GET" );
+ // set in cached OPTIONS "HEAD not implemented"
+ // so it won't be used again on this resource
+ rDAVOptions.setHeadAllowed( false );
+ try
+ {
+ // do a GET with a payload of 0, the server does not
+ // support HEAD (or has HEAD disabled)
+ DAVRequestHeaders aPartialGet;
+ aPartialGet.push_back(
+ DAVRequestHeader(
+ OUString( "Range" ),
+ OUString( "bytes=0-0" )));
+
+ rResAccess->GET0( aPartialGet,
+ aHeaderNames,
+ aResource,
+ xEnv );
+ return true;
+ }
+ catch ( DAVException const & ex )
+ {
+ if ( ex.getError() == DAVException::DAV_HTTP_ERROR )
+ {
+ rDAVOptions.setHttpResponseStatusCode( ex.getStatus() );
+ rDAVOptions.setHttpResponseStatusText( ex.getData() );
+ }
+ }
+ }
+ else
+ {
+ rDAVOptions.setHttpResponseStatusCode( e.getStatus() );
+ rDAVOptions.setHttpResponseStatusText( e.getData() );
+ }
+ }
+ return false;
+ }
+ catch ( ... )
+ {
+ }
+ // set SC_NOT_IMPLEMENTED since at a minimum GET must be implemented in a basic Web server
+ rDAVOptions.setHttpResponseStatusCode( SC_NOT_IMPLEMENTED );
+ rDAVOptions.setHttpResponseStatusText("");
+ return false;
+}
+
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/ucb/source/ucp/webdav-curl/webdavcontent.hxx b/ucb/source/ucp/webdav-curl/webdavcontent.hxx
new file mode 100644
index 0000000000..60661b8674
--- /dev/null
+++ b/ucb/source/ucp/webdav-curl/webdavcontent.hxx
@@ -0,0 +1,305 @@
+/* -*- 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:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+
+#pragma once
+
+#include <memory>
+#include <list>
+#include <rtl/ref.hxx>
+#include <com/sun/star/ucb/ContentCreationException.hpp>
+#include <com/sun/star/ucb/XContentCreator.hpp>
+#include <ucbhelper/contenthelper.hxx>
+#include "DAVResourceAccess.hxx"
+#include "PropertyMap.hxx"
+
+namespace com::sun::star::beans {
+ struct Property;
+ struct PropertyValue;
+}
+
+namespace com::sun::star::io {
+ class XInputStream;
+}
+
+namespace com::sun::star::sdbc {
+ class XRow;
+}
+
+namespace com::sun::star::ucb {
+ struct OpenCommandArgument3;
+ struct PropertyCommandArgument;
+ struct PostCommandArgument2;
+ struct TransferInfo;
+}
+
+namespace http_dav_ucp
+{
+
+
+// UNO service name for the content.
+inline constexpr OUString WEBDAV_CONTENT_SERVICE_NAME = u"com.sun.star.ucb.WebDAVContent"_ustr;
+
+
+class ContentProvider;
+class ContentProperties;
+class CachableContentProperties;
+
+class Content : public ::ucbhelper::ContentImplHelper,
+ public css::ucb::XContentCreator
+{
+ enum ResourceType
+ {
+ UNKNOWN, // the type of the Web resource is unknown
+ NOT_FOUND, // the Web resource does not exists
+ NON_DAV, // the Web resource exists but it's not DAV
+ DAV, // the type of the Web resource is DAV with lock/unlock available
+ DAV_NOLOCK // the type of the Web resource is DAV with no lock/unlock available
+ };
+
+ std::unique_ptr< DAVResourceAccess > m_xResAccess;
+ std::unique_ptr< CachableContentProperties > m_xCachedProps; // locally cached props
+ OUString m_aEscapedTitle;
+ // resource type for general DAV methods
+ ResourceType m_eResourceType;
+ // resource type for general LOCK method only
+ ResourceType m_eResourceTypeForLocks;
+ ContentProvider* m_pProvider; // No need for a ref, base class holds object
+ bool m_bTransient;
+ bool const m_bCollection;
+ bool m_bDidGetOrHead;
+ std::vector< OUString > m_aFailedPropNames;
+ // Options Cache lifetime
+ // for web site implementing OPTIONS, but not dav
+ sal_uInt32 m_nOptsCacheLifeImplWeb;
+ // for WebDAV site where OPTIONS is mandatory
+ sal_uInt32 m_nOptsCacheLifeDAV;
+ // same as above, but when the resource is locked by us
+ sal_uInt32 m_nOptsCacheLifeDAVLocked;
+// For web site not implementing OPTIONS
+ // during this time we assume the site doesn't turn to WebDAV
+ // but remains a simple Web
+ sal_uInt32 m_nOptsCacheLifeNotImpl;
+ // When resource is not found
+ // may be the resource is unavailable only briefly?
+ // so better have this small
+ sal_uInt32 m_nOptsCacheLifeNotFound;
+
+ void initOptsCacheLifeTime();
+
+private:
+ 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;
+
+ /// @throws css::uno::Exception
+ bool isFolder( const css::uno::Reference< css::ucb::XCommandEnvironment >& xEnv );
+
+ /// @throws css::uno::Exception
+ css::uno::Reference< css::sdbc::XRow >
+ getPropertyValues( const css::uno::Sequence< css::beans::Property >& rProperties,
+ const css::uno::Reference< css::ucb::XCommandEnvironment >& xEnv );
+
+ /// @throws css::uno::Exception
+ css::uno::Sequence< css::uno::Any >
+ setPropertyValues( const css::uno::Sequence< css::beans::PropertyValue >& rValues,
+ const css::uno::Reference< css::ucb::XCommandEnvironment >& xEnv );
+
+ typedef rtl::Reference< Content > ContentRef;
+ typedef std::vector< ContentRef > ContentRefList;
+ void queryChildren( ContentRefList& rChildren);
+
+ bool
+ exchangeIdentity( const css::uno::Reference< css::ucb::XContentIdentifier >& xNewId );
+
+ OUString
+ getBaseURI( const std::unique_ptr< DAVResourceAccess > & rResAccess );
+
+ /// @throws css::uno::Exception
+ ResourceType
+ getResourceType( const css::uno::Reference< css::ucb::XCommandEnvironment >& xEnv );
+
+ /// @throws css::uno::Exception
+ ResourceType
+ getResourceType( const css::uno::Reference< css::ucb::XCommandEnvironment >& xEnv,
+ const std::unique_ptr< DAVResourceAccess > & rResAccess,
+ bool * networkAccessAllowed = nullptr );
+
+ // Command "open"
+ /// @throws css::uno::Exception
+ css::uno::Any open(
+ const css::ucb::OpenCommandArgument3 & rArg,
+ const css::uno::Reference<
+ css::ucb::XCommandEnvironment > & xEnv );
+
+ // Command "post"
+ /// @throws css::uno::Exception
+ void post( const css::ucb::PostCommandArgument2 & rArg,
+ const css::uno::Reference< css::ucb::XCommandEnvironment > & xEnv );
+
+ // Command "insert"
+ /// @throws css::uno::Exception
+ void insert( const css::uno::Reference< css::io::XInputStream > & xInputStream,
+ bool bReplaceExisting,
+ const css::uno::Reference< css::ucb::XCommandEnvironment >& Environment );
+
+ // Command "transfer"
+ /// @throws css::uno::Exception
+ void transfer( const css::ucb::TransferInfo & rArgs,
+ const css::uno::Reference< css::ucb::XCommandEnvironment >& Environment );
+
+ // Command "delete"
+ /// @throws css::uno::Exception
+ void destroy( bool bDeletePhysical );
+
+ // Command "lock"
+ /// @throws css::uno::Exception
+ void lock( const css::uno::Reference< css::ucb::XCommandEnvironment >& Environment );
+
+ // Command "unlock"
+ /// @throws css::uno::Exception
+ void unlock( const css::uno::Reference< css::ucb::XCommandEnvironment >& Environment );
+
+ css::uno::Any MapDAVException( const DAVException & e,
+ bool bWrite );
+ /// @throws css::uno::Exception
+ void cancelCommandExecution(
+ const DAVException & e,
+ const css::uno::Reference< css::ucb::XCommandEnvironment > & xEnv,
+ bool bWrite = false );
+
+ static bool shouldAccessNetworkAfterException( const DAVException & e );
+
+ ResourceType resourceTypeForLocks(
+ const css::uno::Reference< css::ucb::XCommandEnvironment >& rEnvironment,
+ const std::unique_ptr< DAVResourceAccess > & rResAccess );
+
+ ResourceType resourceTypeForLocks(
+ const css::uno::Reference< css::ucb::XCommandEnvironment >& rEnvironment );
+
+ // XPropertyContainer replacement
+ /// @throws css::beans::PropertyExistException
+ /// @throws css::beans::IllegalTypeException
+ /// @throws css::lang::IllegalArgumentException
+ /// @throws css::uno::RuntimeException
+ void addProperty( const css::ucb::PropertyCommandArgument &aCmdArg,
+ const css::uno::Reference< css::ucb::XCommandEnvironment >& Environment );
+
+ /// @throws css::beans::PropertyExistException
+ /// @throws css::beans::NotRemoveableException
+ /// @throws css::uno::RuntimeException
+ void removeProperty( const OUString& Name,
+ const css::uno::Reference< css::ucb::XCommandEnvironment >& Environment );
+public:
+ /// @throws css::ucb::ContentCreationException
+ Content( const css::uno::Reference< css::uno::XComponentContext >& rxContext,
+ ContentProvider* pProvider,
+ const css::uno::Reference< css::ucb::XContentIdentifier >& Identifier,
+ rtl::Reference< DAVSessionFactory > const & rSessionFactory );
+ /// @throws css::ucb::ContentCreationException
+ Content( const css::uno::Reference< css::uno::XComponentContext >& rxContext,
+ ContentProvider* pProvider,
+ const css::uno::Reference< css::ucb::XContentIdentifier >& Identifier,
+ rtl::Reference< DAVSessionFactory > const & rSessionFactory,
+ bool isCollection );
+ virtual ~Content() 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 css::uno::Sequence< OUString > SAL_CALL
+ getSupportedServiceNames() override;
+
+ // XContent
+ virtual OUString SAL_CALL
+ getContentType() override;
+
+ // XCommandProcessor
+ 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;
+
+ // XPropertyContainer
+ virtual void SAL_CALL
+ addProperty( const OUString& Name,
+ sal_Int16 Attributes,
+ const css::uno::Any& DefaultValue ) override;
+
+ virtual void SAL_CALL
+ removeProperty( const OUString& Name ) override;
+
+
+ // Additional interfaces
+
+
+ // XContentCreator
+ 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;
+
+
+ // Non-interface methods.
+
+
+ DAVResourceAccess & getResourceAccess() { return *m_xResAccess; }
+
+ // Called from resultset data supplier.
+ static css::uno::Reference< css::sdbc::XRow >
+ getPropertyValues( const css::uno::Reference< css::uno::XComponentContext >& rContext,
+ const css::uno::Sequence< css::beans::Property >& rProperties,
+ const ContentProperties& rData,
+ const rtl::Reference< ::ucbhelper::ContentProviderImplHelper >& rProvider,
+ const OUString& rContentId );
+
+ /// Use OPTIONS method to retrieve the type of the Web resource
+ /// @throws css::uno::Exception
+ void getResourceOptions( const css::uno::Reference< css::ucb::XCommandEnvironment >& xEnv,
+ DAVOptions& rDAVOptions,
+ const std::unique_ptr< DAVResourceAccess > & rResAccess,
+ bool * networkAccessAllowed = nullptr);
+
+ static bool isResourceAvailable( const css::uno::Reference< css::ucb::XCommandEnvironment >& xEnv,
+ const std::unique_ptr< DAVResourceAccess > & rResAccess,
+ DAVOptions& rDAVOptions );
+
+ static void removeCachedPropertyNames( const OUString & rURL );
+
+};
+
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/ucb/source/ucp/webdav-curl/webdavcontentcaps.cxx b/ucb/source/ucp/webdav-curl/webdavcontentcaps.cxx
new file mode 100644
index 0000000000..a7e2bb9689
--- /dev/null
+++ b/ucb/source/ucp/webdav-curl/webdavcontentcaps.cxx
@@ -0,0 +1,636 @@
+/* -*- 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:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include <memory>
+#include <set>
+#include <com/sun/star/beans/Property.hpp>
+#include <com/sun/star/beans/PropertyAttribute.hpp>
+#include <com/sun/star/beans/PropertyValue.hpp>
+#include <com/sun/star/ucb/CommandInfo.hpp>
+#include <com/sun/star/ucb/ContentInfo.hpp>
+#include <com/sun/star/ucb/OpenCommandArgument2.hpp>
+#include <com/sun/star/ucb/InsertCommandArgument.hpp>
+#include <com/sun/star/ucb/PostCommandArgument2.hpp>
+#include <com/sun/star/ucb/PropertyCommandArgument.hpp>
+#include <com/sun/star/ucb/TransferInfo.hpp>
+#include <com/sun/star/uno/Sequence.hxx>
+#include <com/sun/star/util/DateTime.hpp>
+#include <com/sun/star/ucb/Lock.hpp>
+#include <com/sun/star/ucb/LockEntry.hpp>
+#include "webdavcontent.hxx"
+#include "webdavprovider.hxx"
+#include "DAVProperties.hxx"
+#include "ContentProperties.hxx"
+#include "PropfindCache.hxx"
+
+using namespace com::sun::star;
+using namespace http_dav_ucp;
+
+
+// ContentProvider implementation.
+
+
+bool ContentProvider::getProperty(
+ const OUString & rPropName, beans::Property & rProp )
+{
+ if ( !m_pProps )
+ {
+ osl::MutexGuard aGuard( m_aMutex );
+ if ( !m_pProps )
+ {
+ m_pProps = std::make_unique<PropertyMap>();
+
+
+ // Fill map of known properties...
+
+
+ // Mandatory UCB properties.
+ m_pProps->insert(
+ beans::Property(
+ "ContentType",
+ -1,
+ cppu::UnoType<OUString>::get(),
+ beans::PropertyAttribute::BOUND
+ | beans::PropertyAttribute::READONLY ) );
+
+ m_pProps->insert(
+ beans::Property(
+ "IsDocument",
+ -1,
+ cppu::UnoType<bool>::get(),
+ beans::PropertyAttribute::BOUND
+ | beans::PropertyAttribute::READONLY ) );
+
+ m_pProps->insert(
+ beans::Property(
+ "IsFolder",
+ -1,
+ cppu::UnoType<bool>::get(),
+ beans::PropertyAttribute::BOUND
+ | beans::PropertyAttribute::READONLY ) );
+
+ m_pProps->insert(
+ beans::Property(
+ "Title",
+ -1,
+ cppu::UnoType<OUString>::get(),
+ beans::PropertyAttribute::BOUND ) );
+
+ // Optional UCB properties.
+
+ m_pProps->insert(
+ beans::Property(
+ "DateCreated",
+ -1,
+ cppu::UnoType<util::DateTime>::get(),
+ beans::PropertyAttribute::BOUND
+ | beans::PropertyAttribute::READONLY ) );
+
+ m_pProps->insert(
+ beans::Property(
+ "DateModified",
+ -1,
+ cppu::UnoType<util::DateTime>::get(),
+ beans::PropertyAttribute::BOUND
+ | beans::PropertyAttribute::READONLY ) );
+
+ m_pProps->insert(
+ beans::Property(
+ "MediaType",
+ -1,
+ cppu::UnoType<OUString>::get(),
+ beans::PropertyAttribute::BOUND
+ | beans::PropertyAttribute::READONLY ) );
+
+ m_pProps->insert(
+ beans::Property(
+ "Size",
+ -1,
+ cppu::UnoType<sal_Int64>::get(),
+ beans::PropertyAttribute::BOUND
+ | beans::PropertyAttribute::READONLY ) );
+
+ m_pProps->insert(
+ beans::Property(
+ "BaseURI",
+ -1,
+ cppu::UnoType<OUString>::get(),
+ beans::PropertyAttribute::BOUND
+ | beans::PropertyAttribute::READONLY ) );
+
+ m_pProps->insert(
+ beans::Property(
+ "CreatableContentsInfo",
+ -1,
+ cppu::UnoType<uno::Sequence< ucb::ContentInfo >>::get(),
+ beans::PropertyAttribute::BOUND
+ | beans::PropertyAttribute::READONLY ) );
+
+ // Standard DAV properties.
+
+ m_pProps->insert(
+ beans::Property(
+ DAVProperties::CREATIONDATE,
+ -1,
+ cppu::UnoType<OUString>::get(),
+ beans::PropertyAttribute::BOUND
+ | beans::PropertyAttribute::READONLY ) );
+
+ m_pProps->insert(
+ beans::Property(
+ DAVProperties::DISPLAYNAME,
+ -1,
+ cppu::UnoType<OUString>::get(),
+ beans::PropertyAttribute::BOUND ) );
+
+ m_pProps->insert(
+ beans::Property(
+ DAVProperties::GETCONTENTLANGUAGE,
+ -1,
+ cppu::UnoType<OUString>::get(),
+ beans::PropertyAttribute::BOUND
+ | beans::PropertyAttribute::READONLY ) );
+
+ m_pProps->insert(
+ beans::Property(
+ DAVProperties::GETCONTENTLENGTH,
+ -1,
+ cppu::UnoType<OUString>::get(),
+ beans::PropertyAttribute::BOUND
+ | beans::PropertyAttribute::READONLY ) );
+
+ m_pProps->insert(
+ beans::Property(
+ DAVProperties::GETCONTENTTYPE ,
+ -1,
+ cppu::UnoType<OUString>::get(),
+ beans::PropertyAttribute::BOUND
+ | beans::PropertyAttribute::READONLY ) );
+
+ m_pProps->insert(
+ beans::Property(
+ DAVProperties::GETETAG,
+ -1,
+ cppu::UnoType<OUString>::get(),
+ beans::PropertyAttribute::BOUND
+ | beans::PropertyAttribute::READONLY ) );
+
+ m_pProps->insert(
+ beans::Property(
+ DAVProperties::GETLASTMODIFIED,
+ -1,
+ cppu::UnoType<OUString>::get(),
+ beans::PropertyAttribute::BOUND
+ | beans::PropertyAttribute::READONLY ) );
+
+ m_pProps->insert(
+ beans::Property(
+ DAVProperties::LOCKDISCOVERY,
+ -1,
+ cppu::UnoType<uno::Sequence< ucb::Lock >>::get(),
+ beans::PropertyAttribute::BOUND
+ | beans::PropertyAttribute::READONLY ) );
+
+ m_pProps->insert(
+ beans::Property(
+ DAVProperties::RESOURCETYPE,
+ -1,
+ cppu::UnoType<OUString>::get(),
+ beans::PropertyAttribute::BOUND
+ | beans::PropertyAttribute::READONLY ) );
+
+ m_pProps->insert(
+ beans::Property(
+ DAVProperties::SUPPORTEDLOCK,
+ -1,
+ cppu::UnoType<uno::Sequence< ucb::LockEntry >>::get(),
+ beans::PropertyAttribute::BOUND
+ | beans::PropertyAttribute::READONLY ) );
+
+ m_pProps->insert(
+ beans::Property(
+ DAVProperties::EXECUTABLE,
+ -1,
+ cppu::UnoType<OUString>::get(),
+ beans::PropertyAttribute::BOUND ) );
+ }
+ }
+
+
+ // Lookup property.
+
+
+ beans::Property aProp;
+ aProp.Name = rPropName;
+ const PropertyMap::const_iterator it = m_pProps->find( aProp );
+ if ( it != m_pProps->end() )
+ {
+ rProp = *it;
+ }
+ else
+ {
+ // All unknown props are treated as:
+ rProp = beans::Property(
+ rPropName,
+ - 1,
+ cppu::UnoType<OUString>::get(),
+ beans::PropertyAttribute::BOUND );
+ }
+
+ return true;
+}
+
+
+static PropertyNamesCache aStaticPropertyNamesCache;
+
+// static
+void Content::removeCachedPropertyNames( const OUString & rURL )
+{
+ aStaticPropertyNamesCache.removeCachedPropertyNames( rURL );
+}
+
+// Content implementation.
+
+
+// virtual
+uno::Sequence< beans::Property > Content::getProperties(
+ const uno::Reference< ucb::XCommandEnvironment > & xEnv )
+{
+ bool bTransient;
+ std::unique_ptr< DAVResourceAccess > xResAccess;
+ std::unique_ptr< ContentProperties > xCachedProps;
+ rtl::Reference< ContentProvider > xProvider;
+
+ {
+ osl::Guard< osl::Mutex > aGuard( m_aMutex );
+
+ bTransient = m_bTransient;
+ xResAccess.reset( new DAVResourceAccess( *m_xResAccess ) );
+ if (m_xCachedProps)
+ xCachedProps.reset(
+ new ContentProperties( *m_xCachedProps ) );
+ xProvider.set( m_pProvider );
+ }
+
+ std::set< OUString > aPropSet;
+
+ // No server access for just created (not yet committed) objects.
+ // Only a minimal set of properties supported at this stage.
+ if ( !bTransient )
+ {
+ // Obtain all properties supported for this resource from server.
+ DAVOptions aDAVOptions;
+ getResourceOptions( xEnv, aDAVOptions, xResAccess );
+ // only Class 1 is needed for PROPFIND
+ if ( aDAVOptions.isClass1() )
+ {
+ try
+ {
+ std::vector< DAVResourceInfo > props;
+ OUString aTheURL( xResAccess->getURL() );
+ PropertyNames aPropsNames( aTheURL );
+
+ if( !aStaticPropertyNamesCache.getCachedPropertyNames( aTheURL, aPropsNames ) )
+ {
+
+ xResAccess->PROPFIND( DAVZERO, props, xEnv );
+ aPropsNames.setPropertiesNames( props );
+
+ aStaticPropertyNamesCache.addCachePropertyNames( aPropsNames, 10 );
+ }
+ else
+ {
+ props = aPropsNames.getPropertiesNames();
+ }
+
+ // Note: vector should contain exactly one resource info, because
+ // we used a depth of DAVZERO for PROPFIND.
+ if (props.size() == 1)
+ {
+ aPropSet.insert( (*props.begin()).properties.begin(),
+ (*props.begin()).properties.end() );
+ }
+ }
+ catch ( DAVException const & )
+ {
+ }
+ }
+ }
+
+ // Add DAV properties, map DAV properties to UCB properties.
+ bool bHasCreationDate = false; // creationdate <-> DateCreated
+ bool bHasGetLastModified = false; // getlastmodified <-> DateModified
+ bool bHasGetContentType = false; // getcontenttype <-> MediaType
+ bool bHasGetContentLength = false; // getcontentlength <-> Size
+
+ bool bHasContentType = false;
+ bool bHasIsDocument = false;
+ bool bHasIsFolder = false;
+ bool bHasTitle = false;
+ bool bHasBaseURI = false;
+ bool bHasDateCreated = false;
+ bool bHasDateModified = false;
+ bool bHasMediaType = false;
+ bool bHasSize = false;
+ bool bHasCreatableInfos = false;
+
+ {
+ for ( const auto& rProp : aPropSet )
+ {
+ if ( !bHasCreationDate &&
+ ( rProp == DAVProperties::CREATIONDATE ) )
+ {
+ bHasCreationDate = true;
+ }
+ else if ( !bHasGetLastModified &&
+ ( rProp == DAVProperties::GETLASTMODIFIED ) )
+ {
+ bHasGetLastModified = true;
+ }
+ else if ( !bHasGetContentType &&
+ ( rProp == DAVProperties::GETCONTENTTYPE ) )
+ {
+ bHasGetContentType = true;
+ }
+ else if ( !bHasGetContentLength &&
+ ( rProp == DAVProperties::GETCONTENTLENGTH ) )
+ {
+ bHasGetContentLength = true;
+ }
+ else if ( !bHasContentType && rProp == "ContentType" )
+ {
+ bHasContentType = true;
+ }
+ else if ( !bHasIsDocument && rProp == "IsDocument" )
+ {
+ bHasIsDocument = true;
+ }
+ else if ( !bHasIsFolder && rProp == "IsFolder" )
+ {
+ bHasIsFolder = true;
+ }
+ else if ( !bHasTitle && rProp == "Title" )
+ {
+ bHasTitle = true;
+ }
+ else if ( !bHasBaseURI && rProp == "BaseURI" )
+ {
+ bHasBaseURI = true;
+ }
+ else if ( !bHasDateCreated && rProp == "DateCreated" )
+ {
+ bHasDateCreated = true;
+ }
+ else if ( !bHasDateModified && rProp == "DateModified" )
+ {
+ bHasDateModified = true;
+ }
+ else if ( !bHasMediaType && rProp == "MediaType" )
+ {
+ bHasMediaType = true;
+ }
+ else if ( !bHasSize && rProp == "Size" )
+ {
+ bHasSize = true;
+ }
+ else if ( !bHasCreatableInfos && rProp == "CreatableContentsInfo" )
+ {
+ bHasCreatableInfos = true;
+ }
+ }
+ }
+
+ // Add mandatory properties.
+ if ( !bHasContentType )
+ aPropSet.insert(
+ OUString( "ContentType" ) );
+
+ if ( !bHasIsDocument )
+ aPropSet.insert(
+ OUString( "IsDocument" ) );
+
+ if ( !bHasIsFolder )
+ aPropSet.insert(
+ OUString( "IsFolder" ) );
+
+ if ( !bHasTitle )
+ {
+ // Always present since it can be calculated from content's URI.
+ aPropSet.insert(
+ OUString( "Title" ) );
+ }
+
+ // Add optional properties.
+
+ if ( !bHasBaseURI )
+ {
+ // Always present since it can be calculated from content's URI.
+ aPropSet.insert(
+ OUString( "BaseURI" ) );
+ }
+
+ if ( !bHasDateCreated && bHasCreationDate )
+ aPropSet.insert(
+ OUString( "DateCreated" ) );
+
+ if ( !bHasDateModified && bHasGetLastModified )
+ aPropSet.insert(
+ OUString( "DateModified" ) );
+
+ if ( !bHasMediaType && bHasGetContentType )
+ aPropSet.insert(
+ OUString( "MediaType" ) );
+
+ if ( !bHasSize && bHasGetContentLength )
+ aPropSet.insert(
+ OUString( "Size" ) );
+
+ if ( !bHasCreatableInfos )
+ aPropSet.insert(
+ OUString(
+ "CreatableContentsInfo" ) );
+
+ // Add cached properties, if present and still missing.
+ if (xCachedProps)
+ {
+ const std::unique_ptr< PropertyValueMap > & xProps
+ = xCachedProps->getProperties();
+
+ for ( const auto& rEntry : *xProps )
+ aPropSet.insert( rEntry.first );
+ }
+
+ // std::set -> uno::Sequence
+ sal_Int32 nCount = aPropSet.size();
+ uno::Sequence< beans::Property > aProperties( nCount );
+ auto aPropertiesRange = asNonConstRange(aProperties);
+
+ beans::Property aProp;
+ sal_Int32 n = 0;
+
+ for ( const auto& rProp : aPropSet )
+ {
+ xProvider->getProperty( rProp, aProp );
+ aPropertiesRange[ n++ ] = aProp;
+ }
+
+ return aProperties;
+}
+
+
+// virtual
+uno::Sequence< ucb::CommandInfo > Content::getCommands(
+ const uno::Reference< ucb::XCommandEnvironment > & xEnv )
+{
+ osl::Guard< osl::Mutex > aGuard( m_aMutex );
+
+ uno::Sequence< ucb::CommandInfo > aCmdInfo( 10 );
+ auto pCmdInfo = aCmdInfo.getArray();
+
+
+ // Mandatory commands
+
+
+ pCmdInfo[ 0 ] =
+ ucb::CommandInfo(
+ "getCommandInfo",
+ -1,
+ cppu::UnoType<void>::get() );
+ pCmdInfo[ 1 ] =
+ ucb::CommandInfo(
+ "getPropertySetInfo",
+ -1,
+ cppu::UnoType<void>::get() );
+ pCmdInfo[ 2 ] =
+ ucb::CommandInfo(
+ "getPropertyValues",
+ -1,
+ cppu::UnoType<uno::Sequence< beans::Property >>::get());
+ pCmdInfo[ 3 ] =
+ ucb::CommandInfo(
+ "setPropertyValues",
+ -1,
+ cppu::UnoType<uno::Sequence< beans::PropertyValue >>::get());
+
+
+ // Optional standard commands
+
+
+ pCmdInfo[ 4 ] =
+ ucb::CommandInfo(
+ "delete",
+ -1,
+ cppu::UnoType<bool>::get() );
+ pCmdInfo[ 5 ] =
+ ucb::CommandInfo(
+ "insert",
+ -1,
+ cppu::UnoType<ucb::InsertCommandArgument>::get() );
+ pCmdInfo[ 6 ] =
+ ucb::CommandInfo(
+ "open",
+ -1,
+ cppu::UnoType<ucb::OpenCommandArgument2>::get() );
+
+
+ // New commands
+
+
+ pCmdInfo[ 7 ] =
+ ucb::CommandInfo(
+ "post",
+ -1,
+ cppu::UnoType<ucb::PostCommandArgument2>::get() );
+ pCmdInfo[ 8 ] =
+ ucb::CommandInfo(
+ "addProperty",
+ -1,
+ cppu::UnoType<ucb::PropertyCommandArgument>::get() );
+ pCmdInfo[ 9 ] =
+ ucb::CommandInfo(
+ "removeProperty",
+ -1,
+ cppu::UnoType<OUString>::get() );
+
+ bool bFolder = false;
+
+ try
+ {
+ bFolder = isFolder( xEnv );
+ }
+ catch ( uno::Exception const & )
+ {
+ return aCmdInfo;
+ }
+
+ ResourceType eType = resourceTypeForLocks( xEnv );
+ bool bSupportsLocking = ( eType == NOT_FOUND || eType == DAV );
+
+ sal_Int32 nPos = aCmdInfo.getLength();
+ sal_Int32 nMoreCmds = ( bFolder ? 2 : 0 ) + ( bSupportsLocking ? 2 : 0 );
+ if ( nMoreCmds )
+ aCmdInfo.realloc( nPos + nMoreCmds );
+ else
+ return aCmdInfo;
+
+ pCmdInfo = aCmdInfo.getArray();
+
+ if ( bFolder )
+ {
+
+ // Optional standard commands
+
+
+ pCmdInfo[ nPos ] =
+ ucb::CommandInfo(
+ "transfer",
+ -1,
+ cppu::UnoType<ucb::TransferInfo>::get() );
+ nPos++;
+ pCmdInfo[ nPos ] =
+ ucb::CommandInfo(
+ "createNewContent",
+ -1,
+ cppu::UnoType<ucb::ContentInfo>::get() );
+ nPos++;
+ }
+ else
+ {
+ // no document-only commands at the moment.
+ }
+
+ if ( bSupportsLocking )
+ {
+ pCmdInfo[ nPos ] =
+ ucb::CommandInfo(
+ "lock",
+ -1,
+ cppu::UnoType<void>::get() );
+ nPos++;
+ pCmdInfo[ nPos ] =
+ ucb::CommandInfo(
+ "unlock",
+ -1,
+ cppu::UnoType<void>::get() );
+ nPos++;
+ }
+ return aCmdInfo;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/ucb/source/ucp/webdav-curl/webdavdatasupplier.cxx b/ucb/source/ucp/webdav-curl/webdavdatasupplier.cxx
new file mode 100644
index 0000000000..4b7f7786eb
--- /dev/null
+++ b/ucb/source/ucp/webdav-curl/webdavdatasupplier.cxx
@@ -0,0 +1,441 @@
+/* -*- 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:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include <sal/config.h>
+#include <sal/log.hxx>
+
+#include <utility>
+
+#include <rtl/ustrbuf.hxx>
+#include <com/sun/star/ucb/OpenMode.hpp>
+#include <ucbhelper/contentidentifier.hxx>
+#include <ucbhelper/providerhelper.hxx>
+#include "webdavdatasupplier.hxx"
+#include "webdavcontent.hxx"
+#include "DAVProperties.hxx"
+#include "CurlUri.hxx"
+#include <com/sun/star/ucb/IllegalIdentifierException.hpp>
+#include <com/sun/star/ucb/ResultSetException.hpp>
+#include <comphelper/diagnose_ex.hxx>
+
+using namespace com::sun::star;
+using namespace http_dav_ucp;
+
+namespace http_dav_ucp
+{
+
+namespace {
+
+auto DumpResources(std::vector<DAVResource> const& rResources) -> OUString
+{
+ OUStringBuffer buf;
+ for (auto const& rResource : rResources)
+ {
+ buf.append("resource URL: <" + rResource.uri);
+ try {
+ CurlUri const uri(rResource.uri);
+ buf.append("> parsed URL: <"
+ + DecodeURI(uri.GetRelativeReference())
+ + "> ");
+ } catch (...) {
+ // parsing uri could fail
+ buf.append("> parsing URL failed! ");
+ }
+ buf.append("properties: ");
+ for (auto const& it : rResource.properties)
+ {
+ buf.append("\"" + it.Name + "\" ");
+ }
+ buf.append("\n");
+ }
+ buf.stripEnd('\n'); // the last newline is superfluous, remove it
+ return buf.makeStringAndClear();
+}
+
+}
+
+}
+
+
+// DataSupplier Implementation.
+
+
+DataSupplier::DataSupplier(
+ const uno::Reference< uno::XComponentContext >& rxContext,
+ const rtl::Reference< Content >& rContent,
+ sal_Int32 nOpenMode )
+ : m_xContent( rContent ), m_xContext( rxContext ), m_nOpenMode( nOpenMode ),
+ m_bCountFinal( false ), m_bThrowException( false )
+{
+}
+
+
+// virtual
+DataSupplier::~DataSupplier()
+{}
+
+
+// virtual
+OUString DataSupplier::queryContentIdentifierString( sal_uInt32 nIndex )
+{
+ osl::Guard< osl::Mutex > aGuard( m_aMutex );
+
+ if (nIndex < m_Results.size())
+ {
+ OUString aId = m_Results[ nIndex ]->aId;
+ if ( aId.getLength() )
+ {
+ // Already cached.
+ return aId;
+ }
+ }
+
+ if ( getResult( nIndex ) )
+ {
+ OUString aId = m_xContent->getResourceAccess().getURL();
+
+ const ContentProperties& props(*(m_Results[ nIndex ]->pData));
+
+ if ( ( aId.lastIndexOf( '/' ) + 1 ) != aId.getLength() )
+ aId += "/";
+
+ aId += props.getEscapedTitle();
+
+ if ( props.isTrailingSlash() )
+ aId += "/";
+
+ m_Results[ nIndex ]->aId = aId;
+ return aId;
+ }
+ return OUString();
+}
+
+
+// virtual
+uno::Reference< ucb::XContentIdentifier >
+DataSupplier::queryContentIdentifier( sal_uInt32 nIndex )
+{
+ osl::Guard< osl::Mutex > aGuard( m_aMutex );
+
+ if (nIndex < m_Results.size())
+ {
+ uno::Reference< ucb::XContentIdentifier > xId
+ = m_Results[ nIndex ]->xId;
+ if ( xId.is() )
+ {
+ // Already cached.
+ return xId;
+ }
+ }
+
+ OUString aId = queryContentIdentifierString( nIndex );
+ if ( aId.getLength() )
+ {
+ uno::Reference< ucb::XContentIdentifier > xId
+ = new ::ucbhelper::ContentIdentifier( aId );
+ m_Results[ nIndex ]->xId = xId;
+ return xId;
+ }
+ return uno::Reference< ucb::XContentIdentifier >();
+}
+
+
+// virtual
+uno::Reference< ucb::XContent >
+DataSupplier::queryContent( sal_uInt32 nIndex )
+{
+ osl::Guard< osl::Mutex > aGuard( m_aMutex );
+
+ if (nIndex < m_Results.size())
+ {
+ uno::Reference< ucb::XContent > xContent
+ = m_Results[ nIndex ]->xContent;
+ if ( xContent.is() )
+ {
+ // Already cached.
+ return xContent;
+ }
+ }
+
+ uno::Reference< ucb::XContentIdentifier > xId
+ = queryContentIdentifier( nIndex );
+ if ( xId.is() )
+ {
+ try
+ {
+ uno::Reference< ucb::XContent > xContent
+ = m_xContent->getProvider()->queryContent( xId );
+ m_Results[ nIndex ]->xContent = xContent;
+ return xContent;
+
+ }
+ catch ( ucb::IllegalIdentifierException& )
+ {
+ }
+ }
+ return uno::Reference< ucb::XContent >();
+}
+
+
+// virtual
+bool DataSupplier::getResult( sal_uInt32 nIndex )
+{
+ osl::ClearableGuard< osl::Mutex > aGuard( m_aMutex );
+
+ if (nIndex < m_Results.size())
+ {
+ // Result already present.
+ return true;
+ }
+
+ // Obtain values...
+ if ( getData() )
+ {
+ if (nIndex < m_Results.size())
+ {
+ // Result already present.
+ return true;
+ }
+ }
+
+ return false;
+}
+
+
+// virtual
+sal_uInt32 DataSupplier::totalCount()
+{
+ // Obtain values...
+ getData();
+
+ return m_Results.size();
+}
+
+
+// virtual
+sal_uInt32 DataSupplier::currentCount()
+{
+ return m_Results.size();
+}
+
+
+// virtual
+bool DataSupplier::isCountFinal()
+{
+ return m_bCountFinal;
+}
+
+
+// virtual
+uno::Reference< sdbc::XRow > DataSupplier::queryPropertyValues(
+ sal_uInt32 nIndex )
+{
+ osl::Guard< osl::Mutex > aGuard( m_aMutex );
+
+ if (nIndex < m_Results.size())
+ {
+ uno::Reference< sdbc::XRow > xRow = m_Results[ nIndex ]->xRow;
+ if ( xRow.is() )
+ {
+ // Already cached.
+ return xRow;
+ }
+ }
+
+ if ( getResult( nIndex ) )
+ {
+ uno::Reference< sdbc::XRow > xRow
+ = Content::getPropertyValues(
+ m_xContext,
+ getResultSet()->getProperties(),
+ *(m_Results[ nIndex ]->pData),
+ m_xContent->getProvider(),
+ queryContentIdentifierString( nIndex ) );
+ m_Results[ nIndex ]->xRow = xRow;
+ return xRow;
+ }
+
+ return uno::Reference< sdbc::XRow >();
+}
+
+
+// virtual
+void DataSupplier::releasePropertyValues( sal_uInt32 nIndex )
+{
+ osl::Guard< osl::Mutex > aGuard( m_aMutex );
+
+ if (nIndex < m_Results.size())
+ m_Results[ nIndex ]->xRow.clear();
+}
+
+
+// virtual
+void DataSupplier::close()
+{
+}
+
+
+// virtual
+void DataSupplier::validate()
+{
+ if ( m_bThrowException )
+ throw ucb::ResultSetException();
+}
+
+bool DataSupplier::getData()
+{
+ osl::ClearableGuard< osl::Mutex > aGuard( m_aMutex );
+
+ if ( !m_bCountFinal )
+ {
+ std::vector< OUString > propertyNames;
+ ContentProperties::UCBNamesToDAVNames(
+ getResultSet()->getProperties(), propertyNames );
+
+ // Append "resourcetype", if not already present. It's value is
+ // needed to get a valid ContentProperties::pIsFolder value, which
+ // is needed for OpenMode handling.
+
+ bool isNoResourceType = std::none_of(propertyNames.begin(), propertyNames.end(),
+ [](const OUString& rPropName) { return rPropName.equals(DAVProperties::RESOURCETYPE); });
+
+ if ( isNoResourceType )
+ propertyNames.push_back( DAVProperties::RESOURCETYPE );
+
+ std::vector< DAVResource > resources;
+ try
+ {
+ // propfind depth 1, get property values for parent AND for each
+ // child
+ m_xContent->getResourceAccess()
+ .PROPFIND( DAVONE,
+ propertyNames,
+ resources,
+ getResultSet()->getEnvironment() );
+ SAL_INFO("ucb.ucp.webdav", "getData() - " << DumpResources(resources));
+ }
+ catch ( DAVException & )
+ {
+ TOOLS_WARN_EXCEPTION( "ucb.ucp.webdav", "PROPFIND : DAVException" );
+ m_bThrowException = true;
+ }
+
+ if ( !m_bThrowException )
+ {
+ try
+ {
+ CurlUri const aURI(
+ m_xContent->getResourceAccess().getURL() );
+ OUString aPath = aURI.GetPath();
+
+ if ( aPath.endsWith("/") )
+ aPath = aPath.copy( 0, aPath.getLength() - 1 );
+
+ aPath = DecodeURI(aPath);
+ bool bFoundParent = false;
+
+ for ( size_t n = 0; n < resources.size(); ++n )
+ {
+ const DAVResource & rRes = resources[ n ];
+
+ // Filter parent, which is contained somewhere(!) in
+ // the vector.
+ if ( !bFoundParent )
+ {
+ try
+ {
+ CurlUri const aCurrURI( rRes.uri );
+ OUString aCurrPath = aCurrURI.GetPath();
+ if ( aCurrPath.endsWith("/") )
+ aCurrPath
+ = aCurrPath.copy(
+ 0,
+ aCurrPath.getLength() - 1 );
+
+ aCurrPath = DecodeURI(aCurrPath);
+ if ( aPath == aCurrPath )
+ {
+ bFoundParent = true;
+ continue;
+ }
+ }
+ catch ( DAVException const & )
+ {
+ // do nothing, ignore error. continue.
+ }
+ }
+
+ std::unique_ptr<ContentProperties> pContentProperties
+ = std::make_unique<ContentProperties>( rRes );
+
+ // Check resource against open mode.
+ switch ( m_nOpenMode )
+ {
+ case ucb::OpenMode::FOLDERS:
+ {
+ bool bFolder = false;
+
+ const uno::Any & rValue
+ = pContentProperties->getValue( "IsFolder" );
+ rValue >>= bFolder;
+
+ if ( !bFolder )
+ continue;
+
+ break;
+ }
+
+ case ucb::OpenMode::DOCUMENTS:
+ {
+ bool bDocument = false;
+
+ const uno::Any & rValue
+ = pContentProperties->getValue( "IsDocument" );
+ rValue >>= bDocument;
+
+ if ( !bDocument )
+ continue;
+
+ break;
+ }
+
+ case ucb::OpenMode::ALL:
+ default:
+ break;
+ }
+
+ m_Results.push_back(
+ std::make_unique<ResultListEntry>(std::move(pContentProperties)));
+ }
+ }
+ catch ( DAVException const & )
+ {
+ }
+ }
+
+ m_bCountFinal = true;
+
+ // Callback possible, because listeners may be informed!
+ aGuard.clear();
+ getResultSet()->rowCountFinal();
+ }
+ return !m_bThrowException;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/ucb/source/ucp/webdav-curl/webdavdatasupplier.hxx b/ucb/source/ucp/webdav-curl/webdavdatasupplier.hxx
new file mode 100644
index 0000000000..e1f2efba91
--- /dev/null
+++ b/ucb/source/ucp/webdav-curl/webdavdatasupplier.hxx
@@ -0,0 +1,92 @@
+/* -*- 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:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+
+#pragma once
+
+#include <sal/config.h>
+
+#include "ContentProperties.hxx"
+#include <memory>
+#include <rtl/ref.hxx>
+#include <ucbhelper/resultset.hxx>
+
+namespace http_dav_ucp {
+
+struct DataSupplier_Impl;
+class Content;
+struct DAVResource;
+
+class DataSupplier : public ucbhelper::ResultSetDataSupplier
+{
+ bool getData();
+
+public:
+ DataSupplier( const css::uno::Reference< css::uno::XComponentContext >& rxContext,
+ const rtl::Reference< Content >& rContent,
+ 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;
+
+private:
+ struct ResultListEntry
+ {
+ OUString aId;
+ css::uno::Reference< css::ucb::XContentIdentifier > xId;
+ css::uno::Reference< css::ucb::XContent > xContent;
+ css::uno::Reference< css::sdbc::XRow > xRow;
+ std::unique_ptr<ContentProperties> pData;
+
+ explicit ResultListEntry( std::unique_ptr<ContentProperties> && pEntry ) : pData( std::move(pEntry) ) {}
+ };
+
+ typedef std::vector<std::unique_ptr<ResultListEntry>> ResultList;
+
+ osl::Mutex m_aMutex;
+ ResultList m_Results;
+ rtl::Reference< Content > m_xContent;
+ css::uno::Reference< css::uno::XComponentContext > m_xContext;
+ sal_Int32 m_nOpenMode;
+ bool m_bCountFinal;
+ bool m_bThrowException;
+};
+
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/ucb/source/ucp/webdav-curl/webdavprovider.cxx b/ucb/source/ucp/webdav-curl/webdavprovider.cxx
new file mode 100644
index 0000000000..effd6665ad
--- /dev/null
+++ b/ucb/source/ucp/webdav-curl/webdavprovider.cxx
@@ -0,0 +1,174 @@
+/* -*- 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:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include <comphelper/processfactory.hxx>
+#include <ucbhelper/contentidentifier.hxx>
+#include <ucbhelper/macros.hxx>
+#include "webdavprovider.hxx"
+#include "webdavcontent.hxx"
+
+#include <cppuhelper/queryinterface.hxx>
+#include <osl/mutex.hxx>
+#include <com/sun/star/ucb/IllegalIdentifierException.hpp>
+
+#include <tools/urlobj.hxx>
+
+using namespace com::sun::star;
+using namespace http_dav_ucp;
+
+
+// ContentProvider Implementation.
+
+
+ContentProvider::ContentProvider(
+ const uno::Reference< uno::XComponentContext >& rContext )
+: ::ucbhelper::ContentProviderImplHelper( rContext ),
+ m_xDAVSessionFactory( new DAVSessionFactory )
+{
+}
+
+
+// virtual
+ContentProvider::~ContentProvider()
+{}
+
+
+// XInterface methods.
+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< ucb::XContentProvider* >(this)
+ );
+ return aRet.hasValue() ? aRet : OWeakObject::queryInterface( rType );
+}
+
+// XTypeProvider methods.
+
+
+XTYPEPROVIDER_IMPL_3( ContentProvider,
+ lang::XTypeProvider,
+ lang::XServiceInfo,
+ ucb::XContentProvider );
+
+
+// XServiceInfo methods.
+
+OUString
+ContentProvider::getImplementationName()
+{
+ return "com.sun.star.comp.WebDAVContentProvider";
+}
+
+css::uno::Sequence< OUString >
+ContentProvider::getSupportedServiceNames()
+{
+ return { WEBDAV_CONTENT_PROVIDER_SERVICE_NAME };
+}
+
+sal_Bool
+ContentProvider::supportsService(const OUString& s)
+{
+ return cppu::supportsService(this, s);
+}
+
+// XContentProvider methods.
+
+
+// virtual
+uno::Reference< ucb::XContent > SAL_CALL
+ContentProvider::queryContent(
+ const uno::Reference<
+ ucb::XContentIdentifier >& Identifier )
+{
+ // Check URL scheme...
+ INetURLObject aURL(Identifier->getContentIdentifier());
+
+ if (aURL.isSchemeEqualTo(INetProtocol::NotValid))
+ throw ucb::IllegalIdentifierException();
+
+ if (!aURL.isAnyKnownWebDAVScheme())
+ throw ucb::IllegalIdentifierException();
+
+ uno::Reference< ucb::XContentIdentifier > xCanonicId;
+
+ if (aURL.isSchemeEqualTo(INetProtocol::VndSunStarWebdav) ||
+ aURL.isSchemeEqualTo(DAV_URL_SCHEME) ||
+ aURL.isSchemeEqualTo(WEBDAV_URL_SCHEME))
+ {
+ aURL.changeScheme(INetProtocol::Http);
+ xCanonicId = new ::ucbhelper::ContentIdentifier( aURL.getExternalURL() );
+ }
+ else if (aURL.isSchemeEqualTo(VNDSUNSTARWEBDAVS_URL_SCHEME) ||
+ aURL.isSchemeEqualTo(DAVS_URL_SCHEME) ||
+ aURL.isSchemeEqualTo(WEBDAVS_URL_SCHEME))
+ {
+ aURL.changeScheme(INetProtocol::Https);
+ xCanonicId = new ::ucbhelper::ContentIdentifier( aURL.getExternalURL() );
+ }
+ else
+ {
+ xCanonicId = Identifier;
+ }
+
+ osl::MutexGuard aGuard( m_aMutex );
+
+ // Check, if a content with given id already exists...
+ uno::Reference<ucb::XContent> xContent = queryExistingContent(xCanonicId);
+ if ( xContent.is() )
+ return xContent;
+
+ // Create a new content.
+
+ try
+ {
+ xContent = new ::http_dav_ucp::Content(
+ m_xContext, this, xCanonicId, m_xDAVSessionFactory );
+ registerNewContent( xContent );
+ }
+ catch ( ucb::ContentCreationException const & )
+ {
+ throw ucb::IllegalIdentifierException();
+ }
+
+ if ( !xContent->getIdentifier().is() )
+ throw ucb::IllegalIdentifierException();
+
+ return xContent;
+}
+
+extern "C" SAL_DLLPUBLIC_EXPORT css::uno::XInterface*
+ucb_webdav_ContentProvider_get_implementation(
+ css::uno::XComponentContext* context, css::uno::Sequence<css::uno::Any> const&)
+{
+ return cppu::acquire(new ContentProvider(context));
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/ucb/source/ucp/webdav-curl/webdavprovider.hxx b/ucb/source/ucp/webdav-curl/webdavprovider.hxx
new file mode 100644
index 0000000000..138f776eee
--- /dev/null
+++ b/ucb/source/ucp/webdav-curl/webdavprovider.hxx
@@ -0,0 +1,100 @@
+/* -*- 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:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+
+#pragma once
+
+#include <sal/config.h>
+
+#include <memory>
+
+#include <rtl/ref.hxx>
+#include <com/sun/star/beans/Property.hpp>
+#include "DAVSessionFactory.hxx"
+#include <ucbhelper/providerhelper.hxx>
+#include "PropertyMap.hxx"
+
+namespace com::sun::star::lang {
+class XSingleServiceFactory;
+}
+
+namespace http_dav_ucp {
+
+
+// UNO service name for the provider. This name will be used by the UCB to
+// create instances of the provider.
+inline constexpr OUString WEBDAV_CONTENT_PROVIDER_SERVICE_NAME = u"com.sun.star.ucb.WebDAVContentProvider"_ustr;
+
+// URL scheme. This is the scheme the provider will be able to create
+// contents for. The UCB will select the provider ( i.e. in order to create
+// contents ) according to this scheme.
+#define VNDSUNSTARWEBDAV_URL_SCHEME "vnd.sun.star.webdav"
+#define VNDSUNSTARWEBDAVS_URL_SCHEME u"vnd.sun.star.webdavs"
+#define HTTP_URL_SCHEME u"http"
+#define HTTPS_URL_SCHEME u"https"
+#define DAV_URL_SCHEME u"dav"
+#define DAVS_URL_SCHEME u"davs"
+#define WEBDAV_URL_SCHEME u"webdav"
+#define WEBDAVS_URL_SCHEME u"webdavs"
+
+inline constexpr OUString HTTP_CONTENT_TYPE = u"application/" HTTP_URL_SCHEME "-content"_ustr;
+
+#define WEBDAV_CONTENT_TYPE HTTP_CONTENT_TYPE
+inline constexpr OUString WEBDAV_COLLECTION_TYPE = u"application/" VNDSUNSTARWEBDAV_URL_SCHEME "-collection"_ustr;
+
+
+class ContentProvider : public ::ucbhelper::ContentProviderImplHelper
+{
+ rtl::Reference< DAVSessionFactory > m_xDAVSessionFactory;
+ std::unique_ptr<PropertyMap> m_pProps;
+
+public:
+ explicit ContentProvider( const css::uno::Reference< css::uno::XComponentContext >& rContext );
+ 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;
+
+
+ // Non-interface methods.
+
+ bool getProperty( const OUString & rPropName,
+ css::beans::Property & rProp );
+};
+
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/ucb/source/ucp/webdav-curl/webdavresponseparser.cxx b/ucb/source/ucp/webdav-curl/webdavresponseparser.cxx
new file mode 100644
index 0000000000..9a0500d01b
--- /dev/null
+++ b/ucb/source/ucp/webdav-curl/webdavresponseparser.cxx
@@ -0,0 +1,1001 @@
+/* -*- 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:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include "webdavresponseparser.hxx"
+
+#include "DAVProperties.hxx"
+#include "UCBDeadPropertyValue.hxx"
+
+#include <comphelper/processfactory.hxx>
+#include <comphelper/sequence.hxx>
+
+#include <cppuhelper/implbase.hxx>
+#include <com/sun/star/xml/sax/Parser.hpp>
+#include <com/sun/star/xml/sax/InputSource.hpp>
+#include <com/sun/star/xml/sax/XDocumentHandler.hpp>
+#include <com/sun/star/ucb/LockEntry.hpp>
+#include <com/sun/star/ucb/LockScope.hpp>
+#include <com/sun/star/ucb/LockType.hpp>
+#include <com/sun/star/ucb/Lock.hpp>
+#include <map>
+#include <unordered_map>
+#include <rtl/ref.hxx>
+#include <rtl/uri.hxx>
+#include <sal/log.hxx>
+#include <o3tl/string_view.hxx>
+
+using namespace com::sun::star;
+using namespace http_dav_ucp;
+
+
+// WebDAVNamespace enum and StringToEnum converter
+namespace
+{
+ enum WebDAVNamespace
+ {
+ WebDAVNamespace_unknown = 0,
+ WebDAVNamespace_DAV,
+ WebDAVNamespace_ucb_openoffice_org_dav_props,
+
+ WebDAVNamespace_last
+ };
+
+ WebDAVNamespace StrToWebDAVNamespace(::std::u16string_view rStr)
+ {
+ if (rStr == u"DAV:")
+ {
+ return WebDAVNamespace_DAV;
+ }
+ else if (rStr == u"http://ucb.openoffice.org/dav/props/")
+ {
+ return WebDAVNamespace_ucb_openoffice_org_dav_props;
+ }
+
+ return WebDAVNamespace_unknown;
+ }
+} // end of anonymous namespace
+
+// WebDAVName enum and StringToEnum converter using unordered_map
+namespace
+{
+ enum WebDAVName
+ {
+ WebDAVName_unknown = 0,
+ WebDAVName_activelock,
+ WebDAVName_lockdiscovery,
+ WebDAVName_multistatus,
+ WebDAVName_response,
+ WebDAVName_href,
+ WebDAVName_propstat,
+ WebDAVName_prop,
+ WebDAVName_resourcetype,
+ WebDAVName_collection,
+ WebDAVName_getcontenttype,
+ WebDAVName_supportedlock,
+ WebDAVName_lockentry,
+ WebDAVName_lockscope,
+ WebDAVName_depth,
+ WebDAVName_locktoken,
+ WebDAVName_exclusive,
+ WebDAVName_locktype,
+ WebDAVName_owner,
+ WebDAVName_timeout,
+ WebDAVName_write,
+ WebDAVName_shared,
+ WebDAVName_status,
+ WebDAVName_getlastmodified,
+ WebDAVName_creationdate,
+ WebDAVName_getcontentlength,
+ WebDAVName_type,
+ WebDAVName_value,
+ WebDAVName_ucbprop,
+
+ WebDAVName_last
+ };
+
+ WebDAVName StrToWebDAVName(const OUString& rStr)
+ {
+ typedef std::unordered_map< OUString, WebDAVName > WebDAVNameMapper;
+ typedef std::pair< OUString, WebDAVName > WebDAVNameValueType;
+ static WebDAVNameMapper aWebDAVNameMapperList;
+
+ if(aWebDAVNameMapperList.empty())
+ {
+ aWebDAVNameMapperList.insert(WebDAVNameValueType(OUString("activelock"), WebDAVName_activelock));
+ aWebDAVNameMapperList.insert(WebDAVNameValueType(OUString("lockdiscovery"), WebDAVName_lockdiscovery));
+ aWebDAVNameMapperList.insert(WebDAVNameValueType(OUString("multistatus"), WebDAVName_multistatus));
+ aWebDAVNameMapperList.insert(WebDAVNameValueType(OUString("response"), WebDAVName_response));
+ aWebDAVNameMapperList.insert(WebDAVNameValueType(OUString("href"), WebDAVName_href));
+ aWebDAVNameMapperList.insert(WebDAVNameValueType(OUString("propstat"), WebDAVName_propstat));
+ aWebDAVNameMapperList.insert(WebDAVNameValueType(OUString("prop"), WebDAVName_prop));
+ aWebDAVNameMapperList.insert(WebDAVNameValueType(OUString("resourcetype"), WebDAVName_resourcetype));
+ aWebDAVNameMapperList.insert(WebDAVNameValueType(OUString("collection"), WebDAVName_collection));
+ aWebDAVNameMapperList.insert(WebDAVNameValueType(OUString("getcontenttype"), WebDAVName_getcontenttype));
+ aWebDAVNameMapperList.insert(WebDAVNameValueType(OUString("supportedlock"), WebDAVName_supportedlock));
+ aWebDAVNameMapperList.insert(WebDAVNameValueType(OUString("lockentry"), WebDAVName_lockentry));
+ aWebDAVNameMapperList.insert(WebDAVNameValueType(OUString("lockscope"), WebDAVName_lockscope));
+ aWebDAVNameMapperList.insert(WebDAVNameValueType(OUString("depth"), WebDAVName_depth));
+ aWebDAVNameMapperList.insert(WebDAVNameValueType(OUString("locktoken"), WebDAVName_locktoken));
+ aWebDAVNameMapperList.insert(WebDAVNameValueType(OUString("exclusive"), WebDAVName_exclusive));
+ aWebDAVNameMapperList.insert(WebDAVNameValueType(OUString("locktype"), WebDAVName_locktype));
+ aWebDAVNameMapperList.insert(WebDAVNameValueType(OUString("owner"), WebDAVName_owner));
+ aWebDAVNameMapperList.insert(WebDAVNameValueType(OUString("timeout"), WebDAVName_timeout));
+ aWebDAVNameMapperList.insert(WebDAVNameValueType(OUString("write"), WebDAVName_write));
+ aWebDAVNameMapperList.insert(WebDAVNameValueType(OUString("shared"), WebDAVName_shared));
+ aWebDAVNameMapperList.insert(WebDAVNameValueType(OUString("status"), WebDAVName_status));
+ aWebDAVNameMapperList.insert(WebDAVNameValueType(OUString("getlastmodified"), WebDAVName_getlastmodified));
+ aWebDAVNameMapperList.insert(WebDAVNameValueType(OUString("creationdate"), WebDAVName_creationdate));
+ aWebDAVNameMapperList.insert(WebDAVNameValueType(OUString("getcontentlength"), WebDAVName_getcontentlength));
+ aWebDAVNameMapperList.insert(WebDAVNameValueType(OUString("type"), WebDAVName_type));
+ aWebDAVNameMapperList.insert(WebDAVNameValueType(OUString("value"), WebDAVName_value));
+ aWebDAVNameMapperList.insert(WebDAVNameValueType(OUString("ucbprop"), WebDAVName_ucbprop));
+ }
+
+ const WebDAVNameMapper::const_iterator aResult(aWebDAVNameMapperList.find(rStr));
+
+ if(aResult == aWebDAVNameMapperList.end())
+ {
+ return WebDAVName_unknown;
+ }
+ else
+ {
+ return aResult->second;
+ }
+ }
+} // end of anonymous namespace
+
+
+// WebDAVContext, holding information for each start/endElement pair
+
+namespace
+{
+ typedef std::map< OUString, OUString > NamespaceMap;
+
+ class WebDAVContext
+ {
+ private:
+ WebDAVContext* mpParent;
+ NamespaceMap maNamespaceMap;
+ OUString maWhiteSpace;
+
+ OUString maNamespace;
+ OUString maName;
+
+ WebDAVNamespace maWebDAVNamespace;
+ WebDAVName maWebDAVName;
+
+ // local helpers
+ void parseForNamespaceTokens(const uno::Reference< xml::sax::XAttributeList >& xAttribs);
+ OUString mapNamespaceToken(const OUString& rToken) const;
+ void splitName(const OUString& rSource);
+
+ public:
+ WebDAVContext(WebDAVContext* pParent, const OUString& aName, const uno::Reference< xml::sax::XAttributeList >& xAttribs);
+
+ WebDAVContext* getParent() const { return mpParent; }
+ OUString& getWhiteSpace() { return maWhiteSpace; }
+ void setWhiteSpace(const OUString& rNew) { maWhiteSpace = rNew; }
+
+ const OUString& getNamespace() const { return maNamespace; }
+ const OUString& getName() const { return maName; }
+ const WebDAVNamespace& getWebDAVNamespace() const { return maWebDAVNamespace; }
+ const WebDAVName& getWebDAVName() const { return maWebDAVName; }
+ };
+
+ void WebDAVContext::parseForNamespaceTokens(const uno::Reference< xml::sax::XAttributeList >& xAttribs)
+ {
+ const sal_Int16 nAttributes(xAttribs->getLength());
+
+ for(sal_Int16 a(0); a < nAttributes; a++)
+ {
+ const OUString aName(xAttribs->getNameByIndex(a));
+ const sal_Int32 nLen(aName.getLength());
+
+ if(nLen)
+ {
+ if(aName.startsWith("xmlns"))
+ {
+ const sal_Int32 nIndex(aName.indexOf(':', 0));
+
+ if(-1 != nIndex && nIndex + 1 < nLen)
+ {
+ const OUString aToken(aName.copy(nIndex + 1));
+
+ maNamespaceMap.emplace(aToken, xAttribs->getValueByIndex(a));
+ }
+ }
+ }
+ }
+ }
+
+ OUString WebDAVContext::mapNamespaceToken(const OUString& rToken) const
+ {
+ NamespaceMap::const_iterator iter = maNamespaceMap.find(rToken);
+
+ if(maNamespaceMap.end() == iter)
+ {
+ if(getParent())
+ {
+ return getParent()->mapNamespaceToken(rToken);
+ }
+ else
+ {
+ return rToken;
+ }
+ }
+ else
+ {
+ return (*iter).second;
+ }
+ }
+
+ void WebDAVContext::splitName(const OUString& rSource)
+ {
+ const sal_Int32 nLen(rSource.getLength());
+ maNamespace.clear();
+ maName = rSource;
+
+ if(nLen)
+ {
+ const sal_Int32 nIndex(rSource.indexOf(':', 0));
+
+ if(nIndex > 0 && ((nIndex + 1) < nLen))
+ {
+ maNamespace = mapNamespaceToken(rSource.copy(0, nIndex));
+ maName = rSource.copy(nIndex + 1);
+ }
+ }
+ }
+
+ WebDAVContext::WebDAVContext(WebDAVContext* pParent, const OUString& aName, const uno::Reference< xml::sax::XAttributeList >& xAttribs)
+ : mpParent(pParent),
+ maNamespaceMap(),
+ maWhiteSpace(),
+ maNamespace(),
+ maName(),
+ maWebDAVNamespace(WebDAVNamespace_unknown),
+ maWebDAVName(WebDAVName_unknown)
+ {
+ const sal_Int16 nAttributes(xAttribs->getLength());
+
+ if(nAttributes)
+ {
+ // parse evtl. namespace entries
+ parseForNamespaceTokens(xAttribs);
+ }
+
+ // split name to namespace and name
+ splitName(aName);
+
+ // evaluate enums for namespace and name
+ maWebDAVNamespace = StrToWebDAVNamespace(maNamespace);
+ maWebDAVName = StrToWebDAVName(maName);
+ }
+} // end of anonymous namespace
+
+
+// the Xml parser itself
+
+namespace
+{
+ enum WebDAVResponseParserMode
+ {
+ WebDAVResponseParserMode_PropFind = 0,
+ WebDAVResponseParserMode_PropName,
+ WebDAVResponseParserMode_Lock
+ };
+
+ class WebDAVResponseParser : public cppu::WeakImplHelper< css::xml::sax::XDocumentHandler >
+ {
+ private:
+ std::vector< ucb::Lock > maResult_Lock;
+ std::vector< http_dav_ucp::DAVResource > maResult_PropFind;
+ std::vector< http_dav_ucp::DAVResourceInfo > maResult_PropName;
+
+ WebDAVContext* mpContext;
+ OUString maHref;
+ OUString maStatus;
+ OUString m_UCBType;
+ OUString m_UCBValue;
+ std::vector< http_dav_ucp::DAVPropertyValue > maResponseProperties;
+ std::vector< http_dav_ucp::DAVPropertyValue > maPropStatProperties;
+ std::vector< OUString > maResponseNames;
+ std::vector< OUString > maPropStatNames;
+ uno::Sequence< ucb::LockEntry > maLockEntries;
+ ucb::LockScope maLockScope;
+ ucb::LockType maLockType;
+ ucb::Lock maLock;
+ WebDAVResponseParserMode meWebDAVResponseParserMode;
+
+ bool mbResourceTypeCollection : 1;
+ bool mbLockScopeSet : 1;
+ bool mbLockTypeSet : 1;
+
+ // local helpers
+ bool whitespaceIsAvailable() const
+ {
+ return mpContext && mpContext->getWhiteSpace().getLength();
+ }
+ bool hasParent(WebDAVName aWebDAVName) const
+ {
+ return mpContext && mpContext->getParent() && aWebDAVName == mpContext->getParent()->getWebDAVName();
+ }
+ bool propertyIsReady() const
+ {
+ return hasParent(WebDAVName_prop) && whitespaceIsAvailable();
+ }
+ bool isCollectingProperties() const
+ {
+ return WebDAVResponseParserMode_PropFind == meWebDAVResponseParserMode;
+ }
+ bool isCollectingPropNames() const
+ {
+ return WebDAVResponseParserMode_PropName == meWebDAVResponseParserMode;
+ }
+ bool collectThisPropertyAsName() const
+ {
+ return isCollectingPropNames() && hasParent(WebDAVName_prop);
+ }
+ void pop_context()
+ {
+ if(mpContext)
+ {
+ WebDAVContext* pTemp = mpContext;
+ mpContext = mpContext->getParent();
+ delete pTemp;
+ }
+ else
+ {
+ SAL_WARN( "ucb.ucp.webdav", "Parser context pop without context (!)");
+ }
+ }
+
+ public:
+ explicit WebDAVResponseParser(WebDAVResponseParserMode eWebDAVResponseParserMode);
+ virtual ~WebDAVResponseParser() override;
+
+ // Methods XDocumentHandler
+ virtual void SAL_CALL startDocument( ) override;
+ virtual void SAL_CALL endDocument( ) override;
+ virtual void SAL_CALL startElement( const OUString& aName, const uno::Reference< xml::sax::XAttributeList >& xAttribs ) override;
+ virtual void SAL_CALL endElement( const OUString& aName ) override;
+ virtual void SAL_CALL characters( const OUString& aChars ) override;
+ virtual void SAL_CALL ignorableWhitespace( const OUString& aWhitespaces ) override;
+ virtual void SAL_CALL processingInstruction( const OUString& aTarget, const OUString& aData ) override;
+ virtual void SAL_CALL setDocumentLocator( const uno::Reference< xml::sax::XLocator >& xLocator ) override;
+
+ const std::vector< ucb::Lock >& getResult_Lock() const { return maResult_Lock; }
+ const std::vector< http_dav_ucp::DAVResource >& getResult_PropFind() const { return maResult_PropFind; }
+ const std::vector< http_dav_ucp::DAVResourceInfo >& getResult_PropName() const { return maResult_PropName; }
+ };
+
+ WebDAVResponseParser::WebDAVResponseParser(WebDAVResponseParserMode eWebDAVResponseParserMode)
+ : maResult_PropFind(),
+ maResult_PropName(),
+ mpContext(nullptr),
+ maHref(),
+ maStatus(),
+ maResponseProperties(),
+ maPropStatProperties(),
+ maResponseNames(),
+ maPropStatNames(),
+ maLockEntries(),
+ maLockScope(ucb::LockScope_EXCLUSIVE),
+ maLockType(ucb::LockType_WRITE),
+ meWebDAVResponseParserMode(eWebDAVResponseParserMode),
+ mbResourceTypeCollection(false),
+ mbLockScopeSet(false),
+ mbLockTypeSet(false)
+ {
+ }
+
+ WebDAVResponseParser::~WebDAVResponseParser()
+ {
+ SAL_WARN_IF(mpContext, "ucb.ucp.webdav", "Parser destructed with existing content (!)");
+ while(mpContext)
+ {
+ pop_context();
+ }
+ }
+
+ void SAL_CALL WebDAVResponseParser::startDocument( )
+ {
+ SAL_WARN_IF(mpContext, "ucb.ucp.webdav", "Parser start with existing content (!)");
+ }
+
+ void SAL_CALL WebDAVResponseParser::endDocument( )
+ {
+ SAL_WARN_IF(mpContext, "ucb.ucp.webdav", "Parser end with existing content (!)");
+ }
+
+ void SAL_CALL WebDAVResponseParser::startElement( const OUString& aName, const uno::Reference< xml::sax::XAttributeList >& xAttribs )
+ {
+ const sal_Int32 nLen(aName.getLength());
+
+ if(nLen)
+ {
+ // create new context (push)
+ mpContext = new WebDAVContext(mpContext, aName, xAttribs);
+
+ if(collectThisPropertyAsName())
+ {
+ // When collecting property names and parent is prop there is no need
+ // to handle the content of this property deeper (evtl. preparations)
+ }
+ else
+ {
+ switch(mpContext->getWebDAVNamespace())
+ {
+ default: // WebDAVNamespace_unknown, WebDAVNamespace_last or unhandled
+ {
+ break;
+ }
+ case WebDAVNamespace_DAV:
+ {
+ switch(mpContext->getWebDAVName())
+ {
+ default: // WebDAVName_unknown, WebDAVName_last or unhandled
+ {
+ break;
+ }
+ case WebDAVName_propstat:
+ {
+ // propstat start
+ if(isCollectingProperties())
+ {
+ // reset maPropStatProperties
+ maPropStatProperties.clear();
+ }
+ else
+ {
+ // when collecting properties reset maPropStatNames
+ maPropStatNames.clear();
+ }
+ break;
+ }
+ case WebDAVName_response:
+ {
+ // response start, reset Href and status and maResponseProperties
+ maHref.clear();
+ maStatus.clear();
+
+ if(isCollectingProperties())
+ {
+ // reset maResponseProperties
+ maResponseProperties.clear();
+ }
+ else
+ {
+ // reset maResponseNames when collecting properties
+ maResponseNames.clear();
+ }
+ break;
+ }
+ case WebDAVName_resourcetype:
+ {
+ // resourcetype start, reset collection
+ mbResourceTypeCollection = false;
+ break;
+ }
+ case WebDAVName_supportedlock:
+ {
+ // supportedlock start, reset maLockEntries
+ maLockEntries.realloc(0);
+ break;
+ }
+ case WebDAVName_lockentry:
+ {
+ // lockentry start, reset maLockEntries
+ mbLockScopeSet = false;
+ mbLockTypeSet = false;
+ break;
+ }
+ case WebDAVName_activelock:
+ {
+ maLock = ucb::Lock();
+ break;
+ }
+ }
+ break;
+ }
+ case WebDAVNamespace_ucb_openoffice_org_dav_props:
+ {
+ break;
+ }
+ }
+ }
+ }
+ }
+
+ OUString MakePropertyName(WebDAVContext const& rContext)
+ {
+ OUString ret;
+ OString const name(OUStringToOString(rContext.getName(), RTL_TEXTENCODING_UTF8));
+ OString const nameSpace(OUStringToOString(rContext.getNamespace(), RTL_TEXTENCODING_UTF8));
+ DAVProperties::createUCBPropName(nameSpace.getStr(), name.getStr(), ret);
+ return ret;
+ }
+
+ void SAL_CALL WebDAVResponseParser::endElement( const OUString& aName )
+ {
+ const sal_Int32 nLen(aName.getLength());
+ SAL_WARN_IF(!mpContext, "ucb.ucp.webdav", "Parser EndElement without content (!)");
+
+ if(mpContext && nLen)
+ {
+ if(collectThisPropertyAsName())
+ {
+ // name must be encoded as expected by createSerfPropName()
+ OUString const name(MakePropertyName(*mpContext));
+ maPropStatNames.emplace_back(name);
+ }
+ else
+ {
+ switch(mpContext->getWebDAVNamespace())
+ {
+ default: // WebDAVNamespace_unknown, WebDAVNamespace_last or unhandled
+ {
+ break;
+ }
+ case WebDAVNamespace_DAV:
+ {
+ switch(mpContext->getWebDAVName())
+ {
+ default: // WebDAVName_unknown, WebDAVName_last or unhandled
+ {
+ break;
+ }
+ case WebDAVName_href:
+ {
+ // href end, save it if we have whitespace
+ if(whitespaceIsAvailable())
+ {
+ // Sharepoint 2016 workaround: apparently
+ // the result is an IRI (RFC 3987 possibly?)
+ // so try to encode the non-ASCII chars
+ // without changing anything else
+ maHref = ::rtl::Uri::encode(mpContext->getWhiteSpace(),
+ rtl_UriCharClassUric, rtl_UriEncodeKeepEscapes,
+ RTL_TEXTENCODING_UTF8);
+ }
+ break;
+ }
+ case WebDAVName_status:
+ {
+ // status end, save it if we have whitespace
+ if(whitespaceIsAvailable())
+ {
+ maStatus = mpContext->getWhiteSpace();
+ }
+ break;
+ }
+ case WebDAVName_getlastmodified:
+ {
+ // getlastmodified end, safe if content is correct
+ if(propertyIsReady())
+ {
+ http_dav_ucp::DAVPropertyValue aDAVPropertyValue;
+
+ aDAVPropertyValue.Name = "DAV:getlastmodified";
+ aDAVPropertyValue.Value <<= mpContext->getWhiteSpace();
+ maPropStatProperties.push_back(aDAVPropertyValue);
+ }
+ break;
+ }
+ case WebDAVName_creationdate:
+ {
+ // creationdate end, safe if content is correct
+ if(propertyIsReady())
+ {
+ http_dav_ucp::DAVPropertyValue aDAVPropertyValue;
+
+ aDAVPropertyValue.Name = "DAV:creationdate";
+ aDAVPropertyValue.Value <<= mpContext->getWhiteSpace();
+ maPropStatProperties.push_back(aDAVPropertyValue);
+ }
+ break;
+ }
+ case WebDAVName_collection:
+ {
+ // collection end, check and set
+ if(hasParent(WebDAVName_resourcetype))
+ {
+ mbResourceTypeCollection = true;
+ }
+ break;
+ }
+ case WebDAVName_resourcetype:
+ {
+ // resourcetype end, check for collection
+ if(hasParent(WebDAVName_prop))
+ {
+ http_dav_ucp::DAVPropertyValue aDAVPropertyValue;
+
+ aDAVPropertyValue.Name = "DAV:resourcetype";
+ aDAVPropertyValue.Value <<= (mbResourceTypeCollection ? OUString("collection") : OUString());
+ maPropStatProperties.push_back(aDAVPropertyValue);
+ }
+ break;
+ }
+ case WebDAVName_getcontentlength:
+ {
+ // getcontentlength end, safe if content is correct
+ if(propertyIsReady())
+ {
+ http_dav_ucp::DAVPropertyValue aDAVPropertyValue;
+
+ aDAVPropertyValue.Name = "DAV:getcontentlength";
+ aDAVPropertyValue.Value <<= mpContext->getWhiteSpace();
+ maPropStatProperties.push_back(aDAVPropertyValue);
+ }
+ break;
+ }
+ case WebDAVName_getcontenttype:
+ {
+ // getcontenttype end, safe if content is correct
+ if(propertyIsReady())
+ {
+ http_dav_ucp::DAVPropertyValue aDAVPropertyValue;
+
+ aDAVPropertyValue.Name = "DAV:getcontenttype";
+ aDAVPropertyValue.Value <<= mpContext->getWhiteSpace();
+ maPropStatProperties.push_back(aDAVPropertyValue);
+ }
+ break;
+ }
+ case WebDAVName_supportedlock:
+ {
+ // supportedlock end
+ if(hasParent(WebDAVName_prop) && maLockEntries.hasElements())
+ {
+ http_dav_ucp::DAVPropertyValue aDAVPropertyValue;
+
+ aDAVPropertyValue.Name = "DAV:supportedlock";
+ aDAVPropertyValue.Value <<= maLockEntries;
+ maPropStatProperties.push_back(aDAVPropertyValue);
+ }
+ break;
+ }
+ case WebDAVName_lockentry:
+ {
+ // lockentry end
+ if(hasParent(WebDAVName_supportedlock) && (mbLockScopeSet && mbLockTypeSet))
+ {
+ const sal_Int32 nLength(maLockEntries.getLength());
+ ucb::LockEntry aEntry;
+
+ aEntry.Scope = maLockScope;
+ aEntry.Type = maLockType;
+ maLockEntries.realloc(nLength + 1);
+ maLockEntries.getArray()[nLength] = aEntry;
+ }
+ break;
+ }
+ case WebDAVName_owner:
+ {
+ maLock.Owner <<= mpContext->getWhiteSpace();
+ break;
+ }
+ case WebDAVName_timeout:
+ {
+ const OUString sTimeout(mpContext->getWhiteSpace());
+ if (sTimeout == "Infinite")
+ maLock.Timeout = -1;
+ else if (sTimeout.startsWith("Second-"))
+ maLock.Timeout = o3tl::toInt64(sTimeout.subView(7));
+ break;
+ }
+ case WebDAVName_locktoken:
+ {
+ const OUString sLockToken(maHref);
+ SAL_WARN_IF(!sLockToken.startsWith("opaquelocktoken:"), "ucb.ucp.webdav",
+ "Parser error: wrong 'locktoken' value.");
+ const sal_Int32 nLength(maLock.LockTokens.getLength());
+ maLock.LockTokens.realloc(nLength+1);
+ maLock.LockTokens.getArray()[nLength] = sLockToken;
+ break;
+ }
+ case WebDAVName_exclusive:
+ {
+ // exclusive lockscope end
+ if(hasParent(WebDAVName_lockscope))
+ {
+ maLockScope = ucb::LockScope_EXCLUSIVE;
+ mbLockScopeSet = true;
+ }
+ break;
+ }
+ case WebDAVName_shared:
+ {
+ // shared lockscope end
+ if(hasParent(WebDAVName_lockscope))
+ {
+ maLockScope = ucb::LockScope_SHARED;
+ mbLockScopeSet = true;
+ }
+ break;
+ }
+ case WebDAVName_write:
+ {
+ // write locktype end
+ if(hasParent(WebDAVName_locktype))
+ {
+ maLockType = ucb::LockType_WRITE;
+ mbLockTypeSet = true;
+ }
+ break;
+ }
+ case WebDAVName_depth:
+ {
+ OUString const chars(mpContext->getWhiteSpace());
+ if (chars == "0")
+ {
+ maLock.Depth = ucb::LockDepth_ZERO;
+ }
+ else if (chars == "1")
+ {
+ maLock.Depth = ucb::LockDepth_ONE;
+ }
+ else if (chars == "infinity")
+ {
+ maLock.Depth = ucb::LockDepth_INFINITY;
+ }
+ break;
+ }
+ case WebDAVName_activelock:
+ {
+ maLock.Type = maLockType;
+ maLock.Scope = maLockScope;
+ maResult_Lock.push_back(maLock);
+ break;
+ }
+ case WebDAVName_lockdiscovery:
+ {
+ // lockdiscovery may be requested via PROPFIND,
+ // in addition to LOCK! so return it 2 ways
+ if (isCollectingProperties())
+ {
+ http_dav_ucp::DAVPropertyValue aDAVPropertyValue;
+
+ aDAVPropertyValue.Name = "DAV:lockdiscovery";
+ aDAVPropertyValue.Value <<= ::comphelper::containerToSequence(maResult_Lock);
+ maPropStatProperties.push_back(aDAVPropertyValue);
+ }
+ break;
+ }
+ case WebDAVName_propstat:
+ {
+ // propstat end, check status
+ if(maStatus.getLength())
+ {
+ if(maStatus == "HTTP/1.1 200 OK")
+ {
+ if(isCollectingProperties())
+ {
+ if(!maPropStatProperties.empty())
+ {
+ // append to maResponseProperties if okay
+ maResponseProperties.insert(maResponseProperties.end(), maPropStatProperties.begin(), maPropStatProperties.end());
+ }
+ }
+ else
+ {
+ if(!maPropStatNames.empty())
+ {
+ // when collecting properties append to
+ maResponseNames.insert(maResponseNames.end(), maPropStatNames.begin(), maPropStatNames.end());
+ }
+ }
+ }
+ }
+ break;
+ }
+ case WebDAVName_response:
+ {
+ // response end
+ if(maHref.getLength())
+ {
+ if(isCollectingProperties())
+ {
+ // create DAVResource when we have content
+ if(!maResponseProperties.empty())
+ {
+ http_dav_ucp::DAVResource aDAVResource;
+
+ aDAVResource.uri = maHref;
+ aDAVResource.properties = maResponseProperties;
+ maResult_PropFind.push_back(aDAVResource);
+ }
+ }
+ else
+ {
+ // when collecting properties add them to result when there are some
+ if(!maResponseNames.empty())
+ {
+ http_dav_ucp::DAVResourceInfo aDAVResourceInfo;
+
+ aDAVResourceInfo.properties = maResponseNames;
+ maResult_PropName.push_back(aDAVResourceInfo);
+ }
+ }
+ }
+ break;
+ }
+ }
+ break;
+ }
+ case WebDAVNamespace_ucb_openoffice_org_dav_props:
+ {
+ switch(mpContext->getWebDAVName())
+ {
+ case WebDAVName_type:
+ {
+ m_UCBType = mpContext->getWhiteSpace();
+ break;
+ }
+ case WebDAVName_value:
+ {
+ m_UCBValue = mpContext->getWhiteSpace();
+ break;
+ }
+ case WebDAVName_ucbprop:
+ {
+ if (!m_UCBType.isEmpty()
+ && isCollectingProperties())
+ {
+ http_dav_ucp::DAVPropertyValue aDAVPropertyValue;
+ aDAVPropertyValue.Name = MakePropertyName(*mpContext->getParent());
+ if (UCBDeadPropertyValue::createFromXML(m_UCBType, m_UCBValue, aDAVPropertyValue.Value))
+ {
+ maPropStatProperties.push_back(aDAVPropertyValue);
+ }
+ else
+ {
+ SAL_INFO("ucb.ucp.webdav.curl", "cannot parse property value");
+ }
+ }
+ m_UCBType.clear();
+ m_UCBValue.clear();
+ break;
+ }
+ default:
+ break;
+ }
+ break;
+ }
+ }
+ }
+
+ // destroy last context (pop)
+ pop_context();
+ }
+ }
+
+ void SAL_CALL WebDAVResponseParser::characters( const OUString& aChars )
+ {
+ // collect whitespace over evtl. several calls in mpContext
+ SAL_WARN_IF(!mpContext, "ucb.ucp.webdav", "Parser characters without content (!)");
+ const sal_Int32 nLen(aChars.getLength());
+
+ if(mpContext && nLen)
+ {
+ // remove leading/trailing blanks and CRLF
+ const OUString aTrimmedChars(aChars.trim());
+
+ if(aTrimmedChars.getLength())
+ {
+ OUString aNew(mpContext->getWhiteSpace());
+
+ if(aNew.getLength())
+ {
+ // add one char when appending (see html1.1 spec)
+ aNew += " ";
+ }
+
+ aNew += aTrimmedChars;
+ mpContext->setWhiteSpace(aNew);
+ }
+ }
+ }
+
+ void SAL_CALL WebDAVResponseParser::ignorableWhitespace( const OUString& /*aWhitespaces*/ )
+ {
+ }
+
+ void SAL_CALL WebDAVResponseParser::processingInstruction( const OUString& /*aTarget*/, const OUString& /*aData*/ )
+ {
+ }
+
+ void SAL_CALL WebDAVResponseParser::setDocumentLocator( const uno::Reference< xml::sax::XLocator >& /*xLocator*/ )
+ {
+ }
+} // end of anonymous namespace
+
+
+// wrapper for various calls to the parser
+
+namespace
+{
+ template<typename T>
+ void parseWebDAVResponse(
+ const uno::Reference< io::XInputStream >& xInputStream,
+ std::vector< T >& rResult,
+ WebDAVResponseParserMode eWebDAVResponseParserMode,
+ std::vector<T> const & (WebDAVResponseParser::* fn)() const)
+ {
+ if(xInputStream.is())
+ {
+ try
+ {
+ // prepare ParserInputSource
+ xml::sax::InputSource myInputSource;
+ myInputSource.aInputStream = xInputStream;
+
+ // get parser
+ uno::Reference< xml::sax::XParser > xParser = xml::sax::Parser::create(
+ comphelper::getProcessComponentContext() );
+
+ // create parser; connect parser and filter
+ rtl::Reference<WebDAVResponseParser> const pWebDAVResponseParser(
+ new WebDAVResponseParser(eWebDAVResponseParserMode));
+ uno::Reference< xml::sax::XDocumentHandler > xWebDAVHdl(pWebDAVResponseParser);
+ xParser->setDocumentHandler(xWebDAVHdl);
+
+ // finally, parse the stream
+ xParser->parseStream(myInputSource);
+
+ // get result
+ rResult = (pWebDAVResponseParser.get()->*fn)();
+ }
+ catch(uno::Exception&)
+ {
+ SAL_WARN("ucb.ucp.webdav", "WebDAV Parse error (!)");
+ }
+ }
+ }
+} // end of anonymous namespace
+
+
+// helper to parse a XML WebDAV response
+
+namespace http_dav_ucp
+{
+ std::vector< ucb::Lock > parseWebDAVLockResponse(const uno::Reference< io::XInputStream >& xInputStream)
+ {
+ std::vector< ucb::Lock > aResult;
+ parseWebDAVResponse< ucb::Lock >(xInputStream, aResult, WebDAVResponseParserMode_Lock, &WebDAVResponseParser::getResult_Lock);
+ return aResult;
+ }
+
+ std::vector< DAVResource > parseWebDAVPropFindResponse(const uno::Reference< io::XInputStream >& xInputStream)
+ {
+ std::vector< DAVResource > aResult;
+ parseWebDAVResponse< DAVResource >(xInputStream, aResult, WebDAVResponseParserMode_PropFind, &WebDAVResponseParser::getResult_PropFind);
+ return aResult;
+ }
+
+ std::vector< DAVResourceInfo > parseWebDAVPropNameResponse(const uno::Reference< io::XInputStream >& xInputStream)
+ {
+ std::vector< DAVResourceInfo > aResult;
+ parseWebDAVResponse< DAVResourceInfo >(xInputStream, aResult, WebDAVResponseParserMode_PropName, &WebDAVResponseParser::getResult_PropName);
+ return aResult;
+ }
+} // namespace http_dav_ucp
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/ucb/source/ucp/webdav-curl/webdavresponseparser.hxx b/ucb/source/ucp/webdav-curl/webdavresponseparser.hxx
new file mode 100644
index 0000000000..c62cb3205d
--- /dev/null
+++ b/ucb/source/ucp/webdav-curl/webdavresponseparser.hxx
@@ -0,0 +1,38 @@
+/* -*- 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:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#pragma once
+
+#include <com/sun/star/uno/Reference.hxx>
+#include <com/sun/star/io/XInputStream.hpp>
+#include <com/sun/star/ucb/Lock.hpp>
+#include "DAVResource.hxx"
+#include <vector>
+
+namespace http_dav_ucp
+{
+std::vector<css::ucb::Lock>
+parseWebDAVLockResponse(const css::uno::Reference<css::io::XInputStream>& xInputStream);
+std::vector<DAVResource>
+parseWebDAVPropFindResponse(const css::uno::Reference<css::io::XInputStream>& xInputStream);
+std::vector<DAVResourceInfo>
+parseWebDAVPropNameResponse(const css::uno::Reference<css::io::XInputStream>& xInputStream);
+} // namespace http_dav_ucp
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/ucb/source/ucp/webdav-curl/webdavresultset.cxx b/ucb/source/ucp/webdav-curl/webdavresultset.cxx
new file mode 100644
index 0000000000..e67dd1558d
--- /dev/null
+++ b/ucb/source/ucp/webdav-curl/webdavresultset.cxx
@@ -0,0 +1,76 @@
+/* -*- 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:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+/**************************************************************************
+ TODO
+ **************************************************************************
+
+ - This implementation is not a dynamic result set!!! It only implements
+ the necessary interfaces, but never recognizes/notifies changes!!!
+
+ *************************************************************************/
+#include "webdavresultset.hxx"
+
+using namespace com::sun::star;
+using namespace http_dav_ucp;
+
+
+// DynamicResultSet Implementation.
+
+
+DynamicResultSet::DynamicResultSet(
+ const uno::Reference< uno::XComponentContext >& rxContext,
+ const rtl::Reference< Content >& rxContent,
+ const ucb::OpenCommandArgument2& rCommand,
+ const uno::Reference< ucb::XCommandEnvironment >& rxEnv )
+: ResultSetImplHelper( rxContext, rCommand ),
+ m_xContent( rxContent ),
+ m_xEnv( rxEnv )
+{
+}
+
+
+// Non-interface methods.
+
+
+void DynamicResultSet::initStatic()
+{
+ m_xResultSet1
+ = new ::ucbhelper::ResultSet( m_xContext,
+ m_aCommand.Properties,
+ new DataSupplier( m_xContext,
+ m_xContent,
+ m_aCommand.Mode ),
+ m_xEnv );
+}
+
+
+void DynamicResultSet::initDynamic()
+{
+ m_xResultSet1
+ = new ::ucbhelper::ResultSet( m_xContext,
+ m_aCommand.Properties,
+ new DataSupplier( m_xContext,
+ m_xContent,
+ m_aCommand.Mode ),
+ m_xEnv );
+ m_xResultSet2 = m_xResultSet1;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/ucb/source/ucp/webdav-curl/webdavresultset.hxx b/ucb/source/ucp/webdav-curl/webdavresultset.hxx
new file mode 100644
index 0000000000..0a52d7982a
--- /dev/null
+++ b/ucb/source/ucp/webdav-curl/webdavresultset.hxx
@@ -0,0 +1,48 @@
+/* -*- 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:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+
+#pragma once
+
+#include <rtl/ref.hxx>
+#include <ucbhelper/resultsethelper.hxx>
+#include "webdavcontent.hxx"
+#include "webdavdatasupplier.hxx"
+
+namespace http_dav_ucp {
+
+class DynamicResultSet : public ::ucbhelper::ResultSetImplHelper
+{
+ rtl::Reference< Content > m_xContent;
+ 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,
+ const rtl::Reference< Content >& rxContent,
+ const css::ucb::OpenCommandArgument2& rCommand,
+ const css::uno::Reference< css::ucb::XCommandEnvironment >& rxEnv );
+};
+
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */