diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-15 05:54:39 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-15 05:54:39 +0000 |
commit | 267c6f2ac71f92999e969232431ba04678e7437e (patch) | |
tree | 358c9467650e1d0a1d7227a21dac2e3d08b622b2 /ucb/source/ucp | |
parent | Initial commit. (diff) | |
download | libreoffice-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')
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( "&" ); + break; + + case '"': + rBuffer.append( """ ); + break; + + case '\'': + rBuffer.append( "'" ); + break; + + case '<': + rBuffer.append( "<" ); + break; + + case '>': + rBuffer.append( ">" ); + 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: */ |