diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-27 16:51:28 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-27 16:51:28 +0000 |
commit | 940b4d1848e8c70ab7642901a68594e8016caffc (patch) | |
tree | eb72f344ee6c3d9b80a7ecc079ea79e9fba8676d /ucb/source/ucp | |
parent | Initial commit. (diff) | |
download | libreoffice-940b4d1848e8c70ab7642901a68594e8016caffc.tar.xz libreoffice-940b4d1848e8c70ab7642901a68594e8016caffc.zip |
Adding upstream version 1:7.0.4.upstream/1%7.0.4upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'ucb/source/ucp')
293 files changed, 78828 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 000000000..c6d5b28bc --- /dev/null +++ b/ucb/source/ucp/cmis/auth_provider.cxx @@ -0,0 +1,166 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.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) string( OUStringToOString( s, RTL_TEXTENCODING_UTF8 ).getStr() ) +#define STD_TO_OUSTR( str ) OUString( str.c_str(), str.length( ), RTL_TEXTENCODING_UTF8 ) + +#include <com/sun/star/task/XInteractionHandler.hpp> + +#include <ucbhelper/simpleauthenticationrequest.hxx> +#include <ucbhelper/authenticationfallback.hxx> + +#include "auth_provider.hxx" + +using namespace com::sun::star; +using namespace std; + +namespace cmis +{ + bool AuthProvider::authenticationQuery( string& username, 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.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() ) + { + const rtl::Reference< + ucbhelper::InteractionSupplyAuthentication > & xSupp + = xRequest->getAuthenticationSupplier(); + + username = OUSTR_TO_STDSTR( xSupp->getUserName() ); + password = OUSTR_TO_STDSTR( xSupp->getPassword() ); + + 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::onedriveAuthCodeFallback( const char* url, + const char* /*username*/, + const char* /*password*/ ) + { + OUString instructions = "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" + "https://login.live.com/oauth20_desktop.srf?code=YOUR_CODE&lc=1033"; + 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 ( + instructions, url_oustr ); + + xIH->handle( xRequest.get() ); + + 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( "" ); + } + + char* AuthProvider::gdriveAuthCodeFallback( const char* /*url*/, + const char* /*username*/, + const char* /*password*/ ) + { + OUString instructions = "PIN:"; + 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 ( + instructions, "" ); + + xIH->handle( xRequest.get() ); + + 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 000000000..24430401d --- /dev/null +++ b/ucb/source/ucp/cmis/auth_provider.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/. + */ +#ifndef INCLUDED_UCB_SOURCE_UCP_CMIS_AUTH_PROVIDER_HXX +#define INCLUDED_UCB_SOURCE_UCP_CMIS_AUTH_PROVIDER_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 + +#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, + const OUString& sUrl, + const OUString& sBindingUrl ): + m_xEnv( xEnv ), m_sUrl( sUrl ), m_sBindingUrl( sBindingUrl ) { } + + bool authenticationQuery( std::string& username, std::string& password ) override; + + static char* onedriveAuthCodeFallback( const char* url, + const char* /*username*/, + const char* /*password*/ ); + + static char* gdriveAuthCodeFallback( 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(); + + }; +} + +#endif + +/* 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 000000000..cbb084886 --- /dev/null +++ b/ucb/source/ucp/cmis/certvalidation_handler.cxx @@ -0,0 +1,127 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed 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 std; +using namespace com::sun::star; + +namespace cmis +{ + bool CertValidationHandler::validateCertificate( vector< 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() ); + + vector< string >::iterator pIt = aCertificates.begin(); + 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.get() ); + 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 000000000..7aa7f42be --- /dev/null +++ b/ucb/source/ucp/cmis/certvalidation_handler.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: + * + */ +#ifndef INCLUDED_UCB_SOURCE_UCP_CMIS_CERTVALIDATION_HANDLER_HXX +#define INCLUDED_UCB_SOURCE_UCP_CMIS_CERTVALIDATION_HANDLER_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 + +#include <com/sun/star/ucb/XCommandEnvironment.hpp> + +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, + const OUString& sHostname ): + m_xEnv( xEnv ), m_xContext( xContext ), m_sHostname( sHostname ) { } + + bool validateCertificate( std::vector< std::string > certificates ) override; + }; +} + +#endif + +/* 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 000000000..210522a5f --- /dev/null +++ b/ucb/source/ucp/cmis/children_provider.hxx @@ -0,0 +1,29 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.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_CMIS_CHILDREN_PROVIDER_HXX +#define INCLUDED_UCB_SOURCE_UCP_CMIS_CHILDREN_PROVIDER_HXX + +#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; + }; +} + +#endif + +/* 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 000000000..5a345e6fb --- /dev/null +++ b/ucb/source/ucp/cmis/cmis_content.cxx @@ -0,0 +1,2072 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.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 <cstdio> + +#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 <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 "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) string( OUStringToOString( s, RTL_TEXTENCODING_UTF8 ).getStr() ) +#define STD_TO_OUSTR( str ) OUString( str.c_str(), str.length( ), RTL_TEXTENCODING_UTF8 ) + +using namespace com::sun::star; +using namespace std; + +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 long ticks = boostTime.time_of_day().fractional_seconds(); + 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: + { + vector< string > 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: + { + vector< long > 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: + { + vector< double > aCmisDoubles = pProperty->getDoubles( ); + uno::Sequence< double > aDoubles = comphelper::containerToSequence(aCmisDoubles); + aValue <<= aDoubles; + } + break; + case libcmis::PropertyType::Bool: + { + vector< bool > 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: + { + vector< boost::posix_time::ptime > 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(seqValue.begin(), seqValue.end(), 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(seqValue.begin(), seqValue.end(), std::back_inserter(values), + [](const bool nValue) -> std::string { return OUSTR_TO_STDSTR( OUString::boolean( nValue ) ); }); + type = libcmis::PropertyType::Bool; + } + else if ( prop.Type == CMIS_TYPE_INTEGER ) + { + uno::Sequence< sal_Int64 > seqValue; + value >>= seqValue; + std::transform(seqValue.begin(), seqValue.end(), std::back_inserter(values), + [](const sal_Int64 nValue) -> std::string { return OUSTR_TO_STDSTR( OUString::number( nValue ) ); }); + type = libcmis::PropertyType::Integer; + } + else if ( prop.Type == CMIS_TYPE_DECIMAL ) + { + uno::Sequence< double > seqValue; + value >>= seqValue; + std::transform(seqValue.begin(), seqValue.end(), std::back_inserter(values), + [](const double fValue) -> std::string { return OUSTR_TO_STDSTR( OUString::number( fValue ) ); }); + type = libcmis::PropertyType::Decimal; + } + else if ( prop.Type == CMIS_TYPE_DATETIME ) + { + uno::Sequence< util::DateTime > seqValue; + value >>= seqValue; + std::transform(seqValue.begin(), seqValue.end(), std::back_inserter(values), + [](const util::DateTime& rValue) -> std::string { + OUStringBuffer aBuffer; + ::sax::Converter::convertDateTime( aBuffer, rValue, nullptr ); + return OUSTR_TO_STDSTR( aBuffer.makeStringAndClear( ) ); + }); + 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, values ) ); + + return property; + } + + uno::Sequence< uno::Any > generateErrorArguments( const cmis::URL & rURL ) + { + uno::Sequence< uno::Any > aArguments(3); + + size_t i = 0; + aArguments[i++] <<= beans::PropertyValue( + "Binding URL", + - 1, + uno::makeAny( rURL.getBindingUrl() ), + beans::PropertyState_DIRECT_VALUE ); + + aArguments[i++] <<= beans::PropertyValue( + "Username", + -1, + uno::makeAny( rURL.getUsername() ), + beans::PropertyState_DIRECT_VALUE ); + + aArguments[i++] <<= beans::PropertyValue( + "Repository Id", + -1, + uno::makeAny( 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 const & pObject ) + : ContentImplHelper( rxContext, pProvider, Identifier ), + m_pProvider( pProvider ), + m_pSession( nullptr ), + m_pObject( 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 ucbhelper::InternetProxyServer& rProxy = aProxyDecider.getProxy( + INetURLObject::GetScheme( aBindingUrl.GetProtocol( ) ), aBindingUrl.GetHost(), aBindingUrl.GetPort() ); + OUString sProxy = rProxy.aName; + if ( rProxy.nPort > 0 ) + sProxy += ":" + OUString::number( rProxy.nPort ); + libcmis::SessionFactory::setProxySettings( OUSTR_TO_STDSTR( sProxy ), string(), string(), 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 ); + + // Get the auth credentials + AuthProvider aAuthProvider(xEnv, m_xIdentifier->getContentIdentifier(), m_aURL.getBindingUrl()); + AuthProvider::setXEnv( xEnv ); + + string rUsername = OUSTR_TO_STDSTR( m_aURL.getUsername( ) ); + string rPassword = OUSTR_TO_STDSTR( m_aURL.getPassword( ) ); + + bool bIsDone = false; + + while ( !bIsDone ) + { + if (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 ) + { + libcmis::SessionFactory::setOAuth2AuthCodeProvider(AuthProvider::gdriveAuthCodeFallback); + oauth2Data.reset( new 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.reset( new 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::onedriveAuthCodeFallback); + oauth2Data.reset( new 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); + } + + bIsDone = true; + } + catch( const libcmis::Exception & e ) + { + if ( e.getType() != "permissionDenied" ) + 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 ) + { + 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 ) + { + map< string, libcmis::PropertyPtr >& aProperties = pParent->getProperties( ); + map< string, libcmis::PropertyPtr >::iterator it = aProperties.find( "cmis:allowedChildObjectTypeIds" ); + if ( it != aProperties.end( ) ) + { + libcmis::PropertyPtr pProperty = it->second; + if ( pProperty ) + { + vector< 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& ) + { + 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 ); + 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) + { + 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::makeAny( lang::IllegalArgumentException( + "Wrong argument type!", + static_cast< cppu::OWeakObject * >( this ), -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; + map< 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<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() ) + { + map< string, libcmis::PropertyPtr >::iterator it = m_pObjectProps.find( "cmis:name" ); + if ( it != m_pObjectProps.end( ) ) + { + vector< 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() ) + { + map< string, libcmis::PropertyPtr >::iterator it = m_pObjectProps.find( "cmis:objectId" ); + if ( it != m_pObjectProps.end( ) ) + { + vector< 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::makeAny( 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 ); + map< 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 ) + { + string 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::makeAny( 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 uno::Reference< sdbc::XRow >( xRow.get() ); + } + + 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( 1 ); + aArgs[ 0 ] <<= m_xIdentifier->getContentIdentifier(); + uno::Any aErr = uno::makeAny( + ucb::InteractiveAugmentedIOException(OUString(), static_cast< cppu::OWeakObject * >( this ), + 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::makeAny ( ucb::UnsupportedOpenModeException + ( OUString(), static_cast< cppu::OWeakObject * >( this ), + 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::makeAny (ucb::UnsupportedDataSinkException + ( OUString(), static_cast< cppu::OWeakObject * >( this ), + 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< ostream > pOut( new ostringstream ( ios_base::binary | ios_base::in | ios_base::out ) ); + uno::Reference < io::XOutputStream > xOutput = new StdOutputStream( pOut ); + copyData( xIn, xOutput ); + + map< 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 ); + vector< string > aPaths = pDoc->getPaths( ); + if ( !aPaths.empty() ) + { + string sPath = aPaths.front( ); + aCmisUrl.setObjectPath( STD_TO_OUSTR( sPath ) ); + } + else + { + // We may have unfiled document depending on the server, those + // won't have any path, use their ID instead + string sId = pDoc->getId( ); + aCmisUrl.setObjectId( STD_TO_OUSTR( sId ) ); + } + 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 ); + vector< string > aPaths = pPwc->getPaths( ); + if ( !aPaths.empty() ) + { + string 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 + string 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) + vector< libcmis::DocumentPtr > aVersions = pPwc->getAllVersions( ); + for ( const auto& rVersion : aVersions ) + { + libcmis::DocumentPtr pVersion = rVersion; + map< string, libcmis::PropertyPtr > aProps = pVersion->getProperties( ); + bool bIsLatestVersion = false; + map< string, libcmis::PropertyPtr >::iterator propIt = aProps.find( 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 ); + vector< string > aPaths = pVersion->getPaths( ); + if ( !aPaths.empty() ) + { + string 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 + string 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" ); + } + vector< libcmis::DocumentPtr > aCmisVersions = pDoc->getAllVersions( ); + uno::Sequence< document::CmisVersion > aVersions( aCmisVersions.size( ) ); + int i = 0; + for ( const auto& rVersion : aCmisVersions ) + { + libcmis::DocumentPtr pVersion = rVersion; + aVersions[i].Id = STD_TO_OUSTR( pVersion->getId( ) ); + aVersions[i].Author = STD_TO_OUSTR( pVersion->getCreatedBy( ) ); + aVersions[i].TimeStamp = lcl_boostToUnoTime( pVersion->getLastModificationDate( ) ); + aVersions[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::makeAny( + ucb::InteractiveBadTransferURLException( + "Unsupported URL scheme!", + static_cast< cppu::OWeakObject * >( this ) ) ), + xEnv ); + } + } + + SAL_INFO( "ucb.ucp.cmis", "TODO - Content::transfer()" ); + } + + void Content::insert( const uno::Reference< io::XInputStream > & xInputStream, + bool bReplaceExisting, const OUString& rMimeType, + const uno::Reference< ucb::XCommandEnvironment >& xEnv ) + { + if ( !xInputStream.is() ) + { + ucbhelper::cancelCommandExecution( uno::makeAny + ( ucb::MissingInputStreamException + ( OUString(), static_cast< cppu::OWeakObject * >( this ) ) ), + xEnv ); + } + + // For transient content, the URL is the one of the parent + if ( m_bTransient ) + { + 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 ) + { + libcmis::ObjectPtr object; + map< string, libcmis::PropertyPtr >::iterator it = m_pObjectProps.find( "cmis:name" ); + if ( it == m_pObjectProps.end( ) ) + { + ucbhelper::cancelCommandExecution( uno::makeAny + ( uno::RuntimeException( "Missing name property", + static_cast< cppu::OWeakObject * >( this ) ) ), + xEnv ); + } + string newName = it->second->getStrings( ).front( ); + string 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::makeAny + ( uno::RuntimeException( "Can't change a folder into a document and vice-versa.", + static_cast< cppu::OWeakObject * >( this ) ) ), + 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< ostream > pOut( new ostringstream ( ios_base::binary | ios_base::in | ios_base::out ) ); + uno::Reference < io::XOutputStream > xOutput = new StdOutputStream( pOut ); + copyData( xInputStream, xOutput ); + try + { + document->setContentStream( pOut, OUSTR_TO_STDSTR( rMimeType ), string( ), bReplaceExisting ); + } + catch ( const libcmis::Exception& ) + { + ucbhelper::cancelCommandExecution( uno::makeAny + ( uno::RuntimeException( "Error when setting document content", + static_cast< cppu::OWeakObject * >( this ) ) ), + 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::makeAny + ( uno::RuntimeException( "Error when creating folder", + static_cast< cppu::OWeakObject * >( this ) ) ), + xEnv ); + } + } + else + { + boost::shared_ptr< ostream > pOut( new ostringstream ( ios_base::binary | ios_base::in | ios_base::out ) ); + uno::Reference < io::XOutputStream > xOutput = new StdOutputStream( pOut ); + copyData( xInputStream, xOutput ); + try + { + pFolder->createDocument( m_pObjectProps, pOut, OUSTR_TO_STDSTR( rMimeType ), string() ); + sNewPath = STD_TO_OUSTR( newPath ); + } + catch ( const libcmis::Exception& ) + { + ucbhelper::cancelCommandExecution( uno::makeAny + ( uno::RuntimeException( "Error when creating document", + static_cast< cppu::OWeakObject * >( this ) ) ), + xEnv ); + } + } + } + + if ( !sNewPath.isEmpty( ) || !m_sObjectId.isEmpty( ) ) + { + // 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 ); + + 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!", + static_cast< cppu::OWeakObject* >( this ) ); + aRet[ n ] <<= e; + } + else if ( rValue.Name == "Title" ) + { + OUString aNewTitle; + if (!( rValue.Value >>= aNewTitle )) + { + aRet[ n ] <<= beans::IllegalTypeException + ( "Property value has wrong type!", + static_cast< cppu::OWeakObject * >( this ) ); + continue; + } + + if ( aNewTitle.getLength() <= 0 ) + { + aRet[ n ] <<= lang::IllegalArgumentException + ( "Empty title not allowed!", + static_cast< cppu::OWeakObject * >( this ), -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!", + static_cast< cppu::OWeakObject* >( this ) ); + aRet[ 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< 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() throw() + { + ContentImplHelper::acquire(); + } + + void SAL_CALL Content::release() throw() + { + 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 + { + sRet = isFolder( uno::Reference< ucb::XCommandEnvironment >() ) + ? OUStringLiteral(CMIS_FOLDER_TYPE) + : OUStringLiteral(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::makeAny( ucb::UnsupportedCommandException + ( OUString(), + static_cast< cppu::OWeakObject * >( this ) ) ), + 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 ) ) + { + uno::Sequence< ucb::ContentInfo > seq(2); + + // Minimum set of props we really need + uno::Sequence< beans::Property > props( 1 ); + props[0] = beans::Property( + "Title", + -1, + cppu::UnoType<OUString>::get(), + beans::PropertyAttribute::MAYBEVOID | beans::PropertyAttribute::BOUND ); + + // file + seq[0].Type = CMIS_FILE_TYPE; + seq[0].Attributes = ( ucb::ContentInfoAttribute::INSERT_WITH_INPUTSTREAM | + ucb::ContentInfoAttribute::KIND_DOCUMENT ); + seq[0].Properties = props; + + // folder + seq[1].Type = CMIS_FOLDER_TYPE; + seq[1].Attributes = ucb::ContentInfoAttribute::KIND_FOLDER; + seq[1].Properties = props; + + return seq; + } + } + 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 uno::Sequence< ucb::ContentInfo >(); + } + + 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 + { + 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( ) ) + { + map< string, libcmis::PropertyPtr >::iterator propIt = m_pObjectProps.find(rName); + vector< string > values; + values.push_back(rValue); + + if ( propIt == m_pObjectProps.end( ) && getObjectType( xEnv ).get( ) ) + { + map< string, libcmis::PropertyTypePtr > propsTypes = getObjectType( xEnv )->getPropertiesTypes( ); + map< string, libcmis::PropertyTypePtr >::iterator typeIt = propsTypes.find(rName); + + if ( typeIt != propsTypes.end( ) ) + { + libcmis::PropertyTypePtr propType = typeIt->second; + libcmis::PropertyPtr property( new libcmis::Property( propType, values ) ); + m_pObjectProps.insert(pair< string, libcmis::PropertyPtr >(rName, property)); + } + } + else if ( propIt != m_pObjectProps.end( ) ) + { + propIt->second->setValues( values ); + } + } + } +} + +/* 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 000000000..61c45434e --- /dev/null +++ b/ucb/source/ucp/cmis/cmis_content.hxx @@ -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/. + */ + +#ifndef INCLUDED_UCB_SOURCE_UCP_CMIS_CMIS_CONTENT_HXX +#define INCLUDED_UCB_SOURCE_UCP_CMIS_CMIS_CONTENT_HXX + +#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 + +#include <list> + +namespace com::sun::star { + namespace beans { + struct Property; + struct PropertyValue; + } + namespace sdbc { + class XRow; + } +} +namespace ucbhelper +{ + class Content; +} + + +namespace cmis +{ + +#define CMIS_FILE_TYPE "application/vnd.libreoffice.cmis-file" +#define CMIS_FOLDER_TYPE "application/vnd.libreoffice.cmis-folder" + +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, const OUString & 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 const & 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() + throw() override; + virtual void SAL_CALL release() + throw() 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 ); +}; + +} + +#endif + +/* 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 000000000..2a91770cd --- /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 000000000..f7cad8774 --- /dev/null +++ b/ucb/source/ucp/cmis/cmis_datasupplier.hxx @@ -0,0 +1,73 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.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_CMIS_CMIS_DATASUPPLIER_HXX +#define INCLUDED_UCB_SOURCE_UCP_CMIS_CMIS_DATASUPPLIER_HXX + +#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 > const & xCnt ) : xContent( 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; + }; + +} + +#endif + +/* 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 000000000..0d6a45222 --- /dev/null +++ b/ucb/source/ucp/cmis/cmis_provider.cxx @@ -0,0 +1,163 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.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 <ucbhelper/contenthelper.hxx> +#include <ucbhelper/getcomponentcontext.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 ).get(); + 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() + throw() +{ + OWeakObject::acquire(); +} + +void SAL_CALL ContentProvider::release() + throw() +{ + 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 ); + +XSERVICEINFO_COMMOM_IMPL( ContentProvider, + "com.sun.star.comp.CmisContentProvider" ) +/// @throws css::uno::Exception +static css::uno::Reference< css::uno::XInterface > +ContentProvider_CreateInstance( const css::uno::Reference< css::lang::XMultiServiceFactory> & rSMgr ) +{ + css::lang::XServiceInfo* pX = new ContentProvider( ucbhelper::getComponentContext(rSMgr) ); + return css::uno::Reference< css::uno::XInterface >::query( pX ); +} + +css::uno::Sequence< OUString > +ContentProvider::getSupportedServiceNames_Static() +{ + css::uno::Sequence< OUString > aSNS { "com.sun.star.ucb.CmisContentProvider" }; + return aSNS; +} + +ONE_INSTANCE_SERVICE_FACTORY_IMPL( ContentProvider ); + +} + +extern "C" SAL_DLLPUBLIC_EXPORT void * ucpcmis1_component_getFactory( const char *pImplName, + void *pServiceManager, void * ) +{ + void * pRet = nullptr; + + uno::Reference< lang::XMultiServiceFactory > xSMgr + (static_cast< lang::XMultiServiceFactory * >( pServiceManager ) ); + uno::Reference< lang::XSingleServiceFactory > xFactory; + + if ( ::cmis::ContentProvider::getImplementationName_Static().equalsAscii( pImplName ) ) + xFactory = ::cmis::ContentProvider::createServiceFactory( xSMgr ); + + if ( xFactory.is() ) + { + xFactory->acquire(); + pRet = xFactory.get(); + } + + return pRet; +} + +/* 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 000000000..081c7f411 --- /dev/null +++ b/ucb/source/ucp/cmis/cmis_provider.hxx @@ -0,0 +1,66 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.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_CMIS_CMIS_PROVIDER_HXX +#define INCLUDED_UCB_SOURCE_UCP_CMIS_CMIS_PROVIDER_HXX + +#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() + throw() override; + virtual void SAL_CALL release() + throw() 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; + + static OUString getImplementationName_Static(); + static css::uno::Sequence< OUString > getSupportedServiceNames_Static(); + + static css::uno::Reference< css::lang::XSingleServiceFactory > + createServiceFactory( const css::uno::Reference< + css::lang::XMultiServiceFactory >& rxServiceMgr ); + + // 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 ); +}; + +} + +#endif + +/* 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 000000000..ead15fb1e --- /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 <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 ).getStr() ) +#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 > const & aRepos ) + : ContentImplHelper( rxContext, pProvider, Identifier ), + m_pProvider( pProvider ), + m_aURL( Identifier->getContentIdentifier( ) ), + m_sRepositoryId( ), + m_aRepositories( 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::makeAny( lang::IllegalArgumentException( + "Wrong argument type!", + static_cast< cppu::OWeakObject * >( this ), -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 uno::Reference< sdbc::XRow >( xRow.get() ); + } + + 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 ucbhelper::InternetProxyServer& rProxy = aProxyDecider.getProxy( + INetURLObject::GetScheme( aBindingUrl.GetProtocol( ) ), aBindingUrl.GetHost(), aBindingUrl.GetPort() ); + OUString sProxy = rProxy.aName; + if ( rProxy.nPort > 0 ) + sProxy += ":" + OUString::number( rProxy.nPort ); + libcmis::SessionFactory::setProxySettings( OUSTR_TO_STDSTR( sProxy ), std::string(), std::string(), std::string() ); + + if ( m_aRepositories.empty() ) + { + // 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::gdriveAuthCodeFallback ); + oauth2Data.reset( new 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.reset( new 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::onedriveAuthCodeFallback ); + oauth2Data.reset( new 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, 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 000000000..67548a542 --- /dev/null +++ b/ucb/source/ucp/cmis/cmis_repo_content.hxx @@ -0,0 +1,119 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.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_CMIS_CMIS_REPO_CONTENT_HXX +#define INCLUDED_UCB_SOURCE_UCP_CMIS_CMIS_REPO_CONTENT_HXX + +#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 +{ +#define CMIS_REPO_TYPE "application/vnd.libreoffice.cmis-repository" + +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 > const & 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; +}; + +} + +#endif + +/* 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 000000000..782953b65 --- /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 000000000..1393ac926 --- /dev/null +++ b/ucb/source/ucp/cmis/cmis_resultset.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/. + */ + +#ifndef INCLUDED_UCB_SOURCE_UCP_CMIS_CMIS_RESULTSET_HXX +#define INCLUDED_UCB_SOURCE_UCP_CMIS_CMIS_RESULTSET_HXX + +#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 ); + + }; +} + +#endif + +/* 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 000000000..b2af8f877 --- /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: + * + */ + +#ifndef INCLUDED_UCB_SOURCE_UCP_CMIS_CMIS_STRINGS_HXX +#define INCLUDED_UCB_SOURCE_UCP_CMIS_CMIS_STRINGS_HXX + +#define CMIS_TYPE_STRING "String" +#define CMIS_TYPE_INTEGER "Integer" +#define CMIS_TYPE_DECIMAL "Decimal" +#define CMIS_TYPE_DATETIME "Datetime" +#define CMIS_TYPE_BOOL "Bool" + +#endif +/* 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 000000000..ae2bce9dd --- /dev/null +++ b/ucb/source/ucp/cmis/cmis_url.cxx @@ -0,0 +1,113 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.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" + +using namespace std; + +namespace cmis +{ + URL::URL( OUString const & 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( ) + { + 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("/").append(rtl::Uri::encode( sSegment, + rtl_UriCharClassRelSegment, + rtl_UriEncodeKeepEscapes, + RTL_TEXTENCODING_UTF8 )); + } + } + while ( nPos != -1 ); + sUrl += sEncodedPath.makeStringAndClear(); + } 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 000000000..4df9c2831 --- /dev/null +++ b/ucb/source/ucp/cmis/cmis_url.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/. + */ +#ifndef INCLUDED_UCB_SOURCE_UCP_CMIS_CMIS_URL_HXX +#define INCLUDED_UCB_SOURCE_UCP_CMIS_CMIS_URL_HXX + +#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( OUString const & 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( ); + }; +} + +#endif + +/* 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 000000000..a85f77d4e --- /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 "std_inputstream.hxx" + +using namespace std; +using namespace com::sun::star; + +namespace cmis +{ + StdInputStream::StdInputStream( boost::shared_ptr< istream > const & pStream ) : + m_pStream( pStream ), + m_nLength( 0 ) + { + if (m_pStream) + { + streampos nInitPos = m_pStream->tellg( ); + m_pStream->seekg( 0, ios_base::end ); + streampos nEndPos = m_pStream->tellg( ); + m_pStream->seekg( nInitPos, 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( ) throw( ) + { + OWeakObject::acquire(); + } + + void SAL_CALL StdInputStream::release( ) throw( ) + { + OWeakObject::release(); + } + + sal_Int32 SAL_CALL StdInputStream::readBytes( uno::Sequence< sal_Int8 >& aData, sal_Int32 nBytesToRead ) + { + osl::MutexGuard 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 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 ) + { + osl::MutexGuard 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 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 ) + { + osl::MutexGuard aGuard( m_aMutex ); + + if (!m_pStream) + throw io::IOException( ); + + try + { + m_pStream->seekg( nBytesToSkip, ios_base::cur ); + } + catch ( const 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 ) + { + osl::MutexGuard aGuard( m_aMutex ); + + if ( location < 0 || location > m_nLength ) + throw lang::IllegalArgumentException( + "Location can't be negative or greater than the length", + static_cast< cppu::OWeakObject* >( this ), 0 ); + + if (!m_pStream) + throw io::IOException( ); + + try + { + m_pStream->clear( ); // may be needed to rewind the stream + m_pStream->seekg( location, ios_base::beg ); + } + catch ( const ios_base::failure& e ) + { + SAL_INFO( "ucb.ucp.cmis", "StdInputStream::readBytes() error: " << e.what() ); + throw io::IOException( ); + } + } + + sal_Int64 SAL_CALL StdInputStream::getPosition( ) + { + osl::MutexGuard 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 000000000..8d23de87f --- /dev/null +++ b/ucb/source/ucp/cmis/std_inputstream.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/. + */ + +#ifndef INCLUDED_UCB_SOURCE_UCP_CMIS_STD_INPUTSTREAM_HXX +#define INCLUDED_UCB_SOURCE_UCP_CMIS_STD_INPUTSTREAM_HXX + +#include <boost/shared_ptr.hpp> +#include <istream> + +#include <osl/mutex.hxx> +#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 > const & pStream ); + + virtual ~StdInputStream() override; + + virtual css::uno::Any SAL_CALL queryInterface ( const css::uno::Type& rType ) override; + + virtual void SAL_CALL acquire( ) throw ( ) override; + + virtual void SAL_CALL release( ) throw ( ) 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: + + osl::Mutex m_aMutex; + boost::shared_ptr< std::istream > m_pStream; + sal_Int64 m_nLength; + }; + +} + +#endif + +/* 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 000000000..becb8327d --- /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 "std_outputstream.hxx" + +using namespace std; +using namespace com::sun::star; + +namespace cmis +{ + StdOutputStream::StdOutputStream( boost::shared_ptr< ostream > const & pStream ) : + m_pStream( pStream ) + { + } + + StdOutputStream::~StdOutputStream() + { + if (m_pStream) + m_pStream->setstate( 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( ) throw( ) + { + OWeakObject::acquire(); + } + + void SAL_CALL StdOutputStream::release( ) throw( ) + { + OWeakObject::release(); + } + + void SAL_CALL StdOutputStream::writeBytes ( const uno::Sequence< sal_Int8 >& aData ) + { + osl::MutexGuard aGuard( m_aMutex ); + + if (!m_pStream) + throw io::IOException( ); + + try + { + m_pStream->write( reinterpret_cast< const char* >( aData.getConstArray( ) ), aData.getLength( ) ); + } + catch ( const ios_base::failure& e ) + { + SAL_INFO( "ucb.ucp.cmis", "Exception caught when calling write: " << e.what() ); + throw io::IOException( ); + } + } + + void SAL_CALL StdOutputStream::flush ( ) + { + osl::MutexGuard aGuard( m_aMutex ); + + if (!m_pStream) + throw io::IOException( ); + + try + { + m_pStream->flush( ); + } + catch ( const ios_base::failure& e ) + { + SAL_INFO( "ucb.ucp.cmis", "Exception caught when calling flush: " << e.what() ); + throw io::IOException( ); + } + } + + void SAL_CALL StdOutputStream::closeOutput ( ) + { + osl::MutexGuard aGuard( m_aMutex ); + + if (!m_pStream) + throw io::IOException( ); + + m_pStream->setstate( 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 000000000..3108a8cb9 --- /dev/null +++ b/ucb/source/ucp/cmis/std_outputstream.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/. + */ + +#ifndef INCLUDED_UCB_SOURCE_UCP_CMIS_STD_OUTPUTSTREAM_HXX +#define INCLUDED_UCB_SOURCE_UCP_CMIS_STD_OUTPUTSTREAM_HXX + +#include <boost/shared_ptr.hpp> +#include <ostream> + +#include <osl/mutex.hxx> +#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 > const & pStream ); + + virtual ~StdOutputStream( ) override; + + virtual css::uno::Any SAL_CALL queryInterface( const css::uno::Type& rType ) override; + + virtual void SAL_CALL acquire ( ) throw ( ) override; + + virtual void SAL_CALL release ( ) throw ( ) 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: + + osl::Mutex m_aMutex; + boost::shared_ptr< std::ostream > m_pStream; + }; +} + +#endif + +/* 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 000000000..5a32d9119 --- /dev/null +++ b/ucb/source/ucp/cmis/ucpcmis1.component @@ -0,0 +1,15 @@ +<?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@" + prefix="ucpcmis1" xmlns="http://openoffice.org/2010/uno-components"> + <implementation name="com.sun.star.comp.CmisContentProvider"> + <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 000000000..16bf38bbf --- /dev/null +++ b/ucb/source/ucp/expand/ucpexpand.cxx @@ -0,0 +1,235 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this 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 <osl/mutex.hxx> +#include <cppuhelper/compbase.hxx> +#include <cppuhelper/factory.hxx> +#include <cppuhelper/implementationentry.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 <tools/diagnose_ex.h> + +#define EXPAND_PROTOCOL "vnd.sun.star.expand" + + +using namespace ::com::sun::star; + +namespace +{ + +struct MutexHolder +{ + mutable ::osl::Mutex m_mutex; +}; + +typedef ::cppu::WeakComponentImplHelper< + lang::XServiceInfo, ucb::XContentProvider > t_impl_helper; + + +class ExpandContentProviderImpl : protected MutexHolder, 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; + virtual void SAL_CALL disposing() override; + +public: + explicit ExpandContentProviderImpl( + uno::Reference< uno::XComponentContext > const & xComponentContext ) + : t_impl_helper( m_mutex ), + 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 (rBHelper.bInDispose || rBHelper.bDisposed) + { + throw lang::DisposedException( + "expand content provider instance has " + "already been disposed!", + static_cast< OWeakObject * >( + const_cast< ExpandContentProviderImpl * >(this) ) ); + } +} + +void ExpandContentProviderImpl::disposing() +{ +} + + +uno::Reference< uno::XInterface > create( + uno::Reference< uno::XComponentContext > const & xComponentContext ) +{ + return static_cast< ::cppu::OWeakObject * >( + new ExpandContentProviderImpl( xComponentContext ) ); +} + + +OUString implName() +{ + return "com.sun.star.comp.ucb.ExpandContentProvider"; +} + + +uno::Sequence< OUString > supportedServices() +{ + return uno::Sequence< OUString > { + OUString("com.sun.star.ucb.ExpandContentProvider"), + OUString("com.sun.star.ucb.ContentProvider") + }; +} + +// XServiceInfo + +OUString ExpandContentProviderImpl::getImplementationName() +{ + check(); + return implName(); +} + + +uno::Sequence< OUString > ExpandContentProviderImpl::getSupportedServiceNames() +{ + check(); + return supportedServices(); +} + +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.startsWith(EXPAND_PROTOCOL ":")) + { + throw ucb::IllegalIdentifierException( + "expected protocol " EXPAND_PROTOCOL "!", + static_cast< OWeakObject * >( + const_cast< ExpandContentProviderImpl * >(this) ) ); + } + // cut protocol + OUString str( uri.copy( sizeof (EXPAND_PROTOCOL ":") -1 ) ); + // decode uric class chars + str = ::rtl::Uri::decode( + str, 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; + } +} + +static const ::cppu::ImplementationEntry s_entries [] = +{ + { + create, + implName, + supportedServices, + ::cppu::createSingleComponentFactory, + nullptr, 0 + }, + { nullptr, nullptr, nullptr, nullptr, nullptr, 0 } +}; + +} + +extern "C" +{ + +SAL_DLLPUBLIC_EXPORT void * ucpexpand1_component_getFactory( + const char * pImplName, + void * pServiceManager, + void * pRegistryKey ) +{ + return ::cppu::component_getFactoryHelper( + pImplName, pServiceManager, pRegistryKey, s_entries ); +} + +} + +/* 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 000000000..ba7c7e90e --- /dev/null +++ b/ucb/source/ucp/expand/ucpexpand1.component @@ -0,0 +1,25 @@ +<?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@" + prefix="ucpexpand1" xmlns="http://openoffice.org/2010/uno-components"> + <implementation name="com.sun.star.comp.ucb.ExpandContentProvider"> + <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 000000000..56d223146 --- /dev/null +++ b/ucb/source/ucp/ext/ucpext.component @@ -0,0 +1,25 @@ +<?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@" + prefix="ucpext" xmlns="http://openoffice.org/2010/uno-components"> + <implementation name="org.openoffice.comp.ucp.ext.ContentProvider"> + <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 000000000..44cc30f51 --- /dev/null +++ b/ucb/source/ucp/ext/ucpext_content.cxx @@ -0,0 +1,630 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this 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/CommandAbortedException.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 <ucbhelper/propertyvalueset.hxx> +#include <ucbhelper/cancelcommandexecution.hxx> +#include <ucbhelper/content.hxx> +#include <tools/diagnose_ex.h> +#include <rtl/ustrbuf.hxx> +#include <rtl/uri.hxx> +#include <sal/macros.h> +#include <sal/log.hxx> + +#include <algorithm> + + +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::makeAny; + 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( const OUString& i_rBaseURL, const OUString& i_rRelativeURL ) + { + ENSURE_OR_RETURN( !i_rBaseURL.isEmpty(), "illegal base URL", i_rRelativeURL ); + + OUStringBuffer aComposer( i_rBaseURL ); + if ( !i_rBaseURL.endsWith("/") ) + 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 ) + ,m_aIsFolder() + ,m_aContentType() + ,m_sExtensionId() + ,m_sPathIntoExtension() + { + const OUString sURL( getIdentifier()->getContentIdentifier() ); + if ( denotesRootContent( sURL ) ) + { + m_eExtContentType = E_ROOT; + } + else + { + const OUString sRelativeURL( sURL.copy( ContentProvider::getRootURL().getLength() ) ); + const sal_Int32 nSepPos = sRelativeURL.indexOf( '/' ); + if ( ( nSepPos == -1 ) || ( nSepPos == sRelativeURL.getLength() - 1 ) ) + { + m_eExtContentType = E_EXTENSION_ROOT; + } + else + { + m_eExtContentType = E_EXTENSION_CONTENT; + } + } + + if ( m_eExtContentType != E_ROOT ) + { + 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_rEvironment ) + { + Any aRet; + + if ( aCommand.Name == "getPropertyValues" ) + { + Sequence< Property > Properties; + if ( !( aCommand.Argument >>= Properties ) ) + { + ::ucbhelper::cancelCommandExecution( makeAny( IllegalArgumentException( + OUString(), *this, -1 ) ), + i_rEvironment ); + // unreachable + } + + aRet <<= getPropertyValues( Properties, i_rEvironment ); + } + else if ( aCommand.Name == "setPropertyValues" ) + { + Sequence< PropertyValue > aProperties; + if ( !( aCommand.Argument >>= aProperties ) ) + { + ::ucbhelper::cancelCommandExecution( makeAny( IllegalArgumentException( + OUString(), *this, -1 ) ), + i_rEvironment ); + // unreachable + } + + if ( !aProperties.hasElements() ) + { + ::ucbhelper::cancelCommandExecution( makeAny( IllegalArgumentException( + OUString(), *this, -1 ) ), + i_rEvironment ); + // unreachable + } + + aRet <<= setPropertyValues( aProperties ); + } + else if ( aCommand.Name == "getPropertySetInfo" ) + { + // implemented by base class. + aRet <<= getPropertySetInfo( i_rEvironment ); + } + else if ( aCommand.Name == "getCommandInfo" ) + { + // implemented by base class. + aRet <<= getCommandInfo( i_rEvironment ); + } + else if ( aCommand.Name == "open" ) + { + OpenCommandArgument2 aOpenCommand; + if ( !( aCommand.Argument >>= aOpenCommand ) ) + { + ::ucbhelper::cancelCommandExecution( makeAny( IllegalArgumentException( + OUString(), *this, -1 ) ), + i_rEvironment ); + // 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_rEvironment ); + aRet <<= xSet; + } + + if ( aOpenCommand.Sink.is() ) + { + const OUString sPhysicalContentURL( getPhysicalURL() ); + ::ucbhelper::Content aRequestedContent( sPhysicalContentURL, i_rEvironment, m_xContext ); + aRet = aRequestedContent.executeCommand( "open", makeAny( aOpenCommand ) ); + } + } + + else + { + ::ucbhelper::cancelCommandExecution( makeAny( UnsupportedCommandException( + OUString(), *this ) ), + i_rEvironment ); + // 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( const OUString& 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 ( i_rContentIdentifier.match( sRootURL ) + && ( i_rContentIdentifier.getLength() == sRootURL.getLength() + 1 ) + && ( i_rContentIdentifier[ i_rContentIdentifier.getLength() - 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 Reference< XRow >( xRow.get() ); + } + + + 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.get(); + } + + 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 = static_cast< cppu::OWeakObject * >( this ); + aEvent.Further = false; + aEvent.PropertyHandle = -1; + + for ( auto& rRet : 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 ) + return *m_aIsFolder; + + bool bIsFolder = false; + try + { + Sequence< Property > aProps(1); + aProps[0].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 ) + { + try + { + Sequence< Property > aProps(1); + aProps[0].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 000000000..a8f9956ea --- /dev/null +++ b/ucb/source/ucp/ext/ucpext_content.hxx @@ -0,0 +1,137 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#ifndef INCLUDED_UCB_SOURCE_UCP_EXT_UCPEXT_CONTENT_HXX +#define INCLUDED_UCB_SOURCE_UCP_EXT_UCPEXT_CONTENT_HXX + +#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( const OUString& 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 + + +#endif // INCLUDED_UCB_SOURCE_UCP_EXT_UCPEXT_CONTENT_HXX + +/* 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 000000000..7de5fb09a --- /dev/null +++ b/ucb/source/ucp/ext/ucpext_datasupplier.cxx @@ -0,0 +1,346 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this 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 <com/sun/star/ucb/ResultSetException.hpp> +#include <ucbhelper/contentidentifier.hxx> +#include <ucbhelper/providerhelper.hxx> +#include <ucbhelper/content.hxx> +#include <ucbhelper/propertyvalueset.hxx> +#include <tools/diagnose_ex.h> +#include <rtl/ustrbuf.hxx> +#include <sal/log.hxx> + +#include <memory> +#include <vector> + + +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; + + + //= ResultListEntry + + namespace { + + struct ResultListEntry + { + OUString sId; + Reference< XContentIdentifier > xId; + ::rtl::Reference< Content > pContent; + Reference< XRow > xRow; + }; + + } + + typedef ::std::vector< ResultListEntry > ResultList; + + + //= DataSupplier_Impl + + struct DataSupplier_Impl + { + ::osl::Mutex m_aMutex; + ResultList m_aResults; + ::rtl::Reference< Content > m_xContent; + Reference< XComponentContext > m_xContext; + + DataSupplier_Impl( const Reference< XComponentContext >& rxContext, const ::rtl::Reference< Content >& i_rContent ) + :m_xContent( i_rContent ) + ,m_xContext( rxContext ) + { + } + }; + + + //= helper + + namespace + { + OUString lcl_compose( const OUString& i_rBaseURL, const OUString& i_rRelativeURL ) + { + ENSURE_OR_RETURN( !i_rBaseURL.isEmpty(), "illegal base URL", i_rRelativeURL ); + + OUStringBuffer aComposer( i_rBaseURL ); + if ( !i_rBaseURL.endsWith("/") ) + aComposer.append( '/' ); + aComposer.append( i_rRelativeURL ); + return aComposer.makeStringAndClear(); + } + } + + + //= DataSupplier + + + DataSupplier::DataSupplier( const Reference< XComponentContext >& rxContext, + const ::rtl::Reference< Content >& i_rContent ) + :m_pImpl( new DataSupplier_Impl( rxContext, i_rContent ) ) + { + } + + + void DataSupplier::fetchData() + { + try + { + const Reference< XPackageInformationProvider > xPackageInfo = PackageInformationProvider::get( m_pImpl->m_xContext ); + + const OUString sContentIdentifier( m_pImpl->m_xContent->getIdentifier()->getContentIdentifier() ); + + switch ( m_pImpl->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_pImpl->m_aResults.push_back( aEntry ); + } + } + break; + case E_EXTENSION_ROOT: + case E_EXTENSION_CONTENT: + { + const OUString sPackageLocation( m_pImpl->m_xContent->getPhysicalURL() ); + ::ucbhelper::Content aWrappedContent( sPackageLocation, getResultSet()->getEnvironment(), m_pImpl->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_pImpl->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 ) + { + ::osl::Guard< ::osl::Mutex > aGuard( m_pImpl->m_aMutex ); + + if ( i_nIndex < m_pImpl->m_aResults.size() ) + { + const OUString sId = m_pImpl->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 ) + { + ::osl::Guard< ::osl::Mutex > aGuard( m_pImpl->m_aMutex ); + + if ( i_nIndex < m_pImpl->m_aResults.size() ) + { + Reference< XContentIdentifier > xId( m_pImpl->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_pImpl->m_aResults[ i_nIndex ].xId = xId; + return xId; + } + + return Reference< XContentIdentifier >(); + } + + + Reference< XContent > DataSupplier::queryContent( sal_uInt32 i_nIndex ) + { + ::osl::Guard< ::osl::Mutex > aGuard( m_pImpl->m_aMutex ); + ENSURE_OR_RETURN( i_nIndex < m_pImpl->m_aResults.size(), "illegal index!", nullptr ); + + + ::rtl::Reference< Content > pContent( m_pImpl->m_aResults[ i_nIndex ].pContent ); + if ( pContent.is() ) + return pContent.get(); + + Reference< XContentIdentifier > xId( queryContentIdentifier( i_nIndex ) ); + if ( xId.is() ) + { + try + { + Reference< XContent > xContent( m_pImpl->m_xContent->getProvider()->queryContent( xId ) ); + pContent.set( dynamic_cast< Content* >( xContent.get() ) ); + OSL_ENSURE( pContent.is() || !xContent.is(), "DataSupplier::queryContent: invalid content implementation!" ); + m_pImpl->m_aResults[ i_nIndex ].pContent = pContent; + return pContent.get(); + + } + catch ( const IllegalIdentifierException& ) + { + DBG_UNHANDLED_EXCEPTION("ucb.ucp.ext"); + } + } + + return Reference< XContent >(); + } + + + bool DataSupplier::getResult( sal_uInt32 i_nIndex ) + { + ::osl::ClearableGuard< ::osl::Mutex > aGuard( m_pImpl->m_aMutex ); + + // true if result already present. + return m_pImpl->m_aResults.size() > i_nIndex; + } + + + sal_uInt32 DataSupplier::totalCount() + { + ::osl::ClearableGuard< ::osl::Mutex > aGuard( m_pImpl->m_aMutex ); + return m_pImpl->m_aResults.size(); + } + + + sal_uInt32 DataSupplier::currentCount() + { + return m_pImpl->m_aResults.size(); + } + + + bool DataSupplier::isCountFinal() + { + return true; + } + + + Reference< XRow > DataSupplier::queryPropertyValues( sal_uInt32 i_nIndex ) + { + ::osl::MutexGuard aGuard( m_pImpl->m_aMutex ); + ENSURE_OR_RETURN( i_nIndex < m_pImpl->m_aResults.size(), "DataSupplier::queryPropertyValues: illegal index!", nullptr ); + + Reference< XRow > xRow = m_pImpl->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_pImpl->m_xContent->getExtensionContentType() ) + { + case E_ROOT: + { + const OUString& rId( m_pImpl->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_pImpl->m_xContext, getResultSet()->getProperties(), sTitle ); + } + break; + + case E_EXTENSION_ROOT: + case E_EXTENSION_CONTENT: + { + xRow = m_pImpl->m_aResults[ i_nIndex ].pContent->getPropertyValues( + getResultSet()->getProperties(), getResultSet()->getEnvironment() ); + } + break; + default: + OSL_FAIL( "DataSupplier::queryPropertyValues: unhandled case!" ); + break; + } + + m_pImpl->m_aResults[ i_nIndex ].xRow = xRow; + return xRow; + } + + + void DataSupplier::releasePropertyValues( sal_uInt32 i_nIndex ) + { + ::osl::Guard< ::osl::Mutex > aGuard( m_pImpl->m_aMutex ); + + if ( i_nIndex < m_pImpl->m_aResults.size() ) + m_pImpl->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 000000000..28c6691d5 --- /dev/null +++ b/ucb/source/ucp/ext/ucpext_datasupplier.hxx @@ -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 . + */ + +#ifndef INCLUDED_UCB_SOURCE_UCP_EXT_UCPEXT_DATASUPPLIER_HXX +#define INCLUDED_UCB_SOURCE_UCP_EXT_UCPEXT_DATASUPPLIER_HXX + +#include <rtl/ref.hxx> +#include <ucbhelper/resultset.hxx> + +#include <memory> + + +namespace ucb::ucp::ext +{ + + + struct DataSupplier_Impl; + class Content; + + + //= DataSupplier + + class DataSupplier : public ::ucbhelper::ResultSetDataSupplier + { + public: + DataSupplier( + const css::uno::Reference< css::uno::XComponentContext >& rxContext, + const rtl::Reference< Content >& rContent + ); + + 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: + std::unique_ptr< DataSupplier_Impl > m_pImpl; + }; + + +} // namespace ucp::ext + + +#endif // INCLUDED_UCB_SOURCE_UCP_EXT_UCPEXT_DATASUPPLIER_HXX + +/* 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 000000000..de1bb15e4 --- /dev/null +++ b/ucb/source/ucp/ext/ucpext_provider.cxx @@ -0,0 +1,181 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this 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 <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 ContentProvider::getImplementationName_static() + { + return "org.openoffice.comp.ucp.ext.ContentProvider"; + } + + + OUString SAL_CALL ContentProvider::getImplementationName() + { + return getImplementationName_static(); + } + + + Sequence< OUString > ContentProvider::getSupportedServiceNames_static( ) + { + return { "com.sun.star.ucb.ContentProvider", "com.sun.star.ucb.ExtensionContentProvider" }; + } + + + Sequence< OUString > SAL_CALL ContentProvider::getSupportedServiceNames( ) + { + return getSupportedServiceNames_static(); + } + + + Reference< XInterface > ContentProvider::Create( const Reference< XComponentContext >& i_rContext ) + { + return *( new ContentProvider( i_rContext ) ); + } + + + OUString ContentProvider::getRootURL() + { + return "vnd.sun.star.extension://"; + } + + + OUString ContentProvider::getArtificialNodeContentType() + { + return "application/vnd.sun.star.extension-content"; + } + + + namespace + { + void lcl_ensureAndTransfer( OUString& io_rIdentifierFragment, OUStringBuffer& o_rNormalization, const sal_Unicode i_nLeadingChar ) + { + if ( ( io_rIdentifierFragment.isEmpty() ) || ( io_rIdentifierFragment[0] != i_nLeadingChar ) ) + throw IllegalIdentifierException(); + io_rIdentifierFragment = io_rIdentifierFragment.copy( 1 ); + o_rNormalization.append( i_nLeadingChar ); + } + } + + + Reference< XContent > SAL_CALL ContentProvider::queryContent( const Reference< XContentIdentifier >& i_rIdentifier ) + { + // Check URL scheme... + const OUString sScheme( "vnd.sun.star.extension" ); + 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; + aComposer.append( sIdentifier.copy( 0, sScheme.getLength() ).toAsciiLowerCase() ); + + // one : is required after the scheme + OUString sRemaining( sIdentifier.copy( 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.isEmpty() ) + { + // the root content is a special case, it requires / + aComposer.append( "//" ); + } + else + { + if ( sRemaining[0] != '/' ) + { + aComposer.append( '/' ); + aComposer.append( sRemaining ); + } + else + { + lcl_ensureAndTransfer( sRemaining, aComposer, '/' ); + // by now, we moved "vnd.sun.star.extension://" from the URL to aComposer + if ( sRemaining.isEmpty() ) + { + // 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 ).get() ); + 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 + + +/* 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 000000000..1f64e3733 --- /dev/null +++ b/ucb/source/ucp/ext/ucpext_provider.hxx @@ -0,0 +1,64 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#ifndef INCLUDED_UCB_SOURCE_UCP_EXT_UCPEXT_PROVIDER_HXX +#define INCLUDED_UCB_SOURCE_UCP_EXT_UCPEXT_PROVIDER_HXX + +#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; + + // XServiceInfo - static versions + /// @throws css::uno::RuntimeException + static OUString getImplementationName_static( ); + /// @throws css::uno::RuntimeException + static css::uno::Sequence< OUString > getSupportedServiceNames_static(); + static css::uno::Reference< css::uno::XInterface > Create( const css::uno::Reference< css::uno::XComponentContext >& i_rContext ); + + // 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 + + +#endif // INCLUDED_UCB_SOURCE_UCP_EXT_UCPEXT_PROVIDER_HXX + +/* 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 000000000..b810d2956 --- /dev/null +++ b/ucb/source/ucp/ext/ucpext_resultset.cxx @@ -0,0 +1,75 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this 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> + + +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, const ::rtl::Reference< Content >& i_rContent, + const OpenCommandArgument2& i_rCommand, const Reference< XCommandEnvironment >& i_rEnv ) + :ResultSetImplHelper( rxContext, i_rCommand ) + ,m_xEnvironment( i_rEnv ) + ,m_xContent( i_rContent ) + { + } + + + void ResultSet::initStatic() + { + ::rtl::Reference< DataSupplier > pDataSupplier( new DataSupplier( + m_xContext, + m_xContent + ) ); + m_xResultSet1 = new ::ucbhelper::ResultSet( + m_xContext, + m_aCommand.Properties, + pDataSupplier.get(), + 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 000000000..738171836 --- /dev/null +++ b/ucb/source/ucp/ext/ucpext_resultset.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 . + */ + +#ifndef INCLUDED_UCB_SOURCE_UCP_EXT_UCPEXT_RESULTSET_HXX +#define INCLUDED_UCB_SOURCE_UCP_EXT_UCPEXT_RESULTSET_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, + const rtl::Reference< Content >& i_rContent, + 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 + + +#endif // INCLUDED_UCB_SOURCE_UCP_EXT_UCPEXT_RESULTSET_HXX + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/ucb/source/ucp/ext/ucpext_services.cxx b/ucb/source/ucp/ext/ucpext_services.cxx new file mode 100644 index 000000000..b7ffde3d1 --- /dev/null +++ b/ucb/source/ucp/ext/ucpext_services.cxx @@ -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 . + */ + +#include "ucpext_provider.hxx" + +#include <cppuhelper/implementationentry.hxx> + + +namespace ucb::ucp::ext +{ + + + + + //= descriptors for the services implemented in this component + + static struct ::cppu::ImplementationEntry const s_aServiceEntries[] = + { + { + ContentProvider::Create, + ContentProvider::getImplementationName_static, + ContentProvider::getSupportedServiceNames_static, + ::cppu::createOneInstanceComponentFactory, nullptr, 0 + }, + { nullptr, nullptr, nullptr, nullptr, nullptr, 0 } + }; + + +} // namespace ucb::ucp::ext + + +extern "C" +{ + + SAL_DLLPUBLIC_EXPORT void * ucpext_component_getFactory( const char * pImplName, void * pServiceManager, void * pRegistryKey ) + { + return ::cppu::component_getFactoryHelper( pImplName, pServiceManager, pRegistryKey , ::ucb::ucp::ext::s_aServiceEntries ); + } +} + +/* 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 000000000..07645451d --- /dev/null +++ b/ucb/source/ucp/file/bc.cxx @@ -0,0 +1,1208 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this 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 <osl/diagnose.h> +#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/interfacecontainer.hxx> +#include <cppuhelper/supportsservice.hxx> +#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 + +typedef cppu::OMultiTypeInterfaceContainerHelperVar<OUString> + PropertyListeners_impl; + +class fileaccess::PropertyListeners + : public PropertyListeners_impl +{ +public: + explicit PropertyListeners( ::osl::Mutex& aMutex ) + : PropertyListeners_impl( aMutex ) + { + } +}; + + +/****************************************************************************************/ +/* */ +/* BaseContent */ +/* */ +/****************************************************************************************/ + + +// Private Constructor for just inserted Contents + +BaseContent::BaseContent( TaskManager* pMyShell, + const OUString& parentName, + bool bFolder ) + : m_pMyShell( pMyShell ), + m_aUncPath( 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, + const OUString& aUncPath ) + : m_pMyShell( pMyShell ), + m_xContentIdentifier( xContentIdentifier ), + m_aUncPath( 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 ) +{ + osl::MutexGuard aGuard( m_aMutex ); + + if ( ! m_pDisposeEventListeners ) + m_pDisposeEventListeners.reset( + new comphelper::OInterfaceContainerHelper2( m_aEventListenerMutex ) ); + + m_pDisposeEventListeners->addInterface( Listener ); +} + + +void SAL_CALL +BaseContent::removeEventListener( const Reference< lang::XEventListener >& Listener ) +{ + osl::MutexGuard aGuard( m_aMutex ); + + if ( m_pDisposeEventListeners ) + m_pDisposeEventListeners->removeInterface( Listener ); +} + + +void SAL_CALL +BaseContent::dispose() +{ + lang::EventObject aEvt; + std::unique_ptr<comphelper::OInterfaceContainerHelper2> pDisposeEventListeners; + std::unique_ptr<comphelper::OInterfaceContainerHelper2> pContentEventListeners; + std::unique_ptr<comphelper::OInterfaceContainerHelper2> pPropertySetInfoChangeListeners; + std::unique_ptr<PropertyListeners> pPropertyListener; + + { + osl::MutexGuard aGuard( m_aMutex ); + aEvt.Source = static_cast< XContent* >( this ); + + pDisposeEventListeners = std::move(m_pDisposeEventListeners); + pContentEventListeners = std::move(m_pContentEventListeners); + pPropertySetInfoChangeListeners = std::move(m_pPropertySetInfoChangeListeners); + pPropertyListener = std::move(m_pPropertyListener); + } + + if ( pDisposeEventListeners && pDisposeEventListeners->getLength() ) + pDisposeEventListeners->disposeAndClear( aEvt ); + + if ( pContentEventListeners && pContentEventListeners->getLength() ) + pContentEventListeners->disposeAndClear( aEvt ); + + if( pPropertyListener ) + pPropertyListener->disposeAndClear( aEvt ); + + if( pPropertySetInfoChangeListeners ) + pPropertySetInfoChangeListeners->disposeAndClear( 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" ) + { + Sequence< beans::Property > seq(1); + seq[0] = beans::Property( + "CasePreservingURL", + -1, + cppu::UnoType<sal_Bool>::get(), + 0 ); + Reference< sdbc::XRow > xRow = getPropertyValues( CommandId,seq ); + 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; + + osl::MutexGuard aGuard( m_aMutex ); + + if( ! m_pPropertyListener ) + m_pPropertyListener.reset( new PropertyListeners( m_aEventListenerMutex ) ); + + + if( !PropertyNames.hasElements() ) + m_pPropertyListener->addInterface( 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( rName,Listener ); + } +} + + +void SAL_CALL +BaseContent::removePropertiesChangeListener( const Sequence< OUString >& PropertyNames, + const Reference< beans::XPropertiesChangeListener >& Listener ) +{ + if( ! Listener.is() ) + return; + + osl::MutexGuard aGuard( m_aMutex ); + + if( ! m_pPropertyListener ) + return; + + for( const auto& rName : PropertyNames ) + m_pPropertyListener->removeInterface( rName,Listener ); + + m_pPropertyListener->removeInterface( 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 ? + Sequence< beans::Property > seq(1); + seq[0] = beans::Property( "IsDocument", + -1, + cppu::UnoType<sal_Bool>::get(), + 0 ); + Reference< sdbc::XRow > xRow = getPropertyValues( -1,seq ); + 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&) + { + OSL_FAIL( "BaseContent::getContentType - Caught SQLException!" ); + } + } + } + + return OUString(); +} + + +void SAL_CALL +BaseContent::addContentEventListener( + const Reference< XContentEventListener >& Listener ) +{ + osl::MutexGuard aGuard( m_aMutex ); + + if ( ! m_pContentEventListeners ) + m_pContentEventListeners.reset( + new comphelper::OInterfaceContainerHelper2( m_aEventListenerMutex ) ); + + + m_pContentEventListeners->addInterface( Listener ); +} + + +void SAL_CALL +BaseContent::removeContentEventListener( + const Reference< XContentEventListener >& Listener ) +{ + osl::MutexGuard aGuard( m_aMutex ); + + if ( m_pContentEventListeners ) + m_pContentEventListeners->removeInterface( 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 + { + Sequence< beans::Property > seq(1); + seq[0] = beans::Property( "IsDocument", + -1, + cppu::UnoType<sal_Bool>::get(), + 0 ); + Reference< sdbc::XRow > xRow = getPropertyValues( -1,seq ); + IsDocument = xRow->getBoolean( 1 ); + + if ( xRow->wasNull() ) + { + IsDocument = false; +// OSL_FAIL( // "BaseContent::createNewContent - Property value was null!" ); +// return Reference< XContent >(); + } + } + catch (const sdbc::SQLException&) + { + OSL_FAIL( "BaseContent::createNewContent - Caught SQLException!" ); + 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; + + BaseContent* p = new BaseContent( m_pMyShell, dstUncPath, bFolder ); + return Reference< XContent >( p ); +} + + +// XPropertySetInfoChangeNotifier + + +void SAL_CALL +BaseContent::addPropertySetInfoChangeListener( + const Reference< beans::XPropertySetInfoChangeListener >& Listener ) +{ + osl::MutexGuard aGuard( m_aMutex ); + if( ! m_pPropertySetInfoChangeListeners ) + m_pPropertySetInfoChangeListeners.reset( new comphelper::OInterfaceContainerHelper2( m_aEventListenerMutex ) ); + + m_pPropertySetInfoChangeListeners->addInterface( Listener ); +} + + +void SAL_CALL +BaseContent::removePropertySetInfoChangeListener( + const Reference< beans::XPropertySetInfoChangeListener >& Listener ) +{ + osl::MutexGuard aGuard( m_aMutex ); + + if( m_pPropertySetInfoChangeListeners ) + m_pPropertySetInfoChangeListeners->removeInterface( 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 ); + + FileContentIdentifier* p = new FileContentIdentifier( ParentUnq ); + Reference< XContentIdentifier > Identifier( p ); + + 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 <<= OUString(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() ); + } + + const OUString Title("Title"); + + // 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.copy( 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 ); + + // 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 ) ) + { + ret[i] <<= beans::IllegalTypeException( THROW_WHERE ); + break; + } + else if( NewTitle.isEmpty() ) + { + ret[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) + { + ret[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 ) ) + { + osl::MutexGuard 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 ? + Sequence< beans::Property > seq(1); + seq[0] = beans::Property( "IsDocument", + -1, + cppu::UnoType<sal_Bool>::get(), + 0 ); + Reference< sdbc::XRow > xRow = getPropertyValues( nMyCommandIdentifier,seq ); + 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; + + Sequence< beans::Property > seq(1); + seq[0] = beans::Property( "IsDocument", + -1, + cppu::UnoType<sal_Bool>::get(), + 0 ); + + Reference< sdbc::XRow > xRow = getPropertyValues( -1,seq ); + + 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&) + { + OSL_FAIL( "BaseContent::insert - Caught SQLException!" ); + 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( + getTitle(m_aUncPath), + rtl_UriDecodeWithCharset, + RTL_TEXTENCODING_UTF8), + static_cast<cppu::OWeakObject*>(this), + 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 ); + + osl::MutexGuard 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::unique_ptr<ContentEventNotifier> +BaseContent::cDEL() +{ + osl::MutexGuard aGuard( m_aMutex ); + + m_nState |= Deleted; + + std::unique_ptr<ContentEventNotifier> p; + if( m_pContentEventListeners ) + { + p.reset( new ContentEventNotifier( m_pMyShell, + this, + m_xContentIdentifier, + m_pContentEventListeners->getElements() ) ); + } + + return p; +} + + +std::unique_ptr<ContentEventNotifier> +BaseContent::cEXC( const OUString& aNewName ) +{ + osl::MutexGuard aGuard( m_aMutex ); + + Reference< XContentIdentifier > xOldRef = m_xContentIdentifier; + m_aUncPath = aNewName; + FileContentIdentifier* pp = new FileContentIdentifier( aNewName ); + m_xContentIdentifier.set( pp ); + + std::unique_ptr<ContentEventNotifier> p; + if( m_pContentEventListeners ) + p.reset( new ContentEventNotifier( m_pMyShell, + this, + m_xContentIdentifier, + xOldRef, + m_pContentEventListeners->getElements() ) ); + + return p; +} + + +std::unique_ptr<ContentEventNotifier> +BaseContent::cCEL() +{ + osl::MutexGuard aGuard( m_aMutex ); + std::unique_ptr<ContentEventNotifier> p; + if( m_pContentEventListeners ) + p.reset( new ContentEventNotifier( m_pMyShell, + this, + m_xContentIdentifier, + m_pContentEventListeners->getElements() ) ); + + return p; +} + +std::unique_ptr<PropertySetInfoChangeNotifier> +BaseContent::cPSL() +{ + osl::MutexGuard aGuard( m_aMutex ); + std::unique_ptr<PropertySetInfoChangeNotifier> p; + if( m_pPropertySetInfoChangeListeners ) + p.reset( new PropertySetInfoChangeNotifier( this, + m_pPropertySetInfoChangeListeners->getElements() ) ); + + return p; +} + + +std::unique_ptr<PropertyChangeNotifier> +BaseContent::cPCL() +{ + osl::MutexGuard aGuard( m_aMutex ); + + if (!m_pPropertyListener) + return nullptr; + + const Sequence< OUString > seqNames = m_pPropertyListener->getContainedTypes(); + + std::unique_ptr<PropertyChangeNotifier> p; + + if( seqNames.hasElements() ) + { + std::unique_ptr<ListenerMap> listener(new ListenerMap); + for( const auto& rName : seqNames ) + { + cppu::OInterfaceContainerHelper* pContainer = m_pPropertyListener->getContainer(rName); + if (!pContainer) + continue; + (*listener)[rName] = pContainer->getElements(); + } + + p.reset( new PropertyChangeNotifier( this, std::move(listener) ) ); + } + + return p; +} + + +/* 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 000000000..b04a1c903 --- /dev/null +++ b/ucb/source/ucp/file/bc.hxx @@ -0,0 +1,262 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#ifndef INCLUDED_UCB_SOURCE_UCP_FILE_BC_HXX +#define INCLUDED_UCB_SOURCE_UCP_FILE_BC_HXX + +#include <osl/mutex.hxx> +#include <rtl/ustring.hxx> +#include <cppuhelper/implbase.hxx> +#include <comphelper/interfacecontainer2.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, + const OUString& parentName, + bool bFolder ); + + public: + BaseContent( + TaskManager* pMyShell, + const css::uno::Reference< css::ucb::XContentIdentifier >& xContentIdentifier, + const 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::unique_ptr<ContentEventNotifier> cDEL() override; + std::unique_ptr<ContentEventNotifier> cEXC( const OUString& aNewName ) override; + std::unique_ptr<ContentEventNotifier> cCEL() override; + std::unique_ptr<PropertySetInfoChangeNotifier> cPSL() override; + std::unique_ptr<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; + + osl::Mutex m_aMutex; + + osl::Mutex m_aEventListenerMutex; + std::unique_ptr<comphelper::OInterfaceContainerHelper2> m_pDisposeEventListeners; + std::unique_ptr<comphelper::OInterfaceContainerHelper2> m_pContentEventListeners; + std::unique_ptr<comphelper::OInterfaceContainerHelper2> m_pPropertySetInfoChangeListeners; + 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 + +#endif + +/* 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 000000000..a6f0f954c --- /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() + throw() +{ + OWeakObject::acquire(); +} + + +void SAL_CALL +XCommandInfo_impl::release() + throw() +{ + 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(m_pMyShell->m_sCommandInfo.begin(), m_pMyShell->m_sCommandInfo.end(), + [&aName](const CommandInfo& rCommand) { return rCommand.Name == aName; }); + if (pCommand != m_pMyShell->m_sCommandInfo.end()) + return *pCommand; + + throw UnsupportedCommandException( THROW_WHERE ); +} + + +CommandInfo SAL_CALL +XCommandInfo_impl::getCommandInfoByHandle( + sal_Int32 Handle ) +{ + auto pCommand = std::find_if(m_pMyShell->m_sCommandInfo.begin(), m_pMyShell->m_sCommandInfo.end(), + [&Handle](const CommandInfo& rCommand) { return rCommand.Handle == Handle; }); + if (pCommand != m_pMyShell->m_sCommandInfo.end()) + return *pCommand; + + throw UnsupportedCommandException( THROW_WHERE ); +} + + +sal_Bool SAL_CALL +XCommandInfo_impl::hasCommandByName( + const OUString& aName ) +{ + return std::any_of(m_pMyShell->m_sCommandInfo.begin(), m_pMyShell->m_sCommandInfo.end(), + [&aName](const CommandInfo& rCommand) { return rCommand.Name == aName; }); +} + + +sal_Bool SAL_CALL +XCommandInfo_impl::hasCommandByHandle( + sal_Int32 Handle ) +{ + return std::any_of(m_pMyShell->m_sCommandInfo.begin(), m_pMyShell->m_sCommandInfo.end(), + [&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 000000000..2c3d2448e --- /dev/null +++ b/ucb/source/ucp/file/filcmd.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 . + */ +#ifndef INCLUDED_UCB_SOURCE_UCP_FILE_FILCMD_HXX +#define INCLUDED_UCB_SOURCE_UCP_FILE_FILCMD_HXX + +#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() + throw() override; + + virtual void SAL_CALL + release() + throw() 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; + }; + +} + +#endif + +/* 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 000000000..a31de1b79 --- /dev/null +++ b/ucb/source/ucp/file/filerror.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 . + */ + +#ifndef INCLUDED_UCB_SOURCE_UCP_FILE_FILERROR_HXX +#define INCLUDED_UCB_SOURCE_UCP_FILE_FILERROR_HXX + +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 + +} + +#endif // INCLUDED_UCB_SOURCE_UCP_FILE_FILERROR_HXX + +/* 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 000000000..15277a2bc --- /dev/null +++ b/ucb/source/ucp/file/filglob.cxx @@ -0,0 +1,862 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this 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 "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 <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) ); + sal_Int32 i = 0; + aArguments[i++] + <<= PropertyValue("Uri", + -1, + makeAny(rPhysicalUrl), + PropertyState_DIRECT_VALUE); + if (bResourceName) + aArguments[i++] + <<= PropertyValue("ResourceName", + -1, + makeAny(aResourceName), + PropertyState_DIRECT_VALUE); + if (bResourceType) + aArguments[i++] + <<= PropertyValue("ResourceType", + -1, + makeAny(aResourceType), + PropertyState_DIRECT_VALUE); + if (bRemoveProperty) + aArguments[i++] + <<= PropertyValue("Removable", + -1, + makeAny(bRemovable), + PropertyState_DIRECT_VALUE); + + return aArguments; + } +} + + +namespace fileaccess { + + + bool isChild( const OUString& srcUnqPath, + const OUString& dstUnqPath ) + { + static const sal_Unicode slash = '/'; + // Simple lexical comparison + sal_Int32 srcL = srcUnqPath.getLength(); + sal_Int32 dstL = dstUnqPath.getLength(); + + return ( + ( srcUnqPath == dstUnqPath ) + || + ( ( dstL > srcL ) + && + dstUnqPath.startsWith(srcUnqPath) + && + ( dstUnqPath[ srcL ] == slash ) ) + ); + } + + + OUString newName( + const OUString& aNewPrefix, + const OUString& aOldPrefix, + const OUString& old_Name ) + { + sal_Int32 srcL = aOldPrefix.getLength(); + + return aNewPrefix + old_Name.copy( srcL ); + } + + + OUString getTitle( const OUString& aPath ) + { + sal_Int32 lastIndex = aPath.lastIndexOf( '/' ); + return aPath.copy( lastIndex + 1 ); + } + + + OUString getParentName( const OUString& aFileName ) + { + sal_Int32 lastIndex = aFileName.lastIndexOf( '/' ); + OUString aParent = aFileName.copy( 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 ) + { + Sequence< OUString > aSeq( 1 ); + aSeq[0] = + ( errorCode == TASKHANDLING_NONAMESET_INSERT_COMMAND ) ? + OUStringLiteral("Title") : + OUStringLiteral("ContentType"); + + 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; + excep.Name = getTitle(aUncPath); + excep.Classification = InteractionClassification_ERROR; + excep.Context = Reference<XInterface>( xComProc, UNO_QUERY ); + excep.Message = "file exists and overwrite forbidden"; + cancelCommandExecution( Any(excep), xEnv ); + } + else if( errorCode == TASKHANDLING_INVALID_NAME_MKDIR ) + { + InteractiveAugmentedIOException excep; + excep.Code = IOErrorCode_INVALID_CHARACTER; + PropertyValue prop; + prop.Name = "ResourceName"; + prop.Handle = -1; + OUString aClashingName( + rtl::Uri::decode( + getTitle(aUncPath), + rtl_UriDecodeWithCharset, + RTL_TEXTENCODING_UTF8)); + prop.Value <<= aClashingName; + Sequence<Any> seq(1); + seq[0] <<= prop; + excep.Arguments = seq; + excep.Classification = InteractionClassification_ERROR; + excep.Context = Reference<XInterface>( xComProc, UNO_QUERY ); + excep.Message = "the name contained invalid characters"; + 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; + excep.Name = getTitle(aUncPath); + excep.Classification = InteractionClassification_ERROR; + excep.Context = xComProc; + excep.Message = "folder exists and overwrite forbidden"; + 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; + excep.Name = getTitle(aUncPath); + excep.Classification = InteractionClassification_ERROR; + excep.Context = Reference<XInterface>( xComProc, UNO_QUERY ); + excep.Message = "name clash during copy or move"; + + cancelCommandExecution(Any(excep), xEnv); + } + else if( errorCode == TASKHANDLING_NAMECLASHSUPPORT_FOR_MOVE || + errorCode == TASKHANDLING_NAMECLASHSUPPORT_FOR_COPY ) + { + UnsupportedNameClashException excep; + excep.NameClash = minorCode; + excep.Context = Reference<XInterface>( xComProc, UNO_QUERY ); + excep.Message = "name clash value not supported during copy or move"; + + 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 000000000..d1e9a4a01 --- /dev/null +++ b/ucb/source/ucp/file/filglob.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 . + */ +#ifndef INCLUDED_UCB_SOURCE_UCP_FILE_FILGLOB_HXX +#define INCLUDED_UCB_SOURCE_UCP_FILE_FILGLOB_HXX + +#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( const OUString& srcUnqPath, + const OUString& dstUnqPath ); + + + // Changes the prefix in name + extern OUString newName( const OUString& aNewPrefix, + const OUString& aOldPrefix, + const OUString& old_Name ); + + // returns the last part of the given url as title + extern OUString getTitle( const OUString& aPath ); + + // returns the url without last part as parentname + // In case aFileName is root ( file:/// ) root is returned + + extern OUString getParentName( const OUString& 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 + +#endif + +/* 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 000000000..d42fd194d --- /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 000000000..5eaaf62f8 --- /dev/null +++ b/ucb/source/ucp/file/filid.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 . + */ +#ifndef INCLUDED_UCB_SOURCE_UCP_FILE_FILID_HXX +#define INCLUDED_UCB_SOURCE_UCP_FILE_FILID_HXX + +#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 + + +#endif + +/* 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 000000000..ffa5ed19a --- /dev/null +++ b/ucb/source/ucp/file/filinl.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 . + */ +#ifndef INCLUDED_UCB_SOURCE_UCP_FILE_FILINL_HXX +#define INCLUDED_UCB_SOURCE_UCP_FILE_FILINL_HXX + +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( const css::uno::Any& theValue ) const +{ + const_cast<MyProperty*>(this)->Value = theValue; +} +inline void TaskManager::MyProperty::setState( const css::beans::PropertyState& theState ) const +{ + const_cast<MyProperty*>(this)->State = theState; +} + +#endif + +/* 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 000000000..5838dcda3 --- /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 000000000..06e8ae6d6 --- /dev/null +++ b/ucb/source/ucp/file/filinpstr.hxx @@ -0,0 +1,90 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ +#ifndef INCLUDED_UCB_SOURCE_UCP_FILE_FILINPSTR_HXX +#define INCLUDED_UCB_SOURCE_UCP_FILE_FILINPSTR_HXX + +#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() { 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 + +#endif + +/* 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 000000000..21a4b4c66 --- /dev/null +++ b/ucb/source/ucp/file/filinsreq.cxx @@ -0,0 +1,85 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this 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; + excep.Name = aClashingName; + excep.Classification = InteractionClassification_ERROR; + excep.Context = m_xOrigin; + excep.Message = "folder exists and overwrite forbidden"; + aAny <<= excep; + } + else if(nErrorCode == TASKHANDLING_INVALID_NAME_MKDIR) + { + InteractiveAugmentedIOException excep; + excep.Code = IOErrorCode_INVALID_CHARACTER; + PropertyValue prop; + prop.Name = "ResourceName"; + prop.Handle = -1; + prop.Value <<= aClashingName; + Sequence<Any> seq(1); + seq[0] <<= prop; + excep.Arguments = seq; + excep.Classification = InteractionClassification_ERROR; + excep.Context = m_xOrigin; + excep.Message = "the name contained invalid characters"; + aAny <<= excep; + + } + m_xRequest.set(new ::comphelper::OInteractionRequest(aAny, 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 000000000..661852681 --- /dev/null +++ b/ucb/source/ucp/file/filinsreq.hxx @@ -0,0 +1,145 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + + +#ifndef INCLUDED_UCB_SOURCE_UCP_FILE_FILINSREQ_HXX +#define INCLUDED_UCB_SOURCE_UCP_FILE_FILINSREQ_HXX + +#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; + }; + +} + + +#endif + +/* 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 000000000..c5d25b38d --- /dev/null +++ b/ucb/source/ucp/file/filnot.cxx @@ -0,0 +1,248 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this 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 "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, + const std::vector< uno::Reference< uno::XInterface > >& sListeners ) + : m_pMyShell( pMyShell ), + m_xCreatorContent( xCreatorContent ), + m_xCreatorId( xCreatorId ), + m_sListeners( sListeners ) +{ +} + + +ContentEventNotifier::ContentEventNotifier( TaskManager* pMyShell, + const uno::Reference< XContent >& xCreatorContent, + const uno::Reference< XContentIdentifier >& xCreatorId, + const uno::Reference< XContentIdentifier >& xOldId, + const std::vector< uno::Reference< uno::XInterface > >& sListeners ) + : m_pMyShell( pMyShell ), + m_xCreatorContent( xCreatorContent ), + m_xCreatorId( xCreatorId ), + m_xOldId( xOldId ), + m_sListeners( sListeners ) +{ +} + + +void ContentEventNotifier::notifyChildInserted( const OUString& aChildName ) +{ + FileContentIdentifier* p = new FileContentIdentifier( aChildName ); + uno::Reference< XContentIdentifier > xChildId( p ); + + uno::Reference< XContent > xChildContent = m_pMyShell->m_pProvider->queryContent( xChildId ); + + ContentEvent aEvt( m_xCreatorContent, + ContentAction::INSERTED, + xChildContent, + m_xCreatorId ); + + for( const auto& r : m_sListeners ) + { + uno::Reference< XContentEventListener > ref( r, uno::UNO_QUERY ); + if( ref.is() ) + ref->contentEvent( aEvt ); + } +} + +void ContentEventNotifier::notifyDeleted() +{ + + ContentEvent aEvt( m_xCreatorContent, + ContentAction::DELETED, + m_xCreatorContent, + m_xCreatorId ); + + + for( const auto& r : m_sListeners ) + { + uno::Reference< XContentEventListener > ref( r, uno::UNO_QUERY ); + if( ref.is() ) + ref->contentEvent( aEvt ); + } +} + + +void ContentEventNotifier::notifyRemoved( const OUString& aChildName ) +{ + FileContentIdentifier* p = new FileContentIdentifier( aChildName ); + uno::Reference< XContentIdentifier > xChildId( p ); + + BaseContent* pp = new BaseContent( m_pMyShell,xChildId,aChildName ); + { + osl::MutexGuard aGuard( pp->m_aMutex ); + pp->m_nState |= BaseContent::Deleted; + } + + uno::Reference< XContent > xDeletedContent( pp ); + + + ContentEvent aEvt( m_xCreatorContent, + ContentAction::REMOVED, + xDeletedContent, + m_xCreatorId ); + + for( const auto& r : m_sListeners ) + { + uno::Reference< XContentEventListener > ref( r, uno::UNO_QUERY ); + if( ref.is() ) + ref->contentEvent( aEvt ); + } +} + +void ContentEventNotifier::notifyExchanged() +{ + ContentEvent aEvt( m_xCreatorContent, + ContentAction::EXCHANGED, + m_xCreatorContent, + m_xOldId ); + + for( const auto& r : m_sListeners ) + { + uno::Reference< XContentEventListener > ref( r, uno::UNO_QUERY ); + if( ref.is() ) + ref->contentEvent( aEvt ); + } +} + +/*********************************************************************************/ +/* */ +/* PropertySetInfoChangeNotifier */ +/* */ +/*********************************************************************************/ + + +PropertySetInfoChangeNotifier::PropertySetInfoChangeNotifier( + const uno::Reference< XContent >& xCreatorContent, + const std::vector< uno::Reference< uno::XInterface > >& sListeners ) + : m_xCreatorContent( xCreatorContent ), + m_sListeners( sListeners ) +{ + +} + + +void +PropertySetInfoChangeNotifier::notifyPropertyAdded( const OUString & aPropertyName ) +{ + beans::PropertySetInfoChangeEvent aEvt( m_xCreatorContent, + aPropertyName, + -1, + beans::PropertySetInfoChange::PROPERTY_INSERTED ); + + for( const auto& r : m_sListeners ) + { + uno::Reference< beans::XPropertySetInfoChangeListener > ref( r, uno::UNO_QUERY ); + if( ref.is() ) + ref->propertySetInfoChange( aEvt ); + } +} + + +void +PropertySetInfoChangeNotifier::notifyPropertyRemoved( const OUString & aPropertyName ) +{ + beans::PropertySetInfoChangeEvent aEvt( m_xCreatorContent, + aPropertyName, + -1, + beans::PropertySetInfoChange::PROPERTY_REMOVED ); + + for( const auto& r : m_sListeners ) + { + uno::Reference< beans::XPropertySetInfoChangeListener > ref( r, uno::UNO_QUERY ); + if( ref.is() ) + ref->propertySetInfoChange( aEvt ); + } +} + + +/*********************************************************************************/ +/* */ +/* PropertySetInfoChangeNotifier */ +/* */ +/*********************************************************************************/ + + +PropertyChangeNotifier::PropertyChangeNotifier( + const css::uno::Reference< XContent >& xCreatorContent, + std::unique_ptr<ListenerMap> pListeners ) + : m_xCreatorContent( xCreatorContent ), + m_pListeners( std::move(pListeners) ) +{ +} + + +PropertyChangeNotifier::~PropertyChangeNotifier() +{ +} + + +void PropertyChangeNotifier::notifyPropertyChanged( + const uno::Sequence< beans::PropertyChangeEvent >& seqChanged ) +{ + uno::Sequence< beans::PropertyChangeEvent > Changes = seqChanged; + + for( auto& rChange : Changes ) + rChange.Source = m_xCreatorContent; + + // notify listeners for all Events + + uno::Sequence< uno::Reference< uno::XInterface > > seqList = (*m_pListeners)[ OUString() ]; + for( const auto& rListener : std::as_const(seqList) ) + { + uno::Reference< beans::XPropertiesChangeListener > aListener( rListener,uno::UNO_QUERY ); + if( aListener.is() ) + { + aListener->propertiesChange( Changes ); + } + } + + uno::Sequence< beans::PropertyChangeEvent > seq(1); + for( const auto& rChange : std::as_const(Changes) ) + { + seq[0] = rChange; + seqList = (*m_pListeners)[ rChange.PropertyName ]; + + for( const auto& rListener : std::as_const(seqList) ) + { + uno::Reference< beans::XPropertiesChangeListener > aListener( rListener,uno::UNO_QUERY ); + if( aListener.is() ) + { + aListener->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 000000000..02b27e1d7 --- /dev/null +++ b/ucb/source/ucp/file/filnot.hxx @@ -0,0 +1,120 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ +#ifndef INCLUDED_UCB_SOURCE_UCP_FILE_FILNOT_HXX +#define INCLUDED_UCB_SOURCE_UCP_FILE_FILNOT_HXX + +#include <com/sun/star/uno/Sequence.hxx> +#include <com/sun/star/uno/XInterface.hpp> +#include <com/sun/star/beans/PropertyChangeEvent.hpp> +#include <com/sun/star/ucb/XContentIdentifier.hpp> +#include <com/sun/star/ucb/XContent.hpp> +#include <memory> +#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::uno::XInterface > > m_sListeners; + public: + + ContentEventNotifier( + TaskManager* pMyShell, + const css::uno::Reference< css::ucb::XContent >& xCreatorContent, + const css::uno::Reference< css::ucb::XContentIdentifier >& xCreatorId, + const std::vector< css::uno::Reference< css::uno::XInterface > >& 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, + const std::vector< css::uno::Reference< css::uno::XInterface > >& sListeners ); + + void notifyChildInserted( const OUString& aChildName ); + void notifyDeleted(); + void notifyRemoved( const OUString& aChildName ); + void notifyExchanged( ); + }; + + + class PropertySetInfoChangeNotifier + { + private: + css::uno::Reference< css::ucb::XContent > m_xCreatorContent; + std::vector< css::uno::Reference< css::uno::XInterface > > m_sListeners; + public: + PropertySetInfoChangeNotifier( + const css::uno::Reference< css::ucb::XContent >& xCreatorContent, + const std::vector< css::uno::Reference< css::uno::XInterface > >& sListeners ); + + void notifyPropertyAdded( const OUString & aPropertyName ); + void notifyPropertyRemoved( const OUString & aPropertyName ); + }; + + + typedef std::unordered_map< OUString, + css::uno::Sequence< css::uno::Reference< css::uno::XInterface > > > ListenerMap; + + class PropertyChangeNotifier + { + private: + css::uno::Reference< css::ucb::XContent > m_xCreatorContent; + std::unique_ptr<ListenerMap> m_pListeners; + public: + PropertyChangeNotifier( + const css::uno::Reference< css::ucb::XContent >& xCreatorContent, + std::unique_ptr<ListenerMap> pListeners ); + + ~PropertyChangeNotifier(); + + void notifyPropertyChanged( + const css::uno::Sequence< css::beans::PropertyChangeEvent >& seqChanged ); + }; + + + class Notifier + { + public: + // Side effect of this function is the change of the name + virtual std::unique_ptr<ContentEventNotifier> cEXC( const OUString& aNewName ) = 0; + // Side effect is the change of the state of the object to "deleted". + virtual std::unique_ptr<ContentEventNotifier> cDEL() = 0; + virtual std::unique_ptr<ContentEventNotifier> cCEL() = 0; + virtual std::unique_ptr<PropertySetInfoChangeNotifier> cPSL() = 0; + virtual std::unique_ptr<PropertyChangeNotifier> cPCL() = 0; + + protected: + ~Notifier() {} + }; + + +} // end namespace fileaccess + +#endif + +/* 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 000000000..7f64caa14 --- /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" + +using namespace fileaccess; +using namespace com::sun::star; +using namespace com::sun::star::uno; +using namespace com::sun::star::ucb; + +#include "filinl.hxx" + +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() ); + + sal_Int32 count = 0; + for( const auto& rProp : properties ) + { + m_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(m_seq.begin(), m_seq.end(), + [&aName](const beans::Property& rProp) { return rProp.Name == aName; }); + if (pProp != m_seq.end()) + 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(m_seq.begin(), m_seq.end(), + [&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 000000000..85a0e234d --- /dev/null +++ b/ucb/source/ucp/file/filprp.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/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ +#ifndef INCLUDED_UCB_SOURCE_UCP_FILE_FILPRP_HXX +#define INCLUDED_UCB_SOURCE_UCP_FILE_FILPRP_HXX + +#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; + }; +} + +#endif + +/* 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 000000000..5af520bc3 --- /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 000000000..ae2cf89f7 --- /dev/null +++ b/ucb/source/ucp/file/filrec.hxx @@ -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 . + */ + +#ifndef INCLUDED_UCB_SOURCE_UCP_FILE_FILREC_HXX +#define INCLUDED_UCB_SOURCE_UCP_FILE_FILREC_HXX + +#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 +#endif // INCLUDED_UCB_SOURCE_UCP_FILE_FILREC_HXX + +/* 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 000000000..54ba6140f --- /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, + 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() ); + osl::MutexGuard 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) +{ + return nIndex < 1 || m_aValueMap.getLength() < nIndex; +} + +template<typename T> +T XRow_impl::getValue(sal_Int32 columnIndex) +{ + T aValue{}; + osl::MutexGuard 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 000000000..47ba891ca --- /dev/null +++ b/ucb/source/ucp/file/filrow.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 . + */ +#ifndef INCLUDED_UCB_SOURCE_UCP_FILE_FILROW_HXX +#define INCLUDED_UCB_SOURCE_UCP_FILE_FILROW_HXX + +#include <com/sun/star/sdbc/XRow.hpp> +#include <com/sun/star/script/XTypeConverter.hpp> +#include <cppuhelper/implbase.hxx> + +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: + osl::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 ); + template<typename T> + T getValue(sal_Int32 columnIndex); + }; + +} // end namespace fileaccess + +#endif + +/* 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 000000000..470ec88d6 --- /dev/null +++ b/ucb/source/ucp/file/filrset.cxx @@ -0,0 +1,726 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this 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 ) +{ + osl::MutexGuard aGuard( m_aMutex ); + + if ( ! m_pDisposeEventListeners ) + m_pDisposeEventListeners.reset( + new comphelper::OInterfaceContainerHelper2( m_aEventListenerMutex ) ); + + m_pDisposeEventListeners->addInterface( Listener ); +} + + +void SAL_CALL +XResultSet_impl::removeEventListener( + const uno::Reference< lang::XEventListener >& Listener ) +{ + osl::MutexGuard aGuard( m_aMutex ); + + if ( m_pDisposeEventListeners ) + m_pDisposeEventListeners->removeInterface( Listener ); +} + + +void SAL_CALL +XResultSet_impl::dispose() +{ + osl::MutexGuard aGuard( m_aMutex ); + + lang::EventObject aEvt; + aEvt.Source = static_cast< lang::XComponent * >( this ); + + if ( m_pDisposeEventListeners && m_pDisposeEventListeners->getLength() ) + { + m_pDisposeEventListeners->disposeAndClear( aEvt ); + } + if( m_pRowCountListeners && m_pRowCountListeners->getLength() ) + { + m_pRowCountListeners->disposeAndClear( aEvt ); + } + if( m_pIsFinalListeners && m_pIsFinalListeners->getLength() ) + { + m_pIsFinalListeners->disposeAndClear( aEvt ); + } +} + + +void XResultSet_impl::rowCountChanged() +{ + sal_Int32 aOldValue,aNewValue; + std::vector< uno::Reference< uno::XInterface > > seq; + { + osl::MutexGuard aGuard( m_aMutex ); + if( m_pRowCountListeners ) + seq = m_pRowCountListeners->getElements(); + 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& r : seq ) + { + uno::Reference< beans::XPropertyChangeListener > listener( + r, uno::UNO_QUERY ); + if( listener.is() ) + listener->propertyChange( aEv ); + } +} + + +void XResultSet_impl::isFinalChanged() +{ + std::vector< uno::Reference< XInterface > > seq; + { + osl::MutexGuard aGuard( m_aMutex ); + if( m_pIsFinalListeners ) + seq = m_pIsFinalListeners->getElements(); + m_bRowCountFinal = true; + } + beans::PropertyChangeEvent aEv; + aEv.PropertyName = "IsRowCountFinal"; + aEv.Further = false; + aEv.PropertyHandle = -1; + aEv.OldValue <<= false; + aEv.NewValue <<= true; + for( const auto& r : seq ) + { + uno::Reference< beans::XPropertyChangeListener > listener( + r, uno::UNO_QUERY ); + if( listener.is() ) + 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 ) + { + osl::MutexGuard aGuard( m_aMutex ); + m_aItems.push_back( aRow ); + m_aIdents.emplace_back( ); + m_aUnqPath.push_back( aUnqPath ); + rowCountChanged(); + return true; + + } + else if( m_nOpenMode == ucb::OpenMode::DOCUMENTS && ! IsRegular ) + { + continue; + } + else if( m_nOpenMode == ucb::OpenMode::FOLDERS && ! IsRegular ) + { + osl::MutexGuard aGuard( m_aMutex ); + m_aItems.push_back( aRow ); + m_aIdents.emplace_back( ); + m_aUnqPath.push_back( aUnqPath ); + rowCountChanged(); + return true; + } + else if( m_nOpenMode == ucb::OpenMode::FOLDERS && IsRegular ) + { + continue; + } + else + { + osl::MutexGuard aGuard( m_aMutex ); + m_aItems.push_back( aRow ); + m_aIdents.emplace_back( ); + m_aUnqPath.push_back( aUnqPath ); + rowCountChanged(); + 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(); + osl::MutexGuard 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() +{ + osl::MutexGuard 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 ) +{ + osl::ClearableMutexGuard 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.clear(); + + Listener->notify( + ucb::ListEvent( + static_cast< cppu::OWeakObject * >( this ), 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(m_sProperty.begin(), m_sProperty.end(), + [](const beans::Property& rProp) { return rProp.Name == "Title"; }); + if (pProp != m_sProperty.end()) + { + std::vector< ::ucbhelper::ResultSetColumnData > + aColumnData( m_sProperty.getLength() ); + auto n = std::distance(m_sProperty.begin(), pProp); + // @@@ #82177# - Determine correct value! + aColumnData[ n ].isCaseSensitive = false; + + ::ucbhelper::ResultSetMetaData* p = + new ::ucbhelper::ResultSetMetaData( + m_pMyShell->m_xContext, + m_sProperty, + aColumnData ); + return uno::Reference< sdbc::XResultSetMetaData >( p ); + } + + ::ucbhelper::ResultSetMetaData* p = + new ::ucbhelper::ResultSetMetaData( m_pMyShell->m_xContext, m_sProperty ); + return uno::Reference< sdbc::XResultSetMetaData >( p ); +} + + +// XPropertySet +uno::Reference< beans::XPropertySetInfo > SAL_CALL +XResultSet_impl::getPropertySetInfo() +{ + + uno::Sequence< beans::Property > seq(2); + seq[0].Name = "RowCount"; + seq[0].Handle = -1; + seq[0].Type = cppu::UnoType<sal_Int32>::get(); + seq[0].Attributes = beans::PropertyAttribute::READONLY; + + seq[1].Name = "IsRowCountFinal"; + seq[1].Handle = -1; + seq[1].Type = cppu::UnoType<sal_Bool>::get(); + seq[1].Attributes = beans::PropertyAttribute::READONLY; + + XPropertySetInfo_impl* p = new XPropertySetInfo_impl( m_pMyShell, + seq ); + return uno::Reference< beans::XPropertySetInfo > ( p ); +} + + +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" ) + { + osl::MutexGuard aGuard( m_aMutex ); + if ( ! m_pIsFinalListeners ) + m_pIsFinalListeners.reset( + new comphelper::OInterfaceContainerHelper2( m_aEventListenerMutex ) ); + + m_pIsFinalListeners->addInterface( xListener ); + } + else if ( aPropertyName == "RowCount" ) + { + osl::MutexGuard aGuard( m_aMutex ); + if ( ! m_pRowCountListeners ) + m_pRowCountListeners.reset( + new comphelper::OInterfaceContainerHelper2( m_aEventListenerMutex ) ); + m_pRowCountListeners->addInterface( xListener ); + } + else + throw beans::UnknownPropertyException( aPropertyName ); +} + + +void SAL_CALL XResultSet_impl::removePropertyChangeListener( + const OUString& aPropertyName, + const uno::Reference< beans::XPropertyChangeListener >& aListener ) +{ + if( aPropertyName == "IsRowCountFinal" && + m_pIsFinalListeners ) + { + osl::MutexGuard aGuard( m_aMutex ); + m_pIsFinalListeners->removeInterface( aListener ); + } + else if ( aPropertyName == "RowCount" && + m_pRowCountListeners ) + { + osl::MutexGuard aGuard( m_aMutex ); + + m_pRowCountListeners->removeInterface( 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 000000000..1979abeab --- /dev/null +++ b/ucb/source/ucp/file/filrset.hxx @@ -0,0 +1,437 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ +#ifndef INCLUDED_UCB_SOURCE_UCP_FILE_FILRSET_HXX +#define INCLUDED_UCB_SOURCE_UCP_FILE_FILRSET_HXX + +#include <vector> +#include <osl/file.hxx> + +#include <comphelper/interfacecontainer2.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() { 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; + + osl::Mutex m_aMutex; + osl::Mutex m_aEventListenerMutex; + std::unique_ptr<comphelper::OInterfaceContainerHelper2> m_pDisposeEventListeners; + std::unique_ptr<comphelper::OInterfaceContainerHelper2> m_pRowCountListeners; + std::unique_ptr<comphelper::OInterfaceContainerHelper2> m_pIsFinalListeners; + + 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(); + void isFinalChanged(); + }; + + +} // end namespace fileaccess + + +#endif + +/* 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 000000000..3031510a6 --- /dev/null +++ b/ucb/source/ucp/file/filstr.cxx @@ -0,0 +1,274 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this 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( ) +{ + { + osl::MutexGuard aGuard( m_aMutex ); + m_bInputStreamCalled = true; + } + return uno::Reference< io::XInputStream >( this ); +} + + +uno::Reference< io::XOutputStream > SAL_CALL +XStream_impl::getOutputStream( ) +{ + { + osl::MutexGuard 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 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 ) { + io::IOException ex; + ex.Message = "could not close file"; + throw ex; + } + + m_nIsOpen = false; + } +} + +void SAL_CALL +XStream_impl::closeInput() +{ + osl::MutexGuard aGuard( m_aMutex ); + m_bInputStreamCalled = false; + + if( ! m_bOutputStreamCalled ) + closeStream(); +} + + +void SAL_CALL +XStream_impl::closeOutput() +{ + osl::MutexGuard 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", + static_cast< OWeakObject * >(this)); + } +} + +/* 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 000000000..38d786dff --- /dev/null +++ b/ucb/source/ucp/file/filstr.hxx @@ -0,0 +1,149 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ +#ifndef INCLUDED_UCB_SOURCE_UCP_FILE_FILSTR_HXX +#define INCLUDED_UCB_SOURCE_UCP_FILE_FILSTR_HXX + +#include <osl/mutex.hxx> +#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 <cppuhelper/implbase.hxx> + +#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: + + XStream_impl( const OUString& aUncPath, bool bLock ); + + /** + * Returns an error code as given by filerror.hxx + */ + + sal_Int32 CtorSuccess() { 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; + + private: + + osl::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 + +#endif + +/* 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 000000000..8d839d726 --- /dev/null +++ b/ucb/source/ucp/file/filtask.cxx @@ -0,0 +1,2959 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this 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 + +static const OUStringLiteral Title( "Title" ); +static const OUStringLiteral CasePreservingURL( "CasePreservingURL" ); +static const OUStringLiteral IsDocument( "IsDocument" ); +static const OUStringLiteral IsFolder( "IsFolder" ); +static const OUStringLiteral DateModified( "DateModified" ); +static const OUStringLiteral Size( "Size" ); +static const OUStringLiteral IsVolume( "IsVolume" ); +static const OUStringLiteral IsRemoveable( "IsRemoveable" ); +static const OUStringLiteral IsRemote( "IsRemote" ); +static const OUStringLiteral IsCompactDisc( "IsCompactDisc" ); +static const OUStringLiteral IsFloppy( "IsFloppy" ); +static const OUStringLiteral IsHidden( "IsHidden" ); +static const OUStringLiteral ContentType( "ContentType" ); +static const OUStringLiteral IsReadOnly( "IsReadOnly" ); +static const OUStringLiteral CreatableContentsInfo( "CreatableContentsInfo" ); +const OUStringLiteral TaskManager::FolderContentType( "application/vnd.sun.staroffice.fsys-folder" ); +const OUStringLiteral TaskManager::FileContentType( "application/vnd.sun.staroffice.fsys-file" ); + +TaskManager::TaskManager( const uno::Reference< uno::XComponentContext >& rxContext, + FileProvider* pProvider, bool bWithConfig ) + : m_nCommandId( 0 ), + m_pProvider( pProvider ), + m_xContext( rxContext ), + m_sCommandInfo( 9 ) +{ + // 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 ) ); + + // Commands + m_sCommandInfo[0].Name = "getCommandInfo"; + m_sCommandInfo[0].Handle = -1; + m_sCommandInfo[0].ArgType = cppu::UnoType<void>::get(); + + m_sCommandInfo[1].Name = "getPropertySetInfo"; + m_sCommandInfo[1].Handle = -1; + m_sCommandInfo[1].ArgType = cppu::UnoType<void>::get(); + + m_sCommandInfo[2].Name = "getPropertyValues"; + m_sCommandInfo[2].Handle = -1; + m_sCommandInfo[2].ArgType = cppu::UnoType<uno::Sequence< beans::Property >>::get(); + + m_sCommandInfo[3].Name = "setPropertyValues"; + m_sCommandInfo[3].Handle = -1; + m_sCommandInfo[3].ArgType = cppu::UnoType<uno::Sequence< beans::PropertyValue >>::get(); + + m_sCommandInfo[4].Name = "open"; + m_sCommandInfo[4].Handle = -1; + m_sCommandInfo[4].ArgType = cppu::UnoType<OpenCommandArgument>::get(); + + m_sCommandInfo[5].Name = "transfer"; + m_sCommandInfo[5].Handle = -1; + m_sCommandInfo[5].ArgType = cppu::UnoType<TransferInfo>::get(); + + m_sCommandInfo[6].Name = "delete"; + m_sCommandInfo[6].Handle = -1; + m_sCommandInfo[6].ArgType = cppu::UnoType<sal_Bool>::get(); + + m_sCommandInfo[7].Name = "insert"; + m_sCommandInfo[7].Handle = -1; + m_sCommandInfo[7].ArgType = cppu::UnoType<InsertCommandArgument>::get(); + + m_sCommandInfo[8].Name = "createNewContent"; + m_sCommandInfo[8].Handle = -1; + m_sCommandInfo[8].ArgType = cppu::UnoType<ucb::ContentInfo>::get(); + + 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 ) +{ + osl::MutexGuard 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) +{ + osl::ClearableMutexGuard 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.clear(); + + if( ErrorCode != TASKHANDLER_NO_ERROR ) + throw_handler( + ErrorCode, + MinorCode, + xComEnv, + aUncPath, + pContent, + isHandled); +} + + +void TaskManager::clearError( sal_Int32 CommandId ) +{ + osl::MutexGuard 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) +{ + osl::MutexGuard 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 ) +{ + osl::MutexGuard aGuard( m_aMutex ); + TaskMap::iterator it = m_aTaskMap.find( CommandId ); + if( it != m_aTaskMap.end() ) + it->second.installError( ErrorCode,MinorCode ); +} + + +sal_Int32 +TaskManager::getCommandId() +{ + osl::MutexGuard aGuard( m_aMutex ); + return ++m_nCommandId; +} + + +void TaskManager::handleTask( + sal_Int32 CommandId, + const uno::Reference< task::XInteractionRequest >& request ) +{ + osl::MutexGuard 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 ) +{ + osl::MutexGuard 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 ) +{ + osl::MutexGuard aGuard( m_aMutex ); + + ContentMap::iterator it = m_aContent.find( aUnqPath ); + if( it == m_aContent.end() ) + return; + + it->second.notifier.erase(std::remove(it->second.notifier.begin(), it->second.notifier.end(), pNotifier), it->second.notifier.end()); + + 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 ); + + TaskManager::PropertySet::iterator it1 = m_aDefaultProperties.find( newProperty ); + if( it1 != m_aDefaultProperties.end() ) + throw beans::PropertyExistException( THROW_WHERE ); + + { + osl::MutexGuard 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 ); + + TaskManager::PropertySet::iterator it1 = m_aDefaultProperties.find( oldProperty ); + if( it1 != m_aDefaultProperties.end() ) + throw beans::NotRemoveableException( THROW_WHERE ); + + osl::MutexGuard 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 ); + } + } + 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 uno::Reference< io::XInputStream >( pInputStream.get() ); +} + + +/*********************************************************************************/ +/* */ +/* 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 uno::Reference< io::XStream >( pStream.get() ); +} + + +/*********************************************************************************/ +/* */ +/* 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 uno::Reference< XDynamicResultSet > ( p.get() ); +} + + +/*********************************************************************************/ +/* */ +/* info_c implementation */ +/* */ +/*********************************************************************************/ +// Info for commands + +uno::Reference< XCommandInfo > +TaskManager::info_c() +{ + XCommandInfo_impl* p = new XCommandInfo_impl( this ); + return uno::Reference< XCommandInfo >( p ); +} + + +/*********************************************************************************/ +/* */ +/* info_p-Implementation */ +/* */ +/*********************************************************************************/ +// Info for the properties + +uno::Reference< beans::XPropertySetInfo > +TaskManager::info_p( const OUString& aUnqPath ) +{ + osl::MutexGuard aGuard( m_aMutex ); + XPropertySetInfo_impl* p = new XPropertySetInfo_impl( this,aUnqPath ); + return uno::Reference< beans::XPropertySetInfo >( p ); +} + + +/*********************************************************************************/ +/* */ +/* 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 ) +{ + osl::MutexGuard aGuard( m_aMutex ); + + sal_Int32 propChanged = 0; + uno::Sequence< uno::Any > ret( values.getLength() ); + uno::Sequence< beans::PropertyChangeEvent > seqChanged( values.getLength() ); + + TaskManager::ContentMap::iterator it = m_aContent.find( aUnqPath ); + PropertySet& properties = it->second.properties; + TaskManager::PropertySet::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() ) + { + ret[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 ) + { + ret[i] <<= lang::IllegalAccessException( THROW_WHERE ); + continue; + } + + seqChanged[ propChanged ].PropertyName = values[i].Name; + seqChanged[ propChanged ].PropertyHandle = -1; + seqChanged[ propChanged ].Further = false; + seqChanged[ propChanged ].OldValue = aAny; + seqChanged[ 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 + ret[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)} + })); + ret[i] <<= InteractiveAugmentedIOException( + OUString(), + nullptr, + task::InteractionClassification_ERROR, + IOErrorCode_GENERAL, + names ); + } + } + else + ret[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; + } + ret[i] <<= InteractiveAugmentedIOException( + OUString(), + nullptr, + task::InteractionClassification_ERROR, + ioError, + names ); + } + } + else + ret[i] <<= beans::IllegalTypeException( THROW_WHERE ); + } + } + } // end for + + 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); + + { + osl::MutexGuard aGuard( m_aMutex ); + + TaskManager::ContentMap::iterator it = m_aContent.find( aUnqPath ); + commit( it,aFileStatus ); + + PropertySet& propset = it->second.properties; + + std::transform(properties.begin(), properties.end(), seq.begin(), + [&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(); + }); + } + + XRow_impl* p = new XRow_impl( this,seq ); + return uno::Reference< sdbc::XRow >( p ); +} + + +/********************************************************************************/ +/* */ +/* 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 ) +{ + osl::MutexGuard aGuard( m_aMutex ); + + 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(); + + 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 ); + 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() ) + { + + uno::Reference< ucb::XPersistentPropertySet > xS = m_xFileRegistry->openPropertySet( it->first,create ); + if( xS.is() ) + { + uno::Reference< beans::XPropertyContainer > xC( xS,uno::UNO_QUERY ); + uno::Reference< beans::XPropertyAccess > xA( xS,uno::UNO_QUERY ); + + it->second.xS = xS; + it->second.xC = xC; + it->second.xA = xA; + + // 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( const TaskManager::ContentMap::iterator& it, + const osl::FileStatus& aFileStatus ) +{ + TaskManager::PropertySet::iterator it1; + + if( it->second.properties.empty() ) + { + OUString aPath = it->first; + insertDefaultProperties( 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,isFile,isVolume,isRemoveable,isRemote,isFloppy,isCompactDisc; + + sal_Int64 dirSize = 0; + + if( aFileStatus.isValid( osl_FileStatus_Mask_FileSize ) ) + dirSize = aFileStatus.getFileSize(); + + if( aFileStatus.isValid( osl_FileStatus_Mask_Type ) ) + { + 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::makeAny( isVolume ) ); + + it1 = properties.find( MyProperty( IsFolder ) ); + if( it1 != properties.end() ) + it1->setValue( uno::makeAny( isDirectory ) ); + + it1 = properties.find( MyProperty( IsDocument ) ); + if( it1 != properties.end() ) + it1->setValue( uno::makeAny( 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; + isRemote = aVolumeInfo.getRemoteFlag(); + isRemoveable = aVolumeInfo.getRemoveableFlag(); + isCompactDisc = aVolumeInfo.getCompactDiscFlag(); + isFloppy = aVolumeInfo.getFloppyDiskFlag(); + + it1 = properties.find( MyProperty( IsRemote ) ); + if( it1 != properties.end() ) + it1->setValue( uno::makeAny( isRemote ) ); + + it1 = properties.find( MyProperty( IsRemoveable ) ); + if( it1 != properties.end() ) + it1->setValue( uno::makeAny( isRemoveable ) ); + + it1 = properties.find( MyProperty( IsCompactDisc ) ); + if( it1 != properties.end() ) + it1->setValue( uno::makeAny( isCompactDisc ) ); + + it1 = properties.find( MyProperty( IsFloppy ) ); + if( it1 != properties.end() ) + it1->setValue( uno::makeAny( 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::makeAny( 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::makeAny( 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::makeAny( 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::makeAny( aDateTime ) ); + } + } + + it1 = properties.find( MyProperty( CreatableContentsInfo ) ); + if( it1 != properties.end() ) + it1->setValue( uno::makeAny( + 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; + + insertDefaultProperties( aUnqPath ); + { + osl::MutexGuard aGuard( m_aMutex ); + + TaskManager::ContentMap::iterator it = m_aContent.find( aUnqPath ); + commit( it,aFileStatus ); + + PropertySet& propset = it->second.properties; + + std::transform(properties.begin(), properties.end(), seq.begin(), + [&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(); + }); + } + + XRow_impl* p = new XRow_impl( this,seq ); + row = uno::Reference< sdbc::XRow >( p ); + return true; +} + + +// EventListener + + +std::vector< std::unique_ptr< ContentEventNotifier > > +TaskManager::getContentEventListeners( const OUString& aName ) +{ + std::vector< std::unique_ptr<ContentEventNotifier> > listeners; + { + osl::MutexGuard 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::unique_ptr<ContentEventNotifier> notifier = pointer->cCEL(); + if( notifier ) + listeners.push_back( std::move(notifier) ); + } + } + } + return listeners; +} + + +std::vector< std::unique_ptr<ContentEventNotifier> > +TaskManager::getContentDeletedEventListeners( const OUString& aName ) +{ + std::vector< std::unique_ptr< ContentEventNotifier > > listeners; + { + osl::MutexGuard 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::unique_ptr<ContentEventNotifier> notifier = pointer->cDEL(); + if( notifier ) + listeners.push_back( std::move(notifier) ); + } + } + } + return listeners; +} + +void TaskManager::notifyInsert(const std::vector<std::unique_ptr<ContentEventNotifier>>& listeners, + const OUString& aChildName) +{ + for (const auto & l : listeners ) + { + l->notifyChildInserted( aChildName ); + } +} + +void TaskManager::notifyContentDeleted( + const std::vector<std::unique_ptr<ContentEventNotifier>>& listeners) +{ + for( auto const & l : listeners ) + { + l->notifyDeleted(); + } +} + +void TaskManager::notifyContentRemoved( + const std::vector<std::unique_ptr<ContentEventNotifier>>& listeners, const OUString& aChildName) +{ + for( auto const & l : listeners ) + { + l->notifyRemoved( aChildName ); + } +} + + +std::vector< std::unique_ptr< PropertySetInfoChangeNotifier > > +TaskManager::getPropertySetListeners( const OUString& aName ) +{ + std::vector< std::unique_ptr< PropertySetInfoChangeNotifier > > listeners; + { + osl::MutexGuard 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::unique_ptr<PropertySetInfoChangeNotifier> notifier = pointer->cPSL(); + if( notifier ) + listeners.push_back( std::move(notifier) ); + } + } + } + return listeners; +} + +void TaskManager::notifyPropertyAdded( + const std::vector<std::unique_ptr<PropertySetInfoChangeNotifier>>& listeners, + const OUString& aPropertyName) +{ + for( auto const & l : listeners ) + { + l->notifyPropertyAdded( aPropertyName ); + } +} + +void TaskManager::notifyPropertyRemoved( + const std::vector<std::unique_ptr<PropertySetInfoChangeNotifier>>& listeners, + const OUString& aPropertyName) +{ + for( auto const & l : listeners ) + { + l->notifyPropertyRemoved( aPropertyName ); + } +} + + +std::vector< std::unique_ptr< ContentEventNotifier > > +TaskManager::getContentExchangedEventListeners( const OUString& aOldPrefix, + const OUString& aNewPrefix, + bool withChildren ) +{ + std::vector< std::unique_ptr< ContentEventNotifier > > aVector; + + sal_Int32 count; + OUString aOldName; + OUString aNewName; + std::vector< OUString > oldChildList; + + { + osl::MutexGuard 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::unique_ptr<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 + for( const auto& rCopyPtr : copyList ) + itnew->second.notifier.push_back( rCopyPtr ); + } + } + } + } + + return aVector; +} + +void TaskManager::notifyContentExchanged( + const std::vector<std::unique_ptr<ContentEventNotifier>>& listeners_vec) +{ + for( auto & l : listeners_vec) + { + l->notifyExchanged(); + } +} + + +std::vector< std::unique_ptr<PropertyChangeNotifier> > +TaskManager::getPropertyChangeNotifier( const OUString& aName ) +{ + std::vector< std::unique_ptr<PropertyChangeNotifier> > listeners; + { + osl::MutexGuard 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::unique_ptr<PropertyChangeNotifier> notifier = pointer->cPCL(); + if( notifier ) + listeners.push_back( std::move(notifier) ); + } + } + } + return listeners; +} + +void TaskManager::notifyPropertyChanges( + const std::vector<std::unique_ptr<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 + osl::MutexGuard 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< ucb::ContentInfo > seq(2); + + // file + seq[0].Type = FileContentType; + seq[0].Attributes = ucb::ContentInfoAttribute::INSERT_WITH_INPUTSTREAM + | ucb::ContentInfoAttribute::KIND_DOCUMENT; + + uno::Sequence< beans::Property > props( 1 ); + props[0] = beans::Property( + "Title", + -1, + cppu::UnoType<OUString>::get(), + beans::PropertyAttribute::MAYBEVOID + | beans::PropertyAttribute::BOUND ); + seq[0].Properties = props; + + // folder + seq[1].Type = FolderContentType; + seq[1].Attributes = ucb::ContentInfoAttribute::KIND_FOLDER; + seq[1].Properties = props; + return seq; +} + +/*******************************************************************************/ +/* */ +/* some miscellaneous static functions */ +/* */ +/*******************************************************************************/ + +void +TaskManager::getScheme( OUString& Scheme ) +{ + Scheme = "file"; +} + +OUString +TaskManager::getImplementationName_static() +{ + return "com.sun.star.comp.ucb.FileProvider"; +} + + +uno::Sequence< OUString > +TaskManager::getSupportedServiceNames_static() +{ + return { "com.sun.star.ucb.FileContentProvider" }; +} + +/* 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 000000000..78e7e8c82 --- /dev/null +++ b/ucb/source/ucp/file/filtask.hxx @@ -0,0 +1,657 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ +#ifndef INCLUDED_UCB_SOURCE_UCP_FILE_FILTASK_HXX +#define INCLUDED_UCB_SOURCE_UCP_FILE_FILTASK_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 <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( + const css::uno::Reference< css::ucb::XCommandEnvironment >& xCommandEnv ) + : m_bHandled( false ), + m_nErrorCode( TASKHANDLER_NO_ERROR ), + m_nMinorCode( TASKHANDLER_NO_ERROR ), + m_xCommandEnvironment( 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: + + osl::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( const css::uno::Any& theValue ) const; + inline void setState( const css::beans::PropertyState& theState ) const; + }; + + struct eMyProperty + { + bool operator()( const MyProperty& rKey1, const MyProperty& rKey2 ) const + { + return rKey1.getPropertyName() == rKey2.getPropertyName(); + } + }; + + struct hMyProperty + { + size_t operator()( const MyProperty& rName ) const + { + return rName.getPropertyName().hashCode(); + } + }; + + typedef std::unordered_set< MyProperty,hMyProperty,eMyProperty > PropertySet; + typedef std::vector< Notifier* > NotifierList; + + + class UnqPathData + { + public: + UnqPathData(); + UnqPathData(UnqPathData&&); + ~UnqPathData(); + + PropertySet properties; + NotifierList 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: + + /********************************************************************************/ + /* get eventListeners */ + /********************************************************************************/ + + std::vector< std::unique_ptr< ContentEventNotifier > > + getContentEventListeners( const OUString& aName ); + + std::vector< std::unique_ptr< ContentEventNotifier > > + getContentDeletedEventListeners( const OUString& aName ); + + std::vector< std::unique_ptr < ContentEventNotifier > > + getContentExchangedEventListeners( const OUString& aOldPrefix, + const OUString& aNewPrefix, + bool withChildren ); + + std::vector< std::unique_ptr< PropertyChangeNotifier > > + getPropertyChangeNotifier( const OUString& aName ); + + std::vector< std::unique_ptr< PropertySetInfoChangeNotifier > > + getPropertySetListeners( const OUString& aName ); + + + /********************************************************************************/ + /* notify eventListeners */ + /********************************************************************************/ + + static void notifyPropertyChanges( + const std::vector<std::unique_ptr<PropertyChangeNotifier>>& listeners, + const css::uno::Sequence<css::beans::PropertyChangeEvent>& seqChanged); + + static void notifyContentExchanged( + const std::vector<std::unique_ptr<ContentEventNotifier>>& listeners_vec); + + static void + notifyInsert(const std::vector<std::unique_ptr<ContentEventNotifier>>& listeners, + const OUString& aChildName); + + static void + notifyContentDeleted(const std::vector<std::unique_ptr<ContentEventNotifier>>& listeners); + + static void + notifyContentRemoved(const std::vector<std::unique_ptr<ContentEventNotifier>>& listeners, + const OUString& aChildName); + + static void notifyPropertyAdded( + const std::vector<std::unique_ptr<PropertySetInfoChangeNotifier>>& listeners, + const OUString& aPropertyName); + + static void notifyPropertyRemoved( + const std::vector<std::unique_ptr<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( + 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 const OUStringLiteral FolderContentType; + static const OUStringLiteral FileContentType; + + + private: + + PropertySet m_aDefaultProperties; + css::uno::Sequence< css::ucb::CommandInfo > m_sCommandInfo; + + public: + // Miscellaneous: + // Methods for "writeComponentInfo" and "createComponentFactory" + + static void getScheme( OUString& Scheme ); + + static OUString getImplementationName_static(); + + static css::uno::Sequence< OUString > getSupportedServiceNames_static(); + }; + +} // end namespace TaskHandling + +#endif + +/* 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 000000000..182713f76 --- /dev/null +++ b/ucb/source/ucp/file/prov.cxx @@ -0,0 +1,496 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this 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/factory.hxx> +#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 + + +extern "C" SAL_DLLPUBLIC_EXPORT void * ucpfile_component_getFactory( + const char * pImplName, void * pServiceManager, void * ) +{ + void * pRet = nullptr; + + Reference< XMultiServiceFactory > xSMgr( + static_cast< XMultiServiceFactory * >( pServiceManager ) ); + Reference< XSingleServiceFactory > xFactory; + + + // File Content Provider. + + + if ( fileaccess::TaskManager::getImplementationName_static(). + equalsAscii( pImplName ) ) + { + xFactory = FileProvider::createServiceFactory( xSMgr ); + } + + + if ( xFactory.is() ) + { + xFactory->acquire(); + pRet = xFactory.get(); + } + + return pRet; +} + +/****************************************************************************/ +/* */ +/* */ +/* 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 fileaccess::TaskManager::getImplementationName_static(); +} + +sal_Bool SAL_CALL FileProvider::supportsService(const OUString& ServiceName ) +{ + return cppu::supportsService(this, ServiceName); +} + +Sequence< OUString > SAL_CALL +FileProvider::getSupportedServiceNames() +{ + return fileaccess::TaskManager::getSupportedServiceNames_static(); +} + +Reference< XSingleServiceFactory > +FileProvider::createServiceFactory( + const Reference< XMultiServiceFactory >& rxServiceMgr ) +{ + return cppu::createSingleFactory( + rxServiceMgr, + fileaccess::TaskManager::getImplementationName_static(), + FileProvider::CreateInstance, + fileaccess::TaskManager::getSupportedServiceNames_static() ); +} + +Reference< XInterface > SAL_CALL +FileProvider::CreateInstance( + const Reference< XMultiServiceFactory >& xMultiServiceFactory ) +{ + XServiceInfo* xP = new FileProvider(comphelper::getComponentContext(xMultiServiceFactory)); + return Reference< XInterface >::query( xP ); +} + + +// 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(); + FileContentIdentifier* p = new FileContentIdentifier( ContentId,false ); + return Reference< XContentIdentifier >( p ); +} + + +//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() + throw() override; + + virtual void SAL_CALL + release() + throw() 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( 3 ) +{ + m_seq[0] = Property( "HostName", + -1, + cppu::UnoType<OUString>::get(), + PropertyAttribute::READONLY ); + + m_seq[1] = Property( "HomeDirectory", + -1, + cppu::UnoType<OUString>::get(), + PropertyAttribute::READONLY ); + + m_seq[2] = Property( "FileSystemNotation", + -1, + cppu::UnoType<sal_Int32>::get(), + PropertyAttribute::READONLY ); +} + +void SAL_CALL +XPropertySetInfoImpl2::acquire() + throw() +{ + OWeakObject::acquire(); +} + + +void SAL_CALL +XPropertySetInfoImpl2::release() + throw() +{ + 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(m_seq.begin(), m_seq.end(), + [&aName](const Property& rProp) { return rProp.Name == aName; }); + if (pProp != m_seq.end()) + 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(m_seq.begin(), m_seq.end(), + [&aName](const Property& rProp) { return rProp.Name == aName; }); +} + + +void FileProvider::initProperties() +{ + osl::MutexGuard aGuard( m_aMutex ); + if( ! m_xPropertySetInfo.is() ) + { + 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; + + XPropertySetInfoImpl2* p = new XPropertySetInfoImpl2(); + m_xPropertySetInfo.set( p ); + } +} + + +// 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; +} + +/* 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 000000000..845ab416d --- /dev/null +++ b/ucb/source/ucp/file/prov.hxx @@ -0,0 +1,171 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#ifndef INCLUDED_UCB_SOURCE_UCP_FILE_PROV_HXX +#define INCLUDED_UCB_SOURCE_UCP_FILE_PROV_HXX + +#include <osl/mutex.hxx> +#include <com/sun/star/uno/XInterface.hpp> +#include <com/sun/star/uno/XComponentContext.hpp> +#include <com/sun/star/lang/XInitialization.hpp> +#include <com/sun/star/lang/XSingleServiceFactory.hpp> +#include <com/sun/star/lang/XMultiServiceFactory.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> + +// 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; + + + static css::uno::Reference< css::lang::XSingleServiceFactory > + createServiceFactory( + const css::uno::Reference< css::lang::XMultiServiceFactory >& rxServiceMgr ); + + static css::uno::Reference< css::uno::XInterface > SAL_CALL + CreateInstance( + const css::uno::Reference< css::lang::XMultiServiceFactory >& xMultiServiceFactory ); + + // 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(); + osl::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 + +#endif + +/* 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 000000000..53ebf6a0c --- /dev/null +++ b/ucb/source/ucp/file/ucpfile1.component @@ -0,0 +1,25 @@ +<?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@" + prefix="ucpfile" xmlns="http://openoffice.org/2010/uno-components"> + <implementation name="com.sun.star.comp.ucb.FileProvider"> + <service name="com.sun.star.ucb.FileContentProvider"/> + </implementation> +</component> diff --git a/ucb/source/ucp/ftp/curl.hxx b/ucb/source/ucp/ftp/curl.hxx new file mode 100644 index 000000000..f89548d9d --- /dev/null +++ b/ucb/source/ucp/ftp/curl.hxx @@ -0,0 +1,27 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#ifndef INCLUDED_UCB_SOURCE_UCP_FTP_CURL_HXX +#define INCLUDED_UCB_SOURCE_UCP_FTP_CURL_HXX + +#include <curl/curl.h> + +#endif + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/ucb/source/ucp/ftp/ftpcfunc.cxx b/ucb/source/ucp/ftp/ftpcfunc.cxx new file mode 100644 index 000000000..a39043aa7 --- /dev/null +++ b/ucb/source/ucp/ftp/ftpcfunc.cxx @@ -0,0 +1,50 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this 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/file.h> +#include "ftpcontentidentifier.hxx" +#include "ftpcfunc.hxx" + +using namespace ftp; +using namespace com::sun::star::uno; + +extern "C" { + + int file_write(void *buffer,size_t size,size_t nmemb,void *stream) + { + oslFileHandle aFile = reinterpret_cast< oslFileHandle >( stream ); + if( !aFile ) + return 0; + + sal_uInt64 nWritten = 0; + sal_uInt64 nToWrite( size * nmemb ); + osl_writeFile( aFile, buffer, nToWrite, &nWritten ); + + return nWritten != nToWrite ? 0 : nmemb; + } + +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/ucb/source/ucp/ftp/ftpcfunc.hxx b/ucb/source/ucp/ftp/ftpcfunc.hxx new file mode 100644 index 000000000..118faf517 --- /dev/null +++ b/ucb/source/ucp/ftp/ftpcfunc.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 . + */ + +/************************************************************************** + TODO + ************************************************************************** + + *************************************************************************/ +#ifndef INCLUDED_UCB_SOURCE_UCP_FTP_FTPCFUNC_HXX +#define INCLUDED_UCB_SOURCE_UCP_FTP_FTPCFUNC_HXX + +#include <stddef.h> + + +extern "C" { + + /** callback for curl_easy_perform(), + * forwarding the written content to the stream. + * stream has to be of type oslFileHandle. + */ + + + int file_write(void *buffer,size_t size,size_t nmemb,void *stream); + +} + +#endif + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/ucb/source/ucp/ftp/ftpcontainer.hxx b/ucb/source/ucp/ftp/ftpcontainer.hxx new file mode 100644 index 000000000..b39c5b96b --- /dev/null +++ b/ucb/source/ucp/ftp/ftpcontainer.hxx @@ -0,0 +1,59 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this 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 + ************************************************************************** + + *************************************************************************/ + +#ifndef INCLUDED_UCB_SOURCE_UCP_FTP_FTPCONTAINER_HXX +#define INCLUDED_UCB_SOURCE_UCP_FTP_FTPCONTAINER_HXX + +#include <sal/types.h> + +namespace ftp { + +class MemoryContainer { + +public: + + MemoryContainer(); + + ~MemoryContainer(); + + int append( + const void* pBuffer, + size_t size, + size_t nmemb + ) throw(); + + + sal_uInt32 m_nLen,m_nWritePos; + void *m_pBuffer; +}; + +} + + +extern "C" int memory_write( + void *buffer,size_t size,size_t nmemb,void *stream); + +#endif // INCLUDED_UCB_SOURCE_UCP_FTP_FTPCONTAINER_HXX + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/ucb/source/ucp/ftp/ftpcontent.cxx b/ucb/source/ucp/ftp/ftpcontent.cxx new file mode 100644 index 000000000..e655c6cbe --- /dev/null +++ b/ucb/source/ucp/ftp/ftpcontent.cxx @@ -0,0 +1,846 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this 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/beans/PropertyAttribute.hpp> + +#include "ftpdynresultset.hxx" +#include "ftpresultsetfactory.hxx" +#include "ftpresultsetI.hxx" +#include "ftpcontent.hxx" +#include "ftpcontentprovider.hxx" +#include "ftpdirp.hxx" +#include "ftpcontentidentifier.hxx" +#include "ftpintreq.hxx" + +#include <memory> +#include <vector> +#include <string.h> +#include "curl.hxx" +#include <comphelper/propertysequence.hxx> +#include <cppuhelper/queryinterface.hxx> +#include <cppuhelper/supportsservice.hxx> +#include <cppuhelper/typeprovider.hxx> +#include <ucbhelper/cancelcommandexecution.hxx> +#include <ucbhelper/fd_inputstream.hxx> +#include <ucbhelper/propertyvalueset.hxx> +#include <ucbhelper/simpleauthenticationrequest.hxx> +#include <com/sun/star/lang/IllegalAccessException.hpp> +#include <com/sun/star/lang/NoSupportException.hpp> +#include <com/sun/star/ucb/ContentInfoAttribute.hpp> +#include <com/sun/star/ucb/UnsupportedCommandException.hpp> +#include <com/sun/star/beans/IllegalTypeException.hpp> +#include <com/sun/star/beans/UnknownPropertyException.hpp> +#include <com/sun/star/beans/Property.hpp> +#include <com/sun/star/ucb/XCommandInfo.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/XOutputStream.hpp> +#include <com/sun/star/ucb/UnsupportedDataSinkException.hpp> +#include <com/sun/star/ucb/OpenCommandArgument2.hpp> +#include <com/sun/star/ucb/UnsupportedOpenModeException.hpp> +#include <com/sun/star/ucb/InteractiveNetworkConnectException.hpp> +#include <com/sun/star/ucb/InteractiveNetworkResolveNameException.hpp> +#include <com/sun/star/ucb/InteractiveIOException.hpp> +#include <com/sun/star/ucb/MissingPropertiesException.hpp> +#include <com/sun/star/ucb/MissingInputStreamException.hpp> +#include <com/sun/star/ucb/UnsupportedNameClashException.hpp> +#include <com/sun/star/ucb/OpenMode.hpp> +#include <com/sun/star/ucb/IOErrorCode.hpp> + +using namespace ftp; +using namespace com::sun::star::task; +using namespace com::sun::star::container; +using namespace com::sun::star::lang; +using namespace com::sun::star::uno; +using namespace com::sun::star::ucb; +using namespace com::sun::star::beans; +using namespace com::sun::star::io; +using namespace com::sun::star::sdbc; + + +// Content Implementation. + +FTPContent::FTPContent( const Reference< XComponentContext >& rxContext, + FTPContentProvider* pProvider, + const Reference< XContentIdentifier >& Identifier, + const FTPURL& aFTPURL) + : ContentImplHelper(rxContext,pProvider,Identifier) + , m_pFCP(pProvider) + , m_aFTPURL(aFTPURL) + , m_bInserted(false) + , m_bTitleSet(false) +{ +} + +FTPContent::FTPContent( const Reference< XComponentContext >& rxContext, + FTPContentProvider* pProvider, + const Reference< XContentIdentifier >& Identifier, + const ContentInfo& Info) + : ContentImplHelper(rxContext,pProvider,Identifier) + , m_pFCP(pProvider) + , m_aFTPURL(Identifier->getContentIdentifier(), pProvider) + , m_bInserted(true) + , m_bTitleSet(false) + , m_aInfo(Info) +{ +} + +FTPContent::~FTPContent() +{ +} + +// XInterface methods. + +void SAL_CALL FTPContent::acquire() + throw() +{ + OWeakObject::acquire(); +} + +void SAL_CALL FTPContent::release() + throw() +{ + OWeakObject::release(); +} + +css::uno::Any SAL_CALL FTPContent::queryInterface( const css::uno::Type & rType ) +{ + css::uno::Any aRet = cppu::queryInterface( rType, + static_cast< XTypeProvider* >(this), + static_cast< XServiceInfo* >(this), + static_cast< XContent* >(this), + static_cast< XCommandProcessor* >(this), + static_cast< XContentCreator* >(this), + static_cast< XChild* >(this) + ); + return aRet.hasValue() ? aRet : OWeakObject::queryInterface( rType ); +} + +// XTypeProvider methods. + +css::uno::Sequence< sal_Int8 > SAL_CALL FTPContent::getImplementationId() +{ + return css::uno::Sequence<sal_Int8>(); +} + +css::uno::Sequence< css::uno::Type > SAL_CALL FTPContent::getTypes() +{ + static cppu::OTypeCollection s_aCollection( + cppu::UnoType<XTypeProvider>::get(), + cppu::UnoType<XServiceInfo>::get(), + cppu::UnoType<XContent>::get(), + cppu::UnoType<XCommandProcessor>::get(), + cppu::UnoType<XContentCreator>::get(), + cppu::UnoType<XChild>::get() + ); + + return s_aCollection.getTypes(); +} + + +// XServiceInfo methods. + +OUString SAL_CALL FTPContent::getImplementationName() +{ + return "com.sun.star.comp.FTPContent"; +} + +sal_Bool SAL_CALL FTPContent::supportsService( const OUString& ServiceName ) +{ + return cppu::supportsService( this, ServiceName ); +} + +css::uno::Sequence< OUString > SAL_CALL FTPContent::getSupportedServiceNames() +{ + return { "com.sun.star.ucb.FTPContent" }; +} + + +// XContent methods. + +// virtual +OUString SAL_CALL FTPContent::getContentType() +{ + return FTP_CONTENT_TYPE; +} + +// XCommandProcessor methods. + +//virtual +void SAL_CALL FTPContent::abort( sal_Int32 /*CommandId*/ ) +{ +} + + +ResultSetFactory::ResultSetFactory(const Reference<XComponentContext >& rxContext, + const Reference<XContentProvider >& xProvider, + const Sequence<Property>& seq, + const std::vector<FTPDirentry>& dirvec) + : m_xContext(rxContext), + m_xProvider(xProvider), + m_seq(seq), + m_dirvec(dirvec) +{ +} + + +ResultSetBase* ResultSetFactory::createResultSet() +{ + return new ResultSetI(m_xContext, + m_xProvider, + m_seq, + m_dirvec); +} + + +// XCommandProcessor methods. + +namespace { + +enum ACTION { NOACTION, + THROWAUTHENTICATIONREQUEST, + THROWACCESSDENIED, + THROWINTERACTIVECONNECT, + THROWRESOLVENAME, + THROWQUOTE, + THROWNOFILE, + THROWGENERAL }; + +} + +// virtual +Any SAL_CALL FTPContent::execute( const Command& aCommand, + sal_Int32 /*CommandId*/, + const Reference< + XCommandEnvironment >& Environment) +{ + ACTION action(NOACTION); + Any aRet; + + while(true) + { + try + { + if(action == THROWAUTHENTICATIONREQUEST) + { + // try to get a continuation first + OUString aPassword,aAccount; + m_pFCP->forHost(m_aFTPURL.host(), + m_aFTPURL.port(), + m_aFTPURL.username(), + aPassword, + aAccount); + rtl::Reference<ucbhelper::SimpleAuthenticationRequest> + p( new ucbhelper::SimpleAuthenticationRequest( + m_aFTPURL.ident(false, false), + m_aFTPURL.host(), // ServerName + ucbhelper::SimpleAuthenticationRequest::ENTITY_NA, + OUString(), + ucbhelper::SimpleAuthenticationRequest + ::ENTITY_FIXED, + m_aFTPURL.username(), + ucbhelper::SimpleAuthenticationRequest + ::ENTITY_MODIFY, + aPassword)); + + Reference<XInteractionHandler> xInteractionHandler; + if(Environment.is()) + xInteractionHandler = + Environment->getInteractionHandler(); + + if( xInteractionHandler.is()) { + xInteractionHandler->handle(p.get()); + + Reference<XInterface> xSelection( + p->getSelection().get()); + + if(Reference<XInteractionRetry>( + xSelection,UNO_QUERY).is()) + action = NOACTION; + else if(Reference<XInteractionSupplyAuthentication>( + xSelection,UNO_QUERY).is()) { + m_pFCP->setHost( + m_aFTPURL.host(), + m_aFTPURL.port(), + m_aFTPURL.username(), + p->getAuthenticationSupplier()->getPassword(), + aAccount); + action = NOACTION; + } + } + aRet = p->getRequest(); + } + +// if(aCommand.Name.equalsAscii( +// "getPropertyValues") && +// action != NOACTION) { +// // It is not allowed to throw if +// // command is getPropertyValues +// rtl::Reference<ucbhelper::PropertyValueSet> xRow = +// new ucbhelper::PropertyValueSet(m_xSMgr); +// Sequence<Property> Properties; +// aCommand.Argument >>= Properties; +// for(int i = 0; i < Properties.getLength(); ++i) +// xRow->appendVoid(Properties[i]); +// aRet <<= Reference<XRow>(xRow.get()); +// return aRet; +// } + + switch (action) + { + case NOACTION: + break; + + case THROWAUTHENTICATIONREQUEST: + ucbhelper::cancelCommandExecution( + aRet, + Reference<XCommandEnvironment>(nullptr)); + break; + + case THROWACCESSDENIED: + { + Sequence<Any> seq(comphelper::InitAnyPropertySequence( + { + {"Uri", Any(m_aFTPURL.ident(false,false))} + })); + ucbhelper::cancelCommandExecution( + IOErrorCode_ACCESS_DENIED, + seq, + Environment); + break; + } + case THROWINTERACTIVECONNECT: + { + InteractiveNetworkConnectException excep; + excep.Server = m_aFTPURL.host(); + aRet <<= excep; + ucbhelper::cancelCommandExecution( + aRet, + Environment); + break; + } + case THROWRESOLVENAME: + { + InteractiveNetworkResolveNameException excep; + excep.Server = m_aFTPURL.host(); + aRet <<= excep; + ucbhelper::cancelCommandExecution( + aRet, + Environment); + break; + } + case THROWNOFILE: + { + Sequence<Any> seq(comphelper::InitAnyPropertySequence( + { + {"Uri", Any(m_aFTPURL.ident(false,false))} + })); + ucbhelper::cancelCommandExecution( + IOErrorCode_NO_FILE, + seq, + Environment); + break; + } + case THROWQUOTE: + case THROWGENERAL: + ucbhelper::cancelCommandExecution( + IOErrorCode_GENERAL, + Sequence<Any>(0), + Environment); + break; + } + + if(aCommand.Name == "getPropertyValues") { + Sequence<Property> Properties; + if(!(aCommand.Argument >>= Properties)) + { + aRet <<= IllegalArgumentException( + "Wrong argument type!", + static_cast< cppu::OWeakObject * >(this), + -1); + ucbhelper::cancelCommandExecution(aRet,Environment); + } + + aRet <<= getPropertyValues(Properties); + } + else if(aCommand.Name == "setPropertyValues") + { + Sequence<PropertyValue> propertyValues; + + if( ! ( aCommand.Argument >>= propertyValues ) ) { + aRet <<= IllegalArgumentException( + "Wrong argument type!", + static_cast< cppu::OWeakObject * >(this), + -1); + ucbhelper::cancelCommandExecution(aRet,Environment); + } + + aRet <<= setPropertyValues(propertyValues); + } + else if(aCommand.Name == "getCommandInfo") { + // Note: Implemented by base class. + aRet <<= getCommandInfo(Environment); + } + else if(aCommand.Name == "getPropertySetInfo") { + // Note: Implemented by base class. + aRet <<= getPropertySetInfo(Environment); + } + else if(aCommand.Name == "insert") + { + InsertCommandArgument aInsertArgument; + if ( ! ( aCommand.Argument >>= aInsertArgument ) ) { + aRet <<= IllegalArgumentException( + "Wrong argument type!", + static_cast< cppu::OWeakObject * >(this), + -1); + ucbhelper::cancelCommandExecution(aRet,Environment); + } + insert(aInsertArgument,Environment); + } + else if(aCommand.Name == "delete") { + m_aFTPURL.del(); + deleted(); + } + else if(aCommand.Name == "open") { + OpenCommandArgument2 aOpenCommand; + if ( !( aCommand.Argument >>= aOpenCommand ) ) { + aRet <<= IllegalArgumentException( + "Wrong argument type!", + static_cast< cppu::OWeakObject * >(this), + -1); + + ucbhelper::cancelCommandExecution(aRet,Environment); + } + + if(aOpenCommand.Mode == OpenMode::DOCUMENT) { + // Open as a document + Reference<XActiveDataSink> + xActiveDataSink(aOpenCommand.Sink,UNO_QUERY); + Reference< XOutputStream > + xOutputStream(aOpenCommand.Sink,UNO_QUERY); + + if(xActiveDataSink.is()) { + xActiveDataSink->setInputStream( + new ucbhelper::FdInputStream(m_aFTPURL.open())); + } + else if(xOutputStream.is()) { + Reference<XInputStream> xStream( + new ucbhelper::FdInputStream(m_aFTPURL.open())); + for (;;) { + Sequence<sal_Int8> byte_seq(4096); + sal_Int32 n = xStream->readBytes(byte_seq, 4096); + if (n == 0) { + break; + } + try { + if(byte_seq.getLength() != n) + byte_seq.realloc(n); + xOutputStream->writeBytes(byte_seq); + } catch(const NotConnectedException&) { + + } catch(const BufferSizeExceededException&) { + + } catch(const IOException&) { + + } + } + } + else { + aRet <<= UnsupportedDataSinkException( + OUString(), + static_cast< cppu::OWeakObject * >(this), + aOpenCommand.Sink); + ucbhelper::cancelCommandExecution(aRet,Environment); + } + } + else if(aOpenCommand.Mode == OpenMode::ALL || + aOpenCommand.Mode == OpenMode::DOCUMENTS || + aOpenCommand.Mode == OpenMode::FOLDERS ) { + std::vector<FTPDirentry> resvec = + m_aFTPURL.list(sal_Int16(aOpenCommand.Mode)); + Reference< XDynamicResultSet > xSet + = new DynamicResultSet( + m_xContext, + aOpenCommand, + std::make_unique<ResultSetFactory>(m_xContext, + m_xProvider.get(), + aOpenCommand.Properties, + resvec)); + aRet <<= xSet; + } + else if(aOpenCommand.Mode == + OpenMode::DOCUMENT_SHARE_DENY_NONE || + aOpenCommand.Mode == + OpenMode::DOCUMENT_SHARE_DENY_WRITE) { + // Unsupported OpenMode + aRet <<= UnsupportedOpenModeException( + OUString(), + static_cast< cppu::OWeakObject * >(this), + static_cast< sal_Int16 >(aOpenCommand.Mode)); + ucbhelper::cancelCommandExecution(aRet,Environment); + } + else { + aRet <<= IllegalArgumentException( + "Unexpected OpenMode!", + static_cast< cppu::OWeakObject * >(this), + -1); + + ucbhelper::cancelCommandExecution(aRet,Environment); + } + } else if(aCommand.Name == "createNewContent") { + ContentInfo aArg; + if (!(aCommand.Argument >>= aArg)) { + ucbhelper::cancelCommandExecution( + makeAny( + IllegalArgumentException( + "Wrong argument type!", + static_cast< cppu::OWeakObject * >(this), + -1)), + Environment); + // Unreachable + } + aRet <<= createNewContent(aArg); + } else { + aRet <<= UnsupportedCommandException( + aCommand.Name, + static_cast< cppu::OWeakObject * >(this)); + ucbhelper::cancelCommandExecution(aRet,Environment); + } + + return aRet; + } + catch(const curl_exception& e) + { + if(e.code() == CURLE_COULDNT_CONNECT) + action = THROWINTERACTIVECONNECT; + else if(e.code() == CURLE_COULDNT_RESOLVE_HOST ) + action = THROWRESOLVENAME; + else if(e.code() == CURLE_FTP_USER_PASSWORD_INCORRECT || + e.code() == CURLE_LOGIN_DENIED || + e.code() == CURLE_BAD_PASSWORD_ENTERED || + e.code() == CURLE_FTP_WEIRD_PASS_REPLY) + action = THROWAUTHENTICATIONREQUEST; + else if(e.code() == CURLE_FTP_ACCESS_DENIED) + action = THROWACCESSDENIED; + else if(e.code() == CURLE_FTP_QUOTE_ERROR) + action = THROWQUOTE; + else if(e.code() == CURLE_FTP_COULDNT_RETR_FILE) + action = THROWNOFILE; + else + // nothing known about the cause of the error + action = THROWGENERAL; + } + } +} + +#define FTP_FILE "application/vnd.sun.staroffice.ftp-file" + +#define FTP_FOLDER "application/vnd.sun.staroffice.ftp-folder" + +Sequence<ContentInfo > SAL_CALL +FTPContent::queryCreatableContentsInfo( ) +{ + return queryCreatableContentsInfo_Static(); +} + +// static +Sequence<ContentInfo > +FTPContent::queryCreatableContentsInfo_Static( ) +{ + Sequence< ContentInfo > seq(2); + + seq[0].Type = FTP_FILE; + seq[0].Attributes = ContentInfoAttribute::INSERT_WITH_INPUTSTREAM + | ContentInfoAttribute::KIND_DOCUMENT; + Sequence< Property > props( 1 ); + props[0] = Property( + "Title", + -1, + cppu::UnoType<OUString>::get(), + PropertyAttribute::MAYBEVOID + | PropertyAttribute::BOUND ); + seq[0].Properties = props; + + // folder + seq[1].Type = FTP_FOLDER; + seq[1].Attributes = ContentInfoAttribute::KIND_FOLDER; + seq[1].Properties = props; + + return seq; +} + +Reference<XContent > SAL_CALL +FTPContent::createNewContent( const ContentInfo& Info ) +{ + if( Info.Type =="application/vnd.sun.staroffice.ftp-file" || Info.Type == "application/vnd.sun.staroffice.ftp-folder" ) + return new FTPContent(m_xContext, + m_pFCP, + m_xIdentifier,Info); + else + return Reference<XContent>(nullptr); +} + + +Reference<XInterface > SAL_CALL +FTPContent::getParent( ) +{ + Reference<XContentIdentifier> + xIdent(new FTPContentIdentifier(m_aFTPURL.parent())); + return Reference<XInterface>( m_xProvider->queryContent(xIdent), UNO_QUERY ); +} + + +void SAL_CALL +FTPContent::setParent(const Reference<XInterface >& /*Parent*/ ) +{ + throw NoSupportException(); +} + + +OUString FTPContent::getParentURL() +{ + return m_aFTPURL.parent(); +} + +namespace { + +class InsertData + : public CurlInput { + +public: + + explicit InsertData(const Reference<XInputStream>& xInputStream) + : m_xInputStream(xInputStream) { } + virtual ~InsertData() {} + + // returns the number of bytes actually read + virtual sal_Int32 read(sal_Int8 *dest,sal_Int32 nBytesRequested) override; + +private: + + Reference<XInputStream> m_xInputStream; +}; + +} + +sal_Int32 InsertData::read(sal_Int8 *dest,sal_Int32 nBytesRequested) +{ + sal_Int32 m = 0; + + if(m_xInputStream.is()) { + Sequence<sal_Int8> seq(nBytesRequested); + m = m_xInputStream->readBytes(seq,nBytesRequested); + memcpy(dest,seq.getConstArray(),m); + } + return m; +} + + +void FTPContent::insert(const InsertCommandArgument& aInsertCommand, + const Reference<XCommandEnvironment>& Env) +{ + osl::MutexGuard aGuard(m_aMutex); + + if(m_bInserted && !m_bTitleSet) { + MissingPropertiesException excep; + excep.Properties.realloc(1); + excep.Properties[0] = "Title"; + ucbhelper::cancelCommandExecution(Any(excep), Env); + } + + if(m_bInserted && + m_aInfo.Type == FTP_FILE && + !aInsertCommand.Data.is()) + { + MissingInputStreamException excep; + ucbhelper::cancelCommandExecution(Any(excep), Env); + } + + bool bReplace(aInsertCommand.ReplaceExisting); + + retry: + try { + if(m_aInfo.Type == FTP_FILE) { + InsertData data(aInsertCommand.Data); + m_aFTPURL.insert(bReplace,&data); + } else if(m_aInfo.Type == FTP_FOLDER) + m_aFTPURL.mkdir(bReplace); + } catch(const curl_exception& e) { + if(e.code() == FOLDER_MIGHT_EXIST_DURING_INSERT || + e.code() == FILE_MIGHT_EXIST_DURING_INSERT) { + // Interact + Reference<XInteractionHandler> xInt; + if(Env.is()) + xInt = Env->getInteractionHandler(); + + UnsupportedNameClashException excep; + excep.NameClash = 0; //NameClash::ERROR; + + if(!xInt.is()) { + ucbhelper::cancelCommandExecution(Any(excep), Env); + } + + XInteractionRequestImpl request; + const Reference<XInteractionRequest>& xReq(request.getRequest()); + xInt->handle(xReq); + if (request.approved()) { + bReplace = true; + goto retry; + } + else + throw excep; + } + else + throw; + } + + // May not be reached, because both mkdir and insert can throw curl- + // exceptions + m_bInserted = false; + inserted(); +} + + +Reference< XRow > FTPContent::getPropertyValues( + const Sequence< Property >& seqProp +) +{ + rtl::Reference<ucbhelper::PropertyValueSet> xRow = + new ucbhelper::PropertyValueSet(m_xContext); + + FTPDirentry aDirEntry = m_aFTPURL.direntry(); + + for(const auto& rProp : seqProp) { + const OUString& Name = rProp.Name; + if(Name == "Title") + xRow->appendString(rProp,aDirEntry.m_aName); + else if(Name == "CreatableContentsInfo") + xRow->appendObject(rProp, + makeAny(queryCreatableContentsInfo())); + else if(aDirEntry.m_nMode != INETCOREFTP_FILEMODE_UNKNOWN) { + if(Name == "ContentType") + xRow->appendString(rProp, + (aDirEntry.m_nMode & INETCOREFTP_FILEMODE_ISDIR) + ? OUString(FTP_FOLDER) + : OUString(FTP_FILE) ); + else if(Name == "IsReadOnly") + xRow->appendBoolean(rProp, + (aDirEntry.m_nMode + & INETCOREFTP_FILEMODE_WRITE) == 0 ); + else if(Name == "IsDocument") + xRow->appendBoolean(rProp, + (aDirEntry.m_nMode & + INETCOREFTP_FILEMODE_ISDIR) != INETCOREFTP_FILEMODE_ISDIR); + else if(Name == "IsFolder") + xRow->appendBoolean(rProp, + (aDirEntry.m_nMode & + INETCOREFTP_FILEMODE_ISDIR) == INETCOREFTP_FILEMODE_ISDIR); + else if(Name == "Size") + xRow->appendLong(rProp, + aDirEntry.m_nSize); + else if(Name == "DateCreated") + xRow->appendTimestamp(rProp, + aDirEntry.m_aDate); + else + xRow->appendVoid(rProp); + } else + xRow->appendVoid(rProp); + } + + return Reference<XRow>(xRow.get()); +} + + +Sequence<Any> FTPContent::setPropertyValues( + const Sequence<PropertyValue>& seqPropVal) +{ + Sequence<Any> ret(seqPropVal.getLength()); + Sequence<PropertyChangeEvent > evt; + + osl::MutexGuard aGuard(m_aMutex); + for(sal_Int32 i = 0; i < ret.getLength(); ++i) { + if ( seqPropVal[i].Name == "Title" ) { + OUString Title; + if(!(seqPropVal[i].Value >>= Title)) { + ret[i] <<= IllegalTypeException(); + continue; + } else if(Title.isEmpty()) { + ret[i] <<= IllegalArgumentException(); + continue; + } + + if(m_bInserted) { + m_aFTPURL.child(Title); + m_xIdentifier = + new FTPContentIdentifier(m_aFTPURL.ident(false,false)); + m_bTitleSet = true; + } else + try { + OUString OldTitle = m_aFTPURL.ren(Title); + evt.realloc(1); + evt[0].PropertyName = "Title"; + evt[0].Further = false; + evt[0].PropertyHandle = -1; + evt[0].OldValue <<= OldTitle; + evt[0].NewValue <<= Title; + } catch(const curl_exception&) { + InteractiveIOException excep; + // any better possibility here? + // ( the error code is always CURLE_FTP_QUOTE_ERROR ) + excep.Code = IOErrorCode_ACCESS_DENIED; + ret[i] <<= excep; + } + } else { + Sequence<Property> props = + getProperties(Reference<XCommandEnvironment>(nullptr)); + + // either unknown or read-only + ret[i] <<= UnknownPropertyException(); + const auto& rName = seqPropVal[i].Name; + auto pProp = std::find_if(props.begin(), props.end(), + [&rName](const Property& rProp) { return rProp.Name == rName; }); + if (pProp != props.end()) { + ret[i] <<= IllegalAccessException( + "Property is read-only!", + //props[j].Attributes & PropertyAttribute::READONLY + // ? "Property is read-only!" + // : "Access denied!"), + static_cast< cppu::OWeakObject * >( this )); + } + } + } + + if(evt.hasElements()) { + // title has changed + notifyPropertiesChange(evt); + (void)exchange(new FTPContentIdentifier(m_aFTPURL.ident(false,false))); + } + + return ret; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/ucb/source/ucp/ftp/ftpcontent.hxx b/ucb/source/ucp/ftp/ftpcontent.hxx new file mode 100644 index 000000000..90642fd7f --- /dev/null +++ b/ucb/source/ucp/ftp/ftpcontent.hxx @@ -0,0 +1,149 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#ifndef INCLUDED_UCB_SOURCE_UCP_FTP_FTPCONTENT_HXX +#define INCLUDED_UCB_SOURCE_UCP_FTP_FTPCONTENT_HXX + +#include <ucbhelper/contenthelper.hxx> +#include <com/sun/star/ucb/InsertCommandArgument.hpp> +#include <com/sun/star/ucb/XContentCreator.hpp> +#include "ftpurl.hxx" + +namespace com::sun::star::beans { + struct Property; + struct PropertyValue; +} + +namespace com::sun::star::sdbc { + class XRow; +} + + +namespace ftp +{ + +class FTPContentProvider; + +class FTPContent : public ::ucbhelper::ContentImplHelper, + public css::ucb::XContentCreator +{ +public: + + FTPContent( const css::uno::Reference< + css::uno::XComponentContext >& rxContext, + FTPContentProvider* pProvider, + const css::uno::Reference< + css::ucb::XContentIdentifier >& Identifier, + const FTPURL& FtpUrl); + + FTPContent( const css::uno::Reference< + css::uno::XComponentContext >& rxContext, + FTPContentProvider* pProvider, + const css::uno::Reference< + css::ucb::XContentIdentifier >& Identifier, + const css::ucb::ContentInfo& aInfo); + + + virtual ~FTPContent() override; + + // XInterface + virtual css::uno::Any SAL_CALL queryInterface( const css::uno::Type & rType ) override; + virtual void SAL_CALL acquire() + throw() override; + virtual void SAL_CALL release() + throw() 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; + + // 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; + + // 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; + + virtual void SAL_CALL setParent( const css::uno::Reference< css::uno::XInterface >& Parent ) override; + + /// @throws css::uno::RuntimeException + static css::uno::Sequence< css::ucb::ContentInfo > queryCreatableContentsInfo_Static(); + +private: + + FTPContentProvider *m_pFCP; + FTPURL m_aFTPURL; + bool m_bInserted; + bool m_bTitleSet; + css::ucb::ContentInfo m_aInfo; + + 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; + + css::uno::Reference<css::sdbc::XRow> + getPropertyValues( + const css::uno::Sequence< + css::beans::Property>& seqProp + ); + + css::uno::Sequence<css::uno::Any> + setPropertyValues( const css::uno::Sequence< + css::beans::PropertyValue>& seqPropVal); + + void insert(const css::ucb::InsertCommandArgument&, + const css::uno::Reference< + css::ucb::XCommandEnvironment>&); + }; + +} + +#endif + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/ucb/source/ucp/ftp/ftpcontentcaps.cxx b/ucb/source/ucp/ftp/ftpcontentcaps.cxx new file mode 100644 index 000000000..64dd0e92d --- /dev/null +++ b/ucb/source/ucp/ftp/ftpcontentcaps.cxx @@ -0,0 +1,167 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this 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/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/InsertCommandArgument.hpp> +#include <com/sun/star/util/DateTime.hpp> +#include <com/sun/star/uno/Sequence.hxx> + +#include "ftpcontent.hxx" + +using namespace com::sun::star; +using namespace ftp; + +// virtual +uno::Sequence< beans::Property > FTPContent::getProperties( + const uno::Reference< ucb::XCommandEnvironment > & /*xEnv*/) +{ + #define PROPS_COUNT 8 + + static const beans::Property aPropsInfoTable[] = + { + 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 + ), + beans::Property( + "Size", + -1, + cppu::UnoType<sal_Int64>::get(), + beans::PropertyAttribute::BOUND + | beans::PropertyAttribute::READONLY + ), + beans::Property( + "DateCreated", + -1, + cppu::UnoType<util::DateTime>::get(), + beans::PropertyAttribute::BOUND + | beans::PropertyAttribute::READONLY + ), + beans::Property( + "IsReadOnly", + -1, + cppu::UnoType<bool>::get(), + beans::PropertyAttribute::BOUND + | beans::PropertyAttribute::READONLY + ), + beans::Property( + "CreatableContentsInfo", + -1, + cppu::UnoType<uno::Sequence< ucb::ContentInfo >>::get(), + beans::PropertyAttribute::BOUND + | beans::PropertyAttribute::READONLY + ) + }; + + return uno::Sequence< beans::Property >( aPropsInfoTable, PROPS_COUNT ); +} + + +// virtual +uno::Sequence< ucb::CommandInfo > FTPContent::getCommands( + const uno::Reference< ucb::XCommandEnvironment > & /*xEnv*/ ) +{ +// osl::MutexGuard aGuard( m_aMutex ); + + + // Supported commands + + + #define COMMAND_COUNT 8 + + 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() + ), + ucb::CommandInfo( + "open", + -1, + cppu::UnoType<ucb::OpenCommandArgument2>::get() + ), + ucb::CommandInfo( + "insert", + -1, + cppu::UnoType<ucb::InsertCommandArgument>::get() + ), + ucb::CommandInfo( + "delete", + -1, + cppu::UnoType<bool>::get() + ), + ucb::CommandInfo( + "createNewContent", + -1, + cppu::UnoType<ucb::ContentInfo>::get() + ) + }; + + return uno::Sequence< ucb::CommandInfo >( aCommandInfoTable, COMMAND_COUNT ); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/ucb/source/ucp/ftp/ftpcontentidentifier.cxx b/ucb/source/ucp/ftp/ftpcontentidentifier.cxx new file mode 100644 index 000000000..8bdc1b935 --- /dev/null +++ b/ucb/source/ucp/ftp/ftpcontentidentifier.cxx @@ -0,0 +1,63 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this 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 "ftpcontentidentifier.hxx" + +using namespace ftp; +using namespace com::sun::star::uno; +using namespace com::sun::star::ucb; +using namespace com::sun::star::lang; + + +FTPContentIdentifier::FTPContentIdentifier( + const OUString& ident +) + : m_ident(ident) +{ +} + + +FTPContentIdentifier::~FTPContentIdentifier() +{ +} + + +OUString SAL_CALL +FTPContentIdentifier::getContentIdentifier( +) +{ + return m_ident; +} + + +OUString SAL_CALL +FTPContentIdentifier::getContentProviderScheme( +) +{ + return "ftp"; +} + + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/ucb/source/ucp/ftp/ftpcontentidentifier.hxx b/ucb/source/ucp/ftp/ftpcontentidentifier.hxx new file mode 100644 index 000000000..fd7c2a4a4 --- /dev/null +++ b/ucb/source/ucp/ftp/ftpcontentidentifier.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 . + */ + +/************************************************************************** + TODO + ************************************************************************** + + *************************************************************************/ +#ifndef INCLUDED_UCB_SOURCE_UCP_FTP_FTPCONTENTIDENTIFIER_HXX +#define INCLUDED_UCB_SOURCE_UCP_FTP_FTPCONTENTIDENTIFIER_HXX + +#include <cppuhelper/implbase.hxx> +#include <com/sun/star/ucb/XContentIdentifier.hpp> + + +namespace ftp { + + class FTPContentIdentifier : + public cppu::WeakImplHelper<css::ucb::XContentIdentifier> + { + public: + + explicit FTPContentIdentifier(const OUString& ident); + + virtual ~FTPContentIdentifier() override; + + // XContentIdentifier + + virtual OUString SAL_CALL + getContentIdentifier() override; + + virtual OUString SAL_CALL + getContentProviderScheme() override; + + + private: + + OUString m_ident; + }; + +} + + +#endif + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/ucb/source/ucp/ftp/ftpcontentprovider.cxx b/ucb/source/ucp/ftp/ftpcontentprovider.cxx new file mode 100644 index 000000000..57df6e2f6 --- /dev/null +++ b/ucb/source/ucp/ftp/ftpcontentprovider.cxx @@ -0,0 +1,269 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this 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/lang/WrappedTargetRuntimeException.hpp> +#include <com/sun/star/ucb/IllegalIdentifierException.hpp> +#include <com/sun/star/ucb/UniversalContentBroker.hpp> +#include <cppuhelper/exc_hlp.hxx> +#include <cppuhelper/queryinterface.hxx> +#include <cppuhelper/typeprovider.hxx> +#include <cppuhelper/supportsservice.hxx> +#include <cppuhelper/factory.hxx> +#include <ucbhelper/getcomponentcontext.hxx> +#include "ftpcontentprovider.hxx" +#include "ftpcontent.hxx" +#include "ftploaderthread.hxx" + +using namespace ftp; +using namespace com::sun::star::lang; +using namespace com::sun::star::container; +using namespace com::sun::star::uno; +using namespace com::sun::star::ucb; +using namespace com::sun::star::beans; + +// ContentProvider Implementation. + +FTPContentProvider::FTPContentProvider( const Reference< XComponentContext >& rxContext) + : ::ucbhelper::ContentProviderImplHelper(rxContext) +{ +} + + +// virtual +FTPContentProvider::~FTPContentProvider() +{ + m_ftpLoaderThread.reset(); + m_pProxyDecider.reset(); +} + +// XInterface methods. +void SAL_CALL FTPContentProvider::acquire() + throw() +{ + OWeakObject::acquire(); +} + +void SAL_CALL FTPContentProvider::release() + throw() +{ + OWeakObject::release(); +} + +css::uno::Any SAL_CALL FTPContentProvider::queryInterface( const css::uno::Type & rType ) +{ + css::uno::Any aRet = cppu::queryInterface( rType, + static_cast< XTypeProvider* >(this), + static_cast< XServiceInfo* >(this), + static_cast< XContentProvider* >(this) + ); + return aRet.hasValue() ? aRet : OWeakObject::queryInterface( rType ); +} + +// XTypeProvider methods. +css::uno::Sequence< sal_Int8 > SAL_CALL FTPContentProvider::getImplementationId() +{ + return css::uno::Sequence<sal_Int8>(); +} + +css::uno::Sequence< css::uno::Type > SAL_CALL FTPContentProvider::getTypes() +{ + static cppu::OTypeCollection s_aCollection( + cppu::UnoType<XTypeProvider>::get(), + cppu::UnoType<XServiceInfo>::get(), + cppu::UnoType<XContentProvider>::get() + ); + + return s_aCollection.getTypes(); +} + + +// XServiceInfo methods. + +OUString SAL_CALL FTPContentProvider::getImplementationName() +{ + return getImplementationName_Static(); +} + +OUString FTPContentProvider::getImplementationName_Static() +{ + return "com.sun.star.comp.FTPContentProvider"; +} + +sal_Bool SAL_CALL FTPContentProvider::supportsService( const OUString& ServiceName ) +{ + return cppu::supportsService( this, ServiceName ); +} + +css::uno::Sequence< OUString > SAL_CALL FTPContentProvider::getSupportedServiceNames() +{ + return getSupportedServiceNames_Static(); +} + +/// @throws css::uno::Exception +static css::uno::Reference< css::uno::XInterface > +FTPContentProvider_CreateInstance( const css::uno::Reference< + css::lang::XMultiServiceFactory> & rSMgr ) +{ + css::lang::XServiceInfo* pX = new FTPContentProvider( ucbhelper::getComponentContext(rSMgr) ); + return css::uno::Reference< css::uno::XInterface >::query( pX ); +} + +css::uno::Sequence< OUString > FTPContentProvider::getSupportedServiceNames_Static() +{ + css::uno::Sequence<OUString> aSNS { FTP_CONTENT_PROVIDER_SERVICE_NAME }; + return aSNS; +} + +// Service factory implementation. + +css::uno::Reference< css::lang::XSingleServiceFactory > +FTPContentProvider::createServiceFactory( const css::uno::Reference< + css::lang::XMultiServiceFactory >& rxServiceMgr ) +{ + return cppu::createOneInstanceFactory( + rxServiceMgr, + FTPContentProvider::getImplementationName_Static(), + FTPContentProvider_CreateInstance, + FTPContentProvider::getSupportedServiceNames_Static() ); +} + +// XContentProvider methods. + +// virtual +Reference<XContent> SAL_CALL FTPContentProvider::queryContent( + const Reference< XContentIdentifier >& xCanonicId) +{ + // Check, if a content with given id already exists... + Reference<XContent> xContent = queryExistingContent(xCanonicId).get(); + if(xContent.is()) + return xContent; + + // A new content has to be returned: + { + // Initialize + osl::MutexGuard aGuard( m_aMutex ); + if(!m_ftpLoaderThread || !m_pProxyDecider) + { + try { + init(); + } catch (css::uno::Exception const & ex) { + css::uno::Any anyEx = cppu::getCaughtException(); + throw css::lang::WrappedTargetRuntimeException( ex.Message, + css::uno::Reference< css::uno::XInterface >(), + anyEx ); + } catch( ... ) { + throw RuntimeException(); + } + + if(!m_ftpLoaderThread || !m_pProxyDecider) + throw RuntimeException(); + } + } + + try { + FTPURL aURL(xCanonicId->getContentIdentifier(), + this); + + if(!m_pProxyDecider->shouldUseProxy( + "ftp", + aURL.host(), + aURL.port().toInt32())) + { + xContent = new FTPContent( m_xContext, this,xCanonicId,aURL); + registerNewContent(xContent); + } + else { + Reference<XContentProvider> xProvider(UniversalContentBroker::create( m_xContext )->queryContentProvider("http:")); + if(!xProvider.is()) + throw RuntimeException(); + return xProvider->queryContent(xCanonicId); + } + } catch(const malformed_exception&) { + throw IllegalIdentifierException(); + } + + // may throw IllegalIdentifierException + return xContent; +} + +void FTPContentProvider::init() +{ + m_ftpLoaderThread.reset( new FTPLoaderThread() ); + m_pProxyDecider.reset( new ucbhelper::InternetProxyDecider( m_xContext ) ); +} + +CURL* FTPContentProvider::handle() +{ + // Cannot be zero if called from here; + return m_ftpLoaderThread->handle(); +} + + +void FTPContentProvider::forHost( const OUString& host, + const OUString& port, + const OUString& username, + OUString& password, + OUString& account) +{ + osl::MutexGuard aGuard(m_aMutex); + for(const ServerInfo & i : m_ServerInfo) + if(host == i.host && + port == i.port && + username == i.username ) + { + password = i.password; + account = i.account; + return; + } +} + +bool FTPContentProvider::setHost( const OUString& host, + const OUString& port, + const OUString& username, + const OUString& password, + const OUString& account) +{ + ServerInfo inf; + inf.host = host; + inf.port = port; + inf.username = username; + inf.password = password; + inf.account = account; + + bool present(false); + osl::MutexGuard aGuard(m_aMutex); + for(ServerInfo & i : m_ServerInfo) + if(host == i.host && + port == i.port && + username == i.username) + { + present = true; + i.password = password; + i.account = account; + } + + if(!present) + m_ServerInfo.push_back(inf); + + return !present; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/ucb/source/ucp/ftp/ftpcontentprovider.hxx b/ucb/source/ucp/ftp/ftpcontentprovider.hxx new file mode 100644 index 000000000..042ed99fe --- /dev/null +++ b/ucb/source/ucp/ftp/ftpcontentprovider.hxx @@ -0,0 +1,117 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#ifndef INCLUDED_UCB_SOURCE_UCP_FTP_FTPCONTENTPROVIDER_HXX +#define INCLUDED_UCB_SOURCE_UCP_FTP_FTPCONTENTPROVIDER_HXX + +#include <vector> +#include <ucbhelper/proxydecider.hxx> +#include <ucbhelper/providerhelper.hxx> +#include <com/sun/star/lang/XSingleServiceFactory.hpp> +#include <com/sun/star/lang/XMultiServiceFactory.hpp> +#include "curl.hxx" + +// UNO service name for the provider. This name will be used by the UCB to +// create instances of the provider. + +#define FTP_CONTENT_PROVIDER_SERVICE_NAME "com.sun.star.ucb.FTPContentProvider" +#define FTP_CONTENT_TYPE "application/ftp-content" + +/** + * Definition of ftpcontentprovider + */ +namespace ftp +{ + class FTPLoaderThread; + + class FTPContentProvider: + public ::ucbhelper::ContentProviderImplHelper + { + public: + + explicit FTPContentProvider( const css::uno::Reference< css::uno::XComponentContext >& rxContext ); + + virtual ~FTPContentProvider() override; + + // XInterface + virtual css::uno::Any SAL_CALL queryInterface( const css::uno::Type & rType ) override; + virtual void SAL_CALL acquire() + throw() override; + virtual void SAL_CALL release() + throw() 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; + + static OUString getImplementationName_Static(); + static css::uno::Sequence< OUString > getSupportedServiceNames_Static(); + + static css::uno::Reference< css::lang::XSingleServiceFactory > + createServiceFactory( const css::uno::Reference< + css::lang::XMultiServiceFactory >& rxServiceMgr ); + + // XContentProvider + virtual css::uno::Reference< css::ucb::XContent > SAL_CALL + queryContent( const css::uno::Reference< css::ucb::XContentIdentifier >& Identifier ) override; + + CURL* handle(); + + /** host is in the form host:port. + */ + + void forHost(const OUString& host, + const OUString& port, + const OUString& username, + OUString& password, + OUString& account); + + bool setHost(const OUString& host, + const OUString& port, + const OUString& username, + const OUString& password, + const OUString& account); + + struct ServerInfo + { + OUString host; + OUString port; + OUString username; + OUString password; + OUString account; + }; + + private: + std::unique_ptr<FTPLoaderThread> m_ftpLoaderThread; + std::unique_ptr<ucbhelper::InternetProxyDecider> m_pProxyDecider; + std::vector<ServerInfo> m_ServerInfo; + + void init(); + }; // end class FTPContentProvider + +} // end namespace ftp + +#endif + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/ucb/source/ucp/ftp/ftpdirp.cxx b/ucb/source/ucp/ftp/ftpdirp.cxx new file mode 100644 index 000000000..69bea64ab --- /dev/null +++ b/ucb/source/ucp/ftp/ftpdirp.cxx @@ -0,0 +1,1269 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this 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 "ftpdirp.hxx" +#include <osl/time.h> + + +using namespace ftp; + +static bool ascii_isWhitespace( sal_Unicode ch ) +{ + return ((ch <= 0x20) && ch); +} + + +/*======================================================================== + * + * FTPDirectoryParser implementation. + * + *======================================================================*/ +/* + * parseDOS. + * Accepts one of two styles: + * + * 1 *WSP 1*2DIGIT ("." / "-") 1*2DIGIT ("." / "-") 1*4DIGIT 1*WSP + * 1*2DIGIT ":" 1*2DIGIT [*WSP ("A" / "P") "M"] 1*WSP + * ((DIGIT *(DIGIT / "." / ",")) / "<DIR>") 1*WSP 1*OCTET + * + * interpreted as: mm.dd.yy hh:mm (size / <DIR>) name + * + * 2 *WSP 1*DIGIT 1*WSP *(1*CHAR *WSP) *1("DIR" 1*WSP) 1*2DIGIT "-" 1*2DIGIT + * "-" 1*4DIGIT 1*WSP 1*2DIGIT ":" 1*2DIGIT 1*WSP 1*OCTET + * + * interpreted as: size attribs DIR mm-dd-yy hh:mm name + */ + +bool FTPDirectoryParser::parseDOS ( + FTPDirentry &rEntry, + const char *pBuffer) +{ + bool bDirectory = false; + sal_uInt32 nSize = 0; + sal_uInt16 nYear = 0; + sal_uInt16 nMonth = 0; + sal_uInt16 nDay = 0; + sal_uInt16 nHour = 0; + sal_uInt16 nMinute = 0; + + enum StateType + { + STATE_INIT_LWS, + STATE_MONTH_OR_SIZE, + STATE_1_DAY, STATE_1_YEAR, STATE_1_YEAR_LWS, STATE_1_HOUR, + STATE_1_MINUTE, STATE_1_MINUTE_LWS, STATE_1_AP, + STATE_1_APM, STATE_1_LESS, STATE_1_D, STATE_1_DI, + STATE_1_DIR, STATE_1_SIZE, + STATE_2_SIZE, STATE_2_SIZE_LWS, STATE_2_ATTRIB, + STATE_2_D, STATE_2_DI, STATE_2_DIR_LWS, + STATE_2_MONTH, STATE_2_DAY, STATE_2_YEAR, STATE_2_YEAR_LWS, + STATE_2_HOUR, STATE_2_MINUTE, + STATE_LWS_NAME, + STATE_ERROR + }; + + int nDigits = 0; + enum StateType eState = STATE_INIT_LWS; + for (const char *p = pBuffer; + eState != STATE_ERROR && *p; + ++p) + { + switch (eState) + { + case STATE_INIT_LWS: + if (*p >= '0' && *p <= '9') + { + nMonth = *p - '0'; + nDigits = 1; + eState = STATE_MONTH_OR_SIZE; + } + else if (!ascii_isWhitespace(*p)) + eState = STATE_ERROR; + break; + + case STATE_MONTH_OR_SIZE: + if (*p >= '0' && *p <= '9') + { + nMonth = 10 * nMonth + (*p - '0'); + if (nDigits < 2) + ++nDigits; + else + { + nSize = nMonth; + nMonth = 0; + eState = STATE_2_SIZE; + } + } + else if (ascii_isWhitespace(*p)) + { + nSize = nMonth; + nMonth = 0; + eState = STATE_2_SIZE_LWS; + } + else if ((*p == '.' || *p == '-') && nMonth && nMonth <= 12) + { + nDigits = 0; + eState = STATE_1_DAY; + } + else + eState = STATE_ERROR; + break; + + case STATE_1_DAY: + if (*p >= '0' && *p <= '9') + if (nDigits < 2) + { + nDay = 10 * nDay + (*p - '0'); + ++nDigits; + } + else + eState = STATE_ERROR; + else if ((*p == '.' || *p == '-') && nDay && nDay <= 31) + { + nDigits = 0; + eState = STATE_1_YEAR; + } + else + eState = STATE_ERROR; + break; + + case STATE_1_YEAR: + if (*p >= '0' && *p <= '9') + { + if (nDigits < 4) + { + nYear = 10 * nYear + (*p - '0'); + ++nDigits; + } + else + eState = STATE_ERROR; + } + else + { + if (ascii_isWhitespace(*p)) + eState = STATE_1_YEAR_LWS; + else + eState = STATE_ERROR; + } + break; + + case STATE_1_YEAR_LWS: + if (*p >= '0' && *p <= '9') + { + nHour = *p - '0'; + nDigits = 1; + eState = STATE_1_HOUR; + } + else if (!ascii_isWhitespace(*p)) + eState = STATE_ERROR; + break; + + case STATE_1_HOUR: + if (*p >= '0' && *p <= '9') + if (nDigits < 2) + { + nHour = 10 * nHour + (*p - '0'); + ++nDigits; + } + else + eState = STATE_ERROR; + else if (*p == ':' && nHour < 24) + { + nDigits = 0; + eState = STATE_1_MINUTE; + } + else + eState = STATE_ERROR; + break; + + case STATE_1_MINUTE: + if (*p >= '0' && *p <= '9') + if (nDigits < 2) + { + nMinute = 10 * nMinute + (*p - '0'); + ++nDigits; + } + else + eState = STATE_ERROR; + else if ((*p == 'a' || *p == 'A') && nMinute < 60) + if (nHour >= 1 && nHour <= 11) + eState = STATE_1_AP; + else if (nHour == 12) + { + nHour = 0; + eState = STATE_1_AP; + } + else + eState = STATE_ERROR; + else if ((*p == 'p' || *p == 'P') && nMinute < 60) + if (nHour >= 1 && nHour <= 11) + { + nHour += 12; + eState = STATE_1_AP; + } + else if (nHour == 12) + eState = STATE_1_AP; + else + eState = STATE_ERROR; + else if (ascii_isWhitespace(*p) && (nMinute < 60)) + eState = STATE_1_MINUTE_LWS; + else + eState = STATE_ERROR; + break; + + case STATE_1_MINUTE_LWS: + if (*p == 'a' || *p == 'A') + if (nHour >= 1 && nHour <= 11) + eState = STATE_1_AP; + else if (nHour == 12) + { + nHour = 0; + eState = STATE_1_AP; + } + else + eState = STATE_ERROR; + else if (*p == 'p' || *p == 'P') + if (nHour >= 1 && nHour <= 11) + { + nHour += 12; + eState = STATE_1_AP; + } + else if (nHour == 12) + eState = STATE_1_AP; + else + eState = STATE_ERROR; + else if (*p == '<') + eState = STATE_1_LESS; + else if (*p >= '0' && *p <= '9') + { + nSize = *p - '0'; + eState = STATE_1_SIZE; + } + else if (!ascii_isWhitespace(*p)) + eState = STATE_ERROR; + break; + + case STATE_1_AP: + eState = *p == 'm' || *p == 'M' ? STATE_1_APM : STATE_ERROR; + break; + + case STATE_1_APM: + if (*p == '<') + eState = STATE_1_LESS; + else if (*p >= '0' && *p <= '9') + { + nSize = *p - '0'; + eState = STATE_1_SIZE; + } + else if (!ascii_isWhitespace(*p)) + eState = STATE_ERROR; + break; + + case STATE_1_LESS: + eState = *p == 'd' || *p == 'D' ? STATE_1_D : STATE_ERROR; + break; + + case STATE_1_D: + eState = *p == 'i' || *p == 'I' ? STATE_1_DI : STATE_ERROR; + break; + + case STATE_1_DI: + eState = *p == 'r' || *p == 'R' ? STATE_1_DIR : STATE_ERROR; + break; + + case STATE_1_DIR: + if (*p == '>') + { + bDirectory = true; + eState = STATE_LWS_NAME; + } + else + eState = STATE_ERROR; + break; + + case STATE_1_SIZE: + if (*p >= '0' && *p <= '9') + nSize = 10 * nSize + (*p - '0'); + else if (ascii_isWhitespace(*p)) + eState = STATE_LWS_NAME; + else + eState = STATE_ERROR; + break; + + case STATE_2_SIZE: + if (*p >= '0' && *p <= '9') + nSize = 10 * nSize + (*p - '0'); + else if (ascii_isWhitespace(*p)) + eState = STATE_2_SIZE_LWS; + else + eState = STATE_ERROR; + break; + + case STATE_2_SIZE_LWS: + if (*p == 'd' || *p == 'D') + eState = STATE_2_D; + else if ((*p >= 'a' && *p <= 'z') || (*p >= 'A' && *p <= 'Z')) + eState = STATE_2_ATTRIB; + else if (*p >= '0' && *p <= '9') + { + nMonth = *p - '0'; + nDigits = 1; + eState = STATE_2_MONTH; + } + else if (!ascii_isWhitespace(*p)) + eState = STATE_ERROR; + break; + + case STATE_2_ATTRIB: + if (ascii_isWhitespace(*p)) + eState = STATE_2_SIZE_LWS; + else if ((*p < 'a' || *p > 'z') && (*p < 'A' || *p > 'Z')) + eState = STATE_ERROR; + break; + + case STATE_2_D: + if (*p == 'i' || *p == 'I') + eState = STATE_2_DI; + else if ((*p >= 'a' && *p <= 'z') || (*p >= 'A' && *p <= 'Z')) + eState = STATE_2_ATTRIB; + else if (ascii_isWhitespace(*p)) + eState = STATE_2_SIZE_LWS; + else + eState = STATE_ERROR; + break; + + case STATE_2_DI: + if (*p == 'r' || *p == 'R') + { + bDirectory = true; + eState = STATE_2_DIR_LWS; + } + else + { + if ((*p >= 'a' && *p <= 'z') || (*p >= 'A' && *p <= 'Z')) + eState = STATE_2_ATTRIB; + else if (ascii_isWhitespace(*p)) + eState = STATE_2_SIZE_LWS; + else + eState = STATE_ERROR; + } + break; + + case STATE_2_DIR_LWS: + if (*p >= '0' && *p <= '9') + { + nMonth = *p - '0'; + nDigits = 1; + eState = STATE_2_MONTH; + } + else if (!ascii_isWhitespace(*p)) + eState = STATE_ERROR; + break; + + case STATE_2_MONTH: + if (*p >= '0' && *p <= '9') + if (nDigits < 2) + { + nMonth = 10 * nMonth + (*p - '0'); + ++nDigits; + } + else + eState = STATE_ERROR; + else if (*p == '-' && nMonth && nMonth <= 12) + { + nDigits = 0; + eState = STATE_2_DAY; + } + else + eState = STATE_ERROR; + break; + + case STATE_2_DAY: + if (*p >= '0' && *p <= '9') + if (nDigits < 2) + { + nDay = 10 * nDay + (*p - '0'); + ++nDigits; + } + else + eState = STATE_ERROR; + else if (*p == '-' && nDay && nDay <= 31) + { + nDigits = 0; + eState = STATE_2_YEAR; + } + else + eState = STATE_ERROR; + break; + + case STATE_2_YEAR: + if (*p >= '0' && *p <= '9') + { + if (nDigits < 4) + { + nYear = 10 * nYear + (*p - '0'); + ++nDigits; + } + else + eState = STATE_ERROR; + } + else + { + if (ascii_isWhitespace(*p)) + eState = STATE_2_YEAR_LWS; + else + eState = STATE_ERROR; + } + break; + + case STATE_2_YEAR_LWS: + if (*p >= '0' && *p <= '9') + { + nHour = *p - '0'; + nDigits = 1; + eState = STATE_2_HOUR; + } + else if (!ascii_isWhitespace(*p)) + eState = STATE_ERROR; + break; + + case STATE_2_HOUR: + if (*p >= '0' && *p <= '9') + if (nDigits < 2) + { + nHour = 10 * nHour + (*p - '0'); + ++nDigits; + } + else + eState = STATE_ERROR; + else if (*p == ':' && nHour < 24) + { + nDigits = 0; + eState = STATE_2_MINUTE; + } + else + eState = STATE_ERROR; + break; + + case STATE_2_MINUTE: + if (*p >= '0' && *p <= '9') + { + if (nDigits < 2) + { + nMinute = 10 * nMinute + (*p - '0'); + ++nDigits; + } + else + eState = STATE_ERROR; + } + else + { + if (ascii_isWhitespace(*p) && (nMinute < 60)) + eState = STATE_LWS_NAME; + else + eState = STATE_ERROR; + } + break; + + case STATE_LWS_NAME: + if (!ascii_isWhitespace(*p)) + { + setPath (rEntry.m_aName, p); + if (bDirectory) + rEntry.m_nMode |= INETCOREFTP_FILEMODE_ISDIR; + rEntry.m_nSize = nSize; + + setYear (rEntry.m_aDate, nYear); + + rEntry.m_aDate.SetMonth(nMonth); + rEntry.m_aDate.SetDay(nDay); + rEntry.m_aDate.SetHour(nHour); + rEntry.m_aDate.SetMin(nMinute); + + return true; + } + break; + case STATE_ERROR: + break; + } + } + + return false; +} + +/* + * parseVMS. + * Directory entries may span one or two lines: + * + * entry: *lws name *1(*lws <NEWLINE>) 1*lws size 1*lws datetime rest + * + * name: filename "." filetype ";" version + * filename: 1*39fchar + * filetype: 1*39fchar + * version: non0digit *digit + * + * size: "0" / non0digit *digit + * + * datetime: date 1*lwsp time + * date: day "-" month "-" year + * day: (*1"0" non0digit) / ("1"-"2" digit) / ("3" "0"-"1") + * month: "JAN" / "FEB" / "MAR" / "APR" / "MAY" / "JUN" / "JUL" / "AUG" + * / "SEP" / "OCT" / "NOV" / "DEC" ; all case insensitive + * year: 2digit / 4digit + * time: hour ":" minute + * hour: ((*1"0" / "1") digit) / ("2" "0"-"3") + * minute: "0"-"5" digit + * + * rest: *1(lws *<ANY>) + * + * lws: <TAB> / <SPACE> + * non0digit: "1"-"9" + * digit: "0" / non0digit + * fchar: "A"-"Z" / "a"-"z" / digit / "-" / "_" / "$" + * + * For directories, the returned name is the <filename> part; for non- + * directory files, the returned name is the <filename "." filetype> part. + * An entry is a directory iff its filetype is "DIR" (ignoring case). + * + * The READ, WRITE, and ISLINK mode bits are not supported. + * + * The returned size is the <size> part, multiplied by 512, and with the high + * order bits truncated to fit into a sal_uInt32. + * + */ +bool FTPDirectoryParser::parseVMS ( + FTPDirentry &rEntry, + const char *pBuffer) +{ + static OUString aFirstLineName; + static bool bFirstLineDir = false; + + for (bool bFirstLine = true;; bFirstLine = false) + { + const char *p = pBuffer; + if (bFirstLine) + { + // Skip <*lws> part: + while (*p == '\t' || *p == ' ') + ++p; + + // Parse <filename "."> part: + const char *pFileName = p; + while ((*p >= 'A' && *p <= 'Z') || + (*p >= 'a' && *p <= 'z') || + (*p >= '0' && *p <= '9') || + *p == '-' || *p == '_' || *p == '$') + ++p; + + if (*p != '.' || p == pFileName || p - pFileName > 39) + { + if (!aFirstLineName.isEmpty()) + continue; + else + return false; + } + + // Parse <filetype ";"> part: + const char *pFileType = ++p; + while ((*p >= 'A' && *p <= 'Z') || + (*p >= 'a' && *p <= 'z') || + (*p >= '0' && *p <= '9') || + *p == '-' || *p == '_' || *p == '$') + ++p; + + if (*p != ';' || p == pFileName || p - pFileName > 39) + { + if (!aFirstLineName.isEmpty()) + continue; + else + return false; + } + ++p; + + // Set entry's name and mode (ISDIR flag): + if ((p - pFileType == 4) && + (pFileType[0] == 'D' || pFileType[0] == 'd') && + (pFileType[1] == 'I' || pFileType[1] == 'i') && + (pFileType[2] == 'R' || pFileType[2] == 'r') ) + { + setPath (rEntry.m_aName, pFileName, (pFileType - pFileName)); + rEntry.m_nMode = INETCOREFTP_FILEMODE_ISDIR; + } + else + { + setPath (rEntry.m_aName, pFileName, (p - pFileName)); + rEntry.m_nMode = 0; + } + + // Skip <version> part: + if (*p < '1' || *p > '9') + { + if (!aFirstLineName.isEmpty()) + continue; + else + return false; + } + ++p; + while (*p >= '0' && *p <= '9') + ++p; + + // Parse <1*lws> or <*lws <NEWLINE>> part: + bool bLWS = false; + while (*p == '\t' || *p == ' ') + { + bLWS = true; + ++p; + } + if (*p) + { + if (!bLWS) + { + if (!aFirstLineName.isEmpty()) + continue; + else + return false; + } + } + else + { + /* + * First line of entry spanning two lines, + * wait for second line. + */ + aFirstLineName = rEntry.m_aName; + bFirstLineDir = + ((rEntry.m_nMode & INETCOREFTP_FILEMODE_ISDIR) != 0); + return false; + } + } + else + { + /* + * Second line of entry spanning two lines, + * restore entry's name and mode (ISDIR flag). + */ + rEntry.m_aName = aFirstLineName; + rEntry.m_nMode = (bFirstLineDir ? INETCOREFTP_FILEMODE_ISDIR : 0); + + // Skip <1*lws> part: + if (*p != '\t' && *p != ' ') + return false; + ++p; + while (*p == '\t' || *p == ' ') + ++p; + } + + // Parse <size> part and set entry's size: + if (*p < '0' || *p > '9') + return false; + sal_uInt32 nSize = *p - '0'; + if (*p++ != '0') + while (*p >= '0' && *p <= '9') + nSize = 10 * rEntry.m_nSize + (*p++ - '0'); + rEntry.m_nSize = 512 * nSize; + + // Skip <1*lws> part: + if (*p != '\t' && *p != ' ') + return false; + ++p; + while (*p == '\t' || *p == ' ') + ++p; + + // Parse <day "-"> part and set entry date's day: + sal_uInt16 nDay; + if (*p == '0') + { + ++p; + if (*p < '1' || *p > '9') + return false; + nDay = *p++ - '0'; + } + else if (*p == '1' || *p == '2') + { + nDay = *p++ - '0'; + if (*p >= '0' && *p <= '9') + nDay = 10 * nDay + (*p++ - '0'); + } + else if (*p == '3') + { + ++p; + nDay = (*p == '0' || *p == '1') ? 30 + (*p++ - '0') : 3; + } + else if (*p >= '4' && *p <= '9') + nDay = *p++ - '0'; + else + return false; + + rEntry.m_aDate.SetDay(nDay); + if (*p++ != '-') + return false; + + // Parse <month "-"> part and set entry date's month: + char const * pMonth = p; + sal_Int32 const monthLen = 3; + for (int i = 0; i < monthLen; ++i) + { + if (!((*p >= 'A' && *p <= 'Z') || (*p >= 'a' && *p <= 'z'))) + return false; + ++p; + } + if (rtl_str_compareIgnoreAsciiCase_WithLength( + pMonth, monthLen, "JAN", monthLen) == 0) + rEntry.m_aDate.SetMonth(1); + else if (rtl_str_compareIgnoreAsciiCase_WithLength( + pMonth, monthLen, "FEB", monthLen) == 0) + rEntry.m_aDate.SetMonth(2); + else if (rtl_str_compareIgnoreAsciiCase_WithLength( + pMonth, monthLen, "MAR", monthLen) == 0) + rEntry.m_aDate.SetMonth(3); + else if (rtl_str_compareIgnoreAsciiCase_WithLength( + pMonth, monthLen, "APR", monthLen) == 0) + rEntry.m_aDate.SetMonth(4); + else if (rtl_str_compareIgnoreAsciiCase_WithLength( + pMonth, monthLen, "MAY", monthLen) == 0) + rEntry.m_aDate.SetMonth(5); + else if (rtl_str_compareIgnoreAsciiCase_WithLength( + pMonth, monthLen, "JUN", monthLen) == 0) + rEntry.m_aDate.SetMonth(6); + else if (rtl_str_compareIgnoreAsciiCase_WithLength( + pMonth, monthLen, "JUL", monthLen) == 0) + rEntry.m_aDate.SetMonth(7); + else if (rtl_str_compareIgnoreAsciiCase_WithLength( + pMonth, monthLen, "AUG", monthLen) == 0) + rEntry.m_aDate.SetMonth(8); + else if (rtl_str_compareIgnoreAsciiCase_WithLength( + pMonth, monthLen, "SEP", monthLen) == 0) + rEntry.m_aDate.SetMonth(9); + else if (rtl_str_compareIgnoreAsciiCase_WithLength( + pMonth, monthLen, "OCT", monthLen) == 0) + rEntry.m_aDate.SetMonth(10); + else if (rtl_str_compareIgnoreAsciiCase_WithLength( + pMonth, monthLen, "NOV", monthLen) == 0) + rEntry.m_aDate.SetMonth(11); + else if (rtl_str_compareIgnoreAsciiCase_WithLength( + pMonth, monthLen, "DEC", monthLen) == 0) + rEntry.m_aDate.SetMonth(12); + else + return false; + if (*p++ != '-') + return false; + + // Parse <year> part and set entry date's year: + sal_uInt16 nYear = 0; + for (int i = 0; i < 2; ++i) + { + if (*p < '0' || *p > '9') + return false; + nYear = 10 * nYear + (*p++ - '0'); + } + if (*p >= '0' && *p <= '9') + { + nYear = 10 * nYear + (*p++ - '0'); + if (*p < '0' || *p > '9') + return false; + nYear = 10 * nYear + (*p++ - '0'); + } + setYear (rEntry.m_aDate, nYear); + + // Skip <1*lws> part: + if (*p != '\t' && *p != ' ') + return false; + ++p; + while (*p == '\t' || *p == ' ') + ++p; + + // Parse <hour ":"> part and set entry time's hour: + sal_uInt16 nHour; + if (*p == '0' || *p == '1') + { + nHour = *p++ - '0'; + if (*p >= '0' && *p <= '9') + nHour = 10 * nHour + (*p++ - '0'); + } + else if (*p == '2') + { + ++p; + nHour = (*p >= '0' && *p <= '3') ? 20 + (*p++ - '0') : 2; + } + else if (*p >= '3' && *p <= '9') + nHour = *p++ - '0'; + else + return false; + + rEntry.m_aDate.SetHour(nHour); + if (*p++ != ':') + return false; + + /* + * Parse <minute> part and set entry time's minutes, + * seconds (0), and nanoseconds (0). + */ + if (*p < '0' || *p > '5') + return false; + + sal_uInt16 nMinute = *p++ - '0'; + if (*p < '0' || *p > '9') + return false; + + nMinute = 10 * nMinute + (*p++ - '0'); + rEntry.m_aDate.SetMin(nMinute); + rEntry.m_aDate.SetSec(0); + rEntry.m_aDate.SetNanoSec(0); + + // Skip <rest> part: + return !*p || *p == '\t' || *p == ' '; + } +} + +/* + * parseUNIX + */ +bool FTPDirectoryParser::parseUNIX ( + FTPDirentry &rEntry, + const char *pBuffer) +{ + const char *p1, *p2; + p1 = pBuffer; + + if (!((*p1 == '-') || (*p1 == 'd') || (*p1 == 'l'))) + return false; + + // 1st column: FileMode. + if (*p1 == 'd') + rEntry.m_nMode |= INETCOREFTP_FILEMODE_ISDIR; + + if (*p1 == 'l') + rEntry.m_nMode |= INETCOREFTP_FILEMODE_ISLINK; + + // Skip to end of column and set rights by the way + while (*p1 && !ascii_isWhitespace(*p1)) { + if(*p1 == 'r') + rEntry.m_nMode |= INETCOREFTP_FILEMODE_READ; + else if(*p1 == 'w') + rEntry.m_nMode |= INETCOREFTP_FILEMODE_WRITE; + p1++; + } + + /* + * Scan for the sequence of size and date fields: + * *LWS 1*DIGIT 1*LWS 3CHAR 1*LWS 1*2DIGIT 1*LWS + * (4DIGIT / (1*2DIGIT ":" 2DIGIT)) 1*LWS + */ + enum Mode + { + FOUND_NONE, FOUND_SIZE, FOUND_MONTH, FOUND_DAY, FOUND_YEAR_TIME + }; + + const char *pDayStart = nullptr; + const char *pDayEnd = nullptr; + Mode eMode; + for (eMode = FOUND_NONE; *p1 && eMode != FOUND_YEAR_TIME; p1 = p2 + 1) + { + while (*p1 && ascii_isWhitespace(*p1)) + ++p1; + p2 = p1; + while (*p2 && !ascii_isWhitespace(*p2)) + ++p2; + + switch (eMode) + { + case FOUND_NONE: + if (parseUNIX_isSizeField (p1, p2, rEntry.m_nSize)) + eMode = FOUND_SIZE; + break; + + case FOUND_SIZE: + if (parseUNIX_isMonthField (p1, p2, rEntry.m_aDate)) + eMode = FOUND_MONTH; + else if (!parseUNIX_isSizeField (p1, p2, rEntry.m_nSize)) + eMode = FOUND_NONE; + break; + + case FOUND_MONTH: + if (parseUNIX_isDayField (p1, p2, rEntry.m_aDate)) + { + pDayStart = p1; + pDayEnd = p2; + eMode = FOUND_DAY; + } + else if (parseUNIX_isSizeField (p1, p2, rEntry.m_nSize)) + eMode = FOUND_SIZE; + else + eMode = FOUND_NONE; + break; + + case FOUND_DAY: + if (parseUNIX_isYearTimeField (p1, p2, rEntry.m_aDate)) + eMode = FOUND_YEAR_TIME; + else if ( + parseUNIX_isSizeField ( + pDayStart, pDayEnd, rEntry.m_nSize) && + parseUNIX_isMonthField ( + p1, p2, rEntry.m_aDate)) + eMode = FOUND_MONTH; + else if (parseUNIX_isSizeField (p1, p2, rEntry.m_nSize)) + eMode = FOUND_SIZE; + else + eMode = FOUND_NONE; + break; + // coverity[dead_error_begin] - following conditions exist to avoid compiler warning + case FOUND_YEAR_TIME: + break; + } + } + + if (eMode == FOUND_YEAR_TIME) + { + // 9th column: FileName (rest of line). + while (*p1 && ascii_isWhitespace(*p1)) p1++; + setPath (rEntry.m_aName, p1); + + // Done. + return true; + } + return false; +} + +/* + * parseUNIX_isSizeField. + */ +bool FTPDirectoryParser::parseUNIX_isSizeField ( + const char *pStart, + const char *pEnd, + sal_uInt32 &rSize) +{ + if (!*pStart || !*pEnd || pStart == pEnd) + return false; + + rSize = 0; + if (*pStart >= '0' && *pStart <= '9') + { + for (; pStart < pEnd; ++pStart) + if ((*pStart >= '0') && (*pStart <= '9')) + rSize = 10 * rSize + (*pStart - '0'); + else + return false; + return true; + } + else + { + /* + * For a combination of long group name and large file size, + * some FTPDs omit LWS between those two columns. + */ + int nNonDigits = 0; + int nDigits = 0; + + for (; pStart < pEnd; ++pStart) + if ((*pStart >= '1') && (*pStart <= '9')) + { + ++nDigits; + rSize = 10 * rSize + (*pStart - '0'); + } + else if ((*pStart == '0') && nDigits) + { + ++nDigits; + rSize *= 10; + } + else if ((*pStart > ' ') && (sal::static_int_cast<sal_uInt8>(*pStart) <= '\x7F')) + { + nNonDigits += nDigits + 1; + nDigits = 0; + rSize = 0; + } + else + return false; + return ((nNonDigits >= 9) && (nDigits >= 7)); + } +} + +/* + * parseUNIX_isMonthField. + */ +bool FTPDirectoryParser::parseUNIX_isMonthField ( + const char *pStart, + const char *pEnd, + DateTime &rDateTime) +{ + if (!*pStart || !*pEnd || pStart + 3 != pEnd) + return false; + + if ((pStart[0] == 'j' || pStart[0] == 'J') && + (pStart[1] == 'a' || pStart[1] == 'A') && + (pStart[2] == 'n' || pStart[2] == 'N') ) + { + rDateTime.SetMonth(1); + return true; + } + if ((pStart[0] == 'f' || pStart[0] == 'F') && + (pStart[1] == 'e' || pStart[1] == 'E') && + (pStart[2] == 'b' || pStart[2] == 'B') ) + { + rDateTime.SetMonth(2); + return true; + } + if ((pStart[0] == 'm' || pStart[0] == 'M') && + (pStart[1] == 'a' || pStart[1] == 'A') && + (pStart[2] == 'r' || pStart[2] == 'R') ) + { + rDateTime.SetMonth(3); + return true; + } + if ((pStart[0] == 'a' || pStart[0] == 'A') && + (pStart[1] == 'p' || pStart[1] == 'P') && + (pStart[2] == 'r' || pStart[2] == 'R') ) + { + rDateTime.SetMonth(4); + return true; + } + if ((pStart[0] == 'm' || pStart[0] == 'M') && + (pStart[1] == 'a' || pStart[1] == 'A') && + (pStart[2] == 'y' || pStart[2] == 'Y') ) + { + rDateTime.SetMonth(5); + return true; + } + if ((pStart[0] == 'j' || pStart[0] == 'J') && + (pStart[1] == 'u' || pStart[1] == 'U') && + (pStart[2] == 'n' || pStart[2] == 'N') ) + { + rDateTime.SetMonth(6); + return true; + } + if ((pStart[0] == 'j' || pStart[0] == 'J') && + (pStart[1] == 'u' || pStart[1] == 'U') && + (pStart[2] == 'l' || pStart[2] == 'L') ) + { + rDateTime.SetMonth(7); + return true; + } + if ((pStart[0] == 'a' || pStart[0] == 'A') && + (pStart[1] == 'u' || pStart[1] == 'U') && + (pStart[2] == 'g' || pStart[2] == 'G') ) + { + rDateTime.SetMonth(8); + return true; + } + if ((pStart[0] == 's' || pStart[0] == 'S') && + (pStart[1] == 'e' || pStart[1] == 'E') && + (pStart[2] == 'p' || pStart[2] == 'P') ) + { + rDateTime.SetMonth(9); + return true; + } + if ((pStart[0] == 'o' || pStart[0] == 'O') && + (pStart[1] == 'c' || pStart[1] == 'C') && + (pStart[2] == 't' || pStart[2] == 'T') ) + { + rDateTime.SetMonth(10); + return true; + } + if ((pStart[0] == 'n' || pStart[0] == 'N') && + (pStart[1] == 'o' || pStart[1] == 'O') && + (pStart[2] == 'v' || pStart[2] == 'V') ) + { + rDateTime.SetMonth(11); + return true; + } + if ((pStart[0] == 'd' || pStart[0] == 'D') && + (pStart[1] == 'e' || pStart[1] == 'E') && + (pStart[2] == 'c' || pStart[2] == 'C') ) + { + rDateTime.SetMonth(12); + return true; + } + return false; +} + +/* + * parseUNIX_isDayField. + */ +bool FTPDirectoryParser::parseUNIX_isDayField ( + const char *pStart, + const char *pEnd, + DateTime &rDateTime) +{ + if (!*pStart || !*pEnd || pStart == pEnd) + return false; + if (*pStart < '0' || *pStart > '9') + return false; + + sal_uInt16 nDay = *pStart - '0'; + if (pStart + 1 < pEnd) + { + if (pStart + 2 != pEnd || pStart[1] < '0' || pStart[1] > '9') + return false; + nDay = 10 * nDay + (pStart[1] - '0'); + } + if (!nDay || nDay > 31) + return false; + + rDateTime.SetDay(nDay); + return true; +} + +/* + * parseUNIX_isYearTimeField. + */ +bool FTPDirectoryParser::parseUNIX_isYearTimeField ( + const char *pStart, + const char *pEnd, + DateTime &rDateTime) +{ + if (!*pStart || !*pEnd || pStart == pEnd || + *pStart < '0' || *pStart > '9') + return false; + + sal_uInt16 nNumber = *pStart - '0'; + ++pStart; + + if (pStart == pEnd) + return false; + if (*pStart == ':') + return parseUNIX_isTime (pStart, pEnd, nNumber, rDateTime); + if (*pStart < '0' || *pStart > '9') + return false; + + nNumber = 10 * nNumber + (*pStart - '0'); + ++pStart; + + if (pStart == pEnd) + return false; + if (*pStart == ':') + return parseUNIX_isTime (pStart, pEnd, nNumber, rDateTime); + if (*pStart < '0' || *pStart > '9') + return false; + + nNumber = 10 * nNumber + (*pStart - '0'); + ++pStart; + + if (pStart == pEnd || *pStart < '0' || *pStart > '9') + return false; + + nNumber = 10 * nNumber + (*pStart - '0'); + if (pStart + 1 != pEnd || nNumber < 1970) + return false; + + rDateTime.SetYear(nNumber); + rDateTime.SetTime(); + return true; +} + +/* + * parseUNIX_isTime. + */ +bool FTPDirectoryParser::parseUNIX_isTime ( + const char *pStart, + const char *pEnd, + sal_uInt16 nHour, + DateTime &rDateTime) +{ + if ((nHour > 23 ) || (pStart + 3 != pEnd) || + (pStart[1] < '0') || (pStart[1] > '5') || + (pStart[2] < '0') || (pStart[2] > '9') ) + return false; + + sal_uInt16 nMin = 10 * (pStart[1] - '0') + (pStart[2] - '0'); + + rDateTime.SetHour (nHour); + rDateTime.SetMin (nMin); + rDateTime.SetSec (0); + rDateTime.SetNanoSec (0); + +// Date aCurDate; +// if (rDateTime.GetMonth() > aCurDate.GetMonth()) +// rDateTime.SetYear(aCurDate.GetYear() - 1); +// else +// rDateTime.SetYear(aCurDate.GetYear()); +// return sal_True; + + TimeValue aTimeVal; + osl_getSystemTime(&aTimeVal); + oslDateTime aCurrDateTime; + osl_getDateTimeFromTimeValue(&aTimeVal,&aCurrDateTime); + + if (rDateTime.GetMonth() > aCurrDateTime.Month) + rDateTime.SetYear(aCurrDateTime.Year - 1); + else + rDateTime.SetYear(aCurrDateTime.Year); + return true; +} + +/* + * setYear. + * + * Two-digit years are taken as within 50 years back and 49 years forward + * (both ends inclusive) from the current year. The returned date is not + * checked for validity of the given day in the given month and year. + * + */ +void FTPDirectoryParser::setYear ( + DateTime &rDateTime, sal_uInt16 nYear) +{ + if (nYear < 100) + { + TimeValue aTimeVal; + osl_getSystemTime(&aTimeVal); + oslDateTime aCurrDateTime; + osl_getDateTimeFromTimeValue(&aTimeVal,&aCurrDateTime); + sal_uInt16 nCurrentYear = aCurrDateTime.Year; +// sal_uInt16 nCurrentYear = Date().GetYear(); + sal_uInt16 nCurrentCentury = nCurrentYear / 100; + nCurrentYear %= 100; + if (nCurrentYear < 50) + if (nYear <= nCurrentYear) + nYear += nCurrentCentury * 100; + else if (nYear < nCurrentYear + 50) + nYear += nCurrentCentury * 100; + else + nYear += (nCurrentCentury - 1) * 100; + else + if (nYear >= nCurrentYear) + nYear += nCurrentCentury * 100; + else if (nYear >= nCurrentYear - 50) + nYear += nCurrentCentury * 100; + else + nYear += (nCurrentCentury + 1) * 100; + } + + rDateTime.SetYear(nYear); +} + +/* + * setPath. + */ +bool FTPDirectoryParser::setPath ( + OUString &rPath, const char *value, sal_Int32 length) +{ + if (value) + { + if (length < 0) + length = rtl_str_getLength (value); + rPath = OUString (value, length, RTL_TEXTENCODING_UTF8); + } + return (!!value); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/ucb/source/ucp/ftp/ftpdirp.hxx b/ucb/source/ucp/ftp/ftpdirp.hxx new file mode 100644 index 000000000..fa81e1e33 --- /dev/null +++ b/ucb/source/ucp/ftp/ftpdirp.hxx @@ -0,0 +1,161 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this 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 + ************************************************************************** + + *************************************************************************/ +#ifndef INCLUDED_UCB_SOURCE_UCP_FTP_FTPDIRP_HXX +#define INCLUDED_UCB_SOURCE_UCP_FTP_FTPDIRP_HXX + +#include <rtl/ustring.hxx> +#include <com/sun/star/util/DateTime.hpp> + + +namespace ftp { + + /*======================================================================== + * + * the DateTime structure + * + *======================================================================*/ + + struct DateTime + : public css::util::DateTime + { + DateTime() : css::util::DateTime(0, 0, 0, 0, 0, 0, 0, false) { } + + void SetYear(sal_uInt16 year) { Year = year; } + void SetMonth(sal_uInt16 month) { Month = month; } + void SetDay(sal_uInt16 day) { Day = day; } + // Only zero allowed and used for time-argument + void SetTime() { Hours = 0; Minutes = 0; Seconds = 0; NanoSeconds = 0; } + void SetHour(sal_uInt16 hours) { Hours = hours; } + void SetMin(sal_uInt16 minutes) { Minutes = minutes; } + void SetSec(sal_uInt16 seconds) { Seconds = seconds; } + void SetNanoSec(sal_uInt32 nanoSec) { NanoSeconds = nanoSec; } + + sal_uInt16 GetMonth() const { return Month; } + }; + + +/*======================================================================== + * + * the directory information structure + * + *======================================================================*/ + + enum FTPDirentryMode { INETCOREFTP_FILEMODE_UNKNOWN = 0x00, + INETCOREFTP_FILEMODE_READ = 0x01, + INETCOREFTP_FILEMODE_WRITE = 0x02, + INETCOREFTP_FILEMODE_ISDIR = 0x04, + INETCOREFTP_FILEMODE_ISLINK = 0x08 }; + + struct FTPDirentry + { + OUString m_aURL; + OUString m_aName; + DateTime m_aDate; + sal_uInt32 m_nMode; + sal_uInt32 m_nSize; + + FTPDirentry() + : m_aDate(), + m_nMode(INETCOREFTP_FILEMODE_UNKNOWN), + m_nSize(sal_uInt32(-1)) { } + + void clear() { + m_aURL.clear(); + m_aName.clear(); + m_aDate = DateTime(); + m_nMode = INETCOREFTP_FILEMODE_UNKNOWN; + m_nSize = sal_uInt32(-1); + } + }; + + +/*======================================================================== + * + * the directory parser + * + *======================================================================*/ + + + class FTPDirectoryParser + { + public: + static bool parseDOS ( + FTPDirentry &rEntry, + const char *pBuffer ); + + static bool parseVMS ( + FTPDirentry &rEntry, + const char *pBuffer ); + + static bool parseUNIX ( + FTPDirentry &rEntry, + const char *pBuffer ); + + + private: + + static bool parseUNIX_isSizeField ( + const char *pStart, + const char *pEnd, + sal_uInt32 &rSize); + + static bool parseUNIX_isMonthField ( + const char *pStart, + const char *pEnd, + DateTime& rDateTime); + + static bool parseUNIX_isDayField ( + const char *pStart, + const char *pEnd, + DateTime& rDateTime); + + static bool parseUNIX_isYearTimeField ( + const char *pStart, + const char *pEnd, + DateTime& rDateTime); + + static bool parseUNIX_isTime ( + const char *pStart, + const char *pEnd, + sal_uInt16 nHour, + DateTime& rDateTime); + + static void setYear ( + DateTime& rDateTime, + sal_uInt16 nYear); + + static bool setPath ( + OUString& rPath, + const char *value, + sal_Int32 length = -1); + }; + + +} + + +#endif + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/ucb/source/ucp/ftp/ftpdynresultset.cxx b/ucb/source/ucp/ftp/ftpdynresultset.cxx new file mode 100644 index 000000000..8021f9dd4 --- /dev/null +++ b/ucb/source/ucp/ftp/ftpdynresultset.cxx @@ -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 . + */ + +#include <com/sun/star/sdbc/XResultSet.hpp> +#include "ftpdynresultset.hxx" +#include "ftpresultsetfactory.hxx" + +using namespace com::sun::star::lang; +using namespace com::sun::star::sdbc; +using namespace com::sun::star::ucb; +using namespace com::sun::star::uno; + + +using namespace ftp; + + +// DynamicResultSet Implementation. + + +DynamicResultSet::DynamicResultSet( + const Reference< XComponentContext >& rxContext, + const OpenCommandArgument2& rCommand, + std::unique_ptr<ResultSetFactory> pFactory ) + : ResultSetImplHelper( rxContext, rCommand ), + m_pFactory( std::move(pFactory) ) +{ +} + +DynamicResultSet::~DynamicResultSet() +{ +} + + +// Non-interface methods. + + +void DynamicResultSet::initStatic() +{ + m_xResultSet1.set( m_pFactory->createResultSet() ); +} + + +void DynamicResultSet::initDynamic() +{ + m_xResultSet1.set( m_pFactory->createResultSet() ); + + m_xResultSet2 = m_xResultSet1; +} + + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/ucb/source/ucp/ftp/ftpdynresultset.hxx b/ucb/source/ucp/ftp/ftpdynresultset.hxx new file mode 100644 index 000000000..d9919961c --- /dev/null +++ b/ucb/source/ucp/ftp/ftpdynresultset.hxx @@ -0,0 +1,51 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#ifndef INCLUDED_UCB_SOURCE_UCP_FTP_FTPDYNRESULTSET_HXX +#define INCLUDED_UCB_SOURCE_UCP_FTP_FTPDYNRESULTSET_HXX + +#include <memory> +#include <ucbhelper/resultsethelper.hxx> + +namespace ftp { + + class ResultSetFactory; + + class DynamicResultSet : public ::ucbhelper::ResultSetImplHelper + { + std::unique_ptr<ResultSetFactory> m_pFactory; + + private: + virtual void initStatic() override; + virtual void initDynamic() override; + + public: + DynamicResultSet( + const css::uno::Reference< css::uno::XComponentContext >& rxContext, + const css::ucb::OpenCommandArgument2& rCommand, + std::unique_ptr<ResultSetFactory> pFactory ); + + virtual ~DynamicResultSet() override; + }; + +} + +#endif + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/ucb/source/ucp/ftp/ftpintreq.cxx b/ucb/source/ucp/ftp/ftpintreq.cxx new file mode 100644 index 000000000..62c499b16 --- /dev/null +++ b/ucb/source/ucp/ftp/ftpintreq.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 "ftpintreq.hxx" + +#include <comphelper/interaction.hxx> + +#include <com/sun/star/ucb/UnsupportedNameClashException.hpp> +#include <com/sun/star/ucb/NameClash.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::ucb; +using namespace com::sun::star::task; +using namespace ftp; + + +XInteractionApproveImpl::XInteractionApproveImpl() + : m_bSelected(false) +{ +} + +void SAL_CALL XInteractionApproveImpl::select() +{ + m_bSelected = true; +} + + +// XInteractionDisapproveImpl + +XInteractionDisapproveImpl::XInteractionDisapproveImpl() +{ +} + +void SAL_CALL XInteractionDisapproveImpl::select() +{ +} + +// XInteractionRequestImpl + +XInteractionRequestImpl::XInteractionRequestImpl() + : p1( new XInteractionApproveImpl ) +{ + std::vector<uno::Reference<task::XInteractionContinuation>> continuations{ + Reference<XInteractionContinuation>(p1), + Reference<XInteractionContinuation>(new XInteractionDisapproveImpl) }; + UnsupportedNameClashException excep; + excep.NameClash = NameClash::ERROR; + m_xRequest.set(new ::comphelper::OInteractionRequest(Any(excep), continuations)); +} + +bool XInteractionRequestImpl::approved() const +{ + return p1->isSelected(); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/ucb/source/ucp/ftp/ftpintreq.hxx b/ucb/source/ucp/ftp/ftpintreq.hxx new file mode 100644 index 000000000..b7e0ed0cf --- /dev/null +++ b/ucb/source/ucp/ftp/ftpintreq.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 . + */ + +#ifndef INCLUDED_UCB_SOURCE_UCP_FTP_FTPINTREQ_HXX +#define INCLUDED_UCB_SOURCE_UCP_FTP_FTPINTREQ_HXX + +#include <com/sun/star/task/XInteractionDisapprove.hpp> +#include <com/sun/star/task/XInteractionApprove.hpp> +#include <com/sun/star/task/XInteractionRequest.hpp> +#include <cppuhelper/implbase.hxx> + + +namespace ftp { + + class XInteractionApproveImpl : public cppu::WeakImplHelper < + css::task::XInteractionApprove > + { + public: + + XInteractionApproveImpl(); + + virtual void SAL_CALL select() override; + + bool isSelected() const { return m_bSelected;} + + private: + + bool m_bSelected; + }; + + + class XInteractionDisapproveImpl : public cppu::WeakImplHelper < + css::task::XInteractionDisapprove > + { + public: + + XInteractionDisapproveImpl(); + + virtual void SAL_CALL select() override; + }; + + + class XInteractionRequestImpl + { + public: + + XInteractionRequestImpl(); + + bool approved() const; + + css::uno::Reference<css::task::XInteractionRequest> const& getRequest() const + { + return m_xRequest; + } + + private: + + XInteractionApproveImpl* p1; + + css::uno::Reference<css::task::XInteractionRequest> m_xRequest; + + XInteractionRequestImpl(const XInteractionRequestImpl&) = delete; + XInteractionRequestImpl& operator=(const XInteractionRequestImpl&) = delete; + }; + +} + + +#endif + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/ucb/source/ucp/ftp/ftploaderthread.cxx b/ucb/source/ucp/ftp/ftploaderthread.cxx new file mode 100644 index 000000000..0b2777393 --- /dev/null +++ b/ucb/source/ucp/ftp/ftploaderthread.cxx @@ -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 . + */ + +/************************************************************************** + TODO + ************************************************************************** + + *************************************************************************/ +#include "ftploaderthread.hxx" +#include "curl.hxx" + +using namespace ftp; + + +/********************************************************************************/ +/* */ +/* cleanup function for thread specific data */ +/* */ +/********************************************************************************/ + +extern "C" { + + static int memory_write_dummy(void *,size_t,size_t,void *) + { + return 0; + } + + static void delete_CURL(void *pData) + { + // Otherwise response for QUIT will be sent to already destroyed + // MemoryContainer via non-dummy memory_write function. + curl_easy_setopt(static_cast<CURL*>(pData), + CURLOPT_HEADERFUNCTION, + memory_write_dummy); + curl_easy_cleanup(static_cast<CURL*>(pData)); + } + +} + +/********************************************************************************/ +/* */ +/* Member part of FTPLoaderThread */ +/* */ +/********************************************************************************/ + + +FTPLoaderThread::FTPLoaderThread() + : m_threadKey(osl_createThreadKey(delete_CURL)) { +} + + +FTPLoaderThread::~FTPLoaderThread() { + osl_destroyThreadKey(m_threadKey); +} + + +CURL* FTPLoaderThread::handle() { + CURL* ret = static_cast<CURL*>(osl_getThreadKeyData(m_threadKey)); + if(!ret) { + ret = curl_easy_init(); + if (ret != nullptr) { + // Make sure curl is not internally using environment variables like + // "ftp_proxy": + if (curl_easy_setopt(ret, CURLOPT_PROXY, "") != CURLE_OK) { + curl_easy_cleanup(ret); + ret = nullptr; + } + } + osl_setThreadKeyData(m_threadKey, ret); + } + + return ret; +} + + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/ucb/source/ucp/ftp/ftploaderthread.hxx b/ucb/source/ucp/ftp/ftploaderthread.hxx new file mode 100644 index 000000000..0919c9b23 --- /dev/null +++ b/ucb/source/ucp/ftp/ftploaderthread.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 . + */ + +/************************************************************************** + TODO + ************************************************************************** + + *************************************************************************/ + +#ifndef INCLUDED_UCB_SOURCE_UCP_FTP_FTPLOADERTHREAD_HXX +#define INCLUDED_UCB_SOURCE_UCP_FTP_FTPLOADERTHREAD_HXX + +#include <osl/thread.h> +#include "curl.hxx" + +namespace ftp { + + /** A loaderthread acts as factory for CURL-handles, + * the key being ( implicit ) the threadid. + * Owner is a FTPContentProvider-instance + */ + + class FTPLoaderThread + { + public: + + FTPLoaderThread(); + ~FTPLoaderThread(); + + CURL* handle(); + + + private: + FTPLoaderThread(const FTPLoaderThread&) = delete; + FTPLoaderThread& operator=(const FTPLoaderThread&) = delete; + + oslThreadKey m_threadKey; + + }; // end class FTPLoaderThread + +} + + +#endif + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/ucb/source/ucp/ftp/ftpresultsetI.cxx b/ucb/source/ucp/ftp/ftpresultsetI.cxx new file mode 100644 index 000000000..838850229 --- /dev/null +++ b/ucb/source/ucp/ftp/ftpresultsetI.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 <ucbhelper/propertyvalueset.hxx> +#include <rtl/ref.hxx> +#include <com/sun/star/ucb/Command.hpp> +#include "ftpresultsetI.hxx" +#include "ftpcontent.hxx" + + +using namespace std; +using namespace ftp; +using namespace com::sun::star::ucb; +using namespace com::sun::star::lang; +using namespace com::sun::star::uno; +using namespace com::sun::star::beans; +using namespace com::sun::star::sdbc; + + +ResultSetI::ResultSetI(const Reference<XComponentContext>& rxContext, + const Reference<XContentProvider>& xProvider, + const Sequence<Property>& seqProp, + const std::vector<FTPDirentry>& dirvec) + : ResultSetBase(rxContext,xProvider,seqProp) +{ + for(const auto & i : dirvec) + m_aPath.push_back(i.m_aURL); + + // m_aIdents holds the content identifiers + + m_aItems.resize( m_aPath.size() ); + m_aIdents.resize( m_aPath.size() ); + + for(size_t n = 0; n < m_aItems.size(); ++n) { + rtl::Reference<ucbhelper::PropertyValueSet> xRow = + new ucbhelper::PropertyValueSet(rxContext); + + for( const auto& rProp : seqProp) { + const OUString& Name = rProp.Name; + if(Name == "ContentType") + xRow->appendString(rProp, + OUString( "application/ftp" )); + else if(Name == "Title") + xRow->appendString(rProp,dirvec[n].m_aName); + else if(Name == "IsReadOnly") + xRow->appendBoolean(rProp, + (dirvec[n].m_nMode & + INETCOREFTP_FILEMODE_WRITE) == INETCOREFTP_FILEMODE_WRITE); + else if(Name == "IsDocument") + xRow->appendBoolean(rProp, + (dirvec[n].m_nMode & + INETCOREFTP_FILEMODE_ISDIR) != INETCOREFTP_FILEMODE_ISDIR); + else if(Name == "IsFolder") + xRow->appendBoolean(rProp, + ( dirvec[n].m_nMode & + INETCOREFTP_FILEMODE_ISDIR) == INETCOREFTP_FILEMODE_ISDIR); + else if(Name == "Size") + xRow->appendLong(rProp, + dirvec[n].m_nSize); + else if(Name == "DateCreated") + xRow->appendTimestamp(rProp, + dirvec[n].m_aDate); + else if(Name == "CreatableContentsInfo") + xRow->appendObject( + rProp, + makeAny(FTPContent::queryCreatableContentsInfo_Static())); + else + xRow->appendVoid(rProp); + } + m_aItems[n].set(xRow.get()); + } +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/ucb/source/ucp/ftp/ftpresultsetI.hxx b/ucb/source/ucp/ftp/ftpresultsetI.hxx new file mode 100644 index 000000000..9c5b54404 --- /dev/null +++ b/ucb/source/ucp/ftp/ftpresultsetI.hxx @@ -0,0 +1,49 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ +#ifndef INCLUDED_UCB_SOURCE_UCP_FTP_FTPRESULTSETI_HXX +#define INCLUDED_UCB_SOURCE_UCP_FTP_FTPRESULTSETI_HXX + +#include <com/sun/star/ucb/XContentProvider.hpp> +#include <com/sun/star/beans/Property.hpp> +#include "ftpresultsetbase.hxx" +#include "ftpdirp.hxx" + + +namespace ftp { + + class ResultSetI + : public ResultSetBase + { + public: + + ResultSetI( + const css::uno::Reference< css::uno::XComponentContext>& rxContext, + const css::uno::Reference< css::ucb::XContentProvider>& xProvider, + const css::uno::Sequence< css::beans::Property >& seq, + const std::vector<FTPDirentry>& dirvec); + + private: + }; + +} + + +#endif + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/ucb/source/ucp/ftp/ftpresultsetbase.cxx b/ucb/source/ucp/ftp/ftpresultsetbase.cxx new file mode 100644 index 000000000..cb3d017af --- /dev/null +++ b/ucb/source/ucp/ftp/ftpresultsetbase.cxx @@ -0,0 +1,517 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this 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 <com/sun/star/sdbc/SQLException.hpp> +#include <com/sun/star/uno/Reference.h> +#include <com/sun/star/beans/PropertyAttribute.hpp> +#include <ucbhelper/resultsetmetadata.hxx> +#include <cppuhelper/queryinterface.hxx> +#include "ftpresultsetbase.hxx" + +using namespace ftp; +using namespace com::sun::star; + +ResultSetBase::ResultSetBase( + const uno::Reference< uno::XComponentContext >& rxContext, + const uno::Reference< ucb::XContentProvider >& xProvider, + const uno::Sequence< beans::Property >& seq ) + : m_xContext( rxContext ), + m_xProvider( xProvider ), + m_nRow( -1 ), + m_nWasNull( true ), + m_sProperty( seq ) +{ +} + +ResultSetBase::~ResultSetBase() +{ +} + + +// XInterface + +void SAL_CALL +ResultSetBase::acquire() + throw() +{ + OWeakObject::acquire(); +} + + +void SAL_CALL +ResultSetBase::release() + throw() +{ + OWeakObject::release(); +} + + +uno::Any SAL_CALL +ResultSetBase::queryInterface( const uno::Type& rType ) +{ + uno::Any aRet = cppu::queryInterface( + rType, + static_cast< lang::XComponent* >(this), + static_cast< sdbc::XRow* >(this), + static_cast< sdbc::XResultSet* >(this), + static_cast< sdbc::XResultSetMetaDataSupplier* >(this), + static_cast< beans::XPropertySet* >(this), + static_cast< ucb::XContentAccess* >(this) ); + return aRet.hasValue() ? aRet : OWeakObject::queryInterface( rType ); +} + + +// XComponent + + +void SAL_CALL +ResultSetBase::addEventListener( + const uno::Reference< lang::XEventListener >& Listener ) +{ + osl::MutexGuard aGuard( m_aMutex ); + + if ( ! m_pDisposeEventListeners ) + m_pDisposeEventListeners.reset( + new comphelper::OInterfaceContainerHelper2( m_aMutex ) ); + + m_pDisposeEventListeners->addInterface( Listener ); +} + + +void SAL_CALL +ResultSetBase::removeEventListener( + const uno::Reference< lang::XEventListener >& Listener ) +{ + osl::MutexGuard aGuard( m_aMutex ); + + if ( m_pDisposeEventListeners ) + m_pDisposeEventListeners->removeInterface( Listener ); +} + + +void SAL_CALL +ResultSetBase::dispose() +{ + osl::MutexGuard aGuard( m_aMutex ); + + lang::EventObject aEvt; + aEvt.Source = static_cast< lang::XComponent * >( this ); + + if ( m_pDisposeEventListeners && m_pDisposeEventListeners->getLength() ) + { + m_pDisposeEventListeners->disposeAndClear( aEvt ); + } + if( m_pRowCountListeners && m_pRowCountListeners->getLength() ) + { + m_pRowCountListeners->disposeAndClear( aEvt ); + } + if( m_pIsFinalListeners && m_pIsFinalListeners->getLength() ) + { + m_pIsFinalListeners->disposeAndClear( aEvt ); + } +} + + +// XResultSet + +sal_Bool SAL_CALL +ResultSetBase::next() +{ + ++m_nRow; + return m_nRow < sal::static_int_cast<sal_Int32>(m_aItems.size()); +} + + +sal_Bool SAL_CALL +ResultSetBase::isBeforeFirst() +{ + return m_nRow == -1; +} + + +sal_Bool SAL_CALL +ResultSetBase::isAfterLast() +{ + return m_nRow >= sal::static_int_cast<sal_Int32>(m_aItems.size()); // Cannot happen, if m_aFolder.isOpen() +} + + +sal_Bool SAL_CALL +ResultSetBase::isFirst() +{ + return m_nRow == 0; +} + + +sal_Bool SAL_CALL +ResultSetBase::isLast() +{ + if( m_nRow == sal::static_int_cast<sal_Int32>(m_aItems.size()) - 1 ) + return true; + else + return false; +} + + +void SAL_CALL +ResultSetBase::beforeFirst() +{ + m_nRow = -1; +} + + +void SAL_CALL +ResultSetBase::afterLast() +{ + m_nRow = m_aItems.size(); +} + + +sal_Bool SAL_CALL +ResultSetBase::first() +{ + m_nRow = -1; + return next(); +} + + +sal_Bool SAL_CALL +ResultSetBase::last() +{ + m_nRow = m_aItems.size() - 1; + return true; +} + + +sal_Int32 SAL_CALL +ResultSetBase::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 ResultSetBase::absolute( sal_Int32 row ) +{ + if( row >= 0 ) + m_nRow = row - 1; + 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 +ResultSetBase::relative( sal_Int32 row ) +{ + if( isAfterLast() || isBeforeFirst() ) + throw sdbc::SQLException(); + + 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 +ResultSetBase::previous() +{ + if( m_nRow > sal::static_int_cast<sal_Int32>(m_aItems.size()) ) + m_nRow = 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 +ResultSetBase::refreshRow() +{ +} + + +sal_Bool SAL_CALL +ResultSetBase::rowUpdated() +{ + return false; +} + +sal_Bool SAL_CALL +ResultSetBase::rowInserted() +{ + return false; +} + +sal_Bool SAL_CALL +ResultSetBase::rowDeleted() +{ + return false; +} + + +uno::Reference< uno::XInterface > SAL_CALL +ResultSetBase::getStatement() +{ + return uno::Reference< uno::XInterface >(); +} + + +// XCloseable + +void SAL_CALL +ResultSetBase::close() +{ +} + + +OUString SAL_CALL +ResultSetBase::queryContentIdentifierString() +{ + if( 0 <= m_nRow && m_nRow < sal::static_int_cast<sal_Int32>(m_aItems.size()) ) + return m_aPath[m_nRow]; + else + return OUString(); +} + + +uno::Reference< ucb::XContentIdentifier > SAL_CALL +ResultSetBase::queryContentIdentifier() +{ + if( 0 <= m_nRow && m_nRow < sal::static_int_cast<sal_Int32>(m_aItems.size()) ) + { + if(!m_aIdents[m_nRow].is()) { + OUString url = queryContentIdentifierString(); + if(!url.isEmpty() ) + m_aIdents[m_nRow] = + uno::Reference< ucb::XContentIdentifier >( + new ::ucbhelper::ContentIdentifier(url) ); + } + return m_aIdents[m_nRow]; + } + + return uno::Reference<ucb::XContentIdentifier>(); +} + + +uno::Reference< ucb::XContent > SAL_CALL +ResultSetBase::queryContent() +{ + if( 0 <= m_nRow && m_nRow < sal::static_int_cast<sal_Int32>(m_aItems.size()) ) + return m_xProvider->queryContent(queryContentIdentifier()); + else + return uno::Reference< ucb::XContent >(); +} + +namespace { + +class XPropertySetInfoImpl + : public cppu::OWeakObject, + public beans::XPropertySetInfo +{ +public: + + explicit XPropertySetInfoImpl( const uno::Sequence< beans::Property >& aSeq ) + : m_aSeq( aSeq ) + { + } + + void SAL_CALL acquire() + throw() override + { + OWeakObject::acquire(); + } + + + void SAL_CALL release() + throw() override + { + OWeakObject::release(); + } + + uno::Any SAL_CALL queryInterface( const uno::Type& rType ) override + { + uno::Any aRet = cppu::queryInterface( + rType, + static_cast< beans::XPropertySetInfo* >(this) ); + return aRet.hasValue() ? aRet : OWeakObject::queryInterface( rType ); + } + + uno::Sequence< beans::Property > SAL_CALL getProperties() override + { + return m_aSeq; + } + + beans::Property SAL_CALL getPropertyByName( const OUString& aName ) override + { + auto pProp = std::find_if(m_aSeq.begin(), m_aSeq.end(), + [&aName](const beans::Property& rProp) { return aName == rProp.Name; }); + if (pProp != m_aSeq.end()) + return *pProp; + throw beans::UnknownPropertyException(aName); + } + + sal_Bool SAL_CALL hasPropertyByName( const OUString& Name ) override + { + return std::any_of(m_aSeq.begin(), m_aSeq.end(), + [&Name](const beans::Property& rProp) { return Name == rProp.Name; }); + } + +private: + + uno::Sequence< beans::Property > m_aSeq; +}; + +} + +// XPropertySet +uno::Reference< beans::XPropertySetInfo > SAL_CALL +ResultSetBase::getPropertySetInfo() +{ + uno::Sequence< beans::Property > seq(2); + seq[0].Name = "RowCount"; + seq[0].Handle = -1; + seq[0].Type = cppu::UnoType<sal_Int32>::get(); + seq[0].Attributes = beans::PropertyAttribute::READONLY; + + seq[1].Name = "IsRowCountFinal"; + seq[1].Handle = -1; + seq[1].Type = cppu::UnoType<sal_Bool>::get(); + seq[1].Attributes = beans::PropertyAttribute::READONLY; + + //t + return uno::Reference< beans::XPropertySetInfo > ( + new XPropertySetInfoImpl( seq ) ); +} + + +void SAL_CALL ResultSetBase::setPropertyValue( + const OUString& aPropertyName, const uno::Any& /*aValue*/ ) +{ + if( aPropertyName == "IsRowCountFinal" || + aPropertyName == "RowCount" ) + return; + + throw beans::UnknownPropertyException(aPropertyName); +} + + +uno::Any SAL_CALL ResultSetBase::getPropertyValue( + const OUString& PropertyName ) +{ + if( PropertyName == "IsRowCountFinal" ) + { + return uno::Any(true); + } + else if ( PropertyName == "RowCount" ) + { + sal_Int32 count = m_aItems.size(); + return uno::Any(count); + } + else + throw beans::UnknownPropertyException(PropertyName); +} + + +void SAL_CALL ResultSetBase::addPropertyChangeListener( + const OUString& aPropertyName, + const uno::Reference< beans::XPropertyChangeListener >& xListener ) +{ + if( aPropertyName == "IsRowCountFinal" ) + { + osl::MutexGuard aGuard( m_aMutex ); + if ( ! m_pIsFinalListeners ) + m_pIsFinalListeners.reset( + new comphelper::OInterfaceContainerHelper2( m_aMutex ) ); + + m_pIsFinalListeners->addInterface( xListener ); + } + else if ( aPropertyName == "RowCount" ) + { + osl::MutexGuard aGuard( m_aMutex ); + if ( ! m_pRowCountListeners ) + m_pRowCountListeners.reset( + new comphelper::OInterfaceContainerHelper2( m_aMutex ) ); + m_pRowCountListeners->addInterface( xListener ); + } + else + throw beans::UnknownPropertyException(aPropertyName); +} + + +void SAL_CALL ResultSetBase::removePropertyChangeListener( + const OUString& aPropertyName, + const uno::Reference< beans::XPropertyChangeListener >& aListener ) +{ + if( aPropertyName == "IsRowCountFinal" && + m_pIsFinalListeners ) + { + osl::MutexGuard aGuard( m_aMutex ); + m_pIsFinalListeners->removeInterface( aListener ); + } + else if ( aPropertyName == "RowCount" && + m_pRowCountListeners ) + { + osl::MutexGuard aGuard( m_aMutex ); + m_pRowCountListeners->removeInterface( aListener ); + } + else + throw beans::UnknownPropertyException(aPropertyName); +} + + +void SAL_CALL ResultSetBase::addVetoableChangeListener( + const OUString& /*PropertyName*/, + const uno::Reference< beans::XVetoableChangeListener >& /*aListener*/ ) +{ +} + + +void SAL_CALL ResultSetBase::removeVetoableChangeListener( + const OUString& /*PropertyName*/, + const uno::Reference< beans::XVetoableChangeListener >& /*aListener*/ ) +{ +} + + +// XResultSetMetaDataSupplier +uno::Reference< sdbc::XResultSetMetaData > SAL_CALL +ResultSetBase::getMetaData() +{ + ::ucbhelper::ResultSetMetaData* p = + new ::ucbhelper::ResultSetMetaData( m_xContext, m_sProperty ); + return uno::Reference< sdbc::XResultSetMetaData >( p ); +} + + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/ucb/source/ucp/ftp/ftpresultsetbase.hxx b/ucb/source/ucp/ftp/ftpresultsetbase.hxx new file mode 100644 index 000000000..dddf514aa --- /dev/null +++ b/ucb/source/ucp/ftp/ftpresultsetbase.hxx @@ -0,0 +1,413 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ +#ifndef INCLUDED_UCB_SOURCE_UCP_FTP_FTPRESULTSETBASE_HXX +#define INCLUDED_UCB_SOURCE_UCP_FTP_FTPRESULTSETBASE_HXX + +#include <vector> +#include <cppuhelper/weak.hxx> +#include <comphelper/interfacecontainer2.hxx> +#include <com/sun/star/lang/XComponent.hpp> +#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/sdbc/XResultSet.hpp> +#include <com/sun/star/sdbc/XRow.hpp> +#include <com/sun/star/sdbc/XResultSetMetaDataSupplier.hpp> +#include <com/sun/star/ucb/XContentProvider.hpp> +#include <com/sun/star/ucb/XContentIdentifier.hpp> +#include <com/sun/star/uno/XComponentContext.hpp> +#include <com/sun/star/beans/Property.hpp> + + +namespace ftp { + + class ResultSetBase + : public cppu::OWeakObject, + public css::lang::XComponent, + public css::sdbc::XRow, + public css::sdbc::XResultSet, + public css::sdbc::XCloseable, + public css::sdbc::XResultSetMetaDataSupplier, + public css::beans::XPropertySet, + public css::ucb::XContentAccess + { + public: + + ResultSetBase(const css::uno::Reference< css::uno::XComponentContext >& rxContext, + const css::uno::Reference< css::ucb::XContentProvider >& xProvider, + const css::uno::Sequence< css::beans::Property >& seq); + + virtual ~ResultSetBase() override; + + // XInterface + virtual css::uno::Any SAL_CALL + queryInterface( const css::uno::Type& aType ) override; + + virtual void SAL_CALL + acquire() + throw() override; + + virtual void SAL_CALL + release() + throw() 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 + { + OUString ret; + if( 0 <= m_nRow && m_nRow < sal::static_int_cast<sal_Int32>(m_aItems.size()) ) + ret = m_aItems[m_nRow]->getString( columnIndex ); + + return ret; + } + + 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; + + // 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; + + protected: + + css::uno::Reference< css::uno::XComponentContext > + m_xContext; + css::uno::Reference< css::ucb::XContentProvider > + m_xProvider; + sal_Int32 m_nRow; + bool m_nWasNull; + + 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_aPath; + + css::uno::Sequence< css::beans::Property > + m_sProperty; + + osl::Mutex m_aMutex; + std::unique_ptr<comphelper::OInterfaceContainerHelper2> m_pDisposeEventListeners; + std::unique_ptr<comphelper::OInterfaceContainerHelper2> m_pRowCountListeners; + std::unique_ptr<comphelper::OInterfaceContainerHelper2> m_pIsFinalListeners; + }; + + +} // end namespace fileaccess + + +#endif + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/ucb/source/ucp/ftp/ftpresultsetfactory.hxx b/ucb/source/ucp/ftp/ftpresultsetfactory.hxx new file mode 100644 index 000000000..24102b870 --- /dev/null +++ b/ucb/source/ucp/ftp/ftpresultsetfactory.hxx @@ -0,0 +1,59 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this 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 + ************************************************************************** + + *************************************************************************/ +#ifndef INCLUDED_UCB_SOURCE_UCP_FTP_FTPRESULTSETFACTORY_HXX +#define INCLUDED_UCB_SOURCE_UCP_FTP_FTPRESULTSETFACTORY_HXX + +#include "ftpresultsetbase.hxx" +#include "ftpdirp.hxx" +#include <com/sun/star/uno/XComponentContext.hpp> +#include <com/sun/star/beans/Property.hpp> +#include <vector> + +namespace ftp { + +class ResultSetBase; + +class ResultSetFactory +{ +public: + ResultSetFactory(const css::uno::Reference<css::uno::XComponentContext >& rxContext, + const css::uno::Reference<css::ucb::XContentProvider >& xProvider, + const css::uno::Sequence<css::beans::Property>& seq, + const std::vector<FTPDirentry>& dirvec); + + ResultSetBase* createResultSet(); +private: + css::uno::Reference< css::uno::XComponentContext > m_xContext; + css::uno::Reference< css::ucb::XContentProvider > m_xProvider; + css::uno::Sequence< css::beans::Property > m_seq; + std::vector<FTPDirentry> m_dirvec; +}; + +} + + +#endif + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/ucb/source/ucp/ftp/ftpservices.cxx b/ucb/source/ucp/ftp/ftpservices.cxx new file mode 100644 index 000000000..a44ea3a24 --- /dev/null +++ b/ucb/source/ucp/ftp/ftpservices.cxx @@ -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 . + */ + +#include <com/sun/star/lang/XMultiServiceFactory.hpp> +#include <com/sun/star/lang/XSingleServiceFactory.hpp> +#include "ftpcontentprovider.hxx" + +using namespace com::sun::star; +using namespace ftp; + + +extern "C" SAL_DLLPUBLIC_EXPORT void * ucpftp1_component_getFactory( + const char * pImplName, void * pServiceManager, void * /*pRegistryKey*/ ) +{ + void * pRet = nullptr; + + uno::Reference< lang::XMultiServiceFactory > xSMgr( + static_cast< lang::XMultiServiceFactory * >( + pServiceManager ) ); + uno::Reference< lang::XSingleServiceFactory > xFactory; + + + // FTP Content Provider. + + + if ( FTPContentProvider::getImplementationName_Static(). + equalsAscii( pImplName ) ) + { + xFactory = FTPContentProvider::createServiceFactory( xSMgr ); + } + + if ( xFactory.is() ) + { + xFactory->acquire(); + pRet = xFactory.get(); + } + + return pRet; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/ucb/source/ucp/ftp/ftpurl.cxx b/ucb/source/ucp/ftp/ftpurl.cxx new file mode 100644 index 000000000..74b77dec1 --- /dev/null +++ b/ucb/source/ucp/ftp/ftpurl.cxx @@ -0,0 +1,805 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this 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 <sal/log.hxx> + +#include <vector> + +#include <rtl/ustrbuf.hxx> +#include <com/sun/star/ucb/OpenMode.hpp> +#include <string.h> +#include <rtl/uri.hxx> + +#include "ftpurl.hxx" +#include "ftpcontentprovider.hxx" +#include "ftpcfunc.hxx" +#include "ftpcontainer.hxx" +#include <memory> + +using namespace ftp; +using namespace com::sun::star::ucb; +using namespace com::sun::star::uno; + +namespace { + +OUString encodePathSegment(OUString const & decoded) { + return rtl::Uri::encode( + decoded, rtl_UriCharClassPchar, rtl_UriEncodeIgnoreEscapes, + RTL_TEXTENCODING_UTF8); +} + +OUString decodePathSegment(OUString const & encoded) { + return rtl::Uri::decode( + encoded, rtl_UriDecodeWithCharset, RTL_TEXTENCODING_UTF8); +} + +} + +MemoryContainer::MemoryContainer() + : m_nLen(0), + m_nWritePos(0), + m_pBuffer(nullptr) +{ +} + +MemoryContainer::~MemoryContainer() +{ + std::free(m_pBuffer); +} + + +int MemoryContainer::append( + const void* pBuffer, + size_t size, + size_t nmemb +) throw() +{ + sal_uInt32 nLen = size*nmemb; + sal_uInt32 tmp(nLen + m_nWritePos); + + if(m_nLen < tmp) { // enlarge in steps of multiples of 1K + do { + m_nLen+=1024; + } while(m_nLen < tmp); + + if (auto p = std::realloc(m_pBuffer, m_nLen)) + m_pBuffer = p; + else + return 0; + } + + memcpy(static_cast<sal_Int8*>(m_pBuffer)+m_nWritePos, + pBuffer,nLen); + m_nWritePos = tmp; + return nLen; +} + + +extern "C" { + + int memory_write(void *buffer,size_t size,size_t nmemb,void *stream) + { + MemoryContainer *_stream = + static_cast<MemoryContainer*>(stream); + + if(!_stream) + return 0; + + return _stream->append(buffer,size,nmemb); + } + +} + + +FTPURL::FTPURL(const FTPURL& r) + : m_pFCP(r.m_pFCP), + m_aUsername(r.m_aUsername), + m_bShowPassword(r.m_bShowPassword), + m_aHost(r.m_aHost), + m_aPort(r.m_aPort), + m_aPathSegmentVec(r.m_aPathSegmentVec) + +{ +} + + +FTPURL::FTPURL(const OUString& url, + FTPContentProvider* pFCP) + : m_pFCP(pFCP), + m_aUsername("anonymous"), + m_bShowPassword(false), + m_aPort("21") +{ + parse(url); // can reset m_bShowPassword +} + + +FTPURL::~FTPURL() +{ +} + + +void FTPURL::parse(const OUString& url) +{ + OUString aPassword, urlRest; + + if(url.getLength() < 6 || !url.startsWithIgnoreAsciiCase("ftp://", &urlRest)) + throw malformed_exception(); + + // determine "username:password@host:port" + OUString aExpr; + sal_Int32 nIdx = urlRest.indexOf('/'); + if (nIdx == -1) + { + aExpr = urlRest; + urlRest = ""; + } + else + { + aExpr = urlRest.copy(0, nIdx); + urlRest = urlRest.copy(nIdx + 1); + } + + sal_Int32 l = aExpr.indexOf('@'); + m_aHost = aExpr.copy(1+l); + + if(l != -1) { + // Now username and password. + aExpr = aExpr.copy(0,l); + l = aExpr.indexOf(':'); + if(l != -1) { + aPassword = aExpr.copy(1+l); + if(!aPassword.isEmpty()) + m_bShowPassword = true; + } + if(l > 0) + // Overwritten only if the username is not empty. + m_aUsername = aExpr.copy(0,l); + else if(!aExpr.isEmpty()) + m_aUsername = aExpr; + } + + l = m_aHost.lastIndexOf(':'); + sal_Int32 ipv6Back = m_aHost.lastIndexOf(']'); + if((ipv6Back == -1 && l != -1) // not ipv6, but a port + || + (ipv6Back != -1 && 1+ipv6Back == l) // ipv6, and a port + ) + { + if(1+l<m_aHost.getLength()) + m_aPort = m_aHost.copy(1+l); + m_aHost = m_aHost.copy(0,l); + } + + // now determine the pathsegments ... + while(!urlRest.isEmpty()) + { + nIdx = urlRest.indexOf('/'); + OUString segment; + if(nIdx == -1) + { + segment = urlRest; + urlRest = ""; + } + else + { + segment = urlRest.copy(0, nIdx); + urlRest = urlRest.copy(nIdx + 1); + } + if( segment == ".." && !m_aPathSegmentVec.empty() && m_aPathSegmentVec.back() != ".." ) + m_aPathSegmentVec.pop_back(); + else if( segment == "." ) + ; // Ignore + else + // This is a legal name. + m_aPathSegmentVec.push_back( segment ); + } + + if(m_bShowPassword) + m_pFCP->setHost(m_aHost, + m_aPort, + m_aUsername, + aPassword, + ""/*aAccount*/); + + // now check for something like ";type=i" at end of url + if(!m_aPathSegmentVec.empty()) + { + l = m_aPathSegmentVec.back().indexOf(';'); + if (l != -1) + { + m_aType = m_aPathSegmentVec.back().copy(l); + m_aPathSegmentVec.back() = m_aPathSegmentVec.back().copy(0,l); + } + } +} + + +OUString FTPURL::ident(bool withslash,bool internal) const +{ + // rebuild the url as one without ellipses, + // and more important, as one without username and + // password. ( These are set together with the command. ) + + OUStringBuffer bff; + bff.append("ftp://"); + + if( m_aUsername != "anonymous" ) { + bff.append(m_aUsername); + + OUString aPassword,aAccount; + m_pFCP->forHost(m_aHost, + m_aPort, + m_aUsername, + aPassword, + aAccount); + + if((m_bShowPassword || internal) && + !aPassword.isEmpty() ) + bff.append(':') + .append(aPassword); + + bff.append('@'); + } + bff.append(m_aHost); + + if( m_aPort != "21" ) + bff.append(':') + .append(m_aPort) + .append('/'); + else + bff.append('/'); + + for(size_t i = 0; i < m_aPathSegmentVec.size(); ++i) + if(i == 0) + bff.append(m_aPathSegmentVec[i]); + else + bff.append('/').append(m_aPathSegmentVec[i]); + if(withslash) + if(!bff.isEmpty() && bff[bff.getLength()-1] != '/') + bff.append('/'); + + bff.append(m_aType); + return bff.makeStringAndClear(); +} + + +OUString FTPURL::parent(bool internal) const +{ + OUStringBuffer bff; + + bff.append("ftp://"); + + if( m_aUsername != "anonymous" ) { + bff.append(m_aUsername); + + OUString aPassword,aAccount; + m_pFCP->forHost(m_aHost, + m_aPort, + m_aUsername, + aPassword, + aAccount); + + if((internal || m_bShowPassword) && !aPassword.isEmpty()) + bff.append(':') + .append(aPassword); + + bff.append('@'); + } + + bff.append(m_aHost); + + if( m_aPort != "21" ) + bff.append(':') + .append(m_aPort) + .append('/'); + else + bff.append('/'); + + OUString last; + + for(size_t i = 0; i < m_aPathSegmentVec.size(); ++i) + if(1+i == m_aPathSegmentVec.size()) + last = m_aPathSegmentVec[i]; + else if(i == 0) + bff.append(m_aPathSegmentVec[i]); + else + bff.append('/').append(m_aPathSegmentVec[i]); + + if(last.isEmpty()) + bff.append(".."); + else if ( last == ".." ) + bff.append(last).append("/.."); + + bff.append(m_aType); + return bff.makeStringAndClear(); +} + + +void FTPURL::child(const OUString& title) +{ + m_aPathSegmentVec.push_back(encodePathSegment(title)); +} + + +OUString FTPURL::child() const +{ + return + !m_aPathSegmentVec.empty() ? + decodePathSegment(m_aPathSegmentVec.back()) : OUString(); +} + + +/** Listing of a directory. + */ + +namespace ftp { + + namespace { + + enum OS { + FTP_DOS,FTP_UNIX,FTP_VMS,FTP_UNKNOWN + }; + + } + +} + + +#define SET_CONTROL_CONTAINER \ + MemoryContainer control; \ + curl_easy_setopt(curl, \ + CURLOPT_HEADERFUNCTION, \ + memory_write); \ + curl_easy_setopt(curl, \ + CURLOPT_WRITEHEADER, \ + &control) + + +static void setCurlUrl(CURL* curl, OUString const & url) +{ + OString urlParAscii(url.getStr(), + url.getLength(), + RTL_TEXTENCODING_UTF8); + curl_easy_setopt(curl, + CURLOPT_URL, + urlParAscii.getStr()); +}; + +oslFileHandle FTPURL::open() +{ + if(m_aPathSegmentVec.empty()) + throw curl_exception(CURLE_FTP_COULDNT_RETR_FILE); + + CURL *curl = m_pFCP->handle(); + + SET_CONTROL_CONTAINER; + OUString url(ident(false,true)); + setCurlUrl(curl, url); + + oslFileHandle res( nullptr ); + if ( osl_createTempFile( nullptr, &res, nullptr ) == osl_File_E_None ) + { + curl_easy_setopt(curl,CURLOPT_WRITEFUNCTION,file_write); + curl_easy_setopt(curl,CURLOPT_WRITEDATA,res); + + curl_easy_setopt(curl,CURLOPT_POSTQUOTE,0); + CURLcode err = curl_easy_perform(curl); + + if(err == CURLE_OK) + { + oslFileError rc = osl_setFilePos( res, osl_Pos_Absolut, 0 ); + SAL_WARN_IF(rc != osl_File_E_None, "ucb.ucp.ftp", + "osl_setFilePos failed"); + } + else { + osl_closeFile(res); + res = nullptr; + throw curl_exception(err); + } + } + + return res; +} + + +std::vector<FTPDirentry> FTPURL::list( + sal_Int16 nMode +) const +{ + CURL *curl = m_pFCP->handle(); + + SET_CONTROL_CONTAINER; + curl_easy_setopt(curl,CURLOPT_NOBODY,false); + MemoryContainer data; + curl_easy_setopt(curl,CURLOPT_WRITEFUNCTION,memory_write); + curl_easy_setopt(curl,CURLOPT_WRITEDATA,&data); + + OUString url(ident(true,true)); + setCurlUrl(curl, url); + curl_easy_setopt(curl,CURLOPT_POSTQUOTE,0); + + CURLcode err = curl_easy_perform(curl); + if(err != CURLE_OK) + throw curl_exception(err); + + // now evaluate the error messages + + sal_uInt32 len = data.m_nWritePos; + char* fwd = static_cast<char*>(data.m_pBuffer); + char *p1, *p2; + p1 = p2 = fwd; + + OS osKind(FTP_UNKNOWN); + std::vector<FTPDirentry> resvec; + FTPDirentry aDirEntry; + // ensure slash at the end + OUString viewurl(ident(true,false)); + + while(true) { + while(p2-fwd < int(len) && *p2 != '\n') ++p2; + if(p2-fwd == int(len)) break; + + *p2 = 0; + switch(osKind) { + // While FTP knows the 'system'-command, + // which returns the operating system type, + // this is not usable here: There are Windows-server + // formatting the output like UNIX-ls command. + case FTP_DOS: + FTPDirectoryParser::parseDOS(aDirEntry,p1); + break; + case FTP_UNIX: + FTPDirectoryParser::parseUNIX(aDirEntry,p1); + break; + case FTP_VMS: + FTPDirectoryParser::parseVMS(aDirEntry,p1); + break; + default: + if(FTPDirectoryParser::parseUNIX(aDirEntry,p1)) + osKind = FTP_UNIX; + else if(FTPDirectoryParser::parseDOS(aDirEntry,p1)) + osKind = FTP_DOS; + else if(FTPDirectoryParser::parseVMS(aDirEntry,p1)) + osKind = FTP_VMS; + } + aDirEntry.m_aName = aDirEntry.m_aName.trim(); + if( osKind != int(FTP_UNKNOWN) && aDirEntry.m_aName != ".." && aDirEntry.m_aName != "." ) { + aDirEntry.m_aURL = viewurl + encodePathSegment(aDirEntry.m_aName); + + bool isDir = (aDirEntry.m_nMode & INETCOREFTP_FILEMODE_ISDIR) == INETCOREFTP_FILEMODE_ISDIR; + switch(nMode) { + case OpenMode::DOCUMENTS: + if(!isDir) + resvec.push_back(aDirEntry); + break; + case OpenMode::FOLDERS: + if(isDir) + resvec.push_back(aDirEntry); + break; + default: + resvec.push_back(aDirEntry); + }; + } + aDirEntry.clear(); + p1 = p2 + 1; + } + + return resvec; +} + + +OUString FTPURL::net_title() const +{ + CURL *curl = m_pFCP->handle(); + + SET_CONTROL_CONTAINER; + curl_easy_setopt(curl,CURLOPT_NOBODY,true); // no data => no transfer + struct curl_slist *slist = nullptr; + // post request + slist = curl_slist_append(slist,"PWD"); + curl_easy_setopt(curl,CURLOPT_POSTQUOTE,slist); + + bool try_more(true); + CURLcode err; + OUString aNetTitle; + + while(true) { + OUString url(ident(false,true)); + + if(try_more && !url.endsWith("/")) + url += "/"; // add end-slash + else if(!try_more && url.endsWith("/")) + url = url.copy(0,url.getLength()-1); // remove end-slash + + setCurlUrl(curl, url); + err = curl_easy_perform(curl); + + if(err == CURLE_OK) { // get the title from the server + char* fwd = static_cast<char*>(control.m_pBuffer); + sal_uInt32 len = control.m_nWritePos; + + aNetTitle = OUString(fwd,len,RTL_TEXTENCODING_UTF8); + // the buffer now contains the name of the file; + // analyze the output: + // Format of current working directory: + // 257 "/bla/bla" is current directory + sal_Int32 index1 = aNetTitle.lastIndexOf("257"); + index1 = aNetTitle.indexOf('"', index1 + std::strlen("257")) + 1; + sal_Int32 index2 = aNetTitle.indexOf('"', index1); + aNetTitle = index2 > index1 + ? aNetTitle.copy(index1, index2 - index1) : OUString(); + if( aNetTitle != "/" ) { + index1 = aNetTitle.lastIndexOf('/'); + aNetTitle = aNetTitle.copy(1+index1); + } + try_more = false; + } else if(err == CURLE_BAD_PASSWORD_ENTERED) + // the client should retry after getting the correct + // username + password + throw curl_exception(err); +#if LIBCURL_VERSION_NUM>=0x070d01 /* 7.13.1 */ + else if(err == CURLE_LOGIN_DENIED) + // the client should retry after getting the correct + // username + password + throw curl_exception(err); +#endif + else if(try_more && err == CURLE_FTP_ACCESS_DENIED) { + // We were either denied access when trying to login to + // an FTP server or when trying to change working directory + // to the one given in the URL. + if(!m_aPathSegmentVec.empty()) + // determine title from URL + aNetTitle = decodePathSegment(m_aPathSegmentVec.back()); + else + // must be root + aNetTitle = "/"; + try_more = false; + } + + if(try_more) + try_more = false; + else + break; + } + + curl_slist_free_all(slist); + return aNetTitle; +} + + +FTPDirentry FTPURL::direntry() const +{ + OUString nettitle = net_title(); + FTPDirentry aDirentry; + + aDirentry.m_aName = nettitle; // init aDirentry + if( nettitle == "/" || nettitle == ".." ) + aDirentry.m_nMode = INETCOREFTP_FILEMODE_ISDIR; + else + aDirentry.m_nMode = INETCOREFTP_FILEMODE_UNKNOWN; + + aDirentry.m_nSize = 0; + + if( nettitle != "/" ) { + // try to open the parent directory + FTPURL aURL(parent(),m_pFCP); + + std::vector<FTPDirentry> aList = aURL.list(OpenMode::ALL); + + for(const FTPDirentry & d : aList) { + if(d.m_aName == nettitle) { // the relevant file is found + aDirentry = d; + break; + } + } + } + return aDirentry; +} + + +extern "C" { + + static size_t memory_read(void *ptr,size_t size,size_t nmemb,void *stream) + { + sal_Int32 nRequested = sal_Int32(size*nmemb); + CurlInput *curlInput = static_cast<CurlInput*>(stream); + if(curlInput) + return size_t(curlInput->read(static_cast<sal_Int8*>(ptr),nRequested)); + else + return 0; + } + +} + + +void FTPURL::insert(bool replaceExisting,void* stream) const +{ + if(!replaceExisting) { +// FTPDirentry aDirentry(direntry()); +// if(aDirentry.m_nMode == INETCOREFTP_FILEMODE_UNKNOWN) + // throw curl_exception(FILE_EXIST_DURING_INSERT); + throw curl_exception(FILE_MIGHT_EXIST_DURING_INSERT); + } // else + // overwrite is default in libcurl + + CURL *curl = m_pFCP->handle(); + + SET_CONTROL_CONTAINER; + curl_easy_setopt(curl,CURLOPT_NOBODY,false); // no data => no transfer + curl_easy_setopt(curl,CURLOPT_POSTQUOTE,0); + curl_easy_setopt(curl,CURLOPT_QUOTE,0); + curl_easy_setopt(curl,CURLOPT_READFUNCTION,memory_read); + curl_easy_setopt(curl,CURLOPT_READDATA,stream); + curl_easy_setopt(curl, CURLOPT_UPLOAD,1); + + OUString url(ident(false,true)); + setCurlUrl(curl, url); + + CURLcode err = curl_easy_perform(curl); + curl_easy_setopt(curl, CURLOPT_UPLOAD,false); + + if(err != CURLE_OK) + throw curl_exception(err); +} + + +void FTPURL::mkdir(bool ReplaceExisting) const +{ + OString title; + if(!m_aPathSegmentVec.empty()) { + OUString titleOU = m_aPathSegmentVec.back(); + titleOU = decodePathSegment(titleOU); + title = OString(titleOU.getStr(), + titleOU.getLength(), + RTL_TEXTENCODING_UTF8); + } + else + // will give an error + title = OString("/"); + + OString aDel = "del " + title; + OString mkd = "mkd " + title; + + struct curl_slist *slist = nullptr; + + FTPDirentry aDirentry(direntry()); + if(!ReplaceExisting) { +// if(aDirentry.m_nMode != INETCOREFTP_FILEMODE_UNKNOWN) +// throw curl_exception(FOLDER_EXIST_DURING_INSERT); + throw curl_exception(FOLDER_MIGHT_EXIST_DURING_INSERT); + } else if(aDirentry.m_nMode != INETCOREFTP_FILEMODE_UNKNOWN) + slist = curl_slist_append(slist,aDel.getStr()); + + slist = curl_slist_append(slist,mkd.getStr()); + + CURL *curl = m_pFCP->handle(); + SET_CONTROL_CONTAINER; + curl_easy_setopt(curl,CURLOPT_NOBODY,true); // no data => no transfer + curl_easy_setopt(curl,CURLOPT_QUOTE,0); + + // post request + curl_easy_setopt(curl,CURLOPT_POSTQUOTE,slist); + + OUString url(parent(true)); + if(!url.endsWith("/")) + url += "/"; + setCurlUrl(curl, url); + + CURLcode err = curl_easy_perform(curl); + curl_slist_free_all(slist); + if(err != CURLE_OK) + throw curl_exception(err); +} + + +OUString FTPURL::ren(const OUString& NewTitle) +{ + CURL *curl = m_pFCP->handle(); + + // post request + OUString OldTitle = net_title(); + OString renamefrom = "RNFR " + + OString(OldTitle.getStr(), + OldTitle.getLength(), + RTL_TEXTENCODING_UTF8); + + OString renameto = "RNTO " + + OString(NewTitle.getStr(), + NewTitle.getLength(), + RTL_TEXTENCODING_UTF8); + + struct curl_slist *slist = nullptr; + slist = curl_slist_append(slist,renamefrom.getStr()); + slist = curl_slist_append(slist,renameto.getStr()); + curl_easy_setopt(curl,CURLOPT_POSTQUOTE,slist); + + SET_CONTROL_CONTAINER; + curl_easy_setopt(curl,CURLOPT_NOBODY,true); // no data => no transfer + curl_easy_setopt(curl,CURLOPT_QUOTE,0); + + OUString url(parent(true)); + if(!url.endsWith("/")) + url += "/"; + setCurlUrl(curl, url); + + CURLcode err = curl_easy_perform(curl); + curl_slist_free_all(slist); + if(err != CURLE_OK) + throw curl_exception(err); + else if( !m_aPathSegmentVec.empty() && m_aPathSegmentVec.back() != ".." ) + m_aPathSegmentVec.back() = encodePathSegment(NewTitle); + return OldTitle; +} + + +void FTPURL::del() const +{ + FTPDirentry aDirentry(direntry()); + + OString dele(aDirentry.m_aName.getStr(), + aDirentry.m_aName.getLength(), + RTL_TEXTENCODING_UTF8); + + if(aDirentry.m_nMode & INETCOREFTP_FILEMODE_ISDIR) { + std::vector<FTPDirentry> vec = list(sal_Int16(OpenMode::ALL)); + for(const FTPDirentry & i : vec) + { + try { + FTPURL url(i.m_aURL,m_pFCP); + url.del(); + } catch(const curl_exception&) { + } + } + dele = "RMD " + dele; + } + else if(aDirentry.m_nMode != INETCOREFTP_FILEMODE_UNKNOWN) + dele = "DELE " + dele; + else + return; + + // post request + CURL *curl = m_pFCP->handle(); + struct curl_slist *slist = nullptr; + slist = curl_slist_append(slist,dele.getStr()); + curl_easy_setopt(curl,CURLOPT_POSTQUOTE,slist); + + SET_CONTROL_CONTAINER; + curl_easy_setopt(curl,CURLOPT_NOBODY,true); // no data => no transfer + curl_easy_setopt(curl,CURLOPT_QUOTE,0); + + OUString url(parent(true)); + if(!url.endsWith("/")) + url += "/"; + setCurlUrl(curl, url); + + CURLcode err = curl_easy_perform(curl); + curl_slist_free_all(slist); + if(err != CURLE_OK) + throw curl_exception(err); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/ucb/source/ucp/ftp/ftpurl.hxx b/ucb/source/ucp/ftp/ftpurl.hxx new file mode 100644 index 000000000..3686cfe12 --- /dev/null +++ b/ucb/source/ucp/ftp/ftpurl.hxx @@ -0,0 +1,164 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this 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 + ************************************************************************** + + *************************************************************************/ + +#ifndef INCLUDED_UCB_SOURCE_UCP_FTP_FTPURL_HXX +#define INCLUDED_UCB_SOURCE_UCP_FTP_FTPURL_HXX + +#include "curl.hxx" + +#include <rtl/ustring.hxx> +#include <osl/file.h> +#include <vector> + +#include "ftpdirp.hxx" + +namespace ftp { + + /** Forward declarations. + */ + + class FTPContentProvider; + + + enum FTPErrors { FOLDER_MIGHT_EXIST_DURING_INSERT = CURL_LAST, + FILE_MIGHT_EXIST_DURING_INSERT }; + + class malformed_exception : public std::exception { }; + + class curl_exception : public std::exception + { + public: + + explicit curl_exception(sal_Int32 err) + : n_err(err) { } + + sal_Int32 code() const { return n_err; } + + + private: + + sal_Int32 n_err; + }; + + class CurlInput { + public: + // returns the number of bytes actually read + virtual sal_Int32 read(sal_Int8 *dest,sal_Int32 nBytesRequested) = 0; + + protected: + ~CurlInput() {} + }; + + + class FTPURL + { + public: + /// @throws malformed_exception + FTPURL( + const OUString& aIdent, + FTPContentProvider* pFCP + ); + + FTPURL(const FTPURL& r); + + ~FTPURL(); + + const OUString& host() const { return m_aHost; } + + const OUString& port() const { return m_aPort; } + + const OUString& username() const { return m_aUsername; } + + /** This returns the URL, but cleaned from + * unnessary ellipses. + */ + + OUString ident(bool withslash,bool internal) const; + + /** returns the parent url. + */ + + OUString parent(bool internal = false) const; + + /** sets the unencoded title */ + void child(const OUString& title); + + /** returns the unencoded title */ + OUString child() const; + + /// @throws curl_exception + std::vector<FTPDirentry> list(sal_Int16 nMode) const; + + // returns a pointer to an open tempfile, + // sought to the beginning of. + /// @throws curl_exception + oslFileHandle open(); + + /// @throws curl_exception + /// @throws malformed_exception + FTPDirentry direntry() const; + + /// @throws curl_exception + void insert(bool ReplaceExisting,void* stream) const; + + /// @throws curl_exception + /// @throws malformed_exception + void mkdir(bool ReplaceExisting) const; + + /// @throws curl_exception + OUString ren(const OUString& NewTitle); + + /// @throws curl_exception + /// @throws malformed_exception + void del() const; + + + private: + + FTPContentProvider *m_pFCP; + + mutable OUString m_aUsername; + bool m_bShowPassword; + mutable OUString m_aHost; + mutable OUString m_aPort; + mutable OUString m_aType; + + /** Contains the encoded pathsegments of the url. + */ + std::vector<OUString> m_aPathSegmentVec; + + /// @throws malformed_exception + void parse(const OUString& url); + + /// @throws curl_exception + OUString net_title() const; + }; + +} + + +#endif + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/ucb/source/ucp/ftp/ucpftp1.component b/ucb/source/ucp/ftp/ucpftp1.component new file mode 100644 index 000000000..440f89c5d --- /dev/null +++ b/ucb/source/ucp/ftp/ucpftp1.component @@ -0,0 +1,25 @@ +<?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@" + prefix="ucpftp1" xmlns="http://openoffice.org/2010/uno-components"> + <implementation name="com.sun.star.comp.FTPContentProvider"> + <service name="com.sun.star.ucb.FTPContentProvider"/> + </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 000000000..857671397 --- /dev/null +++ b/ucb/source/ucp/gio/gio_content.cxx @@ -0,0 +1,1336 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this 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 <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 >()) + ? OUString( GIO_FOLDER_TYPE ) + : OUString( 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( 1 ); + aArgs[ 0 ] <<= 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, static_cast< cppu::OWeakObject * >(this), false); +} + +css::uno::Any Content::getBadArgExcept() +{ + return css::uno::makeAny( css::lang::IllegalArgumentException( + "Wrong argument type!", + static_cast< cppu::OWeakObject * >( this ), -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; + 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::makeAny( queryCreatableContentsInfo( xEnv ) ) ); + } + else + { + SAL_WARN( + "ucb.ucp.gio", + "Looking for unsupported property " << rProp.Name); + } + } + + return css::uno::Reference< css::sdbc::XRow >( xRow.get() ); +} + +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 identitity. + 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) + { + 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 = static_cast< cppu::OWeakObject * >( this ); + aEvent.Further = false; + aEvent.PropertyHandle = -1; + + sal_Int32 nChanged = 0, nTitlePos = -1; + const char *newName = nullptr; + css::uno::Sequence< css::beans::PropertyChangeEvent > aChanges(nCount); + + css::uno::Sequence< css::uno::Any > aRet( nCount ); + 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" ) + { + aRet[ n ] <<= getReadOnlyException( static_cast< cppu::OWeakObject * >(this) ); + } + else if ( rValue.Name == "Title" ) + { + OUString aNewTitle; + if (!( rValue.Value >>= aNewTitle )) + { + aRet[ n ] <<= css::beans::IllegalTypeException + ( "Property value has wrong type!", + static_cast< cppu::OWeakObject * >( this ) ); + continue; + } + + if ( aNewTitle.getLength() <= 0 ) + { + aRet[ n ] <<= css::lang::IllegalArgumentException + ( "Empty title not allowed!", + static_cast< cppu::OWeakObject * >( this ), -1 ); + continue; + + } + + OString sNewTitle = OUStringToOString(aNewTitle, RTL_TEXTENCODING_UTF8); + 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; + aChanges.getArray()[ nChanged ] = aEvent; + nTitlePos = nChanged++; + + g_file_info_set_name(pNewInfo, newName); + } + } + else + { + SAL_WARN("ucb.ucp.gio", "Unknown property " << rValue.Name); + aRet[ n ] <<= getReadOnlyException( static_cast< cppu::OWeakObject * >(this) ); + //TODO + } + } + + if (nChanged) + { + bool bOk = true; + if (!mbTransient) + { + if ((bOk = doSetFileInfo(pNewInfo))) + { + for (sal_Int32 i = 0; i < nChanged; ++i) + aRet[ i ] = getBadArgExcept(); + } + } + + if (bOk) + { + if (nTitlePos > -1) + { + OUString aNewURL = getParentURL() + + OUString( newName, strlen(newName), RTL_TEXTENCODING_UTF8 ); + css::uno::Reference< css::ucb::XContentIdentifier > xNewId + = new ::ucbhelper::ContentIdentifier( aNewURL ); + + if (!exchangeIdentity( xNewId ) ) + { + aRet[ nTitlePos ] <<= css::uno::Exception + ( "Exchange failed!", + static_cast< cppu::OWeakObject * >( this ) ); + } + } + + 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; + + if (mpFile) //Discard and refetch + { + g_object_unref(mpFile); + mpFile = nullptr; + } + } + + aChanges.realloc( nChanged ); + notifyPropertiesChange( aChanges ); + } + + 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, static_cast< cppu::OWeakObject * >(this)); + + 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( 1 ); + aArgs[ 0 ] <<= m_xIdentifier->getContentIdentifier(); + css::uno::Any aErr = css::uno::makeAny( + css::ucb::InteractiveAugmentedIOException(OUString(), static_cast< cppu::OWeakObject * >( this ), + 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::makeAny ( css::ucb::UnsupportedOpenModeException + ( OUString(), static_cast< cppu::OWeakObject * >( this ), + 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::makeAny (css::ucb::UnsupportedDataSinkException + ( OUString(), static_cast< cppu::OWeakObject * >( this ), + 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::makeAny( css::ucb::UnsupportedCommandException + ( OUString(), + static_cast< cppu::OWeakObject * >( this ) ) ), + 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::makeAny + ( css::ucb::MissingInputStreamException + ( OUString(), static_cast< cppu::OWeakObject * >( this ) ) ), + 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.getLength()) + sDest += aTransferInfo.NewTitle; + 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) + ucbhelper::cancelCommandExecution(mapGIOError(pError), xEnv); +} + +css::uno::Sequence< css::ucb::ContentInfo > Content::queryCreatableContentsInfo( + const css::uno::Reference< css::ucb::XCommandEnvironment >& xEnv) +{ + if ( isFolder( xEnv ) ) + { + css::uno::Sequence< css::ucb::ContentInfo > seq(2); + + // Minimum set of props we really need + css::uno::Sequence< css::beans::Property > props( 1 ); + props[0] = css::beans::Property( + "Title", + -1, + cppu::UnoType<OUString>::get(), + css::beans::PropertyAttribute::MAYBEVOID | css::beans::PropertyAttribute::BOUND ); + + // file + seq[0].Type = GIO_FILE_TYPE; + seq[0].Attributes = ( css::ucb::ContentInfoAttribute::INSERT_WITH_INPUTSTREAM | + css::ucb::ContentInfoAttribute::KIND_DOCUMENT ); + seq[0].Properties = props; + + // folder + seq[1].Type = GIO_FOLDER_TYPE; + seq[1].Attributes = css::ucb::ContentInfoAttribute::KIND_FOLDER; + seq[1].Properties = props; + + return seq; + } + else + { + return css::uno::Sequence< css::ucb::ContentInfo >(); + } +} + +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() throw() +{ + ContentImplHelper::acquire(); +} + +void SAL_CALL Content::release() throw() +{ + 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 000000000..d912ddac2 --- /dev/null +++ b/ucb/source/ucp/gio/gio_content.hxx @@ -0,0 +1,190 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#ifndef INCLUDED_UCB_SOURCE_UCP_GIO_GIO_CONTENT_HXX +#define INCLUDED_UCB_SOURCE_UCP_GIO_GIO_CONTENT_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 <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 +{ + + +#define GIO_FILE_TYPE "application/vnd.sun.staroffice.gio-file" +#define GIO_FOLDER_TYPE "application/vnd.sun.staroffice.gio-folder" + +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() + throw() override; + virtual void SAL_CALL release() + throw() 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(); +}; + +} + +#endif + +/* 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 000000000..fd4335dc5 --- /dev/null +++ b/ucb/source/ucp/gio/gio_datasupplier.cxx @@ -0,0 +1,256 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this 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 "gio_datasupplier.hxx" +#include "gio_content.hxx" + +using namespace gio; + +namespace gio +{ + +DataSupplier::DataSupplier( const rtl::Reference< ::gio::Content >& rContent, sal_Int32 nOpenMode ) + : mxContent(rContent), 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 000000000..a740af1a5 --- /dev/null +++ b/ucb/source/ucp/gio/gio_datasupplier.hxx @@ -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 . + */ + +#ifndef INCLUDED_UCB_SOURCE_UCP_GIO_GIO_DATASUPPLIER_HXX +#define INCLUDED_UCB_SOURCE_UCP_GIO_GIO_DATASUPPLIER_HXX + +#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( 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; +}; + +} + +#endif + +/* 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 000000000..c6df4572b --- /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, static_cast< cppu::OWeakObject * >(this)); + 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 000000000..687041add --- /dev/null +++ b/ucb/source/ucp/gio/gio_inputstream.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 . + */ + +#ifndef INCLUDED_UCB_SOURCE_UCP_GIO_GIO_INPUTSTREAM_HXX +#define INCLUDED_UCB_SOURCE_UCP_GIO_GIO_INPUTSTREAM_HXX + +#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 +#endif + +/* 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 000000000..b78817c67 --- /dev/null +++ b/ucb/source/ucp/gio/gio_mount.cxx @@ -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/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this 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; + + ucbhelper::SimpleAuthenticationRequest::EntityType eUserName = + (flags & G_ASK_PASSWORD_NEED_USERNAME) + ? ucbhelper::SimpleAuthenticationRequest::ENTITY_MODIFY + : ucbhelper::SimpleAuthenticationRequest::ENTITY_NA; + + if (default_user) + aUserName = OUString(default_user, strlen(default_user), RTL_TEXTENCODING_UTF8); + + 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.get() ); + + 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 000000000..421f7b7d3 --- /dev/null +++ b/ucb/source/ucp/gio/gio_mount.hxx @@ -0,0 +1,90 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#ifndef INCLUDED_UCB_SOURCE_UCP_GIO_GIO_MOUNT_HXX +#define INCLUDED_UCB_SOURCE_UCP_GIO_GIO_MOUNT_HXX + +#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 +#endif + +/* 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 000000000..1f334f4df --- /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, static_cast< cppu::OWeakObject * >(this)); +} + +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, static_cast< cppu::OWeakObject * >(this)); +} + +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 000000000..6bf03ce96 --- /dev/null +++ b/ucb/source/ucp/gio/gio_outputstream.hxx @@ -0,0 +1,64 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#ifndef INCLUDED_UCB_SOURCE_UCP_GIO_GIO_OUTPUTSTREAM_HXX +#define INCLUDED_UCB_SOURCE_UCP_GIO_GIO_OUTPUTSTREAM_HXX + +#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() throw () override { OWeakObject::acquire(); } + virtual void SAL_CALL release() throw() 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 +#endif + +/* 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 000000000..92302c2ca --- /dev/null +++ b/ucb/source/ucp/gio/gio_provider.cxx @@ -0,0 +1,156 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this 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/getcomponentcontext.hxx> +#include <ucbhelper/macros.hxx> +#include <cppuhelper/queryinterface.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 ).get(); + 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() + throw() +{ + OWeakObject::acquire(); +} + +void SAL_CALL ContentProvider::release() + throw() +{ + 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 ); + +XSERVICEINFO_COMMOM_IMPL( ContentProvider, + "com.sun.star.comp.GIOContentProvider" ) +/// @throws css::uno::Exception +static css::uno::Reference< css::uno::XInterface > +ContentProvider_CreateInstance( const css::uno::Reference< css::lang::XMultiServiceFactory> & rSMgr ) +{ + css::lang::XServiceInfo* pX = new ContentProvider( ucbhelper::getComponentContext(rSMgr) ); + return css::uno::Reference< css::uno::XInterface >::query( pX ); +} + +css::uno::Sequence< OUString > +ContentProvider::getSupportedServiceNames_Static() +{ + css::uno::Sequence< OUString > aSNS { "com.sun.star.ucb.GIOContentProvider" }; + return aSNS; +} + +ONE_INSTANCE_SERVICE_FACTORY_IMPL( ContentProvider ); + +} + +// 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 void * ucpgio1_component_getFactory( const char *pImplName, + void *pServiceManager, void * ) +{ + void * pRet = nullptr; + + static bool bDisabled = isDisabled(); + if (bDisabled) + return nullptr; + + css::uno::Reference< css::lang::XMultiServiceFactory > xSMgr + (static_cast< css::lang::XMultiServiceFactory * >( pServiceManager ) ); + css::uno::Reference< css::lang::XSingleServiceFactory > xFactory; + +#if !GLIB_CHECK_VERSION(2,36,0) + g_type_init(); +#endif + if ( ::gio::ContentProvider::getImplementationName_Static().equalsAscii( pImplName ) ) + xFactory = ::gio::ContentProvider::createServiceFactory( xSMgr ); + + if ( xFactory.is() ) + { + xFactory->acquire(); + pRet = xFactory.get(); + } + + return pRet; +} + +/* 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 000000000..6bdb127ac --- /dev/null +++ b/ucb/source/ucp/gio/gio_provider.hxx @@ -0,0 +1,69 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#ifndef INCLUDED_UCB_SOURCE_UCP_GIO_GIO_PROVIDER_HXX +#define INCLUDED_UCB_SOURCE_UCP_GIO_GIO_PROVIDER_HXX + +#include <com/sun/star/lang/XSingleServiceFactory.hpp> +#include <com/sun/star/lang/XMultiServiceFactory.hpp> +#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() + throw() override; + virtual void SAL_CALL release() + throw() 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; + + static OUString getImplementationName_Static(); + static css::uno::Sequence< OUString > getSupportedServiceNames_Static(); + + static css::uno::Reference< css::lang::XSingleServiceFactory > + createServiceFactory( const css::uno::Reference< + css::lang::XMultiServiceFactory >& rxServiceMgr ); + + // XContentProvider + virtual css::uno::Reference< css::ucb::XContent > SAL_CALL + queryContent( const css::uno::Reference< css::ucb::XContentIdentifier >& Identifier ) override; +}; + +} + +#endif + +/* 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 000000000..ee836bd0e --- /dev/null +++ b/ucb/source/ucp/gio/gio_resultset.cxx @@ -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 . + */ + +#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, + const rtl::Reference< Content >& rxContent, + const OpenCommandArgument2& rCommand, + const Reference< XCommandEnvironment >& rxEnv ) + : ResultSetImplHelper( rxContext, rCommand ), + m_xContent( rxContent ), + 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 000000000..99dc6c6c6 --- /dev/null +++ b/ucb/source/ucp/gio/gio_resultset.hxx @@ -0,0 +1,49 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#ifndef INCLUDED_UCB_SOURCE_UCP_GIO_GIO_RESULTSET_HXX +#define INCLUDED_UCB_SOURCE_UCP_GIO_GIO_RESULTSET_HXX + +#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, + const rtl::Reference< Content >& rxContent, + const css::ucb::OpenCommandArgument2& rCommand, + const css::uno::Reference< css::ucb::XCommandEnvironment >& rxEnv ); + }; +} + +#endif + +/* 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 000000000..1f1da5948 --- /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", + static_cast< cppu::OWeakObject * >(this)); + + GError *pError=nullptr; + if (!g_seekable_truncate(mpStream, 0, nullptr, &pError)) + convertToIOException(pError, static_cast< cppu::OWeakObject * >(this)); +} + +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", + static_cast< cppu::OWeakObject * >(this)); + + GError *pError=nullptr; + if (!g_seekable_seek(mpStream, location, G_SEEK_SET, nullptr, &pError)) + convertToIOException(pError, static_cast< cppu::OWeakObject * >(this)); +} + +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, static_cast< cppu::OWeakObject * >(this)); + 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 000000000..0d3a62dcc --- /dev/null +++ b/ucb/source/ucp/gio/gio_seekable.hxx @@ -0,0 +1,64 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#ifndef INCLUDED_UCB_SOURCE_UCP_GIO_GIO_SEEKABLE_HXX +#define INCLUDED_UCB_SOURCE_UCP_GIO_GIO_SEEKABLE_HXX + +#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() throw () override { OWeakObject::acquire(); } + virtual void SAL_CALL release() throw() 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 +#endif + +/* 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 000000000..9fccd965d --- /dev/null +++ b/ucb/source/ucp/gio/ucpgio.component @@ -0,0 +1,25 @@ +<?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@" + prefix="ucpgio1" xmlns="http://openoffice.org/2010/uno-components"> + <implementation name="com.sun.star.comp.GIOContentProvider"> + <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 000000000..e5d863bee --- /dev/null +++ b/ucb/source/ucp/hierarchy/dynamicresultset.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 "hierarchydatasupplier.hxx" +#include "dynamicresultset.hxx" + +using namespace com::sun::star; +using namespace hierarchy_ucp; + + +// DynamicResultSet Implementation. + + +DynamicResultSet::DynamicResultSet( + const uno::Reference< uno::XComponentContext >& rxContext, + const rtl::Reference< HierarchyContent >& rxContent, + const ucb::OpenCommandArgument2& rCommand ) +: ResultSetImplHelper( rxContext, rCommand ), + m_xContent( rxContent ) +{ +} + + +// 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 000000000..9dd4780f6 --- /dev/null +++ b/ucb/source/ucp/hierarchy/dynamicresultset.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 . + */ + +#ifndef INCLUDED_UCB_SOURCE_UCP_HIERARCHY_DYNAMICRESULTSET_HXX +#define INCLUDED_UCB_SOURCE_UCP_HIERARCHY_DYNAMICRESULTSET_HXX + +#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, + const rtl::Reference< HierarchyContent >& rxContent, + const css::ucb::OpenCommandArgument2& rCommand ); +}; + +} + +#endif // INCLUDED_UCB_SOURCE_UCP_HIERARCHY_DYNAMICRESULTSET_HXX + +/* 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 000000000..707f0e31a --- /dev/null +++ b/ucb/source/ucp/hierarchy/hierarchycontent.cxx @@ -0,0 +1,1777 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this 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 "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 ) +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, aProps ); +} + + +// static ( "virtual" ctor ) +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, + const HierarchyContentProperties& rProps ) +: ContentImplHelper( rxContext, pProvider, Identifier ), + m_aProps( rProps ), + 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() + throw( ) +{ + ContentImplHelper::acquire(); +} + + +// virtual +void SAL_CALL HierarchyContent::release() + throw( ) +{ + 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::makeAny( lang::IllegalArgumentException( + "Wrong argument type!", + static_cast< cppu::OWeakObject * >( this ), + -1 ) ), + Environment ); + // Unreachable + } + + aRet <<= getPropertyValues( Properties ); + } + else if ( aCommand.Name == "setPropertyValues" ) + { + + // setPropertyValues + + + uno::Sequence< beans::PropertyValue > aProperties; + if ( !( aCommand.Argument >>= aProperties ) ) + { + ucbhelper::cancelCommandExecution( + uno::makeAny( lang::IllegalArgumentException( + "Wrong argument type!", + static_cast< cppu::OWeakObject * >( this ), + -1 ) ), + Environment ); + // Unreachable + } + + if ( !aProperties.hasElements() ) + { + ucbhelper::cancelCommandExecution( + uno::makeAny( lang::IllegalArgumentException( + "No properties!", + static_cast< cppu::OWeakObject * >( this ), + -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::makeAny( lang::IllegalArgumentException( + "Wrong argument type!", + static_cast< cppu::OWeakObject * >( this ), + -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::makeAny( lang::IllegalArgumentException( + "Wrong argument type!", + static_cast< cppu::OWeakObject * >( this ), + -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::makeAny( lang::IllegalArgumentException( + "Wrong argument type!", + static_cast< cppu::OWeakObject * >( this ), + -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::makeAny( lang::IllegalArgumentException( + "Wrong argument type!", + static_cast< cppu::OWeakObject * >( this ), + -1 ) ), + Environment ); + // Unreachable + } + + aRet <<= createNewContent( aInfo ); + } + else + { + + // Unsupported command + + + ucbhelper::cancelCommandExecution( + uno::makeAny( ucb::UnsupportedCommandException( + OUString(), + static_cast< cppu::OWeakObject * >( this ) ) ), + 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() + "/"; + aNewURL += ::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 identitity. + + // 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::makeAny( 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::makeAny( rData.getCreatableContentsInfo() ) ); + + // Append all Additional Core Properties. + + uno::Reference< beans::XPropertySet > xSet = + pProvider->getAdditionalPropertySet( rContentId, false ); + xRow->appendPropertySet( xSet ); + } + + return uno::Reference< sdbc::XRow >( xRow.get() ); +} + + +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() ); + uno::Sequence< beans::PropertyChangeEvent > aChanges( rValues.getLength() ); + sal_Int32 nChanged = 0; + + beans::PropertyChangeEvent aEvent; + aEvent.Source = static_cast< cppu::OWeakObject * >( this ); + 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! + aRet[ n ] <<= lang::IllegalAccessException( + "Property is read-only!", + static_cast< cppu::OWeakObject * >( this ) ); + } + else if ( rValue.Name == "IsDocument" ) + { + // Read-only property! + aRet[ n ] <<= lang::IllegalAccessException( + "Property is read-only!", + static_cast< cppu::OWeakObject * >( this ) ); + } + else if ( rValue.Name == "IsFolder" ) + { + // Read-only property! + aRet[ n ] <<= lang::IllegalAccessException( + "Property is read-only!", + static_cast< cppu::OWeakObject * >( this ) ); + } + else if ( rValue.Name == "CreatableContentsInfo" ) + { + // Read-only property! + aRet[ n ] <<= lang::IllegalAccessException( + "Property is read-only!", + static_cast< cppu::OWeakObject * >( this ) ); + } + else if ( rValue.Name == "Title" ) + { + if ( isReadOnly() ) + { + aRet[ n ] <<= lang::IllegalAccessException( + "Property is read-only!", + static_cast< cppu::OWeakObject * >( this ) ); + } + 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 + { + aRet[ n ] <<= lang::IllegalArgumentException( + "Empty title not allowed!", + static_cast< cppu::OWeakObject * >( this ), + -1 ); + } + } + else + { + aRet[ n ] <<= beans::IllegalTypeException( + "Property value has wrong type!", + static_cast< cppu::OWeakObject * >( this ) ); + } + } + } + else if ( rValue.Name == "TargetURL" ) + { + if ( isReadOnly() ) + { + aRet[ n ] <<= lang::IllegalAccessException( + "Property is read-only!", + static_cast< cppu::OWeakObject * >( this ) ); + } + 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 + { + aRet[ n ] <<= lang::IllegalArgumentException( + "Empty target URL not allowed!", + static_cast< cppu::OWeakObject * >( this ), + -1 ); + } + } + else + { + aRet[ n ] <<= beans::IllegalTypeException( + "Property value has wrong type!", + static_cast< cppu::OWeakObject * >( this ) ); + } + } + else + { + aRet[ n ] <<= beans::UnknownPropertyException( + "TargetURL only supported by links!", + static_cast< cppu::OWeakObject * >( this ) ); + } + } + } + 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 ) + { + aRet[ n ] <<= e; + } + catch ( lang::WrappedTargetException const & e ) + { + aRet[ n ] <<= e; + } + catch ( beans::PropertyVetoException const & e ) + { + aRet[ n ] <<= e; + } + catch ( lang::IllegalArgumentException const & e ) + { + aRet[ n ] <<= e; + } + } + else + { + aRet[ n ] <<= uno::Exception( + "No property set for storing the value!", + static_cast< cppu::OWeakObject * >( this ) ); + } + } + } + + 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 . + aRet[ nTitlePos ] <<= uno::Exception( + "Exchange failed!", + static_cast< cppu::OWeakObject * >( this ) ); + } + 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::makeAny( ucb::UnsupportedCommandException( + "Not supported by root folder!", + static_cast< cppu::OWeakObject * >( this ) ) ), + xEnv ); + // Unreachable + } + + // Check, if all required properties were set. + if ( m_aProps.getTitle().isEmpty() ) + { + uno::Sequence<OUString> aProps { "Title" }; + ucbhelper::cancelCommandExecution( + uno::makeAny( ucb::MissingPropertiesException( + OUString(), + static_cast< cppu::OWeakObject * >( this ), + 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::makeAny( + ucb::NameClashException( + OUString(), + static_cast< cppu::OWeakObject * >( this ), + 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() + "_"; + aNewId += OUString::number( ++nTry ); + xId = new ::ucbhelper::ContentIdentifier( aNewId ); + } + while ( hasData( xId ) && ( nTry < 1000 ) ); + + if ( nTry == 1000 ) + { + ucbhelper::cancelCommandExecution( + uno::makeAny( + ucb::UnsupportedNameClashException( + "Unable to resolve name clash!", + static_cast< cppu::OWeakObject * >( this ), + nNameClashResolve ) ), + xEnv ); + // Unreachable + } + else + { + OUString aNewTitle( m_aProps.getTitle() ); + aNewTitle += "_" + + OUString::number( nTry ); + m_aProps.setTitle( aNewTitle ); + } + } + break; + + case ucb::NameClash::KEEP: // deprecated + case ucb::NameClash::ASK: + default: + if ( hasData( xId ) ) + { + ucbhelper::cancelCommandExecution( + uno::makeAny( + ucb::UnsupportedNameClashException( + OUString(), + static_cast< cppu::OWeakObject * >( this ), + 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::makeAny( ucb::UnsupportedCommandException( + "Not persistent!", + static_cast< cppu::OWeakObject * >( this ) ) ), + xEnv ); + // Unreachable + } + + // Am I the root folder? + if ( m_eKind == ROOT ) + { + ucbhelper::cancelCommandExecution( + uno::makeAny( ucb::UnsupportedCommandException( + "Not supported by root folder!", + static_cast< cppu::OWeakObject * >( this ) ) ), + 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::makeAny( ucb::UnsupportedCommandException( + "Not persistent!", + static_cast< cppu::OWeakObject * >( this ) ) ), + xEnv ); + // Unreachable + } + + // Is source a hierarchy content? + if ( !rInfo.SourceURL.startsWith( HIERARCHY_URL_SCHEME ":/" ) ) + { + ucbhelper::cancelCommandExecution( + uno::makeAny( ucb::InteractiveBadTransferURLException( + OUString(), + static_cast< cppu::OWeakObject * >( this ) ) ), + 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() + ? OUString( HIERARCHY_FOLDER_CONTENT_TYPE ) + : OUString( 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 ) + { + 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 000000000..3e17ac35e --- /dev/null +++ b/ucb/source/ucp/hierarchy/hierarchycontent.hxx @@ -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 . + */ + +#ifndef INCLUDED_UCB_SOURCE_UCP_HIERARCHY_HIERARCHYCONTENT_HXX +#define INCLUDED_UCB_SOURCE_UCP_HIERARCHY_HIERARCHYCONTENT_HXX + +#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 + ? OUString( HIERARCHY_FOLDER_CONTENT_TYPE ) + : OUString( HIERARCHY_LINK_CONTENT_TYPE ) ) {} + + explicit HierarchyContentProperties( const HierarchyEntryData & rData ) + : m_aData( rData ), + m_aContentType( rData.getType() == HierarchyEntryData::FOLDER + ? OUString( HIERARCHY_FOLDER_CONTENT_TYPE ) + : OUString( 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, + const HierarchyContentProperties& rProps ); + 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 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 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() + throw() override; + virtual void SAL_CALL release() + throw() 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 + +#endif // INCLUDED_UCB_SOURCE_UCP_HIERARCHY_HIERARCHYCONTENT_HXX + +/* 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 000000000..836a136f6 --- /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 000000000..0f78cd033 --- /dev/null +++ b/ucb/source/ucp/hierarchy/hierarchydata.cxx @@ -0,0 +1,1136 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this 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 <osl/diagnose.h> +#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 "hierarchyprovider.hxx" +#include "hierarchyuri.hxx" + +using namespace com::sun::star; + +namespace hierarchy_ucp +{ + + +struct HierarchyEntry::iterator_Impl +{ + HierarchyEntryData entry; + uno::Reference< container::XHierarchicalNameAccess > dir; + uno::Reference< util::XOfficeInstallationDirectories > officeDirs; + uno::Sequence< OUString> names; + sal_Int32 pos; + iterator_Impl() + : pos( -1 /* before first */ ) {}; +}; + + +static void makeXMLName( const OUString & rIn, OUStringBuffer & rBuffer ) +{ + sal_Int32 nCount = rIn.getLength(); + for ( sal_Int32 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. + + +#define READ_SERVICE_NAME "com.sun.star.ucb.HierarchyDataReadAccess" +#define READWRITE_SERVICE_NAME "com.sun.star.ucb.HierarchyDataReadWriteAccess" + +// describe path of cfg entry +#define CFGPROPERTY_NODEPATH "nodepath" + + +HierarchyEntry::HierarchyEntry( + const uno::Reference< uno::XComponentContext >& rxContext, + HierarchyContentProvider* pProvider, + const OUString& rURL ) +: m_xContext( rxContext ), + 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() +{ + osl::Guard< osl::Mutex > aGuard( m_aMutex ); + 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 + { + osl::Guard< osl::Mutex > aGuard( m_aMutex ); + + 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 + + OSL_FAIL( "HierarchyEntry::getData - caught NoSuchElementException!" ); + } + return false; +} + + +bool HierarchyEntry::setData( const HierarchyEntryData& rData ) +{ + try + { + osl::Guard< osl::Mutex > 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.copy( 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::makeAny( 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::makeAny( aValue ) ); + + // Set Type value. + sal_Int32 nType + = rData.getType() == HierarchyEntryData::LINK ? 0 : 1; + xNameReplace->replaceByName( + "Type", + uno::makeAny( nType ) ); + + if ( xContainer.is() ) + xContainer->insertByName( + m_aName, uno::makeAny( xNameReplace ) ); + + // Commit changes. + xBatch->commitChanges(); + return true; + } + } + } + } + catch ( lang::IllegalArgumentException const & ) + { + // replaceByName, insertByName + + OSL_FAIL( + "HierarchyEntry::setData - caught IllegalArgumentException!" ); + } + catch ( uno::RuntimeException const & ) + { + throw; + } + catch ( container::NoSuchElementException const & ) + { + // replaceByName, getByName + + OSL_FAIL( + "HierarchyEntry::setData - caught NoSuchElementException!" ); + } + catch ( container::ElementExistException const & ) + { + // insertByName + + OSL_FAIL( + "HierarchyEntry::setData - caught ElementExistException!" ); + } + catch ( lang::WrappedTargetException const & ) + { + // replaceByName, insertByName, getByName, commitChanges + + OSL_FAIL( + "HierarchyEntry::setData - caught WrappedTargetException!" ); + } + catch ( uno::Exception const & ) + { + // createInstance, createInstanceWithArguments + + OSL_FAIL( + "HierarchyEntry::setData - caught Exception!" ); + } + + return false; +} + + +bool HierarchyEntry::move( + const OUString& rNewURL, const HierarchyEntryData& rData ) +{ + osl::Guard< osl::Mutex > aGuard( m_aMutex ); + + OUString aNewPath = createPathFromHierarchyURL( HierarchyUri(rNewURL) ); + + 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.copy( 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.copy( 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 + + OSL_FAIL( "HierarchyEntry::move - caught Exception!" ); + 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 + + OSL_FAIL( "HierarchyEntry::move - caught NoSuchElementException!" ); + return false; + } + catch ( lang::WrappedTargetException const & ) + { + // getByName + + OSL_FAIL( "HierarchyEntry::move - caught WrappedTargetException!" ); + 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 + + OSL_FAIL( "HierarchyEntry::move - caught NoSuchElementException!" ); + 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::makeAny( 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::makeAny( aValue ) ); + sal_Int32 nType = rData.getType() == HierarchyEntryData::LINK ? 0 : 1; + xNewNameReplace->replaceByName( + "Type", + uno::makeAny( nType ) ); + + xNewNameContainer->insertByName( aNewKey, aEntry ); + xNewParentBatch->commitChanges(); + } + catch ( container::NoSuchElementException const & ) + { + // replaceByName, insertByName, getByName + + OSL_FAIL( "HierarchyEntry::move - caught NoSuchElementException!" ); + return false; + } + catch ( lang::IllegalArgumentException const & ) + { + // replaceByName, insertByName + + OSL_FAIL( + "HierarchyEntry::move - caught IllegalArgumentException!" ); + return false; + } + catch ( container::ElementExistException const & ) + { + // insertByName + + OSL_FAIL( "HierarchyEntry::move - caught ElementExistException!" ); + return false; + } + catch ( lang::WrappedTargetException const & ) + { + // replaceByName, insertByName, getByName + + OSL_FAIL( "HierarchyEntry::move - caught WrappedTargetException!" ); + return false; + } + + return true; +} + + +bool HierarchyEntry::remove() +{ + try + { + osl::Guard< osl::Mutex > 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.copy( 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 + + OSL_FAIL( "HierarchyEntry::remove - caught Exception!" ); + } + + return false; +} + + +bool HierarchyEntry::first( iterator const & it ) +{ + osl::Guard< osl::Mutex > aGuard( m_aMutex ); + + if ( it.m_pImpl->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.m_pImpl->names = xNameAccess->getElementNames(); + + uno::Reference< container::XHierarchicalNameAccess > + xHierNameAccess( xNameAccess, uno::UNO_QUERY ); + + OSL_ENSURE( xHierNameAccess.is(), + "HierarchyEntry::first - No hier. name access!" ); + + it.m_pImpl->dir = xHierNameAccess; + + it.m_pImpl->officeDirs = m_xOfficeInstDirs; + } + } + catch ( uno::RuntimeException const & ) + { + throw; + } + catch ( container::NoSuchElementException const& ) + { + // getByHierarchicalName + + OSL_FAIL( + "HierarchyEntry::first - caught NoSuchElementException!" ); + } + catch ( uno::Exception const & ) + { + OSL_FAIL( "HierarchyEntry::first - caught Exception!" ); + } + } + + if ( !it.m_pImpl->names.hasElements() ) + return false; + + it.m_pImpl->pos = 0; + return true; +} + + +bool HierarchyEntry::next( iterator const & it ) +{ + osl::Guard< osl::Mutex > aGuard( m_aMutex ); + + if ( it.m_pImpl->pos == -1 ) + return first( it ); + + ++(it.m_pImpl->pos); + + return ( it.m_pImpl->pos < it.m_pImpl->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; + aNewPath.append( "['" ); + + 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() ) + { + osl::Guard< osl::Mutex > 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 + + OSL_FAIL( "HierarchyEntry::getRootReadAccess - " + "caught Exception!" ); + } + } + } + return m_xRootReadAccess; +} + + +// HierarchyEntry::iterator Implementation. + + +HierarchyEntry::iterator::iterator() + : m_pImpl( new iterator_Impl ) +{ +} + + +HierarchyEntry::iterator::~iterator() +{ +} + + +const HierarchyEntryData& HierarchyEntry::iterator::operator*() const +{ + if ( ( m_pImpl->pos != -1 ) + && ( m_pImpl->dir.is() ) + && ( m_pImpl->pos < m_pImpl->names.getLength() ) ) + { + try + { + OUStringBuffer aKey; + aKey.append( "['" ); + makeXMLName( m_pImpl->names.getConstArray()[ m_pImpl->pos ], aKey ); + aKey.append( "']" ); + + OUString aTitle = aKey.makeStringAndClear(); + OUString aTargetURL = aTitle; + OUString aType = aTitle; + + aTitle += "/Title"; + aTargetURL += "/TargetURL"; + aType += "/Type"; + + OUString aValue; + m_pImpl->dir->getByHierarchicalName( aTitle ) >>= aValue; + m_pImpl->entry.setTitle( aValue ); + + m_pImpl->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 ( m_pImpl->officeDirs.is() && !aValue.isEmpty() ) + aValue = m_pImpl->officeDirs->makeAbsoluteURL( aValue ); + m_pImpl->entry.setTargetURL( aValue ); + + if ( m_pImpl->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 ( m_pImpl->dir->getByHierarchicalName( aType ) >>= nType ) + { + if ( nType == 0 ) + { + m_pImpl->entry.setType( HierarchyEntryData::LINK ); + } + else if ( nType == 1 ) + { + m_pImpl->entry.setType( HierarchyEntryData::FOLDER ); + } + else + { + OSL_FAIL( "HierarchyEntry::getData - " + "Unknown Type value!" ); + } + } + } + + m_pImpl->entry.setName( + m_pImpl->names.getConstArray()[ m_pImpl->pos ] ); + } + catch ( container::NoSuchElementException const & ) + { + m_pImpl->entry = HierarchyEntryData(); + } + } + + return m_pImpl->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 000000000..8810964b3 --- /dev/null +++ b/ucb/source/ucp/hierarchy/hierarchydata.hxx @@ -0,0 +1,137 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#ifndef INCLUDED_UCB_SOURCE_UCP_HIERARCHY_HIERARCHYDATA_HXX +#define INCLUDED_UCB_SOURCE_UCP_HIERARCHY_HIERARCHYDATA_HXX + +#include <rtl/ustring.hxx> +#include <osl/mutex.hxx> +#include <com/sun/star/lang/XMultiServiceFactory.hpp> +#include <com/sun/star/uno/XComponentContext.hpp> +#include <memory> + +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; + ::osl::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( const css::uno::Reference< css::uno::XComponentContext >& rxContext, + 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. + + struct iterator_Impl; + + class iterator + { + friend class HierarchyEntry; + + std::unique_ptr<iterator_Impl> m_pImpl; + + public: + iterator(); + ~iterator(); + + const HierarchyEntryData& operator*() const; + }; + + bool first( iterator const & it ); + bool next ( iterator const & it ); +}; + +} // namespace hierarchy_ucp + +#endif // INCLUDED_UCB_SOURCE_UCP_HIERARCHY_HIERARCHYDATA_HXX + +/* 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 000000000..5e5c63f94 --- /dev/null +++ b/ucb/source/ucp/hierarchy/hierarchydatasource.cxx @@ -0,0 +1,873 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this 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/interfacecontainer2.hxx> +#include <cppuhelper/queryinterface.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 <ucbhelper/getcomponentcontext.hxx> +#include <ucbhelper/macros.hxx> + +using namespace com::sun::star; +using namespace hierarchy_ucp; + + +// describe path of cfg entry +#define CFGPROPERTY_NODEPATH "nodepath" + +#define READ_SERVICE_NAME "com.sun.star.ucb.HierarchyDataReadAccess" +#define READWRITE_SERVICE_NAME "com.sun.star.ucb.HierarchyDataReadWriteAccess" + +#define CONFIG_DATA_ROOT_KEY \ + "/org.openoffice.ucb.Hierarchy/Root" + + +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 +{ + osl::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( const 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() + throw() override; + virtual void SAL_CALL release() + throw() 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( + const uno::Reference< uno::XComponentContext > & rxContext ) +: m_xContext( rxContext ) +{ +} + + +// virtual +HierarchyDataSource::~HierarchyDataSource() +{ +} + +// XServiceInfo methods. + +XSERVICEINFO_COMMOM_IMPL( HierarchyDataSource, + "com.sun.star.comp.ucb.HierarchyDataSource" ) +/// @throws css::uno::Exception +static css::uno::Reference< css::uno::XInterface > +HierarchyDataSource_CreateInstance( const css::uno::Reference< css::lang::XMultiServiceFactory> & rSMgr ) +{ + css::lang::XServiceInfo* pX = new HierarchyDataSource( ucbhelper::getComponentContext(rSMgr) ); + return css::uno::Reference< css::uno::XInterface >::query( pX ); +} + +css::uno::Sequence< OUString > +HierarchyDataSource::getSupportedServiceNames_Static() +{ + return { "com.sun.star.ucb.DefaultHierarchyDataSource", "com.sun.star.ucb.HierarchyDataSource" }; +} + +ONE_INSTANCE_SERVICE_FACTORY_IMPL( HierarchyDataSource ); + + +// XComponent methods. + + +// virtual +void SAL_CALL HierarchyDataSource::dispose() +{ + osl::Guard< osl::Mutex > aGuard( m_aMutex ); + + if ( m_pDisposeEventListeners && m_pDisposeEventListeners->getLength() ) + { + lang::EventObject aEvt; + aEvt.Source = static_cast< lang::XComponent * >( this ); + m_pDisposeEventListeners->disposeAndClear( aEvt ); + } +} + + +// virtual +void SAL_CALL HierarchyDataSource::addEventListener( + const uno::Reference< lang::XEventListener > & Listener ) +{ + osl::Guard< osl::Mutex > aGuard( m_aMutex ); + + if ( !m_pDisposeEventListeners ) + m_pDisposeEventListeners.reset( + new comphelper::OInterfaceContainerHelper2( m_aMutex ) ); + + m_pDisposeEventListeners->addInterface( Listener ); +} + + +// virtual +void SAL_CALL HierarchyDataSource::removeEventListener( + const uno::Reference< lang::XEventListener > & Listener ) +{ + osl::Guard< osl::Mutex > aGuard( m_aMutex ); + + if ( m_pDisposeEventListeners ) + m_pDisposeEventListeners->removeInterface( Listener ); +} + + +// XMultiServiceFactory methods. + + +// virtual +uno::Reference< uno::XInterface > SAL_CALL +HierarchyDataSource::createInstance( const OUString & aServiceSpecifier ) +{ + // Create view to root node. + + beans::PropertyValue aProp; + aProp.Name = CFGPROPERTY_NODEPATH; + aProp.Value <<= OUString( CONFIG_DATA_ROOT_KEY ); + + uno::Sequence< uno::Any > aArguments( 1 ); + aArguments[ 0 ] <<= 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() +{ + uno::Sequence< OUString > aNames( 2 ); + aNames[ 0 ] = READ_SERVICE_NAME; + aNames[ 1 ] = READWRITE_SERVICE_NAME; + return aNames; +} + + +// Non-interface methods + + +uno::Reference< uno::XInterface > +HierarchyDataSource::createInstanceWithArguments( + const OUString & ServiceSpecifier, + const uno::Sequence< uno::Any > & Arguments, + bool bCheckArgs ) +{ + osl::Guard< osl::Mutex > aGuard( m_aMutex ); + + // 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 ); + + 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. + aNewArgs[ 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 uno::Reference< uno::XInterface >( + static_cast< cppu::OWeakObject * >( + new HierarchyDataAccess( xConfigAccess, bReadOnly ) ) ); +} + + +uno::Reference< lang::XMultiServiceFactory > +HierarchyDataSource::getConfigProvider() +{ + if ( !m_xConfigProvider.is() ) + { + osl::Guard< osl::Mutex > 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( + const OUString & rInPath, OUString & rOutPath ) +{ + if ( !rInPath.isEmpty() ) + { + if ( rInPath.startsWith( "/" ) ) + { + OSL_FAIL( "HierarchyDataSource::createConfigPath - " + "Leading slash in node path!" ); + return false; + } + + if ( rInPath.endsWith( "/" ) ) + { + 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; + osl::Guard< osl::Mutex > aGuard( m_aMutex ); + if ( !x.is() ) + x.set( m_xConfigAccess, uno::UNO_QUERY ); + return x; +} + + +HierarchyDataAccess::HierarchyDataAccess( const uno::Reference< + uno::XInterface > & xConfigAccess, + bool bReadOnly ) +: m_xConfigAccess( xConfigAccess ), + m_bReadOnly( bReadOnly ) +{ +} + +// XInterface methods. +void SAL_CALL HierarchyDataAccess::acquire() + throw() +{ + OWeakObject::acquire(); +} + +void SAL_CALL HierarchyDataAccess::release() + throw() +{ + 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 000000000..3c2eb82ba --- /dev/null +++ b/ucb/source/ucp/hierarchy/hierarchydatasource.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 . + */ + +#ifndef INCLUDED_UCB_SOURCE_UCP_HIERARCHY_HIERARCHYDATASOURCE_HXX +#define INCLUDED_UCB_SOURCE_UCP_HIERARCHY_HIERARCHYDATASOURCE_HXX + +#include <osl/mutex.hxx> +#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/lang/XSingleServiceFactory.hpp> +#include <com/sun/star/uno/XComponentContext.hpp> +#include <cppuhelper/implbase.hxx> +#include <memory> + +namespace comphelper { class OInterfaceContainerHelper2; } + +namespace hierarchy_ucp { + + +class HierarchyDataSource : public cppu::WeakImplHelper< + css::lang::XServiceInfo, + css::lang::XComponent, + css::lang::XMultiServiceFactory> +{ + osl::Mutex m_aMutex; + css::uno::Reference< css::uno::XComponentContext > m_xContext; + css::uno::Reference< css::lang::XMultiServiceFactory > m_xConfigProvider; + std::unique_ptr<comphelper::OInterfaceContainerHelper2> m_pDisposeEventListeners; + +public: + explicit HierarchyDataSource( const css::uno::Reference< css::uno::XComponentContext > & rxContext ); + 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; + + static OUString getImplementationName_Static(); + static css::uno::Sequence< OUString > getSupportedServiceNames_Static(); + + static css::uno::Reference< css::lang::XSingleServiceFactory > createServiceFactory( const css::uno::Reference< + css::lang::XMultiServiceFactory >& rxServiceMgr ); + + // 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( const OUString & ServiceSpecifier, + const css::uno::Sequence< + css::uno::Any > & Arguments, + bool bCheckArgs ); + + css::uno::Reference< css::lang::XMultiServiceFactory > getConfigProvider(); + + static bool createConfigPath( const OUString & rInPath, OUString & rOutPath ); +}; + +} // namespace hierarchy_ucp + +#endif // INCLUDED_UCB_SOURCE_UCP_HIERARCHY_HIERARCHYDATASOURCE_HXX + +/* 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 000000000..2415714e7 --- /dev/null +++ b/ucb/source/ucp/hierarchy/hierarchydatasupplier.cxx @@ -0,0 +1,412 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this 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 <vector> + +#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; + +namespace hierarchy_ucp +{ + + +// struct ResultListEntry. + +namespace { + +struct ResultListEntry +{ + OUString aId; + uno::Reference< ucb::XContentIdentifier > xId; + uno::Reference< ucb::XContent > xContent; + uno::Reference< sdbc::XRow > xRow; + HierarchyEntryData aData; + + explicit ResultListEntry( const HierarchyEntryData& rEntry ) : aData( rEntry ) {} +}; + +} + +// ResultList. + + +typedef std::vector< std::unique_ptr<ResultListEntry> > ResultList; + + +// struct DataSupplier_Impl. + + +struct DataSupplier_Impl +{ + osl::Mutex m_aMutex; + ResultList m_aResults; + rtl::Reference< HierarchyContent > m_xContent; + uno::Reference< uno::XComponentContext > m_xContext; + HierarchyEntry m_aFolder; + HierarchyEntry::iterator m_aIterator; + sal_Int32 m_nOpenMode; + bool m_bCountFinal; + + DataSupplier_Impl( + 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 ) {} +}; + + +} + + +// HierarchyResultSetDataSupplier Implementation. + + +HierarchyResultSetDataSupplier::HierarchyResultSetDataSupplier( + const uno::Reference< uno::XComponentContext >& rxContext, + const rtl::Reference< HierarchyContent >& rContent, + sal_Int32 nOpenMode ) +: m_pImpl( new DataSupplier_Impl( rxContext, rContent, nOpenMode ) ) +{ +} + + +// virtual +HierarchyResultSetDataSupplier::~HierarchyResultSetDataSupplier() +{ +} + + +// virtual +OUString HierarchyResultSetDataSupplier::queryContentIdentifierString( + sal_uInt32 nIndex ) +{ + osl::Guard< osl::Mutex > aGuard( m_pImpl->m_aMutex ); + + if ( nIndex < m_pImpl->m_aResults.size() ) + { + OUString aId = m_pImpl->m_aResults[ nIndex ]->aId; + if ( !aId.isEmpty() ) + { + // Already cached. + return aId; + } + } + + if ( getResult( nIndex ) ) + { + OUString aId + = m_pImpl->m_xContent->getIdentifier()->getContentIdentifier(); + + if ( ( aId.lastIndexOf( '/' ) + 1 ) != aId.getLength() ) + aId += "/"; + + aId += m_pImpl->m_aResults[ nIndex ]->aData.getName(); + + m_pImpl->m_aResults[ nIndex ]->aId = aId; + return aId; + } + return OUString(); +} + + +// virtual +uno::Reference< ucb::XContentIdentifier > +HierarchyResultSetDataSupplier::queryContentIdentifier( sal_uInt32 nIndex ) +{ + osl::Guard< osl::Mutex > aGuard( m_pImpl->m_aMutex ); + + if ( nIndex < m_pImpl->m_aResults.size() ) + { + uno::Reference< ucb::XContentIdentifier > xId + = m_pImpl->m_aResults[ nIndex ]->xId; + if ( xId.is() ) + { + // Already cached. + return xId; + } + } + + OUString aId = queryContentIdentifierString( nIndex ); + if ( !aId.isEmpty() ) + { + uno::Reference< ucb::XContentIdentifier > xId + = new ::ucbhelper::ContentIdentifier( aId ); + m_pImpl->m_aResults[ nIndex ]->xId = xId; + return xId; + } + return uno::Reference< ucb::XContentIdentifier >(); +} + + +// virtual +uno::Reference< ucb::XContent > +HierarchyResultSetDataSupplier::queryContent( sal_uInt32 nIndex ) +{ + osl::Guard< osl::Mutex > aGuard( m_pImpl->m_aMutex ); + + if ( nIndex < m_pImpl->m_aResults.size() ) + { + uno::Reference< ucb::XContent > xContent + = m_pImpl->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_pImpl->m_xContent->getProvider()->queryContent( xId ); + m_pImpl->m_aResults[ nIndex ]->xContent = xContent; + return xContent; + + } + catch ( ucb::IllegalIdentifierException const & ) + { + } + } + return uno::Reference< ucb::XContent >(); +} + + +// virtual +bool HierarchyResultSetDataSupplier::getResult( sal_uInt32 nIndex ) +{ + osl::ClearableGuard< osl::Mutex > aGuard( m_pImpl->m_aMutex ); + + if ( m_pImpl->m_aResults.size() > nIndex ) + { + // Result already present. + return true; + } + + // Result not (yet) present. + + if ( m_pImpl->m_bCountFinal ) + return false; + + // Try to obtain result... + + sal_uInt32 nOldCount = m_pImpl->m_aResults.size(); + bool bFound = false; + sal_uInt32 nPos = nOldCount; + + while ( m_pImpl->m_aFolder.next( m_pImpl->m_aIterator ) ) + { + const HierarchyEntryData& rResult = *m_pImpl->m_aIterator; + if ( checkResult( rResult ) ) + { + m_pImpl->m_aResults.emplace_back( new ResultListEntry( rResult ) ); + + if ( nPos == nIndex ) + { + // Result obtained. + bFound = true; + break; + } + } + nPos++; + } + + if ( !bFound ) + m_pImpl->m_bCountFinal = true; + + rtl::Reference< ::ucbhelper::ResultSet > xResultSet = getResultSet().get(); + if ( xResultSet.is() ) + { + // Callbacks follow! + aGuard.clear(); + + if ( nOldCount < m_pImpl->m_aResults.size() ) + xResultSet->rowCountChanged( + nOldCount, m_pImpl->m_aResults.size() ); + + if ( m_pImpl->m_bCountFinal ) + xResultSet->rowCountFinal(); + } + + return bFound; +} + + +// virtual +sal_uInt32 HierarchyResultSetDataSupplier::totalCount() +{ + osl::ClearableGuard< osl::Mutex > aGuard( m_pImpl->m_aMutex ); + + if ( m_pImpl->m_bCountFinal ) + return m_pImpl->m_aResults.size(); + + sal_uInt32 nOldCount = m_pImpl->m_aResults.size(); + + while ( m_pImpl->m_aFolder.next( m_pImpl->m_aIterator ) ) + { + const HierarchyEntryData& rResult = *m_pImpl->m_aIterator; + if ( checkResult( rResult ) ) + m_pImpl->m_aResults.emplace_back( new ResultListEntry( rResult ) ); + } + + m_pImpl->m_bCountFinal = true; + + rtl::Reference< ::ucbhelper::ResultSet > xResultSet = getResultSet().get(); + if ( xResultSet.is() ) + { + // Callbacks follow! + aGuard.clear(); + + if ( nOldCount < m_pImpl->m_aResults.size() ) + xResultSet->rowCountChanged( + nOldCount, m_pImpl->m_aResults.size() ); + + xResultSet->rowCountFinal(); + } + + return m_pImpl->m_aResults.size(); +} + + +// virtual +sal_uInt32 HierarchyResultSetDataSupplier::currentCount() +{ + return m_pImpl->m_aResults.size(); +} + + +// virtual +bool HierarchyResultSetDataSupplier::isCountFinal() +{ + return m_pImpl->m_bCountFinal; +} + + +// virtual +uno::Reference< sdbc::XRow > +HierarchyResultSetDataSupplier::queryPropertyValues( sal_uInt32 nIndex ) +{ + osl::Guard< osl::Mutex > aGuard( m_pImpl->m_aMutex ); + + if ( nIndex < m_pImpl->m_aResults.size() ) + { + uno::Reference< sdbc::XRow > xRow + = m_pImpl->m_aResults[ nIndex ]->xRow; + if ( xRow.is() ) + { + // Already cached. + return xRow; + } + } + + if ( getResult( nIndex ) ) + { + HierarchyContentProperties aData( + m_pImpl->m_aResults[ nIndex ]->aData ); + + uno::Reference< sdbc::XRow > xRow + = HierarchyContent::getPropertyValues( + m_pImpl->m_xContext, + getResultSet()->getProperties(), + aData, + static_cast< HierarchyContentProvider * >( + m_pImpl->m_xContent->getProvider().get() ), + queryContentIdentifierString( nIndex ) ); + m_pImpl->m_aResults[ nIndex ]->xRow = xRow; + return xRow; + } + + return uno::Reference< sdbc::XRow >(); +} + + +// virtual +void HierarchyResultSetDataSupplier::releasePropertyValues( sal_uInt32 nIndex ) +{ + osl::Guard< osl::Mutex > aGuard( m_pImpl->m_aMutex ); + + if ( nIndex < m_pImpl->m_aResults.size() ) + m_pImpl->m_aResults[ nIndex ]->xRow.clear(); +} + + +// virtual +void HierarchyResultSetDataSupplier::close() +{ +} + + +// virtual +void HierarchyResultSetDataSupplier::validate() +{ +} + + +bool HierarchyResultSetDataSupplier::checkResult( + const HierarchyEntryData& rResult ) +{ + switch ( m_pImpl->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 000000000..fe3aa7a61 --- /dev/null +++ b/ucb/source/ucp/hierarchy/hierarchydatasupplier.hxx @@ -0,0 +1,73 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#ifndef INCLUDED_UCB_SOURCE_UCP_HIERARCHY_HIERARCHYDATASUPPLIER_HXX +#define INCLUDED_UCB_SOURCE_UCP_HIERARCHY_HIERARCHYDATASUPPLIER_HXX + +#include <rtl/ref.hxx> +#include <ucbhelper/resultset.hxx> +#include <memory> + +namespace hierarchy_ucp { + +class HierarchyEntryData; +struct DataSupplier_Impl; +class HierarchyContent; + +class HierarchyResultSetDataSupplier : + public ::ucbhelper::ResultSetDataSupplier +{ + std::unique_ptr<DataSupplier_Impl> m_pImpl; + +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 ) 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; +}; + +} // namespace hierarchy_ucp + +#endif // INCLUDED_UCB_SOURCE_UCP_HIERARCHY_HIERARCHYDATASUPPLIER_HXX + +/* 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 000000000..d95d39dd0 --- /dev/null +++ b/ucb/source/ucp/hierarchy/hierarchyprovider.cxx @@ -0,0 +1,282 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this 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 <ucbhelper/contentidentifier.hxx> +#include <ucbhelper/getcomponentcontext.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 ) +: ::ucbhelper::ContentProviderImplHelper( rxContext ) +{ +} + + +// virtual +HierarchyContentProvider::~HierarchyContentProvider() +{ +} + + +// XInterface methods. + +void SAL_CALL HierarchyContentProvider::acquire() + throw() +{ + OWeakObject::acquire(); +} + +void SAL_CALL HierarchyContentProvider::release() + throw() +{ + OWeakObject::release(); +} + +css::uno::Any SAL_CALL HierarchyContentProvider::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), + static_cast< lang::XInitialization* >(this) + ); + return aRet.hasValue() ? aRet : OWeakObject::queryInterface( rType ); +} + +// XTypeProvider methods. + + +XTYPEPROVIDER_IMPL_4( HierarchyContentProvider, + lang::XTypeProvider, + lang::XServiceInfo, + ucb::XContentProvider, + lang::XInitialization ); + + +// XServiceInfo methods. + +XSERVICEINFO_COMMOM_IMPL( HierarchyContentProvider, + "com.sun.star.comp.ucb.HierarchyContentProvider" ) +/// @throws css::uno::Exception +static css::uno::Reference< css::uno::XInterface > +HierarchyContentProvider_CreateInstance( const css::uno::Reference< css::lang::XMultiServiceFactory> & rSMgr ) +{ + css::lang::XServiceInfo* pX = new HierarchyContentProvider( ucbhelper::getComponentContext(rSMgr) ); + return css::uno::Reference< css::uno::XInterface >::query( pX ); +} + +css::uno::Sequence< OUString > +HierarchyContentProvider::getSupportedServiceNames_Static() +{ + css::uno::Sequence< OUString > aSNS { "com.sun.star.ucb.HierarchyContentProvider" }; + return aSNS; +} + +// Service factory implementation. + + +ONE_INSTANCE_SERVICE_FACTORY_IMPL( HierarchyContentProvider ); + + +// 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 ).get(); + 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() ) + { + uno::Sequence< uno::Any > aArguments( 1 ); + beans::PropertyValue aProperty; + aProperty.Name = "nodepath" ; + aProperty.Value <<= OUString(); // root path + aArguments[ 0 ] <<= 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 000000000..1bc72e034 --- /dev/null +++ b/ucb/source/ucp/hierarchy/hierarchyprovider.hxx @@ -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 . + */ + +#ifndef INCLUDED_UCB_SOURCE_UCP_HIERARCHY_HIERARCHYPROVIDER_HXX +#define INCLUDED_UCB_SOURCE_UCP_HIERARCHY_HIERARCHYPROVIDER_HXX + +#include <ucbhelper/providerhelper.hxx> +#include <com/sun/star/lang/XInitialization.hpp> +#include <com/sun/star/lang/XSingleServiceFactory.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 + +#define HIERARCHY_FOLDER_CONTENT_TYPE \ + "application/" HIERARCHY_URL_SCHEME "-folder" +#define HIERARCHY_LINK_CONTENT_TYPE \ + "application/" HIERARCHY_URL_SCHEME "-link" + +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; + +class HierarchyContentProvider : public ::ucbhelper::ContentProviderImplHelper, + public css::lang::XInitialization +{ + 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; + + // XInterface + virtual css::uno::Any SAL_CALL queryInterface( const css::uno::Type & rType ) override; + virtual void SAL_CALL acquire() + throw() override; + virtual void SAL_CALL release() + throw() 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; + + static OUString getImplementationName_Static(); + static css::uno::Sequence< OUString > getSupportedServiceNames_Static(); + + static css::uno::Reference< css::lang::XSingleServiceFactory > + createServiceFactory( const css::uno::Reference< + css::lang::XMultiServiceFactory >& rxServiceMgr ); + + // 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 + +#endif // INCLUDED_UCB_SOURCE_UCP_HIERARCHY_HIERARCHYPROVIDER_HXX + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/ucb/source/ucp/hierarchy/hierarchyservices.cxx b/ucb/source/ucp/hierarchy/hierarchyservices.cxx new file mode 100644 index 000000000..a8161341d --- /dev/null +++ b/ucb/source/ucp/hierarchy/hierarchyservices.cxx @@ -0,0 +1,69 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this 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/XMultiServiceFactory.hpp> +#include <com/sun/star/lang/XSingleServiceFactory.hpp> +#include "hierarchyprovider.hxx" +#include "hierarchydatasource.hxx" + +using namespace com::sun::star; +using namespace hierarchy_ucp; + + +extern "C" SAL_DLLPUBLIC_EXPORT void * ucphier1_component_getFactory( + const char * pImplName, void * pServiceManager, void * /*pRegistryKey*/ ) +{ + void * pRet = nullptr; + + uno::Reference< lang::XMultiServiceFactory > xSMgr( + static_cast< lang::XMultiServiceFactory * >( + pServiceManager ) ); + uno::Reference< lang::XSingleServiceFactory > xFactory; + + + // Hierarchy Content Provider. + + + if ( HierarchyContentProvider::getImplementationName_Static(). + equalsAscii( pImplName ) ) + { + xFactory = HierarchyContentProvider::createServiceFactory( xSMgr ); + } + + + // Hierarchy Data Source. + + + else if ( HierarchyDataSource::getImplementationName_Static(). + equalsAscii( pImplName ) ) + { + xFactory = HierarchyDataSource::createServiceFactory( xSMgr ); + } + + + if ( xFactory.is() ) + { + xFactory->acquire(); + pRet = xFactory.get(); + } + + return pRet; +} + +/* 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 000000000..8cb26e0f7 --- /dev/null +++ b/ucb/source/ucp/hierarchy/hierarchyuri.cxx @@ -0,0 +1,179 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this 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; + +#define HIERARCHY_URL_SCHEME "vnd.sun.star.hier" +#define HIERARCHY_URL_SCHEME_LENGTH 17 +#define DEFAULT_DATA_SOURCE_SERVICE \ + "com.sun.star.ucb.DefaultHierarchyDataSource" + + +// HierarchyUri Implementation. + + +void HierarchyUri::init() const +{ + // Already inited? + if ( !m_aUri.isEmpty() && m_aPath.isEmpty() ) + { + // 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_LENGTH + 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_LENGTH ).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_LENGTH + 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_LENGTH + 2 ) + && + ( m_aUri[ HIERARCHY_URL_SCHEME_LENGTH + 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_LENGTH + 2 ) + && + ( m_aUri[ HIERARCHY_URL_SCHEME_LENGTH + 2 ] != '/' ) ) + { + // other (no root folder) URI without service specifier. + m_aUri = m_aUri.replaceAt( + HIERARCHY_URL_SCHEME_LENGTH + 2, + 0, + "/" DEFAULT_DATA_SOURCE_SERVICE "/" ); + m_aService = DEFAULT_DATA_SOURCE_SERVICE; + + nPos + = HIERARCHY_URL_SCHEME_LENGTH + 3 + m_aService.getLength(); + } + else + { + // URI with service specifier. + sal_Int32 nStart = HIERARCHY_URL_SCHEME_LENGTH + 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 000000000..bf282d508 --- /dev/null +++ b/ucb/source/ucp/hierarchy/hierarchyuri.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/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#ifndef INCLUDED_UCB_SOURCE_UCP_HIERARCHY_HIERARCHYURI_HXX +#define INCLUDED_UCB_SOURCE_UCP_HIERARCHY_HIERARCHYURI_HXX + +#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( const OUString & rUri ) + : m_aUri( rUri ), 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 == "/"; +} +} + +#endif + +/* 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 000000000..6d880b46c --- /dev/null +++ b/ucb/source/ucp/hierarchy/ucphier1.component @@ -0,0 +1,29 @@ +<?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@" + prefix="ucphier1" xmlns="http://openoffice.org/2010/uno-components"> + <implementation name="com.sun.star.comp.ucb.HierarchyContentProvider"> + <service name="com.sun.star.ucb.HierarchyContentProvider"/> + </implementation> + <implementation name="com.sun.star.comp.ucb.HierarchyDataSource"> + <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 000000000..6dba0ddc2 --- /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 000000000..75bc7404f --- /dev/null +++ b/ucb/source/ucp/image/ucpimage.cxx @@ -0,0 +1,166 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.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 <cppuhelper/compbase.hxx> +#include <cppuhelper/supportsservice.hxx> +#include <osl/mutex.hxx> +#include <rtl/uri.hxx> +#include <rtl/ustrbuf.hxx> +#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: + private osl::Mutex, + public cppu::WeakComponentImplHelper< + css::lang::XServiceInfo, css::ucb::XContentProvider> +{ +public: + explicit Provider( + css::uno::Reference<css::uno::XComponentContext> const & context): + WeakComponentImplHelper(*static_cast<Mutex *>(this)), context_(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; + { + osl::MutexGuard g(*this); + 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 SAL_CALL disposing() 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 000000000..62a3a3963 --- /dev/null +++ b/ucb/source/ucp/inc/urihelper.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 . + */ + +#ifndef INCLUDED_UCB_SOURCE_UCP_INC_URIHELPER_HXX +#define INCLUDED_UCB_SOURCE_UCP_INC_URIHELPER_HXX + +#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'?' ); + aResult.append( aParams ); + } + + if ( !aFragment.isEmpty() ) + { + aResult.append( u'#' ); + aResult.append( aFragment ); + } + + return aResult.makeStringAndClear(); + } + +} // namespace + +#endif // INCLUDED_UCB_SOURCE_UCP_INC_URIHELPER_HXX + +/* 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 000000000..19f201771 --- /dev/null +++ b/ucb/source/ucp/package/pkgcontent.cxx @@ -0,0 +1,2687 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this 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 <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 "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 ) +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, 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 ) +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, aURI, Info ); +} + + +// static +OUString Content::getContentType( + const OUString& aScheme, bool bFolder ) +{ + return ( "application/" + + aScheme + + ( bFolder + ? OUStringLiteral("-folder") + : OUStringLiteral("-stream") ) ); +} + + +Content::Content( + const uno::Reference< uno::XComponentContext >& rxContext, + ContentProvider* pProvider, + const uno::Reference< ucb::XContentIdentifier >& Identifier, + const uno::Reference< container::XHierarchicalNameAccess > & Package, + const PackageUri& rUri, + const ContentProperties& rProps ) +: ContentImplHelper( rxContext, pProvider, Identifier ), + m_aUri( rUri ), + m_aProps( rProps ), + m_eState( PERSISTENT ), + m_xPackage( Package ), + m_pProvider( pProvider ), + m_nModifiedProps( NONE_MODIFIED ) +{ +} + + +Content::Content( + const uno::Reference< uno::XComponentContext >& rxContext, + ContentProvider* pProvider, + const uno::Reference< ucb::XContentIdentifier >& Identifier, + const uno::Reference< container::XHierarchicalNameAccess > & Package, + const PackageUri& rUri, + const ucb::ContentInfo& Info ) + : ContentImplHelper( rxContext, pProvider, Identifier ), + m_aUri( rUri ), + m_aProps( Info.Type ), + m_eState( TRANSIENT ), + m_xPackage( Package ), + m_pProvider( pProvider ), + m_nModifiedProps( NONE_MODIFIED ) +{ +} + + +// virtual +Content::~Content() +{ +} + + +// XInterface methods. + + +// virtual +void SAL_CALL Content::acquire() + throw( ) +{ + ContentImplHelper::acquire(); +} + + +// virtual +void SAL_CALL Content::release() + throw( ) +{ + 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::makeAny( lang::IllegalArgumentException( + "Wrong argument type!", + static_cast< cppu::OWeakObject * >( this ), + -1 ) ), + Environment ); + // Unreachable + } + + aRet <<= getPropertyValues( Properties ); + } + else if ( aCommand.Name == "setPropertyValues" ) + { + + // setPropertyValues + + + uno::Sequence< beans::PropertyValue > aProperties; + if ( !( aCommand.Argument >>= aProperties ) ) + { + ucbhelper::cancelCommandExecution( + uno::makeAny( lang::IllegalArgumentException( + "Wrong argument type!", + static_cast< cppu::OWeakObject * >( this ), + -1 ) ), + Environment ); + // Unreachable + } + + if ( !aProperties.hasElements() ) + { + ucbhelper::cancelCommandExecution( + uno::makeAny( lang::IllegalArgumentException( + "No properties!", + static_cast< cppu::OWeakObject * >( this ), + -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::makeAny( lang::IllegalArgumentException( + "Wrong argument type!", + static_cast< cppu::OWeakObject * >( this ), + -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::makeAny( lang::IllegalArgumentException( + "Wrong argument type!", + static_cast< cppu::OWeakObject * >( this ), + -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::makeAny( lang::IllegalArgumentException( + "Wrong argument type!", + static_cast< cppu::OWeakObject * >( this ), + -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::makeAny( lang::IllegalArgumentException( + "Wrong argument type!", + static_cast< cppu::OWeakObject * >( this ), + -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::makeAny( ucb::UnsupportedCommandException( + OUString(), + static_cast< cppu::OWeakObject * >( this ) ) ), + 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 uno::Reference< sdbc::XRow >( xRow.get() ); + } +} + + +// 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::makeAny( + 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::makeAny( + 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 uno::Reference< sdbc::XRow >( xRow.get() ); +} + + +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, + rtl::Reference< + ::ucbhelper::ContentProviderImplHelper >( + m_xProvider.get() ), + 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() ); + uno::Sequence< beans::PropertyChangeEvent > aChanges( rValues.getLength() ); + sal_Int32 nChanged = 0; + + beans::PropertyChangeEvent aEvent; + aEvent.Source = static_cast< cppu::OWeakObject * >( this ); + 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! + aRet[ n ] <<= lang::IllegalAccessException( + "Property is read-only!", + static_cast< cppu::OWeakObject * >( this ) ); + } + else if ( rValue.Name == "IsDocument" ) + { + // Read-only property! + aRet[ n ] <<= lang::IllegalAccessException( + "Property is read-only!", + static_cast< cppu::OWeakObject * >( this ) ); + } + else if ( rValue.Name == "IsFolder" ) + { + // Read-only property! + aRet[ n ] <<= lang::IllegalAccessException( + "Property is read-only!", + static_cast< cppu::OWeakObject * >( this ) ); + } + else if ( rValue.Name == "CreatableContentsInfo" ) + { + // Read-only property! + aRet[ n ] <<= lang::IllegalAccessException( + "Property is read-only!", + static_cast< cppu::OWeakObject * >( this ) ); + } + else if ( rValue.Name == "Title" ) + { + if ( m_aUri.isRootFolder() ) + { + // Read-only property! + aRet[ n ] <<= lang::IllegalAccessException( + "Property is read-only!", + static_cast< cppu::OWeakObject * >( this ) ); + } + 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 + { + aRet[ n ] <<= + lang::IllegalArgumentException( + "Empty title not allowed!", + static_cast< cppu::OWeakObject * >( this ), + -1 ); + } + } + else + { + aRet[ n ] <<= + beans::IllegalTypeException( + "Property value has wrong type!", + static_cast< cppu::OWeakObject * >( this ) ); + } + } + } + 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 + { + aRet[ n ] <<= beans::IllegalTypeException( + "Property value has wrong type!", + static_cast< cppu::OWeakObject * >( this ) ); + } + } + else if ( rValue.Name == "Size" ) + { + // Read-only property! + aRet[ n ] <<= lang::IllegalAccessException( + "Property is read-only!", + static_cast< cppu::OWeakObject * >( this ) ); + } + 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 + { + aRet[ n ] <<= beans::IllegalTypeException( + "Property value has wrong type!", + static_cast< cppu::OWeakObject * >( this ) ); + } + } + else + { + aRet[ n ] <<= beans::UnknownPropertyException( + "Compressed only supported by streams!", + static_cast< cppu::OWeakObject * >( this ) ); + } + } + 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 + { + aRet[ n ] <<= beans::IllegalTypeException( + "Property value has wrong type!", + static_cast< cppu::OWeakObject * >( this ) ); + } + } + else + { + aRet[ n ] <<= beans::UnknownPropertyException( + "Encrypted only supported by streams!", + static_cast< cppu::OWeakObject * >( this ) ); + } + } + else if ( rValue.Name == "HasEncryptedEntries" ) + { + // Read-only property! + aRet[ n ] <<= lang::IllegalAccessException( + "Property is read-only!", + static_cast< cppu::OWeakObject * >( this ) ); + } + 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 + { + aRet[ n ] <<= beans::IllegalTypeException( + "Property value has wrong type!", + static_cast< cppu::OWeakObject * >( this ) ); + } + } + else + { + aRet[ n ] <<= beans::UnknownPropertyException( + "EncryptionKey not supported by non-root folder!", + static_cast< cppu::OWeakObject * >( this ) ); + } + } + 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 ) + { + aRet[ n ] <<= e; + } + catch ( lang::WrappedTargetException const & e ) + { + aRet[ n ] <<= e; + } + catch ( beans::PropertyVetoException const & e ) + { + aRet[ n ] <<= e; + } + catch ( lang::IllegalArgumentException const & e ) + { + aRet[ n ] <<= e; + } + } + else + { + aRet[ n ] <<= uno::Exception( + "No property set for storing the value!", + static_cast< cppu::OWeakObject * >( this ) ); + } + } + } + + if ( bExchange ) + { + uno::Reference< ucb::XContentIdentifier > xOldId = m_xIdentifier; + + // Assemble new content identifier... + OUString aNewURL = m_aUri.getParentUri() + "/"; + aNewURL += ::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 . + aRet[ nTitlePos ] <<= uno::Exception( + "Exchange failed!", + static_cast< cppu::OWeakObject * >( this ) ); + } + } + + 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::makeAny( 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::makeAny( ucb::UnsupportedOpenModeException( + OUString(), + static_cast< cppu::OWeakObject * >( this ), + 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::makeAny( + ucb::UnsupportedDataSinkException( + OUString(), + static_cast< cppu::OWeakObject * >( this ), + 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::makeAny( ucb::MissingInputStreamException( + OUString(), + static_cast< cppu::OWeakObject * >( this ) ) ), + 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::makeAny( ucb::NameClashException( + OUString(), + static_cast< cppu::OWeakObject * >( this ), + 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() + "_"; + aNew += OUString::number( ++nTry ); + aNewUri.setUri( aNew ); + } + while ( hasData( aNewUri ) && ( nTry < 1000 ) ); + + if ( nTry == 1000 ) + { + ucbhelper::cancelCommandExecution( + uno::makeAny( + ucb::UnsupportedNameClashException( + "Unable to resolve name clash!", + static_cast< cppu::OWeakObject * >( this ), + 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::makeAny( + ucb::UnsupportedNameClashException( + OUString(), + static_cast< cppu::OWeakObject * >( this ), + 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::makeAny( ucb::UnsupportedCommandException( + "Not persistent!", + static_cast< cppu::OWeakObject * >( this ) ) ), + 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::makeAny( ucb::UnsupportedCommandException( + "Not persistent!", + static_cast< cppu::OWeakObject * >( this ) ) ), + 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::makeAny( ucb::InteractiveBadTransferURLException( + OUString(), + static_cast< cppu::OWeakObject * >( this ) ) ), + 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 ) + { + 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 identitity. + + // 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::makeAny( 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( 1 ); + aArgs[ 0 ] <<= 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::makeAny( 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::makeAny( m_aProps.aMediaType ) ); + m_nModifiedProps &= ~MEDIATYPE_MODIFIED; + } + + if ( m_nModifiedProps & COMPRESSED_MODIFIED ) + { + if ( !isFolder() ) + xPropSet->setPropertyValue( + "Compressed", + uno::makeAny( m_aProps.bCompressed ) ); + + m_nModifiedProps &= ~COMPRESSED_MODIFIED; + } + + if ( m_nModifiedProps & ENCRYPTED_MODIFIED ) + { + if ( !isFolder() ) + xPropSet->setPropertyValue( + "Encrypted", + uno::makeAny( m_aProps.bEncrypted ) ); + + m_nModifiedProps &= ~ENCRYPTED_MODIFIED; + } + + if ( m_nModifiedProps & ENCRYPTIONKEY_MODIFIED ) + { + if ( !isFolder() ) + xPropSet->setPropertyValue( + "EncryptionKey", + uno::makeAny( 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 000000000..c17973775 --- /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 . + */ + +#ifndef INCLUDED_UCB_SOURCE_UCP_PACKAGE_PKGCONTENT_HXX +#define INCLUDED_UCB_SOURCE_UCP_PACKAGE_PKGCONTENT_HXX + +#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, + const css::uno::Reference< css::container::XHierarchicalNameAccess >& Package, + const PackageUri& rUri, + const ContentProperties& rProps ); + Content( const css::uno::Reference< css::uno::XComponentContext >& rxContext, + ContentProvider* pProvider, + const css::uno::Reference< css::ucb::XContentIdentifier >& Identifier, + const css::uno::Reference< css::container::XHierarchicalNameAccess >& Package, + const PackageUri& rUri, + 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 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 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() + throw() override; + virtual void SAL_CALL release() + throw() 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( const OUString& aScheme, bool bFolder ); +}; + +} + +#endif + +/* 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 000000000..c01201d10 --- /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 000000000..90e1b87a7 --- /dev/null +++ b/ucb/source/ucp/package/pkgdatasupplier.cxx @@ -0,0 +1,455 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this 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 <vector> +#include <osl/diagnose.h> +#include <com/sun/star/container/XEnumeration.hpp> +#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 "pkgdatasupplier.hxx" +#include "pkgcontent.hxx" +#include "pkgprovider.hxx" + +#include "../inc/urihelper.hxx" + +using namespace com::sun::star; +using namespace package_ucp; + +namespace package_ucp +{ + + +// struct ResultListEntry. + +namespace { + +struct ResultListEntry +{ + OUString aURL; + uno::Reference< ucb::XContentIdentifier > xId; + uno::Reference< ucb::XContent > xContent; + uno::Reference< sdbc::XRow > xRow; + + explicit ResultListEntry( const OUString& rURL ) : aURL( rURL ) {} +}; + +} + +// struct DataSupplier_Impl. + + +struct DataSupplier_Impl +{ + osl::Mutex m_aMutex; + std::vector< ResultListEntry > m_aResults; + rtl::Reference< Content > m_xContent; + uno::Reference< uno::XComponentContext > m_xContext; + uno::Reference< container::XEnumeration > m_xFolderEnum; + bool m_bCountFinal; + bool m_bThrowException; + + DataSupplier_Impl( + const uno::Reference< uno::XComponentContext >& rxContext, + const rtl::Reference< Content >& rContent ) + : m_xContent( rContent ), m_xContext( rxContext ), + m_xFolderEnum( rContent->getIterator() ), + m_bCountFinal( !m_xFolderEnum.is() ), m_bThrowException( m_bCountFinal ) + {} +}; + + +} + + +// DataSupplier Implementation. + + +DataSupplier::DataSupplier( + const uno::Reference< uno::XComponentContext >& rxContext, + const rtl::Reference< Content >& rContent ) +: m_pImpl( new DataSupplier_Impl( rxContext, rContent ) ) +{ +} + + +// virtual +DataSupplier::~DataSupplier() +{ +} + + +// virtual +OUString DataSupplier::queryContentIdentifierString( sal_uInt32 nIndex ) +{ + osl::Guard< osl::Mutex > aGuard( m_pImpl->m_aMutex ); + + if ( nIndex < m_pImpl->m_aResults.size() ) + { + OUString aId = m_pImpl->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_pImpl->m_aResults[ nIndex ].aURL; + } + return OUString(); +} + + +// virtual +uno::Reference< ucb::XContentIdentifier > +DataSupplier::queryContentIdentifier( sal_uInt32 nIndex ) +{ + osl::Guard< osl::Mutex > aGuard( m_pImpl->m_aMutex ); + + if ( nIndex < m_pImpl->m_aResults.size() ) + { + uno::Reference< ucb::XContentIdentifier >& xId + = m_pImpl->m_aResults[ nIndex ].xId; + if ( xId.is() ) + { + // Already cached. + return xId; + } + } + + OUString aId = queryContentIdentifierString( nIndex ); + if ( !aId.isEmpty() ) + { + uno::Reference< ucb::XContentIdentifier > xId + = new ::ucbhelper::ContentIdentifier( aId ); + m_pImpl->m_aResults[ 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_pImpl->m_aMutex ); + + if ( nIndex < m_pImpl->m_aResults.size() ) + { + uno::Reference< ucb::XContent >& xContent + = m_pImpl->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_pImpl->m_xContent->getProvider()->queryContent( xId ); + m_pImpl->m_aResults[ nIndex ].xContent = xContent; + return xContent; + + } + catch ( ucb::IllegalIdentifierException const & ) + { + } + } + return uno::Reference< ucb::XContent >(); +} + + +// virtual +bool DataSupplier::getResult( sal_uInt32 nIndex ) +{ + osl::ClearableGuard< osl::Mutex > aGuard( m_pImpl->m_aMutex ); + + if ( m_pImpl->m_aResults.size() > nIndex ) + { + // Result already present. + return true; + } + + // Result not (yet) present. + + if ( m_pImpl->m_bCountFinal ) + return false; + + // Try to obtain result... + + sal_uInt32 nOldCount = m_pImpl->m_aResults.size(); + bool bFound = false; + sal_uInt32 nPos = nOldCount; + + while ( m_pImpl->m_xFolderEnum->hasMoreElements() ) + { + try + { + uno::Reference< container::XNamed > xNamed; + m_pImpl->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_pImpl->m_aResults.push_back( ResultListEntry( aURL ) ); + + if ( nPos == nIndex ) + { + // Result obtained. + bFound = true; + break; + } + + nPos++; + } + catch ( container::NoSuchElementException const & ) + { + m_pImpl->m_bThrowException = true; + break; + } + catch ( lang::WrappedTargetException const & ) + { + m_pImpl->m_bThrowException = true; + break; + } + } + + if ( !bFound ) + m_pImpl->m_bCountFinal = true; + + rtl::Reference< ::ucbhelper::ResultSet > xResultSet = getResultSet().get(); + if ( xResultSet.is() ) + { + // Callbacks follow! + aGuard.clear(); + + if ( nOldCount < m_pImpl->m_aResults.size() ) + xResultSet->rowCountChanged( + nOldCount, m_pImpl->m_aResults.size() ); + + if ( m_pImpl->m_bCountFinal ) + xResultSet->rowCountFinal(); + } + + return bFound; +} + + +// virtual +sal_uInt32 DataSupplier::totalCount() +{ + osl::ClearableGuard< osl::Mutex > aGuard( m_pImpl->m_aMutex ); + + if ( m_pImpl->m_bCountFinal ) + return m_pImpl->m_aResults.size(); + + sal_uInt32 nOldCount = m_pImpl->m_aResults.size(); + + while ( m_pImpl->m_xFolderEnum->hasMoreElements() ) + { + try + { + uno::Reference< container::XNamed > xNamed; + m_pImpl->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_pImpl->m_aResults.push_back( ResultListEntry( aURL ) ); + } + catch ( container::NoSuchElementException const & ) + { + m_pImpl->m_bThrowException = true; + break; + } + catch ( lang::WrappedTargetException const & ) + { + m_pImpl->m_bThrowException = true; + break; + } + } + + m_pImpl->m_bCountFinal = true; + + rtl::Reference< ::ucbhelper::ResultSet > xResultSet = getResultSet().get(); + if ( xResultSet.is() ) + { + // Callbacks follow! + aGuard.clear(); + + if ( nOldCount < m_pImpl->m_aResults.size() ) + xResultSet->rowCountChanged( + nOldCount, m_pImpl->m_aResults.size() ); + + xResultSet->rowCountFinal(); + } + + return m_pImpl->m_aResults.size(); +} + + +// virtual +sal_uInt32 DataSupplier::currentCount() +{ + return m_pImpl->m_aResults.size(); +} + + +// virtual +bool DataSupplier::isCountFinal() +{ + return m_pImpl->m_bCountFinal; +} + + +// virtual +uno::Reference< sdbc::XRow > DataSupplier::queryPropertyValues( + sal_uInt32 nIndex ) +{ + osl::Guard< osl::Mutex > aGuard( m_pImpl->m_aMutex ); + + if ( nIndex < m_pImpl->m_aResults.size() ) + { + uno::Reference< sdbc::XRow >& xRow = m_pImpl->m_aResults[ nIndex ].xRow; + if ( xRow.is() ) + { + // Already cached. + return xRow; + } + } + + if ( getResult( nIndex ) ) + { + uno::Reference< sdbc::XRow > xRow = Content::getPropertyValues( + m_pImpl->m_xContext, + getResultSet()->getProperties(), + static_cast< ContentProvider * >( + m_pImpl->m_xContent->getProvider().get() ), + queryContentIdentifierString( nIndex ) ); + m_pImpl->m_aResults[ nIndex ].xRow = xRow; + return xRow; + } + + return uno::Reference< sdbc::XRow >(); +} + + +// virtual +void DataSupplier::releasePropertyValues( sal_uInt32 nIndex ) +{ + osl::Guard< osl::Mutex > aGuard( m_pImpl->m_aMutex ); + + if ( nIndex < m_pImpl->m_aResults.size() ) + m_pImpl->m_aResults[ nIndex ].xRow.clear(); +} + + +// virtual +void DataSupplier::close() +{ +} + + +// virtual +void DataSupplier::validate() +{ + if ( m_pImpl->m_bThrowException ) + throw ucb::ResultSetException(); +} + + +OUString DataSupplier::assembleChildURL( const OUString& aName ) +{ + OUString aURL; + OUString aContURL + = m_pImpl->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.copy( 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 000000000..678fd2940 --- /dev/null +++ b/ucb/source/ucp/package/pkgdatasupplier.hxx @@ -0,0 +1,68 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#ifndef INCLUDED_UCB_SOURCE_UCP_PACKAGE_PKGDATASUPPLIER_HXX +#define INCLUDED_UCB_SOURCE_UCP_PACKAGE_PKGDATASUPPLIER_HXX + +#include <rtl/ref.hxx> +#include <ucbhelper/resultset.hxx> +#include <memory> + +namespace package_ucp { + +struct DataSupplier_Impl; +class Content; + +class DataSupplier : public ::ucbhelper::ResultSetDataSupplier +{ + std::unique_ptr<DataSupplier_Impl> m_pImpl; + +public: + DataSupplier( const css::uno::Reference< css::uno::XComponentContext >& rxContext, + 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 ); +}; + +} + +#endif + +/* 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 000000000..ee3db6ce0 --- /dev/null +++ b/ucb/source/ucp/package/pkgprovider.cxx @@ -0,0 +1,270 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this 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/getcomponentcontext.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> + +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( const OUString& rName, + const uno::Reference< container::XHierarchicalNameAccess > & xNA, + ContentProvider* pOwner ) + : m_aName( rName ), m_xNA( 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() throw() override + { OWeakObject::acquire(); } + virtual void SAL_CALL + release() throw() 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() + throw() +{ + OWeakObject::acquire(); +} + +void SAL_CALL ContentProvider::release() + throw() +{ + 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. + +XSERVICEINFO_COMMOM_IMPL( ContentProvider, + "com.sun.star.comp.ucb.PackageContentProvider" ) +/// @throws css::uno::Exception +static css::uno::Reference< css::uno::XInterface > +ContentProvider_CreateInstance( const css::uno::Reference< css::lang::XMultiServiceFactory> & rSMgr ) +{ + css::lang::XServiceInfo* pX = new ContentProvider( ucbhelper::getComponentContext(rSMgr) ); + return css::uno::Reference< css::uno::XInterface >::query( pX ); +} + +css::uno::Sequence< OUString > +ContentProvider::getSupportedServiceNames_Static() +{ + css::uno::Sequence< OUString > aSNS { "com.sun.star.ucb.PackageContentProvider" }; + return aSNS; +} + +// Service factory implementation. + + +ONE_INSTANCE_SERVICE_FACTORY_IMPL( ContentProvider ); + + +// 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 ).get(); + 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( 1 ); + aArguments[ 0 ] <<= 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.get(); +} + + +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; + } + } +} + +/* 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 000000000..327000b89 --- /dev/null +++ b/ucb/source/ucp/package/pkgprovider.hxx @@ -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/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#ifndef INCLUDED_UCB_SOURCE_UCP_PACKAGE_PKGPROVIDER_HXX +#define INCLUDED_UCB_SOURCE_UCP_PACKAGE_PKGPROVIDER_HXX + +#include <memory> +#include <ucbhelper/providerhelper.hxx> +#include <com/sun/star/lang/XSingleServiceFactory.hpp> +#include <com/sun/star/lang/XMultiServiceFactory.hpp> +#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() + throw() override; + virtual void SAL_CALL release() + throw() 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; + + static OUString getImplementationName_Static(); + static css::uno::Sequence< OUString > getSupportedServiceNames_Static(); + + static css::uno::Reference< css::lang::XSingleServiceFactory > + createServiceFactory( const css::uno::Reference< + css::lang::XMultiServiceFactory >& rxServiceMgr ); + + // 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 ); +}; + +} + +#endif + +/* 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 000000000..3d0052725 --- /dev/null +++ b/ucb/source/ucp/package/pkgresultset.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 . + */ + + +/************************************************************************** + TODO + ************************************************************************** + + - This implementation is not a dynamic result set!!! It only implements + the necessary interfaces, but never recognizes/notifies changes!!! + + *************************************************************************/ +#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, + 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_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 000000000..60a443ff9 --- /dev/null +++ b/ucb/source/ucp/package/pkgresultset.hxx @@ -0,0 +1,50 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#ifndef INCLUDED_UCB_SOURCE_UCP_PACKAGE_PKGRESULTSET_HXX +#define INCLUDED_UCB_SOURCE_UCP_PACKAGE_PKGRESULTSET_HXX + +#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, + const rtl::Reference< Content >& rxContent, + const css::ucb::OpenCommandArgument2& rCommand, + const css::uno::Reference< css::ucb::XCommandEnvironment >& rxEnv ); +}; + +} + +#endif + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/ucb/source/ucp/package/pkgservices.cxx b/ucb/source/ucp/package/pkgservices.cxx new file mode 100644 index 000000000..c6409e2ca --- /dev/null +++ b/ucb/source/ucp/package/pkgservices.cxx @@ -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/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this 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/XMultiServiceFactory.hpp> +#include <com/sun/star/lang/XSingleServiceFactory.hpp> +#include "pkgprovider.hxx" + +using namespace com::sun::star; + + +extern "C" SAL_DLLPUBLIC_EXPORT void * ucppkg1_component_getFactory( + const char * pImplName, void * pServiceManager, void * /*pRegistryKey*/ ) +{ + void * pRet = nullptr; + + uno::Reference< lang::XMultiServiceFactory > xSMgr( + static_cast< lang::XMultiServiceFactory * >( pServiceManager ) ); + uno::Reference< lang::XSingleServiceFactory > xFactory; + + + // Create factory, if implementation name matches. + + + if ( ::package_ucp::ContentProvider::getImplementationName_Static(). + equalsAscii( pImplName ) ) + { + xFactory = ::package_ucp::ContentProvider::createServiceFactory( xSMgr ); + } + + + if ( xFactory.is() ) + { + xFactory->acquire(); + pRet = xFactory.get(); + } + + return pRet; +} + +/* 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 000000000..d1f530d46 --- /dev/null +++ b/ucb/source/ucp/package/pkguri.cxx @@ -0,0 +1,230 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this 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/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() + ? OUStringLiteral( "&purezip" ) + : OUStringLiteral( "?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, ".." ) + || ::comphelper::OStorageHelper::PathHasSegment( m_aPath, "." ) ) + { + // 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 000000000..90101fdb8 --- /dev/null +++ b/ucb/source/ucp/package/pkguri.hxx @@ -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 . + */ + +#ifndef INCLUDED_UCB_SOURCE_UCP_PACKAGE_PKGURI_HXX +#define INCLUDED_UCB_SOURCE_UCP_PACKAGE_PKGURI_HXX + +#include <rtl/ustring.hxx> + +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( const OUString & rPackageUri ) + : m_aUri( rPackageUri ), 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 == "/"; +} + +} + +#endif + +/* 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 000000000..4210c6d66 --- /dev/null +++ b/ucb/source/ucp/package/ucppkg1.component @@ -0,0 +1,25 @@ +<?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@" + prefix="ucppkg1" xmlns="http://openoffice.org/2010/uno-components"> + <implementation name="com.sun.star.comp.ucb.PackageContentProvider"> + <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 000000000..fa7aa636d --- /dev/null +++ b/ucb/source/ucp/tdoc/tdoc_content.cxx @@ -0,0 +1,2829 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this 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/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 "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( const OUString & 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 ) +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, aProps ); +} + + +// static ( "virtual" ctor ) +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, + const ContentProperties & rProps ) +: ContentImplHelper( rxContext, pProvider, Identifier ), + m_aProps( rProps ), + 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() + throw( ) +{ + ContentImplHelper::acquire(); +} + + +// virtual +void SAL_CALL Content::release() + throw( ) +{ + 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::makeAny( lang::IllegalArgumentException( + "Wrong argument type!", + static_cast< cppu::OWeakObject * >( this ), + -1 ) ), + Environment ); + // Unreachable + } + + aRet <<= getPropertyValues( Properties ); + } + else if ( aCommand.Name == "setPropertyValues" ) + { + + // setPropertyValues + + + uno::Sequence< beans::PropertyValue > aProperties; + if ( !( aCommand.Argument >>= aProperties ) ) + { + ucbhelper::cancelCommandExecution( + uno::makeAny( lang::IllegalArgumentException( + "Wrong argument type!", + static_cast< cppu::OWeakObject * >( this ), + -1 ) ), + Environment ); + // Unreachable + } + + if ( !aProperties.hasElements() ) + { + ucbhelper::cancelCommandExecution( + uno::makeAny( lang::IllegalArgumentException( + "No properties!", + static_cast< cppu::OWeakObject * >( this ), + -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::makeAny( lang::IllegalArgumentException( + "Wrong argument type!", + static_cast< cppu::OWeakObject * >( this ), + -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::makeAny( ucb::UnsupportedCommandException( + "insert command only supported by " + "folders and streams!", + static_cast< cppu::OWeakObject * >( this ) ) ), + Environment ); + // Unreachable + } + + if ( eType == STREAM ) + { + Uri aUri( m_xIdentifier->getContentIdentifier() ); + Uri aParentUri( aUri.getParentUri() ); + if ( aParentUri.isDocument() ) + { + ucbhelper::cancelCommandExecution( + uno::makeAny( ucb::UnsupportedCommandException( + "insert command not supported by " + "streams that are direct children " + "of document root!", + static_cast< cppu::OWeakObject * >( + this ) ) ), + Environment ); + // Unreachable + } + } + + ucb::InsertCommandArgument aArg; + if ( !( aCommand.Argument >>= aArg ) ) + { + ucbhelper::cancelCommandExecution( + uno::makeAny( lang::IllegalArgumentException( + "Wrong argument type!", + static_cast< cppu::OWeakObject * >( this ), + -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::makeAny( ucb::UnsupportedCommandException( + "delete command only supported by " + "folders and streams!", + static_cast< cppu::OWeakObject * >( + this ) ) ), + 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::makeAny( ucb::UnsupportedCommandException( + "transfer command only supported " + "by folders and documents!", + static_cast< cppu::OWeakObject * >( + this ) ) ), + Environment ); + // Unreachable + } + } + + ucb::TransferInfo aInfo; + if ( !( aCommand.Argument >>= aInfo ) ) + { + OSL_FAIL( "Wrong argument type!" ); + ucbhelper::cancelCommandExecution( + uno::makeAny( lang::IllegalArgumentException( + "Wrong argument type!", + static_cast< cppu::OWeakObject * >( this ), + -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::makeAny( ucb::UnsupportedCommandException( + "createNewContent command only " + "supported by folders and " + "documents!", + static_cast< cppu::OWeakObject * >( + this ) ) ), + Environment ); + // Unreachable + } + } + + ucb::ContentInfo aInfo; + if ( !( aCommand.Argument >>= aInfo ) ) + { + OSL_FAIL( "Wrong argument type!" ); + ucbhelper::cancelCommandExecution( + uno::makeAny( lang::IllegalArgumentException( + "Wrong argument type!", + static_cast< cppu::OWeakObject * >( this ), + -1 ) ), + Environment ); + // Unreachable + } + + aRet <<= createNewContent( aInfo ); + } + else + { + + // Unsupported command + + + ucbhelper::cancelCommandExecution( + uno::makeAny( ucb::UnsupportedCommandException( + OUString(), + static_cast< cppu::OWeakObject * >( this ) ) ), + 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() ); + OUStringBuffer aNewURL = aUri.getParentUri(); + aNewURL.append( ::ucb_impl::urihelper::encodeSegment( rTitle ) ); + + return + uno::Reference< ucb::XContentIdentifier >( + new ::ucbhelper::ContentIdentifier( aNewURL.makeStringAndClear() ) ); +} + + +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 identitity. + + // 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 uno::Reference< sdbc::XRow >( xRow.get() ); + } +} + + +// 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::makeAny( rData.getCreatableContentsInfo() ) ); + } + else if ( rProp.Name == "Storage" ) + { + // Storage is only supported by folders. + ContentType eType = rData.getType(); + if ( eType == FOLDER ) + xRow->appendObject( + rProp, + uno::makeAny( + 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::makeAny( + 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::makeAny( rData.getCreatableContentsInfo() ) ); + + // 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::makeAny( 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::makeAny( + pProvider->queryDocumentModel( rContentId ) ) ); + + // Append all Additional Core Properties. + + uno::Reference< beans::XPropertySet > xSet = + pProvider->getAdditionalPropertySet( rContentId, false ); + xRow->appendPropertySet( xSet ); + } + + return uno::Reference< sdbc::XRow >( xRow.get() ); +} + + +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() ); + uno::Sequence< beans::PropertyChangeEvent > aChanges( rValues.getLength() ); + sal_Int32 nChanged = 0; + + beans::PropertyChangeEvent aEvent; + aEvent.Source = static_cast< cppu::OWeakObject * >( this ); + 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! + aRet[ n ] <<= lang::IllegalAccessException( + "Property is read-only!", + static_cast< cppu::OWeakObject * >( this ) ); + } + else if ( rValue.Name == "IsDocument" ) + { + // Read-only property! + aRet[ n ] <<= lang::IllegalAccessException( + "Property is read-only!", + static_cast< cppu::OWeakObject * >( this ) ); + } + else if ( rValue.Name == "IsFolder" ) + { + // Read-only property! + aRet[ n ] <<= lang::IllegalAccessException( + "Property is read-only!", + static_cast< cppu::OWeakObject * >( this ) ); + } + else if ( rValue.Name == "CreatableContentsInfo" ) + { + // Read-only property! + aRet[ n ] <<= lang::IllegalAccessException( + "Property is read-only!", + static_cast< cppu::OWeakObject * >( this ) ); + } + else if ( rValue.Name == "Title" ) + { + // Title is read-only for root and documents. + ContentType eType = m_aProps.getType(); + if ( ( eType == ROOT ) || ( eType == DOCUMENT ) ) + { + aRet[ n ] <<= lang::IllegalAccessException( + "Property is read-only!", + static_cast< cppu::OWeakObject * >( this ) ); + } + 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 + { + aRet[ n ] <<= lang::IllegalArgumentException( + "Empty Title not allowed!", + static_cast< cppu::OWeakObject * >( this ), + -1 ); + } + } + else + { + aRet[ n ] <<= beans::IllegalTypeException( + "Title Property value has wrong type!", + static_cast< cppu::OWeakObject * >( this ) ); + } + } + } + else if ( rValue.Name == "Storage" ) + { + ContentType eType = m_aProps.getType(); + if ( eType == FOLDER ) + { + aRet[ n ] <<= lang::IllegalAccessException( + "Property is read-only!", + static_cast< cppu::OWeakObject * >( this ) ); + } + else + { + // Storage is only supported by folders. + aRet[ n ] <<= beans::UnknownPropertyException( + "Storage property only supported by folders", + static_cast< cppu::OWeakObject * >( this ) ); + } + } + else if ( rValue.Name == "DocumentModel" ) + { + ContentType eType = m_aProps.getType(); + if ( eType == DOCUMENT ) + { + aRet[ n ] <<= lang::IllegalAccessException( + "Property is read-only!", + static_cast< cppu::OWeakObject * >( this ) ); + } + else + { + // Storage is only supported by folders. + aRet[ n ] <<= beans::UnknownPropertyException( + "DocumentModel property only supported by documents", + static_cast< cppu::OWeakObject * >( this ) ); + } + } + 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 ) + { + aRet[ n ] <<= e; + } + catch ( lang::WrappedTargetException const & e ) + { + aRet[ n ] <<= e; + } + catch ( beans::PropertyVetoException const & e ) + { + aRet[ n ] <<= e; + } + catch ( lang::IllegalArgumentException const & e ) + { + aRet[ n ] <<= e; + } + } + else + { + aRet[ n ] <<= uno::Exception( + "No property set for storing the value!", + static_cast< cppu::OWeakObject * >( this ) ); + } + } + } + + 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 . + aRet[ nTitlePos ] <<= uno::Exception( + "Exchange failed!", + static_cast< cppu::OWeakObject * >( this ) ); + } + } + + 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::makeAny( 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::makeAny( ucb::UnsupportedOpenModeException( + OUString(), + static_cast< cppu::OWeakObject * >( this ), + 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::makeAny( + ucb::UnsupportedDataSinkException( + OUString(), + static_cast< cppu::OWeakObject * >( this ), + 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::makeAny( ucb::MissingInputStreamException( + OUString(), + static_cast< cppu::OWeakObject * >( this ) ) ), + xEnv ); + // Unreachable + } + + // Required: Title + + if ( m_aProps.getTitle().isEmpty() ) + m_aProps.setTitle( aUri.getDecodedName() ); + } + + OUStringBuffer aNewURL = aUri.getParentUri(); + aNewURL.append( m_aProps.getTitle() ); + Uri aNewUri( aNewURL.makeStringAndClear() ); + + // Handle possible name clash... + switch ( nNameClashResolve ) + { + // fail. + case ucb::NameClash::ERROR: + if ( hasData( aNewUri ) ) + { + ucbhelper::cancelCommandExecution( + uno::makeAny( ucb::NameClashException( + OUString(), + static_cast< cppu::OWeakObject * >( this ), + 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 + { + OUStringBuffer aNew = aNewUri.getUri(); + aNew.append( "_" ); + aNew.append( OUString::number( ++nTry ) ); + aNewUri.setUri( aNew.makeStringAndClear() ); + } + while ( hasData( aNewUri ) && ( nTry < 1000 ) ); + + if ( nTry == 1000 ) + { + ucbhelper::cancelCommandExecution( + uno::makeAny( + ucb::UnsupportedNameClashException( + "Unable to resolve name clash!", + static_cast< cppu::OWeakObject * >( this ), + nNameClashResolve ) ), + xEnv ); + // Unreachable + } + else + { + OUStringBuffer aNewTitle = m_aProps.getTitle(); + aNewTitle.append( "_" ); + aNewTitle.append( OUString::number( ++nTry ) ); + m_aProps.setTitle( aNewTitle.makeStringAndClear() ); + } + } + break; + + case ucb::NameClash::KEEP: // deprecated + case ucb::NameClash::ASK: + default: + if ( hasData( aNewUri ) ) + { + ucbhelper::cancelCommandExecution( + uno::makeAny( + ucb::UnsupportedNameClashException( + OUString(), + static_cast< cppu::OWeakObject * >( this ), + 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::makeAny( ucb::UnsupportedCommandException( + "Not persistent!", + static_cast< cppu::OWeakObject * >( this ) ) ), + 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( const OUString & rRelativeChildUri ) +{ + osl::Guard< osl::Mutex > aGuard( m_aMutex ); + + const OUString aMyId = getIdentifier()->getContentIdentifier(); + OUStringBuffer aBuf( aMyId ); + if ( !aMyId.endsWith("/") ) + aBuf.append( "/" ); + if ( !rRelativeChildUri.startsWith("/") ) + aBuf.append( rRelativeChildUri ); + else + aBuf.append( std::u16string_view(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( const OUString & 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() ) + { + // callback follows! + aGuard.clear(); + + // Notify "REMOVED" event. + ucb::ContentEvent aEvt( + static_cast< cppu::OWeakObject * >( this ), + ucb::ContentAction::REMOVED, + xChild, + getIdentifier() ); + notifyContentEvent( aEvt ); + } +} + + +void Content::notifyChildInserted( const OUString & 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() ) + { + // callback follows! + aGuard.clear(); + + // Notify "INSERTED" event. + ucb::ContentEvent aEvt( + static_cast< cppu::OWeakObject * >( this ), + 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::makeAny( ucb::UnsupportedCommandException( + "Not persistent!", + static_cast< cppu::OWeakObject * >( this ) ) ), + 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::makeAny( ucb::InteractiveBadTransferURLException( + OUString(), + static_cast< cppu::OWeakObject * >( this ) ) ), + xEnv ); + // Unreachable + } + + OUString aScheme + = rInfo.SourceURL.copy( 0, TDOC_URL_SCHEME_LENGTH + 2 ) + .toAsciiLowerCase(); + if ( aScheme != TDOC_URL_SCHEME ":/" ) + { + // Invalid scheme. + ucbhelper::cancelCommandExecution( + uno::makeAny( ucb::InteractiveBadTransferURLException( + OUString(), + static_cast< cppu::OWeakObject * >( this ) ) ), + xEnv ); + // Unreachable + } + + // Does source URI describe a tdoc folder or stream? + Uri aSourceUri( rInfo.SourceURL ); + if ( !aSourceUri.isValid() ) + { + ucbhelper::cancelCommandExecution( + uno::makeAny( lang::IllegalArgumentException( + "Invalid source URI! Syntax!", + static_cast< cppu::OWeakObject * >( this ), + -1 ) ), + xEnv ); + // Unreachable + } + + if ( aSourceUri.isRoot() || aSourceUri.isDocument() ) + { + ucbhelper::cancelCommandExecution( + uno::makeAny( lang::IllegalArgumentException( + "Invalid source URI! Must describe a folder or stream!", + static_cast< cppu::OWeakObject * >( this ), + -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::makeAny( lang::IllegalArgumentException( + "Invalid source URI! " + "Streams cannot be created as " + "children of document root!", + static_cast< cppu::OWeakObject * >( + this ), + -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::makeAny( lang::IllegalArgumentException( + "Invalid source URI! Unable to determine source type!", + static_cast< cppu::OWeakObject * >( this ), + -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 ) + { + 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() ) + { + 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 + } + + } // rInfo.MoveData +} + + +//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 + OSL_FAIL( "Caught IllegalArgumentException!" ); + return false; + } + catch ( embed::InvalidStorageException const & ) + { + // this storage is in invalid state for any reason + OSL_FAIL( "Caught InvalidStorageException!" ); + 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::makeAny( + OUString( // @@@ better mediatype + "application/binary" ) ) ); + } + catch ( beans::UnknownPropertyException const & ) + { + OSL_FAIL( "Property MediaType not supported!" ); + return false; + } + catch ( beans::PropertyVetoException const & ) + { + OSL_FAIL( "Caught PropertyVetoException!" ); + return false; + } + catch ( lang::IllegalArgumentException const & ) + { + OSL_FAIL( "Caught IllegalArgumentException!" ); + return false; + } + catch ( lang::WrappedTargetException const & ) + { + OSL_FAIL( "Caught WrappedTargetException!" ); + 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 + OSL_FAIL( "Caught NotConnectedException!" ); + closeOutputStream( xOut ); + return false; + } + catch ( io::BufferSizeExceededException const & ) + { + // readSomeBytes, writeBytes + OSL_FAIL( "Caught BufferSizeExceededException!" ); + closeOutputStream( xOut ); + return false; + } + catch ( io::IOException const & ) + { + // readSomeBytes, writeBytes + OSL_FAIL( "Caught IOException!" ); + 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 + OSL_FAIL( "Caught InvalidStorageException!" ); + return; + } + catch ( lang::IllegalArgumentException const & ) + { + // an illegal argument is provided + OSL_FAIL( "Caught IllegalArgumentException!" ); + return; + } + catch ( container::NoSuchElementException const & ) + { + // there is no element with old name in this storage + OSL_FAIL( "Caught NoSuchElementException!" ); + return; + } + catch ( container::ElementExistException const & ) + { + // an element with new name already exists in this storage + OSL_FAIL( "Caught ElementExistException!" ); + return; + } + catch ( io::IOException const & ) + { + // in case of io errors during renaming + OSL_FAIL( "Caught IOException!" ); + return; + } + catch ( embed::StorageWrappedTargetException const & ) + { + // wraps other exceptions + OSL_FAIL( "Caught StorageWrappedTargetException!" ); + 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 + OSL_FAIL( "Caught InvalidStorageException!" ); + return false; + } + catch ( lang::IllegalArgumentException const & ) + { + // an illegal argument is provided + OSL_FAIL( "Caught IllegalArgumentException!" ); + return false; + } + catch ( container::NoSuchElementException const & ) + { + // there is no element with this name in this storage + OSL_FAIL( "Caught NoSuchElementException!" ); + return false; + } + catch ( io::IOException const & ) + { + // in case of io errors during renaming + OSL_FAIL( "Caught IOException!" ); + return false; + } + catch ( embed::StorageWrappedTargetException const & ) + { + // wraps other exceptions + OSL_FAIL( "Caught StorageWrappedTargetException!" ); + 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 + OSL_FAIL( "Caught InvalidStorageException!" ); + return false; + } + catch ( lang::IllegalArgumentException const & ) + { + // an illegal argument is provided + OSL_FAIL( "Caught IllegalArgumentException!" ); + return false; + } + catch ( container::NoSuchElementException const & ) + { + // there is no element with this name in this storage + OSL_FAIL( "Caught NoSuchElementException!" ); + return false; + } + catch ( container::ElementExistException const & ) + { + // there is no element with this name in this storage + OSL_FAIL( "Caught ElementExistException!" ); + return false; + } + catch ( io::IOException const & ) + { + // in case of io errors during renaming + OSL_FAIL( "Caught IOException!" ); + return false; + } + catch ( embed::StorageWrappedTargetException const & ) + { + // wraps other exceptions + OSL_FAIL( "Caught StorageWrappedTargetException!" ); + 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 & ) + { + OSL_FAIL( "Caught IOException!" ); + return false; + } + catch ( lang::WrappedTargetException const & ) + { + OSL_FAIL( "Caught WrappedTargetException!" ); + 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 & ) + { + OSL_FAIL( "Caught NotConnectedException!" ); + } + catch ( io::BufferSizeExceededException const & ) + { + OSL_FAIL( "Caught BufferSizeExceededException!" ); + } + catch ( io::IOException const & ) + { + OSL_FAIL( "Caught IOException!" ); + } + } + 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.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() ) + { + 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 000000000..c8cf050f6 --- /dev/null +++ b/ucb/source/ucp/tdoc/tdoc_content.hxx @@ -0,0 +1,281 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#ifndef INCLUDED_UCB_SOURCE_UCP_TDOC_TDOC_CONTENT_HXX +#define INCLUDED_UCB_SOURCE_UCP_TDOC_TDOC_CONTENT_HXX + +#include <ucbhelper/contenthelper.hxx> +#include <com/sun/star/ucb/XContentCreator.hpp> +#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, const OUString & rTitle ) + : m_eType( rType ), + m_aContentType( rType == STREAM + ? OUString( TDOC_STREAM_CONTENT_TYPE ) + : rType == FOLDER + ? OUString( TDOC_FOLDER_CONTENT_TYPE ) + : rType == DOCUMENT + ? OUString( TDOC_DOCUMENT_CONTENT_TYPE ) + : OUString( TDOC_ROOT_CONTENT_TYPE ) ), + m_aTitle( rTitle ) + {} + + 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, + const ContentProperties & rProps ); + 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 ) { 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( const OUString & 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 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 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() + throw() override; + virtual void SAL_CALL release() + throw() 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( const OUString & rRelativeChildUri ); + void notifyChildInserted( const OUString & rRelativeChildUri ); + + rtl::Reference< ContentProvider > getContentProvider() const + { return rtl::Reference< ContentProvider >( m_pProvider ); } +}; + +} // namespace tdoc_ucp + +#endif // INCLUDED_UCB_SOURCE_UCP_TDOC_TDOC_CONTENT_HXX + +/* 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 000000000..8b19f9ec9 --- /dev/null +++ b/ucb/source/ucp/tdoc/tdoc_contentcaps.cxx @@ -0,0 +1,622 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this 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 + 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 <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 + ) + + // 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 000000000..33c4f11cb --- /dev/null +++ b/ucb/source/ucp/tdoc/tdoc_datasupplier.cxx @@ -0,0 +1,408 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this 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 <vector> + +#include <com/sun/star/ucb/IllegalIdentifierException.hpp> +#include <com/sun/star/ucb/ResultSetException.hpp> +#include <osl/diagnose.h> +#include <ucbhelper/contentidentifier.hxx> + +#include "tdoc_datasupplier.hxx" +#include "tdoc_content.hxx" + +using namespace com::sun::star; +using namespace tdoc_ucp; + +namespace tdoc_ucp +{ + + +// struct ResultListEntry. + +namespace { + +struct ResultListEntry +{ + OUString aURL; + uno::Reference< ucb::XContentIdentifier > xId; + uno::Reference< ucb::XContent > xContent; + uno::Reference< sdbc::XRow > xRow; + + explicit ResultListEntry( const OUString& rURL ) : aURL( rURL ) {} +}; + +} + +// struct DataSupplier_Impl. + + +struct DataSupplier_Impl +{ + osl::Mutex m_aMutex; + std::vector< ResultListEntry > m_aResults; + rtl::Reference< Content > m_xContent; + uno::Reference< uno::XComponentContext > m_xContext; + std::unique_ptr<uno::Sequence< OUString > > m_pNamesOfChildren; + bool m_bCountFinal; + bool m_bThrowException; + + DataSupplier_Impl( + const uno::Reference< uno::XComponentContext >& rxContext, + const rtl::Reference< Content >& rContent ) + : m_xContent( rContent ), m_xContext( rxContext ), + m_bCountFinal( false ), m_bThrowException( false ) + {} +}; + + +} + +// DataSupplier Implementation. +ResultSetDataSupplier::ResultSetDataSupplier( + const uno::Reference< uno::XComponentContext >& rxContext, + const rtl::Reference< Content >& rContent ) +: m_pImpl( new DataSupplier_Impl( rxContext, rContent ) ) +{ +} + +// virtual +ResultSetDataSupplier::~ResultSetDataSupplier() +{ +} + +// virtual +OUString +ResultSetDataSupplier::queryContentIdentifierString( sal_uInt32 nIndex ) +{ + osl::Guard< osl::Mutex > aGuard( m_pImpl->m_aMutex ); + + if ( nIndex < m_pImpl->m_aResults.size() ) + { + OUString aId = m_pImpl->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_pImpl->m_aResults[ nIndex ].aURL; + } + return OUString(); +} + +// virtual +uno::Reference< ucb::XContentIdentifier > +ResultSetDataSupplier::queryContentIdentifier( sal_uInt32 nIndex ) +{ + osl::Guard< osl::Mutex > aGuard( m_pImpl->m_aMutex ); + + if ( nIndex < m_pImpl->m_aResults.size() ) + { + uno::Reference< ucb::XContentIdentifier > xId + = m_pImpl->m_aResults[ nIndex ].xId; + if ( xId.is() ) + { + // Already cached. + return xId; + } + } + + OUString aId = queryContentIdentifierString( nIndex ); + if ( !aId.isEmpty() ) + { + uno::Reference< ucb::XContentIdentifier > xId + = new ::ucbhelper::ContentIdentifier( aId ); + m_pImpl->m_aResults[ nIndex ].xId = xId; + return xId; + } + return uno::Reference< ucb::XContentIdentifier >(); +} + +// virtual +uno::Reference< ucb::XContent > +ResultSetDataSupplier::queryContent( sal_uInt32 nIndex ) +{ + osl::Guard< osl::Mutex > aGuard( m_pImpl->m_aMutex ); + + if ( nIndex < m_pImpl->m_aResults.size() ) + { + uno::Reference< ucb::XContent > xContent + = m_pImpl->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_pImpl->m_xContent->getProvider()->queryContent( xId ); + m_pImpl->m_aResults[ nIndex ].xContent = xContent; + return xContent; + + } + catch ( ucb::IllegalIdentifierException const & ) + { + } + } + return uno::Reference< ucb::XContent >(); +} + +// virtual +bool ResultSetDataSupplier::getResult( sal_uInt32 nIndex ) +{ + osl::ClearableGuard< osl::Mutex > aGuard( m_pImpl->m_aMutex ); + + if ( m_pImpl->m_aResults.size() > nIndex ) + { + // Result already present. + return true; + } + + // Result not (yet) present. + + if ( m_pImpl->m_bCountFinal ) + return false; + + // Try to obtain result... + + sal_uInt32 nOldCount = m_pImpl->m_aResults.size(); + bool bFound = false; + + if ( queryNamesOfChildren() ) + { + for ( sal_uInt32 n = nOldCount; + n < sal::static_int_cast<sal_uInt32>( + m_pImpl->m_pNamesOfChildren->getLength()); + ++n ) + { + const OUString & rName + = m_pImpl->m_pNamesOfChildren->getConstArray()[ n ]; + + if ( rName.isEmpty() ) + { + OSL_FAIL( "ResultDataSupplier::getResult - Empty name!" ); + break; + } + + // Assemble URL for child. + OUString aURL = assembleChildURL( rName ); + + m_pImpl->m_aResults.emplace_back( aURL ); + + if ( n == nIndex ) + { + // Result obtained. + bFound = true; + break; + } + } + } + + if ( !bFound ) + m_pImpl->m_bCountFinal = true; + + rtl::Reference< ::ucbhelper::ResultSet > xResultSet = getResultSet().get(); + if ( xResultSet.is() ) + { + // Callbacks follow! + aGuard.clear(); + + if ( nOldCount < m_pImpl->m_aResults.size() ) + xResultSet->rowCountChanged( nOldCount, m_pImpl->m_aResults.size() ); + + if ( m_pImpl->m_bCountFinal ) + xResultSet->rowCountFinal(); + } + + return bFound; +} + +// virtual +sal_uInt32 ResultSetDataSupplier::totalCount() +{ + osl::ClearableGuard< osl::Mutex > aGuard( m_pImpl->m_aMutex ); + + if ( m_pImpl->m_bCountFinal ) + return m_pImpl->m_aResults.size(); + + sal_uInt32 nOldCount = m_pImpl->m_aResults.size(); + + if ( queryNamesOfChildren() ) + { + for ( sal_uInt32 n = nOldCount; + n < sal::static_int_cast<sal_uInt32>( + m_pImpl->m_pNamesOfChildren->getLength()); + ++n ) + { + const OUString & rName + = m_pImpl->m_pNamesOfChildren->getConstArray()[ n ]; + + if ( rName.isEmpty() ) + { + OSL_FAIL( "ResultDataSupplier::getResult - Empty name!" ); + break; + } + + // Assemble URL for child. + OUString aURL = assembleChildURL( rName ); + + m_pImpl->m_aResults.emplace_back( aURL ); + } + } + + m_pImpl->m_bCountFinal = true; + + rtl::Reference< ::ucbhelper::ResultSet > xResultSet = getResultSet().get(); + if ( xResultSet.is() ) + { + // Callbacks follow! + aGuard.clear(); + + if ( nOldCount < m_pImpl->m_aResults.size() ) + xResultSet->rowCountChanged( nOldCount, m_pImpl->m_aResults.size() ); + + xResultSet->rowCountFinal(); + } + + return m_pImpl->m_aResults.size(); +} + +// virtual +sal_uInt32 ResultSetDataSupplier::currentCount() +{ + return m_pImpl->m_aResults.size(); +} + +// virtual +bool ResultSetDataSupplier::isCountFinal() +{ + return m_pImpl->m_bCountFinal; +} + +// virtual +uno::Reference< sdbc::XRow > +ResultSetDataSupplier::queryPropertyValues( sal_uInt32 nIndex ) +{ + osl::Guard< osl::Mutex > aGuard( m_pImpl->m_aMutex ); + + if ( nIndex < m_pImpl->m_aResults.size() ) + { + uno::Reference< sdbc::XRow > xRow = m_pImpl->m_aResults[ nIndex ].xRow; + if ( xRow.is() ) + { + // Already cached. + return xRow; + } + } + + if ( getResult( nIndex ) ) + { + uno::Reference< sdbc::XRow > xRow = Content::getPropertyValues( + m_pImpl->m_xContext, + getResultSet()->getProperties(), + m_pImpl->m_xContent->getContentProvider().get(), + queryContentIdentifierString( nIndex ) ); + m_pImpl->m_aResults[ nIndex ].xRow = xRow; + return xRow; + } + + return uno::Reference< sdbc::XRow >(); +} + +// virtual +void ResultSetDataSupplier::releasePropertyValues( sal_uInt32 nIndex ) +{ + osl::Guard< osl::Mutex > aGuard( m_pImpl->m_aMutex ); + + if ( nIndex < m_pImpl->m_aResults.size() ) + m_pImpl->m_aResults[ nIndex ].xRow.clear(); +} + +// virtual +void ResultSetDataSupplier::close() +{ +} + +// virtual +void ResultSetDataSupplier::validate() +{ + if ( m_pImpl->m_bThrowException ) + throw ucb::ResultSetException(); +} + +bool ResultSetDataSupplier::queryNamesOfChildren() +{ + osl::Guard< osl::Mutex > aGuard( m_pImpl->m_aMutex ); + + if ( m_pImpl->m_pNamesOfChildren == nullptr ) + { + std::unique_ptr<uno::Sequence< OUString >> pNamesOfChildren( + new uno::Sequence< OUString >() ); + + if ( !m_pImpl->m_xContent->getContentProvider()->queryNamesOfChildren( + m_pImpl->m_xContent->getIdentifier()->getContentIdentifier(), + *pNamesOfChildren ) ) + { + OSL_FAIL( "Got no list of children!" ); + m_pImpl->m_bThrowException = true; + return false; + } + else + { + m_pImpl->m_pNamesOfChildren = std::move( pNamesOfChildren ); + } + } + return true; +} + +OUString +ResultSetDataSupplier::assembleChildURL( const OUString& aName ) +{ + OUString aContURL + = m_pImpl->m_xContent->getIdentifier()->getContentIdentifier(); + OUString aURL( aContURL ); + + 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 000000000..cf888c0d6 --- /dev/null +++ b/ucb/source/ucp/tdoc/tdoc_datasupplier.hxx @@ -0,0 +1,71 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#ifndef INCLUDED_UCB_SOURCE_UCP_TDOC_TDOC_DATASUPPLIER_HXX +#define INCLUDED_UCB_SOURCE_UCP_TDOC_TDOC_DATASUPPLIER_HXX + +#include <rtl/ref.hxx> +#include <ucbhelper/resultset.hxx> +#include <memory> + +namespace tdoc_ucp { + +struct DataSupplier_Impl; +class Content; + +class ResultSetDataSupplier : public ::ucbhelper::ResultSetDataSupplier +{ + std::unique_ptr<DataSupplier_Impl> m_pImpl; + +private: + bool queryNamesOfChildren(); + OUString assembleChildURL( const OUString& aName ); + +public: + ResultSetDataSupplier( + const css::uno::Reference< css::uno::XComponentContext >& rxContext, + const rtl::Reference< Content >& rContent ); + 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; +}; + +} // namespace tdoc_ucp + +#endif // INCLUDED_UCB_SOURCE_UCP_TDOC_TDOC_DATASUPPLIER_HXX + +/* 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 000000000..6713d5401 --- /dev/null +++ b/ucb/source/ucp/tdoc/tdoc_docmgr.cxx @@ -0,0 +1,659 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this 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/diagnose.h> +#include <rtl/ref.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 & ) + { + OSL_FAIL( "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); + + { + osl::MutexGuard 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 ) ); + + { + osl::MutexGuard 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.get()); + + // 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; + + { + osl::MutexGuard 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.get()); + } + } + } + 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!" ); + + osl::MutexGuard 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)); + + osl::MutexGuard 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)); + + osl::MutexGuard 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); + + { + osl::MutexGuard 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!" ); + + { + osl::MutexGuard 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.get()); + } + } + } + } + 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 ) +{ + osl::MutexGuard 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 ) +{ + osl::MutexGuard 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() +{ + osl::MutexGuard aGuard( m_aMtx ); + + return comphelper::mapKeysToSequence( m_aDocs ); +} + + +OUString +OfficeDocumentsManager::queryStorageTitle( const OUString & rDocId ) +{ + osl::MutexGuard aGuard( m_aMtx ); + + DocumentList::const_iterator it = m_aDocs.find( rDocId ); + if ( it == m_aDocs.end() ) + return OUString(); + + return (*it).second.aTitle; +} + + +bool OfficeDocumentsManager::isDocumentPreview( + const uno::Reference< frame::XModel > & xModel ) +{ + if ( !xModel.is() ) + return false; + + ::comphelper::NamedValueCollection aArgs( + xModel->getArgs() ); + bool bIsPreview = aArgs.getOrDefault( "Preview", false ); + return bIsPreview; +} + + +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() ) + { + osl::MutexGuard 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 & ) + { + OSL_FAIL( "Caught IllegalArgumentException!" ); + } + catch ( frame::UnknownModuleException const & ) + { + OSL_FAIL( "Caught UnknownModuleException!" ); + } + + 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; + + if ( !isWithoutOrInTopLevelFrame( xModel ) ) + return false; + + if ( isDocumentPreview( xModel ) ) + 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 000000000..1e03247fe --- /dev/null +++ b/ucb/source/ucp/tdoc/tdoc_docmgr.hxx @@ -0,0 +1,154 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#ifndef INCLUDED_UCB_SOURCE_UCP_TDOC_TDOC_DOCMGR_HXX +#define INCLUDED_UCB_SOURCE_UCP_TDOC_TDOC_DOCMGR_HXX + +#include <map> + +#include <rtl/ref.hxx> +#include <osl/mutex.hxx> + +#include <cppuhelper/implbase.hxx> + +#include <com/sun/star/document/XDocumentEventListener.hpp> +#include <com/sun/star/embed/XStorage.hpp> +#include <com/sun/star/frame/XModel.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/XCloseListener.hpp> + +namespace tdoc_ucp { + + class ContentProvider; + + struct StorageInfo + { + OUString aTitle; + css::uno::Reference< css::embed::XStorage > xStorage; + css::uno::Reference< css::frame::XModel > xModel; + + StorageInfo() {}; // needed for STL map only. + + StorageInfo( + const OUString & rTitle, + const css::uno::Reference< css::embed::XStorage > & rxStorage, + const css::uno::Reference< css::frame::XModel > & rxModel ) + : aTitle( rTitle ), xStorage( rxStorage ), xModel( rxModel ) {} + }; + + + 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 ); + + private: + void buildDocumentsList(); + + bool isOfficeDocument( + const css::uno::Reference< css::uno::XInterface > & xDoc ); + + static bool isDocumentPreview( + const css::uno::Reference< css::frame::XModel > & 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 ); + + osl::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 + +#endif // INCLUDED_UCB_SOURCE_UCP_TDOC_TDOC_DOCMGR_HXX + +/* 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 000000000..eadd8fcd6 --- /dev/null +++ b/ucb/source/ucp/tdoc/tdoc_documentcontentfactory.cxx @@ -0,0 +1,144 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this 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/factory.hxx> +#include <cppuhelper/supportsservice.hxx> + +#include "tdoc_documentcontentfactory.hxx" + +using namespace com::sun::star; +using namespace tdoc_ucp; + + +// DocumentContentFactory Implementation. + + +DocumentContentFactory::DocumentContentFactory( + const uno::Reference< lang::XMultiServiceFactory >& xSMgr ) +: m_xSMgr( xSMgr ) +{ +} + + +// virtual +DocumentContentFactory::~DocumentContentFactory() +{ +} + + +// XServiceInfo methods. + + +// virtual +OUString SAL_CALL DocumentContentFactory::getImplementationName() +{ + return getImplementationName_Static(); +} + +// virtual +sal_Bool SAL_CALL +DocumentContentFactory::supportsService( const OUString& ServiceName ) +{ + return cppu::supportsService(this, ServiceName); +} + +// virtual +uno::Sequence< OUString > SAL_CALL +DocumentContentFactory::getSupportedServiceNames() +{ + return getSupportedServiceNames_Static(); +} + + +// static +OUString DocumentContentFactory::getImplementationName_Static() +{ + return + "com.sun.star.comp.ucb.TransientDocumentsDocumentContentFactory"; +} + + +// static +uno::Sequence< OUString > +DocumentContentFactory::getSupportedServiceNames_Static() +{ + uno::Sequence< OUString > aSNS { "com.sun.star.frame.TransientDocumentsDocumentContentFactory" }; + return aSNS; +} + + +// 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_xSMgr->createInstance("com.sun.star.ucb.TransientDocumentsContentProvider"), + 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!", + static_cast< cppu::OWeakObject * >( this ) ); +} + + +// Service factory implementation. + +/// @throws uno::Exception +static uno::Reference< uno::XInterface > +DocumentContentFactory_CreateInstance( + const uno::Reference< lang::XMultiServiceFactory> & rSMgr ) +{ + return static_cast<lang::XServiceInfo*>(new DocumentContentFactory(rSMgr)); +} + + +// static +uno::Reference< lang::XSingleServiceFactory > +DocumentContentFactory::createServiceFactory( + const uno::Reference< lang::XMultiServiceFactory >& rxServiceMgr ) +{ + return cppu::createOneInstanceFactory( + rxServiceMgr, + DocumentContentFactory::getImplementationName_Static(), + DocumentContentFactory_CreateInstance, + DocumentContentFactory::getSupportedServiceNames_Static() ); +} + +/* 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 000000000..0956541c4 --- /dev/null +++ b/ucb/source/ucp/tdoc/tdoc_documentcontentfactory.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/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#ifndef INCLUDED_UCB_SOURCE_UCP_TDOC_TDOC_DOCUMENTCONTENTFACTORY_HXX +#define INCLUDED_UCB_SOURCE_UCP_TDOC_TDOC_DOCUMENTCONTENTFACTORY_HXX + +#include <com/sun/star/lang/XMultiServiceFactory.hpp> +#include <com/sun/star/lang/XServiceInfo.hpp> +#include <com/sun/star/lang/XSingleServiceFactory.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( const css::uno::Reference< css::lang::XMultiServiceFactory >& rXSMgr ); + 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; + + // Non-UNO interfaces + static OUString + getImplementationName_Static(); + static css::uno::Sequence< OUString > + getSupportedServiceNames_Static(); + + static css::uno::Reference< css::lang::XSingleServiceFactory > + createServiceFactory( const css::uno::Reference< css::lang::XMultiServiceFactory > & rxServiceMgr ); +private: + css::uno::Reference< css::lang::XMultiServiceFactory > m_xSMgr; +}; + +} // namespace tdoc_ucp + +#endif // INCLUDED_UCB_SOURCE_UCP_TDOC_TDOC_DOCUMENTCONTENTFACTORY_HXX + +/* 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 000000000..cac1eaaf6 --- /dev/null +++ b/ucb/source/ucp/tdoc/tdoc_passwordrequest.cxx @@ -0,0 +1,190 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this 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/mutex.hxx> + +#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" + +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() + throw () override; + virtual void SAL_CALL release() + throw () 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: + osl::Mutex m_aMutex; + OUString m_aPassword; + }; + + } +} // namespace tdoc_ucp + + +// InteractionSupplyPassword Implementation. + + +// XInterface methods. + + +// virtual +void SAL_CALL InteractionSupplyPassword::acquire() + throw() +{ + OWeakObject::acquire(); +} + + +// virtual +void SAL_CALL InteractionSupplyPassword::release() + throw() +{ + 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 ) +{ + osl::MutexGuard aGuard( m_aMutex ); + m_aPassword = aPasswd; +} + +// virtual +OUString SAL_CALL InteractionSupplyPassword::getPassword() +{ + osl::MutexGuard 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::makeAny( aRequest ) ); + + // Fill continuations... + uno::Sequence< + uno::Reference< task::XInteractionContinuation > > aContinuations( 3 ); + aContinuations[ 0 ] = new ucbhelper::InteractionAbort( this ); + aContinuations[ 1 ] = new ucbhelper::InteractionRetry( this ); + aContinuations[ 2 ] = 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 000000000..0db24108e --- /dev/null +++ b/ucb/source/ucp/tdoc/tdoc_passwordrequest.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 . + */ + +#ifndef INCLUDED_UCB_SOURCE_UCP_TDOC_TDOC_PASSWORDREQUEST_HXX +#define INCLUDED_UCB_SOURCE_UCP_TDOC_TDOC_PASSWORDREQUEST_HXX + +#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 + +#endif // INCLUDED_UCB_SOURCE_UCP_TDOC_TDOC_PASSWORDREQUEST_HXX + +/* 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 000000000..d2d8c6ae2 --- /dev/null +++ b/ucb/source/ucp/tdoc/tdoc_provider.cxx @@ -0,0 +1,604 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this 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/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/getcomponentcontext.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 ) +: ::ucbhelper::ContentProviderImplHelper( rxContext ), + m_xDocsMgr( new OfficeDocumentsManager( rxContext, this ) ), + m_xStgElemFac( new StorageElementFactory( rxContext, m_xDocsMgr ) ) +{ +} + + +// virtual +ContentProvider::~ContentProvider() +{ + if ( m_xDocsMgr.is() ) + m_xDocsMgr->destroy(); +} + + +// XInterface methods. +void SAL_CALL ContentProvider::acquire() + throw() +{ + OWeakObject::acquire(); +} + +void SAL_CALL ContentProvider::release() + throw() +{ + 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), + static_cast< frame::XTransientDocumentsDocumentContentIdentifierFactory* >(this), + static_cast< frame::XTransientDocumentsDocumentContentFactory* >(this) + ); + return aRet.hasValue() ? aRet : OWeakObject::queryInterface( rType ); +} + +// XTypeProvider methods. + + +XTYPEPROVIDER_IMPL_5( ContentProvider, + lang::XTypeProvider, + lang::XServiceInfo, + ucb::XContentProvider, + frame::XTransientDocumentsDocumentContentIdentifierFactory, + frame::XTransientDocumentsDocumentContentFactory ); + + +// XServiceInfo methods. + +XSERVICEINFO_COMMOM_IMPL( ContentProvider, + "com.sun.star.comp.ucb.TransientDocumentsContentProvider" ) +/// @throws css::uno::Exception +static css::uno::Reference< css::uno::XInterface > +ContentProvider_CreateInstance( const css::uno::Reference< css::lang::XMultiServiceFactory> & rSMgr ) +{ + css::lang::XServiceInfo* pX = new ContentProvider( ucbhelper::getComponentContext(rSMgr) ); + return css::uno::Reference< css::uno::XInterface >::query( pX ); +} + +css::uno::Sequence< OUString > +ContentProvider::getSupportedServiceNames_Static() +{ + css::uno::Sequence< OUString > aSNS { "com.sun.star.ucb.TransientDocumentsContentProvider" }; + return aSNS; +} + +// Service factory implementation. + + +ONE_INSTANCE_SERVICE_FACTORY_IMPL( ContentProvider ); + + +// 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 ).get(); + + 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!", + static_cast< cppu::OWeakObject * >( this ), + 1 ); + } + + OUString aDocId = tdoc_ucp::OfficeDocumentsManager::queryDocumentId(xModel); + if ( aDocId.isEmpty() ) + { + throw lang::IllegalArgumentException( + "Unable to obtain document id from model!", + static_cast< cppu::OWeakObject * >( this ), + 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 ).get(); + + 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!", + static_cast< cppu::OWeakObject * >( this ), + 1 ); +} + + +// interface OfficeDocumentsEventListener + + +// virtual +void ContentProvider::notifyDocumentClosed( const OUString & 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( const OUString & 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 & ) + { + OSL_FAIL( "Caught InvalidStorageException!" ); + } + catch ( lang::IllegalArgumentException const & ) + { + OSL_FAIL( "Caught IllegalArgumentException!" ); + } + catch ( io::IOException const & ) + { + // Okay to happen, for instance when the storage does not exist. + //OSL_ENSURE( false, "Caught IOException!" ); + } + catch ( embed::StorageWrappedTargetException const & ) + { + OSL_FAIL( "Caught embed::StorageWrappedTargetException!" ); + } + } + 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 & ) + { + OSL_FAIL( "Caught InvalidStorageException!" ); + } + catch ( lang::IllegalArgumentException const & ) + { + OSL_FAIL( "Caught IllegalArgumentException!" ); + } + catch ( io::IOException const & ) + { + // Okay to happen, for instance when the storage does not exist. + //OSL_ENSURE( false, "Caught IOException!" ); + } + catch ( embed::StorageWrappedTargetException const & ) + { + OSL_FAIL( "Caught embed::StorageWrappedTargetException!" ); + } + } + + 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 & ) + { + OSL_FAIL( "Caught InvalidStorageException!" ); + } + catch ( lang::IllegalArgumentException const & ) + { + OSL_FAIL( "Caught IllegalArgumentException!" ); + } + catch ( io::IOException const & ) + { + OSL_FAIL( "Caught IOException!" ); + } + catch ( embed::StorageWrappedTargetException const & ) + { + OSL_FAIL( "Caught embed::StorageWrappedTargetException!" ); + } +// 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 & ) + { + OSL_FAIL( "Caught InvalidStorageException!" ); + } + catch ( lang::IllegalArgumentException const & ) + { + OSL_FAIL( "Caught IllegalArgumentException!" ); + } + catch ( io::IOException const & ) + { + // Okay to happen, for instance when the storage does not exist. + //OSL_ENSURE( false, "Caught IOException!" ); + } + catch ( embed::StorageWrappedTargetException const & ) + { + OSL_FAIL( "Caught embed::StorageWrappedTargetException!" ); + } +// 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 & ) + { + OSL_FAIL( "Caught InvalidStorageException!" ); + } + catch ( lang::IllegalArgumentException const & ) + { + OSL_FAIL( "Caught IllegalArgumentException!" ); + } + catch ( io::IOException const & ) + { + // Okay to happen, for instance when the storage does not exist. + //OSL_ENSURE( false, "Caught IOException!" ); + } + catch ( embed::StorageWrappedTargetException const & ) + { + OSL_FAIL( "Caught embed::StorageWrappedTargetException!" ); + } +// 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 & ) + { + OSL_FAIL( "Caught InvalidStorageException!" ); + } + catch ( lang::IllegalArgumentException const & ) + { + OSL_FAIL( "Caught IllegalArgumentException!" ); + } + catch ( io::IOException const & ) + { + // Okay to happen, for instance if the storage does not exist. + //OSL_ENSURE( false, "Caught IOException!" ); + } + catch ( embed::StorageWrappedTargetException const & ) + { + OSL_FAIL( "Caught embed::StorageWrappedTargetException!" ); + } + } + } + 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; +} + +/* 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 000000000..c1498cd48 --- /dev/null +++ b/ucb/source/ucp/tdoc/tdoc_provider.hxx @@ -0,0 +1,152 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#ifndef INCLUDED_UCB_SOURCE_UCP_TDOC_TDOC_PROVIDER_HXX +#define INCLUDED_UCB_SOURCE_UCP_TDOC_TDOC_PROVIDER_HXX + +#include <rtl/ref.hxx> +#include <com/sun/star/frame/XTransientDocumentsDocumentContentFactory.hpp> +#include <com/sun/star/frame/XTransientDocumentsDocumentContentIdentifierFactory.hpp> +#include <com/sun/star/lang/XSingleServiceFactory.hpp> +#include <com/sun/star/lang/XMultiServiceFactory.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 tdoc_ucp { + + +#define TDOC_ROOT_CONTENT_TYPE \ + "application/" TDOC_URL_SCHEME "-root" +#define TDOC_DOCUMENT_CONTENT_TYPE \ + "application/" TDOC_URL_SCHEME "-document" +#define TDOC_FOLDER_CONTENT_TYPE \ + "application/" TDOC_URL_SCHEME "-folder" +#define TDOC_STREAM_CONTENT_TYPE \ + "application/" TDOC_URL_SCHEME "-stream" + + +class StorageElementFactory; + +class ContentProvider + : public ::ucbhelper::ContentProviderImplHelper + , public css::frame::XTransientDocumentsDocumentContentIdentifierFactory + , public css::frame::XTransientDocumentsDocumentContentFactory +{ +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() + throw() override; + virtual void SAL_CALL release() + throw() 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; + + static OUString getImplementationName_Static(); + static css::uno::Sequence< OUString > getSupportedServiceNames_Static(); + + static css::uno::Reference< css::lang::XSingleServiceFactory > + createServiceFactory( const css::uno::Reference< + css::lang::XMultiServiceFactory >& rxServiceMgr ); + + // 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; + + // interface OfficeDocumentsEventListener + void notifyDocumentOpened( const OUString & rDocId ); + void notifyDocumentClosed( const OUString & rDocId ); + +private: + rtl::Reference< OfficeDocumentsManager > m_xDocsMgr; + rtl::Reference< StorageElementFactory > m_xStgElemFac; +}; + +} // namespace tdoc_ucp + +#endif // INCLUDED_UCB_SOURCE_UCP_TDOC_TDOC_PROVIDER_HXX + +/* 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 000000000..c94e5191d --- /dev/null +++ b/ucb/source/ucp/tdoc/tdoc_resultset.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 <ucbhelper/resultset.hxx> + +#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, + const rtl::Reference< Content >& rxContent, + const ucb::OpenCommandArgument2& rCommand ) +: ResultSetImplHelper( rxContext, rCommand ), + m_xContent( rxContent ) +{ +} + + +// 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 000000000..37274daa1 --- /dev/null +++ b/ucb/source/ucp/tdoc/tdoc_resultset.hxx @@ -0,0 +1,50 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#ifndef INCLUDED_UCB_SOURCE_UCP_TDOC_TDOC_RESULTSET_HXX +#define INCLUDED_UCB_SOURCE_UCP_TDOC_TDOC_RESULTSET_HXX + +#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, + const rtl::Reference< Content >& rxContent, + const css::ucb::OpenCommandArgument2& rCommand ); +}; + +} + +#endif // INCLUDED_UCB_SOURCE_UCP_TDOC_TDOC_RESULTSET_HXX + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/ucb/source/ucp/tdoc/tdoc_services.cxx b/ucb/source/ucp/tdoc/tdoc_services.cxx new file mode 100644 index 000000000..c46751bf6 --- /dev/null +++ b/ucb/source/ucp/tdoc/tdoc_services.cxx @@ -0,0 +1,71 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this 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/XMultiServiceFactory.hpp> +#include <com/sun/star/lang/XSingleServiceFactory.hpp> + +#include "tdoc_provider.hxx" +#include "tdoc_documentcontentfactory.hxx" + +using namespace com::sun::star; +using namespace tdoc_ucp; + + +extern "C" SAL_DLLPUBLIC_EXPORT void * ucptdoc1_component_getFactory( + const char * pImplName, void * pServiceManager, void * /*pRegistryKey*/ ) +{ + void * pRet = nullptr; + + uno::Reference< lang::XMultiServiceFactory > xSMgr( + static_cast< lang::XMultiServiceFactory * >( + pServiceManager ) ); + uno::Reference< lang::XSingleServiceFactory > xFactory; + + + // Transient Documents Content Provider. + + + if ( ContentProvider::getImplementationName_Static(). + equalsAscii( pImplName ) ) + { + xFactory = ContentProvider::createServiceFactory( xSMgr ); + } + + + // Transient Documents Document Content Factory. + + + else if ( DocumentContentFactory::getImplementationName_Static(). + equalsAscii( pImplName ) ) + { + xFactory = DocumentContentFactory::createServiceFactory( xSMgr ); + } + + + if ( xFactory.is() ) + { + xFactory->acquire(); + pRet = xFactory.get(); + } + + return pRet; +} + +/* 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 000000000..57052d554 --- /dev/null +++ b/ucb/source/ucp/tdoc/tdoc_stgelems.cxx @@ -0,0 +1,876 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this 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 <osl/diagnose.h> +#include <com/sun/star/io/IOException.hpp> +#include <com/sun/star/lang/DisposedException.hpp> +#include <com/sun/star/reflection/ProxyFactory.hpp> + +#include "tdoc_uri.hxx" + +#include "tdoc_stgelems.hxx" + +using namespace com::sun::star; +using namespace tdoc_ucp; + + +// ParentStorageHolder Implementation. + + +ParentStorageHolder::ParentStorageHolder( + const uno::Reference< embed::XStorage > & xParentStorage, + const OUString & rUri ) +: m_xParentStorage( xParentStorage ), + m_bParentIsRootStorage( false ) +{ + Uri aUri( rUri ); + if ( aUri.isDocument() ) + m_bParentIsRootStorage = true; +} + + +// Storage Implementation. + + +Storage::Storage( const uno::Reference< uno::XComponentContext > & rxContext, + const 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( 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 & ) + { + OSL_FAIL( "Storage::Storage: Caught exception!" ); + } + + OSL_ENSURE( m_xAggProxy.is(), + "Storage::Storage: Wrapped storage cannot be aggregated!" ); + + if ( m_xAggProxy.is() ) + { + 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( + static_cast< cppu::OWeakObject * >( this ) ); + } + 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 ) + { + if ( m_xWrappedComponent.is() ) + { + // "Auto-dispose"... + try + { + m_xWrappedComponent->dispose(); + } + catch ( lang::DisposedException const & ) + { + // might happen. + } + catch ( ... ) + { + OSL_FAIL( "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() + throw () +{ + osl_atomic_increment( &m_refCount ); +} + + +// virtual +void SAL_CALL Storage::release() + throw () +{ + //#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() ) + { + OSL_ENSURE( m_xWrappedTransObj.is(), "No XTransactedObject interface!" ); + + if ( m_xWrappedTransObj.is() ) + { + 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() ) + { + OSL_ENSURE( m_xWrappedTransObj.is(), "No XTransactedObject interface!" ); + + if ( m_xWrappedTransObj.is() ) + { + 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 & ) + { + OSL_FAIL( "OutputStream::OutputStream: Caught exception!" ); + } + + OSL_ENSURE( m_xAggProxy.is(), + "OutputStream::OutputStream: Wrapped stream cannot be aggregated!" ); + + if ( m_xAggProxy.is() ) + { + 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( + static_cast< cppu::OWeakObject * >( this ) ); + } + 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, + const OUString & rUri, + const uno::Reference< embed::XStorage > & xParentStorage, + const uno::Reference< io::XStream > & xStreamToWrap ) +: ParentStorageHolder( xParentStorage, Uri( rUri ).getParentUri() ), + 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 & ) + { + OSL_FAIL( "OutputStream::OutputStream: Caught exception!" ); + } + + OSL_ENSURE( m_xAggProxy.is(), + "OutputStream::OutputStream: Wrapped stream cannot be aggregated!" ); + + if ( m_xAggProxy.is() ) + { + 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( + static_cast< cppu::OWeakObject * >( this ) ); + } + 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(); // @@@ + } + } +} + +/* 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 000000000..acad1b443 --- /dev/null +++ b/ucb/source/ucp/tdoc/tdoc_stgelems.hxx @@ -0,0 +1,343 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#ifndef INCLUDED_UCB_SOURCE_UCP_TDOC_TDOC_STGELEMS_HXX +#define INCLUDED_UCB_SOURCE_UCP_TDOC_TDOC_STGELEMS_HXX + +#include <memory> + +#include <osl/mutex.hxx> +#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" + +namespace tdoc_ucp { + +struct MutexHolder +{ + osl::Mutex m_aMutex; +}; + + +class ParentStorageHolder : public MutexHolder +{ +public: + ParentStorageHolder( + const 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 ) + { osl::MutexGuard aGuard( m_aMutex ); m_xParentStorage = xStg; } + +private: + 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, + const 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() + throw () override; + virtual void SAL_CALL release() + throw () override; + + // XTypeProvider (implemnented 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; + friend class std::unique_ptr< Storage >; +}; + + +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 (implemnented 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, + 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 (implemnented 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(); + + 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 + +#endif // INCLUDED_UCB_SOURCE_UCP_TDOC_TDOC_STGELEMS_HXX + +/* 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 000000000..49493ec7b --- /dev/null +++ b/ucb/source/ucp/tdoc/tdoc_storage.cxx @@ -0,0 +1,616 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this 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 <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( + const uno::Reference< uno::XComponentContext > & rxContext, + const rtl::Reference< OfficeDocumentsManager > & xDocsMgr ) +: m_xDocsMgr( xDocsMgr ), + m_xContext( rxContext ) +{ +} + + +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; + } + + aIt->second = new Storage( m_xContext, this, aUriKey, xParentStorage, xStorage ); + aIt->second->m_aContainerIt = aIt; + return aIt->second; + } +} + + +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, 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 000000000..454347359 --- /dev/null +++ b/ucb/source/ucp/tdoc/tdoc_storage.hxx @@ -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 . + */ + +#ifndef INCLUDED_UCB_SOURCE_UCP_TDOC_TDOC_STORAGE_HXX +#define INCLUDED_UCB_SOURCE_UCP_TDOC_TDOC_STORAGE_HXX + +#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( + const css::uno::Reference< css::uno::XComponentContext > & rxContext, + const 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 + +#endif // INCLUDED_UCB_SOURCE_UCP_TDOC_TDOC_STORAGE_HXX + +/* 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 000000000..592977ea0 --- /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 000000000..15e51e6e8 --- /dev/null +++ b/ucb/source/ucp/tdoc/tdoc_uri.hxx @@ -0,0 +1,110 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#ifndef INCLUDED_UCB_SOURCE_UCP_TDOC_TDOC_URI_HXX +#define INCLUDED_UCB_SOURCE_UCP_TDOC_TDOC_URI_HXX + +#include <rtl/ustring.hxx> + +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( const OUString & rUri ) + : m_aUri( rUri ), 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.copy( m_aDocId.getLength() + 1 ).getLength() < 2 ) ); +} + +} // namespace tdoc_ucp + +#endif // INCLUDED_UCB_SOURCE_UCP_TDOC_TDOC_URI_HXX + +/* 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 000000000..ebc03a445 --- /dev/null +++ b/ucb/source/ucp/tdoc/ucptdoc1.component @@ -0,0 +1,28 @@ +<?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@" + prefix="ucptdoc1" xmlns="http://openoffice.org/2010/uno-components"> + <implementation name="com.sun.star.comp.ucb.TransientDocumentsContentProvider"> + <service name="com.sun.star.ucb.TransientDocumentsContentProvider"/> + </implementation> + <implementation name="com.sun.star.comp.ucb.TransientDocumentsDocumentContentFactory"> + <service name="com.sun.star.frame.TransientDocumentsDocumentContentFactory"/> + </implementation> +</component> diff --git a/ucb/source/ucp/webdav-neon/ContentProperties.cxx b/ucb/source/ucp/webdav-neon/ContentProperties.cxx new file mode 100644 index 000000000..12bbf4f74 --- /dev/null +++ b/ucb/source/ucp/webdav-neon/ContentProperties.cxx @@ -0,0 +1,558 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/************************************************************************* + * + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * Copyright 2000, 2010 Oracle and/or its affiliates. + * + * OpenOffice.org - a multi-platform office productivity suite + * + * This file is part of OpenOffice.org. + * + * OpenOffice.org is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License version 3 + * only, as published by the Free Software Foundation. + * + * OpenOffice.org is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License version 3 for more details + * (a copy is included in the LICENSE file that accompanied this code). + * + * You should have received a copy of the GNU Lesser General Public License + * version 3 along with OpenOffice.org. If not, see + * <http://www.openoffice.org/license.html> + * for a copy of the LGPLv3 License. + * + ************************************************************************/ + + +/************************************************************************** + TODO + ************************************************************************** + + *************************************************************************/ +#include <memory> +#include <com/sun/star/util/DateTime.hpp> +#include "NeonUri.hxx" +#include "DAVResource.hxx" +#include "DAVProperties.hxx" +#include "DateTimeHelper.hxx" +#include "webdavprovider.hxx" +#include "ContentProperties.hxx" + +using namespace com::sun::star; +using namespace webdav_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 + { + NeonUri aURI( rResource.uri ); + m_aEscapedTitle = aURI.GetPathBaseName(); + + (*m_xProps)[ OUString("Title") ] + = PropertyValue( + uno::makeAny( aURI.GetPathBaseNameUnescaped() ), true ); + } + catch ( DAVException const & ) + { + (*m_xProps)[ OUString("Title") ] + = PropertyValue( + uno::makeAny( + 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::makeAny( rTitle ), true ); + (*m_xProps)[ OUString("IsFolder") ] + = PropertyValue( uno::makeAny( bFolder ), true ); + (*m_xProps)[ OUString("IsDocument") ] + = PropertyValue( uno::makeAny( !bFolder ), true ); +} + + +ContentProperties::ContentProperties( const OUString & rTitle ) +: m_xProps( new PropertyValueMap ), + m_bTrailingSlash( false ) +{ + (*m_xProps)[ OUString("Title") ] + = PropertyValue( uno::makeAny( 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 +{ + return get( rName ) != nullptr; +} + + +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; + + for ( const beans::Property & rProp : rProps ) + { + 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 + + for ( const beans::Property & rProp : rProps ) + { + if ( rProp.Name == "DateModified" ) + { + propertyNames.emplace_back("Last-Modified" ); + } + else if ( rProp.Name == "MediaType" ) + { + propertyNames.emplace_back("Content-Type" ); + } + else if ( rProp.Name == "Size" ) + { + propertyNames.emplace_back("Content-Length" ); + } + else + { + propertyNames.push_back( rProp.Name ); + } + } +} + + +bool ContentProperties::containsAllNames( + const uno::Sequence< beans::Property >& rProps, + std::vector< OUString > & rNamesNotContained ) const +{ + rNamesNotContained.clear(); + + for ( const auto& rProp : rProps ) + { + const OUString & rName = rProp.Name; + if ( !contains( rName ) ) + { + // Not found. + rNamesNotContained.push_back( rName ); + } + } + + return rNamesNotContained.empty(); +} + + +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::makeAny( 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::makeAny( 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::makeAny( 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::makeAny( 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::makeAny( 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::makeAny( bFolder ), true ); + (*m_xProps)[ OUString("IsDocument") ] + = PropertyValue( uno::makeAny( !bFolder ), true ); + (*m_xProps)[ OUString("ContentType") ] + = PropertyValue( uno::makeAny( bFolder + ? OUString( WEBDAV_COLLECTION_TYPE ) + : OUString( WEBDAV_CONTENT_TYPE ) ), true ); + } + // else if ( rName.equals( DAVProperties::SOURCE ) ) + // { + // } + // else if ( rName.equals( DAVProperties::SUPPORTEDLOCK ) ) + // { + // } + + // Save property. + (*m_xProps)[ rName ] = PropertyValue( rValue, bIsCaseSensitive ); +} + + +// CachableContentProperties Implementation. + + +namespace +{ + bool isCachable( OUString const & rName, + bool isCaseSensitive ) + { + static 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 (const auto & rNonCachableProp : aNonCachableProps) + { + if ( isCaseSensitive ) + { + if ( rName == rNonCachableProp ) + return false; + } + else + if ( rName.equalsIgnoreAsciiCase( rNonCachableProp ) ) + 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-neon/ContentProperties.hxx b/ucb/source/ucp/webdav-neon/ContentProperties.hxx new file mode 100644 index 000000000..8f11e8726 --- /dev/null +++ b/ucb/source/ucp/webdav-neon/ContentProperties.hxx @@ -0,0 +1,183 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/************************************************************************* + * + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * Copyright 2000, 2010 Oracle and/or its affiliates. + * + * OpenOffice.org - a multi-platform office productivity suite + * + * This file is part of OpenOffice.org. + * + * OpenOffice.org is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License version 3 + * only, as published by the Free Software Foundation. + * + * OpenOffice.org is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License version 3 for more details + * (a copy is included in the LICENSE file that accompanied this code). + * + * You should have received a copy of the GNU Lesser General Public License + * version 3 along with OpenOffice.org. If not, see + * <http://www.openoffice.org/license.html> + * for a copy of the LGPLv3 License. + * + ************************************************************************/ + +#pragma once + +#include <config_lgpl.h> +#include <memory> +#include <unordered_map> +#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 webdav_ucp +{ + +struct DAVResource; + +// PropertyValueMap. +class PropertyValue +{ +private: + css::uno::Any m_aValue; + bool m_bIsCaseSensitive; + +public: + PropertyValue() + : m_bIsCaseSensitive( true ) {} + + PropertyValue( const css::uno::Any & rValue, + bool bIsCaseSensitive ) + : m_aValue( rValue), + 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 & ) = delete; + + const PropertyValue * get( const OUString & rName ) const; +}; + +class CachableContentProperties +{ +private: + ContentProperties m_aProps; + + CachableContentProperties & operator=( const CachableContentProperties & ) = delete; + CachableContentProperties( const CachableContentProperties & ) = delete; + +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 webdav_ucp + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/ucb/source/ucp/webdav-neon/DAVAuthListener.hxx b/ucb/source/ucp/webdav-neon/DAVAuthListener.hxx new file mode 100644 index 000000000..39ca8f938 --- /dev/null +++ b/ucb/source/ucp/webdav-neon/DAVAuthListener.hxx @@ -0,0 +1,53 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/************************************************************************* + * + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * Copyright 2000, 2010 Oracle and/or its affiliates. + * + * OpenOffice.org - a multi-platform office productivity suite + * + * This file is part of OpenOffice.org. + * + * OpenOffice.org is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License version 3 + * only, as published by the Free Software Foundation. + * + * OpenOffice.org is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License version 3 for more details + * (a copy is included in the LICENSE file that accompanied this code). + * + * You should have received a copy of the GNU Lesser General Public License + * version 3 along with OpenOffice.org. If not, see + * <http://www.openoffice.org/license.html> + * for a copy of the LGPLv3 License. + * + ************************************************************************/ +#ifndef INCLUDED_UCB_SOURCE_UCP_WEBDAV_NEON_DAVAUTHLISTENER_HXX +#define INCLUDED_UCB_SOURCE_UCP_WEBDAV_NEON_DAVAUTHLISTENER_HXX + +#include <config_lgpl.h> +#include <salhelper/simplereferenceobject.hxx> +#include <rtl/ustring.hxx> + +namespace webdav_ucp +{ + +class DAVAuthListener : public salhelper::SimpleReferenceObject +{ + public: + virtual int authenticate( + const OUString & inRealm, + const OUString & inHostName, + OUString & inoutUserName, + OUString & outPassWord, + bool bCanUseSystemCredentials ) = 0; +}; + +} // namespace webdav_ucp + +#endif // INCLUDED_UCB_SOURCE_UCP_WEBDAV_NEON_DAVAUTHLISTENER_HXX + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/ucb/source/ucp/webdav-neon/DAVAuthListenerImpl.hxx b/ucb/source/ucp/webdav-neon/DAVAuthListenerImpl.hxx new file mode 100644 index 000000000..a92720b50 --- /dev/null +++ b/ucb/source/ucp/webdav-neon/DAVAuthListenerImpl.hxx @@ -0,0 +1,72 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/************************************************************************* + * + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * Copyright 2000, 2010 Oracle and/or its affiliates. + * + * OpenOffice.org - a multi-platform office productivity suite + * + * This file is part of OpenOffice.org. + * + * OpenOffice.org is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License version 3 + * only, as published by the Free Software Foundation. + * + * OpenOffice.org is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License version 3 for more details + * (a copy is included in the LICENSE file that accompanied this code). + * + * You should have received a copy of the GNU Lesser General Public License + * version 3 along with OpenOffice.org. If not, see + * <http://www.openoffice.org/license.html> + * for a copy of the LGPLv3 License. + * + ************************************************************************/ + +#ifndef INCLUDED_UCB_SOURCE_UCP_WEBDAV_NEON_DAVAUTHLISTENERIMPL_HXX +#define INCLUDED_UCB_SOURCE_UCP_WEBDAV_NEON_DAVAUTHLISTENERIMPL_HXX + +#include <config_lgpl.h> +#include <com/sun/star/ucb/XCommandEnvironment.hpp> +#include "DAVAuthListener.hxx" + + +namespace webdav_ucp +{ + + + + + class DAVAuthListener_Impl : public DAVAuthListener + { + public: + + DAVAuthListener_Impl( + const css::uno::Reference< css::ucb::XCommandEnvironment>& xEnv, + const OUString & inURL ) + : m_xEnv( xEnv ), m_aURL( inURL ) + { + } + + virtual int authenticate( const OUString & inRealm, + const OUString & inHostName, + OUString & inoutUserName, + OUString & outPassWord, + bool bCanUseSystemCredentials ) override; + private: + + const css::uno::Reference< css::ucb::XCommandEnvironment > m_xEnv; + const OUString m_aURL; + + OUString m_aPrevPassword; + OUString m_aPrevUsername; + }; + +} + +#endif + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/ucb/source/ucp/webdav-neon/DAVException.hxx b/ucb/source/ucp/webdav-neon/DAVException.hxx new file mode 100644 index 000000000..3e1046ce2 --- /dev/null +++ b/ucb/source/ucp/webdav-neon/DAVException.hxx @@ -0,0 +1,177 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/************************************************************************* + * + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * Copyright 2000, 2010 Oracle and/or its affiliates. + * + * OpenOffice.org - a multi-platform office productivity suite + * + * This file is part of OpenOffice.org. + * + * OpenOffice.org is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License version 3 + * only, as published by the Free Software Foundation. + * + * OpenOffice.org is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License version 3 for more details + * (a copy is included in the LICENSE file that accompanied this code). + * + * You should have received a copy of the GNU Lesser General Public License + * version 3 along with OpenOffice.org. If not, see + * <http://www.openoffice.org/license.html> + * for a copy of the LGPLv3 License. + * + ************************************************************************/ + +#ifndef INCLUDED_UCB_SOURCE_UCP_WEBDAV_NEON_DAVEXCEPTION_HXX +#define INCLUDED_UCB_SOURCE_UCP_WEBDAV_NEON_DAVEXCEPTION_HXX + +#include <config_lgpl.h> +#include <rtl/ustring.hxx> + +namespace webdav_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_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_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 ), mStatusCode( SC_NONE ) {}; + DAVException( ExceptionCode inExceptionCode, + const OUString & rData ) : + mExceptionCode( inExceptionCode ), mData( rData ), + mStatusCode( SC_NONE ) {}; + DAVException( ExceptionCode inExceptionCode, + const OUString & rData, + sal_uInt16 nStatusCode ) : + mExceptionCode( inExceptionCode ), mData( rData ), + mStatusCode( nStatusCode ) {}; + + const ExceptionCode & getError() const { return mExceptionCode; } + const OUString & getData() const { return mData; } + sal_uInt16 getStatus() const { return mStatusCode; } +}; + +} // namespace webdav_ucp + +#endif // INCLUDED_UCB_SOURCE_UCP_WEBDAV_NEON_DAVEXCEPTION_HXX + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/ucb/source/ucp/webdav-neon/DAVProperties.cxx b/ucb/source/ucp/webdav-neon/DAVProperties.cxx new file mode 100644 index 000000000..5d5634936 --- /dev/null +++ b/ucb/source/ucp/webdav-neon/DAVProperties.cxx @@ -0,0 +1,204 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/************************************************************************* + * + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * Copyright 2000, 2010 Oracle and/or its affiliates. + * + * OpenOffice.org - a multi-platform office productivity suite + * + * This file is part of OpenOffice.org. + * + * OpenOffice.org is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License version 3 + * only, as published by the Free Software Foundation. + * + * OpenOffice.org is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License version 3 for more details + * (a copy is included in the LICENSE file that accompanied this code). + * + * You should have received a copy of the GNU Lesser General Public License + * version 3 along with OpenOffice.org. If not, see + * <http://www.openoffice.org/license.html> + * for a copy of the LGPLv3 License. + * + ************************************************************************/ + + +#include <string.h> +#include "DAVProperties.hxx" + +using namespace webdav_ucp; + +const OUString DAVProperties::CREATIONDATE("DAV:creationdate"); +const OUString DAVProperties::DISPLAYNAME("DAV:displayname"); +const OUString DAVProperties::GETCONTENTLANGUAGE("DAV:getcontentlanguage"); +const OUString DAVProperties::GETCONTENTLENGTH("DAV:getcontentlength"); +const OUString DAVProperties::GETCONTENTTYPE("DAV:getcontenttype"); +const OUString DAVProperties::GETETAG("DAV:getetag"); +const OUString DAVProperties::GETLASTMODIFIED("DAV:getlastmodified"); +const OUString DAVProperties::LOCKDISCOVERY("DAV:lockdiscovery"); +const OUString DAVProperties::RESOURCETYPE("DAV:resourcetype"); +const OUString DAVProperties::SOURCE("DAV:source"); +const OUString DAVProperties::SUPPORTEDLOCK("DAV:supportedlock"); + +const OUString DAVProperties::EXECUTABLE("http://apache.org/dav/props/executable"); + +void DAVProperties::createNeonPropName( const OUString & rFullName, + NeonPropName & rName ) +{ + if ( rFullName.startsWith( "DAV:" ) ) + { + rName.nspace = "DAV:"; + rName.name + = strdup( OUStringToOString( + rFullName.copy( RTL_CONSTASCII_LENGTH( "DAV:" ) ), + RTL_TEXTENCODING_UTF8 ).getStr() ); + } + else if ( rFullName.startsWith( "http://apache.org/dav/props/" ) ) + { + rName.nspace = "http://apache.org/dav/props/"; + rName.name + = strdup( OUStringToOString( + rFullName.copy( + RTL_CONSTASCII_LENGTH( + "http://apache.org/dav/props/" ) ), + RTL_TEXTENCODING_UTF8 ).getStr() ); + } + else if ( rFullName.startsWith( "http://ucb.openoffice.org/dav/props/" ) ) + { + rName.nspace = "http://ucb.openoffice.org/dav/props/"; + rName.name + = strdup( OUStringToOString( + rFullName.copy( + RTL_CONSTASCII_LENGTH( + "http://ucb.openoffice.org/dav/props/" ) ), + RTL_TEXTENCODING_UTF8 ).getStr() ); + } + else if ( rFullName.startsWith( "<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 + { + // Add our namespace to our own properties. + rName.nspace = "http://ucb.openoffice.org/dav/props/"; + rName.name + = strdup( OUStringToOString( rFullName, + RTL_TEXTENCODING_UTF8 ).getStr() ); + } +} + +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.isEmpty() ) + { + // 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 ( DAVProperties::RESOURCETYPE.matchIgnoreAsciiCase( aName, 4 ) || + DAVProperties::SUPPORTEDLOCK.matchIgnoreAsciiCase( aName, 4 ) || + DAVProperties::LOCKDISCOVERY.matchIgnoreAsciiCase( aName, 4 ) || + DAVProperties::CREATIONDATE.matchIgnoreAsciiCase( aName, 4 ) || + DAVProperties::DISPLAYNAME.matchIgnoreAsciiCase( aName, 4 ) || + DAVProperties::GETCONTENTLANGUAGE.matchIgnoreAsciiCase( aName, 4 ) || + DAVProperties::GETCONTENTLENGTH.matchIgnoreAsciiCase( aName, 4 ) || + DAVProperties::GETCONTENTTYPE.matchIgnoreAsciiCase( aName, 4 ) || + DAVProperties::GETETAG.matchIgnoreAsciiCase( aName, 4 ) || + DAVProperties::GETLASTMODIFIED.matchIgnoreAsciiCase( aName, 4 ) || + DAVProperties::SOURCE.matchIgnoreAsciiCase( aName, 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:" + aName + " xmlns:prop=\"" + aNameSpace + "\">"; + } +} + +bool DAVProperties::isUCBDeadProperty( const NeonPropName & rName ) +{ + return ( rName.nspace && + ( rtl_str_compareIgnoreAsciiCase( + rName.nspace, "http://ucb.openoffice.org/dav/props/" ) + == 0 ) ); +} + +bool DAVProperties::isUCBSpecialProperty( + const OUString& rFullName, OUString& rParsedName) +{ + if ( !rFullName.startsWith( "<prop:" ) || !rFullName.endsWith( "\">" ) ) + return false; + + sal_Int32 nStart = strlen( "<prop:" ); + sal_Int32 nEnd = rFullName.indexOf( ' ', nStart ); + if ( nEnd <= nStart ) // incl. -1 for "not found" + return false; + + OUString sPropName( rFullName.copy( nStart, nEnd - nStart ) ); + + // TODO skip whitespaces? + if ( !rFullName.match( "xmlns:prop=\"", ++nEnd ) ) + return false; + + nStart = nEnd + strlen( "xmlns:prop=\"" ); + nEnd = rFullName.indexOf( '"', nStart ); + if ( nEnd != rFullName.getLength() - sal_Int32( strlen( "\">" ) ) + || nEnd == nStart ) + { + return false; + } + + rParsedName = rFullName.copy( nStart, nEnd - nStart ); + if ( !rParsedName.endsWith( "/" ) ) + rParsedName += "/"; + rParsedName += sPropName; + + return rParsedName.getLength(); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/ucb/source/ucp/webdav-neon/DAVProperties.hxx b/ucb/source/ucp/webdav-neon/DAVProperties.hxx new file mode 100644 index 000000000..2064a83b4 --- /dev/null +++ b/ucb/source/ucp/webdav-neon/DAVProperties.hxx @@ -0,0 +1,68 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/************************************************************************* + * + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * Copyright 2000, 2010 Oracle and/or its affiliates. + * + * OpenOffice.org - a multi-platform office productivity suite + * + * This file is part of OpenOffice.org. + * + * OpenOffice.org is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License version 3 + * only, as published by the Free Software Foundation. + * + * OpenOffice.org is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License version 3 for more details + * (a copy is included in the LICENSE file that accompanied this code). + * + * You should have received a copy of the GNU Lesser General Public License + * version 3 along with OpenOffice.org. If not, see + * <http://www.openoffice.org/license.html> + * for a copy of the LGPLv3 License. + * + ************************************************************************/ +#ifndef INCLUDED_UCB_SOURCE_UCP_WEBDAV_NEON_DAVPROPERTIES_HXX +#define INCLUDED_UCB_SOURCE_UCP_WEBDAV_NEON_DAVPROPERTIES_HXX + +#include <config_lgpl.h> +#include <rtl/ustring.hxx> +#include "NeonTypes.hxx" + +namespace webdav_ucp +{ + +struct DAVProperties +{ + static const OUString CREATIONDATE; + static const OUString DISPLAYNAME; + static const OUString GETCONTENTLANGUAGE; + static const OUString GETCONTENTLENGTH; + static const OUString GETCONTENTTYPE; + static const OUString GETETAG; + static const OUString GETLASTMODIFIED; + static const OUString LOCKDISCOVERY; + static const OUString RESOURCETYPE; + static const OUString SOURCE; + static const OUString SUPPORTEDLOCK; + static const OUString EXECUTABLE; + + static void createNeonPropName( const OUString & rFullName, + NeonPropName & rName ); + static void createUCBPropName ( const char * nspace, + const char * name, + OUString & rFullName ); + + static bool isUCBDeadProperty( const NeonPropName & rName ); + static bool isUCBSpecialProperty( const OUString & rFullName, + OUString & rParsedName ); +}; + +} // namespace webdav_ucp + +#endif // INCLUDED_UCB_SOURCE_UCP_WEBDAV_NEON_DAVPROPERTIES_HXX + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/ucb/source/ucp/webdav-neon/DAVRequestEnvironment.hxx b/ucb/source/ucp/webdav-neon/DAVRequestEnvironment.hxx new file mode 100644 index 000000000..98e974d61 --- /dev/null +++ b/ucb/source/ucp/webdav-neon/DAVRequestEnvironment.hxx @@ -0,0 +1,67 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/************************************************************************* + * + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * Copyright 2000, 2010 Oracle and/or its affiliates. + * + * OpenOffice.org - a multi-platform office productivity suite + * + * This file is part of OpenOffice.org. + * + * OpenOffice.org is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License version 3 + * only, as published by the Free Software Foundation. + * + * OpenOffice.org is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License version 3 for more details + * (a copy is included in the LICENSE file that accompanied this code). + * + * You should have received a copy of the GNU Lesser General Public License + * version 3 along with OpenOffice.org. If not, see + * <http://www.openoffice.org/license.html> + * for a copy of the LGPLv3 License. + * + ************************************************************************/ +#ifndef INCLUDED_UCB_SOURCE_UCP_WEBDAV_NEON_DAVREQUESTENVIRONMENT_HXX +#define INCLUDED_UCB_SOURCE_UCP_WEBDAV_NEON_DAVREQUESTENVIRONMENT_HXX + +#include <config_lgpl.h> +#include <vector> +#include <rtl/ref.hxx> +#include <com/sun/star/ucb/XCommandEnvironment.hpp> +#include "DAVAuthListener.hxx" + +namespace webdav_ucp +{ + typedef std::pair< OUString, OUString > DAVRequestHeader; + typedef std::vector< DAVRequestHeader > DAVRequestHeaders; + +struct DAVRequestEnvironment +{ + OUString m_aRequestURI; + rtl::Reference< DAVAuthListener > m_xAuthListener; +// rtl::Reference< DAVStatusListener > m_xStatusListener; +// rtl::Reference< DAVProgressListener > m_xStatusListener; + DAVRequestHeaders m_aRequestHeaders; + uno::Reference< ucb::XCommandEnvironment > m_xEnv; + +DAVRequestEnvironment( const OUString & rRequestURI, + const rtl::Reference< DAVAuthListener > & xListener, + const DAVRequestHeaders & rRequestHeaders, + const uno::Reference< ucb::XCommandEnvironment > & xEnv) + : m_aRequestURI( rRequestURI ), + m_xAuthListener( xListener ), + m_aRequestHeaders( rRequestHeaders ), + m_xEnv( xEnv ){} + + DAVRequestEnvironment() {} +}; + +} // namespace webdav_ucp + +#endif // INCLUDED_UCB_SOURCE_UCP_WEBDAV_NEON_DAVREQUESTENVIRONMENT_HXX + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/ucb/source/ucp/webdav-neon/DAVResource.hxx b/ucb/source/ucp/webdav-neon/DAVResource.hxx new file mode 100644 index 000000000..219b06444 --- /dev/null +++ b/ucb/source/ucp/webdav-neon/DAVResource.hxx @@ -0,0 +1,73 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/************************************************************************* + * + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * Copyright 2000, 2010 Oracle and/or its affiliates. + * + * OpenOffice.org - a multi-platform office productivity suite + * + * This file is part of OpenOffice.org. + * + * OpenOffice.org is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License version 3 + * only, as published by the Free Software Foundation. + * + * OpenOffice.org is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License version 3 for more details + * (a copy is included in the LICENSE file that accompanied this code). + * + * You should have received a copy of the GNU Lesser General Public License + * version 3 along with OpenOffice.org. If not, see + * <http://www.openoffice.org/license.html> + * for a copy of the LGPLv3 License. + * + ************************************************************************/ + +#ifndef INCLUDED_UCB_SOURCE_UCP_WEBDAV_NEON_DAVRESOURCE_HXX +#define INCLUDED_UCB_SOURCE_UCP_WEBDAV_NEON_DAVRESOURCE_HXX + +#include <config_lgpl.h> +#include <vector> + +#include <rtl/ustring.hxx> +#include <com/sun/star/uno/Any.hxx> + +namespace webdav_ucp +{ + +struct DAVPropertyValue +{ + OUString Name; + css::uno::Any Value; + bool IsCaseSensitive; + + DAVPropertyValue() : IsCaseSensitive( true ) {} +}; + +struct DAVResource +{ + OUString uri; + std::vector< DAVPropertyValue > properties; + + DAVResource() {} + explicit DAVResource( const OUString & inUri ) : uri( inUri ) {} +}; + +struct DAVResourceInfo +{ + std::vector < OUString > properties; + + bool operator==( const struct DAVResourceInfo& a ) const + { + return (properties == a.properties ); + } +}; + +} // namespace webdav_ucp + +#endif // INCLUDED_UCB_SOURCE_UCP_WEBDAV_NEON_DAVRESOURCE_HXX + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/ucb/source/ucp/webdav-neon/DAVResourceAccess.cxx b/ucb/source/ucp/webdav-neon/DAVResourceAccess.cxx new file mode 100644 index 000000000..f0576e049 --- /dev/null +++ b/ucb/source/ucp/webdav-neon/DAVResourceAccess.cxx @@ -0,0 +1,1192 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/************************************************************************* + * + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * Copyright 2000, 2010 Oracle and/or its affiliates. + * + * OpenOffice.org - a multi-platform office productivity suite + * + * This file is part of OpenOffice.org. + * + * OpenOffice.org is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License version 3 + * only, as published by the Free Software Foundation. + * + * OpenOffice.org is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License version 3 for more details + * (a copy is included in the LICENSE file that accompanied this code). + * + * You should have received a copy of the GNU Lesser General Public License + * version 3 along with OpenOffice.org. If not, see + * <http://www.openoffice.org/license.html> + * for a copy of the LGPLv3 License. + * + ************************************************************************/ + + +#include <com/sun/star/io/IOException.hpp> +#include <com/sun/star/lang/IllegalArgumentException.hpp> +#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" + +using namespace webdav_ucp; +using namespace com::sun::star; + + +// DAVAuthListener_Impl Implementation. + +static constexpr sal_uInt32 g_nRedirectLimit = 5; + +// virtual +int DAVAuthListener_Impl::authenticate( + const OUString & inRealm, + const OUString & inHostName, + OUString & inoutUserName, + OUString & outPassWord, + bool bCanUseSystemCredentials ) +{ + if ( m_xEnv.is() ) + { + uno::Reference< task::XInteractionHandler > xIH + = m_xEnv->getInteractionHandler(); + + if ( xIH.is() ) + { + // #102871# - Supply username and password from previous try. + // Password container service depends on this! + if ( inoutUserName.isEmpty() ) + inoutUserName = m_aPrevUsername; + + if ( outPassWord.isEmpty() ) + outPassWord = m_aPrevPassword; + + rtl::Reference< ucbhelper::SimpleAuthenticationRequest > xRequest + = new ucbhelper::SimpleAuthenticationRequest( + m_aURL, inHostName, inRealm, inoutUserName, + outPassWord, bCanUseSystemCredentials ); + 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() ) + { + 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. + + +DAVResourceAccess::DAVResourceAccess( + const uno::Reference< uno::XComponentContext > & rxContext, + rtl::Reference< DAVSessionFactory > const & rSessionFactory, + const OUString & rURL ) +: m_aURL( rURL ), + m_xSessionFactory( rSessionFactory ), + m_xContext( rxContext ) +{ +} + + +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 css::uno::Reference< + css::ucb::XCommandEnvironment > & xEnv ) +{ + initialize(); + + int errorCount = 0; + bool bRetry; + do + { + bRetry = false; + try + { + DAVRequestHeaders aHeaders; + + getUserRequestHeaders( xEnv, + getRequestURI(), + css::ucb::WebDAVHTTPMethod_OPTIONS, + aHeaders ); + + m_xSession->OPTIONS( getRequestURI(), + rOptions, + DAVRequestEnvironment( + getRequestURI(), + new DAVAuthListener_Impl( xEnv, m_aURL ), + aHeaders, xEnv ) ); + } + catch ( const DAVException & 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( + getRequestURI(), + new DAVAuthListener_Impl( xEnv, m_aURL ), + aHeaders, xEnv ) ); + } + catch ( const DAVException & 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( + getRequestURI(), + new DAVAuthListener_Impl( xEnv, m_aURL ), + aHeaders, xEnv ) ) ; + } + catch ( const DAVException & 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( + getRequestURI(), + new DAVAuthListener_Impl( xEnv, m_aURL ), + aHeaders, xEnv ) ); + } + catch ( const DAVException & 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( + getRequestURI(), + new DAVAuthListener_Impl( xEnv, m_aURL ), + aHeaders, xEnv ) ); + } + catch ( const DAVException & 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( + getRequestURI(), + new DAVAuthListener_Impl( + xEnv, m_aURL ), + aHeaders, xEnv ) ); + } + catch ( const DAVException & 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( + getRequestURI(), + new DAVAuthListener_Impl( xEnv, m_aURL ), + aHeaders, xEnv ) ); + } + catch ( const DAVException & 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( + getRequestURI(), + new DAVAuthListener_Impl( + xEnv, m_aURL ), + aHeaders, xEnv ) ); + } + catch ( const DAVException & 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->GET0( getRequestURI(), + rHeaderNames, + rResource, + DAVRequestEnvironment( + getRequestURI(), + new DAVAuthListener_Impl( + xEnv, m_aURL ), + rRequestHeaders, xEnv ) ); + } + catch ( const DAVException & 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( + getRequestURI(), + new DAVAuthListener_Impl( xEnv, m_aURL ), + aHeaders, xEnv ) ); + } + catch ( const DAVException & e ) + { + errorCount++; + bRetry = handleException( e, errorCount ); + if ( !bRetry ) + throw; + } + } + while ( bRetry ); +} + + +void DAVResourceAccess::abort() +{ + initialize(); + m_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( + getRequestURI(), + new DAVAuthListener_Impl( xEnv, m_aURL ), + aHeaders, xEnv ) ); + } + catch ( const DAVException & 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( + getRequestURI(), + new DAVAuthListener_Impl( + xEnv, m_aURL ), + aHeaders, xEnv ) ); + } + catch ( const DAVException & 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( + getRequestURI(), + new DAVAuthListener_Impl( xEnv, m_aURL ), + aHeaders, xEnv ) ); + } + catch ( const DAVException & 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( + getRequestURI(), + new DAVAuthListener_Impl( xEnv, m_aURL ), + aHeaders, xEnv ) ); + } + catch ( const DAVException & 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( + getRequestURI(), + new DAVAuthListener_Impl( xEnv, m_aURL ), + aHeaders, xEnv ), + bOverwrite ); + } + catch ( const DAVException & 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( + getRequestURI(), + new DAVAuthListener_Impl( xEnv, m_aURL ), + aHeaders, xEnv ), + bOverwrite ); + } + catch ( const DAVException & 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( + getRequestURI(), + new DAVAuthListener_Impl( xEnv, m_aURL ), + aHeaders, xEnv ) ); + } + catch ( const DAVException & 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( + getRequestURI(), + new DAVAuthListener_Impl( xEnv, m_aURL ), + aHeaders, xEnv ) ); + } + catch ( const DAVException & 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( + getRequestURI(), + new DAVAuthListener_Impl( xEnv, m_aURL ), + aHeaders, xEnv ) ); + } + catch ( const DAVException & 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() ) + { + NeonUri aURI( m_aURL ); + const OUString& aPath( aURI.GetPath() ); + + /* #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( m_aURL, 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() ) + { + uno::Reference< ucb::XWebDAVCommandEnvironment > xDAVEnv( + xEnv, uno::UNO_QUERY ); + + if ( xDAVEnv.is() ) + { + const uno::Sequence< beans::StringPair > aRequestHeaders + = xDAVEnv->getUserRequestHeaders( rURI, eMethod ); + + for ( const auto& rRequestHeader : aRequestHeaders ) + { + rRequestHeaders.emplace_back( + rRequestHeader.First, + rRequestHeader.Second ); + } + } + } + + // Make sure a User-Agent header is always included, as at least + // en.wikipedia.org:80 forces back 403 "Scripts should use an informative + // User-Agent string with contact information, or they may be IP-blocked + // without notice" otherwise: + if ( std::any_of(rRequestHeaders.begin(), rRequestHeaders.end(), + [](const DAVRequestHeader& rHeader) { return rHeader.first.equalsIgnoreAsciiCase( "User-Agent" ); }) ) + { + return; + } + rRequestHeaders.emplace_back( "User-Agent", "LibreOffice" ); +} + +// This function member implements the control on cyclical redirections +bool DAVResourceAccess::detectRedirectCycle( + const OUString& rRedirectURL ) +{ + osl::Guard< osl::Mutex > aGuard( m_aMutex ); + + NeonUri 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( static_cast< size_t >( 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 NeonUri& rUri) { + // if equal, cyclical redirection detected + return aUri == rUri; }); +} + + +void DAVResourceAccess::resetUri() +{ + osl::Guard< osl::Mutex > aGuard( m_aMutex ); + if ( !m_aRedirectURIs.empty() ) + { + std::vector< NeonUri >::const_iterator it = m_aRedirectURIs.begin(); + + NeonUri aUri( *it ); + m_aRedirectURIs.clear(); + setURL ( aUri.GetURI() ); + initialize(); + } +} + + +bool DAVResourceAccess::handleException( const DAVException & e, int 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; + // #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 + 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 + { + return errorCount < 3; + } + 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-neon/DAVResourceAccess.hxx b/ucb/source/ucp/webdav-neon/DAVResourceAccess.hxx new file mode 100644 index 000000000..30ec5256f --- /dev/null +++ b/ucb/source/ucp/webdav-neon/DAVResourceAccess.hxx @@ -0,0 +1,234 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/************************************************************************* + * + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * Copyright 2000, 2010 Oracle and/or its affiliates. + * + * OpenOffice.org - a multi-platform office productivity suite + * + * This file is part of OpenOffice.org. + * + * OpenOffice.org is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License version 3 + * only, as published by the Free Software Foundation. + * + * OpenOffice.org is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License version 3 for more details + * (a copy is included in the LICENSE file that accompanied this code). + * + * You should have received a copy of the GNU Lesser General Public License + * version 3 along with OpenOffice.org. If not, see + * <http://www.openoffice.org/license.html> + * for a copy of the LGPLv3 License. + * + ************************************************************************/ + +#ifndef INCLUDED_UCB_SOURCE_UCP_WEBDAV_NEON_DAVRESOURCEACCESS_HXX +#define INCLUDED_UCB_SOURCE_UCP_WEBDAV_NEON_DAVRESOURCEACCESS_HXX + +#include <config_lgpl.h> +#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/WebDAVHTTPMethod.hpp> +#include <com/sun/star/ucb/XCommandEnvironment.hpp> +#include "DAVAuthListener.hxx" +#include "DAVException.hxx" +#include "DAVSession.hxx" +#include "DAVResource.hxx" +#include "DAVTypes.hxx" +#include "NeonUri.hxx" + +namespace webdav_ucp +{ + +class DAVSessionFactory; + +class DAVResourceAccess +{ + osl::Mutex m_aMutex; + OUString m_aURL; + OUString m_aPath; + css::uno::Sequence< css::beans::NamedValue > m_aFlags; + rtl::Reference< DAVSession > m_xSession; + rtl::Reference< DAVSessionFactory > m_xSessionFactory; + css::uno::Reference< css::uno::XComponentContext > m_xContext; + std::vector< NeonUri > m_aRedirectURIs; + +public: + DAVResourceAccess( const css::uno::Reference< css::uno::XComponentContext > & rxContext, + rtl::Reference< DAVSessionFactory > const & rSessionFactory, + const OUString & rURL ); + DAVResourceAccess( const DAVResourceAccess & rOther ); + + DAVResourceAccess & operator=( const DAVResourceAccess & rOther ); + + /// @throws DAVException + void setFlags( const css::uno::Sequence< css::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 css::uno::RuntimeException + /// @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 css::uno::RuntimeException + /// @throws DAVException + void + PUT( const css::uno::Reference< css::io::XInputStream > & rStream, + const css::uno::Reference< css::ucb::XCommandEnvironment > & xEnv ); + + /// @throws css::uno::RuntimeException + /// @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 css::uno::RuntimeException + /// @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( const DAVException & e, int errorCount ); + +private: + const OUString & getRequestURI() const; + /// @throws DAVException + bool detectRedirectCycle( const OUString& rRedirectURL ); + /// @throws DAVException + void initialize(); +}; + +} // namespace webdav_ucp + +#endif // INCLUDED_UCB_SOURCE_UCP_WEBDAV_NEON_DAVRESOURCEACCESS_HXX + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/ucb/source/ucp/webdav-neon/DAVSession.hxx b/ucb/source/ucp/webdav-neon/DAVSession.hxx new file mode 100644 index 000000000..72eaf39bd --- /dev/null +++ b/ucb/source/ucp/webdav-neon/DAVSession.hxx @@ -0,0 +1,216 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/************************************************************************* + * + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * Copyright 2000, 2010 Oracle and/or its affiliates. + * + * OpenOffice.org - a multi-platform office productivity suite + * + * This file is part of OpenOffice.org. + * + * OpenOffice.org is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License version 3 + * only, as published by the Free Software Foundation. + * + * OpenOffice.org is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License version 3 for more details + * (a copy is included in the LICENSE file that accompanied this code). + * + * You should have received a copy of the GNU Lesser General Public License + * version 3 along with OpenOffice.org. If not, see + * <http://www.openoffice.org/license.html> + * for a copy of the LGPLv3 License. + * + ************************************************************************/ + +#ifndef INCLUDED_UCB_SOURCE_UCP_WEBDAV_NEON_DAVSESSION_HXX +#define INCLUDED_UCB_SOURCE_UCP_WEBDAV_NEON_DAVSESSION_HXX + +#include <config_lgpl.h> +#include <memory> +#include <rtl/ustring.hxx> +#include <com/sun/star/io/XInputStream.hpp> +#include <com/sun/star/io/XOutputStream.hpp> +#include "DAVProperties.hxx" +#include "DAVResource.hxx" +#include "DAVSessionFactory.hxx" +#include "DAVTypes.hxx" +#include "DAVRequestEnvironment.hxx" + +namespace com::sun::star::beans { + struct NamedValue; +} + +namespace com::sun::star::ucb { + struct Lock; +} + +namespace webdav_ucp +{ + +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 & inPath, + const css::uno::Sequence< css::beans::NamedValue >& rFlags ) = 0; + + virtual bool UsesProxy() = 0; + + // DAV methods + + /// @throws std::exception + virtual void OPTIONS( const OUString & inPath, + DAVOptions& rOptions, + const DAVRequestEnvironment & rEnv ) = 0; + + // allprop & named + /// @throws std::exception + virtual void PROPFIND( const OUString & inPath, + const Depth inDepth, + const std::vector< OUString > & inPropertyNames, + std::vector< DAVResource > & ioResources, + const DAVRequestEnvironment & rEnv ) = 0; + + // propnames + /// @throws std::exception + virtual void PROPFIND( const OUString & inPath, + const Depth inDepth, + std::vector< DAVResourceInfo > & ioResInfo, + const DAVRequestEnvironment & rEnv ) = 0; + + /// @throws std::exception + virtual void PROPPATCH( const OUString & inPath, + const std::vector< ProppatchValue > & inValues, + const DAVRequestEnvironment & rEnv ) = 0; + + /// @throws std::exception + virtual void HEAD( const OUString & inPath, + const std::vector< OUString > & inHeaderNames, + DAVResource & ioResource, + const DAVRequestEnvironment & rEnv ) = 0; + + /// @throws std::exception + virtual css::uno::Reference< css::io::XInputStream > + GET( const OUString & inPath, + const DAVRequestEnvironment & rEnv ) = 0; + + /// @throws std::exception + virtual void GET( const OUString & inPath, + css::uno::Reference< css::io::XOutputStream >& o, + const DAVRequestEnvironment & rEnv ) = 0; + + /// @throws std::exception + virtual css::uno::Reference< css::io::XInputStream > + GET( const OUString & inPath, + const std::vector< OUString > & inHeaderNames, + DAVResource & ioResource, + const DAVRequestEnvironment & rEnv ) = 0; + + // used as HEAD substitute when HEAD is not implemented on server + /// @throws std::exception + virtual void + GET0( const OUString & inPath, + const std::vector< OUString > & inHeaderNames, + DAVResource & ioResource, + const DAVRequestEnvironment & rEnv ) = 0; + + /// @throws std::exception + 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 std::exception + virtual void PUT( const OUString & inPath, + const css::uno::Reference< css::io::XInputStream >& s, + const DAVRequestEnvironment & rEnv ) = 0; + + /// @throws std::exception + 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 std::exception + 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 std::exception + virtual void MKCOL( const OUString & inPath, + const DAVRequestEnvironment & rEnv ) = 0; + + /// @throws std::exception + virtual void COPY( const OUString & inSource, + const OUString & inDestination, + const DAVRequestEnvironment & rEnv, + bool inOverwrite ) = 0; + + /// @throws std::exception + virtual void MOVE( const OUString & inSource, + const OUString & inDestination, + const DAVRequestEnvironment & rEnv, + bool inOverwrite ) = 0; + + /// @throws std::exception + virtual void DESTROY( const OUString & inPath, + const DAVRequestEnvironment & rEnv ) = 0; + + // set new lock. + /// @throws std::exception + virtual void LOCK( const OUString & inPath, + css::ucb::Lock & inLock, + const DAVRequestEnvironment & rEnv ) = 0; + + /// @throws std::exception + virtual void UNLOCK( const OUString & inPath, + const DAVRequestEnvironment & rEnv ) = 0; + + /// @throws std::exception + virtual void abort() = 0; + +protected: + rtl::Reference< DAVSessionFactory > m_xFactory; + + explicit DAVSession( rtl::Reference< DAVSessionFactory > const & rFactory ) + : m_xFactory( rFactory ), m_nRefCount( 0 ) {} + + virtual ~DAVSession() {} + +private: + DAVSessionFactory::Map::iterator m_aContainerIt; + oslInterlockedCount m_nRefCount; + + friend class DAVSessionFactory; + friend struct std::default_delete< DAVSession >; +}; + +} // namespace webdav_ucp + +#endif // INCLUDED_UCB_SOURCE_UCP_WEBDAV_NEON_DAVSESSION_HXX + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/ucb/source/ucp/webdav-neon/DAVSessionFactory.cxx b/ucb/source/ucp/webdav-neon/DAVSessionFactory.cxx new file mode 100644 index 000000000..fd6fca081 --- /dev/null +++ b/ucb/source/ucp/webdav-neon/DAVSessionFactory.cxx @@ -0,0 +1,99 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/************************************************************************* + * + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * Copyright 2000, 2010 Oracle and/or its affiliates. + * + * OpenOffice.org - a multi-platform office productivity suite + * + * This file is part of OpenOffice.org. + * + * OpenOffice.org is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License version 3 + * only, as published by the Free Software Foundation. + * + * OpenOffice.org is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License version 3 for more details + * (a copy is included in the LICENSE file that accompanied this code). + * + * You should have received a copy of the GNU Lesser General Public License + * version 3 along with OpenOffice.org. If not, see + * <http://www.openoffice.org/license.html> + * for a copy of the LGPLv3 License. + * + ************************************************************************/ + +#include <memory> +#include "DAVSessionFactory.hxx" +#include "NeonSession.hxx" +#include "NeonUri.hxx" +#include <osl/diagnose.h> + +using namespace webdav_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 ) +{ + osl::MutexGuard aGuard( m_aMutex ); + + m_xContext = rxContext; + + 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() ) + { + NeonUri aURI( inUri ); + + std::unique_ptr<DAVSession> xElement( + new NeonSession(this, inUri, rFlags, *m_xProxyDecider)); + + aIt = m_aMap.emplace( inUri, xElement.get() ).first; + aIt->second->m_aContainerIt = aIt; + xElement.release(); + 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(); + + // If URL scheme is different from http or https we definitely + // have to use a proxy and therefore can optimize the getProxy + // call a little: + NeonUri aURI( inUri ); + + aIt->second = new NeonSession(this, inUri, rFlags, *m_xProxyDecider); + aIt->second->m_aContainerIt = aIt; + return aIt->second; + } +} + +void DAVSessionFactory::releaseElement( DAVSession const * pElement ) +{ + OSL_ASSERT( pElement ); + osl::MutexGuard 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-neon/DAVSessionFactory.hxx b/ucb/source/ucp/webdav-neon/DAVSessionFactory.hxx new file mode 100644 index 000000000..d74e05aeb --- /dev/null +++ b/ucb/source/ucp/webdav-neon/DAVSessionFactory.hxx @@ -0,0 +1,88 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/************************************************************************* + * + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * Copyright 2000, 2010 Oracle and/or its affiliates. + * + * OpenOffice.org - a multi-platform office productivity suite + * + * This file is part of OpenOffice.org. + * + * OpenOffice.org is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License version 3 + * only, as published by the Free Software Foundation. + * + * OpenOffice.org is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License version 3 for more details + * (a copy is included in the LICENSE file that accompanied this code). + * + * You should have received a copy of the GNU Lesser General Public License + * version 3 along with OpenOffice.org. If not, see + * <http://www.openoffice.org/license.html> + * for a copy of the LGPLv3 License. + * + ************************************************************************/ +#ifndef INCLUDED_UCB_SOURCE_UCP_WEBDAV_NEON_DAVSESSIONFACTORY_HXX +#define INCLUDED_UCB_SOURCE_UCP_WEBDAV_NEON_DAVSESSIONFACTORY_HXX + +#ifdef min +#undef min // GNU libstdc++ <memory> includes <limit> which defines methods called min... +#endif +#include <config_lgpl.h> +#include <map> +#include <memory> +#include <osl/mutex.hxx> +#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 webdav_ucp +{ + +class DAVSession; + +class DAVSessionFactory : public salhelper::SimpleReferenceObject +{ +public: + virtual ~DAVSessionFactory() override; + + /// @throws DAVException + rtl::Reference< DAVSession > + createDAVSession( const OUString & inUri, + const ::uno::Sequence< css::beans::NamedValue >& rFlags, + const ::uno::Reference< ::uno::XComponentContext >& rxContext ); + + const ::uno::Reference< ::uno::XComponentContext >& getComponentContext() const { return m_xContext; } +private: + typedef std::map< OUString, DAVSession * > Map; + + Map m_aMap; + osl::Mutex m_aMutex; + std::unique_ptr< ucbhelper::InternetProxyDecider > m_xProxyDecider; + + ::uno::Reference< ::uno::XComponentContext > m_xContext; + + void releaseElement( DAVSession const * pElement ); + + friend class DAVSession; +}; + +} // namespace webdav_ucp + +#endif // INCLUDED_UCB_SOURCE_UCP_WEBDAV_NEON_DAVSESSIONFACTORY_HXX + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/ucb/source/ucp/webdav-neon/DAVTypes.cxx b/ucb/source/ucp/webdav-neon/DAVTypes.cxx new file mode 100644 index 000000000..6cf4a01b3 --- /dev/null +++ b/ucb/source/ucp/webdav-neon/DAVTypes.cxx @@ -0,0 +1,193 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.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 "DAVTypes.hxx" +#include "../inc/urihelper.hxx" +#include "NeonUri.hxx" + +using namespace webdav_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 ) +{ + osl::MutexGuard aGuard( m_aMutex ); + OUString aEncodedUrl( ucb_impl::urihelper::encodeURI( NeonUri::unescape( rURL ) ) ); + normalizeURLLastChar( aEncodedUrl ); + + // search the URL in the static map + DAVOptionsMap::iterator 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 ) +{ + osl::MutexGuard aGuard( m_aMutex ); + OUString aEncodedUrl( ucb_impl::urihelper::encodeURI( NeonUri::unescape( rURL ) ) ); + normalizeURLLastChar( aEncodedUrl ); + + DAVOptionsMap::iterator it = m_aTheCache.find( aEncodedUrl ); + if ( it != m_aTheCache.end() ) + { + m_aTheCache.erase( it ); + } +} + +void DAVOptionsCache::addDAVOptions( DAVOptions & rDAVOptions, const sal_uInt32 nLifeTime ) +{ + osl::MutexGuard aGuard( m_aMutex ); + OUString aURL( rDAVOptions.getURL() ); + + OUString aEncodedUrl( ucb_impl::urihelper::encodeURI( NeonUri::unescape( 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 = m_aTheCache.find( aEncodedUrl ); + if ( it != m_aTheCache.end() ) + { // already in cache, check LifeTime + if ( (*it).second.getRequestedTimeLife() == nLifeTime ) + return; // same lifetime, do nothing + } + // 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 ) +{ + osl::MutexGuard aGuard( m_aMutex ); + OUString aEncodedUrl( ucb_impl::urihelper::encodeURI( NeonUri::unescape( rURL ) ) ); + normalizeURLLastChar( aEncodedUrl ); + + DAVOptionsMap::iterator 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-neon/DAVTypes.hxx b/ucb/source/ucp/webdav-neon/DAVTypes.hxx new file mode 100644 index 000000000..0f6a34a3d --- /dev/null +++ b/ucb/source/ucp/webdav-neon/DAVTypes.hxx @@ -0,0 +1,211 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/************************************************************************* + * + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * Copyright 2000, 2010 Oracle and/or its affiliates. + * + * OpenOffice.org - a multi-platform office productivity suite + * + * This file is part of OpenOffice.org. + * + * OpenOffice.org is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License version 3 + * only, as published by the Free Software Foundation. + * + * OpenOffice.org is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License version 3 for more details + * (a copy is included in the LICENSE file that accompanied this code). + * + * You should have received a copy of the GNU Lesser General Public License + * version 3 along with OpenOffice.org. If not, see + * <http://www.openoffice.org/license.html> + * for a copy of the LGPLv3 License. + * + ************************************************************************/ + +#ifndef INCLUDED_UCB_SOURCE_UCP_WEBDAV_NEON_DAVTYPES_HXX +#define INCLUDED_UCB_SOURCE_UCP_WEBDAV_NEON_DAVTYPES_HXX + +#include <config_lgpl.h> +#include <map> +#include <osl/mutex.hxx> +#include <rtl/ustring.hxx> +#include <com/sun/star/uno/Any.hxx> + +namespace webdav_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 final + { + 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; + osl::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 operation; + OUString name; + css::uno::Any value; + + ProppatchValue( const ProppatchOperation o, + const OUString & n, + const css::uno::Any & v ) + : operation( o ), name( n ), value( v ) {} + }; +} // namespace webdav_ucp + +#endif // INCLUDED_UCB_SOURCE_UCP_WEBDAV_NEON_DAVTYPES_HXX + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/ucb/source/ucp/webdav-neon/DateTimeHelper.cxx b/ucb/source/ucp/webdav-neon/DateTimeHelper.cxx new file mode 100644 index 000000000..88ca23f16 --- /dev/null +++ b/ucb/source/ucp/webdav-neon/DateTimeHelper.cxx @@ -0,0 +1,247 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/************************************************************************* + * + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * Copyright 2000, 2010 Oracle and/or its affiliates. + * + * OpenOffice.org - a multi-platform office productivity suite + * + * This file is part of OpenOffice.org. + * + * OpenOffice.org is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License version 3 + * only, as published by the Free Software Foundation. + * + * OpenOffice.org is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License version 3 for more details + * (a copy is included in the LICENSE file that accompanied this code). + * + * You should have received a copy of the GNU Lesser General Public License + * version 3 along with OpenOffice.org. If not, see + * <http://www.openoffice.org/license.html> + * for a copy of the LGPLv3 License. + * + ************************************************************************/ + + +#include <osl/time.h> +#include <com/sun/star/util/DateTime.hpp> +#include "DateTimeHelper.hxx" + +using namespace com::sun::star::util; + +using namespace webdav_ucp; + + +bool DateTimeHelper::ISO8601_To_DateTime (const OUString& s, + DateTime& dateTime) +{ + OString aDT (s.getStr(), s.getLength(), 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::convertMonthToInt (const OUString& month) +{ + if (month == "Jan") + return 1; + else if (month == "Feb") + return 2; + else if (month == "Mar") + return 3; + else if (month == "Apr") + return 4; + else if (month == "May") + return 5; + else if (month == "Jun") + return 6; + else if (month == "Jul") + return 7; + else if (month == "Aug") + return 8; + else if (month == "Sep") + return 9; + else if (month == "Oct") + return 10; + else if (month == "Nov") + return 11; + else if (month == "Dec") + return 12; + else + return 0; +} + +bool DateTimeHelper::RFC2068_To_DateTime (const OUString& s, + DateTime& dateTime) +{ + int year; + int day; + int hours; + int minutes; + int seconds; + char string_month[3 + 1]; + char string_day[3 + 1]; + + sal_Int32 found = s.indexOf (','); + if (found != -1) + { + OString aDT (s.getStr(), s.getLength(), 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 (s.getStr(), s.getLength(), 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 != 0; +} + +bool DateTimeHelper::convert (const OUString& 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-neon/DateTimeHelper.hxx b/ucb/source/ucp/webdav-neon/DateTimeHelper.hxx new file mode 100644 index 000000000..5abf6d400 --- /dev/null +++ b/ucb/source/ucp/webdav-neon/DateTimeHelper.hxx @@ -0,0 +1,64 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/************************************************************************* + * + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * Copyright 2000, 2010 Oracle and/or its affiliates. + * + * OpenOffice.org - a multi-platform office productivity suite + * + * This file is part of OpenOffice.org. + * + * OpenOffice.org is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License version 3 + * only, as published by the Free Software Foundation. + * + * OpenOffice.org is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License version 3 for more details + * (a copy is included in the LICENSE file that accompanied this code). + * + * You should have received a copy of the GNU Lesser General Public License + * version 3 along with OpenOffice.org. If not, see + * <http://www.openoffice.org/license.html> + * for a copy of the LGPLv3 License. + * + ************************************************************************/ + +#ifndef INCLUDED_UCB_SOURCE_UCP_WEBDAV_NEON_DATETIMEHELPER_HXX +#define INCLUDED_UCB_SOURCE_UCP_WEBDAV_NEON_DATETIMEHELPER_HXX + +#include <config_lgpl.h> +#include <sal/types.h> +#include <rtl/ustring.hxx> + +namespace com::sun::star::util { + struct DateTime; +} + + +namespace webdav_ucp +{ + +class DateTimeHelper +{ +private: + static sal_Int32 convertMonthToInt (const OUString& ); + + static bool ISO8601_To_DateTime (const OUString&, + css::util::DateTime& ); + + static bool RFC2068_To_DateTime (const OUString&, + css::util::DateTime& ); + +public: + static bool convert (const OUString&, + css::util::DateTime& ); +}; + +} // namespace webdav_ucp + +#endif + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/ucb/source/ucp/webdav-neon/LinkSequence.cxx b/ucb/source/ucp/webdav-neon/LinkSequence.cxx new file mode 100644 index 000000000..ea49352d9 --- /dev/null +++ b/ucb/source/ucp/webdav-neon/LinkSequence.cxx @@ -0,0 +1,214 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/************************************************************************* + * + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * Copyright 2000, 2010 Oracle and/or its affiliates. + * + * OpenOffice.org - a multi-platform office productivity suite + * + * This file is part of OpenOffice.org. + * + * OpenOffice.org is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License version 3 + * only, as published by the Free Software Foundation. + * + * OpenOffice.org is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License version 3 for more details + * (a copy is included in the LICENSE file that accompanied this code). + * + * You should have received a copy of the GNU Lesser General Public License + * version 3 along with OpenOffice.org. If not, see + * <http://www.openoffice.org/license.html> + * for a copy of the LGPLv3 License. + * + ************************************************************************/ + +#include <config_lgpl.h> +#include <string.h> +#include <ne_xml.h> +#include <memory> + +#include "LinkSequence.hxx" + +using namespace webdav_ucp; +using namespace com::sun::star; + +namespace { + +struct LinkSequenceParseContext +{ + std::unique_ptr<ucb::Link> pLink; + bool hasSource; + bool hasDestination; + + LinkSequenceParseContext() + : hasSource( false ), hasDestination( false ) {} +}; + +} + +#define STATE_TOP (1) + +#define STATE_LINK (STATE_TOP) +#define STATE_DST (STATE_TOP + 1) +#define STATE_SRC (STATE_TOP + 2) + + +extern "C" { + +static int LinkSequence_startelement_callback( + void *, + int parent, + const char * /*nspace*/, + const char *name, + const char ** ) +{ + if ( name != nullptr ) + { + switch ( parent ) + { + case NE_XML_STATEROOT: + if ( strcmp( name, "link" ) == 0 ) + return STATE_LINK; + break; + + case STATE_LINK: + if ( strcmp( name, "dst" ) == 0 ) + return STATE_DST; + else if ( strcmp( name, "src" ) == 0 ) + return STATE_SRC; + break; + } + } + return NE_XML_DECLINE; +} + + +static int LinkSequence_chardata_callback( + void *userdata, + int state, + const char *buf, + size_t len ) +{ + LinkSequenceParseContext * pCtx + = static_cast< LinkSequenceParseContext * >( userdata ); + if ( !pCtx->pLink ) + pCtx->pLink.reset( new ucb::Link ); + + switch ( state ) + { + case STATE_DST: + pCtx->pLink->Destination + = OUString( buf, len, RTL_TEXTENCODING_ASCII_US ); + pCtx->hasDestination = true; + break; + + case STATE_SRC: + pCtx->pLink->Source + = OUString( buf, len, RTL_TEXTENCODING_ASCII_US ); + pCtx->hasSource = true; + break; + } + return 0; // zero to continue, non-zero to abort parsing +} + + +static int LinkSequence_endelement_callback( + void *userdata, + int state, + const char *, + const char * ) +{ + LinkSequenceParseContext * pCtx + = static_cast< LinkSequenceParseContext * >( userdata ); + if ( !pCtx->pLink ) + pCtx->pLink.reset( new ucb::Link ); + + switch ( state ) + { + case STATE_LINK: + if ( !pCtx->hasDestination || !pCtx->hasSource ) + return 1; // abort + break; + } + return 0; // zero to continue, non-zero to abort parsing +} + +} + +// static +bool LinkSequence::createFromXML( const OString & rInData, + uno::Sequence< ucb::Link > & rOutData ) +{ + const sal_Int32 TOKEN_LENGTH = 7; // </link> + bool success = true; + + // rInData may contain multiple <link>...</link> tags. + sal_Int32 nCount = 0; + sal_Int32 nStart = 0; + sal_Int32 nEnd = rInData.indexOf( "</link>" ); + while ( nEnd > -1 ) + { + ne_xml_parser * parser = ne_xml_create(); + if ( !parser ) + { + success = false; + break; + } + + LinkSequenceParseContext aCtx; + ne_xml_push_handler( parser, + LinkSequence_startelement_callback, + LinkSequence_chardata_callback, + LinkSequence_endelement_callback, + &aCtx ); + + ne_xml_parse( parser, + rInData.getStr() + nStart, + nEnd - nStart + TOKEN_LENGTH ); + + success = !ne_xml_failed( parser ); + + ne_xml_destroy( parser ); + + if ( !success ) + break; + + if ( aCtx.pLink ) + { + nCount++; + if ( nCount > rOutData.getLength() ) + rOutData.realloc( rOutData.getLength() + 1 ); + + rOutData[ nCount - 1 ] = *aCtx.pLink; + } + + nStart = nEnd + TOKEN_LENGTH; + nEnd = rInData.indexOf( "</link>", nStart ); + } + + return success; +} + + +// static +bool LinkSequence::toXML( const uno::Sequence< ucb::Link > & rInData, + OUString & rOutData ) +{ + // <link><src>value</src><dst>value</dst></link><link><src>... + + for ( const auto& rLink : rInData ) + { + rOutData += "<link><src>"; + rOutData += rLink.Source; + rOutData += "</src><dst>"; + rOutData += rLink.Destination; + rOutData += "</dst></link>"; + } + return rInData.hasElements(); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/ucb/source/ucp/webdav-neon/LinkSequence.hxx b/ucb/source/ucp/webdav-neon/LinkSequence.hxx new file mode 100644 index 000000000..8c88610dd --- /dev/null +++ b/ucb/source/ucp/webdav-neon/LinkSequence.hxx @@ -0,0 +1,52 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/************************************************************************* + * + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * Copyright 2000, 2010 Oracle and/or its affiliates. + * + * OpenOffice.org - a multi-platform office productivity suite + * + * This file is part of OpenOffice.org. + * + * OpenOffice.org is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License version 3 + * only, as published by the Free Software Foundation. + * + * OpenOffice.org is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License version 3 for more details + * (a copy is included in the LICENSE file that accompanied this code). + * + * You should have received a copy of the GNU Lesser General Public License + * version 3 along with OpenOffice.org. If not, see + * <http://www.openoffice.org/license.html> + * for a copy of the LGPLv3 License. + * + ************************************************************************/ + +#ifndef INCLUDED_UCB_SOURCE_UCP_WEBDAV_NEON_LINKSEQUENCE_HXX +#define INCLUDED_UCB_SOURCE_UCP_WEBDAV_NEON_LINKSEQUENCE_HXX + +#include <rtl/string.hxx> +#include <com/sun/star/uno/Sequence.hxx> +#include <com/sun/star/ucb/Link.hpp> + +namespace webdav_ucp +{ + +class LinkSequence +{ +public: + static bool createFromXML( const OString & rInData, + css::uno::Sequence< css::ucb::Link > & rOutData ); + static bool toXML( const css::uno::Sequence< css::ucb::Link > & rInData, + OUString & rOutData ); +}; + +} + +#endif // INCLUDED_UCB_SOURCE_UCP_WEBDAV_NEON_LINKSEQUENCE_HXX + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/ucb/source/ucp/webdav-neon/LockEntrySequence.cxx b/ucb/source/ucp/webdav-neon/LockEntrySequence.cxx new file mode 100644 index 000000000..61be4420c --- /dev/null +++ b/ucb/source/ucp/webdav-neon/LockEntrySequence.cxx @@ -0,0 +1,246 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/************************************************************************* + * + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * Copyright 2000, 2010 Oracle and/or its affiliates. + * + * OpenOffice.org - a multi-platform office productivity suite + * + * This file is part of OpenOffice.org. + * + * OpenOffice.org is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License version 3 + * only, as published by the Free Software Foundation. + * + * OpenOffice.org is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License version 3 for more details + * (a copy is included in the LICENSE file that accompanied this code). + * + * You should have received a copy of the GNU Lesser General Public License + * version 3 along with OpenOffice.org. If not, see + * <http://www.openoffice.org/license.html> + * for a copy of the LGPLv3 License. + * + ************************************************************************/ + +#include <config_lgpl.h> +#include <string.h> +#include <ne_xml.h> +#include "LockEntrySequence.hxx" +#include <memory> + +using namespace webdav_ucp; +using namespace com::sun::star; + +namespace { + +struct LockEntrySequenceParseContext +{ + std::unique_ptr<ucb::LockEntry> pEntry; + bool hasScope; + bool hasType; + + LockEntrySequenceParseContext() + : hasScope( false ), hasType( false ) {} +}; + +} + +#define STATE_TOP (1) + +#define STATE_LOCKENTRY (STATE_TOP) +#define STATE_LOCKSCOPE (STATE_TOP + 1) +#define STATE_EXCLUSIVE (STATE_TOP + 2) +#define STATE_SHARED (STATE_TOP + 3) +#define STATE_LOCKTYPE (STATE_TOP + 4) +#define STATE_WRITE (STATE_TOP + 5) + + +extern "C" { + +static int LockEntrySequence_startelement_callback( + void *, + int parent, + const char * /*nspace*/, + const char *name, + const char ** ) +{ + if ( name != nullptr ) + { + switch ( parent ) + { + case NE_XML_STATEROOT: + if ( strcmp( name, "lockentry" ) == 0 ) + return STATE_LOCKENTRY; + break; + + case STATE_LOCKENTRY: + if ( strcmp( name, "lockscope" ) == 0 ) + return STATE_LOCKSCOPE; + else if ( strcmp( name, "locktype" ) == 0 ) + return STATE_LOCKTYPE; + +#define IIS_BUGS_WORKAROUND + +#ifdef IIS_BUGS_WORKAROUND + /* IIS (6) returns XML violating RFC 4918 + for DAV:supportedlock property value. + + <lockentry> + <write></write> + <shared></shared> + </lockentry> + <lockentry> + <write></write> + <exclusive></exclusive> + </lockentry> + + Bother... + */ + else if ( strcmp( name, "exclusive" ) == 0 ) + return STATE_EXCLUSIVE; + else if ( strcmp( name, "shared" ) == 0 ) + return STATE_SHARED; + else if ( strcmp( name, "write" ) == 0 ) + return STATE_WRITE; +#endif + break; + + case STATE_LOCKSCOPE: + if ( strcmp( name, "exclusive" ) == 0 ) + return STATE_EXCLUSIVE; + else if ( strcmp( name, "shared" ) == 0 ) + return STATE_SHARED; + break; + + case STATE_LOCKTYPE: + if ( strcmp( name, "write" ) == 0 ) + return STATE_WRITE; + break; + } + } + return NE_XML_DECLINE; +} + + +static int LockEntrySequence_chardata_callback( + void *, + int, + const char *, + size_t ) +{ + return 0; // zero to continue, non-zero to abort parsing +} + + +static int LockEntrySequence_endelement_callback( + void *userdata, + int state, + const char *, + const char * ) +{ + LockEntrySequenceParseContext * pCtx + = static_cast< LockEntrySequenceParseContext * >( userdata ); + if ( !pCtx->pEntry ) + pCtx->pEntry.reset( new ucb::LockEntry ); + + switch ( state ) + { + case STATE_EXCLUSIVE: + pCtx->pEntry->Scope = ucb::LockScope_EXCLUSIVE; + pCtx->hasScope = true; + break; + + case STATE_SHARED: + pCtx->pEntry->Scope = ucb::LockScope_SHARED; + pCtx->hasScope = true; + break; + + case STATE_WRITE: + pCtx->pEntry->Type = ucb::LockType_WRITE; + pCtx->hasType = true; + break; + + case STATE_LOCKSCOPE: + if ( !pCtx->hasScope ) + return 1; // abort + break; + + case STATE_LOCKTYPE: + if ( !pCtx->hasType ) + return 1; // abort + break; + + case STATE_LOCKENTRY: + if ( !pCtx->hasType || !pCtx->hasScope ) + return 1; // abort + break; + + default: + break; + } + return 0; // zero to continue, non-zero to abort parsing +} + +} + +// static +bool LockEntrySequence::createFromXML( const OString & rInData, + uno::Sequence< + ucb::LockEntry > & rOutData ) +{ + const sal_Int32 TOKEN_LENGTH = 12; // </lockentry> + bool success = true; + + // rInData may contain multiple <lockentry>...</lockentry> tags. + sal_Int32 nCount = 0; + sal_Int32 nStart = 0; + sal_Int32 nEnd = rInData.indexOf( "</lockentry>" ); + while ( nEnd > -1 ) + { + ne_xml_parser * parser = ne_xml_create(); + if ( !parser ) + { + success = false; + break; + } + + LockEntrySequenceParseContext aCtx; + ne_xml_push_handler( parser, + LockEntrySequence_startelement_callback, + LockEntrySequence_chardata_callback, + LockEntrySequence_endelement_callback, + &aCtx ); + + ne_xml_parse( parser, + rInData.getStr() + nStart, + nEnd - nStart + TOKEN_LENGTH ); + + success = !ne_xml_failed( parser ); + + ne_xml_destroy( parser ); + + if ( !success ) + break; + + if ( aCtx.pEntry ) + { + nCount++; + if ( nCount > rOutData.getLength() ) + rOutData.realloc( rOutData.getLength() + 2 ); + + rOutData[ nCount - 1 ] = *aCtx.pEntry; + } + + nStart = nEnd + TOKEN_LENGTH; + nEnd = rInData.indexOf( "</lockentry>", nStart ); + } + + rOutData.realloc( nCount ); + return success; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/ucb/source/ucp/webdav-neon/LockEntrySequence.hxx b/ucb/source/ucp/webdav-neon/LockEntrySequence.hxx new file mode 100644 index 000000000..87f173203 --- /dev/null +++ b/ucb/source/ucp/webdav-neon/LockEntrySequence.hxx @@ -0,0 +1,50 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/************************************************************************* + * + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * Copyright 2000, 2010 Oracle and/or its affiliates. + * + * OpenOffice.org - a multi-platform office productivity suite + * + * This file is part of OpenOffice.org. + * + * OpenOffice.org is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License version 3 + * only, as published by the Free Software Foundation. + * + * OpenOffice.org is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License version 3 for more details + * (a copy is included in the LICENSE file that accompanied this code). + * + * You should have received a copy of the GNU Lesser General Public License + * version 3 along with OpenOffice.org. If not, see + * <http://www.openoffice.org/license.html> + * for a copy of the LGPLv3 License. + * + ************************************************************************/ + +#ifndef INCLUDED_UCB_SOURCE_UCP_WEBDAV_NEON_LOCKENTRYSEQUENCE_HXX +#define INCLUDED_UCB_SOURCE_UCP_WEBDAV_NEON_LOCKENTRYSEQUENCE_HXX + +#include <rtl/string.hxx> +#include <com/sun/star/uno/Sequence.hxx> +#include <com/sun/star/ucb/LockEntry.hpp> + +namespace webdav_ucp +{ + +class LockEntrySequence +{ +public: + static bool createFromXML( const OString & rInData, + css::uno::Sequence< css::ucb::LockEntry > & rOutData ); +}; + +} + +#endif // INCLUDED_UCB_SOURCE_UCP_WEBDAV_NEON_LOCKENTRYSEQUENCE_HXX + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/ucb/source/ucp/webdav-neon/LockSequence.cxx b/ucb/source/ucp/webdav-neon/LockSequence.cxx new file mode 100644 index 000000000..b9399c60d --- /dev/null +++ b/ucb/source/ucp/webdav-neon/LockSequence.cxx @@ -0,0 +1,360 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/************************************************************************* + * + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * Copyright 2000, 2010 Oracle and/or its affiliates. + * + * OpenOffice.org - a multi-platform office productivity suite + * + * This file is part of OpenOffice.org. + * + * OpenOffice.org is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License version 3 + * only, as published by the Free Software Foundation. + * + * OpenOffice.org is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License version 3 for more details + * (a copy is included in the LICENSE file that accompanied this code). + * + * You should have received a copy of the GNU Lesser General Public License + * version 3 along with OpenOffice.org. If not, see + * <http://www.openoffice.org/license.html> + * for a copy of the LGPLv3 License. + * + ************************************************************************/ + +#include <config_lgpl.h> +#include <string.h> +#include <ne_xml.h> +#include "LockSequence.hxx" +#include <memory> +#include <sal/log.hxx> + +using namespace webdav_ucp; +using namespace com::sun::star; + +namespace { + +struct LockSequenceParseContext +{ + std::unique_ptr<ucb::Lock> pLock; + bool hasLockScope; + bool hasLockType; + bool hasDepth; + bool hasHREF; + bool hasTimeout; + + LockSequenceParseContext() + : hasLockScope( false ), hasLockType( false ), + hasDepth( false ), hasHREF( false ), hasTimeout( false ) {} +}; + +} + +#define STATE_TOP (1) + +#define STATE_ACTIVELOCK (STATE_TOP) +#define STATE_LOCKSCOPE (STATE_TOP + 1) +#define STATE_LOCKTYPE (STATE_TOP + 2) +#define STATE_DEPTH (STATE_TOP + 3) +#define STATE_OWNER (STATE_TOP + 4) +#define STATE_TIMEOUT (STATE_TOP + 5) +#define STATE_LOCKTOKEN (STATE_TOP + 6) +#define STATE_EXCLUSIVE (STATE_TOP + 7) +#define STATE_SHARED (STATE_TOP + 8) +#define STATE_WRITE (STATE_TOP + 9) +#define STATE_HREF (STATE_TOP + 10) + + +extern "C" { + +static int LockSequence_startelement_callback( + void *, + int parent, + const char * /*nspace*/, + const char *name, + const char ** ) +{ + if ( name != nullptr ) + { + switch ( parent ) + { + case NE_XML_STATEROOT: + if ( strcmp( name, "activelock" ) == 0 ) + return STATE_ACTIVELOCK; + break; + + case STATE_ACTIVELOCK: + if ( strcmp( name, "lockscope" ) == 0 ) + return STATE_LOCKSCOPE; + else if ( strcmp( name, "locktype" ) == 0 ) + return STATE_LOCKTYPE; + else if ( strcmp( name, "depth" ) == 0 ) + return STATE_DEPTH; + else if ( strcmp( name, "owner" ) == 0 ) + return STATE_OWNER; + else if ( strcmp( name, "timeout" ) == 0 ) + return STATE_TIMEOUT; + else if ( strcmp( name, "locktoken" ) == 0 ) + return STATE_LOCKTOKEN; + break; + + case STATE_LOCKSCOPE: + if ( strcmp( name, "exclusive" ) == 0 ) + return STATE_EXCLUSIVE; + else if ( strcmp( name, "shared" ) == 0 ) + return STATE_SHARED; + break; + + case STATE_LOCKTYPE: + if ( strcmp( name, "write" ) == 0 ) + return STATE_WRITE; + break; + + case STATE_LOCKTOKEN: + if ( strcmp( name, "href" ) == 0 ) + return STATE_HREF; + break; + + case STATE_OWNER: + // owner elem contains ANY. Accept anything; no state change. + return STATE_OWNER; + } + } + return NE_XML_DECLINE; +} + + +static int LockSequence_chardata_callback( + void *userdata, + int state, + const char *buf, + size_t len ) +{ + LockSequenceParseContext * pCtx + = static_cast< LockSequenceParseContext * >( userdata ); + if ( !pCtx->pLock ) + pCtx->pLock.reset( new ucb::Lock ); + + // Beehive sends XML values containing trailing newlines. + if ( buf[ len - 1 ] == 0x0a ) + len--; + + switch ( state ) + { + case STATE_DEPTH: + if ( rtl_str_compareIgnoreAsciiCase_WithLength( + buf, len, "0", 1 ) == 0 ) + { + pCtx->pLock->Depth = ucb::LockDepth_ZERO; + pCtx->hasDepth = true; + } + else if ( rtl_str_compareIgnoreAsciiCase_WithLength( + buf, len, "1", 1 ) == 0 ) + { + pCtx->pLock->Depth = ucb::LockDepth_ONE; + pCtx->hasDepth = true; + } + else if ( rtl_str_compareIgnoreAsciiCase_WithLength( + buf, len, "infinity", 8 ) == 0 ) + { + pCtx->pLock->Depth = ucb::LockDepth_INFINITY; + pCtx->hasDepth = true; + } + else + SAL_WARN( "ucb.ucp.webdav", "LockSequence_chardata_callback - Unknown depth!" ); + break; + + case STATE_OWNER: + { + // collect raw XML data... (owner contains ANY) + OUString aValue; + pCtx->pLock->Owner >>= aValue; + aValue += OUString( buf, len, RTL_TEXTENCODING_ASCII_US ); + pCtx->pLock->Owner <<= aValue; + break; + } + + case STATE_TIMEOUT: + + // RFC2518, RFC2616: + + // TimeType = ("Second-" DAVTimeOutVal | "Infinite" | Other) + // DAVTimeOutVal = 1*digit + // Other = "Extend" field-value + // field-value = *( field-content | LWS ) + // field-content = <the OCTETs making up the field-value + // and consisting of either *TEXT or combinations + // of token, separators, and quoted-string> + // + // RFC4918, <http://tools.ietf.org/html/rfc4918#section-10.7> + // "The timeout value for TimeType "Second" MUST + // NOT be greater than 2^32-1." + + if ( rtl_str_compareIgnoreAsciiCase_WithLength( + buf, len, "Infinite", 8 ) == 0 ) + { + pCtx->pLock->Timeout = sal_Int64( -1 ); + pCtx->hasTimeout = true; + } + else if ( rtl_str_shortenedCompareIgnoreAsciiCase_WithLength( + buf, len, "Second-", 7, 7 ) == 0 ) + { + pCtx->pLock->Timeout + = OString( buf + 7, len - 7 ).toInt64(); + pCtx->hasTimeout = true; + } +// else if ( rtl_str_shortenedCompareIgnoreCase_WithLength( +// buf, len, "Extend", 6, 6 ) == 0 ) +// { +// @@@ +// } + else + { + pCtx->pLock->Timeout = sal_Int64( -1 ); + pCtx->hasTimeout = true; + SAL_WARN( "ucb.ucp.webdav", "LockSequence_chardata_callback - Unknown timeout!" ); + } + break; + + case STATE_HREF: + { + // collect hrefs. + sal_Int32 nPos = pCtx->pLock->LockTokens.getLength(); + pCtx->pLock->LockTokens.realloc( nPos + 1 ); + pCtx->pLock->LockTokens[ nPos ] + = OUString( buf, len, RTL_TEXTENCODING_ASCII_US ); + pCtx->hasHREF = true; + break; + } + + } + + return 0; // zero to continue, non-zero to abort parsing +} + + +static int LockSequence_endelement_callback( + void *userdata, + int state, + const char *, + const char * ) +{ + LockSequenceParseContext * pCtx + = static_cast< LockSequenceParseContext * >( userdata ); + if ( !pCtx->pLock ) + pCtx->pLock.reset( new ucb::Lock ); + + switch ( state ) + { + case STATE_EXCLUSIVE: + pCtx->pLock->Scope = ucb::LockScope_EXCLUSIVE; + pCtx->hasLockScope = true; + break; + + case STATE_SHARED: + pCtx->pLock->Scope = ucb::LockScope_SHARED; + pCtx->hasLockScope = true; + break; + + case STATE_WRITE: + pCtx->pLock->Type = ucb::LockType_WRITE; + pCtx->hasLockType = true; + break; + + case STATE_DEPTH: + if ( !pCtx->hasDepth ) + return 1; // abort + break; + + case STATE_HREF: + if ( !pCtx->hasHREF ) + return 1; // abort + break; + + case STATE_TIMEOUT: + if ( !pCtx->hasTimeout ) + return 1; // abort + break; + + case STATE_LOCKSCOPE: + if ( !pCtx->hasLockScope ) + return 1; // abort + break; + + case STATE_LOCKTYPE: + if ( !pCtx->hasLockType ) + return 1; // abort + break; + + case STATE_ACTIVELOCK: + if ( !pCtx->hasLockType || !pCtx->hasDepth ) + return 1; // abort + break; + + default: + break; + } + return 0; // zero to continue, non-zero to abort parsing +} + +} + +// static +bool LockSequence::createFromXML( const OString & rInData, + uno::Sequence< ucb::Lock > & rOutData ) +{ + const sal_Int32 TOKEN_LENGTH = 13; // </activelock> + bool success = true; + + // rInData may contain multiple <activelock>...</activelock> tags. + sal_Int32 nCount = 0; + sal_Int32 nStart = 0; + sal_Int32 nEnd = rInData.indexOf( "</activelock>" ); + while ( nEnd > -1 ) + { + ne_xml_parser * parser = ne_xml_create(); + if ( !parser ) + { + success = false; + break; + } + + LockSequenceParseContext aCtx; + ne_xml_push_handler( parser, + LockSequence_startelement_callback, + LockSequence_chardata_callback, + LockSequence_endelement_callback, + &aCtx ); + + ne_xml_parse( parser, + rInData.getStr() + nStart, + nEnd - nStart + TOKEN_LENGTH ); + + success = !ne_xml_failed( parser ); + + ne_xml_destroy( parser ); + + if ( !success ) + break; + + if ( aCtx.pLock ) + { + nCount++; + if ( nCount > rOutData.getLength() ) + rOutData.realloc( rOutData.getLength() + 1 ); + + rOutData[ nCount - 1 ] = *aCtx.pLock; + } + + nStart = nEnd + TOKEN_LENGTH; + nEnd = rInData.indexOf( "</activelock>", nStart ); + } + + return success; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/ucb/source/ucp/webdav-neon/LockSequence.hxx b/ucb/source/ucp/webdav-neon/LockSequence.hxx new file mode 100644 index 000000000..a35ddee99 --- /dev/null +++ b/ucb/source/ucp/webdav-neon/LockSequence.hxx @@ -0,0 +1,51 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/************************************************************************* + * + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * Copyright 2000, 2010 Oracle and/or its affiliates. + * + * OpenOffice.org - a multi-platform office productivity suite + * + * This file is part of OpenOffice.org. + * + * OpenOffice.org is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License version 3 + * only, as published by the Free Software Foundation. + * + * OpenOffice.org is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License version 3 for more details + * (a copy is included in the LICENSE file that accompanied this code). + * + * You should have received a copy of the GNU Lesser General Public License + * version 3 along with OpenOffice.org. If not, see + * <http://www.openoffice.org/license.html> + * for a copy of the LGPLv3 License. + * + ************************************************************************/ + +#ifndef INCLUDED_UCB_SOURCE_UCP_WEBDAV_NEON_LOCKSEQUENCE_HXX +#define INCLUDED_UCB_SOURCE_UCP_WEBDAV_NEON_LOCKSEQUENCE_HXX + +#include <config_lgpl.h> +#include <rtl/string.hxx> +#include <com/sun/star/uno/Sequence.hxx> +#include <com/sun/star/ucb/Lock.hpp> + +namespace webdav_ucp +{ + +class LockSequence +{ +public: + static bool createFromXML( const OString & rInData, + css::uno::Sequence< css::ucb::Lock > & rOutData ); +}; + +} + +#endif // INCLUDED_UCB_SOURCE_UCP_WEBDAV_NEON_LOCKSEQUENCE_HXX + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/ucb/source/ucp/webdav-neon/NeonHeadRequest.cxx b/ucb/source/ucp/webdav-neon/NeonHeadRequest.cxx new file mode 100644 index 000000000..68f24f440 --- /dev/null +++ b/ucb/source/ucp/webdav-neon/NeonHeadRequest.cxx @@ -0,0 +1,139 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/************************************************************************* + * + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * Copyright 2000, 2010 Oracle and/or its affiliates. + * + * OpenOffice.org - a multi-platform office productivity suite + * + * This file is part of OpenOffice.org. + * + * OpenOffice.org is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License version 3 + * only, as published by the Free Software Foundation. + * + * OpenOffice.org is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License version 3 for more details + * (a copy is included in the LICENSE file that accompanied this code). + * + * You should have received a copy of the GNU Lesser General Public License + * version 3 along with OpenOffice.org. If not, see + * <http://www.openoffice.org/license.html> + * for a copy of the LGPLv3 License. + * + ************************************************************************/ + +#include <osl/mutex.hxx> +#include <sal/log.hxx> +#include "NeonHeadRequest.hxx" +#include "NeonSession.hxx" + +using namespace webdav_ucp; +using namespace com::sun::star; + +namespace { + +void process_headers( ne_request * req, + DAVResource & rResource, + const std::vector< OUString > & rHeaderNames ) +{ + void * cursor = nullptr; + const char * name, *value; + +#if defined SAL_LOG_INFO + { + for ( const auto& rHeader : rHeaderNames ) + { + SAL_INFO( "ucb.ucp.webdav", "HEAD - requested header: " << rHeader ); + } + } +#endif + while ( ( cursor = ne_response_header_iterate( req, cursor, + &name, &value ) ) != nullptr ) { + // The HTTP header `field-name` must be a `token`, which can only contain a subset of ASCII; + // assume that Neon will already have rejected any invalid data, so that it is guaranteed + // that `name` is ASCII-only: + OUString aHeaderName( OUString::createFromAscii( name ) ); + // The HTTP header `field-value` may contain obsolete (as per RFC 7230) `obs-text` non-ASCII + // %x80-FF octets, lets preserve them as individual characters in `aHeaderValue` by treating + // `value` as ISO 8859-1: + OUString aHeaderValue(value, strlen(value), RTL_TEXTENCODING_ISO_8859_1); + + SAL_INFO( "ucb.ucp.webdav", "HEAD - received header: " << aHeaderName << ":" << aHeaderValue); + + // Note: Empty vector means that all headers are requested. + bool bIncludeIt = rHeaderNames.empty(); + + if ( !bIncludeIt ) + { + // Check whether this header was requested. + auto it = std::find_if(rHeaderNames.begin(), rHeaderNames.end(), + [&aHeaderName](const OUString& rName) { + // header names are case insensitive + return rName.equalsIgnoreAsciiCase( aHeaderName ); + }); + + if ( it != rHeaderNames.end() ) + { + aHeaderName = *it; + bIncludeIt = true; + } + } + + if ( bIncludeIt ) + { + // Create & set the PropertyValue + DAVPropertyValue thePropertyValue; + // header names are case insensitive, so are the + // corresponding property names + thePropertyValue.Name = aHeaderName.toAsciiLowerCase(); + thePropertyValue.IsCaseSensitive = false; + thePropertyValue.Value <<= aHeaderValue; + + // Add the newly created PropertyValue + rResource.properties.push_back( thePropertyValue ); + } + } +} + +} // namespace + +NeonHeadRequest::NeonHeadRequest( HttpSession * inSession, + const OUString & inPath, + const std::vector< OUString > & + inHeaderNames, + DAVResource & ioResource, + int & nError ) +{ + ioResource.uri = inPath; + ioResource.properties.clear(); + + // Create and dispatch HEAD request. Install catcher for all response + // header fields. + ne_request * req = ne_request_create( inSession, + "HEAD", + OUStringToOString( + inPath, + RTL_TEXTENCODING_UTF8 ).getStr() ); + + { + osl::Guard< osl::Mutex > theGlobalGuard(getGlobalNeonMutex()); + nError = ne_request_dispatch( req ); + } + + process_headers( req, ioResource, inHeaderNames ); + + if ( nError == NE_OK && ne_get_status( req )->klass != 2 ) + nError = NE_ERROR; + + ne_request_destroy( req ); +} + +NeonHeadRequest::~NeonHeadRequest() +{ +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/ucb/source/ucp/webdav-neon/NeonHeadRequest.hxx b/ucb/source/ucp/webdav-neon/NeonHeadRequest.hxx new file mode 100644 index 000000000..06a28af03 --- /dev/null +++ b/ucb/source/ucp/webdav-neon/NeonHeadRequest.hxx @@ -0,0 +1,56 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/************************************************************************* + * + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * Copyright 2000, 2010 Oracle and/or its affiliates. + * + * OpenOffice.org - a multi-platform office productivity suite + * + * This file is part of OpenOffice.org. + * + * OpenOffice.org is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License version 3 + * only, as published by the Free Software Foundation. + * + * OpenOffice.org is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License version 3 for more details + * (a copy is included in the LICENSE file that accompanied this code). + * + * You should have received a copy of the GNU Lesser General Public License + * version 3 along with OpenOffice.org. If not, see + * <http://www.openoffice.org/license.html> + * for a copy of the LGPLv3 License. + * + ************************************************************************/ + +#ifndef INCLUDED_UCB_SOURCE_UCP_WEBDAV_NEON_NEONHEADREQUEST_HXX +#define INCLUDED_UCB_SOURCE_UCP_WEBDAV_NEON_NEONHEADREQUEST_HXX + +#include <config_lgpl.h> +#include <vector> +#include "NeonTypes.hxx" +#include "DAVResource.hxx" + +namespace webdav_ucp +{ + +class NeonHeadRequest +{ +public: + // named / allprop + NeonHeadRequest( HttpSession* inSession, + const OUString & inPath, + const std::vector< OUString > & inHeaderNames, + DAVResource & ioResource, + int & nError ); + ~NeonHeadRequest(); +}; + +} // namespace webdav_ucp + +#endif // INCLUDED_UCB_SOURCE_UCP_WEBDAV_NEON_NEONHEADREQUEST_HXX + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/ucb/source/ucp/webdav-neon/NeonInputStream.cxx b/ucb/source/ucp/webdav-neon/NeonInputStream.cxx new file mode 100644 index 000000000..848dc2b81 --- /dev/null +++ b/ucb/source/ucp/webdav-neon/NeonInputStream.cxx @@ -0,0 +1,136 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/************************************************************************* + * + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * Copyright 2000, 2010 Oracle and/or its affiliates. + * + * OpenOffice.org - a multi-platform office productivity suite + * + * This file is part of OpenOffice.org. + * + * OpenOffice.org is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License version 3 + * only, as published by the Free Software Foundation. + * + * OpenOffice.org is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License version 3 for more details + * (a copy is included in the LICENSE file that accompanied this code). + * + * You should have received a copy of the GNU Lesser General Public License + * version 3 along with OpenOffice.org. If not, see + * <http://www.openoffice.org/license.html> + * for a copy of the LGPLv3 License. + * + ************************************************************************/ + +#include "NeonInputStream.hxx" + +#include <com/sun/star/lang/IllegalArgumentException.hpp> +#include <cppuhelper/queryinterface.hxx> + +#include <string.h> + +using namespace cppu; +using namespace com::sun::star::io; +using namespace com::sun::star::uno; +using namespace webdav_ucp; + +NeonInputStream::NeonInputStream() +: mLen( 0 ), + mPos( 0 ) +{ +} + +NeonInputStream::~NeonInputStream() +{ +} + +// Allows the caller to add some data to the "end" of the stream +void NeonInputStream::AddToStream( const char * inBuf, sal_Int32 inLen ) +{ + mInputBuffer.realloc( sal::static_int_cast<sal_Int32>(mLen) + inLen ); + memcpy( mInputBuffer.getArray() + mLen, inBuf, inLen ); + mLen += inLen; +} + +Any NeonInputStream::queryInterface( const Type &type ) +{ + Any aRet = ::cppu::queryInterface( type, + static_cast< XInputStream * >( this ), + static_cast< XSeekable * >( this ) ); + return aRet.hasValue() ? aRet : OWeakObject::queryInterface( type ); +} + +// "Reads" the specified number of bytes from the stream +sal_Int32 SAL_CALL NeonInputStream::readBytes( + css::uno::Sequence< sal_Int8 >& aData, sal_Int32 nBytesToRead ) +{ + // Work out how much we're actually going to write + sal_Int32 theBytes2Read = nBytesToRead; + sal_Int32 theBytesLeft = sal::static_int_cast<sal_Int32>(mLen - mPos); + if ( theBytes2Read > theBytesLeft ) + theBytes2Read = theBytesLeft; + + // Realloc buffer. + aData.realloc( theBytes2Read ); + + // Write the data + memcpy( + aData.getArray(), mInputBuffer.getConstArray() + mPos, theBytes2Read ); + + // Update our stream position for next time + mPos += theBytes2Read; + + return theBytes2Read; +} + +sal_Int32 SAL_CALL NeonInputStream::readSomeBytes( + css::uno::Sequence< sal_Int8 >& aData, sal_Int32 nMaxBytesToRead ) +{ + // Warning: What should this be doing ? + return readBytes( aData, nMaxBytesToRead ); +} + +// Moves the current stream position forward +void SAL_CALL NeonInputStream::skipBytes( sal_Int32 nBytesToSkip ) +{ + mPos += nBytesToSkip; + if ( mPos >= mLen ) + mPos = mLen; +} + +// Returns the number of unread bytes currently remaining on the stream +sal_Int32 SAL_CALL NeonInputStream::available( ) +{ + return std::min<sal_Int64>(SAL_MAX_INT32, mLen - mPos); +} + +void SAL_CALL NeonInputStream::closeInput() +{ +} + +void SAL_CALL NeonInputStream::seek( sal_Int64 location ) +{ + if ( location < 0 ) + throw css::lang::IllegalArgumentException(); + + if ( location > mLen ) + throw css::lang::IllegalArgumentException(); + + mPos = location; +} + +sal_Int64 SAL_CALL NeonInputStream::getPosition() +{ + return mPos; +} + +sal_Int64 SAL_CALL NeonInputStream::getLength() +{ + return mLen; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/ucb/source/ucp/webdav-neon/NeonInputStream.hxx b/ucb/source/ucp/webdav-neon/NeonInputStream.hxx new file mode 100644 index 000000000..e496d72bd --- /dev/null +++ b/ucb/source/ucp/webdav-neon/NeonInputStream.hxx @@ -0,0 +1,97 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/************************************************************************* + * + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * Copyright 2000, 2010 Oracle and/or its affiliates. + * + * OpenOffice.org - a multi-platform office productivity suite + * + * This file is part of OpenOffice.org. + * + * OpenOffice.org is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License version 3 + * only, as published by the Free Software Foundation. + * + * OpenOffice.org is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License version 3 for more details + * (a copy is included in the LICENSE file that accompanied this code). + * + * You should have received a copy of the GNU Lesser General Public License + * version 3 along with OpenOffice.org. If not, see + * <http://www.openoffice.org/license.html> + * for a copy of the LGPLv3 License. + * + ************************************************************************/ +#ifndef INCLUDED_UCB_SOURCE_UCP_WEBDAV_NEON_NEONINPUTSTREAM_HXX +#define INCLUDED_UCB_SOURCE_UCP_WEBDAV_NEON_NEONINPUTSTREAM_HXX + +#include <config_lgpl.h> +#include <sal/types.h> +#include <cppuhelper/weak.hxx> +#include <com/sun/star/io/XInputStream.hpp> +#include <com/sun/star/io/XSeekable.hpp> + + +namespace webdav_ucp +{ + +// A simple XInputStream implementation provided specifically for use +// by the DAVSession::GET method. +class NeonInputStream : public css::io::XInputStream, + public css::io::XSeekable, + public ::cppu::OWeakObject +{ + private: + css::uno::Sequence< sal_Int8 > mInputBuffer; + sal_Int64 mLen; + sal_Int64 mPos; + + public: + NeonInputStream(); + virtual ~NeonInputStream() override; + + // Add some data to the end of the stream + void AddToStream( const char * inBuf, sal_Int32 inLen ); + + // XInterface + virtual css::uno::Any SAL_CALL queryInterface( const css::uno::Type & type ) override; + + virtual void SAL_CALL acquire() + throw () override + { OWeakObject::acquire(); } + + virtual void SAL_CALL release() + throw() override + { OWeakObject::release(); } + + + // 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; + + // XSeekable + virtual void SAL_CALL seek( sal_Int64 location ) override; + + virtual sal_Int64 SAL_CALL getPosition() override; + + virtual sal_Int64 SAL_CALL getLength() override; +}; + +} // namespace webdav_ucp +#endif // INCLUDED_UCB_SOURCE_UCP_WEBDAV_NEON_NEONINPUTSTREAM_HXX + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/ucb/source/ucp/webdav-neon/NeonLockStore.cxx b/ucb/source/ucp/webdav-neon/NeonLockStore.cxx new file mode 100644 index 000000000..a4d25b4b1 --- /dev/null +++ b/ucb/source/ucp/webdav-neon/NeonLockStore.cxx @@ -0,0 +1,241 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/************************************************************************* + * + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * Copyright 2000, 2010 Oracle and/or its affiliates. + * + * OpenOffice.org - a multi-platform office productivity suite + * + * This file is part of OpenOffice.org. + * + * OpenOffice.org is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License version 3 + * only, as published by the Free Software Foundation. + * + * OpenOffice.org is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License version 3 for more details + * (a copy is included in the LICENSE file that accompanied this code). + * + * You should have received a copy of the GNU Lesser General Public License + * version 3 along with OpenOffice.org. If not, see + * <http://www.openoffice.org/license.html> + * for a copy of the LGPLv3 License. + * + ************************************************************************/ + + +#include <ne_uri.h> +#include <rtl/ustring.hxx> +#include <osl/time.h> +#include <osl/thread.hxx> +#include <sal/log.hxx> +#include <salhelper/thread.hxx> +#include "NeonSession.hxx" +#include "NeonLockStore.hxx" + +using namespace webdav_ucp; + +namespace webdav_ucp { + +class TickerThread : public salhelper::Thread +{ + bool m_bFinish; + NeonLockStore & m_rLockStore; + +public: + + explicit TickerThread( NeonLockStore & rLockStore ) + : Thread( "NeonTickerThread" ), m_bFinish( false ) + , m_rLockStore( rLockStore ) + { + } + + void finish() { m_bFinish = true; } + +private: + + virtual void execute() override; +}; + +} // namespace webdav_ucp + +void TickerThread::execute() +{ + 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; + } + + salhelper::Thread::wait(TimeValue(0, 1000000000 / nNth)); + } + + SAL_INFO( "ucb.ucp.webdav", "TickerThread: stop." ); +} + +NeonLockStore::NeonLockStore() + : m_pNeonLockStore( ne_lockstore_create() ) +{ + /* + * ne_lockstore_create() never returns a NULL; neon calls abort() in case of an out-of-memory + * situation. + * Please see: + * <http://www.webdav.org/neon/doc/html/refneon.html> + * topic title "Memory handling", copied here verbatim: + * + * "neon does not attempt to cope gracefully with an out-of-memory situation; + * instead, by default, the abort function is called to immediately terminate + * the process. An application may register a custom function which will be + * called before abort in such a situation; see ne_oom_callback." + */ +} + +NeonLockStore::~NeonLockStore() +{ + { + osl::ClearableMutexGuard aGuard(m_aMutex); + stopTicker(aGuard); + } // actually no threads should even try to access members now + + // release active locks, if any. + SAL_WARN_IF( !m_aLockInfoMap.empty(), "ucb.ucp.webdav", "NeonLockStore::~NeonLockStore - Releasing active locks!" ); + + for ( auto& rLockInfo : m_aLockInfoMap ) + { + NeonLock * pLock = rLockInfo.first; + rLockInfo.second.xSession->UNLOCK( pLock ); + + ne_lockstore_remove( m_pNeonLockStore, pLock ); + ne_lock_destroy( pLock ); + } + + ne_lockstore_destroy( m_pNeonLockStore ); +} + +void NeonLockStore::startTicker() +{ + osl::MutexGuard aGuard( m_aMutex ); + + if ( !m_pTickerThread.is() ) + { + m_pTickerThread = new TickerThread( *this ); + m_pTickerThread->launch(); + } +} + +void NeonLockStore::stopTicker(osl::ClearableMutexGuard & 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.clear(); + + if (pTickerThread.is() && pTickerThread->getIdentifier() != osl::Thread::getCurrentIdentifier()) + pTickerThread->join(); // without m_aMutex locked (to prevent deadlock) +} + +void NeonLockStore::registerSession( HttpSession * pHttpSession ) +{ + osl::MutexGuard aGuard( m_aMutex ); + + ne_lockstore_register( m_pNeonLockStore, pHttpSession ); +} + +NeonLock * NeonLockStore::findByUri( OUString const & rUri ) +{ + osl::MutexGuard aGuard( m_aMutex ); + + ne_uri aUri; + ne_uri_parse( OUStringToOString( + rUri, RTL_TEXTENCODING_UTF8 ).getStr(), &aUri ); + return ne_lockstore_findbyuri( m_pNeonLockStore, &aUri ); +} + +void NeonLockStore::addLock( NeonLock * pLock, + rtl::Reference< NeonSession > const & xSession, + sal_Int32 nLastChanceToSendRefreshRequest ) +{ + osl::MutexGuard aGuard( m_aMutex ); + + ne_lockstore_add( m_pNeonLockStore, pLock ); + m_aLockInfoMap[ pLock ] + = LockInfo( xSession, nLastChanceToSendRefreshRequest ); + + startTicker(); +} + +void NeonLockStore::removeLock( NeonLock * pLock ) +{ + osl::ClearableMutexGuard aGuard( m_aMutex ); + + m_aLockInfoMap.erase( pLock ); + ne_lockstore_remove( m_pNeonLockStore, pLock ); + + if ( m_aLockInfoMap.empty() ) + stopTicker(aGuard); +} + +void NeonLockStore::removeLockDeferred(NeonLock* pLock) +{ + osl::MutexGuard aGuard(m_aMutex); + + m_aRemoveDeferred.push_back(pLock); +} + +void NeonLockStore::refreshLocks() +{ + osl::MutexGuard aGuard( m_aMutex ); + + for ( auto& rLockInfo : m_aLockInfoMap ) + { + LockInfo & rInfo = rLockInfo.second; + if ( rInfo.nLastChanceToSendRefreshRequest != -1 ) + { + // 30 seconds or less remaining until lock expires? + TimeValue t1; + osl_getSystemTime( &t1 ); + if ( rInfo.nLastChanceToSendRefreshRequest - 30 + <= sal_Int32( t1.Seconds ) ) + { + // refresh the lock. + sal_Int32 nlastChanceToSendRefreshRequest = -1; + if ( rInfo.xSession->LOCK( + rLockInfo.first, + /* out param */ nlastChanceToSendRefreshRequest ) ) + { + rInfo.nLastChanceToSendRefreshRequest + = nlastChanceToSendRefreshRequest; + } + else + { + // refresh failed. stop auto-refresh. + rInfo.nLastChanceToSendRefreshRequest = -1; + } + } + } + } + // removeLock will not need to actually release the lock, because this is run from TickerThread + for (auto pLock : m_aRemoveDeferred) + removeLock(pLock); + m_aRemoveDeferred.clear(); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/ucb/source/ucp/webdav-neon/NeonLockStore.hxx b/ucb/source/ucp/webdav-neon/NeonLockStore.hxx new file mode 100644 index 000000000..fa64cb320 --- /dev/null +++ b/ucb/source/ucp/webdav-neon/NeonLockStore.hxx @@ -0,0 +1,99 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/************************************************************************* + * + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * Copyright 2000, 2010 Oracle and/or its affiliates. + * + * OpenOffice.org - a multi-platform office productivity suite + * + * This file is part of OpenOffice.org. + * + * OpenOffice.org is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License version 3 + * only, as published by the Free Software Foundation. + * + * OpenOffice.org is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License version 3 for more details + * (a copy is included in the LICENSE file that accompanied this code). + * + * You should have received a copy of the GNU Lesser General Public License + * version 3 along with OpenOffice.org. If not, see + * <http://www.openoffice.org/license.html> + * for a copy of the LGPLv3 License. + * + ************************************************************************/ +#ifndef INCLUDED_UCB_SOURCE_UCP_WEBDAV_NEON_NEONLOCKSTORE_HXX +#define INCLUDED_UCB_SOURCE_UCP_WEBDAV_NEON_NEONLOCKSTORE_HXX + +#include <config_lgpl.h> +#include <map> + +#include <ne_locks.h> + +#include <osl/mutex.hxx> +#include <rtl/ref.hxx> +#include "NeonTypes.hxx" + +namespace webdav_ucp +{ + +class TickerThread; +class NeonSession; + +struct LockInfo +{ + rtl::Reference< NeonSession > xSession; + sal_Int32 nLastChanceToSendRefreshRequest; + + LockInfo() + : nLastChanceToSendRefreshRequest( -1 ) {} + + LockInfo( rtl::Reference< NeonSession > const & _xSession, + sal_Int32 _nLastChanceToSendRefreshRequest ) + : xSession( _xSession ), + nLastChanceToSendRefreshRequest( _nLastChanceToSendRefreshRequest ) {} + +}; + +typedef std::map< NeonLock *, LockInfo > LockInfoMap; + +class NeonLockStore +{ + osl::Mutex m_aMutex; + ne_lock_store * m_pNeonLockStore; + rtl::Reference< TickerThread > m_pTickerThread; + LockInfoMap m_aLockInfoMap; + std::vector<NeonLock*> m_aRemoveDeferred; + +public: + NeonLockStore(); + ~NeonLockStore(); + + void registerSession( HttpSession * pHttpSession ); + + NeonLock * findByUri( OUString const & rUri ); + + void addLock( NeonLock * pLock, + rtl::Reference< NeonSession > const & xSession, + // time in seconds since Jan 1 1970 + // -1: infinite lock, no refresh + sal_Int32 nLastChanceToSendRefreshRequest ); + + void removeLock( NeonLock * pLock ); + void removeLockDeferred(NeonLock* pLock); + + void refreshLocks(); + +private: + void startTicker(); + void stopTicker(osl::ClearableMutexGuard & rGuard); +}; + +} // namespace webdav_ucp + +#endif // INCLUDED_UCB_SOURCE_UCP_WEBDAV_NEON_NEONLOCKSTORE_HXX + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/ucb/source/ucp/webdav-neon/NeonPropFindRequest.cxx b/ucb/source/ucp/webdav-neon/NeonPropFindRequest.cxx new file mode 100644 index 000000000..cf03274c5 --- /dev/null +++ b/ucb/source/ucp/webdav-neon/NeonPropFindRequest.cxx @@ -0,0 +1,321 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/************************************************************************* + * + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * Copyright 2000, 2010 Oracle and/or its affiliates. + * + * OpenOffice.org - a multi-platform office productivity suite + * + * This file is part of OpenOffice.org. + * + * OpenOffice.org is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License version 3 + * only, as published by the Free Software Foundation. + * + * OpenOffice.org is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License version 3 for more details + * (a copy is included in the LICENSE file that accompanied this code). + * + * You should have received a copy of the GNU Lesser General Public License + * version 3 along with OpenOffice.org. If not, see + * <http://www.openoffice.org/license.html> + * for a copy of the LGPLv3 License. + * + ************************************************************************/ + + +#include <osl/mutex.hxx> +#include <rtl/strbuf.hxx> +#include <sal/log.hxx> +#include "NeonSession.hxx" +#include "NeonTypes.hxx" +#include "DAVProperties.hxx" +#include "NeonPropFindRequest.hxx" +#include "LinkSequence.hxx" +#include "LockSequence.hxx" +#include "LockEntrySequence.hxx" +#include "UCBDeadPropertyValue.hxx" +#include <memory> + +using namespace com::sun::star::uno; +using namespace com::sun::star::ucb; +using namespace std; +using namespace webdav_ucp; + + +namespace +{ + // strip "DAV:" namespace from XML snippets to avoid + // parser error (undeclared namespace) later on. + OString stripDavNamespace( const OString & in ) + { + const OString inXML( in.toAsciiLowerCase() ); + + OStringBuffer buf; + sal_Int32 start = 0; + sal_Int32 end = inXML.indexOf( "dav:" ); + while ( end != -1 ) + { + if ( inXML[ end - 1 ] == '<' || + inXML[ end - 1 ] == '/' ) + { + // copy from original buffer - preserve case. + buf.append( in.copy( start, end - start ) ); + } + else + { + // copy from original buffer - preserve case. + buf.append( in.copy( start, end - start + 4 ) ); + } + start = end + 4; + end = inXML.indexOf( "dav:", start ); + } + buf.append( inXML.copy( start ) ); + + return buf.makeStringAndClear(); + } +} + +extern "C" { + +static int NPFR_propfind_iter( void* userdata, + const NeonPropName* pname, + const char* value, + const HttpStatus* status ) +{ + /* + HTTP Response Status Classes: + + - 1: Informational - Request received, continuing process + + - 2: Success - The action was successfully received, + understood, and accepted + + - 3: Redirection - Further action must be taken in order to + complete the request + + - 4: Client Error - The request contains bad syntax or cannot + be fulfilled + + - 5: Server Error - The server failed to fulfill an apparently + valid request + */ + + if ( status->klass > 2 ) + return 0; // Error getting this property. Go on. + + // Create & set the PropertyValue + DAVPropertyValue thePropertyValue; + thePropertyValue.IsCaseSensitive = true; + + SAL_WARN_IF( !pname->nspace, "ucb.ucp.webdav", "NPFR_propfind_iter - No XML namespace!" ); + + DAVProperties::createUCBPropName( pname->nspace, + pname->name, + thePropertyValue.Name ); + bool bHasValue = false; + if ( DAVProperties::isUCBDeadProperty( *pname ) ) + { + // DAV dead property added by WebDAV UCP? + if ( UCBDeadPropertyValue::createFromXML( + value, thePropertyValue.Value ) ) + { + SAL_WARN_IF( !thePropertyValue.Value.hasValue(), + "ucb.ucp.webdav", "NPFR_propfind_iter - No value for UCBDeadProperty!" ); + bHasValue = true; + } + } + + if ( !bHasValue ) + { + if ( rtl_str_compareIgnoreAsciiCase( + pname->name, "resourcetype" ) == 0 ) + { + OString aValue( value ); + aValue = aValue.trim(); // #107358# remove leading/trailing spaces + if ( !aValue.isEmpty() ) + { + aValue = stripDavNamespace( aValue ).toAsciiLowerCase(); + if ( aValue.startsWith("<collection") ) + { + thePropertyValue.Value + <<= OUString("collection"); + } + } + + if ( !thePropertyValue.Value.hasValue() ) + { + // Take over the value exactly as supplied by the server. + thePropertyValue.Value <<= OUString::createFromAscii( value ); + } + } + else if ( rtl_str_compareIgnoreAsciiCase( + pname->name, "supportedlock" ) == 0 ) + { + Sequence< LockEntry > aEntries; + LockEntrySequence::createFromXML( + stripDavNamespace( value ), aEntries ); + thePropertyValue.Value <<= aEntries; + } + else if ( rtl_str_compareIgnoreAsciiCase( + pname->name, "lockdiscovery" ) == 0 ) + { + Sequence< Lock > aLocks; + LockSequence::createFromXML( + stripDavNamespace( value ), aLocks ); + thePropertyValue.Value <<= aLocks; + } + else if ( rtl_str_compareIgnoreAsciiCase( pname->name, "source" ) == 0 ) + { + Sequence< Link > aLinks; + LinkSequence::createFromXML( + stripDavNamespace( value ), aLinks ); + thePropertyValue.Value <<= aLinks; + } + else + { + thePropertyValue.Value + <<= OStringToOUString( value, RTL_TEXTENCODING_UTF8 ); + } + } + + // Add the newly created PropertyValue + DAVResource* theResource = static_cast< DAVResource * >( userdata ); + theResource->properties.push_back( thePropertyValue ); + + return 0; // Go on. +} + +static void NPFR_propfind_results( void* userdata, + const ne_uri* uri, + const NeonPropFindResultSet* set ) +{ + // @@@ href is not the uri! DAVResource ctor wants uri! + + DAVResource theResource( + OStringToOUString( uri->path, RTL_TEXTENCODING_UTF8 ) ); + + ne_propset_iterate( set, NPFR_propfind_iter, &theResource ); + + // Add entry to resources list. + vector< DAVResource > * theResources + = static_cast< vector< DAVResource > * >( userdata ); + theResources->push_back( theResource ); +} + +static int NPFR_propnames_iter( void* userdata, + const NeonPropName* pname, + const char* /*value*/, + const HttpStatus* /*status*/ ) +{ + OUString aFullName; + DAVProperties::createUCBPropName( pname->nspace, + pname->name, + aFullName ); + + DAVResourceInfo* theResource = static_cast< DAVResourceInfo * >( userdata ); + theResource->properties.push_back( aFullName ); + return 0; +} + +static void NPFR_propnames_results( void* userdata, + const ne_uri* /*uri*/, + const NeonPropFindResultSet* results ) +{ + // @@@ href is not the uri! DAVResourceInfo ctor wants uri! + // Create entry for the resource. + DAVResourceInfo theResource; + + // Fill entry. + ne_propset_iterate( results, NPFR_propnames_iter, &theResource ); + + // Add entry to resources list. + vector< DAVResourceInfo > * theResources + = static_cast< vector< DAVResourceInfo > * >( userdata ); + theResources->push_back( theResource ); +} + +} + +NeonPropFindRequest::NeonPropFindRequest( HttpSession* inSession, + const char* inPath, + const Depth inDepth, + const vector< OUString >& inPropNames, + vector< DAVResource >& ioResources, + int & nError ) +{ + // Generate the list of properties we're looking for + int thePropCount = inPropNames.size(); + if ( thePropCount > 0 ) + { + std::unique_ptr<NeonPropName[]> thePropNames(new NeonPropName[ thePropCount + 1 ]); + int theIndex; + + for ( theIndex = 0; theIndex < thePropCount; theIndex ++ ) + { + // Split fullname into namespace and name! + DAVProperties::createNeonPropName( + inPropNames[ theIndex ], thePropNames[ theIndex ] ); + } + thePropNames[ theIndex ].nspace = nullptr; + thePropNames[ theIndex ].name = nullptr; + + { + osl::Guard< osl::Mutex > theGlobalGuard(getGlobalNeonMutex()); + nError = ne_simple_propfind( inSession, + inPath, + inDepth, + thePropNames.get(), + NPFR_propfind_results, + &ioResources ); + } + + for ( theIndex = 0; theIndex < thePropCount; theIndex ++ ) + free( const_cast<char *>(thePropNames[ theIndex ].name) ); + } + else + { + // ALLPROP + osl::Guard< osl::Mutex > theGlobalGuard(getGlobalNeonMutex()); + nError = ne_simple_propfind( inSession, + inPath, + inDepth, + nullptr, // 0 == allprop + NPFR_propfind_results, + &ioResources ); + } + + // #87585# - Sometimes neon lies (because some servers lie). + if ( ( nError == NE_OK ) && ioResources.empty() ) + nError = NE_ERROR; +} + +NeonPropFindRequest::NeonPropFindRequest( + HttpSession* inSession, + const char* inPath, + const Depth inDepth, + std::vector< DAVResourceInfo > & ioResInfo, + int & nError ) +{ + { + osl::Guard< osl::Mutex > theGlobalGuard(getGlobalNeonMutex()); + nError = ne_propnames( inSession, + inPath, + inDepth, + NPFR_propnames_results, + &ioResInfo ); + } + + // #87585# - Sometimes neon lies (because some servers lie). + if ( ( nError == NE_OK ) && ioResInfo.empty() ) + nError = NE_ERROR; +} + +NeonPropFindRequest::~NeonPropFindRequest( ) +{ +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/ucb/source/ucp/webdav-neon/NeonPropFindRequest.hxx b/ucb/source/ucp/webdav-neon/NeonPropFindRequest.hxx new file mode 100644 index 000000000..b435a80ba --- /dev/null +++ b/ucb/source/ucp/webdav-neon/NeonPropFindRequest.hxx @@ -0,0 +1,66 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/************************************************************************* + * + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * Copyright 2000, 2010 Oracle and/or its affiliates. + * + * OpenOffice.org - a multi-platform office productivity suite + * + * This file is part of OpenOffice.org. + * + * OpenOffice.org is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License version 3 + * only, as published by the Free Software Foundation. + * + * OpenOffice.org is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License version 3 for more details + * (a copy is included in the LICENSE file that accompanied this code). + * + * You should have received a copy of the GNU Lesser General Public License + * version 3 along with OpenOffice.org. If not, see + * <http://www.openoffice.org/license.html> + * for a copy of the LGPLv3 License. + * + ************************************************************************/ + +#ifndef INCLUDED_UCB_SOURCE_UCP_WEBDAV_NEON_NEONPROPFINDREQUEST_HXX +#define INCLUDED_UCB_SOURCE_UCP_WEBDAV_NEON_NEONPROPFINDREQUEST_HXX + +#include <config_lgpl.h> +#include <vector> +#include <rtl/ustring.hxx> +#include "NeonTypes.hxx" +#include "DAVTypes.hxx" +#include "DAVResource.hxx" + +namespace webdav_ucp +{ + +class NeonPropFindRequest +{ +public: + // named / allprop + NeonPropFindRequest( HttpSession* inSession, + const char* inPath, + const Depth inDepth, + const std::vector< OUString > & inPropNames, + std::vector< DAVResource > & ioResources, + int & nError ); + // propnames + NeonPropFindRequest( HttpSession* inSession, + const char* inPath, + const Depth inDepth, + std::vector< DAVResourceInfo > & ioResInfo, + int & nError ); + + ~NeonPropFindRequest(); +}; + +} // namespace webdav_ucp + +#endif // INCLUDED_UCB_SOURCE_UCP_WEBDAV_NEON_NEONPROPFINDREQUEST_HXX + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/ucb/source/ucp/webdav-neon/NeonSession.cxx b/ucb/source/ucp/webdav-neon/NeonSession.cxx new file mode 100644 index 000000000..7fac08345 --- /dev/null +++ b/ucb/source/ucp/webdav-neon/NeonSession.cxx @@ -0,0 +1,2361 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/************************************************************************* + * + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * Copyright 2000, 2010 Oracle and/or its affiliates. + * + * OpenOffice.org - a multi-platform office productivity suite + * + * This file is part of OpenOffice.org. + * + * OpenOffice.org is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License version 3 + * only, as published by the Free Software Foundation. + * + * OpenOffice.org is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License version 3 for more details + * (a copy is included in the LICENSE file that accompanied this code). + * + * You should have received a copy of the GNU Lesser General Public License + * version 3 along with OpenOffice.org. If not, see + * <http://www.openoffice.org/license.html> + * for a copy of the LGPLv3 License. + * + ************************************************************************/ + + +#include <unordered_map> +#include <vector> +#include <string.h> +#include <sal/log.hxx> +#include <osl/diagnose.h> +#include <osl/thread.h> +#include <osl/time.h> +#include <ne_socket.h> +#include <ne_auth.h> +#include <ne_redirect.h> +#include <ne_ssl.h> + +// old neon versions forgot to set this +extern "C" { +#include <ne_compress.h> +} + +#include <libxml/parser.h> +#include <comphelper/sequence.hxx> +#include <ucbhelper/simplecertificatevalidationrequest.hxx> + +#include "DAVAuthListener.hxx" +#include "NeonTypes.hxx" +#include "NeonSession.hxx" +#include "NeonInputStream.hxx" +#include "NeonPropFindRequest.hxx" +#include "NeonHeadRequest.hxx" +#include "NeonUri.hxx" +#include "LinkSequence.hxx" +#include "UCBDeadPropertyValue.hxx" + +#include <officecfg/Inet.hxx> +#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/xml/crypto/XSecurityEnvironment.hpp> +#include <com/sun/star/security/XCertificate.hpp> +#include <com/sun/star/security/CertificateValidity.hpp> +#include <com/sun/star/security/CertificateContainerStatus.hpp> +#include <com/sun/star/security/CertificateContainer.hpp> +#include <com/sun/star/security/XCertificateContainer.hpp> +#include <com/sun/star/ucb/Lock.hpp> +#include <com/sun/star/beans/NamedValue.hpp> +#include <com/sun/star/xml/crypto/SEInitializer.hpp> + + +using namespace com::sun::star; +using namespace webdav_ucp; + +#ifndef EOL +# define EOL "\r\n" +#endif + +namespace { + +struct RequestData +{ + // POST + OUString aContentType; + OUString aReferer; + + RequestData() {} + RequestData( const OUString & rContentType, + const OUString & rReferer ) + : aContentType( rContentType ), aReferer( rReferer ) {} +}; + +struct equalPtr +{ + bool operator()( const ne_request* p1, const ne_request* p2 ) const + { + return p1 == p2; + } +}; + +struct hashPtr +{ + size_t operator()( const ne_request* p ) const + { + return reinterpret_cast<size_t>(p); + } +}; + +} + +typedef std::unordered_map +< + ne_request*, + RequestData, + hashPtr, + equalPtr +> +RequestDataMap; + +static sal_uInt16 makeStatusCode( const OUString & rStatusText ) +{ + // Extract status code from session error string. Unfortunately + // neon provides no direct access to the status code... + + if ( rStatusText.getLength() < 3 ) + { + SAL_WARN( "ucb.ucp.webdav", "makeStatusCode - status text string to short!" ); + return 0; + } + + sal_Int32 nPos = rStatusText.indexOf( ' ' ); + if ( nPos == -1 ) + { + SAL_WARN( "ucb.ucp.webdav", "makeStatusCode - wrong status text format!" ); + return 0; + } + + return sal_uInt16( rStatusText.copy( 0, nPos ).toInt32() ); +} + +static bool noKeepAlive( const uno::Sequence< beans::NamedValue >& rFlags ) +{ + if ( !rFlags.hasElements() ) + return false; + + // find "KeepAlive" property + const beans::NamedValue* pValue( + std::find_if(rFlags.begin(), rFlags.end(), + [] (beans::NamedValue const& rNV) { return rNV.Name == "KeepAlive"; } )); + return pValue != rFlags.end() && !pValue->Value.get<bool>(); +} + +namespace { + +struct NeonRequestContext +{ + uno::Reference< io::XOutputStream > xOutputStream; + rtl::Reference< NeonInputStream > xInputStream; + const std::vector< OUString > * pHeaderNames; + DAVResource * pResource; + + explicit NeonRequestContext( uno::Reference< io::XOutputStream > const & xOutStrm ) + : xOutputStream( xOutStrm ), + pHeaderNames( nullptr ), pResource( nullptr ) {} + + explicit NeonRequestContext( const rtl::Reference< NeonInputStream > & xInStrm ) + : xInputStream( xInStrm ), + pHeaderNames( nullptr ), pResource( nullptr ) {} + + NeonRequestContext( uno::Reference< io::XOutputStream > const & xOutStrm, + const std::vector< OUString > & inHeaderNames, + DAVResource & ioResource ) + : xOutputStream( xOutStrm ), + pHeaderNames( &inHeaderNames ), pResource( &ioResource ) {} + + NeonRequestContext( const rtl::Reference< NeonInputStream > & xInStrm, + const std::vector< OUString > & inHeaderNames, + DAVResource & ioResource ) + : xInputStream( xInStrm ), + pHeaderNames( &inHeaderNames ), pResource( &ioResource ) {} + + void ResponseBlockReader(const char * inBuf, size_t inLen) + { + if (xInputStream.is()) + xInputStream->AddToStream( inBuf, inLen ); + } + + void ResponseBlockWriter(const char * inBuf, size_t inLen) + { + if (xOutputStream.is()) + { + const uno::Sequence< sal_Int8 > aSeq( reinterpret_cast<sal_Int8 const *>(inBuf), inLen ); + xOutputStream->writeBytes( aSeq ); + } + } + +}; + +} + +// A simple Neon response_block_reader for use with an XInputStream +extern "C" { + +static int NeonSession_ResponseBlockReader(void * inUserData, + const char * inBuf, + size_t inLen ) +{ + // neon sometimes calls this function with (inLen == 0)... + if ( inLen > 0 ) + { + NeonRequestContext * pCtx = static_cast<NeonRequestContext*>(inUserData); + pCtx->ResponseBlockReader(inBuf, inLen); + } + return 0; +} + +// A simple Neon response_block_reader for use with an XOutputStream +static int NeonSession_ResponseBlockWriter( void * inUserData, + const char * inBuf, + size_t inLen ) +{ + // neon calls this function with (inLen == 0)... + if ( inLen > 0 ) + { + NeonRequestContext * pCtx = static_cast<NeonRequestContext*>(inUserData); + pCtx->ResponseBlockWriter(inBuf, inLen); + } + return 0; +} + +static int NeonSession_NeonAuth( void * inUserData, +#if defined NE_FEATURE_SSPI && ! defined SYSTEM_NEON + const char * inAuthProtocol, +#endif + const char * inRealm, + int attempt, + char * inoutUserName, + char * inoutPassWord ) +{ +/* The callback used to request the username and password in the given + * realm. The username and password must be copied into the buffers + * which are both of size NE_ABUFSIZ. The 'attempt' parameter is zero + * on the first call to the callback, and increases by one each time + * an attempt to authenticate fails. + * + * The callback must return zero to indicate that authentication + * should be attempted with the username/password, or non-zero to + * cancel the request. (if non-zero, username and password are + * ignored.) */ + + NeonSession * theSession = static_cast<NeonSession*>(inUserData); + const char * pAuthProtocol; +#if defined NE_FEATURE_SSPI && ! defined SYSTEM_NEON + pAuthProtocol = inAuthProtocol; +#else + pAuthProtocol = nullptr; +#endif + return theSession->NeonAuth(pAuthProtocol, inRealm, attempt, inoutUserName, inoutPassWord); +} + +} + +int NeonSession::NeonAuth(const char* inAuthProtocol, const char* inRealm, + int attempt, char* inoutUserName, char * inoutPassWord) +{ + osl::Guard< osl::Mutex > theGuard( m_aMutex ); + +/* The callback used to request the username and password in the given + * realm. The username and password must be copied into the buffers + * which are both of size NE_ABUFSIZ. The 'attempt' parameter is zero + * on the first call to the callback, and increases by one each time + * an attempt to authenticate fails. + * + * The callback must return zero to indicate that authentication + * should be attempted with the username/password, or non-zero to + * cancel the request. (if non-zero, username and password are + * ignored.) */ + + DAVAuthListener * pListener + = getRequestEnvironment().m_xAuthListener.get(); + if ( !pListener ) + { + // abort + return -1; + } + OUString theUserName; + OUString thePassWord; + + if ( attempt == 0 ) + { + // neon does not handle username supplied with request URI (for + // instance when doing FTP over proxy - last checked: 0.23.5 ) + + try + { + NeonUri uri( getRequestEnvironment().m_aRequestURI ); + const OUString& aUserInfo( uri.GetUserInfo() ); + if ( !aUserInfo.isEmpty() ) + { + sal_Int32 nPos = aUserInfo.indexOf( '@' ); + if ( nPos == -1 ) + { + theUserName = aUserInfo; + } + else + { + theUserName = aUserInfo.copy( 0, nPos ); + thePassWord = aUserInfo.copy( nPos + 1 ); + } + } + } + catch ( DAVException const & ) + { + // abort + return -1; + } + } + else + { + // username buffer is prefilled with user name from last attempt. + theUserName = OUString::createFromAscii( inoutUserName ); + // @@@ Neon does not initialize password buffer (last checked: 0.22.0). + //thePassWord = OUString::createFromAscii( inoutPassWord ); + } + +#if defined NE_FEATURE_SSPI && ! defined SYSTEM_NEON + const bool bCanUseSystemCreds + = (attempt == 0) && // avoid endless loops + ne_has_support( NE_FEATURE_SSPI ) && // Windows-only feature. + ( ( ne_strcasecmp( inAuthProtocol, "NTLM" ) == 0 ) || + ( ne_strcasecmp( inAuthProtocol, "Negotiate" ) == 0 ) ); +#else + (void)inAuthProtocol; + const bool bCanUseSystemCreds = false; +#endif + + int theRetVal = pListener->authenticate( + OUString::createFromAscii( inRealm ), + getHostName(), + theUserName, + thePassWord, + bCanUseSystemCreds); + + OString aUser( OUStringToOString( theUserName, RTL_TEXTENCODING_UTF8 ) ); + if ( aUser.getLength() > ( NE_ABUFSIZ - 1 ) ) + { + SAL_WARN( "ucb.ucp.webdav", "NeonSession_NeonAuth - username too long!" ); + return -1; + } + + OString aPass( OUStringToOString( thePassWord, RTL_TEXTENCODING_UTF8 ) ); + if ( aPass.getLength() > ( NE_ABUFSIZ - 1 ) ) + { + SAL_WARN( "ucb.ucp.webdav", "NeonSession_NeonAuth - password too long!" ); + return -1; + } + + // #100211# - checked + strcpy( inoutUserName, aUser.getStr() ); + strcpy( inoutPassWord, aPass.getStr() ); + + return theRetVal; +} + +namespace { + OUString GetHostnamePart( const OUString& _rRawString ) + { + OUString sPart; + OUString sPartId("CN="); + sal_Int32 nContStart = _rRawString.indexOf( sPartId ); + if ( nContStart != -1 ) + { + nContStart += sPartId.getLength(); + sal_Int32 nContEnd = _rRawString.indexOf( ',', nContStart ); + sPart = nContEnd != -1 ? + _rRawString.copy(nContStart, nContEnd - nContStart) : + _rRawString.copy(nContStart); + } + return sPart; + } +} // namespace + +extern "C" { + +static int NeonSession_CertificationNotify( void *userdata, + int, + const ne_ssl_certificate *cert ) +{ + NeonSession * pSession = static_cast< NeonSession * >( userdata ); + return pSession->CertificationNotify(cert); +} + +} + +int NeonSession::CertificationNotify(const ne_ssl_certificate *cert) +{ + osl::Guard< osl::Mutex > theGuard( m_aMutex ); + + OSL_ASSERT( cert ); + + uno::Reference< security::XCertificateContainer > xCertificateContainer; + try + { + xCertificateContainer = security::CertificateContainer::create( getComponentContext() ); + } + catch ( uno::Exception const & ) + { + } + + if ( !xCertificateContainer.is() ) + return 1; + + char * dn = ne_ssl_readable_dname( ne_ssl_cert_subject( cert ) ); + OUString cert_subject( dn, strlen( dn ), RTL_TEXTENCODING_UTF8, 0 ); + + ne_free( dn ); + + security::CertificateContainerStatus certificateContainer( + xCertificateContainer->hasCertificate( + getHostName(), cert_subject ) ); + + if ( certificateContainer != security::CertificateContainerStatus_NOCERT ) + return + certificateContainer == security::CertificateContainerStatus_TRUSTED + ? 0 + : 1; + + uno::Reference< xml::crypto::XSEInitializer > xSEInitializer; + try + { + xSEInitializer = xml::crypto::SEInitializer::create( getComponentContext() ); + } + catch ( uno::Exception const & ) + { + } + + if ( !xSEInitializer.is() ) + return 1; + + uno::Reference< xml::crypto::XXMLSecurityContext > xSecurityContext( + xSEInitializer->createSecurityContext( OUString() ) ); + + uno::Reference< xml::crypto::XSecurityEnvironment > xSecurityEnv( + xSecurityContext->getSecurityEnvironment() ); + + //The end entity certificate + char * eeCertB64 = ne_ssl_cert_export( cert ); + + OString sEECertB64( eeCertB64 ); + + uno::Reference< security::XCertificate > xEECert( + xSecurityEnv->createCertificateFromAscii( + OStringToOUString( sEECertB64, RTL_TEXTENCODING_ASCII_US ) ) ); + + ne_free( eeCertB64 ); + eeCertB64 = nullptr; + + std::vector< uno::Reference< security::XCertificate > > vecCerts; + const ne_ssl_certificate * issuerCert = cert; + do + { + //get the intermediate certificate + //the returned value is const ! Therefore it does not need to be freed + //with ne_ssl_cert_free, which takes a non-const argument + issuerCert = ne_ssl_cert_signedby( issuerCert ); + if ( nullptr == issuerCert ) + break; + + char * imCertB64 = ne_ssl_cert_export( issuerCert ); + OString sInterMediateCertB64( imCertB64 ); + ne_free( imCertB64 ); + + uno::Reference< security::XCertificate> xImCert( + xSecurityEnv->createCertificateFromAscii( + OStringToOUString( sInterMediateCertB64, RTL_TEXTENCODING_ASCII_US ) ) ); + if ( xImCert.is() ) + vecCerts.push_back( xImCert ); + } + while ( true ); + + sal_Int64 certValidity = xSecurityEnv->verifyCertificate( xEECert, + ::comphelper::containerToSequence( vecCerts ) ); + + if ( isDomainMatch( + GetHostnamePart( xEECert->getSubjectName() ) ) ) + { + // if host name matched with certificate then look if the + // certificate was ok + if( certValidity == security::CertificateValidity::VALID ) + return 0; + } + + const uno::Reference< ucb::XCommandEnvironment > xEnv( + getRequestEnvironment().m_xEnv ); + if ( xEnv.is() ) + { + uno::Reference< task::XInteractionHandler > xIH( + xEnv->getInteractionHandler() ); + if ( xIH.is() ) + { + rtl::Reference< ucbhelper::SimpleCertificateValidationRequest > + xRequest( new ucbhelper::SimpleCertificateValidationRequest( + static_cast<sal_Int32>(certValidity), xEECert, getHostName() ) ); + xIH->handle( xRequest.get() ); + + rtl::Reference< ucbhelper::InteractionContinuation > xSelection + = xRequest->getSelection(); + + if ( xSelection.is() ) + { + uno::Reference< task::XInteractionApprove > xApprove( + xSelection.get(), uno::UNO_QUERY ); + if ( xApprove.is() ) + { + xCertificateContainer->addCertificate( + getHostName(), cert_subject, true ); + return 0; + } + else + { + // Don't trust cert + xCertificateContainer->addCertificate( + getHostName(), cert_subject, false ); + return 1; + } + } + } + else + { + // Don't trust cert + xCertificateContainer->addCertificate( + getHostName(), cert_subject, false ); + return 1; + } + } + return 1; +} + +extern "C" { + +static void NeonSession_PreSendRequest( ne_request * req, + void * userdata, + ne_buffer * headers ) +{ + // userdata -> value returned by 'create' + NeonSession * pSession = static_cast< NeonSession * >( userdata ); + if (!pSession) + return; + pSession->PreSendRequest(req, headers); +} + +} + +void NeonSession::PreSendRequest(ne_request* req, ne_buffer* headers) +{ + osl::Guard< osl::Mutex > theGuard( m_aMutex ); + + // If there is a proxy server in between, it shall never use + // cached data. We always want 'up-to-date' data. + ne_buffer_concat( headers, "Pragma: no-cache", EOL, nullptr ); + // alternative, but understood by HTTP 1.1 servers only: + // ne_buffer_concat( headers, "Cache-Control: max-age=0", EOL, NULL ); + + const RequestDataMap * pRequestData + = static_cast< const RequestDataMap* >( + getRequestData() ); + + RequestDataMap::const_iterator it = pRequestData->find( req ); + if ( it != pRequestData->end() ) + { + if ( !(*it).second.aContentType.isEmpty() ) + { + char * pData = headers->data; + if ( strstr( pData, "Content-Type:" ) == nullptr ) + { + OString aType + = OUStringToOString( (*it).second.aContentType, + RTL_TEXTENCODING_UTF8 ); + ne_buffer_concat( headers, "Content-Type: ", + aType.getStr(), EOL, nullptr ); + } + } + + if ( !(*it).second.aReferer.isEmpty() ) + { + char * pData = headers->data; + if ( strstr( pData, "Referer:" ) == nullptr ) + { + OString aReferer + = OUStringToOString( (*it).second.aReferer, + RTL_TEXTENCODING_UTF8 ); + ne_buffer_concat( headers, "Referer: ", + aReferer.getStr(), EOL, nullptr ); + } + } + } + + const DAVRequestHeaders & rHeaders + = getRequestEnvironment().m_aRequestHeaders; + + for ( const auto& rHeader : rHeaders ) + { + OString aHeader + = OUStringToOString( rHeader.first, + RTL_TEXTENCODING_UTF8 ); + OString aValue + = OUStringToOString( rHeader.second, + RTL_TEXTENCODING_UTF8 ); + ne_buffer_concat( headers, aHeader.getStr(), ": ", + aValue.getStr(), EOL, nullptr ); + } +} + +//See https://bugzilla.redhat.com/show_bug.cgi?id=544619#c4 +//neon is threadsafe, but uses gnutls which is only thread-safe +//if initialized to be thread-safe. cups, unfortunately, generally +//initializes it first, and as non-thread-safe, leaving the entire +//stack unsafe +namespace webdav_ucp +{ + osl::Mutex& getGlobalNeonMutex() + { + static osl::Mutex aMutex; + return aMutex; + } +} + +// static members +bool NeonSession::m_bGlobalsInited = false; +NeonLockStore NeonSession::m_aNeonLockStore; + +NeonSession::NeonSession( const rtl::Reference< DAVSessionFactory > & rSessionFactory, + const OUString& inUri, + const uno::Sequence< beans::NamedValue >& rFlags, + const ucbhelper::InternetProxyDecider & rProxyDecider ) + : DAVSession( rSessionFactory ) + , m_nProxyPort( 0 ) + , m_aFlags( rFlags ) + , m_pHttpSession( nullptr ) + , m_pRequestData( new RequestDataMap ) + , m_rProxyDecider( rProxyDecider ) +{ + NeonUri theUri( inUri ); + m_aScheme = theUri.GetScheme(); + m_aHostName = theUri.GetHost(); + m_nPort = theUri.GetPort(); + SAL_INFO( "ucb.ucp.webdav", "NeonSession ctor - URL <" << inUri << ">" ); +} + +NeonSession::~NeonSession( ) +{ + if ( m_pHttpSession ) + { + { + osl::Guard< osl::Mutex > theGlobalGuard(getGlobalNeonMutex()); + ne_session_destroy( m_pHttpSession ); + } + m_pHttpSession = nullptr; + } + delete static_cast< RequestDataMap * >( m_pRequestData ); +} + +void NeonSession::Init( const DAVRequestEnvironment & rEnv ) +{ + osl::Guard< osl::Mutex > theGuard( m_aMutex ); + m_aEnv = rEnv; + Init(); +} + +void NeonSession::Init() +{ + osl::Guard< osl::Mutex > theGuard( m_aMutex ); + + bool bCreateNewSession = m_bNeedNewSession; + m_bNeedNewSession = false; + + if ( m_pHttpSession == nullptr ) + { + // Ensure that Neon sockets are initialized + osl::Guard< osl::Mutex > theGlobalGuard(getGlobalNeonMutex()); + if (!m_bGlobalsInited ) + { + if ( ne_sock_init() != 0 ) + throw DAVException( DAVException::DAV_SESSION_CREATE, + NeonUri::makeConnectionEndPointString( + m_aHostName, m_nPort ) ); + + // #122205# - libxml2 needs to be initialized once if used by + // multithreaded programs like OOo. + xmlInitParser(); +#if OSL_DEBUG_LEVEL > 0 + // for more debug flags see ne_utils.h; NE_DEBUGGING must be defined + // while compiling neon in order to actually activate neon debug + // output. + ne_debug_init( stderr, NE_DBG_FLUSH + | NE_DBG_HTTP + // | NE_DBG_HTTPBODY + // | NE_DBG_HTTPAUTH + // | NE_DBG_XML + // | NE_DBG_XMLPARSE + | NE_DBG_LOCKS + | NE_DBG_SSL + ); +#endif + m_bGlobalsInited = true; + } + + const ucbhelper::InternetProxyServer & rProxyCfg = getProxySettings(); + + m_aProxyName = rProxyCfg.aName; + m_nProxyPort = rProxyCfg.nPort; + + // Not yet initialized. Create new session. + bCreateNewSession = true; + } + else + { + // #112271# Check whether proxy settings are still valid (They may + // change at any time). If not, create new Neon session. + + const ucbhelper::InternetProxyServer & rProxyCfg = getProxySettings(); + + if ( ( rProxyCfg.aName != m_aProxyName ) + || ( rProxyCfg.nPort != m_nProxyPort ) ) + { + m_aProxyName = rProxyCfg.aName; + m_nProxyPort = rProxyCfg.nPort; + + bCreateNewSession = true; + } + + if (bCreateNewSession) + { + // new session needed, destroy old first + { + osl::Guard< osl::Mutex > theGlobalGuard(getGlobalNeonMutex()); + ne_session_destroy( m_pHttpSession ); + } + m_pHttpSession = nullptr; + } + } + + if ( !bCreateNewSession ) + return; + + const sal_Int32 nConnectTimeoutMax = 180; + const sal_Int32 nConnectTimeoutMin = 2; + const sal_Int32 nReadTimeoutMax = 180; + const sal_Int32 nReadTimeoutMin = 20; + + // @@@ For FTP over HTTP proxy inUserInfo is needed to be able to + // build the complete request URI (including user:pass), but + // currently (0.22.0) neon does not allow to pass the user info + // to the session + + { + osl::Guard< osl::Mutex > theGlobalGuard(getGlobalNeonMutex()); + m_pHttpSession = ne_session_create( + OUStringToOString( m_aScheme, RTL_TEXTENCODING_UTF8 ).getStr(), + /* theUri.GetUserInfo(), + @@@ for FTP via HTTP proxy, but not supported by Neon */ + OUStringToOString( m_aHostName, RTL_TEXTENCODING_UTF8 ).getStr(), + m_nPort ); + } + + if ( m_pHttpSession == nullptr ) + throw DAVException( DAVException::DAV_SESSION_CREATE, + NeonUri::makeConnectionEndPointString( + m_aHostName, m_nPort ) ); + + // Register the session with the lock store + m_aNeonLockStore.registerSession( m_pHttpSession ); + + if ( m_aScheme.equalsIgnoreAsciiCase("https") ) + { + // Set a failure callback for certificate check + ne_ssl_set_verify( + m_pHttpSession, NeonSession_CertificationNotify, this); + + // Tell Neon to tell the SSL library used (OpenSSL or + // GnuTLS, I guess) to use a default set of root + // certificates. + ne_ssl_trust_default_ca(m_pHttpSession); + } + + // Add hooks (i.e. for adding additional headers to the request) + ne_hook_pre_send( m_pHttpSession, NeonSession_PreSendRequest, this ); + + if ( !m_aProxyName.isEmpty() ) + { + ne_session_proxy( m_pHttpSession, + OUStringToOString( + m_aProxyName, + RTL_TEXTENCODING_UTF8 ).getStr(), + m_nProxyPort ); + } + + // avoid KeepAlive? + if ( noKeepAlive(m_aFlags) ) + ne_set_session_flag( m_pHttpSession, NE_SESSFLAG_PERSIST, 0 ); + + // Register for redirects. + ne_redirect_register( m_pHttpSession ); + + // authentication callbacks. + ne_add_server_auth( m_pHttpSession, NE_AUTH_ALL, NeonSession_NeonAuth, this ); + ne_add_proxy_auth ( m_pHttpSession, NE_AUTH_ALL, NeonSession_NeonAuth, this ); + + // set timeout to connect + // if connect_timeout is not set, neon returns NE_CONNECT when the TCP socket default + // timeout elapses + // with connect_timeout set neon returns NE_TIMEOUT if elapsed when the connection + // didn't succeed + // grab it from configuration + uno::Reference< uno::XComponentContext > rContext = m_xFactory->getComponentContext(); + + // set the timeout (in seconds) used when making a connection + sal_Int32 nConnectTimeout = officecfg::Inet::Settings::ConnectTimeout::get( rContext ); + ne_set_connect_timeout( m_pHttpSession, + std::max( nConnectTimeoutMin, + std::min( nConnectTimeout, nConnectTimeoutMax ) ) ); + + // provides a read time out facility as well + // set the timeout (in seconds) used when reading from a socket. + sal_Int32 nReadTimeout = officecfg::Inet::Settings::ReadTimeout::get( rContext ); + ne_set_read_timeout( m_pHttpSession, + std::max( nReadTimeoutMin, + std::min( nReadTimeout, nReadTimeoutMax ) ) ); + +} + +bool NeonSession::CanUse( const OUString & inUri, + const uno::Sequence< beans::NamedValue >& rFlags ) +{ + try + { + NeonUri theUri( inUri ); + if ( ( theUri.GetPort() == m_nPort ) && + ( theUri.GetHost() == m_aHostName ) && + ( theUri.GetScheme() == m_aScheme ) && + ( rFlags == m_aFlags ) ) + return true; + } + catch ( DAVException const & ) + { + return false; + } + return false; +} + +bool NeonSession::UsesProxy() +{ + Init(); + return !m_aProxyName.isEmpty() ; +} + +void NeonSession::OPTIONS( const OUString & inPath, + DAVOptions & rOptions, // contains the name+values of every header + const DAVRequestEnvironment & rEnv ) +{ + osl::Guard< osl::Mutex > theGuard( m_aMutex ); + + SAL_INFO( "ucb.ucp.webdav", "OPTIONS - relative URL <" << inPath << ">" ); + + rOptions.init(); + + Init( rEnv ); + int theRetVal; + + ne_request *req = ne_request_create(m_pHttpSession, "OPTIONS", OUStringToOString( + inPath, RTL_TEXTENCODING_UTF8 ).getStr()); + { + osl::Guard< osl::Mutex > theGlobalGuard(getGlobalNeonMutex()); + theRetVal = ne_request_dispatch(req); + } + + //check if http error is in the 200 class (no error) + if (theRetVal == NE_OK && ne_get_status(req)->klass != 2) { + theRetVal = NE_ERROR; + } + + if ( theRetVal == NE_OK ) + { + void *cursor = nullptr; + const char *name, *value; + while ( ( cursor = ne_response_header_iterate( + req, cursor, &name, &value ) ) != nullptr ) + { + OUString aHeaderName(OUString(name, strlen(name), RTL_TEXTENCODING_ASCII_US).toAsciiLowerCase()); + OUString aHeaderValue(value, strlen(value), RTL_TEXTENCODING_ASCII_US); + + // display the single header + SAL_INFO( "ucb.ucp.webdav", "OPTIONS - received header: " << aHeaderName << ":" << aHeaderValue ); + + if ( aHeaderName == "allow" ) + { + rOptions.setAllowedMethods( aHeaderValue ); + } + else if ( aHeaderName == "dav" ) + { + // check type of dav capability + // need to parse the value, token separator: "," + // 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 + // silly parser written using OUString, not very efficient + // but quick and easy to write... + sal_Int32 nFromIndex = 0; + sal_Int32 nNextIndex = 0; + while( ( nNextIndex = aHeaderValue.indexOf( ",", nFromIndex ) ) != -1 ) + { // found a comma + // try to convert from nFromIndex to nNextIndex -1 in a number + // if this is 1 or 2 or 3, use for class setting + sal_Int32 nClass = + aHeaderValue.copy( nFromIndex, nNextIndex - nFromIndex ).toInt32(); + switch( nClass ) + { + case 1: + rOptions.setClass1(); + break; + case 2: + rOptions.setClass2(); + break; + case 3: + rOptions.setClass3(); + break; + default: + ; + } + // next starting point + nFromIndex = nNextIndex + 1; + } + // check for last fragment + if ( nFromIndex < aHeaderValue.getLength() ) + { + sal_Int32 nClass = aHeaderValue.copy( nFromIndex ).toInt32(); + switch( nClass ) + { + case 1: + rOptions.setClass1(); + break; + case 2: + rOptions.setClass2(); + break; + case 3: + rOptions.setClass3(); + break; + default: + ; + } + } + } + } + // if applicable, check for lock state: + if( rOptions.isClass2() || rOptions.isClass3() ) + { + //dav with lock possible, check for locked state + if ( m_aNeonLockStore.findByUri( + makeAbsoluteURL( inPath ) ) != nullptr ) + { + // we own a lock for this URL, + // set locked state + rOptions.setLocked(); + } + } + } + + ne_request_destroy(req); + + HandleError( theRetVal, inPath, rEnv ); +} + +void NeonSession::PROPFIND( const OUString & inPath, + const Depth inDepth, + const std::vector< OUString > & inPropNames, + std::vector< DAVResource > & ioResources, + const DAVRequestEnvironment & rEnv ) +{ + + osl::Guard< osl::Mutex > theGuard( m_aMutex ); + +#if defined SAL_LOG_INFO + { //debug + SAL_INFO( "ucb.ucp.webdav", "PROPFIND - relative URL: <" << inPath << "> Depth: " << inDepth ); + for(const auto& rPropName : inPropNames) + { + SAL_INFO( "ucb.ucp.webdav", "PROPFIND - property requested: " << rPropName ); + } + } //debug +#endif + + Init( rEnv ); + + int theRetVal = NE_OK; + NeonPropFindRequest theRequest( m_pHttpSession, + OUStringToOString( + inPath, RTL_TEXTENCODING_UTF8 ).getStr(), + inDepth, + inPropNames, + ioResources, + theRetVal ); + + HandleError( theRetVal, inPath, rEnv ); +} + +void NeonSession::PROPFIND( const OUString & inPath, + const Depth inDepth, + std::vector< DAVResourceInfo > & ioResInfo, + const DAVRequestEnvironment & rEnv ) +{ + osl::Guard< osl::Mutex > theGuard( m_aMutex ); + SAL_INFO( "ucb.ucp.webdav", "PROPFIND - relative URL: <" << inPath << "> Depth: " << inDepth ); + + Init( rEnv ); + + int theRetVal = NE_OK; + NeonPropFindRequest theRequest( m_pHttpSession, + OUStringToOString( + inPath, RTL_TEXTENCODING_UTF8 ).getStr(), + inDepth, + ioResInfo, + theRetVal ); + +#if defined SAL_LOG_INFO + { //debug + for ( const auto& rResInfo : ioResInfo ) + { + for ( const auto& rProp : rResInfo.properties ) + { + SAL_INFO( "ucb.ucp.webdav", "PROPFIND - returned property (name only): " << rProp ); + } + } + } //debug +#endif + + HandleError( theRetVal, inPath, rEnv ); +} + +void NeonSession::PROPPATCH( const OUString & inPath, + const std::vector< ProppatchValue > & inValues, + const DAVRequestEnvironment & rEnv ) +{ + SAL_INFO( "ucb.ucp.webdav", "PROPPATCH - relative URL <" << inPath << ">" ); + + /* @@@ Which standard live properties can be set by the client? + This is a known WebDAV RFC issue ( verified: 04/10/2001 ) + --> http://www.ics.uci.edu/pub/ietf/webdav/protocol/issues.html + + mod_dav implementation: + + creationdate r ( File System prop ) + displayname w + getcontentlanguage r ( #ifdef DAV_DISABLE_WRITEABLE_PROPS ) + getcontentlength r ( File System prop ) + getcontenttype r ( #ifdef DAV_DISABLE_WRITEABLE_PROPS ) + getetag r ( File System prop ) + getlastmodified r ( File System prop ) + lockdiscovery r + resourcetype r + source w + supportedlock r + executable w ( #ifndef WIN32 ) + + All dead properties are of course writable. + */ + + int theRetVal = NE_OK; + + int n; // for the "for" loop + + // Generate the list of properties we want to set. + int nPropCount = inValues.size(); + std::unique_ptr<ne_proppatch_operation[]> pItems( + new ne_proppatch_operation[ nPropCount + 1 ]); + for ( n = 0; n < nPropCount; ++n ) + { + const ProppatchValue & rValue = inValues[ n ]; + + // Split fullname into namespace and name! + ne_propname * pName = new ne_propname; + DAVProperties::createNeonPropName( rValue.name, *pName ); + pItems[ n ].name = pName; + + if ( rValue.operation == PROPSET ) + { + pItems[ n ].type = ne_propset; + + OUString aStringValue; + if ( DAVProperties::isUCBDeadProperty( *pName ) ) + { + // DAV dead property added by WebDAV UCP? + if ( !UCBDeadPropertyValue::toXML( rValue.value, + aStringValue ) ) + { + // Error! + pItems[ n ].value = nullptr; + theRetVal = NE_ERROR; + nPropCount = n + 1; + break; + } + } + else if ( !( rValue.value >>= aStringValue ) ) + { + // complex properties... + if ( rValue.name == DAVProperties::SOURCE ) + { + uno::Sequence< ucb::Link > aLinks; + if ( rValue.value >>= aLinks ) + { + LinkSequence::toXML( aLinks, aStringValue ); + } + else + { + // Error! + pItems[ n ].value = nullptr; + theRetVal = NE_ERROR; + nPropCount = n + 1; + break; + } + } + else + { + SAL_WARN( "ucb.ucp.webdav", "PROPPATCH - Unsupported type!" ); + // Error! + pItems[ n ].value = nullptr; + theRetVal = NE_ERROR; + nPropCount = n + 1; + break; + } + } + pItems[ n ].value + = strdup( OUStringToOString( aStringValue, + RTL_TEXTENCODING_UTF8 ).getStr() ); + } + else + { + pItems[ n ].type = ne_propremove; + pItems[ n ].value = nullptr; + } + } + + if ( theRetVal == NE_OK ) + { + osl::Guard< osl::Mutex > theGuard( m_aMutex ); + + Init( rEnv ); + + pItems[ n ].name = nullptr; + + theRetVal = ne_proppatch( m_pHttpSession, + OUStringToOString( + inPath, RTL_TEXTENCODING_UTF8 ).getStr(), + pItems.get() ); + } + + for ( n = 0; n < nPropCount; ++n ) + { + free( const_cast<char *>(pItems[ n ].name->name) ); + delete pItems[ n ].name; + free( const_cast<char *>(pItems[ n ].value) ); + } + + HandleError( theRetVal, inPath, rEnv ); +} + +void NeonSession::HEAD( const OUString & inPath, + const std::vector< OUString > & inHeaderNames, + DAVResource & ioResource, + const DAVRequestEnvironment & rEnv ) +{ + osl::Guard< osl::Mutex > theGuard( m_aMutex ); + SAL_INFO( "ucb.ucp.webdav", "HEAD - relative URL <" << inPath << ">" ); + + Init( rEnv ); + + int theRetVal = NE_OK; + NeonHeadRequest theRequest( m_pHttpSession, + inPath, + inHeaderNames, + ioResource, + theRetVal ); + + HandleError( theRetVal, inPath, rEnv ); +} + +uno::Reference< io::XInputStream > +NeonSession::GET( const OUString & inPath, + const DAVRequestEnvironment & rEnv ) +{ + osl::Guard< osl::Mutex > theGuard( m_aMutex ); + SAL_INFO( "ucb.ucp.webdav", "GET - relative URL <" << inPath << ">" ); + + Init( rEnv ); + + rtl::Reference< NeonInputStream > xInputStream( new NeonInputStream ); + NeonRequestContext aCtx( xInputStream ); + int theRetVal = GET( m_pHttpSession, + OUStringToOString( + inPath, RTL_TEXTENCODING_UTF8 ).getStr(), + NeonSession_ResponseBlockReader, + false, + &aCtx ); + + HandleError( theRetVal, inPath, rEnv ); + + return uno::Reference< io::XInputStream >( xInputStream.get() ); +} + +void NeonSession::GET( const OUString & inPath, + uno::Reference< io::XOutputStream > & ioOutputStream, + const DAVRequestEnvironment & rEnv ) +{ + osl::Guard< osl::Mutex > theGuard( m_aMutex ); + SAL_INFO( "ucb.ucp.webdav", "GET - relative URL <" << inPath << ">" ); + + Init( rEnv ); + + NeonRequestContext aCtx( ioOutputStream ); + int theRetVal = GET( m_pHttpSession, + OUStringToOString( + inPath, RTL_TEXTENCODING_UTF8 ).getStr(), + NeonSession_ResponseBlockWriter, + false, + &aCtx ); + + HandleError( theRetVal, inPath, rEnv ); +} + +uno::Reference< io::XInputStream > +NeonSession::GET( const OUString & inPath, + const std::vector< OUString > & inHeaderNames, + DAVResource & ioResource, + const DAVRequestEnvironment & rEnv ) +{ + osl::Guard< osl::Mutex > theGuard( m_aMutex ); + SAL_INFO( "ucb.ucp.webdav", "GET - relative URL <" << inPath << ">" ); + + Init( rEnv ); + + ioResource.uri = inPath; + ioResource.properties.clear(); + + rtl::Reference< NeonInputStream > xInputStream( new NeonInputStream ); + NeonRequestContext aCtx( xInputStream, inHeaderNames, ioResource ); + int theRetVal = GET( m_pHttpSession, + OUStringToOString( + inPath, RTL_TEXTENCODING_UTF8 ).getStr(), + NeonSession_ResponseBlockReader, + true, + &aCtx ); + + HandleError( theRetVal, inPath, rEnv ); + + return uno::Reference< io::XInputStream >( xInputStream.get() ); +} + +void NeonSession::GET0( const OUString & inPath, + const std::vector< OUString > & inHeaderNames, + DAVResource & ioResource, + const DAVRequestEnvironment & rEnv ) +{ + osl::Guard< osl::Mutex > theGuard( m_aMutex ); + SAL_INFO( "ucb.ucp.webdav", "GET - relative URL <" << inPath << ">" ); + + Init( rEnv ); + + ioResource.uri = inPath; + ioResource.properties.clear(); + + rtl::Reference< NeonInputStream > xInputStream( new NeonInputStream ); + NeonRequestContext aCtx( xInputStream, inHeaderNames, ioResource ); + int theRetVal = GET0( m_pHttpSession, + OUStringToOString( + inPath, RTL_TEXTENCODING_UTF8 ).getStr(), + true, + &aCtx ); + + HandleError( theRetVal, inPath, rEnv ); +} + +void NeonSession::GET( const OUString & inPath, + uno::Reference< io::XOutputStream > & ioOutputStream, + const std::vector< OUString > & inHeaderNames, + DAVResource & ioResource, + const DAVRequestEnvironment & rEnv ) +{ + osl::Guard< osl::Mutex > theGuard( m_aMutex ); + SAL_INFO( "ucb.ucp.webdav", "GET - relative URL <" << inPath << ">" ); + + Init( rEnv ); + + ioResource.uri = inPath; + ioResource.properties.clear(); + + NeonRequestContext aCtx( ioOutputStream, inHeaderNames, ioResource ); + int theRetVal = GET( m_pHttpSession, + OUStringToOString( + inPath, RTL_TEXTENCODING_UTF8 ).getStr(), + NeonSession_ResponseBlockWriter, + true, + &aCtx ); + + HandleError( theRetVal, inPath, rEnv ); +} + +void NeonSession::PUT( const OUString & inPath, + const uno::Reference< io::XInputStream > & inInputStream, + const DAVRequestEnvironment & rEnv ) +{ + osl::Guard< osl::Mutex > theGuard( m_aMutex ); + SAL_INFO( "ucb.ucp.webdav", "PUT - relative URL <" << inPath << ">" ); + + uno::Sequence< sal_Int8 > aDataToSend; + if ( !getDataFromInputStream( inInputStream, aDataToSend, false ) ) + throw DAVException( DAVException::DAV_INVALID_ARG ); + + Init( rEnv ); + + int theRetVal = PUT( m_pHttpSession, + OUStringToOString( + inPath, RTL_TEXTENCODING_UTF8 ).getStr(), + reinterpret_cast< const char * >( + aDataToSend.getConstArray() ), + aDataToSend.getLength() ); + + HandleError( theRetVal, inPath, rEnv ); +} + +uno::Reference< io::XInputStream > +NeonSession::POST( const OUString & inPath, + const OUString & rContentType, + const OUString & rReferer, + const uno::Reference< io::XInputStream > & inInputStream, + const DAVRequestEnvironment & rEnv ) +{ + osl::Guard< osl::Mutex > theGuard( m_aMutex ); + SAL_INFO( "ucb.ucp.webdav", "POST - relative URL <" << inPath << ">" ); + + uno::Sequence< sal_Int8 > aDataToSend; + if ( !getDataFromInputStream( inInputStream, aDataToSend, true ) ) + throw DAVException( DAVException::DAV_INVALID_ARG ); + + Init( rEnv ); + + rtl::Reference< NeonInputStream > xInputStream( new NeonInputStream ); + NeonRequestContext aCtx( xInputStream ); + int theRetVal = POST( m_pHttpSession, + OUStringToOString( + inPath, RTL_TEXTENCODING_UTF8 ).getStr(), + reinterpret_cast< const char * >( + aDataToSend.getConstArray() ), + NeonSession_ResponseBlockReader, + &aCtx, + rContentType, + rReferer ); + + HandleError( theRetVal, inPath, rEnv ); + + return uno::Reference< io::XInputStream >( xInputStream.get() ); +} + +void NeonSession::POST( const OUString & inPath, + const OUString & rContentType, + const OUString & rReferer, + const uno::Reference< io::XInputStream > & inInputStream, + uno::Reference< io::XOutputStream > & oOutputStream, + const DAVRequestEnvironment & rEnv ) +{ + osl::Guard< osl::Mutex > theGuard( m_aMutex ); + SAL_INFO( "ucb.ucp.webdav", "POST - relative URL <" << inPath << ">" ); + + uno::Sequence< sal_Int8 > aDataToSend; + if ( !getDataFromInputStream( inInputStream, aDataToSend, true ) ) + throw DAVException( DAVException::DAV_INVALID_ARG ); + + Init( rEnv ); + + NeonRequestContext aCtx( oOutputStream ); + int theRetVal = POST( m_pHttpSession, + OUStringToOString( + inPath, RTL_TEXTENCODING_UTF8 ).getStr(), + reinterpret_cast< const char * >( + aDataToSend.getConstArray() ), + NeonSession_ResponseBlockWriter, + &aCtx, + rContentType, + rReferer ); + + HandleError( theRetVal, inPath, rEnv ); +} + +void NeonSession::MKCOL( const OUString & inPath, + const DAVRequestEnvironment & rEnv ) +{ + osl::Guard< osl::Mutex > theGuard( m_aMutex ); + SAL_INFO( "ucb.ucp.webdav", "MKCOL - relative URL <" << inPath << ">" ); + + Init( rEnv ); + + int theRetVal = ne_mkcol( m_pHttpSession, + OUStringToOString( + inPath, RTL_TEXTENCODING_UTF8 ).getStr() ); + + HandleError( theRetVal, inPath, rEnv ); +} + +void NeonSession::COPY( const OUString & inSourceURL, + const OUString & inDestinationURL, + const DAVRequestEnvironment & rEnv, + bool inOverWrite ) +{ + osl::Guard< osl::Mutex > theGuard( m_aMutex ); + SAL_INFO( "ucb.ucp.webdav", "COPY - inSourceURL: "<<inSourceURL<<" inDestinationURL: "<<inDestinationURL); + + Init( rEnv ); + + NeonUri theSourceUri( inSourceURL ); + NeonUri theDestinationUri( inDestinationURL ); + + int theRetVal = ne_copy( m_pHttpSession, + inOverWrite ? 1 : 0, + NE_DEPTH_INFINITE, + OUStringToOString( + theSourceUri.GetPath(), + RTL_TEXTENCODING_UTF8 ).getStr(), + OUStringToOString( + theDestinationUri.GetPath(), + RTL_TEXTENCODING_UTF8 ).getStr() ); + + HandleError( theRetVal, inSourceURL, rEnv ); +} + +void NeonSession::MOVE( const OUString & inSourceURL, + const OUString & inDestinationURL, + const DAVRequestEnvironment & rEnv, + bool inOverWrite ) +{ + osl::Guard< osl::Mutex > theGuard( m_aMutex ); + SAL_INFO( "ucb.ucp.webdav", "MOVE - inSourceURL: "<<inSourceURL<<" inDestinationURL: "<<inDestinationURL); + + Init( rEnv ); + + NeonUri theSourceUri( inSourceURL ); + NeonUri theDestinationUri( inDestinationURL ); + int theRetVal = ne_move( m_pHttpSession, + inOverWrite ? 1 : 0, + OUStringToOString( + theSourceUri.GetPath(), + RTL_TEXTENCODING_UTF8 ).getStr(), + OUStringToOString( + theDestinationUri.GetPath(), + RTL_TEXTENCODING_UTF8 ).getStr() ); + + HandleError( theRetVal, inSourceURL, rEnv ); +} + +void NeonSession::DESTROY( const OUString & inPath, + const DAVRequestEnvironment & rEnv ) +{ + osl::Guard< osl::Mutex > theGuard( m_aMutex ); + SAL_INFO( "ucb.ucp.webdav", "DESTROY - relative URL <" << inPath << ">" ); + + Init( rEnv ); + + int theRetVal = ne_delete( m_pHttpSession, + OUStringToOString( + inPath, RTL_TEXTENCODING_UTF8 ).getStr() ); + + HandleError( theRetVal, inPath, rEnv ); +} + +namespace +{ + sal_Int32 lastChanceToSendRefreshRequest( TimeValue const & rStart, + int timeout ) + { + TimeValue aEnd; + osl_getSystemTime( &aEnd ); + + // Try to estimate a safe absolute time for sending the + // lock refresh request. + sal_Int32 lastChanceToSendRefreshRequest = -1; + if ( timeout != NE_TIMEOUT_INFINITE ) + { + sal_Int32 calltime = aEnd.Seconds - rStart.Seconds; + if ( calltime <= timeout ) + { + lastChanceToSendRefreshRequest = rStart.Seconds + timeout; + } + else + { + SAL_WARN( "ucb.ucp.webdav", "LOCK - no chance to refresh lock before timeout!" ); + } + } + return lastChanceToSendRefreshRequest; + } + +} // namespace + +// Set new lock +void NeonSession::LOCK( const OUString & inPath, + ucb::Lock & rLock, + const DAVRequestEnvironment & rEnv ) +{ + osl::Guard< osl::Mutex > theGuard( m_aMutex ); + SAL_INFO( "ucb.ucp.webdav", "LOCK (create) - relative URL: <" << inPath << ">" ); + + // before issuing the lock command, + // better check first if we already have one on this href + if ( m_aNeonLockStore.findByUri( + makeAbsoluteURL( inPath ) ) != nullptr ) + { + // we already own a lock for this href + // no need to ask for another + // TODO: add a lockdiscovery request for confirmation + // checking the locktoken, the only item that's unique + return; + } + + Init( rEnv ); + + /* Create a depth zero, exclusive write lock, with default timeout + * (allowing a server to pick a default). token, owner and uri are + * unset. */ + NeonLock * theLock = ne_lock_create(); + + // Set the lock uri + ne_uri aUri; + ne_uri_parse( OUStringToOString( makeAbsoluteURL( inPath ), + RTL_TEXTENCODING_UTF8 ).getStr(), + &aUri ); + theLock->uri = aUri; + + // Set the lock depth + switch( rLock.Depth ) + { + case ucb::LockDepth_ZERO: + theLock->depth = NE_DEPTH_ZERO; + break; + case ucb::LockDepth_ONE: + theLock->depth = NE_DEPTH_ONE; + break; + case ucb::LockDepth_INFINITY: + theLock->depth = NE_DEPTH_INFINITE; + break; + default: + throw DAVException( DAVException::DAV_INVALID_ARG ); + } + + // Set the lock scope + switch ( rLock.Scope ) + { + case ucb::LockScope_EXCLUSIVE: + theLock->scope = ne_lockscope_exclusive; + break; + case ucb::LockScope_SHARED: + theLock->scope = ne_lockscope_shared; + break; + default: + throw DAVException( DAVException::DAV_INVALID_ARG ); + } + + // Set the lock timeout + theLock->timeout = static_cast<long>(rLock.Timeout); + + // Set the lock owner + OUString aValue; + rLock.Owner >>= aValue; + theLock->owner = + ne_strdup( OUStringToOString( aValue, + RTL_TEXTENCODING_UTF8 ).getStr() ); + TimeValue startCall; + osl_getSystemTime( &startCall ); + + int theRetVal = ne_lock( m_pHttpSession, theLock ); + + if ( theRetVal == NE_OK ) + { + m_aNeonLockStore.addLock( theLock, + this, + lastChanceToSendRefreshRequest( + startCall, theLock->timeout ) ); + + uno::Sequence< OUString > aTokens( 1 ); + aTokens[ 0 ] = OUString::createFromAscii( theLock->token ); + rLock.LockTokens = aTokens; + + SAL_INFO( "ucb.ucp.webdav", "LOCK (create) - Created lock for <" << makeAbsoluteURL( inPath ) + << "> token: <" << theLock->token << "> timeout: " << theLock->timeout << " sec."); + } + else + { + ne_lock_destroy( theLock ); + + SAL_INFO( "ucb.ucp.webdav", "LOCK (create) - Obtaining lock for <" + << makeAbsoluteURL( inPath ) << " failed!" ); + } + + HandleError( theRetVal, inPath, rEnv ); +} + +// Refresh existing lock +bool NeonSession::LOCK( NeonLock * pLock, + sal_Int32 & rlastChanceToSendRefreshRequest ) +{ + osl::Guard< osl::Mutex > theGuard( m_aMutex ); + +#if defined SAL_LOG_INFO + { + char * p = ne_uri_unparse( &(pLock->uri) ); + SAL_INFO( "ucb.ucp.webdav", "LOCK (refresh) - relative URL: <" << p << "> token: <" << pLock->token << ">" ); + ne_free( p ); + } +#endif + + // refresh existing lock. + + TimeValue startCall; + osl_getSystemTime( &startCall ); + + // save the current requested timeout, because ne_lock_refresh uses + // pLock->timeout as an out parameter. This prevents a feedback-loop, + // where we would request a shorter timeout on each refresh. + long timeout = pLock->timeout; + const int theRetVal = ne_lock_refresh(m_pHttpSession, pLock); + if (theRetVal == NE_OK) + { + rlastChanceToSendRefreshRequest + = lastChanceToSendRefreshRequest( startCall, pLock->timeout ); + + SAL_INFO( "ucb.ucp.webdav", "LOCK (refresh) - Lock successfully refreshed." ); + pLock->timeout = timeout; + return true; + } + else + { +#if defined SAL_LOG_WARN + char * p = ne_uri_unparse( &(pLock->uri) ); + SAL_WARN( "ucb.ucp.webdav", "LOCK (refresh) - not refreshed! Relative URL: <" << p << "> token: <" << pLock->token << ">" ); + ne_free( p ); +#endif + if (theRetVal == NE_AUTH) + { + // tdf#126279: see handling of NE_AUTH in HandleError + m_bNeedNewSession = true; + m_aNeonLockStore.removeLockDeferred(pLock); + } + return false; + } +} + +void NeonSession::UNLOCK( const OUString & inPath, + const DAVRequestEnvironment & rEnv ) +{ + osl::Guard< osl::Mutex > theGuard( m_aMutex ); + + // get the neon lock from lock store + NeonLock * theLock + = m_aNeonLockStore.findByUri( makeAbsoluteURL( inPath ) ); + if ( !theLock ) + throw DAVException( DAVException::DAV_NOT_LOCKED ); + + SAL_INFO( "ucb.ucp.webdav", "UNLOCK - relative URL: <" << inPath << "> token: <" << theLock->token << ">" ); + Init( rEnv ); + + int theRetVal = ne_unlock( m_pHttpSession, theLock ); + + if ( theRetVal == NE_OK ) + { + m_aNeonLockStore.removeLock( theLock ); + ne_lock_destroy( theLock ); + } + else + { + SAL_INFO( "ucb.ucp.webdav", "UNLOCK - Unlocking of <" + << makeAbsoluteURL( inPath ) << "> failed." ); + } + + HandleError( theRetVal, inPath, rEnv ); +} + +bool NeonSession::UNLOCK( NeonLock * pLock ) +{ + osl::Guard< osl::Mutex > theGuard( m_aMutex ); + +#if defined SAL_LOG_INFO + { + char * p = ne_uri_unparse( &(pLock->uri) ); + SAL_INFO( "ucb.ucp.webdav", "UNLOCK (from store) - relative URL: <" << p << "> token: <" << pLock->token << ">" ); + ne_free( p ); + } +#endif + + const int theRetVal = ne_unlock(m_pHttpSession, pLock); + if (theRetVal == NE_OK) + { +#if defined SAL_LOG_INFO + { + char * p = ne_uri_unparse( &(pLock->uri) ); + SAL_INFO( "ucb.ucp.webdav", "UNLOCK (from store) - relative URL: <" << p << "> token: <" << pLock->token << "> succeeded." ); + ne_free( p ); + } +#endif + return true; + } + else + { +#if defined SAL_LOG_INFO + { + char * p = ne_uri_unparse( &(pLock->uri) ); + SAL_INFO( "ucb.ucp.webdav", "UNLOCK (from store) - relative URL: <" << p << "> token: <" << pLock->token << "> failed!" ); + ne_free( p ); + } +#endif + if (theRetVal == NE_AUTH) + { + // tdf#126279: see handling of NE_AUTH in HandleError + m_bNeedNewSession = true; + } + return false; + } +} + +void NeonSession::abort() +{ + SAL_INFO( "ucb.ucp.webdav", "neon commands cannot be aborted" ); +} + +ucbhelper::InternetProxyServer NeonSession::getProxySettings() const +{ + if ( m_aScheme == "http" || m_aScheme == "https" ) + { + return m_rProxyDecider.getProxy( m_aScheme, + m_aHostName, + m_nPort ); + } + else + { + return m_rProxyDecider.getProxy( m_aScheme, + OUString() /* not used */, + -1 /* not used */ ); + } +} + +namespace { + +bool containsLocktoken( const uno::Sequence< ucb::Lock > & rLocks, + const char * token ) +{ + return std::any_of(rLocks.begin(), rLocks.end(), [&token](const ucb::Lock& rLock) { + const uno::Sequence< OUString > & rTokens = rLock.LockTokens; + return std::any_of(rTokens.begin(), rTokens.end(), + [&token](const OUString& rToken) { return rToken.equalsAscii( token ); }); + }); +} + +} // namespace + +bool NeonSession::removeExpiredLocktoken( const OUString & inURL, + const DAVRequestEnvironment & rEnv ) +{ + NeonLock * theLock = m_aNeonLockStore.findByUri( inURL ); + if ( !theLock ) + return false; + + // do a lockdiscovery to check whether this lock is still valid. + try + { + // @@@ Alternative: use ne_lock_discover() => less overhead + + std::vector< DAVResource > aResources; + std::vector< OUString > aPropNames; + aPropNames.push_back( DAVProperties::LOCKDISCOVERY ); + + PROPFIND( rEnv.m_aRequestURI, DAVZERO, aPropNames, aResources, rEnv ); + + if ( aResources.empty() ) + return false; + + for ( const auto& rProp : aResources[ 0 ].properties ) + { + if ( rProp.Name == DAVProperties::LOCKDISCOVERY ) + { + uno::Sequence< ucb::Lock > aLocks; + if ( !( rProp.Value >>= aLocks ) ) + return false; + + if ( !containsLocktoken( aLocks, theLock->token ) ) + { + // expired! + break; + } + + // still valid. + return false; + } + } + + // No lockdiscovery prop in propfind result / locktoken not found + // in propfind result -> not locked + SAL_WARN( "ucb.ucp.webdav", "Removing expired lock token for <" << inURL + << "> token: " << theLock->token ); + + m_aNeonLockStore.removeLock( theLock ); + ne_lock_destroy( theLock ); + return true; + } + catch ( DAVException const & ) + { + } + return false; +} + +// Common error handler +void NeonSession::HandleError( int nError, + const OUString & inPath, + const DAVRequestEnvironment & rEnv ) +{ + // Map error code to DAVException. + switch ( nError ) + { + case NE_OK: + return; + + case NE_ERROR: // Generic error + { + const char* sErr = ne_get_error(m_pHttpSession); + OUString aText(sErr, strlen(sErr), osl_getThreadTextEncoding()); + + sal_uInt16 code = makeStatusCode( aText ); + + SAL_WARN( "ucb.ucp.webdav", "Neon returned NE_ERROR, http response status code was: " << code << " '" << aText << "'" ); + if ( SC_BAD_REQUEST <= code && code < SC_INTERNAL_SERVER_ERROR ) + { + // error codes in the range 4xx + switch ( code ) + { + case SC_LOCKED: + { + if ( m_aNeonLockStore.findByUri( + makeAbsoluteURL( inPath ) ) == nullptr ) + { + // locked by 3rd party + throw DAVException( DAVException::DAV_LOCKED ); + } + else + { + // locked by ourself + throw DAVException( DAVException::DAV_LOCKED_SELF ); + } + } + break; + case SC_PRECONDITION_FAILED: + case SC_BAD_REQUEST: + { + // Special handling for 400 and 412 status codes, which may indicate + // that a lock previously obtained by us has been released meanwhile + // by the server. Unfortunately, RFC is not clear at this point, + // thus server implementations behave different... + if ( removeExpiredLocktoken( makeAbsoluteURL( inPath ), rEnv ) ) + throw DAVException( DAVException::DAV_LOCK_EXPIRED ); + } + break; + case SC_REQUEST_TIMEOUT: + { + throw DAVException( DAVException::DAV_HTTP_TIMEOUT, + NeonUri::makeConnectionEndPointString( + m_aHostName, m_nPort ) ); + } + break; + case SC_UNAUTHORIZED: // User authentication failed on server + { + throw DAVException( DAVException::DAV_HTTP_AUTH, + NeonUri::makeConnectionEndPointString( + m_aHostName, m_nPort ) ); + } + break; + case SC_GONE: + case SC_LENGTH_REQUIRED: + case SC_REQUEST_ENTITY_TOO_LARGE: + case SC_REQUEST_URI_TOO_LONG: + case SC_UNSUPPORTED_MEDIA_TYPE: + case SC_REQUESTED_RANGE_NOT_SATISFIABLE: + case SC_EXPECTATION_FAILED: + case SC_UNPROCESSABLE_ENTITY: + case SC_FAILED_DEPENDENCY: + case SC_CONFLICT: + case SC_NOT_ACCEPTABLE: + case SC_PAYMENT_REQUIRED: + case SC_PROXY_AUTHENTICATION_REQUIRED: + default: + // set 400 error, if not one of others + code = SC_BAD_REQUEST; + [[fallthrough]]; + case SC_FORBIDDEN: + case SC_NOT_FOUND: + case SC_METHOD_NOT_ALLOWED: + throw DAVException( DAVException::DAV_HTTP_ERROR, aText, code ); + break; + } + } + else if ( SC_INTERNAL_SERVER_ERROR <= code ) + { + // deal with HTTP response status codes higher then 500 + // error codes in the range 5xx, server errors + // but there exists unofficial code in the range 1000 and up + // for example see: + // <https://support.cloudflare.com/hc/en-us/sections/200820298-Error-Pages> (retrieved 2016-10-05) + switch ( code ) + { + // the error codes case before the default case are not actively + // managed by LO + case SC_BAD_GATEWAY: + case SC_SERVICE_UNAVAILABLE: + case SC_GATEWAY_TIMEOUT: + case SC_HTTP_VERSION_NOT_SUPPORTED: + case SC_INSUFFICIENT_STORAGE: + default: + // set 500 error, if not one of others + // expand the error code + code = SC_INTERNAL_SERVER_ERROR; + [[fallthrough]]; + case SC_INTERNAL_SERVER_ERROR: + case SC_NOT_IMPLEMENTED: + throw DAVException( DAVException::DAV_HTTP_ERROR, aText, code ); + break; + } + } + else + throw DAVException( DAVException::DAV_HTTP_ERROR, aText, code ); + } + break; + case NE_LOOKUP: // Name lookup failed. + SAL_WARN( "ucb.ucp.webdav", "Name lookup failed" ); + throw DAVException( DAVException::DAV_HTTP_LOOKUP, + NeonUri::makeConnectionEndPointString( + m_aHostName, m_nPort ) ); + + case NE_AUTH: // User authentication failed on server + // m_pHttpSession could get invalidated, e.g., as result of clean_session called in + // ah_post_send in case when auth_challenge failed, which invalidates the auth_session + // which we established in Init(): the auth_session's sspi_host gets disposed, and + // next attempt to authenticate would crash in continue_sspi trying to dereference it + m_bNeedNewSession = true; + throw DAVException( DAVException::DAV_HTTP_AUTH, + NeonUri::makeConnectionEndPointString( + m_aHostName, m_nPort ) ); + + case NE_PROXYAUTH: // User authentication failed on proxy + SAL_WARN( "ucb.ucp.webdav", "DAVException::DAV_HTTP_AUTHPROXY" ); + throw DAVException( DAVException::DAV_HTTP_AUTHPROXY, + NeonUri::makeConnectionEndPointString( + m_aProxyName, m_nProxyPort ) ); + + case NE_CONNECT: // Could not connect to server + SAL_WARN( "ucb.ucp.webdav", "DAVException::DAV_HTTP_CONNECT" ); + throw DAVException( DAVException::DAV_HTTP_CONNECT, + NeonUri::makeConnectionEndPointString( + m_aHostName, m_nPort ) ); + + case NE_TIMEOUT: // Connection timed out + SAL_WARN( "ucb.ucp.webdav", "DAVException::DAV_HTTP_TIMEOUT" ); + throw DAVException( DAVException::DAV_HTTP_TIMEOUT, + NeonUri::makeConnectionEndPointString( + m_aHostName, m_nPort ) ); + + case NE_FAILED: // The precondition failed + SAL_WARN( "ucb.ucp.webdav", "The precondition failed" ); + throw DAVException( DAVException::DAV_HTTP_FAILED, + NeonUri::makeConnectionEndPointString( + m_aHostName, m_nPort ) ); + + case NE_RETRY: // Retry request (ne_end_request ONLY) + throw DAVException( DAVException::DAV_HTTP_RETRY, + NeonUri::makeConnectionEndPointString( + m_aHostName, m_nPort ) ); + + case NE_REDIRECT: + { + NeonUri aUri( ne_redirect_location( m_pHttpSession ) ); + SAL_INFO( "ucb.ucp.webdav", "DAVException::DAV_HTTP_REDIRECT: new URI: " << aUri.GetURI() ); + throw DAVException( + DAVException::DAV_HTTP_REDIRECT, aUri.GetURI() ); + } + default: + { + SAL_WARN( "ucb.ucp.webdav", "Unknown Neon error code!" ); + const char* sErr = ne_get_error(m_pHttpSession); + throw DAVException( DAVException::DAV_HTTP_ERROR, + OUString(sErr, strlen(sErr), osl_getThreadTextEncoding()) ); + } + } +} + +namespace { + +void runResponseHeaderHandler( void * userdata, + const char * value ) +{ + OUString aHeader(value, strlen(value), RTL_TEXTENCODING_ASCII_US); + sal_Int32 nPos = aHeader.indexOf( ':' ); + + if ( nPos != -1 ) + { + OUString aHeaderName( aHeader.copy( 0, nPos ) ); + + NeonRequestContext * pCtx + = static_cast< NeonRequestContext * >( userdata ); + + // Note: Empty vector means that all headers are requested. + bool bIncludeIt = pCtx->pHeaderNames->empty(); + + if ( !bIncludeIt ) + { + // Check whether this header was requested. + auto it = std::find_if(pCtx->pHeaderNames->cbegin(), pCtx->pHeaderNames->cend(), + [&aHeaderName](const OUString& rName) { + // header names are case insensitive + return rName.equalsIgnoreAsciiCase( aHeaderName ); }); + + if ( it != pCtx->pHeaderNames->end() ) + { + aHeaderName = *it; + bIncludeIt = true; + } + } + + if ( bIncludeIt ) + { + // Create & set the PropertyValue + DAVPropertyValue thePropertyValue; + // header names are case insensitive, so are the + // corresponding property names. + thePropertyValue.Name = aHeaderName.toAsciiLowerCase(); + thePropertyValue.IsCaseSensitive = false; + + if ( nPos < aHeader.getLength() ) + thePropertyValue.Value <<= aHeader.copy( nPos + 1 ).trim(); + + // Add the newly created PropertyValue + pCtx->pResource->properties.push_back( thePropertyValue ); + } + } +} + +} // namespace + +int NeonSession::GET( ne_session * sess, + const char * uri, + ne_block_reader reader, + bool getheaders, + void * userdata ) +{ + //struct get_context ctx; + ne_request * req = ne_request_create( sess, "GET", uri ); + int ret; + + ne_decompress * dc + = ne_decompress_reader( req, ne_accept_2xx, reader, userdata ); + + { + osl::Guard< osl::Mutex > theGlobalGuard(getGlobalNeonMutex()); + ret = ne_request_dispatch( req ); + } + + if ( getheaders ) + { + void *cursor = nullptr; + const char *name, *value; + while ( ( cursor = ne_response_header_iterate( + req, cursor, &name, &value ) ) != nullptr ) + { + char buffer[8192]; + + SAL_INFO( "ucb.ucp.webdav", "GET - received header: " << name << ": " << value ); + ne_snprintf(buffer, sizeof buffer, "%s: %s", name, value); + runResponseHeaderHandler(userdata, buffer); + } + } + + if ( ret == NE_OK && ne_get_status( req )->klass != 2 ) + ret = NE_ERROR; + + if ( dc != nullptr ) + ne_decompress_destroy(dc); + + ne_request_destroy( req ); + return ret; +} + +int NeonSession::GET0( ne_session * sess, + const char * uri, + bool getheaders, + void * userdata ) +{ + //struct get_context ctx; + ne_request * req = ne_request_create( sess, "GET", uri ); + int ret; + + { + osl::Guard< osl::Mutex > theGlobalGuard(getGlobalNeonMutex()); + ret = ne_request_dispatch( req ); + } + + if ( getheaders ) + { + void *cursor = nullptr; + const char *name, *value; + while ( ( cursor = ne_response_header_iterate( + req, cursor, &name, &value ) ) != nullptr ) + { + char buffer[8192]; + + SAL_INFO( "ucb.ucp.webdav", "GET - received header: " << name << ": " << value ); + ne_snprintf(buffer, sizeof buffer, "%s: %s", name, value); + runResponseHeaderHandler(userdata, buffer); + } + } + + if ( ret == NE_OK && ne_get_status( req )->klass != 2 ) + ret = NE_ERROR; + + ne_request_destroy( req ); + return ret; +} + +int NeonSession::PUT( ne_session * sess, + const char * uri, + const char * buffer, + size_t size) +{ + ne_request * req = ne_request_create( sess, "PUT", uri ); + int ret; + + // tdf#99246 + // extract the path of uri + // ne_lock_using_resource below compares path, ignores all the rest. + // in case of Web proxy active, this function uri parameter is instead absolute + ne_uri aUri; + ne_uri_parse( uri, &aUri ); + ne_lock_using_resource( req, aUri.path, 0 ); + ne_lock_using_parent( req, uri ); + + ne_set_request_body_buffer( req, buffer, size ); + + { + osl::Guard< osl::Mutex > theGlobalGuard(getGlobalNeonMutex()); + ret = ne_request_dispatch( req ); + } + + if ( ret == NE_OK && ne_get_status( req )->klass != 2 ) + ret = NE_ERROR; + + ne_request_destroy( req ); + return ret; +} + +int NeonSession::POST( ne_session * sess, + const char * uri, + const char * buffer, + ne_block_reader reader, + void * userdata, + const OUString & rContentType, + const OUString & rReferer ) +{ + ne_request * req = ne_request_create( sess, "POST", uri ); + //struct get_context ctx; + int ret; + + RequestDataMap * pData = nullptr; + + if ( !rContentType.isEmpty() || !rReferer.isEmpty() ) + { + // Remember contenttype and referer. Data will be added to HTTP request + // header in 'PreSendRequest' callback. + pData = static_cast< RequestDataMap* >( m_pRequestData ); + (*pData)[ req ] = RequestData( rContentType, rReferer ); + } + + //ctx.total = -1; + //ctx.fd = fd; + //ctx.error = 0; + //ctx.session = sess; + + ///* Read the value of the Content-Length header into ctx.total */ + //ne_add_response_header_handler( req, "Content-Length", + // ne_handle_numeric_header, &ctx.total ); + + ne_add_response_body_reader( req, ne_accept_2xx, reader, userdata ); + + ne_set_request_body_buffer( req, buffer, strlen( buffer ) ); + + { + osl::Guard< osl::Mutex > theGlobalGuard(getGlobalNeonMutex()); + ret = ne_request_dispatch( req ); + } + + //if ( ctx.error ) + // ret = NE_ERROR; + //else + if ( ret == NE_OK && ne_get_status( req )->klass != 2 ) + ret = NE_ERROR; + + ne_request_destroy( req ); + + if ( pData ) + { + // Remove request data from session's list. + RequestDataMap::iterator it = pData->find( req ); + if ( it != pData->end() ) + pData->erase( it ); + } + + return ret; +} + +bool +NeonSession::getDataFromInputStream( + const uno::Reference< io::XInputStream > & xStream, + uno::Sequence< sal_Int8 > & rData, + bool bAppendTrailingZeroByte ) +{ + if ( xStream.is() ) + { + uno::Reference< io::XSeekable > xSeekable( xStream, uno::UNO_QUERY ); + if ( xSeekable.is() ) + { + try + { + sal_Int32 nSize + = sal::static_int_cast<sal_Int32>(xSeekable->getLength()); + sal_Int32 nRead + = xStream->readBytes( rData, nSize ); + + if ( nRead == nSize ) + { + if ( bAppendTrailingZeroByte ) + { + rData.realloc( nSize + 1 ); + rData[ nSize ] = sal_Int8( 0 ); + } + return true; + } + } + catch ( io::NotConnectedException const & ) + { + // readBytes + } + catch ( io::BufferSizeExceededException const & ) + { + // readBytes + } + catch ( io::IOException const & ) + { + // getLength, readBytes + } + } + else + { + try + { + uno::Sequence< sal_Int8 > aBuffer; + sal_Int32 nPos = 0; + + sal_Int32 nRead = xStream->readSomeBytes( aBuffer, 65536 ); + while ( nRead > 0 ) + { + if ( rData.getLength() < ( nPos + nRead ) ) + rData.realloc( nPos + nRead ); + + aBuffer.realloc( nRead ); + memcpy( static_cast<void*>( rData.getArray() + nPos ), + static_cast<const void*>(aBuffer.getConstArray()), + nRead ); + nPos += nRead; + + aBuffer.realloc( 0 ); + nRead = xStream->readSomeBytes( aBuffer, 65536 ); + } + + if ( bAppendTrailingZeroByte ) + { + rData.realloc( nPos + 1 ); + rData[ nPos ] = sal_Int8( 0 ); + } + return true; + } + catch ( io::NotConnectedException const & ) + { + // readBytes + } + catch ( io::BufferSizeExceededException const & ) + { + // readBytes + } + catch ( io::IOException const & ) + { + // readBytes + } + } + } + return false; +} + +bool +NeonSession::isDomainMatch( const OUString& certHostName ) +{ + OUString hostName = getHostName(); + + if (hostName.equalsIgnoreAsciiCase( certHostName ) ) + return true; + + if ( certHostName.startsWith( "*" ) && + hostName.getLength() >= certHostName.getLength() ) + { + OUString cmpStr = certHostName.copy( 1 ); + + if ( hostName.matchIgnoreAsciiCase( + cmpStr, hostName.getLength() - cmpStr.getLength() ) ) + return true; + } + return false; +} + +OUString NeonSession::makeAbsoluteURL( OUString const & rURL ) const +{ + try + { + // Is URL relative or already absolute? + if ( !rURL.isEmpty() && rURL[ 0 ] != '/' ) + { + // absolute. + return rURL; + } + else + { + ne_uri aUri = {}; + + ne_fill_server_uri( m_pHttpSession, &aUri ); + aUri.path + = ne_strdup( OUStringToOString( + rURL, RTL_TEXTENCODING_UTF8 ).getStr() ); + NeonUri aNeonUri( &aUri ); + ne_uri_free( &aUri ); + return aNeonUri.GetURI(); + } + } + catch ( DAVException const & ) + { + } + // error. + return OUString(); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/ucb/source/ucp/webdav-neon/NeonSession.hxx b/ucb/source/ucp/webdav-neon/NeonSession.hxx new file mode 100644 index 000000000..51b477373 --- /dev/null +++ b/ucb/source/ucp/webdav-neon/NeonSession.hxx @@ -0,0 +1,288 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/************************************************************************* + * + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * Copyright 2000, 2010 Oracle and/or its affiliates. + * + * OpenOffice.org - a multi-platform office productivity suite + * + * This file is part of OpenOffice.org. + * + * OpenOffice.org is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License version 3 + * only, as published by the Free Software Foundation. + * + * OpenOffice.org is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License version 3 for more details + * (a copy is included in the LICENSE file that accompanied this code). + * + * You should have received a copy of the GNU Lesser General Public License + * version 3 along with OpenOffice.org. If not, see + * <http://www.openoffice.org/license.html> + * for a copy of the LGPLv3 License. + * + ************************************************************************/ + +#ifndef INCLUDED_UCB_SOURCE_UCP_WEBDAV_NEON_NEONSESSION_HXX +#define INCLUDED_UCB_SOURCE_UCP_WEBDAV_NEON_NEONSESSION_HXX + +#include <config_lgpl.h> +#include <vector> +#include <osl/mutex.hxx> +#include "DAVSession.hxx" +#include "NeonTypes.hxx" +#include "NeonLockStore.hxx" +#include <com/sun/star/lang/XMultiServiceFactory.hpp> + +namespace ucbhelper { class ProxyDecider; } + +namespace webdav_ucp +{ + +// A DAVSession implementation using the neon/expat library +class NeonSession : public DAVSession +{ +private: + osl::Mutex m_aMutex; + OUString m_aScheme; + OUString m_aHostName; + OUString m_aProxyName; + sal_Int32 m_nPort; + sal_Int32 m_nProxyPort; + css::uno::Sequence< css::beans::NamedValue > m_aFlags; + HttpSession * m_pHttpSession; + bool m_bNeedNewSession = false; // Something happened that could invalidate m_pHttpSession + void * m_pRequestData; + const ucbhelper::InternetProxyDecider & m_rProxyDecider; + + // @@@ This should really be per-request data. But Neon currently + // (0.23.5) has no interface for passing per-request user data. + // Theoretically, a NeonSession instance could handle multiple requests + // at a time --currently it doesn't. Thus this is not an issue at the + // moment. + DAVRequestEnvironment m_aEnv; + + static bool m_bGlobalsInited; + static NeonLockStore m_aNeonLockStore; + +protected: + virtual ~NeonSession() override; + +public: + /// @throws std::exception + NeonSession( const rtl::Reference< DAVSessionFactory > & rSessionFactory, + const OUString& inUri, + const css::uno::Sequence< css::beans::NamedValue >& rFlags, + const ucbhelper::InternetProxyDecider & rProxyDecider ); + + // DAVSession methods + virtual bool CanUse( const OUString & inPath, + const css::uno::Sequence< css::beans::NamedValue >& rFlags ) override; + + virtual bool UsesProxy() override; + + const DAVRequestEnvironment & getRequestEnvironment() const + { return m_aEnv; } + + virtual void + OPTIONS( const OUString & inPath, + DAVOptions& rOptions, // contains the name+values + const DAVRequestEnvironment & rEnv ) override; + + // allprop & named + virtual void + PROPFIND( const OUString & inPath, + const Depth inDepth, + const std::vector< OUString > & inPropNames, + std::vector< DAVResource > & ioResources, + const DAVRequestEnvironment & rEnv ) override; + + // propnames + virtual void + PROPFIND( const OUString & inPath, + const Depth inDepth, + std::vector< DAVResourceInfo >& ioResInfo, + const DAVRequestEnvironment & rEnv ) override; + + virtual void + PROPPATCH( const OUString & inPath, + const std::vector< ProppatchValue > & inValues, + const DAVRequestEnvironment & rEnv ) override; + + virtual void + HEAD( const OUString & inPath, + const std::vector< OUString > & inHeaderNames, + DAVResource & ioResource, + const DAVRequestEnvironment & rEnv ) override; + + virtual css::uno::Reference< css::io::XInputStream > + GET( const OUString & inPath, + const DAVRequestEnvironment & rEnv ) override; + + virtual void + GET( const OUString & inPath, + css::uno::Reference< css::io::XOutputStream > & ioOutputStream, + const DAVRequestEnvironment & rEnv ) override; + + virtual css::uno::Reference< css::io::XInputStream > + GET( const OUString & inPath, + const std::vector< OUString > & inHeaderNames, + DAVResource & ioResource, + const DAVRequestEnvironment & rEnv ) override; + + virtual void + GET0( const OUString & inPath, + const std::vector< OUString > & inHeaderNames, + DAVResource & ioResource, + const DAVRequestEnvironment & rEnv ) override; + + virtual void + GET( const OUString & inPath, + css::uno::Reference< css::io::XOutputStream > & ioOutputStream, + const std::vector< OUString > & inHeaderNames, + DAVResource & ioResource, + const DAVRequestEnvironment & rEnv ) override; + + virtual void + PUT( const OUString & inPath, + const css::uno::Reference< css::io::XInputStream > & inInputStream, + const DAVRequestEnvironment & rEnv ) override; + + 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 ) override; + + 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 ) override; + + virtual void + MKCOL( const OUString & inPath, + const DAVRequestEnvironment & rEnv ) override; + + virtual void + COPY( const OUString & inSourceURL, + const OUString & inDestinationURL, + const DAVRequestEnvironment & rEnv, + bool inOverWrite ) override; + + virtual void + MOVE( const OUString & inSourceURL, + const OUString & inDestinationURL, + const DAVRequestEnvironment & rEnv, + bool inOverWrite ) override; + + virtual void DESTROY( const OUString & inPath, + const DAVRequestEnvironment & rEnv ) override; + + // set new lock. + virtual void LOCK( const OUString & inURL, + css::ucb::Lock & inLock, + const DAVRequestEnvironment & rEnv ) override; + + virtual void UNLOCK( const OUString & inURL, + const DAVRequestEnvironment & rEnv ) override; + + // helpers + virtual void abort() override; + + const OUString & getHostName() const { return m_aHostName; } + + ::uno::Reference< ::uno::XComponentContext > const & getComponentContext() const + { return m_xFactory->getComponentContext(); } + + const void * getRequestData() const { return m_pRequestData; } + + bool isDomainMatch( const OUString& certHostName ); + + int CertificationNotify(const ne_ssl_certificate *cert); + + int NeonAuth(const char* inAuthProtocol, const char* inRealm, + int attempt, char* inoutUserName, char * inoutPassWord); + + void PreSendRequest(ne_request* req, ne_buffer* headers); + +private: + friend class NeonLockStore; + + /// @throws css::uno::RuntimeException + void Init(); + + /// @throws css::uno::RuntimeException + void Init( const DAVRequestEnvironment & rEnv ); + + // ret: true => retry request. + /// @throws std::exception + void HandleError( int nError, + const OUString & inPath, + const DAVRequestEnvironment & rEnv ); + + ucbhelper::InternetProxyServer getProxySettings() const; + + bool removeExpiredLocktoken( const OUString & inURL, + const DAVRequestEnvironment & rEnv ); + + // refresh lock, called by NeonLockStore::refreshLocks + bool LOCK( NeonLock * pLock, + sal_Int32 & rlastChanceToSendRefreshRequest ); + + // unlock, called by NeonLockStore::~NeonLockStore + bool UNLOCK( NeonLock * pLock ); + + // low level GET implementation, used by public GET implementations + static int GET( ne_session * sess, + const char * uri, + ne_block_reader reader, + bool getheaders, + void * userdata ); + + // low level GET implementation, used by public GET implementations + // used as a HEAD substitute when head is not available + static int GET0( ne_session * sess, + const char * uri, + bool getheaders, + void * userdata ); + + // Buffer-based PUT implementation. Neon only has file descriptor- + // based API. + static int PUT( ne_session * sess, + const char * uri, + const char * buffer, + size_t size ); + + // Buffer-based POST implementation. Neon only has file descriptor- + // based API. + int POST( ne_session * sess, + const char * uri, + const char * buffer, + ne_block_reader reader, + void * userdata, + const OUString & rContentType, + const OUString & rReferer ); + + // Helper: XInputStream -> Sequence< sal_Int8 > + static bool getDataFromInputStream( + const css::uno::Reference< css::io::XInputStream > & xStream, + css::uno::Sequence< sal_Int8 > & rData, + bool bAppendTrailingZeroByte ); + + OUString makeAbsoluteURL( OUString const & rURL ) const; +}; + +osl::Mutex& getGlobalNeonMutex(); + +} // namespace webdav_ucp + +#endif // INCLUDED_UCB_SOURCE_UCP_WEBDAV_NEON_NEONSESSION_HXX + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/ucb/source/ucp/webdav-neon/NeonTypes.hxx b/ucb/source/ucp/webdav-neon/NeonTypes.hxx new file mode 100644 index 000000000..3e2a7c575 --- /dev/null +++ b/ucb/source/ucp/webdav-neon/NeonTypes.hxx @@ -0,0 +1,57 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/************************************************************************* + * + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * Copyright 2000, 2010 Oracle and/or its affiliates. + * + * OpenOffice.org - a multi-platform office productivity suite + * + * This file is part of OpenOffice.org. + * + * OpenOffice.org is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License version 3 + * only, as published by the Free Software Foundation. + * + * OpenOffice.org is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License version 3 for more details + * (a copy is included in the LICENSE file that accompanied this code). + * + * You should have received a copy of the GNU Lesser General Public License + * version 3 along with OpenOffice.org. If not, see + * <http://www.openoffice.org/license.html> + * for a copy of the LGPLv3 License. + * + ************************************************************************/ + +#ifndef INCLUDED_UCB_SOURCE_UCP_WEBDAV_NEON_NEONTYPES_HXX +#define INCLUDED_UCB_SOURCE_UCP_WEBDAV_NEON_NEONTYPES_HXX + +#include <config_lgpl.h> +#include <ne_session.h> +#include <ne_utils.h> +#include <ne_basic.h> +#include <ne_props.h> + +#if defined(__GNUC__) +# pragma GCC diagnostic push +# pragma GCC diagnostic ignored "-Wshadow" +#endif +#include <ne_locks.h> +#if defined(__GNUC__) +# pragma GCC diagnostic pop +#endif + +typedef ne_session HttpSession; +typedef ne_status HttpStatus; + +typedef ne_propname NeonPropName; +typedef ne_prop_result_set NeonPropFindResultSet; + +typedef struct ne_lock NeonLock; + +#endif // INCLUDED_UCB_SOURCE_UCP_WEBDAV_NEON_NEONTYPES_HXX + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/ucb/source/ucp/webdav-neon/NeonUri.cxx b/ucb/source/ucp/webdav-neon/NeonUri.cxx new file mode 100644 index 000000000..c1c263421 --- /dev/null +++ b/ucb/source/ucp/webdav-neon/NeonUri.cxx @@ -0,0 +1,291 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/************************************************************************* + * + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * Copyright 2000, 2010 Oracle and/or its affiliates. + * + * OpenOffice.org - a multi-platform office productivity suite + * + * This file is part of OpenOffice.org. + * + * OpenOffice.org is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License version 3 + * only, as published by the Free Software Foundation. + * + * OpenOffice.org is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License version 3 for more details + * (a copy is included in the LICENSE file that accompanied this code). + * + * You should have received a copy of the GNU Lesser General Public License + * version 3 along with OpenOffice.org. If not, see + * <http://www.openoffice.org/license.html> + * for a copy of the LGPLv3 License. + * + ************************************************************************/ + +#include <sal/config.h> + +#include <rtl/uri.hxx> +#include <rtl/ustring.hxx> +#include <rtl/ustrbuf.hxx> +#include <ne_alloc.h> +#include "NeonUri.hxx" +#include "DAVException.hxx" + +#include "../inc/urihelper.hxx" + +using namespace webdav_ucp; + +// FIXME: not sure whether initializing a ne_uri statically is supposed to work +// the string fields of ne_uri are char*, not const char* + +#ifdef __GNUC__ +#pragma GCC diagnostic ignored "-Wwrite-strings" +#endif + +namespace { + +const ne_uri g_sUriDefaultsHTTP = { const_cast<char *>("http"), + nullptr, + nullptr, + DEFAULT_HTTP_PORT, + nullptr, + nullptr, + nullptr }; +const ne_uri g_sUriDefaultsHTTPS = { const_cast<char *>("https"), + nullptr, + nullptr, + DEFAULT_HTTPS_PORT, + nullptr, + nullptr, + nullptr }; +const ne_uri g_sUriDefaultsFTP = { const_cast<char *>("ftp"), + nullptr, + nullptr, + DEFAULT_FTP_PORT, + nullptr, + nullptr, + nullptr }; +} // namespace + +NeonUri::NeonUri( const ne_uri * inUri ) +{ + if ( inUri == nullptr ) + throw DAVException( DAVException::DAV_INVALID_ARG ); + + char * uri = ne_uri_unparse( inUri ); + + if ( uri == nullptr ) + throw DAVException( DAVException::DAV_INVALID_ARG ); + + init( OString( uri ), inUri ); + ne_free( uri ); + + calculateURI(); +} + +NeonUri::NeonUri( const OUString & inUri ) +{ + if ( inUri.isEmpty() ) + throw DAVException( DAVException::DAV_INVALID_ARG ); + + // #i77023# + OUString aEscapedUri( ucb_impl::urihelper::encodeURI( inUri ) ); + + OString theInputUri( + aEscapedUri.getStr(), aEscapedUri.getLength(), RTL_TEXTENCODING_UTF8 ); + + ne_uri theUri; + if ( ne_uri_parse( theInputUri.getStr(), &theUri ) != 0 ) + { + ne_uri_free( &theUri ); + throw DAVException( DAVException::DAV_INVALID_ARG ); + } + + init( theInputUri, &theUri ); + ne_uri_free( &theUri ); + + calculateURI(); +} + +void NeonUri::init( const OString & rUri, const ne_uri * pUri ) +{ + // Complete URI. + const ne_uri * pUriDefs + = rUri.matchIgnoreAsciiCase( "ftp:" ) ? + &g_sUriDefaultsFTP : + rUri.matchIgnoreAsciiCase( "https:" ) ? + &g_sUriDefaultsHTTPS : + &g_sUriDefaultsHTTP; + + mScheme = OStringToOUString( + pUri->scheme ? pUri->scheme : pUriDefs->scheme, + RTL_TEXTENCODING_UTF8 ); + mUserInfo = OStringToOUString( + pUri->userinfo ? pUri->userinfo : pUriDefs->userinfo, + RTL_TEXTENCODING_UTF8 ); + mHostName = OStringToOUString( + pUri->host ? pUri->host : pUriDefs->host, + RTL_TEXTENCODING_UTF8 ); + mPort = pUri->port > 0 ? pUri->port : pUriDefs->port; + mPath = OStringToOUString( + pUri->path ? pUri->path : pUriDefs->path, + RTL_TEXTENCODING_UTF8 ); + + if ( pUri->query ) + { + mPath += "?" + OStringToOUString( pUri->query, RTL_TEXTENCODING_UTF8 ); + } + + if ( pUri->fragment ) + { + mPath += "#" + OStringToOUString( pUri->fragment, RTL_TEXTENCODING_UTF8 ); + } +} + +void NeonUri::calculateURI () +{ + OUStringBuffer aBuf( 256 ); + aBuf.append( mScheme ); + aBuf.append( "://" ); + if ( !mUserInfo.isEmpty() ) + { + //TODO! differentiate between empty and missing userinfo + aBuf.append( mUserInfo ); + aBuf.append( "@" ); + } + // Is host a numeric IPv6 address? + if ( ( mHostName.indexOf( ':' ) != -1 ) && + ( mHostName[ 0 ] != '[' ) ) + { + aBuf.append( "[" ); + aBuf.append( mHostName ); + aBuf.append( "]" ); + } + else + { + aBuf.append( mHostName ); + } + + // append port, but only, if not default port. + bool bAppendPort = true; + switch ( mPort ) + { + case DEFAULT_HTTP_PORT: + bAppendPort = mScheme != "http"; + break; + + case DEFAULT_HTTPS_PORT: + bAppendPort = mScheme != "https"; + break; + + case DEFAULT_FTP_PORT: + bAppendPort = mScheme != "ftp"; + break; + } + if ( bAppendPort ) + { + aBuf.append( ":" ); + aBuf.append( OUString::number( mPort ) ); + } + aBuf.append( mPath ); + + mURI = aBuf.makeStringAndClear(); +} + +OUString NeonUri::GetPathBaseName () const +{ + sal_Int32 nPos = mPath.lastIndexOf ('/'); + sal_Int32 nTrail = 0; + if (nPos == mPath.getLength () - 1) + { + // Trailing slash found. Skip. + nTrail = 1; + nPos = mPath.lastIndexOf ('/', nPos); + } + if (nPos != -1) + { + OUString aTemp( + mPath.copy (nPos + 1, mPath.getLength () - nPos - 1 - nTrail) ); + + // query, fragment present? + nPos = aTemp.indexOf( '?' ); + if ( nPos == -1 ) + nPos = aTemp.indexOf( '#' ); + + if ( nPos != -1 ) + aTemp = aTemp.copy( 0, nPos ); + + return aTemp; + } + else + return "/"; +} + +bool NeonUri::operator== ( const NeonUri & rOther ) const +{ + return ( mURI == rOther.mURI ); +} + +OUString NeonUri::GetPathBaseNameUnescaped () const +{ + return unescape( GetPathBaseName() ); +} + +void NeonUri::AppendPath (const OUString& rPath) +{ + if (mPath.lastIndexOf ('/') != mPath.getLength () - 1) + mPath += "/"; + + mPath += rPath; + calculateURI (); +}; + +// static +OUString NeonUri::escapeSegment( const OUString& segment ) +{ + return rtl::Uri::encode( segment, + rtl_UriCharClassPchar, + rtl_UriEncodeIgnoreEscapes, + RTL_TEXTENCODING_UTF8 ); +} + +// static +OUString NeonUri::unescape( const OUString& segment ) +{ + return rtl::Uri::decode( segment, + rtl_UriDecodeWithCharset, + RTL_TEXTENCODING_UTF8 ); +} + +// static +OUString NeonUri::makeConnectionEndPointString( + const OUString & rHostName, int nPort ) +{ + OUStringBuffer aBuf; + + // Is host a numeric IPv6 address? + if ( ( rHostName.indexOf( ':' ) != -1 ) && + ( rHostName[ 0 ] != '[' ) ) + { + aBuf.append( "[" ); + aBuf.append( rHostName ); + aBuf.append( "]" ); + } + else + { + aBuf.append( rHostName ); + } + + if ( ( nPort != DEFAULT_HTTP_PORT ) && ( nPort != DEFAULT_HTTPS_PORT ) ) + { + aBuf.append( ":" ); + aBuf.append( OUString::number( nPort ) ); + } + return aBuf.makeStringAndClear(); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/ucb/source/ucp/webdav-neon/NeonUri.hxx b/ucb/source/ucp/webdav-neon/NeonUri.hxx new file mode 100644 index 000000000..fae505e50 --- /dev/null +++ b/ucb/source/ucp/webdav-neon/NeonUri.hxx @@ -0,0 +1,102 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/************************************************************************* + * + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * Copyright 2000, 2010 Oracle and/or its affiliates. + * + * OpenOffice.org - a multi-platform office productivity suite + * + * This file is part of OpenOffice.org. + * + * OpenOffice.org is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License version 3 + * only, as published by the Free Software Foundation. + * + * OpenOffice.org is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License version 3 for more details + * (a copy is included in the LICENSE file that accompanied this code). + * + * You should have received a copy of the GNU Lesser General Public License + * version 3 along with OpenOffice.org. If not, see + * <http://www.openoffice.org/license.html> + * for a copy of the LGPLv3 License. + * + ************************************************************************/ +#ifndef INCLUDED_UCB_SOURCE_UCP_WEBDAV_NEON_NEONURI_HXX +#define INCLUDED_UCB_SOURCE_UCP_WEBDAV_NEON_NEONURI_HXX + +#include <config_lgpl.h> +#include <ne_uri.h> +#include <rtl/ustring.hxx> +#include "DAVException.hxx" + +namespace webdav_ucp +{ + +#define DEFAULT_HTTP_PORT 80 +#define DEFAULT_HTTPS_PORT 443 +#define DEFAULT_FTP_PORT 21 + +// A URI implementation for use with the neon/expat library +class NeonUri +{ + private: + OUString mURI; + OUString mScheme; + OUString mUserInfo; + OUString mHostName; + sal_Int32 mPort; + OUString mPath; + + void init( const OString & rUri, const ne_uri * pUri ); + void calculateURI (); + + public: + /// @throws DAVException + explicit NeonUri( const OUString & inUri ); + /// @throws DAVException + explicit NeonUri( const ne_uri * inUri ); + + bool operator== ( const NeonUri & rOther ) const; + bool operator!= ( const NeonUri & rOther ) const + { return !operator==( rOther ); } + + const OUString & GetURI() const + { return mURI; }; + const OUString & GetScheme() const + { return mScheme; }; + const OUString & GetUserInfo() const + { return mUserInfo; }; + const OUString & GetHost() const + { return mHostName; }; + sal_Int32 GetPort() const + { return mPort; }; + const OUString & GetPath() const + { return mPath; }; + + OUString GetPathBaseName() const; + + OUString GetPathBaseNameUnescaped() const; + + void SetScheme (const OUString& scheme) + { mScheme = scheme; calculateURI (); }; + + void AppendPath (const OUString& rPath); + + static OUString escapeSegment( const OUString& segment ); + static OUString unescape( const OUString& string ); + + // "host:port", omit ":port" for port 80 and 443 + static OUString makeConnectionEndPointString( + const OUString & rHostName, + int nPort ); +}; + +} // namespace webdav_ucp + +#endif // INCLUDED_UCB_SOURCE_UCP_WEBDAV_NEON_NEONURI_HXX + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/ucb/source/ucp/webdav-neon/PropertyMap.hxx b/ucb/source/ucp/webdav-neon/PropertyMap.hxx new file mode 100644 index 000000000..159651085 --- /dev/null +++ b/ucb/source/ucp/webdav-neon/PropertyMap.hxx @@ -0,0 +1,68 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/************************************************************************* + * + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * Copyright 2000, 2010 Oracle and/or its affiliates. + * + * OpenOffice.org - a multi-platform office productivity suite + * + * This file is part of OpenOffice.org. + * + * OpenOffice.org is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License version 3 + * only, as published by the Free Software Foundation. + * + * OpenOffice.org is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License version 3 for more details + * (a copy is included in the LICENSE file that accompanied this code). + * + * You should have received a copy of the GNU Lesser General Public License + * version 3 along with OpenOffice.org. If not, see + * <http://www.openoffice.org/license.html> + * for a copy of the LGPLv3 License. + * + ************************************************************************/ + +#ifndef INCLUDED_UCB_SOURCE_UCP_WEBDAV_NEON_PROPERTYMAP_HXX +#define INCLUDED_UCB_SOURCE_UCP_WEBDAV_NEON_PROPERTYMAP_HXX + +#include <config_lgpl.h> +#include <com/sun/star/beans/Property.hpp> +#include <unordered_set> + +namespace webdav_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; + +} + +#endif + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/ucb/source/ucp/webdav-neon/PropfindCache.cxx b/ucb/source/ucp/webdav-neon/PropfindCache.cxx new file mode 100644 index 000000000..a54003661 --- /dev/null +++ b/ucb/source/ucp/webdav-neon/PropfindCache.cxx @@ -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/. + */ + +#include <osl/time.h> +#include "PropfindCache.hxx" + +namespace webdav_ucp +{ + + // PropertyNames implementation + + PropertyNames::PropertyNames() : + m_nStaleTime( 0 ), + m_sURL(), + m_aPropertiesNames() + { + } + + PropertyNames::PropertyNames( const OUString& rURL ) : + m_nStaleTime( 0 ), + m_sURL( rURL ), + m_aPropertiesNames() + { + } + + //PropertyNamesCache implementation + + PropertyNamesCache::PropertyNamesCache() + { + } + + PropertyNamesCache::~PropertyNamesCache() + { + } + + bool PropertyNamesCache::getCachedPropertyNames( const OUString& rURL, PropertyNames& rCacheElement ) + { + // search the URL in the static map + osl::MutexGuard aGuard( m_aMutex ); + PropNameCache::const_iterator 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 ) + { + osl::MutexGuard aGuard( m_aMutex ); + PropNameCache::const_iterator it = m_aTheCache.find( rURL ); + if ( it != m_aTheCache.end() ) + { + m_aTheCache.erase( it ); + } + } + + void PropertyNamesCache::addCachePropertyNames( PropertyNames& rCacheElement ) + { + osl::MutexGuard aGuard( m_aMutex ); + OUString aURL( rCacheElement.getURL() ); + TimeValue t1; + osl_getSystemTime( &t1 ); + rCacheElement.setStaleTime( t1.Seconds + 10 ); + + 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-neon/PropfindCache.hxx b/ucb/source/ucp/webdav-neon/PropfindCache.hxx new file mode 100644 index 000000000..bcbdff93e --- /dev/null +++ b/ucb/source/ucp/webdav-neon/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 <osl/mutex.hxx> +#include <map> +#include <vector> + +#include "DAVResource.hxx" + +namespace webdav_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 final + { + /// 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( const OUString& rURL ); + + 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() const { 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 final + { + PropNameCache m_aTheCache; + osl::Mutex m_aMutex; + + public: + PropertyNamesCache(); + ~PropertyNamesCache(); + + bool getCachedPropertyNames( const OUString& URL, PropertyNames& rCacheElement ); + void removeCachedPropertyNames( const OUString& URL ); + void addCachePropertyNames( PropertyNames& rCacheElement ); + }; + +} + +#endif + +/* vim:set shiftwidth=4 softtabstop=4 expandtab cinoptions=b1,g0,N-s cinkeys+=0=break: */ diff --git a/ucb/source/ucp/webdav-neon/UCBDeadPropertyValue.cxx b/ucb/source/ucp/webdav-neon/UCBDeadPropertyValue.cxx new file mode 100644 index 000000000..6a06f0dcb --- /dev/null +++ b/ucb/source/ucp/webdav-neon/UCBDeadPropertyValue.cxx @@ -0,0 +1,509 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/************************************************************************* + * + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * Copyright 2000, 2010 Oracle and/or its affiliates. + * + * OpenOffice.org - a multi-platform office productivity suite + * + * This file is part of OpenOffice.org. + * + * OpenOffice.org is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License version 3 + * only, as published by the Free Software Foundation. + * + * OpenOffice.org is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License version 3 for more details + * (a copy is included in the LICENSE file that accompanied this code). + * + * You should have received a copy of the GNU Lesser General Public License + * version 3 along with OpenOffice.org. If not, see + * <http://www.openoffice.org/license.html> + * for a copy of the LGPLv3 License. + * + ************************************************************************/ + +#include <config_lgpl.h> +#include <string.h> +#include <ne_xml.h> +#include <rtl/ustrbuf.hxx> +#include <sal/log.hxx> +#include "UCBDeadPropertyValue.hxx" +#include <memory> + +using namespace webdav_ucp; +using namespace com::sun::star; + +namespace { + +struct UCBDeadPropertyValueParseContext +{ + std::unique_ptr<OUString> pType; + std::unique_ptr<OUString> pValue; + + UCBDeadPropertyValueParseContext() {} +}; + +} + +static const char aTypeString[] = "string"; +static const char aTypeLong[] = "long"; +static const char aTypeShort[] = "short"; +static const char aTypeBoolean[] = "boolean"; +static const char aTypeChar[] = "char"; +static const char aTypeByte[] = "byte"; +static const char aTypeHyper[] = "hyper"; +static const char aTypeFloat[] = "float"; +static const char aTypeDouble[] = "double"; + +static const char aXMLPre[] = "<ucbprop><type>"; +static const char aXMLMid[] = "</type><value>"; +static const char aXMLEnd[] = "</value></ucbprop>"; + + +#define STATE_TOP (1) + +#define STATE_UCBPROP (STATE_TOP) +#define STATE_TYPE (STATE_TOP + 1) +#define STATE_VALUE (STATE_TOP + 2) + + +extern "C" { + +static int UCBDeadPropertyValue_startelement_callback( + void *, + int parent, + const char * /*nspace*/, + const char *name, + const char ** ) +{ + if ( name != nullptr ) + { + switch ( parent ) + { + case NE_XML_STATEROOT: + if ( strcmp( name, "ucbprop" ) == 0 ) + return STATE_UCBPROP; + break; + + case STATE_UCBPROP: + if ( strcmp( name, "type" ) == 0 ) + return STATE_TYPE; + else if ( strcmp( name, "value" ) == 0 ) + return STATE_VALUE; + break; + } + } + return NE_XML_DECLINE; +} + + +static int UCBDeadPropertyValue_chardata_callback( + void *userdata, + int state, + const char *buf, + size_t len ) +{ + UCBDeadPropertyValueParseContext * pCtx + = static_cast< UCBDeadPropertyValueParseContext * >( userdata ); + + switch ( state ) + { + case STATE_TYPE: + assert( !pCtx->pType && + "UCBDeadPropertyValue_endelement_callback - " + "Type already set!" ); + pCtx->pType.reset( new OUString( buf, len, RTL_TEXTENCODING_ASCII_US ) ); + break; + + case STATE_VALUE: + assert( !pCtx->pValue && + "UCBDeadPropertyValue_endelement_callback - " + "Value already set!" ); + pCtx->pValue.reset( new OUString( buf, len, RTL_TEXTENCODING_ASCII_US ) ); + break; + } + return 0; // zero to continue, non-zero to abort parsing +} + + +static int UCBDeadPropertyValue_endelement_callback( + void *userdata, + int state, + const char *, + const char * ) +{ + UCBDeadPropertyValueParseContext * pCtx + = static_cast< UCBDeadPropertyValueParseContext * >( userdata ); + + switch ( state ) + { + case STATE_TYPE: + if ( !pCtx->pType ) + return 1; // abort + break; + + case STATE_VALUE: + if ( !pCtx->pValue ) + return 1; // abort + break; + + case STATE_UCBPROP: + if ( !pCtx->pType || ! pCtx->pValue ) + return 1; // abort + break; + } + return 0; // zero to continue, non-zero to abort parsing +} + +} + +static OUString encodeValue( const OUString & rValue ) +{ + // Note: I do not use the usual & + < + > encoding, because + // I want to prevent any XML parser from trying to 'understand' + // the value. This caused problems: + + // Example: + // - Unencoded property value: x<z + // PROPPATCH: + // - Encoded property value: x<z + // - UCBDeadPropertyValue::toXML result: + // <ucbprop><type>string</type><value>x<z</value></ucbprop> + // PROPFIND: + // - parser replaces < by > ==> error (not well formed) + + OUStringBuffer aResult; + const sal_Unicode * pValue = rValue.getStr(); + + sal_Int32 nCount = rValue.getLength(); + for ( sal_Int32 n = 0; n < nCount; ++n ) + { + const sal_Unicode c = pValue[ n ]; + + if ( '%' == c ) + aResult.append( "%per;" ); + else if ( '<' == c ) + aResult.append( "%lt;" ); + else if ( '>' == c ) + aResult.append( "%gt;" ); + else + aResult.append( c ); + } + return aResult.makeStringAndClear(); +} + + +static OUString decodeValue( const OUString & rValue ) +{ + OUStringBuffer aResult; + const sal_Unicode * pValue = rValue.getStr(); + + sal_Int32 nPos = 0; + sal_Int32 nEnd = rValue.getLength(); + + while ( nPos < nEnd ) + { + sal_Unicode c = pValue[ nPos ]; + + if ( '%' == c ) + { + nPos++; + + if ( nPos == nEnd ) + { + SAL_WARN( "ucb.ucp.webdav", "decodeValue() - syntax error!" ); + return OUString(); + } + + c = pValue[ nPos ]; + + if ( 'p' == c ) + { + // %per; + + if ( nPos > nEnd - 4 ) + { + SAL_WARN( "ucb.ucp.webdav", "decodeValue() - syntax error!" ); + return OUString(); + } + + if ( ( 'e' == pValue[ nPos + 1 ] ) + && + ( 'r' == pValue[ nPos + 2 ] ) + && + ( ';' == pValue[ nPos + 3 ] ) ) + { + aResult.append( '%' ); + nPos += 3; + } + else + { + SAL_WARN( "ucb.ucp.webdav", "decodeValue() - syntax error!" ); + return OUString(); + } + } + else if ( 'l' == c ) + { + // %lt; + + if ( nPos > nEnd - 3 ) + { + SAL_WARN( "ucb.ucp.webdav", "decodeValue() - syntax error!" ); + return OUString(); + } + + if ( ( 't' == pValue[ nPos + 1 ] ) + && + ( ';' == pValue[ nPos + 2 ] ) ) + { + aResult.append( '<' ); + nPos += 2; + } + else + { + SAL_WARN( "ucb.ucp.webdav", "decodeValue() - syntax error!" ); + return OUString(); + } + } + else if ( 'g' == c ) + { + // %gt; + + if ( nPos > nEnd - 3 ) + { + SAL_WARN( "ucb.ucp.webdav", "decodeValue() - syntax error!" ); + return OUString(); + } + + if ( ( 't' == pValue[ nPos + 1 ] ) + && + ( ';' == pValue[ nPos + 2 ] ) ) + { + aResult.append( '>' ); + nPos += 2; + } + else + { + SAL_WARN( "ucb.ucp.webdav", "decodeValue() - syntax error!" ); + return OUString(); + } + } + else + { + SAL_WARN( "ucb.ucp.webdav", "decodeValue() - syntax error!" ); + return OUString(); + } + } + else + aResult.append( c ); + + nPos++; + } + + return aResult.makeStringAndClear(); +} + + +// static +bool UCBDeadPropertyValue::supportsType( const uno::Type & rType ) +{ + return 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(); +} + + +// static +bool UCBDeadPropertyValue::createFromXML( const OString & rInData, + uno::Any & rOutData ) +{ + bool success = false; + + ne_xml_parser * parser = ne_xml_create(); + if ( parser ) + { + UCBDeadPropertyValueParseContext aCtx; + ne_xml_push_handler( parser, + UCBDeadPropertyValue_startelement_callback, + UCBDeadPropertyValue_chardata_callback, + UCBDeadPropertyValue_endelement_callback, + &aCtx ); + + ne_xml_parse( parser, rInData.getStr(), rInData.getLength() ); + + success = !ne_xml_failed( parser ); + + ne_xml_destroy( parser ); + + if ( success ) + { + if ( aCtx.pType && aCtx.pValue ) + { + // Decode aCtx.pValue! It may contain XML reserved chars. + OUString aStringValue = decodeValue( *aCtx.pValue ); + if ( aCtx.pType->equalsIgnoreAsciiCase( aTypeString ) ) + { + rOutData <<= aStringValue; + } + else if ( aCtx.pType->equalsIgnoreAsciiCase( aTypeLong ) ) + { + rOutData <<= aStringValue.toInt32(); + } + else if ( aCtx.pType->equalsIgnoreAsciiCase( aTypeShort ) ) + { + rOutData <<= sal_Int16( aStringValue.toInt32() ); + } + else if ( aCtx.pType->equalsIgnoreAsciiCase( aTypeBoolean ) ) + { + if ( aStringValue.equalsIgnoreAsciiCase("true") ) + rOutData <<= true; + else + rOutData <<= false; + } + else if ( aCtx.pType->equalsIgnoreAsciiCase( aTypeChar ) ) + { + rOutData <<= aStringValue.toChar(); + } + else if ( aCtx.pType->equalsIgnoreAsciiCase( aTypeByte ) ) + { + rOutData <<= sal_Int8( aStringValue.toChar() ); + } + else if ( aCtx.pType->equalsIgnoreAsciiCase( aTypeHyper ) ) + { + rOutData <<= aStringValue.toInt64(); + } + else if ( aCtx.pType->equalsIgnoreAsciiCase( aTypeFloat ) ) + { + rOutData <<= aStringValue.toFloat(); + } + else if ( aCtx.pType->equalsIgnoreAsciiCase( aTypeDouble ) ) + { + rOutData <<= aStringValue.toDouble(); + } + else + { + SAL_WARN( "ucb.ucp.webdav", "createFromXML() - " + "Unsupported property type!" ); + success = false; + } + } + else + success = false; + } + } + + return success; +} + + +// static +bool UCBDeadPropertyValue::toXML( const uno::Any & rInData, + OUString & rOutData ) +{ + // <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", "toXML() - unsupported property type!" ); + return false; + } + + // Encode value! It must not contain XML reserved chars! + aStringValue = encodeValue( aStringValue ); + + rOutData = aXMLPre; + rOutData += aStringType; + rOutData += aXMLMid; + rOutData += aStringValue; + rOutData += aXMLEnd; + + return true; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/ucb/source/ucp/webdav-neon/UCBDeadPropertyValue.hxx b/ucb/source/ucp/webdav-neon/UCBDeadPropertyValue.hxx new file mode 100644 index 000000000..b93c142d5 --- /dev/null +++ b/ucb/source/ucp/webdav-neon/UCBDeadPropertyValue.hxx @@ -0,0 +1,53 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/************************************************************************* + * + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * Copyright 2000, 2010 Oracle and/or its affiliates. + * + * OpenOffice.org - a multi-platform office productivity suite + * + * This file is part of OpenOffice.org. + * + * OpenOffice.org is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License version 3 + * only, as published by the Free Software Foundation. + * + * OpenOffice.org is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License version 3 for more details + * (a copy is included in the LICENSE file that accompanied this code). + * + * You should have received a copy of the GNU Lesser General Public License + * version 3 along with OpenOffice.org. If not, see + * <http://www.openoffice.org/license.html> + * for a copy of the LGPLv3 License. + * + ************************************************************************/ + +#ifndef INCLUDED_UCB_SOURCE_UCP_WEBDAV_NEON_UCBDEADPROPERTYVALUE_HXX +#define INCLUDED_UCB_SOURCE_UCP_WEBDAV_NEON_UCBDEADPROPERTYVALUE_HXX + +#include <rtl/string.hxx> +#include <com/sun/star/uno/Any.hxx> + +namespace webdav_ucp +{ + +class UCBDeadPropertyValue +{ +public: + static bool supportsType( const css::uno::Type & rType ); + + static bool createFromXML( const OString & rInData, + css::uno::Any & rOutData ); + static bool toXML( const css::uno::Any & rInData, + OUString & rOutData ); +}; + +} + +#endif // INCLUDED_UCB_SOURCE_UCP_WEBDAV_NEON_UCBDEADPROPERTYVALUE_HXX + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/ucb/source/ucp/webdav-neon/ucpdav1.component b/ucb/source/ucp/webdav-neon/ucpdav1.component new file mode 100644 index 000000000..5bebe2535 --- /dev/null +++ b/ucb/source/ucp/webdav-neon/ucpdav1.component @@ -0,0 +1,34 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!--********************************************************************** +* +* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. +* +* Copyright 2000, 2010 Oracle and/or its affiliates. +* +* OpenOffice.org - a multi-platform office productivity suite +* +* This file is part of OpenOffice.org. +* +* OpenOffice.org is free software: you can redistribute it and/or modify +* it under the terms of the GNU Lesser General Public License version 3 +* only, as published by the Free Software Foundation. +* +* OpenOffice.org is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU Lesser General Public License version 3 for more details +* (a copy is included in the LICENSE file that accompanied this code). +* +* You should have received a copy of the GNU Lesser General Public License +* version 3 along with OpenOffice.org. If not, see +* <http://www.openoffice.org/license.html> +* for a copy of the LGPLv3 License. +* +**********************************************************************--> + +<component loader="com.sun.star.loader.SharedLibrary" environment="@CPPU_ENV@" + prefix="ucpdav1" xmlns="http://openoffice.org/2010/uno-components"> + <implementation name="com.sun.star.comp.WebDAVContentProvider"> + <service name="com.sun.star.ucb.WebDAVContentProvider"/> + </implementation> +</component> diff --git a/ucb/source/ucp/webdav-neon/webdavcontent.cxx b/ucb/source/ucp/webdav-neon/webdavcontent.cxx new file mode 100644 index 000000000..931195e35 --- /dev/null +++ b/ucb/source/ucp/webdav-neon/webdavcontent.cxx @@ -0,0 +1,4217 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/************************************************************************* + * + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * Copyright 2000, 2010 Oracle and/or its affiliates. + * + * OpenOffice.org - a multi-platform office productivity suite + * + * This file is part of OpenOffice.org. + * + * OpenOffice.org is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License version 3 + * only, as published by the Free Software Foundation. + * + * OpenOffice.org is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License version 3 for more details + * (a copy is included in the LICENSE file that accompanied this code). + * + * You should have received a copy of the GNU Lesser General Public License + * version 3 along with OpenOffice.org. If not, see + * <http://www.openoffice.org/license.html> + * for a copy of the LGPLv3 License. + * + ************************************************************************/ + + +/************************************************************************** + TODO + ************************************************************************** + + *************************************************************************/ + +#include <memory> +#include <rtl/uri.hxx> +#include <rtl/ustrbuf.hxx> +#include <sal/log.hxx> +#include <cppuhelper/queryinterface.hxx> +#include <officecfg/Inet.hxx> +#include <ucbhelper/contentidentifier.hxx> +#include <ucbhelper/propertyvalueset.hxx> +#include <ucbhelper/simpleinteractionrequest.hxx> +#include <ucbhelper/cancelcommandexecution.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/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 <ucbhelper/macros.hxx> + +#include "webdavcontent.hxx" +#include "webdavprovider.hxx" +#include "webdavresultset.hxx" +#include "ContentProperties.hxx" +#include "NeonUri.hxx" +#include "UCBDeadPropertyValue.hxx" + +using namespace com::sun::star; +using namespace webdav_ucp; + +namespace +{ + // implement a GET to substitute HEAD, when HEAD not available + 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.emplace_back( 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.emplace_back( "Accept-Ranges" ); // see <https://tools.ietf.org/html/rfc7233#section-2.3> + aHeaderNames.emplace_back( "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 + // <https://tools.ietf.org/html/rfc7233#section-2> + // needs some explanation for this + // probably some changes? + // 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() ) ); + + NeonUri 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() + throw( ) +{ + ContentImplHelper::acquire(); +} + + +// virtual +void SAL_CALL Content::release() + throw( ) +{ + 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() +{ + return { WEBDAV_CONTENT_SERVICE_NAME }; +} + + +// 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::makeAny( lang::IllegalArgumentException( + "Wrong argument type!", + static_cast< cppu::OWeakObject * >( this ), + -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::makeAny( lang::IllegalArgumentException( + "Wrong argument type!", + static_cast< cppu::OWeakObject * >( this ), + -1 ) ), + Environment ); + // Unreachable + } + + if ( !aProperties.hasElements() ) + { + ucbhelper::cancelCommandExecution( + uno::makeAny( lang::IllegalArgumentException( + "No properties!", + static_cast< cppu::OWeakObject * >( this ), + -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::makeAny( lang::IllegalArgumentException( + "Wrong argument type!", + static_cast< cppu::OWeakObject * >( this ), + -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::makeAny( lang::IllegalArgumentException( + "Wrong argument type!", + static_cast< cppu::OWeakObject * >( this ), + -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::makeAny( lang::IllegalArgumentException( + "Wrong argument type!", + static_cast< cppu::OWeakObject * >( this ), + -1 ) ), + Environment ); + // Unreachable + } + + transfer( transferArgs, Environment ); + } + else if ( aCommand.Name == "post" ) + { + + // post + + + ucb::PostCommandArgument2 aArg; + if ( !( aCommand.Argument >>= aArg ) ) + { + ucbhelper::cancelCommandExecution( + uno::makeAny( lang::IllegalArgumentException( + "Wrong argument type!", + static_cast< cppu::OWeakObject * >( this ), + -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::makeAny( lang::IllegalArgumentException( + "Wrong argument type!", + static_cast< cppu::OWeakObject * >( this ), + -1 ) ), + Environment ); + // Unreachable + } + + aRet <<= createNewContent( aArg ); + } + else if ( aCommand.Name == "addProperty" ) + { + ucb::PropertyCommandArgument aPropArg; + if ( !( aCommand.Argument >>= aPropArg )) + { + ucbhelper::cancelCommandExecution( + uno::makeAny( lang::IllegalArgumentException( + "Wrong argument type!", + static_cast< cppu::OWeakObject * >( this ), + -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::makeAny( e ), Environment ); + } + catch ( const beans::IllegalTypeException&e ) + { + ucbhelper::cancelCommandExecution( uno::makeAny( e ), Environment ); + } + catch ( const lang::IllegalArgumentException&e ) + { + ucbhelper::cancelCommandExecution( uno::makeAny( e ), Environment ); + } + } + else if ( aCommand.Name == "removeProperty" ) + { + OUString sPropName; + if ( !( aCommand.Argument >>= sPropName ) ) + { + ucbhelper::cancelCommandExecution( + uno::makeAny( lang::IllegalArgumentException( + "Wrong argument type!", + static_cast< cppu::OWeakObject * >( this ), + -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::makeAny( e ), Environment ); + } + catch( const beans::NotRemoveableException &e ) + { + ucbhelper::cancelCommandExecution( uno::makeAny( e ), Environment ); + } + } + else + { + + // Unsupported command + + + ucbhelper::cancelCommandExecution( + uno::makeAny( ucb::UnsupportedCommandException( + aCommand.Name, + static_cast< cppu::OWeakObject * >( this ) ) ), + 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 ucb::PropertyCommandArgument& aCmdArg, + const uno::Reference< ucb::XCommandEnvironment >& xEnv ) +{ +// if ( m_bTransient ) +// @@@ ??? + + if ( aCmdArg.Property.Name.isEmpty() ) + throw lang::IllegalArgumentException( + "\"addProperty\" with empty Property.Name", + static_cast< cppu::OWeakObject * >( this ), + -1 ); + + // Check property type. + if ( !UCBDeadPropertyValue::supportsType( aCmdArg.Property.Type ) ) + { + throw beans::IllegalTypeException( + "\"addProperty\" unsupported Property.Type", + static_cast< cppu::OWeakObject * >( this ) ); + } + + if ( aCmdArg.DefaultValue.hasValue() + && aCmdArg.DefaultValue.getValueType() != aCmdArg.Property.Type ) + { + throw beans::IllegalTypeException( + "\"addProperty\" DefaultValue does not match Property.Type", + static_cast< ::cppu::OWeakObject * >( this ) ); + } + + + // 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( + aCmdArg.Property.Name, aSpecialName ); + + // Note: This requires network access! + if ( getPropertySetInfo( xEnv, false /* don't cache data */ ) + ->hasPropertyByName( + bIsSpecial ? aSpecialName : aCmdArg.Property.Name ) ) + { + // Property does already exist. + throw beans::PropertyExistException(); + } + + + // Add a new dynamic property. + + + ProppatchValue aValue( + PROPSET, aCmdArg.Property.Name, aCmdArg.DefaultValue ); + + 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( + static_cast< cppu::OWeakObject * >( this ), + bIsSpecial ? aSpecialName : aCmdArg.Property.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 : aCmdArg.Property.Name, + aCmdArg.Property.Attributes, aCmdArg.DefaultValue ); + } + else + { + if ( shouldAccessNetworkAfterException( e ) ) + { + try + { + ResourceType eType = getResourceType( xEnv ); + switch ( eType ) + { + case UNKNOWN: + case DAV: + throw lang::IllegalArgumentException(); + + case FTP: + case NON_DAV: + // Store property locally. + ContentImplHelper::addProperty( + bIsSpecial ? aSpecialName : aCmdArg.Property.Name, + aCmdArg.Property.Attributes, aCmdArg.DefaultValue ); + 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 ) +{ + + // 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( + static_cast< cppu::OWeakObject * >( this ), + 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 + { + ResourceType eType = getResourceType( xEnv ); + switch ( eType ) + { + case UNKNOWN: + case DAV: + throw beans::UnknownPropertyException(Name); + + case FTP: + 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.isEmpty() ) + 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(); + + assert( !aURL.isEmpty() && "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 ::webdav_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 ); + + if ( rProperties.hasElements() ) + { + uno::Reference< beans::XPropertySet > xAdditionalPropSet; + bool bTriedToGetAdditionalPropSet = false; + + for ( const beans::Property& rProp : rProperties ) + { + // 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 ) + { + 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.get() ); +} + +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< ucb::XContentIdentifier > xIdentifier; + rtl::Reference< ::ucbhelper::ContentProviderImplHelper > xProvider; + + { + osl::Guard< osl::Mutex > aGuard( m_aMutex ); + + aUnescapedTitle = NeonUri::unescape( m_aEscapedTitle ); + xIdentifier.set( m_xIdentifier ); + xProvider.set( m_xProvider.get() ); + 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)); + } + } + + if ( !m_bTransient && !bHasAll ) + { + + // Obtain values from server... + + + // First, identify whether resource is DAV or not + bool bNetworkAccessAllowed = true; + 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.getLength() ); + + if ( !m_aFailedPropNames.empty() ) + { + sal_Int32 nProps = rProperties.getLength(); + std::copy(rProperties.begin(), rProperties.end(), aProperties.begin()); + + aProperties.realloc( nProps ); + } + else + { + aProperties = rProperties; + } + + if ( aProperties.hasElements() ) + 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 + for ( const auto& rProp : resources[0].properties ) + { + OUString aPropValue; + bool bValue; + uno::Sequence< ucb::LockEntry > aSupportedLocks; + if( rProp.Value >>= aPropValue ) + SAL_INFO( "ucb.ucp.webdav", "PROPFIND (getPropertyValues) - returned property: " << rProp.Name << ":" << aPropValue ); + else if( rProp.Value >>= bValue ) + SAL_INFO( "ucb.ucp.webdav", "PROPFIND (getPropertyValues) - returned property: " << rProp.Name << ":" << + ( bValue ? "true" : "false" ) ); + else if( rProp.Value >>= aSupportedLocks ) + { + SAL_INFO( "ucb.ucp.webdav", "PROPFIND (getPropertyValues) - returned property: " << rProp.Name << ":" ); + for ( sal_Int32 n = 0; n < aSupportedLocks.getLength(); ++n ) + { + SAL_INFO( "ucb.ucp.webdav"," scope: " + << ( aSupportedLocks[ n ].Scope == css::ucb::LockScope_SHARED ? "shared" : "exclusive" ) + << ", type: " + << ( aSupportedLocks[ n ].Type != css::ucb::LockType_WRITE ? "" : "write" ) ); + } + } + } + } +#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 ) ) + && !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.emplace_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. + NeonUri 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. + + if ( eType == DAV ) + { + if (!xProps) + xProps.reset(new ContentProperties(aUnescapedTitle)); + else + xProps->addProperty("Title", uno::makeAny(aUnescapedTitle), true); + } + else + { + if (!xProps) + xProps.reset( new ContentProperties( aUnescapedTitle, false ) ); + else + xProps->addProperty( + "Title", + uno::makeAny( aUnescapedTitle ), + true ); + + xProps->addProperty( + "IsFolder", + uno::makeAny( false ), + true ); + xProps->addProperty( + "IsDocument", + uno::makeAny( true ), + 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 ( const auto& rProp : aMissingProps ) + { + // For the time being only a couple of properties need to be added + if ( rProp == "DateModified" || rProp == "DateCreated" ) + { + util::DateTime aDate; + xProps->addProperty( + rProp, + uno::makeAny( aDate ), + true ); + } + // If WebDAV didn't return the resource type, assume default + // This happens e.g. for lists exported by SharePoint + else if ( rProp == "IsFolder" ) + { + xProps->addProperty( + rProp, + uno::makeAny( false ), + true ); + } + else if ( rProp == "IsDocument" ) + { + xProps->addProperty( + rProp, + uno::makeAny( true ), + true ); + } + } + } + + for ( const auto& rProperty : rProperties ) + { + const OUString rName = rProperty.Name; + if ( rName == "BaseURI" ) + { + // Add BaseURI property, if requested. + xProps->addProperty( + "BaseURI", + uno::makeAny( getBaseURI( xResAccess ) ), + true ); + } + else if ( rName == "CreatableContentsInfo" ) + { + // Add CreatableContentsInfo property, if requested. + bool bFolder = false; + xProps->getValue( + "IsFolder" ) + >>= bFolder; + xProps->addProperty( + "CreatableContentsInfo", + uno::makeAny( bFolder + ? queryCreatableContentsInfo() + : uno::Sequence< ucb::ContentInfo >() ), + true ); + } + } + + uno::Reference< sdbc::XRow > xResultRow + = getPropertyValues( m_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 = NeonUri::escapeSegment( 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() ); + uno::Sequence< beans::PropertyChangeEvent > aChanges( rValues.getLength() ); + sal_Int32 nChanged = 0; + + beans::PropertyChangeEvent aEvent; + aEvent.Source = static_cast< cppu::OWeakObject * >( this ); + aEvent.Further = false; + // aEvent.PropertyName = + aEvent.PropertyHandle = -1; + // aEvent.OldValue = + // aEvent.NewValue = + + std::vector< ProppatchValue > aProppatchValues; + + 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! + aRet[ n ] <<= lang::IllegalAccessException( + "Property is read-only!", + static_cast< cppu::OWeakObject * >( this ) ); + continue; + } + + + // Mandatory props. + + + if ( rName == "ContentType" ) + { + // Read-only property! + aRet[ n ] <<= lang::IllegalAccessException( + "Property is read-only!", + static_cast< cppu::OWeakObject * >( this ) ); + } + else if ( rName == "IsDocument" ) + { + // Read-only property! + aRet[ n ] <<= lang::IllegalAccessException( + "Property is read-only!", + static_cast< cppu::OWeakObject * >( this ) ); + } + else if ( rName == "IsFolder" ) + { + // Read-only property! + aRet[ n ] <<= lang::IllegalAccessException( + "Property is read-only!", + static_cast< cppu::OWeakObject * >( this ) ); + } + else if ( rName == "Title" ) + { + OUString aNewValue; + if ( rValue.Value >>= aNewValue ) + { + // No empty titles! + if ( !aNewValue.isEmpty() ) + { + try + { + NeonUri 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 & ) + { + aRet[ n ] <<= lang::IllegalArgumentException( + "Invalid content identifier!", + static_cast< cppu::OWeakObject * >( this ), + -1 ); + } + } + else + { + aRet[ n ] <<= lang::IllegalArgumentException( + "Empty title not allowed!", + static_cast< cppu::OWeakObject * >( this ), + -1 ); + } + } + else + { + aRet[ n ] <<= beans::IllegalTypeException( + "Property value has wrong type!", + static_cast< cppu::OWeakObject * >( this ) ); + } + } + 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! + aRet[ n ] <<= beans::UnknownPropertyException( + "Property is unknown!", + static_cast< cppu::OWeakObject * >( this ) ); + continue; + } + + if ( rName == "Size" ) + { + // Read-only property! + aRet[ n ] <<= lang::IllegalAccessException( + "Property is read-only!", + static_cast< cppu::OWeakObject * >( this ) ); + } + else if ( rName == "DateCreated" ) + { + // Read-only property! + aRet[ n ] <<= lang::IllegalAccessException( + "Property is read-only!", + static_cast< cppu::OWeakObject * >( this ) ); + } + else if ( rName == "DateModified" ) + { + // Read-only property! + aRet[ n ] <<= lang::IllegalAccessException( + "Property is read-only!", + static_cast< cppu::OWeakObject * >( this ) ); + } + else if ( rName == "MediaType" ) + { + // Read-only property! + // (but could be writable, if 'getcontenttype' would be) + aRet[ n ] <<= lang::IllegalAccessException( + "Property is read-only!", + static_cast< cppu::OWeakObject * >( this ) ); + } + if ( rName == "CreatableContentsInfo" ) + { + // Read-only property! + aRet[ n ] <<= lang::IllegalAccessException( + "Property is read-only!", + static_cast< cppu::OWeakObject * >( this ) ); + } + else + { + if ( getResourceType( xEnv, xResAccess ) == DAV ) + { + // Property value will be set on server. + ProppatchValue aValue( PROPSET, rName, rValue.Value ); + aProppatchValues.push_back( aValue ); + } + 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 ) + { + aRet[ n ] <<= e; + } + catch ( lang::WrappedTargetException const & e ) + { + aRet[ n ] <<= e; + } + catch ( beans::PropertyVetoException const & e ) + { + aRet[ n ] <<= e; + } + catch ( lang::IllegalArgumentException const & e ) + { + aRet[ n ] <<= e; + } + } + else + { + aRet[ n ] <<= uno::Exception( + "No property set for storing the value!", + static_cast< cppu::OWeakObject * >( this ) ); + } + } + } + } + } // 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!" ); + cancelCommandExecution( e, xEnv ); + // unreachable + } + } + + if ( bExchange ) + { + // Assemble new content identifier... + + OUString aNewURL = getParentURL(); + if ( aNewURL.lastIndexOf( '/' ) != ( aNewURL.getLength() - 1 ) ) + aNewURL += "/"; + + aNewURL += NeonUri::escapeSegment( aNewTitle ); + + uno::Reference< ucb::XContentIdentifier > xNewId + = new ::ucbhelper::ContentIdentifier( aNewURL ); + + NeonUri sourceURI( xIdentifier->getContentIdentifier() ); + NeonUri targetURI( xNewId->getContentIdentifier() ); + + try + { + 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.GetPath(), 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(), +// sal_True ); + } + else + { + // Do not set new title! + aNewTitle.clear(); + + // Set error . + aRet[ nTitlePos ] <<= uno::Exception( + "Exchange failed!", + static_cast< cppu::OWeakObject * >( this ) ); + } + } + catch ( DAVException const & e ) + { + // Do not set new title! + aNewTitle.clear(); + + // Set error . + aRet[ nTitlePos ] = MapDAVException( e, true ); + } + } + + if ( !aNewTitle.isEmpty() ) + { + osl::Guard< osl::Mutex > aGuard( m_aMutex ); + + aEvent.PropertyName = "Title"; + aEvent.OldValue <<= aOldTitle; + aEvent.NewValue <<= aNewTitle; + + m_aEscapedTitle = NeonUri::escapeSegment( 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! + + OUStringBuffer aMsg; + if ( getResourceType( xEnv ) == FTP ) + { + aMsg.append( "FTP over HTTP proxy: resource cannot " + "be opened as folder! Wrong Open Mode!" ); + } + else + { + aMsg.append( "Non-folder resource cannot be " + "opened as folder! Wrong Open Mode!" ); + } + + ucbhelper::cancelCommandExecution( + uno::makeAny( + lang::IllegalArgumentException( + aMsg.makeStringAndClear(), + static_cast< cppu::OWeakObject * >( this ), + -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::makeAny( + ucb::UnsupportedOpenModeException( + OUString(), + static_cast< cppu::OWeakObject * >( this ), + 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 ) + { + // 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::makeAny( + ucb::UnsupportedDataSinkException( + OUString(), + static_cast< cppu::OWeakObject * >( this ), + 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::makeAny( + ucb::UnsupportedDataSinkException( + OUString(), + static_cast< cppu::OWeakObject * >( this ), + 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 = 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< ::webdav_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::makeAny( ucb::MissingPropertiesException( + OUString(), + static_cast< cppu::OWeakObject * >( this ), + 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 ) + { + ucb::UnsupportedNameClashException aEx( + "Unable to write without overwrite!", + static_cast< cppu::OWeakObject * >( this ), + ucb::NameClash::ERROR ); + + uno::Reference< task::XInteractionHandler > xIH; + + if ( Environment.is() ) + xIH = Environment->getInteractionHandler(); + + if ( !xIH.is() ) + { + // No IH; throw. + throw aEx; + } + + uno::Any aExAsAny( uno::makeAny( aEx ) ); + + rtl::Reference< ucbhelper::SimpleInteractionRequest > xRequest + = new ucbhelper::SimpleInteractionRequest( + aExAsAny, + ContinuationFlags::Approve | ContinuationFlags::Disapprove ); + xIH->handle( xRequest.get() ); + + 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; + } + + } + } + + 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 + { + NeonUri aURI( aURL ); + aTitle = aURI.GetPathBaseNameUnescaped(); + } + catch ( DAVException const & ) + { + } + + ucbhelper::cancelCommandExecution( + uno::makeAny( + ucb::NameClashException( + OUString(), + static_cast< cppu::OWeakObject * >( this ), + 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::makeAny( + ucb::MissingInputStreamException( + OUString(), + static_cast< cppu::OWeakObject * >( this ) ) ), + 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< ucb::XContentIdentifier > xIdentifier; + uno::Reference< ucb::XContentProvider > xProvider; + std::unique_ptr< DAVResourceAccess > xResAccess; + + { + osl::Guard< osl::Mutex > aGuard( m_aMutex ); + + xIdentifier.set( m_xIdentifier ); + xProvider.set( m_xProvider.get() ); + xResAccess.reset(new DAVResourceAccess(*m_xResAccess)); + } + + NeonUri sourceURI( rArgs.SourceURL ); + NeonUri targetURI( xIdentifier->getContentIdentifier() ); + + OUString aTargetURI; + try + { + 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::makeAny( + ucb::InteractiveBadTransferURLException( + "Unsupported URL scheme!", + static_cast< cppu::OWeakObject * >( this ) ) ), + 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().isEmpty() && + ( sourceURI.GetHost() != targetURI.GetHost() ) ) + { + ucbhelper::cancelCommandExecution( + uno::makeAny( ucb::InteractiveBadTransferURLException( + "Different hosts!", + static_cast< cppu::OWeakObject * >( this ) ) ), + 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( m_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.GetPath(), + 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(), +// sal_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.GetPath(), + 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(), +// sal_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 ucb::NameClash::ERROR: + { + ucbhelper::cancelCommandExecution( + uno::makeAny( + ucb::NameClashException( + OUString(), + static_cast< cppu::OWeakObject * >( this ), + task::InteractionClassification_ERROR, + aTargetURI ) ), + Environment ); + [[fallthrough]]; // Unreachable + } + + case ucb::NameClash::OVERWRITE: + break; + + case ucb::NameClash::KEEP: // deprecated + case ucb::NameClash::RENAME: + case ucb::NameClash::ASK: + default: + { + ucbhelper::cancelCommandExecution( + uno::makeAny( + ucb::UnsupportedNameClashException( + OUString(), + static_cast< cppu::OWeakObject * >( this ), + 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... + + ::webdav_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 ( const auto& rSupportedLock : std::as_const(aSupportedLocks) ) + { + if ( rSupportedLock.Scope + == ucb::LockScope_EXCLUSIVE && + rSupportedLock.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 + + const OUString aScheme( + rURL.copy( 0, rURL.indexOf( ':' ) ).toAsciiLowerCase() ); + + if ( aScheme == FTP_URL_SCHEME ) + { + eResourceTypeForLocks = FTP; + } + else + { + 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[ 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 + + for ( const auto& rProp : resources[0].properties ) + { + if ( rProp.Name == DAVProperties::SUPPORTEDLOCK ) + { + wasSupportedlockFound = true; + uno::Sequence< ucb::LockEntry > aSupportedLocks; + if ( rProp.Value >>= aSupportedLocks ) + { + bool isSupported = std::any_of(aSupportedLocks.begin(), aSupportedLocks.end(), + [](const ucb::LockEntry& rLock) { + // 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 + return rLock.Scope == ucb::LockScope_EXCLUSIVE + && rLock.Type == ucb::LockType_WRITE; + }); + if (isSupported) + { + // requested locking mode is supported + eResourceTypeForLocks = DAV; + SAL_INFO( "ucb.ucp.webdav", "resourceTypeForLocks - URL: <" + << m_xIdentifier->getContentIdentifier() << ">, DAV lock/unlock supported"); + } + 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; + aOwnerAny + <<= OUString("LibreOffice - http://www.libreoffice.org/"); + + 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!", + static_cast< cppu::OWeakObject * >( this ), + task::InteractionClassification_ERROR, + aURL, + false ); + } + break; + 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?", + static_cast< cppu::OWeakObject * >( this ), + 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; + break; + 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; + break; + default: + //fallthrough + ; + } + + SAL_WARN( "ucb.ucp.webdav","lock() DAVException - URL: <" + << m_xIdentifier->getContentIdentifier() << ">, DAV ExceptionCode: " << e.getError() << ", HTTP error: " << e.getStatus() ); + cancelCommandExecution( e, Environment ); + // 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; + break; + 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; + break; + 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 ); + // 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 identitity. + + // 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 ); + aProperties[ 0 ].Name = "IsFolder"; + aProperties[ 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( 1 ); + aArgs[ 0 ] <<= beans::PropertyValue( + "Uri", -1, + uno::makeAny(aURL), + beans::PropertyState_DIRECT_VALUE); + + aException <<= + ucb::InteractiveAugmentedIOException( + "Not found!", + static_cast< cppu::OWeakObject * >( this ), + 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(), + static_cast< cppu::OWeakObject * >( this ), + task::InteractionClassification_ERROR, + e.getData() ); + else + aException <<= + ucb::InteractiveNetworkReadException( + e.getData(), + static_cast< cppu::OWeakObject * >( this ), + task::InteractionClassification_ERROR, + e.getData() ); + break; + } + + case DAVException::DAV_HTTP_LOOKUP: + aException <<= + ucb::InteractiveNetworkResolveNameException( + OUString(), + static_cast< cppu::OWeakObject * >( this ), + 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(), + static_cast< cppu::OWeakObject * >( this ), + 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(), + static_cast< cppu::OWeakObject * >( this ), + -1 ); + break; + + case DAVException::DAV_LOCKED: + aException <<= + ucb::InteractiveLockingLockedException( + "Locked!", + static_cast< cppu::OWeakObject * >( this ), + task::InteractionClassification_ERROR, + aURL, + false ); // not SelfOwned + break; + + case DAVException::DAV_LOCKED_SELF: + aException <<= + ucb::InteractiveLockingLockedException( + "Locked (self!)", + static_cast< cppu::OWeakObject * >( this ), + task::InteractionClassification_ERROR, + aURL, + true ); // SelfOwned + break; + + case DAVException::DAV_NOT_LOCKED: + aException <<= + ucb::InteractiveLockingNotLockedException( + "Not locked!", + static_cast< cppu::OWeakObject * >( this ), + task::InteractionClassification_ERROR, + aURL ); + break; + + case DAVException::DAV_LOCK_EXPIRED: + aException <<= + ucb::InteractiveLockingLockExpiredException( + "Lock expired!", + static_cast< cppu::OWeakObject * >( this ), + task::InteractionClassification_ERROR, + aURL ); + break; + + default: + aException <<= + ucb::InteractiveNetworkGeneralException( + OUString(), + static_cast< cppu::OWeakObject * >( this ), + task::InteractionClassification_ERROR ); + break; + } + + return aException; +} + + +// static +bool Content::shouldAccessNetworkAfterException( const DAVException & e ) +{ + return !(( 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_AUTH ) || + ( e.getError() == DAVException::DAV_HTTP_AUTHPROXY )); +} + + +void Content::cancelCommandExecution( + const DAVException & e, + const uno::Reference< ucb::XCommandEnvironment > & xEnv, + bool bWrite /* = sal_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.isEmpty() ) + { + 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; + + const OUString & rURL = rResAccess->getURL(); + const OUString aScheme( + rURL.copy( 0, rURL.indexOf( ':' ) ).toAsciiLowerCase() ); + + if ( aScheme == FTP_URL_SCHEME ) + { + eResourceType = FTP; + } + else + { + 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 ); + aProperties[ 0 ].Name = "IsFolder"; + aProperties[ 1 ].Name = "IsDocument"; + aProperties[ 2 ].Name = "IsReadOnly"; + aProperties[ 3 ].Name = "MediaType"; + aProperties[ 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 + for ( const auto& rProp : resources[0].properties ) + { + OUString aPropValue; + bool bValue; + uno::Sequence< ucb::LockEntry > aSupportedLocks; + if(rProp.Value >>= aPropValue ) + SAL_INFO( "ucb.ucp.webdav", "PROPFIND (getResourceType) - ret'd prop: " << rProp.Name << ":" << aPropValue ); + else if( rProp.Value >>= bValue ) + SAL_INFO( "ucb.ucp.webdav", "PROPFIND (getResourceType) - ret'd prop: " << rProp.Name << ":" << + ( bValue ? "true" : "false" ) ); + else if( rProp.Value >>= aSupportedLocks ) + { + SAL_INFO( "ucb.ucp.webdav", "PROPFIND (getResourceType) - ret'd prop: " << rProp.Name << ":" ); + for ( sal_Int32 n = 0; n < aSupportedLocks.getLength(); ++n ) + { + SAL_INFO( "ucb.ucp.webdav","PROPFIND (getResourceType) - supportedlock[" << n <<"]: scope: " + << ( aSupportedLocks[ n ].Scope == css::ucb::LockScope_SHARED ? "shared" : "exclusive" ) + << ", type: " + << ( aSupportedLocks[ n ].Type != css::ucb::LockType_WRITE ? "" : "write" ) ); + } + } + } + } +#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( rURL, 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 + } + } + } + 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 ) + { + // behave same as DAVException::DAV_HTTP_TIMEOUT or DAVException::DAV_HTTP_CONNECT was thrown + try + { + // extract host name and connection port + NeonUri theUri( rURL ); + const OUString& aHostName = theUri.GetHost(); + sal_Int32 nPort = theUri.GetPort(); + throw DAVException( DAVException::DAV_HTTP_TIMEOUT, + NeonUri::makeConnectionEndPointString( 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 <" << rURL << ">: " + << +eResourceType << " vs. " << +m_eResourceType); + } + SAL_INFO( "ucb.ucp.webdav", "m_eResourceType for <"<<rURL<<">: " << 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)); + } + Content::ResourceType const 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_xContext ); + m_nOptsCacheLifeImplWeb = std::max( sal_uInt32( 0 ), + std::min( nAtime, sal_uInt32( 3600 ) ) ); + + nAtime = officecfg::Inet::Settings::OptsCacheLifeDAV::get( m_xContext ); + m_nOptsCacheLifeDAV = std::max( sal_uInt32( 0 ), + std::min( nAtime, sal_uInt32( 3600 ) ) ); + + nAtime = officecfg::Inet::Settings::OptsCacheLifeDAVLocked::get( m_xContext ); + m_nOptsCacheLifeDAVLocked = std::max( sal_uInt32( 0 ), + std::min( nAtime, sal_uInt32( 3600 ) ) ); + + nAtime = officecfg::Inet::Settings::OptsCacheLifeNotImpl::get( m_xContext ); + m_nOptsCacheLifeNotImpl = std::max( sal_uInt32( 0 ), + std::min( nAtime, sal_uInt32( 43200 ) ) ); + + nAtime = officecfg::Inet::Settings::OptsCacheLifeNotFound::get( m_xContext ); + 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_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... + 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 = 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< 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( OUString() ); + 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.emplace_back( + 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( OUString() ); + return false; +} + + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/ucb/source/ucp/webdav-neon/webdavcontent.hxx b/ucb/source/ucp/webdav-neon/webdavcontent.hxx new file mode 100644 index 000000000..4a976a26b --- /dev/null +++ b/ucb/source/ucp/webdav-neon/webdavcontent.hxx @@ -0,0 +1,320 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/************************************************************************* + * + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * Copyright 2000, 2010 Oracle and/or its affiliates. + * + * OpenOffice.org - a multi-platform office productivity suite + * + * This file is part of OpenOffice.org. + * + * OpenOffice.org is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License version 3 + * only, as published by the Free Software Foundation. + * + * OpenOffice.org is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License version 3 for more details + * (a copy is included in the LICENSE file that accompanied this code). + * + * You should have received a copy of the GNU Lesser General Public License + * version 3 along with OpenOffice.org. If not, see + * <http://www.openoffice.org/license.html> + * for a copy of the LGPLv3 License. + * + ************************************************************************/ + +#ifndef INCLUDED_UCB_SOURCE_UCP_WEBDAV_NEON_WEBDAVCONTENT_HXX +#define INCLUDED_UCB_SOURCE_UCP_WEBDAV_NEON_WEBDAVCONTENT_HXX + +#include <config_lgpl.h> +#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 PostCommandArgument2; + struct PropertyCommandArgument; + struct TransferInfo; +} + +namespace webdav_ucp +{ + + +// UNO service name for the content. +#define WEBDAV_CONTENT_SERVICE_NAME "com.sun.star.ucb.WebDAVContent" + + +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 + FTP, // the Web resource exists but it's ftp + 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 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 ); + + /// @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::UnknownPropertyException + /// @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 + /// @throws css::uno::RuntimeException + 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 + /// @throws css::uno::RuntimeException + 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() + throw() override; + virtual void SAL_CALL release() + throw() 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 >& rxContext, + 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 ); + +}; + +} + +#endif + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/ucb/source/ucp/webdav-neon/webdavcontentcaps.cxx b/ucb/source/ucp/webdav-neon/webdavcontentcaps.cxx new file mode 100644 index 000000000..d8ff4ff6f --- /dev/null +++ b/ucb/source/ucp/webdav-neon/webdavcontentcaps.cxx @@ -0,0 +1,653 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/************************************************************************* + * + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * Copyright 2000, 2010 Oracle and/or its affiliates. + * + * OpenOffice.org - a multi-platform office productivity suite + * + * This file is part of OpenOffice.org. + * + * OpenOffice.org is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License version 3 + * only, as published by the Free Software Foundation. + * + * OpenOffice.org is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License version 3 for more details + * (a copy is included in the LICENSE file that accompanied this code). + * + * You should have received a copy of the GNU Lesser General Public License + * version 3 along with OpenOffice.org. If not, see + * <http://www.openoffice.org/license.html> + * for a copy of the LGPLv3 License. + * + ************************************************************************/ + +/************************************************************************** + TODO + ************************************************************************** + + *************************************************************************/ + +#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/Link.hpp> +#include <com/sun/star/ucb/Lock.hpp> +#include <com/sun/star/ucb/LockEntry.hpp> +#include "webdavcontent.hxx" +#include "webdavprovider.hxx" +#include "ContentProperties.hxx" +#include "PropfindCache.hxx" + +using namespace com::sun::star; +using namespace webdav_ucp; + + +// ContentProvider implementation. + + +void ContentProvider::getProperty( + const OUString & rPropName, beans::Property & rProp ) +{ + if ( !m_pProps ) + { + osl::MutexGuard aGuard( m_aMutex ); + if ( !m_pProps ) + { + m_pProps.reset( new 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::SOURCE, + -1, + cppu::UnoType<uno::Sequence< ucb::Link >>::get(), + beans::PropertyAttribute::BOUND ) ); + + 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 ); + } +} + + +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 ); + } + 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 ); + + sal_Int32 n = 0; + beans::Property aProp; + + for ( const auto& rProp : aPropSet ) + { + xProvider->getProperty( rProp, aProp ); + aProperties[ 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 ); + + + // Mandatory commands + + + aCmdInfo[ 0 ] = + ucb::CommandInfo( + "getCommandInfo", + -1, + cppu::UnoType<void>::get() ); + aCmdInfo[ 1 ] = + ucb::CommandInfo( + "getPropertySetInfo", + -1, + cppu::UnoType<void>::get() ); + aCmdInfo[ 2 ] = + ucb::CommandInfo( + "getPropertyValues", + -1, + cppu::UnoType<uno::Sequence< beans::Property >>::get() ); + aCmdInfo[ 3 ] = + ucb::CommandInfo( + "setPropertyValues", + -1, + cppu::UnoType<uno::Sequence< beans::PropertyValue >>::get() ); + + + // Optional standard commands + + + aCmdInfo[ 4 ] = + ucb::CommandInfo( + "delete", + -1, + cppu::UnoType<bool>::get() ); + aCmdInfo[ 5 ] = + ucb::CommandInfo( + "insert", + -1, + cppu::UnoType<ucb::InsertCommandArgument>::get() ); + aCmdInfo[ 6 ] = + ucb::CommandInfo( + "open", + -1, + cppu::UnoType<ucb::OpenCommandArgument2>::get() ); + + + // New commands + + + aCmdInfo[ 7 ] = + ucb::CommandInfo( + "post", + -1, + cppu::UnoType<ucb::PostCommandArgument2>::get() ); + aCmdInfo[ 8 ] = + ucb::CommandInfo( + "addProperty", + -1, + cppu::UnoType<ucb::PropertyCommandArgument>::get() ); + aCmdInfo[ 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; + + if ( bFolder ) + { + + // Optional standard commands + + + aCmdInfo[ nPos ] = + ucb::CommandInfo( + "transfer", + -1, + cppu::UnoType<ucb::TransferInfo>::get() ); + nPos++; + aCmdInfo[ nPos ] = + ucb::CommandInfo( + "createNewContent", + -1, + cppu::UnoType<ucb::ContentInfo>::get() ); + nPos++; + } + else + { + // no document-only commands at the moment. + } + + if ( bSupportsLocking ) + { + aCmdInfo[ nPos ] = + ucb::CommandInfo( + "lock", + -1, + cppu::UnoType<void>::get() ); + nPos++; + aCmdInfo[ 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-neon/webdavdatasupplier.cxx b/ucb/source/ucp/webdav-neon/webdavdatasupplier.cxx new file mode 100644 index 000000000..7675f6c5d --- /dev/null +++ b/ucb/source/ucp/webdav-neon/webdavdatasupplier.cxx @@ -0,0 +1,487 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/************************************************************************* + * + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * Copyright 2000, 2010 Oracle and/or its affiliates. + * + * OpenOffice.org - a multi-platform office productivity suite + * + * This file is part of OpenOffice.org. + * + * OpenOffice.org is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License version 3 + * only, as published by the Free Software Foundation. + * + * OpenOffice.org is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License version 3 for more details + * (a copy is included in the LICENSE file that accompanied this code). + * + * You should have received a copy of the GNU Lesser General Public License + * version 3 along with OpenOffice.org. If not, see + * <http://www.openoffice.org/license.html> + * for a copy of the LGPLv3 License. + * + ************************************************************************/ + + +/************************************************************************** + TODO + ************************************************************************** + + *************************************************************************/ + +#include <sal/log.hxx> +#include <com/sun/star/ucb/IllegalIdentifierException.hpp> +#include <com/sun/star/ucb/OpenMode.hpp> +#include <com/sun/star/ucb/ResultSetException.hpp> +#include <ucbhelper/contentidentifier.hxx> +#include <ucbhelper/providerhelper.hxx> +#include <memory> +#include <vector> +#include "webdavdatasupplier.hxx" +#include "webdavcontent.hxx" +#include "ContentProperties.hxx" +#include "NeonUri.hxx" + +using namespace com::sun::star; +using namespace webdav_ucp; + +namespace webdav_ucp +{ + + +// struct ResultListEntry. + +namespace { + +struct ResultListEntry +{ + OUString aId; + uno::Reference< ucb::XContentIdentifier > xId; + uno::Reference< ucb::XContent > xContent; + uno::Reference< sdbc::XRow > xRow; + std::shared_ptr<ContentProperties> const pData; + + explicit ResultListEntry(std::shared_ptr<ContentProperties> const& pEntry) + : pData(pEntry) + {} +}; + +} + +// ResultList. + + +typedef std::vector<std::unique_ptr<ResultListEntry>> ResultList; + + +// struct DataSupplier_Impl. + + +struct DataSupplier_Impl +{ + osl::Mutex m_aMutex; + ResultList m_Results; + rtl::Reference< Content > m_xContent; + uno::Reference< uno::XComponentContext > m_xContext; + sal_Int32 m_nOpenMode; + bool m_bCountFinal; + bool m_bThrowException; + + DataSupplier_Impl( + 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 ) {} +}; + +} + + +// DataSupplier Implementation. + + +DataSupplier::DataSupplier( + const uno::Reference< uno::XComponentContext >& rxContext, + const rtl::Reference< Content >& rContent, + sal_Int32 nOpenMode ) +: m_pImpl( new DataSupplier_Impl( rxContext, rContent, nOpenMode ) ) +{ +} + + +// virtual +DataSupplier::~DataSupplier() +{ +} + + +// virtual +OUString DataSupplier::queryContentIdentifierString( sal_uInt32 nIndex ) +{ + osl::Guard< osl::Mutex > aGuard( m_pImpl->m_aMutex ); + + if (nIndex < m_pImpl->m_Results.size()) + { + OUString aId = m_pImpl->m_Results[ nIndex ]->aId; + if ( !aId.isEmpty() ) + { + // Already cached. + return aId; + } + } + + if ( getResult( nIndex ) ) + { + OUString aId = m_pImpl->m_xContent->getResourceAccess().getURL(); + + const ContentProperties& props(*(m_pImpl->m_Results[ nIndex ]->pData)); + + if ( ( aId.lastIndexOf( '/' ) + 1 ) != aId.getLength() ) + aId += "/"; + + aId += props.getEscapedTitle(); + + if ( props.isTrailingSlash() ) + aId += "/"; + + m_pImpl->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_pImpl->m_aMutex ); + + if (nIndex < m_pImpl->m_Results.size()) + { + uno::Reference< ucb::XContentIdentifier > xId + = m_pImpl->m_Results[ nIndex ]->xId; + if ( xId.is() ) + { + // Already cached. + return xId; + } + } + + OUString aId = queryContentIdentifierString( nIndex ); + if ( !aId.isEmpty() ) + { + uno::Reference< ucb::XContentIdentifier > xId + = new ::ucbhelper::ContentIdentifier( aId ); + m_pImpl->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_pImpl->m_aMutex ); + + if (nIndex < m_pImpl->m_Results.size()) + { + uno::Reference< ucb::XContent > xContent + = m_pImpl->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_pImpl->m_xContent->getProvider()->queryContent( xId ); + m_pImpl->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_pImpl->m_aMutex ); + + if (nIndex < m_pImpl->m_Results.size()) + { + // Result already present. + return true; + } + + // Obtain values... + if ( getData() ) + { + if (nIndex < m_pImpl->m_Results.size()) + { + // Result already present. + return true; + } + } + + return false; +} + + +// virtual +sal_uInt32 DataSupplier::totalCount() +{ + // Obtain values... + getData(); + + return m_pImpl->m_Results.size(); +} + + +// virtual +sal_uInt32 DataSupplier::currentCount() +{ + return m_pImpl->m_Results.size(); +} + + +// virtual +bool DataSupplier::isCountFinal() +{ + return m_pImpl->m_bCountFinal; +} + + +// virtual +uno::Reference< sdbc::XRow > DataSupplier::queryPropertyValues( + sal_uInt32 nIndex ) +{ + osl::Guard< osl::Mutex > aGuard( m_pImpl->m_aMutex ); + + if (nIndex < m_pImpl->m_Results.size()) + { + uno::Reference< sdbc::XRow > xRow = m_pImpl->m_Results[ nIndex ]->xRow; + if ( xRow.is() ) + { + // Already cached. + return xRow; + } + } + + if ( getResult( nIndex ) ) + { + uno::Reference< sdbc::XRow > xRow + = Content::getPropertyValues( + m_pImpl->m_xContext, + getResultSet()->getProperties(), + *(m_pImpl->m_Results[ nIndex ]->pData), + rtl::Reference< ::ucbhelper::ContentProviderImplHelper >( + m_pImpl->m_xContent->getProvider().get() ), + queryContentIdentifierString( nIndex ) ); + m_pImpl->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_pImpl->m_aMutex ); + + if (nIndex < m_pImpl->m_Results.size()) + m_pImpl->m_Results[ nIndex ]->xRow.clear(); +} + + +// virtual +void DataSupplier::close() +{ +} + + +// virtual +void DataSupplier::validate() +{ + if ( m_pImpl->m_bThrowException ) + throw ucb::ResultSetException(); +} + + +bool DataSupplier::getData() +{ + osl::ClearableGuard< osl::Mutex > aGuard( m_pImpl->m_aMutex ); + + if ( !m_pImpl->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& rName) { return rName == 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_pImpl->m_xContent->getResourceAccess() + .PROPFIND( DAVONE, + propertyNames, + resources, + getResultSet()->getEnvironment() ); +#if defined SAL_LOG_INFO + { + //print the resource for every URI returned + for ( const auto& rResource : resources ) + { + NeonUri aCurrURI( rResource.uri ); + OUString aCurrPath = aCurrURI.GetPath(); + aCurrPath = NeonUri::unescape( aCurrPath ); + SAL_INFO( "ucb.ucp.webdav", "getData() - resource URL: <" << rResource.uri << ">, unescaped to: <" << aCurrPath << "> )" ); + for ( const auto& rProp : rResource.properties ) + { + SAL_INFO( "ucb.ucp.webdav", "PROPFIND - property name: " << rProp.Name ); + } + } + } +#endif + } + catch ( DAVException & ) + { + SAL_WARN( "ucb.ucp.webdav", "Running PROPFIND: DAVException" ); + m_pImpl->m_bThrowException = true; + } + + if ( !m_pImpl->m_bThrowException ) + { + try + { + NeonUri aURI( + m_pImpl->m_xContent->getResourceAccess().getURL() ); + OUString aPath = aURI.GetPath(); + + if ( aPath.endsWith("/") ) + aPath = aPath.copy( 0, aPath.getLength() - 1 ); + + aPath = NeonUri::unescape( aPath ); + bool bFoundParent = false; + + for (DAVResource & rRes : resources) + { + // Filter parent, which is contained somewhere(!) in + // the vector. + if ( !bFoundParent ) + { + try + { + NeonUri aCurrURI( rRes.uri ); + OUString aCurrPath = aCurrURI.GetPath(); + if ( aCurrPath.endsWith("/") ) + aCurrPath + = aCurrPath.copy( + 0, + aCurrPath.getLength() - 1 ); + + aCurrPath = NeonUri::unescape( aCurrPath ); + if ( aPath == aCurrPath ) + { + bFoundParent = true; + continue; + } + } + catch ( DAVException const & ) + { + // do nothing, ignore error. continue. + } + } + + std::shared_ptr<ContentProperties> const + pContentProperties = std::make_shared<ContentProperties>(rRes); + + // Check resource against open mode. + switch ( m_pImpl->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_pImpl->m_Results.push_back( + std::make_unique<ResultListEntry>(pContentProperties)); + } + } + catch ( DAVException const & ) + { + } + } + + m_pImpl->m_bCountFinal = true; + + // Callback possible, because listeners may be informed! + aGuard.clear(); + getResultSet()->rowCountFinal(); + } + return !m_pImpl->m_bThrowException; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/ucb/source/ucp/webdav-neon/webdavdatasupplier.hxx b/ucb/source/ucp/webdav-neon/webdavdatasupplier.hxx new file mode 100644 index 000000000..bd5b73340 --- /dev/null +++ b/ucb/source/ucp/webdav-neon/webdavdatasupplier.hxx @@ -0,0 +1,81 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/************************************************************************* + * + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * Copyright 2000, 2010 Oracle and/or its affiliates. + * + * OpenOffice.org - a multi-platform office productivity suite + * + * This file is part of OpenOffice.org. + * + * OpenOffice.org is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License version 3 + * only, as published by the Free Software Foundation. + * + * OpenOffice.org is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License version 3 for more details + * (a copy is included in the LICENSE file that accompanied this code). + * + * You should have received a copy of the GNU Lesser General Public License + * version 3 along with OpenOffice.org. If not, see + * <http://www.openoffice.org/license.html> + * for a copy of the LGPLv3 License. + * + ************************************************************************/ + +#ifndef INCLUDED_UCB_SOURCE_UCP_WEBDAV_NEON_WEBDAVDATASUPPLIER_HXX +#define INCLUDED_UCB_SOURCE_UCP_WEBDAV_NEON_WEBDAVDATASUPPLIER_HXX + +#include <config_lgpl.h> +#include <memory> +#include <rtl/ref.hxx> +#include <ucbhelper/resultset.hxx> + +namespace webdav_ucp { + +struct DataSupplier_Impl; +class Content; + +class DataSupplier : public ucbhelper::ResultSetDataSupplier +{ + std::unique_ptr<DataSupplier_Impl> m_pImpl; + +private: + 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; +}; + +} + +#endif + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/ucb/source/ucp/webdav-neon/webdavprovider.cxx b/ucb/source/ucp/webdav-neon/webdavprovider.cxx new file mode 100644 index 000000000..2b0bd5bfe --- /dev/null +++ b/ucb/source/ucp/webdav-neon/webdavprovider.cxx @@ -0,0 +1,188 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/************************************************************************* + * + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * Copyright 2000, 2010 Oracle and/or its affiliates. + * + * OpenOffice.org - a multi-platform office productivity suite + * + * This file is part of OpenOffice.org. + * + * OpenOffice.org is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License version 3 + * only, as published by the Free Software Foundation. + * + * OpenOffice.org is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License version 3 for more details + * (a copy is included in the LICENSE file that accompanied this code). + * + * You should have received a copy of the GNU Lesser General Public License + * version 3 along with OpenOffice.org. If not, see + * <http://www.openoffice.org/license.html> + * for a copy of the LGPLv3 License. + * + ************************************************************************/ + +#include <sal/config.h> + +#include <com/sun/star/ucb/IllegalIdentifierException.hpp> +#include <comphelper/processfactory.hxx> +#include <ucbhelper/contentidentifier.hxx> +#include <ucbhelper/getcomponentcontext.hxx> +#include <ucbhelper/macros.hxx> +#include <cppuhelper/queryinterface.hxx> +#include "webdavprovider.hxx" +#include "webdavcontent.hxx" + +#include <osl/mutex.hxx> +#include <tools/urlobj.hxx> + +using namespace com::sun::star; +using namespace webdav_ucp; + + +// ContentProvider Implementation. + + +ContentProvider::ContentProvider( + const uno::Reference< uno::XComponentContext >& rxContext ) +: ::ucbhelper::ContentProviderImplHelper( rxContext ), + m_xDAVSessionFactory( new DAVSessionFactory ) +{ +} + + +// virtual +ContentProvider::~ContentProvider() +{ +} + + +// XInterface methods. + +void SAL_CALL ContentProvider::acquire() + throw() +{ + OWeakObject::acquire(); +} + +void SAL_CALL ContentProvider::release() + throw() +{ + 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. + +XSERVICEINFO_COMMOM_IMPL( ContentProvider, + "com.sun.star.comp.WebDAVContentProvider" ) +/// @throws css::uno::Exception +static css::uno::Reference< css::uno::XInterface > +ContentProvider_CreateInstance( const css::uno::Reference< css::lang::XMultiServiceFactory> & rSMgr ) +{ + css::lang::XServiceInfo* pX = new ContentProvider( ucbhelper::getComponentContext(rSMgr) ); + return css::uno::Reference< css::uno::XInterface >::query( pX ); +} + +css::uno::Sequence< OUString > +ContentProvider::getSupportedServiceNames_Static() +{ + css::uno::Sequence< OUString > aSNS { WEBDAV_CONTENT_PROVIDER_SERVICE_NAME }; + return aSNS; +} + +// Service factory implementation. + + +ONE_INSTANCE_SERVICE_FACTORY_IMPL( ContentProvider ); + + +// 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 ).get(); + if ( xContent.is() ) + return xContent; + + // Create a new content. + + try + { + xContent = new ::webdav_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; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/ucb/source/ucp/webdav-neon/webdavprovider.hxx b/ucb/source/ucp/webdav-neon/webdavprovider.hxx new file mode 100644 index 000000000..80556ab23 --- /dev/null +++ b/ucb/source/ucp/webdav-neon/webdavprovider.hxx @@ -0,0 +1,115 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/************************************************************************* + * + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * Copyright 2000, 2010 Oracle and/or its affiliates. + * + * OpenOffice.org - a multi-platform office productivity suite + * + * This file is part of OpenOffice.org. + * + * OpenOffice.org is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License version 3 + * only, as published by the Free Software Foundation. + * + * OpenOffice.org is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License version 3 for more details + * (a copy is included in the LICENSE file that accompanied this code). + * + * You should have received a copy of the GNU Lesser General Public License + * version 3 along with OpenOffice.org. If not, see + * <http://www.openoffice.org/license.html> + * for a copy of the LGPLv3 License. + * + ************************************************************************/ + +#ifndef INCLUDED_UCB_SOURCE_UCP_WEBDAV_NEON_WEBDAVPROVIDER_HXX +#define INCLUDED_UCB_SOURCE_UCP_WEBDAV_NEON_WEBDAVPROVIDER_HXX + +#include <memory> +#include <config_lgpl.h> +#include <rtl/ref.hxx> +#include <com/sun/star/beans/Property.hpp> +#include <com/sun/star/lang/XSingleServiceFactory.hpp> +#include "DAVSessionFactory.hxx" +#include <ucbhelper/providerhelper.hxx> +#include "PropertyMap.hxx" + +namespace webdav_ucp { + + +// UNO service name for the provider. This name will be used by the UCB to +// create instances of the provider. +#define WEBDAV_CONTENT_PROVIDER_SERVICE_NAME "com.sun.star.ucb.WebDAVContentProvider" + +// 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 "http" +#define HTTPS_URL_SCHEME "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" + +#define FTP_URL_SCHEME "ftp" + +#define HTTP_CONTENT_TYPE "application/" HTTP_URL_SCHEME "-content" + +#define WEBDAV_CONTENT_TYPE HTTP_CONTENT_TYPE +#define WEBDAV_COLLECTION_TYPE "application/" VNDSUNSTARWEBDAV_URL_SCHEME "-collection" + + +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 >& rxContext ); + virtual ~ContentProvider() override; + + // XInterface + virtual css::uno::Any SAL_CALL queryInterface( const css::uno::Type & rType ) override; + virtual void SAL_CALL acquire() + throw() override; + virtual void SAL_CALL release() + throw() 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; + + static OUString getImplementationName_Static(); + static css::uno::Sequence< OUString > getSupportedServiceNames_Static(); + + static css::uno::Reference< css::lang::XSingleServiceFactory > + createServiceFactory( const css::uno::Reference< + css::lang::XMultiServiceFactory >& rxServiceMgr ); + + // XContentProvider + virtual css::uno::Reference< css::ucb::XContent > SAL_CALL + queryContent( const css::uno::Reference< css::ucb::XContentIdentifier >& Identifier ) override; + + + // Non-interface methods. + + void getProperty( const OUString & rPropName, + css::beans::Property & rProp ); +}; + +} + +#endif + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/ucb/source/ucp/webdav-neon/webdavresultset.cxx b/ucb/source/ucp/webdav-neon/webdavresultset.cxx new file mode 100644 index 000000000..be4fadc9b --- /dev/null +++ b/ucb/source/ucp/webdav-neon/webdavresultset.cxx @@ -0,0 +1,86 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/************************************************************************* + * + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * Copyright 2000, 2010 Oracle and/or its affiliates. + * + * OpenOffice.org - a multi-platform office productivity suite + * + * This file is part of OpenOffice.org. + * + * OpenOffice.org is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License version 3 + * only, as published by the Free Software Foundation. + * + * OpenOffice.org is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License version 3 for more details + * (a copy is included in the LICENSE file that accompanied this code). + * + * You should have received a copy of the GNU Lesser General Public License + * version 3 along with OpenOffice.org. If not, see + * <http://www.openoffice.org/license.html> + * for a copy of the LGPLv3 License. + * + ************************************************************************/ + + +/************************************************************************** + 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 webdav_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-neon/webdavresultset.hxx b/ucb/source/ucp/webdav-neon/webdavresultset.hxx new file mode 100644 index 000000000..470b16f25 --- /dev/null +++ b/ucb/source/ucp/webdav-neon/webdavresultset.hxx @@ -0,0 +1,60 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/************************************************************************* + * + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * Copyright 2000, 2010 Oracle and/or its affiliates. + * + * OpenOffice.org - a multi-platform office productivity suite + * + * This file is part of OpenOffice.org. + * + * OpenOffice.org is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License version 3 + * only, as published by the Free Software Foundation. + * + * OpenOffice.org is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License version 3 for more details + * (a copy is included in the LICENSE file that accompanied this code). + * + * You should have received a copy of the GNU Lesser General Public License + * version 3 along with OpenOffice.org. If not, see + * <http://www.openoffice.org/license.html> + * for a copy of the LGPLv3 License. + * + ************************************************************************/ + +#ifndef INCLUDED_UCB_SOURCE_UCP_WEBDAV_NEON_WEBDAVRESULTSET_HXX +#define INCLUDED_UCB_SOURCE_UCP_WEBDAV_NEON_WEBDAVRESULTSET_HXX + +#include <config_lgpl.h> +#include <rtl/ref.hxx> +#include <ucbhelper/resultsethelper.hxx> +#include "webdavcontent.hxx" +#include "webdavdatasupplier.hxx" + +namespace webdav_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 ); +}; + +} + +#endif + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/ucb/source/ucp/webdav-neon/webdavservices.cxx b/ucb/source/ucp/webdav-neon/webdavservices.cxx new file mode 100644 index 000000000..b29955535 --- /dev/null +++ b/ucb/source/ucp/webdav-neon/webdavservices.cxx @@ -0,0 +1,67 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/************************************************************************* + * + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * Copyright 2000, 2010 Oracle and/or its affiliates. + * + * OpenOffice.org - a multi-platform office productivity suite + * + * This file is part of OpenOffice.org. + * + * OpenOffice.org is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License version 3 + * only, as published by the Free Software Foundation. + * + * OpenOffice.org is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License version 3 for more details + * (a copy is included in the LICENSE file that accompanied this code). + * + * You should have received a copy of the GNU Lesser General Public License + * version 3 along with OpenOffice.org. If not, see + * <http://www.openoffice.org/license.html> + * for a copy of the LGPLv3 License. + * + ************************************************************************/ + +#include <com/sun/star/lang/XMultiServiceFactory.hpp> +#include <com/sun/star/lang/XSingleServiceFactory.hpp> +#include "webdavprovider.hxx" + +using namespace com::sun::star; + + +extern "C" SAL_DLLPUBLIC_EXPORT void * ucpdav1_component_getFactory( + const char * pImplName, void * pServiceManager, void * /*pRegistryKey*/ ) +{ + void * pRet = nullptr; + + uno::Reference< lang::XMultiServiceFactory > xSMgr( + static_cast< lang::XMultiServiceFactory * >( + pServiceManager ) ); + uno::Reference< lang::XSingleServiceFactory > xFactory; + + + // WebDAV Content Provider. + + + if ( ::webdav_ucp::ContentProvider::getImplementationName_Static(). + equalsAscii( pImplName ) ) + { + xFactory = ::webdav_ucp::ContentProvider::createServiceFactory( xSMgr ); + } + + + if ( xFactory.is() ) + { + xFactory->acquire(); + pRet = xFactory.get(); + } + + return pRet; +} + + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/ucb/source/ucp/webdav/AprEnv.cxx b/ucb/source/ucp/webdav/AprEnv.cxx new file mode 100644 index 000000000..a1e61e5f9 --- /dev/null +++ b/ucb/source/ucp/webdav/AprEnv.cxx @@ -0,0 +1,64 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this 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 "AprEnv.hxx" + +namespace apr_environment +{ + +AprEnv::AprEnv() + : mpAprPool( nullptr ) +{ + apr_initialize(); + + apr_pool_create(&mpAprPool, nullptr); + + mpSerfLockStore = new http_dav_ucp::SerfLockStore(); +} + +AprEnv::~AprEnv() +{ + delete mpSerfLockStore; + + apr_pool_destroy(mpAprPool); + + apr_terminate(); +} + +/* static */ +AprEnv* AprEnv::getAprEnv() +{ + static AprEnv rAprEnv; + + return &rAprEnv; +} + +apr_pool_t* AprEnv::getAprPool() +{ + return mpAprPool; +} + +http_dav_ucp::SerfLockStore* AprEnv::getSerfLockStore() +{ + return mpSerfLockStore; +} + +} // namespace apr_environment + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/ucb/source/ucp/webdav/AprEnv.hxx b/ucb/source/ucp/webdav/AprEnv.hxx new file mode 100644 index 000000000..4590c64aa --- /dev/null +++ b/ucb/source/ucp/webdav/AprEnv.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 . + */ + + +#ifndef INCLUDED_UCB_SOURCE_UCP_WEBDAV_APRENV_HXX +#define INCLUDED_UCB_SOURCE_UCP_WEBDAV_APRENV_HXX + +#include <apr_pools.h> +#include "SerfLockStore.hxx" + +namespace apr_environment +{ + +// singleton class providing environment for APR libraries +class AprEnv +{ + public: + ~AprEnv(); + + static AprEnv* getAprEnv(); + + apr_pool_t* getAprPool(); + + http_dav_ucp::SerfLockStore* getSerfLockStore(); + + private: + apr_pool_t* mpAprPool; + // SerfLockStore is a static object and has to be destroyed + // before AprEnv, so store it here. + http_dav_ucp::SerfLockStore* mpSerfLockStore; + + AprEnv(); + + AprEnv(const AprEnv&) = delete; + AprEnv& operator=(const AprEnv&) = delete; +}; + +} // namespace apr_environment + +#endif // INCLUDED_UCB_SOURCE_UCP_WEBDAV_APRENV_HXX + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/ucb/source/ucp/webdav/ContentProperties.cxx b/ucb/source/ucp/webdav/ContentProperties.cxx new file mode 100644 index 000000000..85406e680 --- /dev/null +++ b/ucb/source/ucp/webdav/ContentProperties.cxx @@ -0,0 +1,593 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this 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 "SerfUri.hxx" +#include "DAVResource.hxx" +#include "DAVProperties.hxx" +#include "DateTimeHelper.hxx" +#include "webdavprovider.hxx" +#include "ContentProperties.hxx" + +#include <sal/log.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 ) +{ + SAL_WARN_IF( !rResource.uri.getLength(), "ucb.ucp.webdav", + "ContentProperties ctor - Empty resource URI!" ); + + // Title + try + { + SerfUri aURI( rResource.uri ); + m_aEscapedTitle = aURI.GetPathBaseName(); + + (*m_xProps)[ OUString( "Title" ) ] + = PropertyValue( + uno::makeAny( aURI.GetPathBaseNameUnescaped() ), true ); + } + catch ( DAVException const & ) + { + (*m_xProps)[ OUString( "Title" ) ] + = PropertyValue( + uno::makeAny( + 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::makeAny( rTitle ), true ); + (*m_xProps)[ OUString( "IsFolder" ) ] + = PropertyValue( uno::makeAny( bFolder ), true ); + (*m_xProps)[ OUString( "IsDocument" ) ] + = PropertyValue( uno::makeAny( bool( !bFolder ) ), true ); +} + + +ContentProperties::ContentProperties( const OUString & rTitle ) +: m_xProps( new PropertyValueMap ), + m_bTrailingSlash( false ) +{ + (*m_xProps)[ OUString( "Title" ) ] + = PropertyValue( uno::makeAny( 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.get() + ? 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, + bool bIncludeUnmatched /* = true */ ) +{ + + // 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 + { + if ( bIncludeUnmatched ) + propertyNames.push_back( rProp.Name ); + } + } +} + + +// static +void ContentProperties::UCBNamesToHTTPNames( + const uno::Sequence< beans::Property > & rProps, + std::vector< OUString > & propertyNames, + bool bIncludeUnmatched /* = true */ ) +{ + + // 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 + { + if ( bIncludeUnmatched ) + 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::addProperties( const ContentProperties & rProps ) +{ + for ( const auto& rProp : *rProps.m_xProps ) + { + addProperty( + rProp.first, rProp.second.value(), rProp.second.isCaseSensitive() ); + } +} + + +void ContentProperties::addProperties( + const std::vector< DAVPropertyValue > & rProps ) +{ + for ( const auto& rProp : rProps ) + { + addProperty( rProp ); + } +} + + +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::makeAny( 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::makeAny( aValue.toInt64() ), true ); + } + else if ( rName == "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::makeAny( 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 == "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::makeAny( aDate ), true ); + } + else if ( rName == "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::makeAny( 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::makeAny( bFolder ), true ); + (*m_xProps)[ OUString( "IsDocument" ) ] + = PropertyValue( uno::makeAny( bool( !bFolder ) ), true ); + (*m_xProps)[ OUString( "ContentType" ) ] + = PropertyValue( uno::makeAny( bFolder + ? OUString( WEBDAV_COLLECTION_TYPE ) + : OUString( WEBDAV_CONTENT_TYPE ) ), true ); + } + // else if ( rName.equals( DAVProperties::SUPPORTEDLOCK ) ) + // { + // } + + // Save property. + (*m_xProps)[ rName ] = PropertyValue( rValue, bIsCaseSensitive ); +} + + +// CachableContentProperties Implementation. + + +namespace +{ + bool isCachable( OUString const & 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.equals( aNonCachableProps[ n ] ) ) + return false; + } + else + if ( rName.equalsIgnoreAsciiCase( 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/ContentProperties.hxx b/ucb/source/ucp/webdav/ContentProperties.hxx new file mode 100644 index 000000000..a48383f8e --- /dev/null +++ b/ucb/source/ucp/webdav/ContentProperties.hxx @@ -0,0 +1,184 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + + +#ifndef INCLUDED_UCB_SOURCE_UCP_WEBDAV_CONTENTPROPERTIES_HXX +#define INCLUDED_UCB_SOURCE_UCP_WEBDAV_CONTENTPROPERTIES_HXX + +#include <memory> +#include <unordered_map> +#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( const css::uno::Any & rValue, + bool bIsCaseSensitive ) + : m_aValue( rValue), + 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 unless bIncludeUnmatched is set to false. + // 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, + bool bIncludeUnmatched = true ); + + // 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 unless bIncludeUnmatched is set to false. + // 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, + bool bIncludeUnmatched = true ); + + // 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 entries. + void addProperties( const ContentProperties & rProps ); + + // overwrites probably existing entries. + void addProperties( const std::vector< DAVPropertyValue > & rProps ); + + // 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 + +#endif // INCLUDED_UCB_SOURCE_UCP_WEBDAV_CONTENTPROPERTIES_HXX + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/ucb/source/ucp/webdav/DAVAuthListener.hxx b/ucb/source/ucp/webdav/DAVAuthListener.hxx new file mode 100644 index 000000000..95f61c0a0 --- /dev/null +++ b/ucb/source/ucp/webdav/DAVAuthListener.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 . + */ + + +#ifndef INCLUDED_UCB_SOURCE_UCP_WEBDAV_DAVAUTHLISTENER_HXX +#define INCLUDED_UCB_SOURCE_UCP_WEBDAV_DAVAUTHLISTENER_HXX + +#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 + +#endif // INCLUDED_UCB_SOURCE_UCP_WEBDAV_DAVAUTHLISTENER_HXX + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/ucb/source/ucp/webdav/DAVAuthListenerImpl.hxx b/ucb/source/ucp/webdav/DAVAuthListenerImpl.hxx new file mode 100644 index 000000000..fc3f9a845 --- /dev/null +++ b/ucb/source/ucp/webdav/DAVAuthListenerImpl.hxx @@ -0,0 +1,64 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + + +#ifndef INCLUDED_UCB_SOURCE_UCP_WEBDAV_DAVAUTHLISTENERIMPL_HXX +#define INCLUDED_UCB_SOURCE_UCP_WEBDAV_DAVAUTHLISTENERIMPL_HXX + +#include "DAVAuthListener.hxx" +#include <com/sun/star/ucb/XCommandEnvironment.hpp> + + +namespace http_dav_ucp +{ + + + + + class DAVAuthListener_Impl : public DAVAuthListener + { + public: + + DAVAuthListener_Impl( + const css::uno::Reference<css::ucb::XCommandEnvironment>& xEnv, + const OUString & inURL ) + : m_xEnv( xEnv ), m_aURL( 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; + }; + +} + +#endif + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/ucb/source/ucp/webdav/DAVException.hxx b/ucb/source/ucp/webdav/DAVException.hxx new file mode 100644 index 000000000..3b21067e5 --- /dev/null +++ b/ucb/source/ucp/webdav/DAVException.hxx @@ -0,0 +1,168 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + + +#ifndef INCLUDED_UCB_SOURCE_UCP_WEBDAV_DAVEXCEPTION_HXX +#define INCLUDED_UCB_SOURCE_UCP_WEBDAV_DAVEXCEPTION_HXX + +#include <rtl/ustring.hxx> + +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) +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 +const sal_uInt16 SC_INSUFFICIENT_STORAGE = 507; + + +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_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, + const OUString & rData ) + : mExceptionCode( inExceptionCode ) + , mData( rData ) + , mStatusCode( SC_NONE ) + {}; + DAVException( ExceptionCode inExceptionCode, + const OUString & rData, + sal_uInt16 nStatusCode ) + : mExceptionCode( inExceptionCode ) + , mData( rData ) + , mStatusCode( nStatusCode ) + {}; + + const ExceptionCode & getError() const { return mExceptionCode; } + const OUString & getData() const { return mData; } + sal_uInt16 getStatus() const { return mStatusCode; } +}; + +} // namespace http_dav_ucp + +#endif // INCLUDED_UCB_SOURCE_UCP_WEBDAV_DAVEXCEPTION_HXX + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/ucb/source/ucp/webdav/DAVProperties.cxx b/ucb/source/ucp/webdav/DAVProperties.cxx new file mode 100644 index 000000000..a08a8488d --- /dev/null +++ b/ucb/source/ucp/webdav/DAVProperties.cxx @@ -0,0 +1,222 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this 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 <string.h> +#include "DAVProperties.hxx" +#include <rtl/ustrbuf.hxx> + +using namespace http_dav_ucp; + +const OUString DAVProperties::CREATIONDATE = + OUString( "DAV:creationdate" ); +const OUString DAVProperties::DISPLAYNAME = + OUString( "DAV:displayname" ); +const OUString DAVProperties::GETCONTENTLANGUAGE = + OUString( "DAV:getcontentlanguage" ); +const OUString DAVProperties::GETCONTENTLENGTH = + OUString( "DAV:getcontentlength" ); +const OUString DAVProperties::GETCONTENTTYPE = + OUString( "DAV:getcontenttype" ); +const OUString DAVProperties::GETETAG = + OUString( "DAV:getetag" ); +const OUString DAVProperties::GETLASTMODIFIED = + OUString( "DAV:getlastmodified" ); +const OUString DAVProperties::LOCKDISCOVERY = + OUString( "DAV:lockdiscovery" ); +const OUString DAVProperties::RESOURCETYPE = + OUString( "DAV:resourcetype" ); +const OUString DAVProperties::SUPPORTEDLOCK = + OUString( "DAV:supportedlock" ); + +const OUString DAVProperties::EXECUTABLE = + OUString( "http://apache.org/dav/props/executable" ); + + +// static +void DAVProperties::createSerfPropName( const OUString & rFullName, + SerfPropName & rName ) +{ + if ( rFullName.startsWith( "DAV:" ) ) + { + rName.nspace = "DAV:"; + rName.name + = strdup( OUStringToOString( + rFullName.copy( RTL_CONSTASCII_LENGTH( "DAV:" ) ), + RTL_TEXTENCODING_UTF8 ).getStr() ); + } + else if ( rFullName.startsWith( "http://apache.org/dav/props/" ) ) + { + rName.nspace = "http://apache.org/dav/props/"; + rName.name + = strdup( OUStringToOString( + rFullName.copy( + RTL_CONSTASCII_LENGTH( + "http://apache.org/dav/props/" ) ), + RTL_TEXTENCODING_UTF8 ).getStr() ); + } + else if ( rFullName.startsWith( "http://ucb.openoffice.org/dav/props/" ) ) + { + rName.nspace = "http://ucb.openoffice.org/dav/props/"; + rName.name + = strdup( OUStringToOString( + rFullName.copy( + RTL_CONSTASCII_LENGTH( + "http://ucb.openoffice.org/dav/props/" ) ), + RTL_TEXTENCODING_UTF8 ).getStr() ); + } + else if ( rFullName.startsWith( "<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 + { + // 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 ( DAVProperties::RESOURCETYPE.matchIgnoreAsciiCase( aName, 4 ) || + DAVProperties::SUPPORTEDLOCK.matchIgnoreAsciiCase( aName, 4 ) || + DAVProperties::LOCKDISCOVERY.matchIgnoreAsciiCase( aName, 4 ) || + DAVProperties::CREATIONDATE.matchIgnoreAsciiCase( aName, 4 ) || + DAVProperties::DISPLAYNAME.matchIgnoreAsciiCase( aName, 4 ) || + DAVProperties::GETCONTENTLANGUAGE.matchIgnoreAsciiCase( aName, 4 ) || + DAVProperties::GETCONTENTLENGTH.matchIgnoreAsciiCase( aName, 4 ) || + DAVProperties::GETCONTENTTYPE.matchIgnoreAsciiCase( aName, 4 ) || + DAVProperties::GETETAG.matchIgnoreAsciiCase( aName, 4 ) || + DAVProperties::GETLASTMODIFIED.matchIgnoreAsciiCase( aName, 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(const OUString& rFullName, OUString& rParsedName) +{ + sal_Int32 nLen = rFullName.getLength(); + if ( nLen <= 0 || + !rFullName.startsWith( "<prop:" ) || + !rFullName.endsWith( "\">" ) ) + return false; + + sal_Int32 nStart = RTL_CONSTASCII_LENGTH( "<prop:" ); + sal_Int32 nEnd = rFullName.indexOf( ' ', nStart ); + if ( nEnd == -1 ) + return false; + + OUString sPropName = rFullName.copy( nStart, nEnd - nStart ); + if ( !sPropName.getLength() ) + return false; + + // TODO skip whitespaces? + if ( !rFullName.match( "xmlns:prop=\"", ++nEnd ) ) + return false; + + nStart = nEnd + RTL_CONSTASCII_LENGTH( "xmlns:prop=\"" ); + nEnd = rFullName.indexOf( '"', nStart ); + if ( nEnd != nLen - RTL_CONSTASCII_LENGTH( "\">" ) ) + return false; + + OUString sNamesp = rFullName.copy( nStart, nEnd - nStart ); + if ( !( nLen = sNamesp.getLength() ) ) + 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/DAVProperties.hxx b/ucb/source/ucp/webdav/DAVProperties.hxx new file mode 100644 index 000000000..926afd6a7 --- /dev/null +++ b/ucb/source/ucp/webdav/DAVProperties.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 . + */ + + +#ifndef INCLUDED_UCB_SOURCE_UCP_WEBDAV_DAVPROPERTIES_HXX +#define INCLUDED_UCB_SOURCE_UCP_WEBDAV_DAVPROPERTIES_HXX + +#include <rtl/ustring.hxx> + +namespace http_dav_ucp +{ + +typedef struct { const char *nspace, *name; } SerfPropName; + +struct DAVProperties +{ + static const OUString CREATIONDATE; + static const OUString DISPLAYNAME; + static const OUString GETCONTENTLANGUAGE; + static const OUString GETCONTENTLENGTH; + static const OUString GETCONTENTTYPE; + static const OUString GETETAG; + static const OUString GETLASTMODIFIED; + static const OUString LOCKDISCOVERY; + static const OUString RESOURCETYPE; + static const OUString SUPPORTEDLOCK; + static const OUString EXECUTABLE; + + static void createSerfPropName( const OUString & rFullName, + SerfPropName & rName ); + static void createUCBPropName ( const char * nspace, + const char * name, + OUString & rFullName ); + + static bool isUCBDeadProperty( const SerfPropName & rName ); + static bool isUCBSpecialProperty( const OUString & rFullName, + OUString & rParsedName ); +}; + +} // namespace http_dav_ucp + +#endif // INCLUDED_UCB_SOURCE_UCP_WEBDAV_DAVPROPERTIES_HXX + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/ucb/source/ucp/webdav/DAVRequestEnvironment.hxx b/ucb/source/ucp/webdav/DAVRequestEnvironment.hxx new file mode 100644 index 000000000..1b1faff89 --- /dev/null +++ b/ucb/source/ucp/webdav/DAVRequestEnvironment.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 . + */ + + +#ifndef INCLUDED_UCB_SOURCE_UCP_WEBDAV_DAVREQUESTENVIRONMENT_HXX +#define INCLUDED_UCB_SOURCE_UCP_WEBDAV_DAVREQUESTENVIRONMENT_HXX + +#include <vector> +#include <rtl/ref.hxx> +#include <com/sun/star/ucb/XCommandEnvironment.hpp> +#include "DAVAuthListener.hxx" + +namespace http_dav_ucp +{ + typedef std::pair< OUString, OUString > DAVRequestHeader; + typedef std::vector< DAVRequestHeader > DAVRequestHeaders; + +struct DAVRequestEnvironment +{ + OUString m_aRequestURI; + rtl::Reference< DAVAuthListener > m_xAuthListener; + DAVRequestHeaders m_aRequestHeaders; + css::uno::Reference< css::ucb::XCommandEnvironment > m_xEnv; + +DAVRequestEnvironment( const OUString & rRequestURI, + const rtl::Reference< DAVAuthListener > & xListener, + const DAVRequestHeaders & rRequestHeaders, + const css::uno::Reference< css::ucb::XCommandEnvironment > & xEnv) + : m_aRequestURI( rRequestURI ), + m_xAuthListener( xListener ), + m_aRequestHeaders( rRequestHeaders ), + m_xEnv( xEnv ){} + + DAVRequestEnvironment() {} +}; + +} // namespace http_dav_ucp + +#endif // INCLUDED_UCB_SOURCE_UCP_WEBDAV_DAVREQUESTENVIRONMENT_HXX + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/ucb/source/ucp/webdav/DAVResource.hxx b/ucb/source/ucp/webdav/DAVResource.hxx new file mode 100644 index 000000000..aca9f3846 --- /dev/null +++ b/ucb/source/ucp/webdav/DAVResource.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 . + */ + + +#ifndef INCLUDED_UCB_SOURCE_UCP_WEBDAV_DAVRESOURCE_HXX +#define INCLUDED_UCB_SOURCE_UCP_WEBDAV_DAVRESOURCE_HXX + +#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; + + DAVResource() {} + explicit DAVResource( const OUString & inUri ) : uri( inUri ) {} +}; + +struct DAVResourceInfo +{ + OUString uri; + std::vector < OUString > properties; + + explicit DAVResourceInfo( const OUString & inUri ) : uri( inUri ) {} +}; + +} // namespace http_dav_ucp + +#endif // INCLUDED_UCB_SOURCE_UCP_WEBDAV_DAVRESOURCE_HXX + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/ucb/source/ucp/webdav/DAVResourceAccess.cxx b/ucb/source/ucp/webdav/DAVResourceAccess.cxx new file mode 100644 index 000000000..90001a818 --- /dev/null +++ b/ucb/source/ucp/webdav/DAVResourceAccess.cxx @@ -0,0 +1,1116 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this 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 <sal/log.hxx> + +#include "DAVAuthListenerImpl.hxx" +#include "DAVResourceAccess.hxx" + +#include <com/sun/star/lang/IllegalArgumentException.hpp> +#include <com/sun/star/io/IOException.hpp> + +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, + true /*bAllowPersistentStoring*/, + bCanUseSystemCredentials ); + 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() ) + { + 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. + + +DAVResourceAccess::DAVResourceAccess( + const uno::Reference< uno::XComponentContext > & rContext, + rtl::Reference< DAVSessionFactory > const & rSessionFactory, + const OUString & rURL ) +: m_aURL( rURL ), + m_xSessionFactory( rSessionFactory ), + m_xContext( rContext ) +{ +} + + +DAVResourceAccess::DAVResourceAccess( const DAVResourceAccess & rOther ) +: m_aURL( rOther.m_aURL ), + m_aPath( rOther.m_aPath ), + 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_xSession = rOther.m_xSession; + m_xSessionFactory = rOther.m_xSessionFactory; + m_xContext = rOther.m_xContext; + m_aRedirectURIs = rOther.m_aRedirectURIs; + + return *this; +} + + +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( + getRequestURI(), + new DAVAuthListener_Impl( xEnv, m_aURL ), + aHeaders, xEnv ) ); + } + catch ( DAVException & 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( + getRequestURI(), + new DAVAuthListener_Impl( xEnv, m_aURL ), + aHeaders, xEnv ) ) ; + } + catch ( DAVException & 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( + getRequestURI(), + new DAVAuthListener_Impl( xEnv, m_aURL ), + aHeaders, xEnv ) ); + } + catch ( DAVException & 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( + getRequestURI(), + new DAVAuthListener_Impl( xEnv, m_aURL ), + aHeaders, xEnv ) ); + } + catch ( DAVException & 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( + getRequestURI(), + new DAVAuthListener_Impl( + xEnv, m_aURL ), + aHeaders, xEnv ) ); + } + catch ( DAVException & 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( + getRequestURI(), + new DAVAuthListener_Impl( xEnv, m_aURL ), + aHeaders, xEnv ) ); + } + catch ( DAVException & 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( + getRequestURI(), + new DAVAuthListener_Impl( + xEnv, m_aURL ), + aHeaders, xEnv ) ); + } + catch ( DAVException & e ) + { + errorCount++; + bRetry = handleException( e, errorCount ); + if ( !bRetry ) + throw; + } + } + while ( bRetry ); + + return xStream; +} + + +uno::Reference< io::XInputStream > DAVResourceAccess::GET( + DAVRequestHeaders &rRequestHeaders, + 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 + { + getUserRequestHeaders( xEnv, + getRequestURI(), + ucb::WebDAVHTTPMethod_GET, + rRequestHeaders ); + + xStream = m_xSession->GET( getRequestURI(), + rHeaderNames, + rResource, + DAVRequestEnvironment( + getRequestURI(), + new DAVAuthListener_Impl( + xEnv, m_aURL ), + rRequestHeaders, xEnv ) ); + } + catch ( DAVException & e ) + { + errorCount++; + bRetry = handleException( e, errorCount ); + if ( !bRetry ) + throw; + } + } + while ( bRetry ); + + return xStream; +} + + +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( + getRequestURI(), + new DAVAuthListener_Impl( xEnv, m_aURL ), + aHeaders, xEnv ) ); + } + catch ( DAVException & e ) + { + errorCount++; + bRetry = handleException( e, errorCount ); + if ( !bRetry ) + throw; + } + } + while ( bRetry ); +} + + +void DAVResourceAccess::abort() +{ + // 17.11.09 (tkr): abort currently disabled caused by issue i106766 + // initialize(); + // m_xSession->abort(); + SAL_INFO("ucb.ucp.webdav", "Not implemented. -> #i106766#" ); +} + + +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( + getRequestURI(), + new DAVAuthListener_Impl( xEnv, m_aURL ), + aHeaders, xEnv ) ); + } + catch ( DAVException & 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( + getRequestURI(), + new DAVAuthListener_Impl( + xEnv, m_aURL ), + aHeaders, xEnv ) ); + } + catch ( DAVException & 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( + getRequestURI(), + new DAVAuthListener_Impl( xEnv, m_aURL ), + aHeaders, xEnv ) ); + } + catch ( DAVException & 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( + getRequestURI(), + new DAVAuthListener_Impl( xEnv, m_aURL ), + aHeaders, xEnv ) ); + } + catch ( DAVException & 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( + getRequestURI(), + new DAVAuthListener_Impl( xEnv, m_aURL ), + aHeaders, xEnv ), + bOverwrite ); + } + catch ( DAVException & 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( + getRequestURI(), + new DAVAuthListener_Impl( xEnv, m_aURL ), + aHeaders, xEnv ), + bOverwrite ); + } + catch ( DAVException & 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( + getRequestURI(), + new DAVAuthListener_Impl( xEnv, m_aURL ), + aHeaders, xEnv ) ); + } + catch ( DAVException & 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( + getRequestURI(), + new DAVAuthListener_Impl( xEnv, m_aURL ), + aHeaders, xEnv ) ); + } + catch ( DAVException & 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( + getRequestURI(), + new DAVAuthListener_Impl( xEnv, m_aURL ), + aHeaders, xEnv ) ); + } + catch ( DAVException & e ) + { + errorCount++; + bRetry = handleException( e, errorCount ); + if ( !bRetry ) + throw; + } + } + while ( bRetry ); +} + + +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() ) + { + SerfUri aURI( m_aURL ); + OUString aPath( aURI.GetPath() ); + + /* #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_xSession.clear(); + + // create new webdav session + m_xSession + = m_xSessionFactory->createDAVSession( m_aURL, m_xContext ); + + if ( !m_xSession.is() ) + return; + } + + // Own URI is needed for 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 +{ + SAL_WARN_IF( !m_xSession.is(), "ucb.ucp.webdav", + "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 ) ); + } +} + + +bool DAVResourceAccess::detectRedirectCycle( + const OUString& rRedirectURL ) +{ + osl::Guard< osl::Mutex > aGuard( m_aMutex ); + + SerfUri aUri( rRedirectURL ); + + return std::any_of(m_aRedirectURIs.begin(), m_aRedirectURIs.end(), + [&aUri](const SerfUri& rUri) { return aUri == rUri; }); +} + + +void DAVResourceAccess::resetUri() +{ + osl::Guard< osl::Mutex > aGuard( m_aMutex ); + if ( ! m_aRedirectURIs.empty() ) + { + std::vector< SerfUri >::const_iterator it = m_aRedirectURIs.begin(); + + SerfUri aUri( *it ); + m_aRedirectURIs.clear(); + setURL ( aUri.GetURI() ); + initialize(); + } +} + + +bool DAVResourceAccess::handleException( DAVException & e, int 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; + // --> tkr #67048# copy & paste images doesn't display. + // 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. + if ( ( e.getStatus() < 400 || e.getStatus() >= 500 || + e.getStatus() == 413 ) && + errorCount < 3 ) + { + return true; + } + return false; + // <-- + // --> tkr: 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/DAVResourceAccess.hxx b/ucb/source/ucp/webdav/DAVResourceAccess.hxx new file mode 100644 index 000000000..37d76d83f --- /dev/null +++ b/ucb/source/ucp/webdav/DAVResourceAccess.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/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + + +#ifndef INCLUDED_UCB_SOURCE_UCP_WEBDAV_DAVRESOURCEACCESS_HXX +#define INCLUDED_UCB_SOURCE_UCP_WEBDAV_DAVRESOURCEACCESS_HXX + +#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/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 "SerfUri.hxx" + +namespace http_dav_ucp +{ + +class DAVSessionFactory; + +class DAVResourceAccess +{ + osl::Mutex m_aMutex; + OUString m_aURL; + OUString m_aPath; + rtl::Reference< DAVSession > m_xSession; + rtl::Reference< DAVSessionFactory > m_xSessionFactory; + css::uno::Reference< css::uno::XComponentContext > m_xContext; + std::vector< SerfUri > m_aRedirectURIs; + +public: + DAVResourceAccess() = default; + DAVResourceAccess( const css::uno::Reference< css::uno::XComponentContext > & rContext, + rtl::Reference< DAVSessionFactory > const & rSessionFactory, + const OUString & rURL ); + DAVResourceAccess( const DAVResourceAccess & rOther ); + + DAVResourceAccess & operator=( const DAVResourceAccess & rOther ); + + /// @throws DAVException + void setURL( const OUString & rNewURL ); + + void resetUri(); + + const OUString & getURL() const { return m_aURL; } + + rtl::Reference< DAVSessionFactory > getSessionFactory() const + { return m_xSessionFactory; } + + // DAV methods + + + // 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 ); + + /// @throws DAVException + css::uno::Reference< css::io::XInputStream > + GET( 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 + static abort(); + + // helper + static void + getUserRequestHeaders( + const css::uno::Reference< css::ucb::XCommandEnvironment > & xEnv, + const OUString & rURI, + css::ucb::WebDAVHTTPMethod eMethod, + DAVRequestHeaders & rRequestHeaders ); + +private: + const OUString & getRequestURI() const; + /// @throws DAVException + bool detectRedirectCycle( const OUString& rRedirectURL ); + /// @throws DAVException + bool handleException( DAVException & e, int errorCount ); + /// @throws DAVException + void initialize(); +}; + +} // namespace http_dav_ucp + +#endif // INCLUDED_UCB_SOURCE_UCP_WEBDAV_DAVRESOURCEACCESS_HXX + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/ucb/source/ucp/webdav/DAVSession.hxx b/ucb/source/ucp/webdav/DAVSession.hxx new file mode 100644 index 000000000..aa47b1504 --- /dev/null +++ b/ucb/source/ucp/webdav/DAVSession.hxx @@ -0,0 +1,205 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + + +#ifndef INCLUDED_UCB_SOURCE_UCP_WEBDAV_DAVSESSION_HXX +#define INCLUDED_UCB_SOURCE_UCP_WEBDAV_DAVSESSION_HXX + +#include <memory> +#include <rtl/ustring.hxx> +#include <com/sun/star/io/XInputStream.hpp> +#include <com/sun/star/io/XOutputStream.hpp> +#include "DAVResource.hxx" +#include "DAVSessionFactory.hxx" +#include "DAVTypes.hxx" +#include "DAVRequestEnvironment.hxx" + +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 & inPath ) = 0; + + virtual bool UsesProxy() = 0; + + // DAV methods + + + // NOT USED + /* + virtual void OPTIONS( const OUString & inPath, + DAVCapabilities & outCapabilities, + const DAVRequestEnvironment & rEnv ) + throw( DAVException ) = 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; + + // refresh existing lock. + /// @throws DAVException + virtual sal_Int64 LOCK( const OUString & inPath, + sal_Int64 nTimeout, + const DAVRequestEnvironment & rEnv ) = 0; + + /// @throws DAVException + virtual void UNLOCK( const OUString & inPath, + const DAVRequestEnvironment & rEnv ) = 0; + + /// @throws DAVException + virtual void abort() = 0; + +protected: + rtl::Reference< DAVSessionFactory > m_xFactory; + + explicit DAVSession( rtl::Reference< DAVSessionFactory > const & rFactory ) + : m_xFactory( rFactory ), m_nRefCount( 0 ) {} + + virtual ~DAVSession() {} + +private: + DAVSessionFactory::Map::iterator m_aContainerIt; + oslInterlockedCount m_nRefCount; + + friend class DAVSessionFactory; + friend struct std::default_delete< DAVSession >; +}; + +} // namespace http_dav_ucp + +#endif // INCLUDED_UCB_SOURCE_UCP_WEBDAV_DAVSESSION_HXX + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/ucb/source/ucp/webdav/DAVSessionFactory.cxx b/ucb/source/ucp/webdav/DAVSessionFactory.cxx new file mode 100644 index 000000000..6a0963f91 --- /dev/null +++ b/ucb/source/ucp/webdav/DAVSessionFactory.cxx @@ -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 . + */ + +#include <memory> +#include "DAVSessionFactory.hxx" +#include "SerfSession.hxx" +#include "SerfUri.hxx" + +using namespace http_dav_ucp; +using namespace com::sun::star; + +DAVSessionFactory::~DAVSessionFactory() +{ +} + +rtl::Reference< DAVSession > DAVSessionFactory::createDAVSession( + const OUString & inUri, + const uno::Reference< uno::XComponentContext > & rxContext ) +{ + osl::MutexGuard aGuard( m_aMutex ); + + if ( !m_xProxyDecider.get() ) + m_xProxyDecider.reset( new ucbhelper::InternetProxyDecider( rxContext ) ); + + Map::iterator aIt = std::find_if(m_aMap.begin(), m_aMap.end(), + [&inUri](const Map::value_type& rEntry) { return rEntry.second->CanUse( inUri ); }); + + if ( aIt == m_aMap.end() ) + { + SerfUri aURI( inUri ); + + std::unique_ptr< DAVSession > xElement( + new SerfSession( this, inUri, *m_xProxyDecider ) ); + + aIt = m_aMap.emplace( inUri, xElement.get() ).first; + aIt->second->m_aContainerIt = aIt; + xElement.release(); + 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(); + + // If URL scheme is different from http or https we definitely + // have to use a proxy and therefore can optimize the getProxy + // call a little: + SerfUri aURI( inUri ); + + aIt->second = new SerfSession( this, inUri, *m_xProxyDecider ); + aIt->second->m_aContainerIt = aIt; + return aIt->second; + } +} + +void DAVSessionFactory::releaseElement( DAVSession * pElement ) +{ + assert( pElement ); + osl::MutexGuard 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/DAVSessionFactory.hxx b/ucb/source/ucp/webdav/DAVSessionFactory.hxx new file mode 100644 index 000000000..ec1fcfe3c --- /dev/null +++ b/ucb/source/ucp/webdav/DAVSessionFactory.hxx @@ -0,0 +1,72 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + + +#ifndef INCLUDED_UCB_SOURCE_UCP_WEBDAV_DAVSESSIONFACTORY_HXX +#define INCLUDED_UCB_SOURCE_UCP_WEBDAV_DAVSESSIONFACTORY_HXX + +#ifdef min +#undef min // GNU libstdc++ <memory> includes <limit> which defines methods called min... +#endif +#include <map> +#include <memory> +#include <osl/mutex.hxx> +#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::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 css::uno::Reference< css::uno::XComponentContext >& rxContext ); + +private: + typedef std::map< OUString, DAVSession * > Map; + + Map m_aMap; + osl::Mutex m_aMutex; + std::unique_ptr< ucbhelper::InternetProxyDecider > m_xProxyDecider; + + void releaseElement( DAVSession * pElement ); + + friend class DAVSession; +}; + +} // namespace http_dav_ucp + +#endif // INCLUDED_UCB_SOURCE_UCP_WEBDAV_DAVSESSIONFACTORY_HXX + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/ucb/source/ucp/webdav/DAVTypes.hxx b/ucb/source/ucp/webdav/DAVTypes.hxx new file mode 100644 index 000000000..3cce18abb --- /dev/null +++ b/ucb/source/ucp/webdav/DAVTypes.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 . + */ + + +#ifndef INCLUDED_UCB_SOURCE_UCP_WEBDAV_DAVTYPES_HXX +#define INCLUDED_UCB_SOURCE_UCP_WEBDAV_DAVTYPES_HXX + +#include <rtl/ustring.hxx> +#include <com/sun/star/uno/Any.hxx> + +namespace http_dav_ucp +{ +/* RFC 2518 + +15.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. + +15.2 Class 2 + + A class 2 compliant resource MUST meet all class 1 requirements and + support the LOCK method, the supportedlock property, the + lockdiscovery property, the Time-Out response header and the Lock- + Token request header. A class "2" compliant resource SHOULD also + support the Time-Out 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. +*/ + +struct DAVCapabilities +{ + bool class1; + bool class2; + bool executable; // supports "executable" property (introduced by mod_dav) + + DAVCapabilities() : class1( false ), class2( false ), executable( false ) {} +}; + +enum Depth { DAVZERO = 0, DAVONE = 1, DAVINFINITY = -1 }; + +enum ProppatchOperation { PROPSET = 0, PROPREMOVE = 1 }; + +struct ProppatchValue +{ + ProppatchOperation operation; + OUString name; + css::uno::Any value; + + ProppatchValue( const ProppatchOperation o, + const OUString & n, + const css::uno::Any & v ) + : operation( o ), name( n ), value( v ) {} +}; + +} // namespace http_dav_ucp + +#endif // INCLUDED_UCB_SOURCE_UCP_WEBDAV_DAVTYPES_HXX + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/ucb/source/ucp/webdav/DateTimeHelper.cxx b/ucb/source/ucp/webdav/DateTimeHelper.cxx new file mode 100644 index 000000000..dfa21ed56 --- /dev/null +++ b/ucb/source/ucp/webdav/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 (const OUString& s, + DateTime& dateTime) +{ + OString aDT (s.getStr(), s.getLength(), 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 (const OUString& month) +{ + if (month == "Jan") + return 1; + else if (month == "Feb") + return 2; + else if (month == "Mar") + return 3; + else if (month == "Apr") + return 4; + else if (month == "May") + return 5; + else if (month == "Jun") + return 6; + else if (month == "Jul") + return 7; + else if (month == "Aug") + return 8; + else if (month == "Sep") + return 9; + else if (month == "Oct") + return 10; + else if (month == "Nov") + return 11; + else if (month == "Dec") + return 12; + else + return 0; +} + +bool DateTimeHelper::RFC2068_To_DateTime (const OUString& s, + DateTime& dateTime) +{ + int year; + int day; + int hours; + int minutes; + int seconds; + char string_month[3 + 1]; + char string_day[3 + 1]; + + sal_Int32 found = s.indexOf (','); + if (found != -1) + { + OString aDT (s.getStr(), s.getLength(), 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 (s.getStr(), s.getLength(), 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 (const OUString& 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/DateTimeHelper.hxx b/ucb/source/ucp/webdav/DateTimeHelper.hxx new file mode 100644 index 000000000..b63921533 --- /dev/null +++ b/ucb/source/ucp/webdav/DateTimeHelper.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 . + */ + + +#ifndef INCLUDED_UCB_SOURCE_UCP_WEBDAV_DATETIMEHELPER_HXX +#define INCLUDED_UCB_SOURCE_UCP_WEBDAV_DATETIMEHELPER_HXX + +#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 (const OUString& ); + + static bool ISO8601_To_DateTime (const OUString&, + css::util::DateTime& ); + + static bool RFC2068_To_DateTime (const OUString&, + css::util::DateTime& ); + +public: + static bool convert (const OUString&, + css::util::DateTime& ); +}; + +} // namespace http_dav_ucp + +#endif + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/ucb/source/ucp/webdav/PropertyMap.hxx b/ucb/source/ucp/webdav/PropertyMap.hxx new file mode 100644 index 000000000..ef5693d76 --- /dev/null +++ b/ucb/source/ucp/webdav/PropertyMap.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 . + */ + + +#ifndef INCLUDED_UCB_SOURCE_UCP_WEBDAV_PROPERTYMAP_HXX +#define INCLUDED_UCB_SOURCE_UCP_WEBDAV_PROPERTYMAP_HXX + +#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; + +} + +#endif + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/ucb/source/ucp/webdav/SerfCallbacks.cxx b/ucb/source/ucp/webdav/SerfCallbacks.cxx new file mode 100644 index 000000000..5d1194ea9 --- /dev/null +++ b/ucb/source/ucp/webdav/SerfCallbacks.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 . + */ + +#include "SerfCallbacks.hxx" + +#include "SerfSession.hxx" +#include "SerfRequestProcessor.hxx" + +using namespace http_dav_ucp; + +extern "C" apr_status_t Serf_ConnectSetup( apr_socket_t *skt, + serf_bucket_t **read_bkt, + serf_bucket_t **write_bkt, + void *setup_baton, + apr_pool_t *pool ) +{ + SerfSession* pSerfSession = static_cast< SerfSession* >( setup_baton ); + return pSerfSession->setupSerfConnection( skt, + read_bkt, + write_bkt, + pool ); +} + +extern "C" apr_status_t Serf_Credentials( char **username, + char **password, + serf_request_t *request, + void *baton, + int code, + const char *authn_type, + const char *realm, + apr_pool_t *pool ) +{ + SerfRequestProcessor* pReqProc = static_cast< SerfRequestProcessor* >( baton ); + return pReqProc->provideSerfCredentials( username, + password, + request, + code, + authn_type, + realm, + pool ); +} + +extern "C" apr_status_t Serf_CertificateChainValidation( + void* pSerfSession, + int nFailures, + int /*nErrorCode*/, + const serf_ssl_certificate_t * const * pCertificateChainBase64Encoded, + apr_size_t nCertificateChainLength) +{ + return static_cast<SerfSession*>(pSerfSession) + ->verifySerfCertificateChain(nFailures, pCertificateChainBase64Encoded, nCertificateChainLength); +} + +extern "C" apr_status_t Serf_SetupRequest( serf_request_t *request, + void *setup_baton, + serf_bucket_t **req_bkt, + serf_response_acceptor_t *acceptor, + void **acceptor_baton, + serf_response_handler_t *handler, + void **handler_baton, + apr_pool_t * pool ) +{ + SerfRequestProcessor* pReqProc = static_cast< SerfRequestProcessor* >( setup_baton ); + return pReqProc->setupSerfRequest( request, + req_bkt, + acceptor, + acceptor_baton, + handler, + handler_baton, + pool ); +} + +extern "C" serf_bucket_t* Serf_AcceptResponse( serf_request_t *request, + serf_bucket_t *stream, + void *acceptor_baton, + apr_pool_t *pool ) +{ + SerfRequestProcessor* pReqProc = static_cast< SerfRequestProcessor* >( acceptor_baton ); + return pReqProc->acceptSerfResponse( request, + stream, + pool ); +} + +extern "C" apr_status_t Serf_HandleResponse( serf_request_t *request, + serf_bucket_t *response, + void *handler_baton, + apr_pool_t *pool ) +{ + SerfRequestProcessor* pReqProc = static_cast< SerfRequestProcessor* >( handler_baton ); + return pReqProc->handleSerfResponse( request, + response, + pool ); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/ucb/source/ucp/webdav/SerfCallbacks.hxx b/ucb/source/ucp/webdav/SerfCallbacks.hxx new file mode 100644 index 000000000..b82246e68 --- /dev/null +++ b/ucb/source/ucp/webdav/SerfCallbacks.hxx @@ -0,0 +1,69 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + + +#ifndef INCLUDED_UCB_SOURCE_UCP_WEBDAV_SERFCALLBACKS_HXX +#define INCLUDED_UCB_SOURCE_UCP_WEBDAV_SERFCALLBACKS_HXX + +#include <serf.h> + +extern "C" apr_status_t Serf_ConnectSetup( apr_socket_t *skt, + serf_bucket_t **read_bkt, + serf_bucket_t **write_bkt, + void *setup_baton, + apr_pool_t *pool ); + +extern "C" apr_status_t Serf_Credentials( char **username, + char **password, + serf_request_t *request, + void *baton, + int code, + const char *authn_type, + const char *realm, + apr_pool_t *pool ); + +extern "C" apr_status_t Serf_CertificateChainValidation( + void* pSerfSession, + int nFailures, + int error_depth, + const serf_ssl_certificate_t * const * pCertificateChainBase64Encoded, + apr_size_t nCertificateChainLength); + +extern "C" apr_status_t Serf_SetupRequest( serf_request_t *request, + void *setup_baton, + serf_bucket_t **req_bkt, + serf_response_acceptor_t *acceptor, + void **acceptor_baton, + serf_response_handler_t *handler, + void **handler_baton, + apr_pool_t * pool ); + +extern "C" serf_bucket_t* Serf_AcceptResponse( serf_request_t *request, + serf_bucket_t *stream, + void *acceptor_baton, + apr_pool_t *pool ); + +extern "C" apr_status_t Serf_HandleResponse( serf_request_t *request, + serf_bucket_t *response, + void *handler_baton, + apr_pool_t *pool ); + +#endif // INCLUDED_UCB_SOURCE_UCP_WEBDAV_SERFCALLBACKS_HXX + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/ucb/source/ucp/webdav/SerfCopyReqProcImpl.cxx b/ucb/source/ucp/webdav/SerfCopyReqProcImpl.cxx new file mode 100644 index 000000000..0f9a4ea20 --- /dev/null +++ b/ucb/source/ucp/webdav/SerfCopyReqProcImpl.cxx @@ -0,0 +1,82 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this 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 "SerfCopyReqProcImpl.hxx" + +#include <serf.h> + +namespace http_dav_ucp +{ + +SerfCopyReqProcImpl::SerfCopyReqProcImpl( const char* inSourcePath, + const DAVRequestHeaders& inRequestHeaders, + const char* inDestinationPath, + const bool inOverwrite ) + : SerfRequestProcessorImpl( inSourcePath, inRequestHeaders ) + , mDestPathStr( inDestinationPath ) + , mbOverwrite( inOverwrite ) +{ +} + +SerfCopyReqProcImpl::~SerfCopyReqProcImpl() +{ +} + +serf_bucket_t * SerfCopyReqProcImpl::createSerfRequestBucket( serf_request_t * inSerfRequest ) +{ + // create serf request + serf_bucket_t *req_bkt = serf_request_bucket_request_create( inSerfRequest, + "COPY", + getPathStr(), + nullptr, + serf_request_get_alloc( inSerfRequest ) ); + + // set request header fields + serf_bucket_t* hdrs_bkt = serf_bucket_request_get_headers( req_bkt ); + // general header fields provided by caller + setRequestHeaders( hdrs_bkt ); + + // COPY specific header fields + serf_bucket_headers_set( hdrs_bkt, "Destination", mDestPathStr ); + if ( mbOverwrite ) + { + serf_bucket_headers_set( hdrs_bkt, "Overwrite", "T" ); + } + else + { + serf_bucket_headers_set( hdrs_bkt, "Overwrite", "F" ); + } + + return req_bkt; +} + +void SerfCopyReqProcImpl::processChunkOfResponseData( const char* /*data*/, + apr_size_t /*len*/ ) +{ + // nothing to do; +} + +void SerfCopyReqProcImpl::handleEndOfResponseData( serf_bucket_t * /*inSerfResponseBucket*/ ) +{ + // nothing to do; +} + +} // namespace http_dav_ucp + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/ucb/source/ucp/webdav/SerfCopyReqProcImpl.hxx b/ucb/source/ucp/webdav/SerfCopyReqProcImpl.hxx new file mode 100644 index 000000000..92d12abb6 --- /dev/null +++ b/ucb/source/ucp/webdav/SerfCopyReqProcImpl.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 . + */ + +#ifndef INCLUDED_UCB_SOURCE_UCP_WEBDAV_SERFCOPYREQPROCIMPL_HXX +#define INCLUDED_UCB_SOURCE_UCP_WEBDAV_SERFCOPYREQPROCIMPL_HXX + +#include "SerfRequestProcessorImpl.hxx" + +namespace http_dav_ucp +{ + +class SerfCopyReqProcImpl : public SerfRequestProcessorImpl +{ +public: + SerfCopyReqProcImpl( const char* inSourcePath, + const DAVRequestHeaders& inRequestHeaders, + const char* inDestinationPath, + const bool inOverwrite ); + + virtual ~SerfCopyReqProcImpl() override; + + virtual + serf_bucket_t * createSerfRequestBucket( serf_request_t * inSerfRequest ) override; + +protected: + virtual + void processChunkOfResponseData( const char* data, apr_size_t len ) override; + + virtual + void handleEndOfResponseData( serf_bucket_t * inSerfResponseBucket ) override; + +private: + const char* mDestPathStr; + const bool mbOverwrite; +}; + +} // namespace http_dav_ucp + +#endif // INCLUDED_UCB_SOURCE_UCP_WEBDAV_SERFCOPYREQPROCIMPL_HXX + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/ucb/source/ucp/webdav/SerfDeleteReqProcImpl.cxx b/ucb/source/ucp/webdav/SerfDeleteReqProcImpl.cxx new file mode 100644 index 000000000..141bb0b47 --- /dev/null +++ b/ucb/source/ucp/webdav/SerfDeleteReqProcImpl.cxx @@ -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 . + */ + +#include "SerfDeleteReqProcImpl.hxx" + +#include <serf.h> + +namespace http_dav_ucp +{ + +SerfDeleteReqProcImpl::SerfDeleteReqProcImpl( const char* inPath, + const DAVRequestHeaders& inRequestHeaders ) + : SerfRequestProcessorImpl( inPath, inRequestHeaders ) +{ +} + +SerfDeleteReqProcImpl::~SerfDeleteReqProcImpl() +{ +} + +serf_bucket_t * SerfDeleteReqProcImpl::createSerfRequestBucket( serf_request_t * inSerfRequest ) +{ + // create serf request + serf_bucket_t *req_bkt = serf_request_bucket_request_create( inSerfRequest, + "DELETE", + getPathStr(), + nullptr, + serf_request_get_alloc( inSerfRequest ) ); + + // set request header fields + serf_bucket_t* hdrs_bkt = serf_bucket_request_get_headers( req_bkt ); + // general header fields provided by caller + setRequestHeaders( hdrs_bkt ); + + return req_bkt; +} + +void SerfDeleteReqProcImpl::processChunkOfResponseData( const char* /*data*/, + apr_size_t /*len*/ ) +{ + // nothing to do; +} + +void SerfDeleteReqProcImpl::handleEndOfResponseData( serf_bucket_t * /*inSerfResponseBucket*/ ) +{ + // nothing to do; +} + +} // namespace http_dav_ucp + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/ucb/source/ucp/webdav/SerfDeleteReqProcImpl.hxx b/ucb/source/ucp/webdav/SerfDeleteReqProcImpl.hxx new file mode 100644 index 000000000..0bb8cd60e --- /dev/null +++ b/ucb/source/ucp/webdav/SerfDeleteReqProcImpl.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 . + */ + +#ifndef INCLUDED_UCB_SOURCE_UCP_WEBDAV_SERFDELETEREQPROCIMPL_HXX +#define INCLUDED_UCB_SOURCE_UCP_WEBDAV_SERFDELETEREQPROCIMPL_HXX + +#include "SerfRequestProcessorImpl.hxx" + +namespace http_dav_ucp +{ + +class SerfDeleteReqProcImpl : public SerfRequestProcessorImpl +{ +public: + SerfDeleteReqProcImpl( const char* inPath, + const DAVRequestHeaders& inRequestHeaders ); + + virtual ~SerfDeleteReqProcImpl() override; + + virtual + serf_bucket_t * createSerfRequestBucket( serf_request_t * inSerfRequest ) override; + +protected: + virtual + void processChunkOfResponseData( const char* data, apr_size_t len ) override; + + virtual + void handleEndOfResponseData( serf_bucket_t * inSerfResponseBucket ) override; + +}; + +} // namespace http_dav_ucp + +#endif // INCLUDED_UCB_SOURCE_UCP_WEBDAV_SERFDELETEREQPROCIMPL_HXX + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/ucb/source/ucp/webdav/SerfGetReqProcImpl.cxx b/ucb/source/ucp/webdav/SerfGetReqProcImpl.cxx new file mode 100644 index 000000000..c06b94f16 --- /dev/null +++ b/ucb/source/ucp/webdav/SerfGetReqProcImpl.cxx @@ -0,0 +1,173 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this 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 "SerfGetReqProcImpl.hxx" + +using namespace com::sun::star; + +namespace http_dav_ucp +{ + +SerfGetReqProcImpl::SerfGetReqProcImpl( const char* inPath, + const DAVRequestHeaders& inRequestHeaders, + const rtl::Reference< SerfInputStream > & xioInStrm ) + : SerfRequestProcessorImpl( inPath, inRequestHeaders ) + , xInputStream( xioInStrm ) + , xOutputStream() + , mpHeaderNames( nullptr ) + , mpResource( nullptr ) +{ +} + +SerfGetReqProcImpl::SerfGetReqProcImpl( const char* inPath, + const DAVRequestHeaders& inRequestHeaders, + const rtl::Reference< SerfInputStream > & xioInStrm, + const std::vector< OUString > & inHeaderNames, + DAVResource & ioResource ) + : SerfRequestProcessorImpl( inPath, inRequestHeaders ) + , xInputStream( xioInStrm ) + , xOutputStream() + , mpHeaderNames( &inHeaderNames ) + , mpResource( &ioResource ) +{ +} + +SerfGetReqProcImpl::SerfGetReqProcImpl( const char* inPath, + const DAVRequestHeaders& inRequestHeaders, + const css::uno::Reference< css::io::XOutputStream > & xioOutStrm ) + : SerfRequestProcessorImpl( inPath, inRequestHeaders ) + , xInputStream() + , xOutputStream( xioOutStrm ) + , mpHeaderNames( nullptr ) + , mpResource( nullptr ) +{ +} + +SerfGetReqProcImpl::SerfGetReqProcImpl( const char* inPath, + const DAVRequestHeaders& inRequestHeaders, + const css::uno::Reference< css::io::XOutputStream > & xioOutStrm, + const std::vector< OUString > & inHeaderNames, + DAVResource & ioResource ) + : SerfRequestProcessorImpl( inPath, inRequestHeaders ) + , xInputStream() + , xOutputStream( xioOutStrm ) + , mpHeaderNames( &inHeaderNames ) + , mpResource( &ioResource ) +{ +} + +SerfGetReqProcImpl::~SerfGetReqProcImpl() +{ +} + +serf_bucket_t * SerfGetReqProcImpl::createSerfRequestBucket( serf_request_t * inSerfRequest ) +{ + // create serf request + serf_bucket_t *req_bkt = serf_request_bucket_request_create( inSerfRequest, + "GET", + getPathStr(), + nullptr, + serf_request_get_alloc( inSerfRequest ) ); + + // set request header fields + serf_bucket_t* hdrs_bkt = serf_bucket_request_get_headers( req_bkt ); + // general header fields provided by caller + setRequestHeaders( hdrs_bkt ); + + return req_bkt; +} + +void SerfGetReqProcImpl::processChunkOfResponseData( const char* data, + apr_size_t len ) +{ + if ( xInputStream.is() ) + { + xInputStream->AddToStream( data, len ); + } + else if ( xOutputStream.is() ) + { + const uno::Sequence< sal_Int8 > aDataSeq( reinterpret_cast<const sal_Int8 *>(data), len ); + xOutputStream->writeBytes( aDataSeq ); + } +} + +namespace +{ + apr_status_t Serf_ProcessResponseHeader( void* inUserData, + const char* inHeaderName, + const char* inHeaderValue ) + { + SerfGetReqProcImpl* pReqProcImpl = static_cast< SerfGetReqProcImpl* >( inUserData ); + pReqProcImpl->processSingleResponseHeader( inHeaderName, + inHeaderValue ); + + return APR_SUCCESS; + } +} // end of anonymous namespace + +void SerfGetReqProcImpl::handleEndOfResponseData( serf_bucket_t * inSerfResponseBucket ) +{ + // read response header, if requested + if ( mpHeaderNames != nullptr && mpResource != nullptr ) + { + serf_bucket_t* SerfHeaderBucket = serf_bucket_response_get_headers( inSerfResponseBucket ); + if ( SerfHeaderBucket != nullptr ) + { + serf_bucket_headers_do( SerfHeaderBucket, + Serf_ProcessResponseHeader, + this ); + } + } +} + +void SerfGetReqProcImpl::processSingleResponseHeader( const char* inHeaderName, + const char* inHeaderValue ) +{ + OUString aHeaderName( OUString::createFromAscii( inHeaderName ) ); + + bool bStoreHeaderField = false; + + if ( mpHeaderNames->empty() ) + { + // store all header fields + bStoreHeaderField = true; + } + else + { + // store only header fields which are requested + bStoreHeaderField = std::any_of(mpHeaderNames->begin(), mpHeaderNames->end(), + [&aHeaderName](const OUString& rHeaderName) { + // header names are case insensitive + return rHeaderName.equalsIgnoreAsciiCase( aHeaderName ); + }); + } + + if ( bStoreHeaderField ) + { + DAVPropertyValue thePropertyValue; + thePropertyValue.IsCaseSensitive = false; + thePropertyValue.Name = aHeaderName; + thePropertyValue.Value <<= OUString::createFromAscii( inHeaderValue ); + mpResource->properties.push_back( thePropertyValue ); + } +} + +} // namespace http_dav_ucp + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/ucb/source/ucp/webdav/SerfGetReqProcImpl.hxx b/ucb/source/ucp/webdav/SerfGetReqProcImpl.hxx new file mode 100644 index 000000000..d043f3087 --- /dev/null +++ b/ucb/source/ucp/webdav/SerfGetReqProcImpl.hxx @@ -0,0 +1,84 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#ifndef INCLUDED_UCB_SOURCE_UCP_WEBDAV_SERFGETREQPROCIMPL_HXX +#define INCLUDED_UCB_SOURCE_UCP_WEBDAV_SERFGETREQPROCIMPL_HXX + +#include "SerfRequestProcessorImpl.hxx" + +#include <vector> +#include <rtl/ustring.hxx> +#include "DAVResource.hxx" + +#include "SerfInputStream.hxx" +#include <com/sun/star/io/XOutputStream.hpp> + +namespace http_dav_ucp +{ + +class SerfGetReqProcImpl : public SerfRequestProcessorImpl +{ +public: + SerfGetReqProcImpl( const char* inPath, + const DAVRequestHeaders& inRequestHeaders, + const rtl::Reference< SerfInputStream > & xioInStrm ); + + SerfGetReqProcImpl( const char* inPath, + const DAVRequestHeaders& inRequestHeaders, + const rtl::Reference< SerfInputStream > & xioInStrm, + const std::vector< OUString > & inHeaderNames, + DAVResource & ioResource ); + + SerfGetReqProcImpl( const char* inPath, + const DAVRequestHeaders& inRequestHeaders, + const css::uno::Reference< css::io::XOutputStream > & xioOutStrm ); + + SerfGetReqProcImpl( const char* inPath, + const DAVRequestHeaders& inRequestHeaders, + const css::uno::Reference< css::io::XOutputStream > & xioOutStrm, + const std::vector< OUString > & inHeaderNames, + DAVResource & ioResource ); + + virtual ~SerfGetReqProcImpl() override; + + virtual + serf_bucket_t * createSerfRequestBucket( serf_request_t * inSerfRequest ) override; + + void processSingleResponseHeader( const char* inHeaderName, + const char* inHeaderValue ); + +protected: + virtual + void processChunkOfResponseData( const char* data, apr_size_t len ) override; + + virtual + void handleEndOfResponseData( serf_bucket_t * inSerfResponseBucket ) override; + +private: + rtl::Reference< SerfInputStream > xInputStream; + css::uno::Reference< css::io::XOutputStream > xOutputStream; + const std::vector< OUString > * mpHeaderNames; + DAVResource* mpResource; +}; + +} // namespace http_dav_ucp + +#endif // INCLUDED_UCB_SOURCE_UCP_WEBDAV_SERFGETREQPROCIMPL_HXX + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/ucb/source/ucp/webdav/SerfHeadReqProcImpl.cxx b/ucb/source/ucp/webdav/SerfHeadReqProcImpl.cxx new file mode 100644 index 000000000..a771570da --- /dev/null +++ b/ucb/source/ucp/webdav/SerfHeadReqProcImpl.cxx @@ -0,0 +1,128 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this 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 "SerfHeadReqProcImpl.hxx" + +using namespace com::sun::star; + +namespace http_dav_ucp +{ + +SerfHeadReqProcImpl::SerfHeadReqProcImpl( const char* inPath, + const DAVRequestHeaders& inRequestHeaders, + const std::vector< OUString > & inHeaderNames, + DAVResource & ioResource ) + : SerfRequestProcessorImpl( inPath, inRequestHeaders ) + , mpHeaderNames( &inHeaderNames ) + , mpResource( &ioResource ) +{ +} + +SerfHeadReqProcImpl::~SerfHeadReqProcImpl() +{ +} + +serf_bucket_t * SerfHeadReqProcImpl::createSerfRequestBucket( serf_request_t * inSerfRequest ) +{ + // create serf request + serf_bucket_t *req_bkt = serf_request_bucket_request_create( inSerfRequest, + "HEAD", + getPathStr(), + + nullptr, + serf_request_get_alloc( inSerfRequest ) ); + + // set request header fields + serf_bucket_t* hdrs_bkt = serf_bucket_request_get_headers( req_bkt ); + // general header fields provided by caller + setRequestHeaders( hdrs_bkt ); + + return req_bkt; +} + +void SerfHeadReqProcImpl::processChunkOfResponseData( const char* /*data*/, + apr_size_t /*len*/ ) +{ + // nothing to do +} + +namespace +{ + apr_status_t Serf_ProcessResponseHeader( void* inUserData, + const char* inHeaderName, + const char* inHeaderValue ) + { + SerfHeadReqProcImpl* pReqProcImpl = static_cast< SerfHeadReqProcImpl* >( inUserData ); + pReqProcImpl->processSingleResponseHeader( inHeaderName, + inHeaderValue ); + + return APR_SUCCESS; + } +} // end of anonymous namespace + +void SerfHeadReqProcImpl::handleEndOfResponseData( serf_bucket_t * inSerfResponseBucket ) +{ + // read response header, if requested + if ( mpHeaderNames != nullptr && mpResource != nullptr ) + { + serf_bucket_t* SerfHeaderBucket = serf_bucket_response_get_headers( inSerfResponseBucket ); + if ( SerfHeaderBucket != nullptr ) + { + serf_bucket_headers_do( SerfHeaderBucket, + Serf_ProcessResponseHeader, + this ); + } + } +} + +void SerfHeadReqProcImpl::processSingleResponseHeader( const char* inHeaderName, + const char* inHeaderValue ) +{ + OUString aHeaderName( OUString::createFromAscii( inHeaderName ) ); + + bool bStoreHeaderField = false; + + if ( mpHeaderNames->empty() ) + { + // store all header fields + bStoreHeaderField = true; + } + else + { + // store only header fields which are requested + bStoreHeaderField = std::any_of(mpHeaderNames->begin(), mpHeaderNames->end(), + [&aHeaderName](const OUString& rHeaderName) { + // header names are case insensitive + return rHeaderName.equalsIgnoreAsciiCase( aHeaderName ); + }); + } + + if ( bStoreHeaderField ) + { + DAVPropertyValue thePropertyValue; + thePropertyValue.IsCaseSensitive = false; + thePropertyValue.Name = aHeaderName; + thePropertyValue.Value <<= OUString::createFromAscii( inHeaderValue ); + mpResource->properties.push_back( thePropertyValue ); + } +} + +} // namespace http_dav_ucp + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/ucb/source/ucp/webdav/SerfHeadReqProcImpl.hxx b/ucb/source/ucp/webdav/SerfHeadReqProcImpl.hxx new file mode 100644 index 000000000..b1a03a07e --- /dev/null +++ b/ucb/source/ucp/webdav/SerfHeadReqProcImpl.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 . + */ + +#ifndef INCLUDED_UCB_SOURCE_UCP_WEBDAV_SERFHEADREQPROCIMPL_HXX +#define INCLUDED_UCB_SOURCE_UCP_WEBDAV_SERFHEADREQPROCIMPL_HXX + +#include "SerfRequestProcessorImpl.hxx" + +#include <vector> +#include <rtl/ustring.hxx> +#include "DAVResource.hxx" + +#include "SerfInputStream.hxx" +#include <com/sun/star/io/XOutputStream.hpp> + +namespace http_dav_ucp +{ + +class SerfHeadReqProcImpl : public SerfRequestProcessorImpl +{ +public: + SerfHeadReqProcImpl( const char* inPath, + const DAVRequestHeaders& inRequestHeaders, + const std::vector< OUString > & inHeaderNames, + DAVResource & ioResource ); + + virtual ~SerfHeadReqProcImpl() override; + + virtual + serf_bucket_t * createSerfRequestBucket( serf_request_t * inSerfRequest ) override; + + void processSingleResponseHeader( const char* inHeaderName, + const char* inHeaderValue ); + +protected: + virtual + void processChunkOfResponseData( const char* data, apr_size_t len ) override; + + virtual + void handleEndOfResponseData( serf_bucket_t * inSerfResponseBucket ) override; + +private: + const std::vector< OUString > * mpHeaderNames; + DAVResource* mpResource; +}; + +} // namespace http_dav_ucp + +#endif // INCLUDED_UCB_SOURCE_UCP_WEBDAV_SERFHEADREQPROCIMPL_HXX + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/ucb/source/ucp/webdav/SerfInputStream.cxx b/ucb/source/ucp/webdav/SerfInputStream.cxx new file mode 100644 index 000000000..498ed26e6 --- /dev/null +++ b/ucb/source/ucp/webdav/SerfInputStream.cxx @@ -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 . + */ + +#include "SerfInputStream.hxx" + +#include <cppuhelper/queryinterface.hxx> + +#include <com/sun/star/lang/IllegalArgumentException.hpp> + +#include <string.h> + +using namespace cppu; +using namespace com::sun::star::io; +using namespace com::sun::star::uno; +using namespace http_dav_ucp; + +// Constructor + +SerfInputStream::SerfInputStream() +: mLen( 0 ), + mPos( 0 ) +{ +} + + +// Destructor + +SerfInputStream::~SerfInputStream() +{ +} + + +// AddToStream +// Allows the caller to add some data to the "end" of the stream + +void SerfInputStream::AddToStream( const char * inBuf, sal_Int32 inLen ) +{ + mInputBuffer.realloc( sal::static_int_cast<sal_Int32>(mLen) + inLen ); + memcpy( mInputBuffer.getArray() + mLen, inBuf, inLen ); + mLen += inLen; +} + + +// queryInterface + +Any SerfInputStream::queryInterface( const Type &type ) +{ + Any aRet = ::cppu::queryInterface( type, + static_cast< XInputStream * >( this ), + static_cast< XSeekable * >( this ) ); + return aRet.hasValue() ? aRet : OWeakObject::queryInterface( type ); +} + + +// readBytes +// "Reads" the specified number of bytes from the stream + +sal_Int32 SAL_CALL SerfInputStream::readBytes( + css::uno::Sequence< sal_Int8 >& aData, sal_Int32 nBytesToRead ) +{ + // Work out how much we're actually going to write + sal_Int32 theBytes2Read = nBytesToRead; + sal_Int32 theBytesLeft = sal::static_int_cast<sal_Int32>(mLen - mPos); + if ( theBytes2Read > theBytesLeft ) + theBytes2Read = theBytesLeft; + + // Realloc buffer. + aData.realloc( theBytes2Read ); + + // Write the data + memcpy( + aData.getArray(), mInputBuffer.getConstArray() + mPos, theBytes2Read ); + + // Update our stream position for next time + mPos += theBytes2Read; + + return theBytes2Read; +} + + +// readSomeBytes + +sal_Int32 SAL_CALL SerfInputStream::readSomeBytes( + css::uno::Sequence< sal_Int8 >& aData, sal_Int32 nMaxBytesToRead ) +{ + // Warning: What should this be doing ? + return readBytes( aData, nMaxBytesToRead ); +} + + +// skipBytes +// Moves the current stream position forward + +void SAL_CALL SerfInputStream::skipBytes( sal_Int32 nBytesToSkip ) +{ + mPos += nBytesToSkip; + if ( mPos >= mLen ) + mPos = mLen; +} + + +// available +// Returns the number of unread bytes currently remaining on the stream + +sal_Int32 SAL_CALL SerfInputStream::available( ) +{ + return std::min<sal_Int64>(SAL_MAX_INT32, mLen - mPos); +} + + +// closeInput + +void SAL_CALL SerfInputStream::closeInput() +{ +} + + +// seek + +void SAL_CALL SerfInputStream::seek( sal_Int64 location ) +{ + if ( location < 0 || location > mLen ) + throw css::lang::IllegalArgumentException(); + mPos = location; +} + + +// getPosition + +sal_Int64 SAL_CALL SerfInputStream::getPosition() +{ + return mPos; +} + + +// getLength + +sal_Int64 SAL_CALL SerfInputStream::getLength() +{ + return mLen; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/ucb/source/ucp/webdav/SerfInputStream.hxx b/ucb/source/ucp/webdav/SerfInputStream.hxx new file mode 100644 index 000000000..ca9520ed8 --- /dev/null +++ b/ucb/source/ucp/webdav/SerfInputStream.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 . + */ + + +#ifndef INCLUDED_UCB_SOURCE_UCP_WEBDAV_SERFINPUTSTREAM_HXX +#define INCLUDED_UCB_SOURCE_UCP_WEBDAV_SERFINPUTSTREAM_HXX + +#include <sal/types.h> +#include <cppuhelper/weak.hxx> +#include <com/sun/star/io/XInputStream.hpp> +#include <com/sun/star/io/XSeekable.hpp> + + +namespace http_dav_ucp +{ + + +// SerfInputStream +// A simple XInputStream implementation provided specifically for use +// by the DAVSession::GET method. + +class SerfInputStream : public css::io::XInputStream, + public css::io::XSeekable, + public ::cppu::OWeakObject +{ + private: + css::uno::Sequence< sal_Int8 > mInputBuffer; + sal_Int64 mLen; + sal_Int64 mPos; + + public: + SerfInputStream(); + virtual ~SerfInputStream() override; + + // Add some data to the end of the stream + void AddToStream( const char * inBuf, sal_Int32 inLen ); + + // XInterface + virtual css::uno::Any SAL_CALL queryInterface( const css::uno::Type & type ) override; + + virtual void SAL_CALL acquire() + throw () override + { OWeakObject::acquire(); } + + virtual void SAL_CALL release() + throw() override + { OWeakObject::release(); } + + + // 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; + + // XSeekable + virtual void SAL_CALL seek( sal_Int64 location ) override; + + virtual sal_Int64 SAL_CALL getPosition() override; + + virtual sal_Int64 SAL_CALL getLength() override; +}; + +} // namespace http_dav_ucp + +#endif // INCLUDED_UCB_SOURCE_UCP_WEBDAV_SERFINPUTSTREAM_HXX + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/ucb/source/ucp/webdav/SerfLockReqProcImpl.cxx b/ucb/source/ucp/webdav/SerfLockReqProcImpl.cxx new file mode 100644 index 000000000..713bf5299 --- /dev/null +++ b/ucb/source/ucp/webdav/SerfLockReqProcImpl.cxx @@ -0,0 +1,203 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this 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 "SerfLockReqProcImpl.hxx" + +#include "AprEnv.hxx" +#include "SerfSession.hxx" +#include "DAVException.hxx" + +#include "webdavresponseparser.hxx" +#include <rtl/strbuf.hxx> +#include <sal/log.hxx> + +namespace http_dav_ucp +{ + +SerfLockReqProcImpl::SerfLockReqProcImpl( const char* inPath, + const DAVRequestHeaders& inRequestHeaders, + SerfSession& rSession, + const css::ucb::Lock& rLock, + sal_Int32* plastChanceToSendRefreshRequest ) + : SerfRequestProcessorImpl( inPath, inRequestHeaders ) + , m_rSession( rSession ) + , m_aLock( rLock ) + , m_plastChanceToSendRefreshRequest( plastChanceToSendRefreshRequest ) + , m_xInputStream( new SerfInputStream() ) +{ +} + +SerfLockReqProcImpl::~SerfLockReqProcImpl() +{ +} + +serf_bucket_t * SerfLockReqProcImpl::createSerfRequestBucket( serf_request_t * inSerfRequest ) +{ + serf_bucket_alloc_t* pSerfBucketAlloc = serf_request_get_alloc( inSerfRequest ); + + OStringBuffer aBody("<?xml version=\"1.0\" encoding=\"utf-8\"?>\n" + "<lockinfo xmlns='DAV:'>\n <lockscope>"); + + // Set the lock scope + switch ( m_aLock.Scope ) + { + case css::ucb::LockScope_EXCLUSIVE: + aBody.append("<exclusive/>"); + break; + case css::ucb::LockScope_SHARED: + aBody.append("<shared/>"); + break; + default: + throw DAVException( DAVException::DAV_INVALID_ARG ); + } + aBody.append("</lockscope>\n <locktype><write/></locktype>\n"); + + // Set the lock owner + OUString aValue; + if ((m_aLock.Owner >>= aValue) && !aValue.isEmpty()) + { + aBody.append(" <owner>"); + aBody.append(OUStringToOString(aValue, RTL_TEXTENCODING_UTF8)); + aBody.append("</owner>\n"); + } + aBody.append("</lockinfo>\n"); + + const OString aBodyText(aBody.makeStringAndClear()); + serf_bucket_t* body_bkt = nullptr; + + if (!m_plastChanceToSendRefreshRequest) + body_bkt = serf_bucket_simple_copy_create( aBodyText.getStr(), + aBodyText.getLength(), + pSerfBucketAlloc ); + + // create serf request + serf_bucket_t *req_bkt = serf_request_bucket_request_create( inSerfRequest, + "LOCK", + getPathStr(), + body_bkt, + pSerfBucketAlloc ); + if (!m_plastChanceToSendRefreshRequest) + handleChunkedEncoding(req_bkt, aBodyText.getLength()); + + // set request header fields + serf_bucket_t* hdrs_bkt = serf_bucket_request_get_headers( req_bkt ); + + // general header fields provided by caller + setRequestHeaders( hdrs_bkt ); + + // request specific header fields + const char * depth = nullptr; + switch( m_aLock.Depth ) + { + case css::ucb::LockDepth_ZERO: + depth = "0"; + break; + case css::ucb::LockDepth_ONE: + depth = "1"; + break; + case css::ucb::LockDepth_INFINITY: + depth = "infinity"; + break; + default: + throw DAVException( DAVException::DAV_INVALID_ARG ); + } + if (!m_plastChanceToSendRefreshRequest) + { + serf_bucket_headers_set( hdrs_bkt, "Depth", depth ); + serf_bucket_headers_set( hdrs_bkt, "Content-Type", "application/xml" ); + } + else + { + const OString sToken( "(<" + OUStringToOString( apr_environment::AprEnv::getAprEnv()-> + getSerfLockStore()->getLockToken( OUString::createFromAscii(getPathStr())), + RTL_TEXTENCODING_UTF8 ) + ">)" ); + serf_bucket_headers_set( hdrs_bkt, "If", sToken.getStr() ); + } + + // Set the lock timeout + if (m_aLock.Timeout == -1) + serf_bucket_headers_set( hdrs_bkt, "Timeout", "Infinite" ); + else if (m_aLock.Timeout > 0) + { + const OString aTimeValue("Second-" + OString::number(m_aLock.Timeout)); + serf_bucket_headers_set( hdrs_bkt, "Timeout", aTimeValue.getStr() ); + } + else + serf_bucket_headers_set( hdrs_bkt, "Timeout", "Second-180" ); + + osl_getSystemTime( &m_aStartCall ); + + return req_bkt; +} + +void SerfLockReqProcImpl::processChunkOfResponseData( const char* data, + apr_size_t len ) +{ + if ( m_xInputStream.is() ) + { + m_xInputStream->AddToStream( data, len ); + } +} + +void SerfLockReqProcImpl::handleEndOfResponseData( serf_bucket_t * ) +{ + const std::vector< css::ucb::Lock > aLocks( parseWebDAVLockResponse( m_xInputStream.get() ) ); + + if (!aLocks.empty()) + { + for (size_t i = 0; i < aLocks.size(); ++i) + { + sal_Int64 timeout = aLocks[i].Timeout; + TimeValue aEnd; + osl_getSystemTime( &aEnd ); + // Try to estimate a safe absolute time for sending the + // lock refresh request. + sal_Int32 lastChanceToSendRefreshRequest = -1; + if ( timeout != -1 ) + { + sal_Int32 calltime = aEnd.Seconds - m_aStartCall.Seconds; + if ( calltime <= timeout ) + lastChanceToSendRefreshRequest = aEnd.Seconds + timeout - calltime; + else + SAL_WARN("ucb.ucp.webdav", "No chance to refresh lock before timeout!" ); + } + if (m_plastChanceToSendRefreshRequest) + { + *m_plastChanceToSendRefreshRequest = lastChanceToSendRefreshRequest; + assert(aLocks.size() == 1); + // We are just refreshing lock, do not add it into SerfLockStore + break; + } + apr_environment::AprEnv::getAprEnv()->getSerfLockStore()->addLock( + OUString::createFromAscii(getPathStr()), + aLocks[i].LockTokens[0], + &m_rSession, lastChanceToSendRefreshRequest ); + SAL_INFO("ucb.ucp.webdav", "SerfSession::LOCK: created lock for " + << getPathStr() << ". token: " << aLocks[i].LockTokens[0]); + } + } + else + { + SAL_INFO("ucb.ucp.webdav", "SerfSession::LOCK: obtaining lock failed!"); + } +} + +} // namespace http_dav_ucp + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/ucb/source/ucp/webdav/SerfLockReqProcImpl.hxx b/ucb/source/ucp/webdav/SerfLockReqProcImpl.hxx new file mode 100644 index 000000000..29d1361eb --- /dev/null +++ b/ucb/source/ucp/webdav/SerfLockReqProcImpl.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 . + */ + +#ifndef INCLUDED_UCB_SOURCE_UCP_WEBDAV_SERFLOCKREQPROCIMPL_HXX +#define INCLUDED_UCB_SOURCE_UCP_WEBDAV_SERFLOCKREQPROCIMPL_HXX + +#include "SerfRequestProcessorImpl.hxx" +#include "SerfInputStream.hxx" + +#include <com/sun/star/ucb/Lock.hpp> +#include <osl/time.h> + +namespace http_dav_ucp +{ + +class SerfSession; + +class SerfLockReqProcImpl : public SerfRequestProcessorImpl +{ +public: + SerfLockReqProcImpl( const char* inPath, + const DAVRequestHeaders& inRequestHeaders, + SerfSession& rSession, + const css::ucb::Lock& rLock, + sal_Int32* plastChanceToSendRefreshRequest = nullptr ); + + virtual ~SerfLockReqProcImpl() override; + + virtual + serf_bucket_t * createSerfRequestBucket( serf_request_t * inSerfRequest ) override; + +private: + virtual + void processChunkOfResponseData( const char* data, apr_size_t len ) override; + + virtual + void handleEndOfResponseData( serf_bucket_t * inSerfResponseBucket ) override; + + SerfSession& m_rSession; + css::ucb::Lock m_aLock; + // if m_plastChanceToSendRefreshRequest is not 0 we are sending just refresh request + sal_Int32* m_plastChanceToSendRefreshRequest; + TimeValue m_aStartCall; + rtl::Reference< SerfInputStream > m_xInputStream; +}; + +} // namespace http_dav_ucp + +#endif // INCLUDED_UCB_SOURCE_UCP_WEBDAV_SERFLOCKREQPROCIMPL_HXX + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/ucb/source/ucp/webdav/SerfLockStore.cxx b/ucb/source/ucp/webdav/SerfLockStore.cxx new file mode 100644 index 000000000..ec2f6ae1b --- /dev/null +++ b/ucb/source/ucp/webdav/SerfLockStore.cxx @@ -0,0 +1,218 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this 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/ustring.hxx> +#include <sal/log.hxx> +#include <osl/time.h> +#include <osl/thread.hxx> +#include "SerfSession.hxx" +#include "SerfLockStore.hxx" + +using namespace http_dav_ucp; + +namespace http_dav_ucp { + +class TickerThread : public osl::Thread +{ + bool m_bFinish; + SerfLockStore & m_rLockStore; + +public: + + explicit TickerThread( SerfLockStore & rLockStore ) + : osl::Thread(), m_bFinish( false ), m_rLockStore( rLockStore ) {} + + void finish() { m_bFinish = true; } + +protected: + + virtual void SAL_CALL run() override; +}; + +} // namespace http_dav_ucp + + +void TickerThread::run() +{ + 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; + } + + TimeValue aTV; + aTV.Seconds = 0; + aTV.Nanosec = 1000000000 / nNth; + wait( aTV ); + } + + SAL_INFO("ucb.ucp.webdav", "TickerThread: stop." ); +} + + +SerfLockStore::SerfLockStore() + : m_pTickerThread( nullptr ) + , m_bFinishing( false ) +{ +} + + +SerfLockStore::~SerfLockStore() +{ + stopTicker(); + m_bFinishing = true; + + // 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->UNLOCK( rLockInfo.first ); + } +} + +bool SerfLockStore::finishing() const +{ + return m_bFinishing; +} + +void SerfLockStore::startTicker() +{ + osl::MutexGuard aGuard( m_aMutex ); + + if ( !m_pTickerThread ) + { + m_pTickerThread = new TickerThread( *this ); + m_pTickerThread->create(); + } +} + + +void SerfLockStore::stopTicker() +{ + osl::MutexGuard aGuard( m_aMutex ); + + if ( m_pTickerThread ) + { + m_pTickerThread->finish(); + m_pTickerThread->join(); + delete m_pTickerThread; + m_pTickerThread = nullptr; + } +} + +OUString SerfLockStore::getLockToken( const OUString& rLock ) +{ + osl::MutexGuard aGuard( m_aMutex ); + + LockInfoMap::const_iterator it( m_aLockInfoMap.find( rLock ) ); + if ( it != m_aLockInfoMap.end() ) + return (*it).second.m_sToken; + + SAL_WARN("ucb.ucp.webdav", "SerfLockStore::getLockToken: lock not found!" ); + return OUString(); +} + +void SerfLockStore::addLock( const OUString& rLock, + const OUString& sToken, + rtl::Reference< SerfSession > const & xSession, + sal_Int32 nLastChanceToSendRefreshRequest ) +{ + osl::MutexGuard aGuard( m_aMutex ); + + m_aLockInfoMap[ rLock ] + = LockInfo( sToken, xSession, nLastChanceToSendRefreshRequest ); + + startTicker(); +} + + +void SerfLockStore::updateLock( const OUString& rLock, + sal_Int32 nLastChanceToSendRefreshRequest ) +{ + osl::MutexGuard aGuard( m_aMutex ); + + LockInfoMap::iterator it( m_aLockInfoMap.find( rLock ) ); + SAL_WARN_IF( it == m_aLockInfoMap.end(), "ucb.ucp.webdav", + "SerfLockStore::updateLock: lock not found!" ); + + if ( it != m_aLockInfoMap.end() ) + { + (*it).second.m_nLastChanceToSendRefreshRequest + = nLastChanceToSendRefreshRequest; + } +} + + +void SerfLockStore::removeLock( const OUString& rLock ) +{ + osl::MutexGuard aGuard( m_aMutex ); + + m_aLockInfoMap.erase( rLock ); + + if ( m_aLockInfoMap.empty() ) + stopTicker(); +} + + +void SerfLockStore::refreshLocks() +{ + osl::MutexGuard aGuard( m_aMutex ); + + 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; + if ( rInfo.m_xSession->LOCK( + rLockInfo.first, &nlastChanceToSendRefreshRequest ) ) + { + rInfo.m_nLastChanceToSendRefreshRequest + = nlastChanceToSendRefreshRequest; + } + else + { + // refresh failed. stop auto-refresh. + rInfo.m_nLastChanceToSendRefreshRequest = -1; + } + } + } + } +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/ucb/source/ucp/webdav/SerfLockStore.hxx b/ucb/source/ucp/webdav/SerfLockStore.hxx new file mode 100644 index 000000000..6a927562b --- /dev/null +++ b/ucb/source/ucp/webdav/SerfLockStore.hxx @@ -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 . + */ + + +#ifndef INCLUDED_UCB_SOURCE_UCP_WEBDAV_SERFLOCKSTORE_HXX +#define INCLUDED_UCB_SOURCE_UCP_WEBDAV_SERFLOCKSTORE_HXX + +#include <map> +#include <osl/mutex.hxx> +#include <rtl/ref.hxx> +#include <rtl/ustring.hxx> +#include "SerfSession.hxx" + +namespace http_dav_ucp +{ + +class TickerThread; + +struct LockInfo +{ + OUString m_sToken; + rtl::Reference< SerfSession > m_xSession; + sal_Int32 m_nLastChanceToSendRefreshRequest; + + LockInfo() + : m_nLastChanceToSendRefreshRequest( -1 ) {} + + LockInfo( const OUString& sToken, + rtl::Reference< SerfSession > const & xSession, + sal_Int32 nLastChanceToSendRefreshRequest ) + : m_sToken( sToken ), + m_xSession( xSession ), + m_nLastChanceToSendRefreshRequest( nLastChanceToSendRefreshRequest ) {} +}; + +typedef std::map< OUString, LockInfo > LockInfoMap; + +class SerfLockStore +{ + osl::Mutex m_aMutex; + TickerThread * m_pTickerThread; + bool m_bFinishing; + LockInfoMap m_aLockInfoMap; + +public: + SerfLockStore(); + ~SerfLockStore(); + + bool finishing() const; + OUString getLockToken( const OUString& rLock ); + + void addLock( const OUString& rLock, + const OUString& sToken, + rtl::Reference< SerfSession > const & xSession, + // time in seconds since Jan 1 1970 + // -1: infinite lock, no refresh + sal_Int32 nLastChanceToSendRefreshRequest ); + + void updateLock( const OUString& rLock, + sal_Int32 nLastChanceToSendRefreshRequest ); + + void removeLock( const OUString& rLock ); + + void refreshLocks(); + +private: + void startTicker(); + void stopTicker(); +}; + +} // namespace http_dav_ucp + +#endif // INCLUDED_UCB_SOURCE_UCP_WEBDAV_SERFLOCKSTORE_HXX + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/ucb/source/ucp/webdav/SerfMkColReqProcImpl.cxx b/ucb/source/ucp/webdav/SerfMkColReqProcImpl.cxx new file mode 100644 index 000000000..4641b3f35 --- /dev/null +++ b/ucb/source/ucp/webdav/SerfMkColReqProcImpl.cxx @@ -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 . + */ + +#include "SerfMkColReqProcImpl.hxx" + +#include <serf.h> + +namespace http_dav_ucp +{ + +SerfMkColReqProcImpl::SerfMkColReqProcImpl( const char* inPath, + const DAVRequestHeaders& inRequestHeaders ) + : SerfRequestProcessorImpl( inPath,inRequestHeaders ) +{ +} + +SerfMkColReqProcImpl::~SerfMkColReqProcImpl() +{ +} + +serf_bucket_t * SerfMkColReqProcImpl::createSerfRequestBucket( serf_request_t * inSerfRequest ) +{ + // create serf request + serf_bucket_t *req_bkt = serf_request_bucket_request_create( inSerfRequest, + "MKCOL", + getPathStr(), + nullptr, + serf_request_get_alloc( inSerfRequest ) ); + + // set request header fields + serf_bucket_t* hdrs_bkt = serf_bucket_request_get_headers( req_bkt ); + // general header fields provided by caller + setRequestHeaders( hdrs_bkt ); + + return req_bkt; +} + +void SerfMkColReqProcImpl::processChunkOfResponseData( const char* /*data*/, + apr_size_t /*len*/ ) +{ + // nothing to do; +} + +void SerfMkColReqProcImpl::handleEndOfResponseData( serf_bucket_t * /*inSerfResponseBucket*/ ) +{ + // nothing to do; +} + +} // namespace http_dav_ucp + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/ucb/source/ucp/webdav/SerfMkColReqProcImpl.hxx b/ucb/source/ucp/webdav/SerfMkColReqProcImpl.hxx new file mode 100644 index 000000000..08269ef47 --- /dev/null +++ b/ucb/source/ucp/webdav/SerfMkColReqProcImpl.hxx @@ -0,0 +1,51 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#ifndef INCLUDED_UCB_SOURCE_UCP_WEBDAV_SERFMKCOLREQPROCIMPL_HXX +#define INCLUDED_UCB_SOURCE_UCP_WEBDAV_SERFMKCOLREQPROCIMPL_HXX + +#include "SerfRequestProcessorImpl.hxx" + +namespace http_dav_ucp +{ + +class SerfMkColReqProcImpl : public SerfRequestProcessorImpl +{ +public: + SerfMkColReqProcImpl( const char* inPath, + const DAVRequestHeaders& inRequestHeaders ); + + virtual ~SerfMkColReqProcImpl() override; + + virtual + serf_bucket_t * createSerfRequestBucket( serf_request_t * inSerfRequest ) override; + +protected: + virtual + void processChunkOfResponseData( const char* data, apr_size_t len ) override; + + virtual + void handleEndOfResponseData( serf_bucket_t * inSerfResponseBucket ) override; +}; + +} // namespace http_dav_ucp + +#endif // INCLUDED_UCB_SOURCE_UCP_WEBDAV_SERFMKCOLREQPROCIMPL_HXX + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/ucb/source/ucp/webdav/SerfMoveReqProcImpl.cxx b/ucb/source/ucp/webdav/SerfMoveReqProcImpl.cxx new file mode 100644 index 000000000..fc775abd3 --- /dev/null +++ b/ucb/source/ucp/webdav/SerfMoveReqProcImpl.cxx @@ -0,0 +1,82 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this 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 "SerfMoveReqProcImpl.hxx" + +#include <serf.h> + +namespace http_dav_ucp +{ + +SerfMoveReqProcImpl::SerfMoveReqProcImpl( const char* inSourcePath, + const DAVRequestHeaders& inRequestHeaders, + const char* inDestinationPath, + const bool inOverwrite ) + : SerfRequestProcessorImpl( inSourcePath, inRequestHeaders ) + , mDestPathStr( inDestinationPath ) + , mbOverwrite( inOverwrite ) +{ +} + +SerfMoveReqProcImpl::~SerfMoveReqProcImpl() +{ +} + +serf_bucket_t * SerfMoveReqProcImpl::createSerfRequestBucket( serf_request_t * inSerfRequest ) +{ + // create serf request + serf_bucket_t *req_bkt = serf_request_bucket_request_create( inSerfRequest, + "MOVE", + getPathStr(), + nullptr, + serf_request_get_alloc( inSerfRequest ) ); + + // set request header fields + serf_bucket_t* hdrs_bkt = serf_bucket_request_get_headers( req_bkt ); + // general header fields provided by caller + setRequestHeaders( hdrs_bkt ); + + // MOVE specific header fields + serf_bucket_headers_set( hdrs_bkt, "Destination", mDestPathStr ); + if ( mbOverwrite ) + { + serf_bucket_headers_set( hdrs_bkt, "Overwrite", "T" ); + } + else + { + serf_bucket_headers_set( hdrs_bkt, "Overwrite", "F" ); + } + + return req_bkt; +} + +void SerfMoveReqProcImpl::processChunkOfResponseData( const char* /*data*/, + apr_size_t /*len*/ ) +{ + // nothing to do; +} + +void SerfMoveReqProcImpl::handleEndOfResponseData( serf_bucket_t * /*inSerfResponseBucket*/ ) +{ + // nothing to do; +} + +} // namespace http_dav_ucp + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/ucb/source/ucp/webdav/SerfMoveReqProcImpl.hxx b/ucb/source/ucp/webdav/SerfMoveReqProcImpl.hxx new file mode 100644 index 000000000..73a8574b4 --- /dev/null +++ b/ucb/source/ucp/webdav/SerfMoveReqProcImpl.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 . + */ + +#ifndef INCLUDED_UCB_SOURCE_UCP_WEBDAV_SERFMOVEREQPROCIMPL_HXX +#define INCLUDED_UCB_SOURCE_UCP_WEBDAV_SERFMOVEREQPROCIMPL_HXX + +#include "SerfRequestProcessorImpl.hxx" + +namespace http_dav_ucp +{ + +class SerfMoveReqProcImpl : public SerfRequestProcessorImpl +{ +public: + SerfMoveReqProcImpl( const char* inSourcePath, + const DAVRequestHeaders& inRequestHeaders, + const char* inDestinationPath, + const bool inOverwrite ); + + virtual ~SerfMoveReqProcImpl() override; + + virtual + serf_bucket_t * createSerfRequestBucket( serf_request_t * inSerfRequest ) override; + +protected: + virtual + void processChunkOfResponseData( const char* data, apr_size_t len ) override; + + virtual + void handleEndOfResponseData( serf_bucket_t * inSerfResponseBucket ) override; + +private: + const char* mDestPathStr; + const bool mbOverwrite; +}; + +} // namespace http_dav_ucp + +#endif // INCLUDED_UCB_SOURCE_UCP_WEBDAV_SERFMOVEREQPROCIMPL_HXX + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/ucb/source/ucp/webdav/SerfPostReqProcImpl.cxx b/ucb/source/ucp/webdav/SerfPostReqProcImpl.cxx new file mode 100644 index 000000000..bbef1970e --- /dev/null +++ b/ucb/source/ucp/webdav/SerfPostReqProcImpl.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 "SerfPostReqProcImpl.hxx" + +#include <serf.h> + +using namespace com::sun::star; + +namespace http_dav_ucp +{ + +SerfPostReqProcImpl::SerfPostReqProcImpl( const char* inPath, + const DAVRequestHeaders& inRequestHeaders, + const char* inData, + apr_size_t inDataLen, + const char* inContentType, + const char* inReferer, + const rtl::Reference< SerfInputStream > & xioInStrm ) + : SerfRequestProcessorImpl( inPath, inRequestHeaders ) + , mpPostData( inData ) + , mnPostDataLen( inDataLen ) + , mpContentType( inContentType ) + , mpReferer( inReferer ) + , xInputStream( xioInStrm ) + , xOutputStream() +{ +} + +SerfPostReqProcImpl::SerfPostReqProcImpl( const char* inPath, + const DAVRequestHeaders& inRequestHeaders, + const char* inData, + apr_size_t inDataLen, + const char* inContentType, + const char* inReferer, + const css::uno::Reference< css::io::XOutputStream > & xioOutStrm ) + : SerfRequestProcessorImpl( inPath, inRequestHeaders ) + , mpPostData( inData ) + , mnPostDataLen( inDataLen ) + , mpContentType( inContentType ) + , mpReferer( inReferer ) + , xInputStream() + , xOutputStream( xioOutStrm ) +{ +} + +SerfPostReqProcImpl::~SerfPostReqProcImpl() +{ +} + +serf_bucket_t * SerfPostReqProcImpl::createSerfRequestBucket( serf_request_t * inSerfRequest ) +{ + serf_bucket_alloc_t* pSerfBucketAlloc = serf_request_get_alloc( inSerfRequest ); + + // create body bucket + serf_bucket_t* body_bkt = nullptr; + if ( mpPostData != nullptr && mnPostDataLen > 0 ) + { + body_bkt = SERF_BUCKET_SIMPLE_STRING_LEN( mpPostData, mnPostDataLen, pSerfBucketAlloc ); + } + + // create serf request + serf_bucket_t *req_bkt = serf_request_bucket_request_create( inSerfRequest, + "POST", + getPathStr(), + body_bkt, + serf_request_get_alloc( inSerfRequest ) ); + + // set request header fields + serf_bucket_t* hdrs_bkt = serf_bucket_request_get_headers( req_bkt ); + // general header fields provided by caller + setRequestHeaders( hdrs_bkt ); + + handleChunkedEncoding(req_bkt, mnPostDataLen); + + // request specific header fields + if ( mpContentType != nullptr ) + { + serf_bucket_headers_set( hdrs_bkt, "Content-Type", mpContentType ); + } + if ( mpReferer != nullptr ) + { + serf_bucket_headers_set( hdrs_bkt, "Referer", mpReferer ); + } + + return req_bkt; +} + +void SerfPostReqProcImpl::processChunkOfResponseData( const char* data, + apr_size_t len ) +{ + if ( xInputStream.is() ) + { + xInputStream->AddToStream( data, len ); + } + else if ( xOutputStream.is() ) + { + const uno::Sequence< sal_Int8 > aDataSeq( reinterpret_cast<const sal_Int8 *>(data), len ); + xOutputStream->writeBytes( aDataSeq ); + } +} + +void SerfPostReqProcImpl::handleEndOfResponseData( serf_bucket_t * /*inSerfResponseBucket*/ ) +{ + // nothing to do; +} + +} // namespace http_dav_ucp + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/ucb/source/ucp/webdav/SerfPostReqProcImpl.hxx b/ucb/source/ucp/webdav/SerfPostReqProcImpl.hxx new file mode 100644 index 000000000..aebd60b36 --- /dev/null +++ b/ucb/source/ucp/webdav/SerfPostReqProcImpl.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 . + */ + +#ifndef INCLUDED_UCB_SOURCE_UCP_WEBDAV_SERFPOSTREQPROCIMPL_HXX +#define INCLUDED_UCB_SOURCE_UCP_WEBDAV_SERFPOSTREQPROCIMPL_HXX + +#include "SerfRequestProcessorImpl.hxx" + +#include "SerfInputStream.hxx" +#include <com/sun/star/io/XOutputStream.hpp> + +namespace http_dav_ucp +{ + +class SerfPostReqProcImpl : public SerfRequestProcessorImpl +{ +public: + SerfPostReqProcImpl( const char* inPath, + const DAVRequestHeaders& inRequestHeaders, + const char* inData, + apr_size_t inDataLen, + const char* inContentType, + const char* inReferer, + const rtl::Reference< SerfInputStream > & xioInStrm ); + + SerfPostReqProcImpl( const char* inPath, + const DAVRequestHeaders& inRequestHeaders, + const char* inData, + apr_size_t inDataLen, + const char* inContentType, + const char* inReferer, + const css::uno::Reference< css::io::XOutputStream > & xioOutStrm ); + + virtual ~SerfPostReqProcImpl() override; + + virtual + serf_bucket_t * createSerfRequestBucket( serf_request_t * inSerfRequest ) override; + +protected: + virtual + void processChunkOfResponseData( const char* data, apr_size_t len ) override; + + virtual + void handleEndOfResponseData( serf_bucket_t * inSerfResponseBucket ) override; + +private: + const char* mpPostData; + apr_size_t mnPostDataLen; + const char* mpContentType; + const char* mpReferer; + rtl::Reference< SerfInputStream > xInputStream; + css::uno::Reference< css::io::XOutputStream > xOutputStream; + +}; + +} // namespace http_dav_ucp + +#endif // INCLUDED_UCB_SOURCE_UCP_WEBDAV_SERFPOSTREQPROCIMPL_HXX + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/ucb/source/ucp/webdav/SerfPropFindReqProcImpl.cxx b/ucb/source/ucp/webdav/SerfPropFindReqProcImpl.cxx new file mode 100644 index 000000000..39a471fee --- /dev/null +++ b/ucb/source/ucp/webdav/SerfPropFindReqProcImpl.cxx @@ -0,0 +1,195 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this 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 "SerfPropFindReqProcImpl.hxx" +#include "DAVProperties.hxx" + +#include "webdavresponseparser.hxx" +#include <rtl/strbuf.hxx> + + +using namespace com::sun::star; + +namespace http_dav_ucp +{ + +SerfPropFindReqProcImpl::SerfPropFindReqProcImpl( const char* inPath, + const DAVRequestHeaders& inRequestHeaders, + const Depth inDepth, + const std::vector< OUString > & inPropNames, + std::vector< DAVResource > & ioResources ) + : SerfRequestProcessorImpl( inPath, inRequestHeaders ) + , mDepthStr( nullptr ) + , mpPropNames( &inPropNames ) + , mpResources( &ioResources ) + , mpResInfo( nullptr ) + , mbOnlyPropertyNames( false ) + , xInputStream( new SerfInputStream() ) +{ + init( inDepth ); +} + +SerfPropFindReqProcImpl::SerfPropFindReqProcImpl( const char* inPath, + const DAVRequestHeaders& inRequestHeaders, + const Depth inDepth, + std::vector< DAVResourceInfo > & ioResInfo ) + : SerfRequestProcessorImpl( inPath, inRequestHeaders ) + , mDepthStr( nullptr ) + , mpPropNames( nullptr ) + , mpResources( nullptr ) + , mpResInfo( &ioResInfo ) + , mbOnlyPropertyNames( true ) + , xInputStream( new SerfInputStream() ) +{ + init( inDepth ); +} + +void SerfPropFindReqProcImpl::init( const Depth inDepth ) +{ + switch ( inDepth ) + { + case DAVZERO: + mDepthStr = "0"; + break; + case DAVONE: + mDepthStr = "1"; + break; + case DAVINFINITY: + mDepthStr = "infinity"; + break; + } +} + +SerfPropFindReqProcImpl::~SerfPropFindReqProcImpl() +{ +} + +#define PROPFIND_HEADER "<?xml version=\"1.0\" encoding=\"utf-8\"?><propfind xmlns=\"DAV:\">" +#define PROPFIND_TRAILER "</propfind>" + +serf_bucket_t * SerfPropFindReqProcImpl::createSerfRequestBucket( serf_request_t * inSerfRequest ) +{ + serf_bucket_alloc_t* pSerfBucketAlloc = serf_request_get_alloc( inSerfRequest ); + + // body bucket - certain properties OR all properties OR only property names + serf_bucket_t* body_bkt = nullptr; + OString aBodyText; + { + OStringBuffer aBuffer; + aBuffer.append( PROPFIND_HEADER ); + + // create and fill body bucket with requested properties + const int nPropCount = ( !mbOnlyPropertyNames && mpPropNames ) + ? mpPropNames->size() + : 0; + if ( nPropCount > 0 ) + { + aBuffer.append( "<prop>" ); + SerfPropName thePropName; + for ( int theIndex = 0; theIndex < nPropCount; theIndex ++ ) + { + // split fullname into namespace and name! + DAVProperties::createSerfPropName( (*mpPropNames)[ theIndex ], + thePropName ); + + /* <*propname* xmlns="*propns*" /> */ + aBuffer.append( "<" ); + aBuffer.append( thePropName.name ); + aBuffer.append( " xmlns=\"" ); + aBuffer.append( thePropName.nspace ); + aBuffer.append( "\"/>" ); + } + + aBuffer.append( "</prop>" ); + } + else + { + if ( mbOnlyPropertyNames ) + { + aBuffer.append( "<propname/>" ); + } + else + { + aBuffer.append( "<allprop/>" ); + } + } + + aBuffer.append( PROPFIND_TRAILER ); + aBodyText = aBuffer.makeStringAndClear(); + body_bkt = serf_bucket_simple_copy_create( aBodyText.getStr(), + aBodyText.getLength(), + pSerfBucketAlloc ); + } + + // create serf request + serf_bucket_t *req_bkt = serf_request_bucket_request_create( inSerfRequest, + "PROPFIND", + getPathStr(), + body_bkt, + pSerfBucketAlloc ); + handleChunkedEncoding(req_bkt, aBodyText.getLength()); + + // set request header fields + serf_bucket_t* hdrs_bkt = serf_bucket_request_get_headers( req_bkt ); + if (hdrs_bkt != nullptr) + { + // general header fields provided by caller + setRequestHeaders( hdrs_bkt ); + + // request specific header fields + serf_bucket_headers_set( hdrs_bkt, "Depth", mDepthStr ); + if (hdrs_bkt!=nullptr && body_bkt != nullptr && aBodyText.getLength() > 0 ) + { + serf_bucket_headers_set( hdrs_bkt, "Content-Type", "application/xml" ); + } + } + else + { + assert(!"Headers Bucket missing"); + } + + return req_bkt; +} + +void SerfPropFindReqProcImpl::processChunkOfResponseData( const char* data, + apr_size_t len ) +{ + if ( xInputStream.is() ) + { + xInputStream->AddToStream( data, len ); + } +} + +void SerfPropFindReqProcImpl::handleEndOfResponseData( serf_bucket_t * /*inSerfResponseBucket*/ ) +{ + if ( mbOnlyPropertyNames ) + { + const std::vector< DAVResourceInfo > rResInfo( parseWebDAVPropNameResponse( xInputStream.get() ) ); + *mpResInfo = rResInfo; + } + else + { + const std::vector< DAVResource > rResources( parseWebDAVPropFindResponse( xInputStream.get() ) ); + *mpResources = rResources; + } +} + +} // namespace http_dav_ucp + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/ucb/source/ucp/webdav/SerfPropFindReqProcImpl.hxx b/ucb/source/ucp/webdav/SerfPropFindReqProcImpl.hxx new file mode 100644 index 000000000..2acabda00 --- /dev/null +++ b/ucb/source/ucp/webdav/SerfPropFindReqProcImpl.hxx @@ -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 . + */ + +#ifndef INCLUDED_UCB_SOURCE_UCP_WEBDAV_SERFPROPFINDREQPROCIMPL_HXX +#define INCLUDED_UCB_SOURCE_UCP_WEBDAV_SERFPROPFINDREQPROCIMPL_HXX + +#include "SerfRequestProcessorImpl.hxx" + +#include <vector> +#include <rtl/ustring.hxx> +#include "DAVTypes.hxx" +#include "DAVResource.hxx" + +#include "SerfInputStream.hxx" + +namespace http_dav_ucp +{ + +class SerfPropFindReqProcImpl : public SerfRequestProcessorImpl +{ +public: + SerfPropFindReqProcImpl( const char* inPath, + const DAVRequestHeaders& inRequestHeaders, + const Depth inDepth, + const std::vector< OUString > & inPropNames, + std::vector< DAVResource > & ioResources ); + + SerfPropFindReqProcImpl( const char* inPath, + const DAVRequestHeaders& inRequestHeaders, + const Depth inDepth, + std::vector< DAVResourceInfo > & ioResInfo ); + + virtual ~SerfPropFindReqProcImpl() override; + + virtual + serf_bucket_t * createSerfRequestBucket( serf_request_t * inSerfRequest ) override; + +protected: + virtual + void processChunkOfResponseData( const char* data, apr_size_t len ) override; + + virtual + void handleEndOfResponseData( serf_bucket_t * inSerfResponseBucket ) override; + +private: + void init( const Depth inDepth ); + + const char* mDepthStr; + const std::vector< OUString > * mpPropNames; + std::vector< DAVResource > * mpResources; + std::vector< DAVResourceInfo > * mpResInfo; + + const bool mbOnlyPropertyNames; + rtl::Reference< SerfInputStream > xInputStream; +}; + +} // namespace http_dav_ucp + +#endif // INCLUDED_UCB_SOURCE_UCP_WEBDAV_SERFPROPFINDREQPROCIMPL_HXX + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/ucb/source/ucp/webdav/SerfPropPatchReqProcImpl.cxx b/ucb/source/ucp/webdav/SerfPropPatchReqProcImpl.cxx new file mode 100644 index 000000000..4540ccbd0 --- /dev/null +++ b/ucb/source/ucp/webdav/SerfPropPatchReqProcImpl.cxx @@ -0,0 +1,189 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this 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/ustring.hxx> +#include <rtl/ustrbuf.hxx> +#include "DAVProperties.hxx" +#include "UCBDeadPropertyValue.hxx" + +#include "SerfPropPatchReqProcImpl.hxx" + +namespace http_dav_ucp +{ + +SerfPropPatchReqProcImpl::SerfPropPatchReqProcImpl( const char* inPath, + const DAVRequestHeaders& inRequestHeaders, + const std::vector< ProppatchValue > & inProperties ) + : SerfRequestProcessorImpl( inPath, inRequestHeaders ) + , mpProperties( &inProperties ) +{ +} + +SerfPropPatchReqProcImpl::~SerfPropPatchReqProcImpl() +{ +} + +#define PROPPATCH_HEADER "<?xml version=\"1.0\" encoding=\"utf-8\"?><propertyupdate xmlns=\"DAV:\">" +#define PROPPATCH_TRAILER "</propertyupdate>" + +serf_bucket_t * SerfPropPatchReqProcImpl::createSerfRequestBucket( serf_request_t * inSerfRequest ) +{ + serf_bucket_alloc_t* pSerfBucketAlloc = serf_request_get_alloc( inSerfRequest ); + + // body bucket + serf_bucket_t* body_bkt = nullptr; + OString aBodyText; + { + // create and fill body bucket with properties to be set or removed + static const struct + { + const char *str; + sal_Int32 len; + } + OpCode [] = { + { RTL_CONSTASCII_STRINGPARAM( "set" ) }, + { RTL_CONSTASCII_STRINGPARAM( "remove" ) } + }; + const int nPropCount = ( mpProperties != nullptr ) + ? mpProperties->size() + : 0; + if ( nPropCount > 0 ) + { + OUStringBuffer aBuffer; + // add PropPatch xml header in front + aBuffer.append( PROPPATCH_HEADER ); + + // <*operation code*><prop> + + ProppatchOperation lastOp = (*mpProperties)[ 0 ].operation; + aBuffer.append( "<" ); + aBuffer.appendAscii( OpCode[lastOp].str, OpCode[lastOp].len ); + aBuffer.append( "><prop>" ); + + SerfPropName thePropName; + for ( int n = 0; n < nPropCount; ++n ) + { + const ProppatchValue & rProperty = (*mpProperties)[ n ]; + // split fullname into namespace and name! + DAVProperties::createSerfPropName( rProperty.name, + thePropName ); + + if ( rProperty.operation != lastOp ) + { + // </prop></*last operation code*><*operation code><prop> + aBuffer.append( "</prop></" ); + aBuffer.appendAscii( OpCode[lastOp].str, OpCode[lastOp].len ); + aBuffer.append( "><" ); + aBuffer.appendAscii( OpCode[rProperty.operation].str, OpCode[rProperty.operation].len ); + aBuffer.append( "><prop>" ); + } + + // <*propname* xmlns="*propns*" + aBuffer.append( "<" ); + aBuffer.appendAscii( thePropName.name ); + aBuffer.append( " xmlns=\"" ); + aBuffer.appendAscii( thePropName.nspace ); + aBuffer.append( "\"" ); + + if ( rProperty.operation == PROPSET ) + { + // >*property value*</*propname*> + aBuffer.append( ">" ); + + OUString aStringValue; + if ( DAVProperties::isUCBDeadProperty( thePropName ) ) + { + UCBDeadPropertyValue::toXML( rProperty.value, + aStringValue ); + } + else + { + rProperty.value >>= aStringValue; + } + aBuffer.append( aStringValue ); + aBuffer.append( "</" ); + aBuffer.appendAscii( thePropName.name ); + aBuffer.append( ">" ); + } + else + { + aBuffer.append( "/>" ); + } + + lastOp = rProperty.operation; + } + + // </prop></*last operation code*> + aBuffer.append( "</prop></" ); + aBuffer.appendAscii( OpCode[lastOp].str, OpCode[lastOp].len ); + aBuffer.append( ">" ); + + // add PropPatch xml trailer at end + aBuffer.append( PROPPATCH_TRAILER ); + + aBodyText = OUStringToOString( aBuffer.makeStringAndClear(), RTL_TEXTENCODING_UTF8 ); + body_bkt = serf_bucket_simple_copy_create( aBodyText.getStr(), + aBodyText.getLength(), + pSerfBucketAlloc ); + } + } + + // create serf request + serf_bucket_t *req_bkt = serf_request_bucket_request_create( inSerfRequest, + "PROPPATCH", + getPathStr(), + body_bkt, + pSerfBucketAlloc ) ; + handleChunkedEncoding(req_bkt, aBodyText.getLength()); + + // set request header fields + serf_bucket_t* hdrs_bkt = serf_bucket_request_get_headers( req_bkt ); + if (hdrs_bkt != nullptr) + { + // general header fields provided by caller + setRequestHeaders( hdrs_bkt ); + + // request specific header fields + if ( body_bkt != nullptr && aBodyText.getLength() > 0 ) + { + serf_bucket_headers_set( hdrs_bkt, "Content-Type", "application/xml" ); + } + } + else + { + assert(!"Headers Bucket missing"); + } + + return req_bkt; +} + +void SerfPropPatchReqProcImpl::processChunkOfResponseData( const char* /*data*/, + apr_size_t /*len*/ ) +{ + // nothing to do; +} + +void SerfPropPatchReqProcImpl::handleEndOfResponseData( serf_bucket_t * /*inSerfResponseBucket*/ ) +{ + // nothing to do; +} + +} // namespace http_dav_ucp + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/ucb/source/ucp/webdav/SerfPropPatchReqProcImpl.hxx b/ucb/source/ucp/webdav/SerfPropPatchReqProcImpl.hxx new file mode 100644 index 000000000..330e0d953 --- /dev/null +++ b/ucb/source/ucp/webdav/SerfPropPatchReqProcImpl.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 . + */ + +#ifndef INCLUDED_UCB_SOURCE_UCP_WEBDAV_SERFPROPPATCHREQPROCIMPL_HXX +#define INCLUDED_UCB_SOURCE_UCP_WEBDAV_SERFPROPPATCHREQPROCIMPL_HXX + +#include "SerfRequestProcessorImpl.hxx" + +#include <vector> +#include "DAVTypes.hxx" + +namespace http_dav_ucp +{ + +class SerfPropPatchReqProcImpl : public SerfRequestProcessorImpl +{ +public: + SerfPropPatchReqProcImpl( const char* inPath, + const DAVRequestHeaders& inRequestHeaders, + const std::vector< ProppatchValue > & inProperties ); + + virtual ~SerfPropPatchReqProcImpl() override; + + virtual + serf_bucket_t * createSerfRequestBucket( serf_request_t * inSerfRequest ) override; + +protected: + virtual + void processChunkOfResponseData( const char* data, apr_size_t len ) override; + + virtual + void handleEndOfResponseData( serf_bucket_t * inSerfResponseBucket ) override; + +private: + const std::vector< ProppatchValue > * mpProperties; +}; + +} // namespace http_dav_ucp + +#endif // INCLUDED_UCB_SOURCE_UCP_WEBDAV_SERFPROPPATCHREQPROCIMPL_HXX + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/ucb/source/ucp/webdav/SerfPutReqProcImpl.cxx b/ucb/source/ucp/webdav/SerfPutReqProcImpl.cxx new file mode 100644 index 000000000..466f7a10c --- /dev/null +++ b/ucb/source/ucp/webdav/SerfPutReqProcImpl.cxx @@ -0,0 +1,90 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this 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/ustring.hxx> + +#include "SerfPutReqProcImpl.hxx" + +#include <serf.h> + +namespace http_dav_ucp +{ + +SerfPutReqProcImpl::SerfPutReqProcImpl( const char* inPath, + const DAVRequestHeaders& inRequestHeaders, + const char* inData, + apr_size_t inDataLen, + const OUString& sToken ) + : SerfRequestProcessorImpl( inPath, inRequestHeaders ) + , mpData( inData ) + , mnDataLen( inDataLen ) + , msToken( sToken ) +{ +} + +SerfPutReqProcImpl::~SerfPutReqProcImpl() +{ +} + +serf_bucket_t * SerfPutReqProcImpl::createSerfRequestBucket( serf_request_t * inSerfRequest ) +{ + serf_bucket_alloc_t* pSerfBucketAlloc = serf_request_get_alloc( inSerfRequest ); + + // create body bucket + serf_bucket_t* body_bkt = nullptr; + if ( mpData != nullptr && mnDataLen > 0 ) + { + body_bkt = SERF_BUCKET_SIMPLE_STRING_LEN( mpData, mnDataLen, pSerfBucketAlloc ); + } + + // create serf request + serf_bucket_t *req_bkt = serf_request_bucket_request_create( inSerfRequest, + "PUT", + getPathStr(), + body_bkt, + serf_request_get_alloc( inSerfRequest ) ); + handleChunkedEncoding(req_bkt, mnDataLen); + + // set request header fields + serf_bucket_t* hdrs_bkt = serf_bucket_request_get_headers( req_bkt ); + // general header fields provided by caller + setRequestHeaders( hdrs_bkt ); + + // 'If' header with token, so that we can save document locked by us + const OString sIfHeader( "<" + rtl::OStringView(getPathStr()) + "> (<" + OUStringToOString( + msToken, RTL_TEXTENCODING_UTF8) + ">)" ); + serf_bucket_headers_set( hdrs_bkt, "If", sIfHeader.getStr() ); + + return req_bkt; +} + +void SerfPutReqProcImpl::processChunkOfResponseData( const char* /*data*/, + apr_size_t /*len*/ ) +{ + // nothing to do; +} + +void SerfPutReqProcImpl::handleEndOfResponseData( serf_bucket_t * /*inSerfResponseBucket*/ ) +{ + // nothing to do; +} + +} // namespace http_dav_ucp + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/ucb/source/ucp/webdav/SerfPutReqProcImpl.hxx b/ucb/source/ucp/webdav/SerfPutReqProcImpl.hxx new file mode 100644 index 000000000..8eeb791ee --- /dev/null +++ b/ucb/source/ucp/webdav/SerfPutReqProcImpl.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 . + */ + +#ifndef INCLUDED_UCB_SOURCE_UCP_WEBDAV_SERFPUTREQPROCIMPL_HXX +#define INCLUDED_UCB_SOURCE_UCP_WEBDAV_SERFPUTREQPROCIMPL_HXX + +#include "SerfRequestProcessorImpl.hxx" + +namespace http_dav_ucp +{ + +class SerfPutReqProcImpl : public SerfRequestProcessorImpl +{ +public: + SerfPutReqProcImpl( const char* inPath, + const DAVRequestHeaders& inRequestHeaders, + const char* inData, + apr_size_t inDataLen, + const OUString& sToken ); + + + virtual ~SerfPutReqProcImpl() override; + + virtual + serf_bucket_t * createSerfRequestBucket( serf_request_t * inSerfRequest ) override; + +protected: + virtual + void processChunkOfResponseData( const char* data, apr_size_t len ) override; + + virtual + void handleEndOfResponseData( serf_bucket_t * inSerfResponseBucket ) override; + +private: + const char* mpData; + apr_size_t mnDataLen; + OUString msToken; +}; + +} // namespace http_dav_ucp + +#endif // INCLUDED_UCB_SOURCE_UCP_WEBDAV_SERFPUTREQPROCIMPL_HXX + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/ucb/source/ucp/webdav/SerfRequestProcessor.cxx b/ucb/source/ucp/webdav/SerfRequestProcessor.cxx new file mode 100644 index 000000000..21cc15ec0 --- /dev/null +++ b/ucb/source/ucp/webdav/SerfRequestProcessor.cxx @@ -0,0 +1,596 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this 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 "SerfRequestProcessor.hxx" + +#include "AprEnv.hxx" +#include "SerfCallbacks.hxx" +#include "SerfSession.hxx" +#include "SerfPropFindReqProcImpl.hxx" +#include "SerfPropPatchReqProcImpl.hxx" +#include "SerfGetReqProcImpl.hxx" +#include "SerfHeadReqProcImpl.hxx" +#include "SerfPutReqProcImpl.hxx" +#include "SerfPostReqProcImpl.hxx" +#include "SerfDeleteReqProcImpl.hxx" +#include "SerfMkColReqProcImpl.hxx" +#include "SerfCopyReqProcImpl.hxx" +#include "SerfMoveReqProcImpl.hxx" +#include "SerfLockReqProcImpl.hxx" +#include "SerfUnlockReqProcImpl.hxx" + +#include <apr_strings.h> + +namespace http_dav_ucp +{ + +SerfRequestProcessor::SerfRequestProcessor( SerfSession& rSerfSession, + const OUString & inPath, + const bool bUseChunkedEncoding ) + : mrSerfSession( rSerfSession ) + , mPathStr( nullptr ) + , mbUseChunkedEncoding( bUseChunkedEncoding ) + , mDestPathStr( nullptr ) + , mContentType( nullptr ) + , mReferer( nullptr ) + , mpProcImpl( nullptr ) + , mbProcessingDone( false ) + , mpDAVException() + , mnHTTPStatusCode( SC_NONE ) + , mHTTPStatusCodeText() + , mRedirectLocation() + , mnSuccessfulCredentialAttempts( 0 ) + , mbInputOfCredentialsAborted( false ) + , mbSetupSerfRequestCalled( false ) + , mbAcceptSerfResponseCalled( false ) + , mbHandleSerfResponseCalled( false ) +{ + mPathStr = apr_pstrdup( SerfSession::getAprPool(), + OUStringToOString( inPath, RTL_TEXTENCODING_UTF8 ).getStr() ); +} + +SerfRequestProcessor::~SerfRequestProcessor() +{ + delete mpProcImpl; + delete mpDAVException; +} + +void SerfRequestProcessor::prepareProcessor() +{ + delete mpDAVException; + mpDAVException = nullptr; + mnHTTPStatusCode = SC_NONE; + mHTTPStatusCodeText.clear(); + mRedirectLocation.clear(); + + mnSuccessfulCredentialAttempts = 0; + mbInputOfCredentialsAborted = false; + mbSetupSerfRequestCalled = false; + mbAcceptSerfResponseCalled = false; + mbHandleSerfResponseCalled = false; +} + +// PROPFIND - allprop & named +bool SerfRequestProcessor::processPropFind( const Depth inDepth, + const std::vector< OUString > & inPropNames, + std::vector< DAVResource > & ioResources, + apr_status_t& outSerfStatus ) +{ + mpProcImpl = new SerfPropFindReqProcImpl( mPathStr, + mrSerfSession.getRequestEnvironment().m_aRequestHeaders, + inDepth, + inPropNames, + ioResources ); + outSerfStatus = runProcessor(); + + return outSerfStatus == APR_SUCCESS; +} + +// PROPFIND - property names +bool SerfRequestProcessor::processPropFind( const Depth inDepth, + std::vector< DAVResourceInfo > & ioResInfo, + apr_status_t& outSerfStatus ) +{ + mpProcImpl = new SerfPropFindReqProcImpl( mPathStr, + mrSerfSession.getRequestEnvironment().m_aRequestHeaders, + inDepth, + ioResInfo ); + outSerfStatus = runProcessor(); + + return outSerfStatus == APR_SUCCESS; +} + +// PROPPATCH +bool SerfRequestProcessor::processPropPatch( const std::vector< ProppatchValue > & inProperties, + apr_status_t& outSerfStatus ) +{ + mpProcImpl = new SerfPropPatchReqProcImpl( mPathStr, + mrSerfSession.getRequestEnvironment().m_aRequestHeaders, + inProperties ); + outSerfStatus = runProcessor(); + + return outSerfStatus == APR_SUCCESS; +} + +// GET +bool SerfRequestProcessor::processGet( const rtl::Reference< SerfInputStream >& xioInStrm, + apr_status_t& outSerfStatus ) +{ + mpProcImpl = new SerfGetReqProcImpl( mPathStr, + mrSerfSession.getRequestEnvironment().m_aRequestHeaders, + xioInStrm ); + outSerfStatus = runProcessor(); + + return outSerfStatus == APR_SUCCESS; +} + +// GET inclusive header fields +bool SerfRequestProcessor::processGet( const rtl::Reference< SerfInputStream >& xioInStrm, + const std::vector< OUString > & inHeaderNames, + DAVResource & ioResource, + apr_status_t& outSerfStatus ) +{ + mpProcImpl = new SerfGetReqProcImpl( mPathStr, + mrSerfSession.getRequestEnvironment().m_aRequestHeaders, + xioInStrm, + inHeaderNames, + ioResource ); + outSerfStatus = runProcessor(); + + return outSerfStatus == APR_SUCCESS; +} + +// GET +bool SerfRequestProcessor::processGet( const css::uno::Reference< css::io::XOutputStream >& xioOutStrm, + apr_status_t& outSerfStatus ) +{ + mpProcImpl = new SerfGetReqProcImpl( mPathStr, + mrSerfSession.getRequestEnvironment().m_aRequestHeaders, + xioOutStrm ); + outSerfStatus = runProcessor(); + + return outSerfStatus == APR_SUCCESS; +} + +// GET inclusive header fields +bool SerfRequestProcessor::processGet( const css::uno::Reference< css::io::XOutputStream >& xioOutStrm, + const std::vector< OUString > & inHeaderNames, + DAVResource & ioResource, + apr_status_t& outSerfStatus ) +{ + mpProcImpl = new SerfGetReqProcImpl( mPathStr, + mrSerfSession.getRequestEnvironment().m_aRequestHeaders, + xioOutStrm, + inHeaderNames, + ioResource ); + outSerfStatus = runProcessor(); + + return outSerfStatus == APR_SUCCESS; +} + +// HEAD +bool SerfRequestProcessor::processHead( const std::vector< OUString > & inHeaderNames, + DAVResource & ioResource, + apr_status_t& outSerfStatus ) +{ + mpProcImpl = new SerfHeadReqProcImpl( mPathStr, + mrSerfSession.getRequestEnvironment().m_aRequestHeaders, + inHeaderNames, + ioResource ); + outSerfStatus = runProcessor(); + + return outSerfStatus == APR_SUCCESS; +} + +// PUT +bool SerfRequestProcessor::processPut( const char* inData, + apr_size_t inDataLen, + apr_status_t& outSerfStatus ) +{ + // get the lock from lock store + const OUString sToken( + apr_environment::AprEnv::getAprEnv()->getSerfLockStore()->getLockToken( + OUString::createFromAscii(mPathStr)) ); + + mpProcImpl = new SerfPutReqProcImpl( mPathStr, + mrSerfSession.getRequestEnvironment().m_aRequestHeaders, + inData, + inDataLen, + sToken ); + outSerfStatus = runProcessor(); + + return outSerfStatus == APR_SUCCESS; +} + +// POST +bool SerfRequestProcessor::processPost( const char* inData, + apr_size_t inDataLen, + const OUString & inContentType, + const OUString & inReferer, + const rtl::Reference< SerfInputStream >& xioInStrm, + apr_status_t& outSerfStatus ) +{ + mContentType = apr_pstrdup( SerfSession::getAprPool(), + OUStringToOString( inContentType, RTL_TEXTENCODING_UTF8 ).getStr() ); + mReferer = apr_pstrdup( SerfSession::getAprPool(), + OUStringToOString( inReferer, RTL_TEXTENCODING_UTF8 ).getStr() ); + mpProcImpl = new SerfPostReqProcImpl( mPathStr, + mrSerfSession.getRequestEnvironment().m_aRequestHeaders, + inData, + inDataLen, + mContentType, + mReferer, + xioInStrm ); + outSerfStatus = runProcessor(); + + return outSerfStatus == APR_SUCCESS; +} + +// POST +bool SerfRequestProcessor::processPost( const char* inData, + apr_size_t inDataLen, + const OUString & inContentType, + const OUString & inReferer, + const css::uno::Reference< css::io::XOutputStream >& xioOutStrm, + apr_status_t& outSerfStatus ) +{ + mContentType = apr_pstrdup( SerfSession::getAprPool(), + OUStringToOString( inContentType, RTL_TEXTENCODING_UTF8 ).getStr() ); + mReferer = apr_pstrdup( SerfSession::getAprPool(), + OUStringToOString( inReferer, RTL_TEXTENCODING_UTF8 ).getStr() ); + mpProcImpl = new SerfPostReqProcImpl( mPathStr, + mrSerfSession.getRequestEnvironment().m_aRequestHeaders, + inData, + inDataLen, + mContentType, + mReferer, + xioOutStrm ); + outSerfStatus = runProcessor(); + + return outSerfStatus == APR_SUCCESS; +} + +// DELETE +bool SerfRequestProcessor::processDelete( apr_status_t& outSerfStatus ) +{ + mpProcImpl = new SerfDeleteReqProcImpl( mPathStr, + mrSerfSession.getRequestEnvironment().m_aRequestHeaders ); + outSerfStatus = runProcessor(); + + return outSerfStatus == APR_SUCCESS; +} + +// MKCOL +bool SerfRequestProcessor::processMkCol( apr_status_t& outSerfStatus ) +{ + mpProcImpl = new SerfMkColReqProcImpl( mPathStr, + mrSerfSession.getRequestEnvironment().m_aRequestHeaders ); + outSerfStatus = runProcessor(); + + return outSerfStatus == APR_SUCCESS; +} + +// COPY +bool SerfRequestProcessor::processCopy( const OUString & inDestinationPath, + const bool inOverwrite, + apr_status_t& outSerfStatus ) +{ + mDestPathStr = apr_pstrdup( SerfSession::getAprPool(), + OUStringToOString( inDestinationPath, RTL_TEXTENCODING_UTF8 ).getStr() ); + mpProcImpl = new SerfCopyReqProcImpl( mPathStr, + mrSerfSession.getRequestEnvironment().m_aRequestHeaders, + mDestPathStr, + inOverwrite ); + outSerfStatus = runProcessor(); + + return outSerfStatus == APR_SUCCESS; +} + +// MOVE +bool SerfRequestProcessor::processMove( const OUString & inDestinationPath, + const bool inOverwrite, + apr_status_t& outSerfStatus ) +{ + mDestPathStr = apr_pstrdup( SerfSession::getAprPool(), + OUStringToOString( inDestinationPath, RTL_TEXTENCODING_UTF8 ).getStr() ); + mpProcImpl = new SerfMoveReqProcImpl( mPathStr, + mrSerfSession.getRequestEnvironment().m_aRequestHeaders, + mDestPathStr, + inOverwrite ); + outSerfStatus = runProcessor(); + + return outSerfStatus == APR_SUCCESS; +} + + +bool SerfRequestProcessor::processLock( const css::ucb::Lock & rLock, sal_Int32 *plastChanceToSendRefreshRequest ) +{ + mpProcImpl = new SerfLockReqProcImpl( mPathStr, + mrSerfSession.getRequestEnvironment().m_aRequestHeaders, + mrSerfSession, + rLock, + plastChanceToSendRefreshRequest ); + + return runProcessor() == APR_SUCCESS; +} + +bool SerfRequestProcessor::processUnlock() +{ + // get the lock from lock store + const OUString sToken( + apr_environment::AprEnv::getAprEnv()->getSerfLockStore()->getLockToken( + OUString::createFromAscii(mPathStr)) ); + if ( sToken.isEmpty() ) + throw DAVException( DAVException::DAV_NOT_LOCKED ); + + mpProcImpl = new SerfUnlockReqProcImpl( mPathStr, + mrSerfSession.getRequestEnvironment().m_aRequestHeaders, + sToken ); + + return runProcessor() == APR_SUCCESS; +} + +apr_status_t SerfRequestProcessor::runProcessor() +{ + prepareProcessor(); + + // activate chunked encoding, if requested + if ( mbUseChunkedEncoding ) + { + mpProcImpl->activateChunkedEncoding(); + } + + // create serf request + serf_connection_request_create( mrSerfSession.getSerfConnection(), + Serf_SetupRequest, + this ); + + // perform serf request + mbProcessingDone = false; + apr_status_t status = APR_SUCCESS; + serf_context_t* pSerfContext = mrSerfSession.getSerfContext(); + apr_pool_t* pAprPool = SerfSession::getAprPool(); + while ( true ) + { + status = serf_context_run( pSerfContext, + SERF_DURATION_FOREVER, + pAprPool ); + if ( APR_STATUS_IS_TIMEUP( status ) ) + { + continue; + } + if ( status != APR_SUCCESS ) + { + break; + } + if ( mbProcessingDone ) + { + break; + } + } + + postprocessProcessor( status ); + + return status; +} + +void SerfRequestProcessor::postprocessProcessor( const apr_status_t inStatus ) +{ + if ( inStatus == APR_SUCCESS ) + { + return; + } + + switch ( inStatus ) + { + case APR_EGENERAL: + case SERF_ERROR_AUTHN_FAILED: + // general error; <mnHTTPStatusCode> provides more information + { + switch ( mnHTTPStatusCode ) + { + case SC_NONE: + if ( !mbSetupSerfRequestCalled ) + { + mpDAVException = new DAVException( DAVException::DAV_HTTP_LOOKUP, + SerfUri::makeConnectionEndPointString( mrSerfSession.getHostName(), + mrSerfSession.getPort() ) ); + } + else if ( mbInputOfCredentialsAborted ) + { + mpDAVException = new DAVException( DAVException::DAV_HTTP_NOAUTH, + SerfUri::makeConnectionEndPointString( mrSerfSession.getHostName(), + mrSerfSession.getPort() ) ); + } + else + { + mpDAVException = new DAVException( DAVException::DAV_HTTP_ERROR, + mHTTPStatusCodeText, + mnHTTPStatusCode ); + } + break; + case SC_MOVED_PERMANENTLY: + case SC_MOVED_TEMPORARILY: + case SC_SEE_OTHER: + case SC_TEMPORARY_REDIRECT: + mpDAVException = new DAVException( DAVException::DAV_HTTP_REDIRECT, + mRedirectLocation ); + break; + default: + mpDAVException = new DAVException( DAVException::DAV_HTTP_ERROR, + mHTTPStatusCodeText, + mnHTTPStatusCode ); + break; + } + } + break; + + default: + mpDAVException = new DAVException( DAVException::DAV_HTTP_ERROR ); + break; + } + +} + +apr_status_t SerfRequestProcessor::provideSerfCredentials( char ** outUsername, + char ** outPassword, + serf_request_t * inRequest, + int inCode, + const char *inAuthProtocol, + const char *inRealm, + apr_pool_t *inAprPool ) +{ + // as each successful provided credentials are tried twice - see below - the + // number of real attempts is half of the value of <mnSuccessfulCredentialAttempts> + if ( (mnSuccessfulCredentialAttempts / 2) >= 5 || + mbInputOfCredentialsAborted ) + { + mbInputOfCredentialsAborted = true; + return SERF_ERROR_AUTHN_FAILED; + } + + // because serf keeps credentials only for a connection in case of digest authentication + // we give each successful provided credentials a second try in order to workaround the + // situation that the connection for which the credentials have been provided has been closed + // before the provided credentials could be applied for the request. + apr_status_t status = mrSerfSession.provideSerfCredentials( (mnSuccessfulCredentialAttempts % 2) == 1, + outUsername, + outPassword, + inRequest, + inCode, + inAuthProtocol, + inRealm, + inAprPool ); + if ( status != APR_SUCCESS ) + { + mbInputOfCredentialsAborted = true; + } + else + { + ++mnSuccessfulCredentialAttempts; + } + + return status; +} + +apr_status_t SerfRequestProcessor::setupSerfRequest( serf_request_t * inSerfRequest, + serf_bucket_t ** outSerfRequestBucket, + serf_response_acceptor_t * outSerfResponseAcceptor, + void ** outSerfResponseAcceptorBaton, + serf_response_handler_t * outSerfResponseHandler, + void ** outSerfResponseHandlerBaton, + apr_pool_t * /*inAprPool*/ ) +{ + mbSetupSerfRequestCalled = true; + *outSerfRequestBucket = mpProcImpl->createSerfRequestBucket( inSerfRequest ); + + // apply callbacks for accepting response and handling response + *outSerfResponseAcceptor = Serf_AcceptResponse; + *outSerfResponseAcceptorBaton = this; + *outSerfResponseHandler = Serf_HandleResponse; + *outSerfResponseHandlerBaton = this; + + return APR_SUCCESS; +} + +serf_bucket_t* SerfRequestProcessor::acceptSerfResponse( serf_request_t * inSerfRequest, + serf_bucket_t * inSerfStreamBucket, + apr_pool_t * inAprPool ) +{ + mbAcceptSerfResponseCalled = true; + return mrSerfSession.acceptSerfResponse( inSerfRequest, + inSerfStreamBucket, + inAprPool ); +} + +apr_status_t SerfRequestProcessor::handleSerfResponse( serf_request_t * inSerfRequest, + serf_bucket_t * inSerfResponseBucket, + apr_pool_t * inAprPool ) +{ + mbHandleSerfResponseCalled = true; + + // some general response handling and error handling + { + if ( !inSerfResponseBucket ) + { + /* A NULL response can come back if the request failed completely */ + mbProcessingDone = true; + return APR_EGENERAL; + } + + serf_status_line sl; + apr_status_t status = serf_bucket_response_status( inSerfResponseBucket, &sl ); + if ( status ) + { + mbProcessingDone = false; // allow another try in order to get a response + return status; + } + // TODO - check, if response status code handling is correct + mnHTTPStatusCode = ( sl.version != 0 && sl.code >= 0 ) + ? static_cast< sal_uInt16 >( sl.code ) + : SC_NONE; + if ( sl.reason ) + { + mHTTPStatusCodeText = OUString::createFromAscii( sl.reason ); + } + if ( ( sl.version == 0 || sl.code < 0 ) || + mnHTTPStatusCode >= 300 ) + { + if ( mnHTTPStatusCode == 301 || + mnHTTPStatusCode == 302 || + mnHTTPStatusCode == 303 || + mnHTTPStatusCode == 307 ) + { + // new location for certain redirections + serf_bucket_t *headers = serf_bucket_response_get_headers( inSerfResponseBucket ); + const char* location = serf_bucket_headers_get( headers, "Location" ); + if ( location ) + { + mRedirectLocation = OUString::createFromAscii( location ); + } + mbProcessingDone = true; + return APR_EGENERAL; + } + else if ( mrSerfSession.isHeadRequestInProgress() && + ( mnHTTPStatusCode == 401 || mnHTTPStatusCode == 407 ) ) + { + // keep going as authentication is not required on HEAD request. + // the response already contains header fields. + } + else + { + mbProcessingDone = true; + return APR_EGENERAL; + } + } + } + + // request specific processing of the response bucket + apr_status_t status = APR_SUCCESS; + mbProcessingDone = mpProcImpl->processSerfResponseBucket( inSerfRequest, + inSerfResponseBucket, + inAprPool, + status ); + + return status; +} + +} // namespace http_dav_ucp + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/ucb/source/ucp/webdav/SerfRequestProcessor.hxx b/ucb/source/ucp/webdav/SerfRequestProcessor.hxx new file mode 100644 index 000000000..dea753fed --- /dev/null +++ b/ucb/source/ucp/webdav/SerfRequestProcessor.hxx @@ -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 . + */ + + +#ifndef INCLUDED_UCB_SOURCE_UCP_WEBDAV_SERFREQUESTPROCESSOR_HXX +#define INCLUDED_UCB_SOURCE_UCP_WEBDAV_SERFREQUESTPROCESSOR_HXX + +#include <apr_errno.h> +#include <apr_pools.h> + +#include <serf.h> + +#include "DAVTypes.hxx" +#include "DAVResource.hxx" +#include "DAVException.hxx" + +#include "SerfInputStream.hxx" +#include <com/sun/star/io/XOutputStream.hpp> +#include <com/sun/star/ucb/Lock.hpp> + +#include <rtl/ref.hxx> + +namespace http_dav_ucp +{ + +class SerfSession; +class SerfRequestProcessorImpl; + +class SerfRequestProcessor +{ +public: + SerfRequestProcessor( SerfSession& rSerfSession, + const OUString & inPath, + const bool bUseChunkedEncoding ); + ~SerfRequestProcessor(); + + // PROPFIND - allprop & named + bool processPropFind( const Depth inDepth, + const std::vector< OUString > & inPropNames, + std::vector< DAVResource > & ioResources, + apr_status_t& outSerfStatus ); + + // PROPFIND - property names + bool processPropFind( const Depth inDepth, + std::vector< DAVResourceInfo > & ioResInfo, + apr_status_t& outSerfStatus ); + + // PROPPATCH + bool processPropPatch( const std::vector< ProppatchValue > & inProperties, + apr_status_t& outSerfStatus ); + + // GET + bool processGet( const rtl::Reference< SerfInputStream >& xioInStrm, + apr_status_t& outSerfStatus ); + + // GET inclusive header fields + bool processGet( const rtl::Reference< SerfInputStream >& xioInStrm, + const std::vector< OUString > & inHeaderNames, + DAVResource & ioResource, + apr_status_t& outSerfStatus ); + + // GET + bool processGet( const css::uno::Reference< css::io::XOutputStream >& xioOutStrm, + apr_status_t& outSerfStatus ); + + // GET inclusive header fields + bool processGet( const css::uno::Reference< css::io::XOutputStream >& xioOutStrm, + const std::vector< OUString > & inHeaderNames, + DAVResource & ioResource, + apr_status_t& outSerfStatus ); + + // HEAD + bool processHead( const std::vector< OUString > & inHeaderNames, + DAVResource & ioResource, + apr_status_t& outSerfStatus ); + + // PUT + bool processPut( const char* inData, + apr_size_t inDataLen, + apr_status_t& outSerfStatus ); + + // POST + bool processPost( const char* inData, + apr_size_t inDataLen, + const OUString & inContentType, + const OUString & inReferer, + const rtl::Reference< SerfInputStream >& xioInStrm, + apr_status_t& outSerfStatus ); + + // POST + bool processPost( const char* inData, + apr_size_t inDataLen, + const OUString & inContentType, + const OUString & inReferer, + const css::uno::Reference< css::io::XOutputStream >& xioOutStrm, + apr_status_t& outSerfStatus ); + + // DELETE + bool processDelete( apr_status_t& outSerfStatus ); + + // MKCOL + bool processMkCol( apr_status_t& outSerfStatus ); + + // COPY + bool processCopy( const OUString & inDestinationPath, + const bool inOverwrite, + apr_status_t& outSerfStatus ); + + // MOVE + bool processMove( const OUString & inDestinationPath, + const bool inOverwrite, + apr_status_t& outSerfStatus ); + + //LOCK + bool processLock( const css::ucb::Lock & rLock, sal_Int32 *plastChanceToSendRefreshRequest = nullptr ); + + //UNLOCK + bool processUnlock(); + + apr_status_t provideSerfCredentials( char ** outUsername, + char ** outPassword, + serf_request_t * inRequest, + int inCode, + const char *inAuthProtocol, + const char *inRealm, + apr_pool_t *inAprPool ); + + apr_status_t setupSerfRequest( serf_request_t * inSerfRequest, + serf_bucket_t ** outSerfRequestBucket, + serf_response_acceptor_t * outSerfResponseAcceptor, + void ** outSerfResponseAcceptorBaton, + serf_response_handler_t * outSerfResponseHandler, + void ** outSerfResponseHandlerBaton, + apr_pool_t * inAprPool ); + + serf_bucket_t* acceptSerfResponse( serf_request_t * inSerfRequest, + serf_bucket_t * inSerfStreamBucket, + apr_pool_t* inAprPool ); + + apr_status_t handleSerfResponse( serf_request_t * inSerfRequest, + serf_bucket_t * inSerfResponseBucket, + apr_pool_t * inAprPool ); + +//private: + void prepareProcessor(); + apr_status_t runProcessor(); + void postprocessProcessor( const apr_status_t inStatus ); + + SerfSession& mrSerfSession; + const char* mPathStr; + const bool mbUseChunkedEncoding; + const char* mDestPathStr; + const char* mContentType; + const char* mReferer; + SerfRequestProcessorImpl* mpProcImpl; + + bool mbProcessingDone; + + DAVException* mpDAVException; + sal_uInt16 mnHTTPStatusCode; + OUString mHTTPStatusCodeText; + OUString mRedirectLocation; + + sal_uInt8 mnSuccessfulCredentialAttempts; + bool mbInputOfCredentialsAborted; + bool mbSetupSerfRequestCalled; + bool mbAcceptSerfResponseCalled; + bool mbHandleSerfResponseCalled; +}; + +} // namespace http_dav_ucp + +#endif // INCLUDED_UCB_SOURCE_UCP_WEBDAV_SERFREQUESTPROCESSOR_HXX + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/ucb/source/ucp/webdav/SerfRequestProcessorImpl.cxx b/ucb/source/ucp/webdav/SerfRequestProcessorImpl.cxx new file mode 100644 index 000000000..dffd5e907 --- /dev/null +++ b/ucb/source/ucp/webdav/SerfRequestProcessorImpl.cxx @@ -0,0 +1,151 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this 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 "SerfRequestProcessorImpl.hxx" +#include <sal/log.hxx> + +namespace +{ +// Define a magic value that is used by serf to reset chunked +// encoding. The value definition is not supported by serf, hence the +// definition here. +static const apr_int64_t SERF_UNKNOWN_LENGTH (-1); +} + +namespace http_dav_ucp +{ + +SerfRequestProcessorImpl::SerfRequestProcessorImpl( const char* inPath, + const DAVRequestHeaders& inRequestHeaders ) + : mPathStr( inPath ) + , mrRequestHeaders( inRequestHeaders ) + , mbUseChunkedEncoding( false ) +{ +} + +SerfRequestProcessorImpl::~SerfRequestProcessorImpl() +{ +} + +const char* SerfRequestProcessorImpl::getPathStr() const +{ + return mPathStr; +} + +void SerfRequestProcessorImpl::activateChunkedEncoding() +{ + mbUseChunkedEncoding = true; +} + +bool SerfRequestProcessorImpl::useChunkedEncoding() const +{ + return mbUseChunkedEncoding; +} + + +void SerfRequestProcessorImpl::handleChunkedEncoding ( + serf_bucket_t* pRequestBucket, + apr_int64_t nLength) const +{ + if (pRequestBucket != nullptr) + { + if (useChunkedEncoding()) + { + // Activate chunked encoding. + serf_bucket_request_set_CL(pRequestBucket, SERF_UNKNOWN_LENGTH); + } + else + { + // Deactivate chunked encoding by setting the length. + serf_bucket_request_set_CL(pRequestBucket, nLength); + } + } +} + + +void SerfRequestProcessorImpl::setRequestHeaders( serf_bucket_t* inoutSerfHeaderBucket ) +{ + bool bHasUserAgent( false ); + + for ( const auto& rHeader : mrRequestHeaders ) + { + const OString aHeader = OUStringToOString( rHeader.first, RTL_TEXTENCODING_UTF8 ); + const OString aValue = OUStringToOString( rHeader.second, RTL_TEXTENCODING_UTF8 ); + + SAL_INFO("ucb.ucp.webdav", "Request Header - \"" << aHeader << ": " << aValue << "\""); + if ( !bHasUserAgent ) + bHasUserAgent = rHeader.first == "User-Agent"; + + serf_bucket_headers_setc( inoutSerfHeaderBucket, + aHeader.getStr(), + aValue.getStr() ); + } + + if ( !bHasUserAgent ) + { + serf_bucket_headers_set( inoutSerfHeaderBucket, + "User-Agent", "LibreOffice" ); + } + + serf_bucket_headers_set( inoutSerfHeaderBucket, "Accept-Encoding", "gzip"); +} + +bool SerfRequestProcessorImpl::processSerfResponseBucket( serf_request_t * /*inSerfRequest*/, + serf_bucket_t * inSerfResponseBucket, + apr_pool_t * /*inAprPool*/, + apr_status_t & outStatus ) +{ + const char* data; + apr_size_t len; + + while (true) { + outStatus = serf_bucket_read(inSerfResponseBucket, 8096, &data, &len); + if (SERF_BUCKET_READ_ERROR(outStatus)) + { + return true; + } + + if ( len > 0 ) + { + processChunkOfResponseData( data, len ); + } + + /* are we done yet? */ + if (APR_STATUS_IS_EOF(outStatus)) + { + handleEndOfResponseData( inSerfResponseBucket ); + + outStatus = APR_EOF; + return true; + } + + /* have we drained the response so far? */ + if ( APR_STATUS_IS_EAGAIN( outStatus ) ) + { + return false; + } + } + + /* NOTREACHED */ + return true; +} + +} // namespace http_dav_ucp + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/ucb/source/ucp/webdav/SerfRequestProcessorImpl.hxx b/ucb/source/ucp/webdav/SerfRequestProcessorImpl.hxx new file mode 100644 index 000000000..3909dca5a --- /dev/null +++ b/ucb/source/ucp/webdav/SerfRequestProcessorImpl.hxx @@ -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 . + */ + +#ifndef INCLUDED_UCB_SOURCE_UCP_WEBDAV_SERFREQUESTPROCESSORIMPL_HXX +#define INCLUDED_UCB_SOURCE_UCP_WEBDAV_SERFREQUESTPROCESSORIMPL_HXX + +#include <serf.h> + +#include <sal/types.h> +#include "DAVRequestEnvironment.hxx" + +namespace http_dav_ucp +{ + +class SerfRequestProcessorImpl +{ +public: + SerfRequestProcessorImpl( const char* inPath, + const DAVRequestHeaders& inRequestHeaders ); + + virtual ~SerfRequestProcessorImpl(); + + /*pure*/ virtual + serf_bucket_t * createSerfRequestBucket( serf_request_t * inSerfRequest ) = 0; + + bool processSerfResponseBucket( serf_request_t * inSerfRequest, + + serf_bucket_t * inSerfResponseBucket, + apr_pool_t * inAprPool, + apr_status_t & outStatus ); + + void activateChunkedEncoding(); + + /** Turn chunked encoding on or off, depending on the result of + useChunkedEncoding(). + */ + void handleChunkedEncoding ( + serf_bucket_t* pRequestBucket, + apr_int64_t nLength) const; + +protected: + void setRequestHeaders( serf_bucket_t* inoutSerfHeaderBucket ); + + /*pure*/ virtual + void processChunkOfResponseData( const char* data, apr_size_t len ) = 0; + + /*pure*/ virtual + void handleEndOfResponseData( serf_bucket_t * inSerfResponseBucket ) = 0; + + const char* getPathStr() const; + bool useChunkedEncoding() const; + +private: + const char* mPathStr; + const DAVRequestHeaders& mrRequestHeaders; + bool mbUseChunkedEncoding; +}; + +} // namespace http_dav_ucp + +#endif // INCLUDED_UCB_SOURCE_UCP_WEBDAV_SERFREQUESTPROCESSORIMPL_HXX + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/ucb/source/ucp/webdav/SerfSession.cxx b/ucb/source/ucp/webdav/SerfSession.cxx new file mode 100644 index 000000000..8f7a7e38c --- /dev/null +++ b/ucb/source/ucp/webdav/SerfSession.cxx @@ -0,0 +1,1489 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this 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 <vector> +#include <string.h> +#include <sal/log.hxx> +#include <comphelper/processfactory.hxx> +#include <comphelper/sequence.hxx> +#include <ucbhelper/simplecertificatevalidationrequest.hxx> + +#include "AprEnv.hxx" +#include <apr_strings.h> + +#include "DAVAuthListener.hxx" +#include "SerfSession.hxx" +#include "SerfUri.hxx" +#include "SerfRequestProcessor.hxx" +#include "SerfCallbacks.hxx" +#include "SerfInputStream.hxx" + +#include <com/sun/star/xml/crypto/SEInitializer.hpp> +#include <com/sun/star/xml/crypto/XSecurityEnvironment.hpp> +#include <com/sun/star/security/XCertificate.hpp> +#include <com/sun/star/security/CertificateValidity.hpp> +#include <com/sun/star/security/CertificateContainerStatus.hpp> +#include <com/sun/star/security/CertificateContainer.hpp> +#include <com/sun/star/security/XCertificateContainer.hpp> +#include <com/sun/star/security/CertAltNameEntry.hpp> +#include <com/sun/star/security/XSanExtension.hpp> +#include <com/sun/star/io/NotConnectedException.hpp> +#include <com/sun/star/io/BufferSizeExceededException.hpp> +#include <com/sun/star/io/IOException.hpp> +#define OID_SUBJECT_ALTERNATIVE_NAME "2.5.29.17" + +#include <com/sun/star/ucb/Lock.hpp> +#include <com/sun/star/xml/crypto/XSEInitializer.hpp> + +using namespace com::sun::star; +using namespace http_dav_ucp; + +// Constructor + +SerfSession::SerfSession( + const rtl::Reference< DAVSessionFactory > & rSessionFactory, + const OUString& inUri, + const ucbhelper::InternetProxyDecider & rProxyDecider ) + : DAVSession( rSessionFactory ) + , m_aMutex() + , m_aUri( inUri ) + , m_aProxyName() + , m_nProxyPort( 0 ) + , m_pSerfConnection( nullptr ) + , m_pSerfContext( nullptr ) + , m_bIsHeadRequestInProgress( false ) + , m_bUseChunkedEncoding( false ) + , m_bNoOfTransferEncodingSwitches( 0 ) + , m_rProxyDecider( rProxyDecider ) + , m_aEnv() +{ + m_pSerfContext = serf_context_create( getAprPool() ); + + m_pSerfBucket_Alloc = serf_bucket_allocator_create( getAprPool(), nullptr, nullptr ); +} + + +// Destructor + +SerfSession::~SerfSession( ) +{ + if ( m_pSerfConnection ) + { + serf_connection_close( m_pSerfConnection ); + m_pSerfConnection = nullptr; + } +} + + +void SerfSession::Init( const DAVRequestEnvironment & rEnv ) +{ + osl::Guard< osl::Mutex > theGuard( m_aMutex ); + m_aEnv = rEnv; + Init(); +} + + +void SerfSession::Init() +{ + osl::Guard< osl::Mutex > theGuard( m_aMutex ); + + bool bCreateNewSession = false; + + if ( m_pSerfConnection == nullptr ) + { + const ucbhelper::InternetProxyServer & rProxyCfg = getProxySettings(); + + m_aProxyName = rProxyCfg.aName; + m_nProxyPort = rProxyCfg.nPort; + + // Not yet initialized. Create new session. + bCreateNewSession = true; + } + else + { + const ucbhelper::InternetProxyServer & rProxyCfg = getProxySettings(); + + if ( ( rProxyCfg.aName != m_aProxyName ) + || ( rProxyCfg.nPort != m_nProxyPort ) ) + { + m_aProxyName = rProxyCfg.aName; + m_nProxyPort = rProxyCfg.nPort; + + // new session needed, destroy old first + serf_connection_close( m_pSerfConnection ); + m_pSerfConnection = nullptr; + bCreateNewSession = true; + } + } + + if ( bCreateNewSession ) + { + // TODO - close_connection callback + apr_status_t status = serf_connection_create2( &m_pSerfConnection, + m_pSerfContext, + m_aUri.getAprUri(), + Serf_ConnectSetup, this, + nullptr /* close connection callback */, nullptr /* close connection baton */, + getAprPool() ); + + if ( m_pSerfConnection == nullptr ||status != APR_SUCCESS ) + { + throw DAVException( DAVException::DAV_SESSION_CREATE, + SerfUri::makeConnectionEndPointString( m_aUri.GetHost(), m_aUri.GetPort() ) ); + } + + // Register the session with the lock store +// m_aSerfLockStore.registerSession( m_pSerfConnection ); + + if ( m_aProxyName.getLength() ) + { + apr_sockaddr_t *proxy_address = nullptr; + status = apr_sockaddr_info_get( &proxy_address, + OUStringToOString( m_aProxyName, RTL_TEXTENCODING_UTF8 ).getStr(), + APR_UNSPEC, + static_cast<apr_port_t>(m_nProxyPort), + 0, getAprPool() ); + + if ( status != APR_SUCCESS ) + { + throw DAVException( DAVException::DAV_SESSION_CREATE, + SerfUri::makeConnectionEndPointString( m_aUri.GetHost(), m_aUri.GetPort() ) ); + } + + serf_config_proxy( m_pSerfContext, proxy_address ); + } + + + serf_config_credentials_callback( m_pSerfContext, Serf_Credentials ); + + m_bUseChunkedEncoding = isSSLNeeded(); + } +} + +apr_pool_t* SerfSession::getAprPool() +{ + return apr_environment::AprEnv::getAprEnv()->getAprPool(); +} + +serf_bucket_alloc_t* SerfSession::getSerfBktAlloc() +{ + return m_pSerfBucket_Alloc; +} + +serf_context_t* SerfSession::getSerfContext() +{ + return m_pSerfContext; +} + +serf_connection_t* SerfSession::getSerfConnection() +{ + return m_pSerfConnection; +} + +bool SerfSession::isHeadRequestInProgress() +{ + return m_bIsHeadRequestInProgress; +} + +bool SerfSession::isSSLNeeded() +{ + return m_aUri.GetScheme().equalsIgnoreAsciiCase( "https" ); +} + +char* SerfSession::getHostinfo() +{ + return m_aUri.getAprUri().hostinfo; +} + + +// virtual +bool SerfSession::CanUse( const OUString & inUri ) +{ + try + { + SerfUri theUri( inUri ); + if ( ( theUri.GetPort() == m_aUri.GetPort() ) && + ( theUri.GetHost() == m_aUri.GetHost() ) && + ( theUri.GetScheme() == m_aUri.GetScheme() ) ) + { + return true; + } + } + catch ( DAVException const & ) + { + return false; + } + return false; +} + + +// virtual +bool SerfSession::UsesProxy() +{ + Init(); + return ( m_aProxyName.getLength() > 0 ); +} + +apr_status_t SerfSession::setupSerfConnection( apr_socket_t * inAprSocket, + serf_bucket_t **outSerfInputBucket, + serf_bucket_t **outSerfOutputBucket, + apr_pool_t* /*inAprPool*/ ) +{ + serf_bucket_t *tmpInputBkt; + tmpInputBkt = serf_context_bucket_socket_create( getSerfContext(), + inAprSocket, + getSerfBktAlloc() ); + + if ( isSSLNeeded() ) + { + tmpInputBkt = serf_bucket_ssl_decrypt_create( tmpInputBkt, + nullptr, + getSerfBktAlloc() ); + /** Set the callback that is called to authenticate the + certificate (chain). + */ + serf_ssl_server_cert_chain_callback_set( + serf_bucket_ssl_decrypt_context_get(tmpInputBkt), + nullptr, + Serf_CertificateChainValidation, + this); + serf_ssl_set_hostname( serf_bucket_ssl_decrypt_context_get( tmpInputBkt ), + getHostinfo() ); + + *outSerfOutputBucket = serf_bucket_ssl_encrypt_create( *outSerfOutputBucket, + serf_bucket_ssl_decrypt_context_get( tmpInputBkt ), + getSerfBktAlloc() ); + } + + *outSerfInputBucket = tmpInputBkt; + + return APR_SUCCESS; +} + +apr_status_t SerfSession::provideSerfCredentials( bool bGiveProvidedCredentialsASecondTry, + char ** outUsername, + char ** outPassword, + serf_request_t * /*inRequest*/, + int /*inCode*/, + const char *inAuthProtocol, + const char *inRealm, + apr_pool_t *inAprPool ) +{ + DAVAuthListener * pListener = getRequestEnvironment().m_xAuthListener.get(); + if ( !pListener ) + { + // abort + return SERF_ERROR_AUTHN_FAILED; + } + + OUString theUserName; + OUString thePassWord; + try + { + SerfUri uri( getRequestEnvironment().m_aRequestURI ); + OUString aUserInfo( uri.GetUserInfo() ); + if ( aUserInfo.getLength() ) + { + sal_Int32 nPos = aUserInfo.indexOf( '@' ); + if ( nPos == -1 ) + { + theUserName = aUserInfo; + } + else + { + theUserName = aUserInfo.copy( 0, nPos ); + thePassWord = aUserInfo.copy( nPos + 1 ); + } + } + } + catch ( DAVException const & ) + { + // abort + return SERF_ERROR_AUTHN_FAILED; + } + + const bool bCanUseSystemCreds = ( ( strcasecmp( inAuthProtocol, "NTLM" ) == 0 ) || + ( strcasecmp( inAuthProtocol, "Negotiate" ) == 0 ) ); + + int theRetVal = pListener->authenticate( OUString::createFromAscii( inRealm ), + getHostName(), + theUserName, + thePassWord, + bCanUseSystemCreds, + bGiveProvidedCredentialsASecondTry ); + + if ( theRetVal == 0 ) + { + *outUsername = apr_pstrdup( inAprPool, OUStringToOString( theUserName, RTL_TEXTENCODING_UTF8 ).getStr() ); + *outPassword = apr_pstrdup( inAprPool, OUStringToOString( thePassWord, RTL_TEXTENCODING_UTF8 ).getStr() ); + } + + return theRetVal != 0 ? SERF_ERROR_AUTHN_FAILED : APR_SUCCESS; +} + +apr_status_t SerfSession::verifySerfCertificateChain ( + int, + const serf_ssl_certificate_t * const * pCertificateChainBase64Encoded, + int nCertificateChainLength) +{ + // Check arguments. + if (pCertificateChainBase64Encoded == nullptr || nCertificateChainLength<=0) + { + assert(pCertificateChainBase64Encoded != nullptr); + assert(nCertificateChainLength>0); + return SERF_SSL_CERT_UNKNOWN_FAILURE; + } + + // When called from SerfLockStore::~SerfLockStore(), + // css::xml::crypto::SEInitializer::create() will fail + // but we want to send unlock commands anyway, + // so just ignore certificates and return here. + if (apr_environment::AprEnv::getAprEnv()->getSerfLockStore()->finishing()) + return APR_SUCCESS; + + // Create some crypto objects to decode and handle the base64 + // encoded certificate chain. + uno::Reference< security::XCertificateContainer > xCertificateContainer; + uno::Reference< xml::crypto::XXMLSecurityContext > xSecurityContext; + uno::Reference< xml::crypto::XSecurityEnvironment > xSecurityEnv; + try + { + css::uno::Reference< css::uno::XComponentContext > xContext = + ::comphelper::getProcessComponentContext(); + // Create a certificate container. + xCertificateContainer = security::CertificateContainer::create( xContext ); + + css::uno::Reference< css::xml::crypto::XSEInitializer > xSEInitializer = + css::xml::crypto::SEInitializer::create( xContext ); + + xSecurityContext = xSEInitializer->createSecurityContext( OUString() ); + if (xSecurityContext.is()) + xSecurityEnv = xSecurityContext->getSecurityEnvironment(); + + if ( ! xSecurityContext.is() || ! xSecurityEnv.is()) + { + // Do we have to dispose xSEInitializer or xCertificateContainer? + return SERF_SSL_CERT_UNKNOWN_FAILURE; + } + } + catch ( uno::Exception const &) + { + return SERF_SSL_CERT_UNKNOWN_FAILURE; + } + + // Decode the server certificate. + const char* sBase64EncodedServerCertificate ( + serf_ssl_cert_export( + pCertificateChainBase64Encoded[0], + getAprPool())); + uno::Reference< security::XCertificate > xServerCertificate( + xSecurityEnv->createCertificateFromAscii( + OUString::createFromAscii(sBase64EncodedServerCertificate))); + if ( ! xServerCertificate.is()) + return SERF_SSL_CERT_UNKNOWN_FAILURE; + + // Get the subject from the server certificate. + OUString sServerCertificateSubject (xServerCertificate->getSubjectName()); + sal_Int32 nIndex = 0; + while (nIndex >= 0) + { + const OUString sToken (sServerCertificateSubject.getToken(0, ',', nIndex)); + if (sToken.startsWith("CN=")) + { + sServerCertificateSubject = sToken.copy(3); + break; + } + else if (sToken.startsWith(" CN=")) + { + sServerCertificateSubject = sToken.copy(4); + break; + } + } + + // When the certificate container already contains a (trusted) + // entry for the server then we do not have to authenticate any + // certificate. + const security::CertificateContainerStatus eStatus ( + xCertificateContainer->hasCertificate( + getHostName(), sServerCertificateSubject ) ); + if (eStatus != security::CertificateContainerStatus_NOCERT) + { + return eStatus == security::CertificateContainerStatus_TRUSTED + ? APR_SUCCESS + : SERF_SSL_CERT_UNKNOWN_FAILURE; + } + + // The shortcut failed, so try to verify the whole chain. This is + // done outside the isDomainMatch() block because the result is + // used by the interaction handler. + std::vector< uno::Reference< security::XCertificate > > aChain; + for (nIndex = 1; nIndex < nCertificateChainLength; ++nIndex) + { + const char* sBase64EncodedCertificate ( + serf_ssl_cert_export( + pCertificateChainBase64Encoded[nIndex], + getAprPool())); + uno::Reference< security::XCertificate > xCertificate( + xSecurityEnv->createCertificateFromAscii( + OUString::createFromAscii(sBase64EncodedCertificate))); + if ( ! xCertificate.is()) + return SERF_SSL_CERT_UNKNOWN_FAILURE; + aChain.push_back(xCertificate); + } + const sal_Int64 nVerificationResult (xSecurityEnv->verifyCertificate( + xServerCertificate, + ::comphelper::containerToSequence(aChain))); + + // When the certificate matches the host name then we can use the + // result of the verification. + bool bHostnameMatchesCertHostnames = false; + { + uno::Sequence< uno::Reference< security::XCertificateExtension > > extensions = xServerCertificate->getExtensions(); + uno::Sequence< security::CertAltNameEntry > altNames; + for (sal_Int32 i = 0 ; i < extensions.getLength(); ++i) + { + uno::Reference< security::XCertificateExtension >element = extensions[i]; + + const OString aId ( reinterpret_cast<const char *>(const_cast<const signed char *>(element->getExtensionId().getArray())), element->getExtensionId().getLength()); + if ( aId.equals( OID_SUBJECT_ALTERNATIVE_NAME ) ) + { + uno::Reference< security::XSanExtension > sanExtension ( element, uno::UNO_QUERY ); + altNames = sanExtension->getAlternativeNames(); + break; + } + } + + uno::Sequence< OUString > certHostNames(altNames.getLength() + 1); + certHostNames[0] = sServerCertificateSubject; + for( int n = 0; n < altNames.getLength(); ++n ) + { + if (altNames[n].Type == security::ExtAltNameType_DNS_NAME) + { + altNames[n].Value >>= certHostNames[n+1]; + } + } + + for ( int i = 0; i < certHostNames.getLength() && !bHostnameMatchesCertHostnames; ++i ) + { + bHostnameMatchesCertHostnames = isDomainMatch( certHostNames[i] ); + } + + } + if ( bHostnameMatchesCertHostnames ) + { + + if (nVerificationResult == 0) + { + // Certificate (chain) is valid. + xCertificateContainer->addCertificate(getHostName(), sServerCertificateSubject, true); + return APR_SUCCESS; + } + else if ((nVerificationResult & security::CertificateValidity::CHAIN_INCOMPLETE) != 0) + { + // We do not have enough information for verification, + // neither automatically (as we just discovered) nor + // manually (so there is no point in showing any dialog.) + return SERF_SSL_CERT_UNKNOWN_FAILURE; + } + else if ((nVerificationResult & + (security::CertificateValidity::INVALID | security::CertificateValidity::REVOKED)) != 0) + { + // Certificate (chain) is invalid. + xCertificateContainer->addCertificate(getHostName(), sServerCertificateSubject, false); + return SERF_SSL_CERT_UNKNOWN_FAILURE; + } + else + { + // For all other we have to ask the user. + } + } + + // We have not been able to automatically verify (or falsify) the + // certificate chain. To resolve this we have to ask the user. + const uno::Reference< ucb::XCommandEnvironment > xEnv( getRequestEnvironment().m_xEnv ); + if ( xEnv.is() ) + { + uno::Reference< task::XInteractionHandler > xIH( xEnv->getInteractionHandler() ); + if ( xIH.is() ) + { + rtl::Reference< ucbhelper::SimpleCertificateValidationRequest > + xRequest( new ucbhelper::SimpleCertificateValidationRequest( + static_cast<sal_Int32>(nVerificationResult), xServerCertificate, getHostName() ) ); + xIH->handle( xRequest.get() ); + + rtl::Reference< ucbhelper::InteractionContinuation > xSelection + = xRequest->getSelection(); + + if ( xSelection.is() ) + { + uno::Reference< task::XInteractionApprove > xApprove( xSelection.get(), uno::UNO_QUERY ); + if ( xApprove.is() ) + { + xCertificateContainer->addCertificate( getHostName(), sServerCertificateSubject, true ); + return APR_SUCCESS; + } + else + { + // Don't trust cert + xCertificateContainer->addCertificate( getHostName(), sServerCertificateSubject, false ); + return SERF_SSL_CERT_UNKNOWN_FAILURE; + } + } + } + else + { + // Don't trust cert + xCertificateContainer->addCertificate( getHostName(), sServerCertificateSubject, false ); + return SERF_SSL_CERT_UNKNOWN_FAILURE; + } + } + + return SERF_SSL_CERT_UNKNOWN_FAILURE; +} + +serf_bucket_t* SerfSession::acceptSerfResponse( serf_request_t * inSerfRequest, + serf_bucket_t * inSerfStreamBucket, + apr_pool_t* /*inAprPool*/ ) +{ + // get the per-request bucket allocator + serf_bucket_alloc_t* SerfBktAlloc = serf_request_get_alloc( inSerfRequest ); + + // create a barrier bucket so the response doesn't eat us! + serf_bucket_t *responseBkt = serf_bucket_barrier_create( inSerfStreamBucket, + SerfBktAlloc ); + + // create response bucket + responseBkt = serf_bucket_response_create( responseBkt, + SerfBktAlloc ); + + if ( isHeadRequestInProgress() ) + { + // advise the response bucket that this was from a HEAD request and that it should not expect to see a response body. + serf_bucket_response_set_head( responseBkt ); + } + + return responseBkt; +} + +SerfRequestProcessor* SerfSession::createReqProc( const OUString & inPath ) +{ + return new SerfRequestProcessor( *this, + inPath, + m_bUseChunkedEncoding ); +} + + +// PROPFIND - allprop & named + +void SerfSession::PROPFIND( const OUString & inPath, + const Depth inDepth, + const std::vector< OUString > & inPropNames, + std::vector< DAVResource > & ioResources, + const DAVRequestEnvironment & rEnv ) +{ + osl::Guard< osl::Mutex > theGuard( m_aMutex ); + + Init( rEnv ); + + apr_status_t status = APR_SUCCESS; + std::shared_ptr<SerfRequestProcessor> aReqProc( createReqProc( inPath ) ); + aReqProc->processPropFind( inDepth, + inPropNames, + ioResources, + status ); + + if ( status == APR_SUCCESS && + aReqProc->mpDAVException == nullptr && + ioResources.empty() ) + { + m_aEnv = DAVRequestEnvironment(); + throw DAVException( DAVException::DAV_HTTP_ERROR, inPath, APR_EGENERAL ); + } + HandleError( aReqProc ); +} + + +// PROPFIND - propnames + +void SerfSession::PROPFIND( const OUString & inPath, + const Depth inDepth, + std::vector< DAVResourceInfo > & ioResInfo, + const DAVRequestEnvironment & rEnv ) +{ + osl::Guard< osl::Mutex > theGuard( m_aMutex ); + + Init( rEnv ); + + apr_status_t status = APR_SUCCESS; + std::shared_ptr<SerfRequestProcessor> aReqProc( createReqProc( inPath ) ); + aReqProc->processPropFind( inDepth, + ioResInfo, + status ); + + if ( status == APR_SUCCESS && + aReqProc->mpDAVException == nullptr && + ioResInfo.empty() ) + { + m_aEnv = DAVRequestEnvironment(); + throw DAVException( DAVException::DAV_HTTP_ERROR, inPath, APR_EGENERAL ); + } + HandleError( aReqProc ); +} + + +// PROPPATCH + +void SerfSession::PROPPATCH( const OUString & inPath, + const std::vector< ProppatchValue > & inValues, + const DAVRequestEnvironment & rEnv ) +{ + osl::Guard< osl::Mutex > theGuard( m_aMutex ); + + Init( rEnv ); + + apr_status_t status = APR_SUCCESS; + std::shared_ptr<SerfRequestProcessor> aReqProc( createReqProc( inPath ) ); + aReqProc->processPropPatch( inValues, + status ); + + HandleError( aReqProc ); +} + + +// HEAD + +void SerfSession::HEAD( const OUString & inPath, + const std::vector< OUString > & inHeaderNames, + DAVResource & ioResource, + const DAVRequestEnvironment & rEnv ) +{ + osl::Guard< osl::Mutex > theGuard( m_aMutex ); + + Init( rEnv ); + + m_bIsHeadRequestInProgress = true; + + std::shared_ptr<SerfRequestProcessor> aReqProc( createReqProc( inPath ) ); + ioResource.uri = inPath; + ioResource.properties.clear(); + apr_status_t status = APR_SUCCESS; + aReqProc->processHead( inHeaderNames, + ioResource, + status ); + + m_bIsHeadRequestInProgress = false; + + HandleError( aReqProc ); +} + + +// GET + +uno::Reference< io::XInputStream > +SerfSession::GET( const OUString & inPath, + const DAVRequestEnvironment & rEnv ) +{ + osl::Guard< osl::Mutex > theGuard( m_aMutex ); + + Init( rEnv ); + + rtl::Reference< SerfInputStream > xInputStream( new SerfInputStream ); + apr_status_t status = APR_SUCCESS; + std::shared_ptr<SerfRequestProcessor> aReqProc( createReqProc( inPath ) ); + aReqProc->processGet( xInputStream, + status ); + + HandleError( aReqProc ); + + return uno::Reference< io::XInputStream >( xInputStream.get() ); +} + + +// GET + +void SerfSession::GET( const OUString & inPath, + uno::Reference< io::XOutputStream > & ioOutputStream, + const DAVRequestEnvironment & rEnv ) +{ + osl::Guard< osl::Mutex > theGuard( m_aMutex ); + + Init( rEnv ); + + apr_status_t status = APR_SUCCESS; + std::shared_ptr<SerfRequestProcessor> aReqProc( createReqProc( inPath ) ); + aReqProc->processGet( ioOutputStream, + status ); + + HandleError( aReqProc ); +} + + +// GET + +uno::Reference< io::XInputStream > +SerfSession::GET( const OUString & inPath, + const std::vector< OUString > & inHeaderNames, + DAVResource & ioResource, + const DAVRequestEnvironment & rEnv ) +{ + osl::Guard< osl::Mutex > theGuard( m_aMutex ); + + Init( rEnv ); + + std::shared_ptr<SerfRequestProcessor> aReqProc( createReqProc( inPath ) ); + rtl::Reference< SerfInputStream > xInputStream( new SerfInputStream ); + ioResource.uri = inPath; + ioResource.properties.clear(); + apr_status_t status = APR_SUCCESS; + aReqProc->processGet( xInputStream, + inHeaderNames, + ioResource, + status ); + + HandleError( aReqProc ); + + return uno::Reference< io::XInputStream >( xInputStream.get() ); +} + + +// GET + +void SerfSession::GET( const OUString & inPath, + uno::Reference< io::XOutputStream > & ioOutputStream, + const std::vector< OUString > & inHeaderNames, + DAVResource & ioResource, + const DAVRequestEnvironment & rEnv ) +{ + osl::Guard< osl::Mutex > theGuard( m_aMutex ); + + Init( rEnv ); + + std::shared_ptr<SerfRequestProcessor> aReqProc( createReqProc( inPath ) ); + ioResource.uri = inPath; + ioResource.properties.clear(); + apr_status_t status = APR_SUCCESS; + aReqProc->processGet( ioOutputStream, + inHeaderNames, + ioResource, + status ); + + HandleError( aReqProc ); +} + + +// PUT + +void SerfSession::PUT( const OUString & inPath, + const uno::Reference< io::XInputStream > & inInputStream, + const DAVRequestEnvironment & rEnv ) +{ + osl::Guard< osl::Mutex > theGuard( m_aMutex ); + + Init( rEnv ); + + std::shared_ptr<SerfRequestProcessor> aReqProc( createReqProc( inPath ) ); + uno::Sequence< sal_Int8 > aDataToSend; + if ( !getDataFromInputStream( inInputStream, aDataToSend, false ) ) + throw DAVException( DAVException::DAV_INVALID_ARG ); + apr_status_t status = APR_SUCCESS; + aReqProc->processPut( reinterpret_cast< const char * >( aDataToSend.getConstArray() ), + aDataToSend.getLength(), + status ); + + HandleError( aReqProc ); +} + + +// POST + +uno::Reference< io::XInputStream > +SerfSession::POST( const OUString & inPath, + const OUString & rContentType, + const OUString & rReferer, + const uno::Reference< io::XInputStream > & inInputStream, + const DAVRequestEnvironment & rEnv ) +{ + osl::Guard< osl::Mutex > theGuard( m_aMutex ); + + uno::Sequence< sal_Int8 > aDataToSend; + if ( !getDataFromInputStream( inInputStream, aDataToSend, true ) ) + { + throw DAVException( DAVException::DAV_INVALID_ARG ); + } + + Init( rEnv ); + + std::shared_ptr<SerfRequestProcessor> aReqProc( createReqProc( inPath ) ); + rtl::Reference< SerfInputStream > xInputStream( new SerfInputStream ); + apr_status_t status = APR_SUCCESS; + aReqProc->processPost( reinterpret_cast< const char * >( aDataToSend.getConstArray() ), + aDataToSend.getLength(), + rContentType, + rReferer, + xInputStream, + status ); + + HandleError( aReqProc ); + return uno::Reference< io::XInputStream >( xInputStream.get() ); +} + + +// POST + +void SerfSession::POST( const OUString & inPath, + const OUString & rContentType, + const OUString & rReferer, + const uno::Reference< io::XInputStream > & inInputStream, + uno::Reference< io::XOutputStream > & oOutputStream, + const DAVRequestEnvironment & rEnv ) +{ + osl::Guard< osl::Mutex > theGuard( m_aMutex ); + + uno::Sequence< sal_Int8 > aDataToSend; + if ( !getDataFromInputStream( inInputStream, aDataToSend, true ) ) + { + throw DAVException( DAVException::DAV_INVALID_ARG ); + } + + Init( rEnv ); + + std::shared_ptr<SerfRequestProcessor> aReqProc( createReqProc( inPath ) ); + apr_status_t status = APR_SUCCESS; + aReqProc->processPost( reinterpret_cast< const char * >( aDataToSend.getConstArray() ), + aDataToSend.getLength(), + rContentType, + rReferer, + oOutputStream, + status ); + + HandleError( aReqProc ); +} + + +// MKCOL + +void SerfSession::MKCOL( const OUString & inPath, + const DAVRequestEnvironment & rEnv ) +{ + osl::Guard< osl::Mutex > theGuard( m_aMutex ); + + Init( rEnv ); + + std::shared_ptr<SerfRequestProcessor> aReqProc( createReqProc( inPath ) ); + apr_status_t status = APR_SUCCESS; + aReqProc->processMkCol( status ); + + HandleError( aReqProc ); +} + + +// COPY + +void SerfSession::COPY( const OUString & inSourceURL, + const OUString & inDestinationURL, + const DAVRequestEnvironment & rEnv, + bool inOverWrite ) +{ + osl::Guard< osl::Mutex > theGuard( m_aMutex ); + + Init( rEnv ); + + SerfUri theSourceUri( inSourceURL ); + std::shared_ptr<SerfRequestProcessor> aReqProc( createReqProc( theSourceUri.GetPath() ) ); + apr_status_t status = APR_SUCCESS; + aReqProc->processCopy( inDestinationURL, inOverWrite, status ); + + HandleError( aReqProc ); +} + + +// MOVE + +void SerfSession::MOVE( const OUString & inSourceURL, + const OUString & inDestinationURL, + const DAVRequestEnvironment & rEnv, + bool inOverWrite ) +{ + osl::Guard< osl::Mutex > theGuard( m_aMutex ); + + Init( rEnv ); + + SerfUri theSourceUri( inSourceURL ); + std::shared_ptr<SerfRequestProcessor> aReqProc( createReqProc( theSourceUri.GetPath() ) ); + apr_status_t status = APR_SUCCESS; + aReqProc->processMove( inDestinationURL, inOverWrite, status ); + + HandleError( aReqProc ); +} + + +// DESTROY + +void SerfSession::DESTROY( const OUString & inPath, + const DAVRequestEnvironment & rEnv ) +{ + osl::Guard< osl::Mutex > theGuard( m_aMutex ); + + Init( rEnv ); + + std::shared_ptr<SerfRequestProcessor> aReqProc( createReqProc( inPath ) ); + apr_status_t status = APR_SUCCESS; + aReqProc->processDelete( status ); + + HandleError( aReqProc ); +} + + +/* +namespace +{ + sal_Int32 lastChanceToSendRefreshRequest( TimeValue const & rStart, + int timeout ) + { + TimeValue aEnd; + osl_getSystemTime( &aEnd ); + + // Try to estimate a safe absolute time for sending the + // lock refresh request. + sal_Int32 lastChanceToSendRefreshRequest = -1; + if ( timeout != NE_TIMEOUT_INFINITE ) + { + sal_Int32 calltime = aEnd.Seconds - rStart.Seconds; + if ( calltime <= timeout ) + { + lastChanceToSendRefreshRequest + = aEnd.Seconds + timeout - calltime; + } + else + { + SAL_INFO("ucb.ucp.webdav", "No chance to refresh lock before timeout!" ); + } + } + return lastChanceToSendRefreshRequest; + } + +} // namespace +*/ + +// LOCK (set new lock) + +void SerfSession::LOCK( const OUString & inPath, + ucb::Lock & rLock, + const DAVRequestEnvironment & rEnv ) +{ + osl::Guard< osl::Mutex > theGuard( m_aMutex ); + + Init( rEnv ); + + std::shared_ptr<SerfRequestProcessor> aReqProc( createReqProc( inPath ) ); + aReqProc->processLock( rLock ); + + HandleError( aReqProc ); +} + + +// LOCK (refresh existing lock) + +sal_Int64 SerfSession::LOCK( const OUString & /*inPath*/, + sal_Int64 nTimeout, + const DAVRequestEnvironment & /*rEnv*/ ) +{ + osl::Guard< osl::Mutex > theGuard( m_aMutex ); + + return nTimeout; + /* + // Try to get the neon lock from lock store + SerfLock * theLock + = m_aSerfLockStore.findByUri( makeAbsoluteURL( inPath ) ); + if ( !theLock ) + throw DAVException( DAVException::DAV_NOT_LOCKED ); + + Init( rEnv ); + + // refresh existing lock. + theLock->timeout = static_cast< long >( nTimeout ); + + TimeValue startCall; + osl_getSystemTime( &startCall ); + + int theRetVal = ne_lock_refresh( m_pHttpSession, theLock ); + + if ( theRetVal == NE_OK ) + { + m_aSerfLockStore.updateLock( theLock, + lastChanceToSendRefreshRequest( + startCall, theLock->timeout ) ); + } + + HandleError( theRetVal, inPath, rEnv ); + + return theLock->timeout; + */ +} + + +// LOCK (refresh existing lock) + +bool SerfSession::LOCK( const OUString& rLock, + sal_Int32 *plastChanceToSendRefreshRequest ) +{ + osl::Guard< osl::Mutex > theGuard( m_aMutex ); + + std::shared_ptr<SerfRequestProcessor> aReqProc( createReqProc( rLock ) ); + aReqProc->processLock( ucb::Lock(), plastChanceToSendRefreshRequest ); + + try + { + HandleError( aReqProc ); + SAL_INFO("ucb.ucp.webdav", "Refreshing LOCK of " << rLock << " succeeded." ); + return true; + } + catch(...) + { + SAL_INFO("ucb.ucp.webdav", "Refreshing LOCK of " << rLock << " failed!" ); + return false; + } +} + + +// UNLOCK + +void SerfSession::UNLOCK( const OUString & inPath, + const DAVRequestEnvironment & rEnv ) +{ + osl::Guard< osl::Mutex > theGuard( m_aMutex ); + + Init( rEnv ); + + std::shared_ptr<SerfRequestProcessor> aReqProc( createReqProc( inPath ) ); + aReqProc->processUnlock(); + + try + { + HandleError( aReqProc ); + SAL_INFO("ucb.ucp.webdav", "UNLOCK of " << inPath << " succeeded." ); + apr_environment::AprEnv::getAprEnv()->getSerfLockStore()->removeLock( inPath ); + } + catch(...) + { + SAL_INFO("ucb.ucp.webdav", "UNLOCK of " << inPath << " failed!" ); + } +} + + +// UNLOCK + +void SerfSession::UNLOCK( const OUString& rLock ) +{ + osl::Guard< osl::Mutex > theGuard( m_aMutex ); + + std::shared_ptr<SerfRequestProcessor> aReqProc( createReqProc( rLock ) ); + aReqProc->processUnlock(); + + try + { + HandleError( aReqProc ); + SAL_INFO("ucb.ucp.webdav", "UNLOCK of " << rLock << " succeeded." ); + } + catch(...) + { + SAL_INFO("ucb.ucp.webdav", "UNLOCK of " << rLock << " failed!" ); + } +} + + +void SerfSession::abort() +{ + // 11.11.09 (tkr): The following code lines causing crashes if + // closing a ongoing connection. It turned out that this existing + // solution doesn't work in multi-threading environments. + // So I disabled them in 3.2. . Issue #73893# should fix it in OOo 3.3. + //if ( m_pHttpSession ) + // ne_close_connection( m_pHttpSession ); +} + + +ucbhelper::InternetProxyServer SerfSession::getProxySettings() const +{ + if ( m_aUri.GetScheme() == "http" || m_aUri.GetScheme() == "https" ) + { + return m_rProxyDecider.getProxy( m_aUri.GetScheme(), + m_aUri.GetHost(), + m_aUri.GetPort() ); + } + else + { + // TODO: figure out, if this case can occur + return m_rProxyDecider.getProxy( m_aUri.GetScheme(), + OUString() /* not used */, + -1 /* not used */ ); + } +} + +/* + +namespace { + +bool containsLocktoken( const uno::Sequence< ucb::Lock > & rLocks, + const char * token ) +{ + for ( sal_Int32 n = 0; n < rLocks.getLength(); ++n ) + { + const uno::Sequence< OUString > & rTokens + = rLocks[ n ].LockTokens; + for ( sal_Int32 m = 0; m < rTokens.getLength(); ++m ) + { + if ( rTokens[ m ].equalsAscii( token ) ) + return true; + } + } + return false; +} + +} // namespace +*/ + + +bool SerfSession::removeExpiredLocktoken( const OUString & /*inURL*/, + const DAVRequestEnvironment & /*rEnv*/ ) +{ + return true; + /* + SerfLock * theLock = m_aSerfLockStore.findByUri( inURL ); + if ( !theLock ) + return false; + + // do a lockdiscovery to check whether this lock is still valid. + try + { + // @@@ Alternative: use ne_lock_discover() => less overhead + + std::vector< DAVResource > aResources; + std::vector< OUString > aPropNames; + aPropNames.push_back( DAVProperties::LOCKDISCOVERY ); + + PROPFIND( rEnv.m_aRequestURI, DAVZERO, aPropNames, aResources, rEnv ); + + if ( aResources.empty() ) + return false; + + std::vector< DAVPropertyValue >::const_iterator it + = aResources[ 0 ].properties.begin(); + std::vector< DAVPropertyValue >::const_iterator end + = aResources[ 0 ].properties.end(); + + while ( it != end ) + { + if ( (*it).Name.equals( DAVProperties::LOCKDISCOVERY ) ) + { + uno::Sequence< ucb::Lock > aLocks; + if ( !( (*it).Value >>= aLocks ) ) + return false; + + if ( !containsLocktoken( aLocks, theLock->token ) ) + { + // expired! + break; + } + + // still valid. + return false; + } + ++it; + } + + // No lockdiscovery prop in propfind result / locktoken not found + // in propfind result -> not locked + SAL_INFO("ucb.ucp.webdav", "SerfSession::removeExpiredLocktoken: Removing " + " expired lock token for " << inURL << ". token: " << theLock->token ); + + m_aSerfLockStore.removeLock( theLock ); + ne_lock_destroy( theLock ); + return true; + } + catch ( DAVException const & ) + { + } + return false; + */ +} + + +// HandleError +// Common Error Handler + +void SerfSession::HandleError( std::shared_ptr<SerfRequestProcessor> rReqProc ) +{ + m_aEnv = DAVRequestEnvironment(); + + if ( rReqProc->mpDAVException ) + { + DAVException* mpDAVExp( rReqProc->mpDAVException ); + + serf_connection_reset( getSerfConnection() ); + + if ( mpDAVExp->getStatus() == 413 && + m_bNoOfTransferEncodingSwitches < 2 ) + { + m_bUseChunkedEncoding = !m_bUseChunkedEncoding; + ++m_bNoOfTransferEncodingSwitches; + } + + throw DAVException( mpDAVExp->getError(), + mpDAVExp->getData(), + mpDAVExp->getStatus() ); + } + + /* + // Map error code to DAVException. + switch ( nError ) + { + case NE_OK: + return; + + case NE_ERROR: // Generic error + { + OUString aText = OUString::createFromAscii( + ne_get_error( m_pHttpSession ) ); + + sal_uInt16 code = makeStatusCode( aText ); + + if ( code == SC_LOCKED ) + { + if ( m_aSerfLockStore.findByUri( + makeAbsoluteURL( inPath ) ) == 0 ) + { + // locked by 3rd party + throw DAVException( DAVException::DAV_LOCKED ); + } + else + { + // locked by ourself + throw DAVException( DAVException::DAV_LOCKED_SELF ); + } + } + + // Special handling for 400 and 412 status codes, which may indicate + // that a lock previously obtained by us has been released meanwhile + // by the server. Unfortunately, RFC is not clear at this point, + // thus server implementations behave different... + else if ( code == SC_BAD_REQUEST || code == SC_PRECONDITION_FAILED ) + { + if ( removeExpiredLocktoken( makeAbsoluteURL( inPath ), rEnv ) ) + throw DAVException( DAVException::DAV_LOCK_EXPIRED ); + } + + throw DAVException( DAVException::DAV_HTTP_ERROR, aText, code ); + } + case NE_LOOKUP: // Name lookup failed. + throw DAVException( DAVException::DAV_HTTP_LOOKUP, + SerfUri::makeConnectionEndPointString( + m_aHostName, m_nPort ) ); + + case NE_AUTH: // User authentication failed on server + throw DAVException( DAVException::DAV_HTTP_AUTH, + SerfUri::makeConnectionEndPointString( + m_aHostName, m_nPort ) ); + + case NE_PROXYAUTH: // User authentication failed on proxy + throw DAVException( DAVException::DAV_HTTP_AUTHPROXY, + SerfUri::makeConnectionEndPointString( + m_aProxyName, m_nProxyPort ) ); + + case NE_CONNECT: // Could not connect to server + throw DAVException( DAVException::DAV_HTTP_CONNECT, + SerfUri::makeConnectionEndPointString( + m_aHostName, m_nPort ) ); + + case NE_TIMEOUT: // Connection timed out + throw DAVException( DAVException::DAV_HTTP_TIMEOUT, + SerfUri::makeConnectionEndPointString( + m_aHostName, m_nPort ) ); + + case NE_FAILED: // The precondition failed + throw DAVException( DAVException::DAV_HTTP_FAILED, + SerfUri::makeConnectionEndPointString( + m_aHostName, m_nPort ) ); + + case NE_RETRY: // Retry request (ne_end_request ONLY) + throw DAVException( DAVException::DAV_HTTP_RETRY, + SerfUri::makeConnectionEndPointString( + m_aHostName, m_nPort ) ); + + case NE_REDIRECT: + { + SerfUri aUri( ne_redirect_location( m_pHttpSession ) ); + throw DAVException( + DAVException::DAV_HTTP_REDIRECT, aUri.GetURI() ); + } + default: + { + SAL_INFO("ucb.ucp.webdav", "SerfSession::HandleError : Unknown Serf error code!" ); + throw DAVException( DAVException::DAV_HTTP_ERROR, + OUString::createFromAscii( + ne_get_error( m_pHttpSession ) ) ); + } + } + */ +} + + +// static +bool +SerfSession::getDataFromInputStream( + const uno::Reference< io::XInputStream > & xStream, + uno::Sequence< sal_Int8 > & rData, + bool bAppendTrailingZeroByte ) +{ + if ( xStream.is() ) + { + uno::Reference< io::XSeekable > xSeekable( xStream, uno::UNO_QUERY ); + if ( xSeekable.is() ) + { + try + { + sal_Int32 nSize + = sal::static_int_cast<sal_Int32>(xSeekable->getLength()); + sal_Int32 nRead + = xStream->readBytes( rData, nSize ); + + if ( nRead == nSize ) + { + if ( bAppendTrailingZeroByte ) + { + rData.realloc( nSize + 1 ); + rData[ nSize ] = sal_Int8( 0 ); + } + return true; + } + } + catch ( io::NotConnectedException const & ) + { + // readBytes + } + catch ( io::BufferSizeExceededException const & ) + { + // readBytes + } + catch ( io::IOException const & ) + { + // getLength, readBytes + } + } + else + { + try + { + uno::Sequence< sal_Int8 > aBuffer; + sal_Int32 nPos = 0; + + sal_Int32 nRead = xStream->readSomeBytes( aBuffer, 65536 ); + while ( nRead > 0 ) + { + if ( rData.getLength() < ( nPos + nRead ) ) + rData.realloc( nPos + nRead ); + + aBuffer.realloc( nRead ); + memcpy( rData.getArray() + nPos, aBuffer.getConstArray(), nRead ); + nPos += nRead; + + aBuffer.realloc( 0 ); + nRead = xStream->readSomeBytes( aBuffer, 65536 ); + } + + if ( bAppendTrailingZeroByte ) + { + rData.realloc( nPos + 1 ); + rData[ nPos ] = sal_Int8( 0 ); + } + return true; + } + catch ( io::NotConnectedException const & ) + { + // readBytes + } + catch ( io::BufferSizeExceededException const & ) + { + // readBytes + } + catch ( io::IOException const & ) + { + // readBytes + } + } + } + return false; +} + + +bool +SerfSession::isDomainMatch( const OUString & certHostName ) +{ + OUString hostName = getHostName(); + + if (hostName.equalsIgnoreAsciiCase( certHostName ) ) + return true; + + if ( certHostName.startsWith( "*" ) && + hostName.getLength() >= certHostName.getLength() ) + { + OUString cmpStr = certHostName.copy( 1 ); + + if ( hostName.matchIgnoreAsciiCase( + cmpStr, hostName.getLength() - cmpStr.getLength() ) ) + return true; + } + return false; +} + +/* + +OUString SerfSession::makeAbsoluteURL( OUString const & rURL ) const +{ + try + { + // Is URL relative or already absolute? + if ( rURL[ 0 ] != '/' ) + { + // absolute. + return OUString( rURL ); + } + else + { + ne_uri aUri; + memset( &aUri, 0, sizeof( aUri ) ); + + ne_fill_server_uri( m_pHttpSession, &aUri ); + aUri.path + = ne_strdup( OUStringToOString( + rURL, RTL_TEXTENCODING_UTF8 ).getStr() ); + SerfUri aSerfUri( &aUri ); + ne_uri_free( &aUri ); + return aSerfUri.GetURI(); + } + } + catch ( DAVException const & ) + { + } + // error. + return OUString(); +} +*/ + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/ucb/source/ucp/webdav/SerfSession.hxx b/ucb/source/ucp/webdav/SerfSession.hxx new file mode 100644 index 000000000..8c2a14439 --- /dev/null +++ b/ucb/source/ucp/webdav/SerfSession.hxx @@ -0,0 +1,262 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + + +#ifndef INCLUDED_UCB_SOURCE_UCP_WEBDAV_SERFSESSION_HXX +#define INCLUDED_UCB_SOURCE_UCP_WEBDAV_SERFSESSION_HXX + +#include <osl/mutex.hxx> +#include <memory> +#include <vector> +#include "DAVSession.hxx" +#include "SerfUri.hxx" + +#include <serf.h> + +namespace ucbhelper { class ProxyDecider; } + +namespace http_dav_ucp +{ + +class SerfRequestProcessor; + + +// SerfSession +// A DAVSession implementation using the neon/expat library + + +class SerfSession : public DAVSession +{ +private: + osl::Mutex m_aMutex; + + SerfUri m_aUri; + + OUString m_aProxyName; + sal_Int32 m_nProxyPort; + + serf_connection_t* m_pSerfConnection; + serf_context_t* m_pSerfContext; + serf_bucket_alloc_t* m_pSerfBucket_Alloc; + bool m_bIsHeadRequestInProgress; + bool m_bUseChunkedEncoding; + sal_Int16 m_bNoOfTransferEncodingSwitches; + + const ucbhelper::InternetProxyDecider & m_rProxyDecider; + + DAVRequestEnvironment m_aEnv; + + char* getHostinfo(); + bool isSSLNeeded(); + + SerfRequestProcessor* createReqProc( const OUString & inPath ); + +protected: + virtual ~SerfSession() override; + +public: + /// @throws DAVException + SerfSession( const rtl::Reference< DAVSessionFactory > & rSessionFactory, + const OUString& inUri, + const ucbhelper::InternetProxyDecider & rProxyDecider ); + + // Serf library callbacks + apr_status_t setupSerfConnection( apr_socket_t * inAprSocket, + serf_bucket_t **outSerfInputBucket, + serf_bucket_t **outSerfOutputBucket, + apr_pool_t* inAprPool ); + + apr_status_t provideSerfCredentials( bool bGiveProvidedCredentialsASecondTry, + char ** outUsername, + char ** outPassword, + serf_request_t * inRequest, + int inCode, + const char *inAuthProtocol, + const char *inRealm, + apr_pool_t *inAprPool ); + + apr_status_t verifySerfCertificateChain ( + int nFailures, + const serf_ssl_certificate_t * const * pCertificateChainBase64Encoded, + int nCertificateChainLength); + + serf_bucket_t* acceptSerfResponse( serf_request_t * inSerfRequest, + serf_bucket_t * inSerfStreamBucket, + apr_pool_t* inAprPool ); + + // Serf-related data structures + static apr_pool_t* getAprPool(); + serf_bucket_alloc_t* getSerfBktAlloc(); + serf_context_t* getSerfContext(); + serf_connection_t* getSerfConnection(); + + // DAVSession methods + virtual bool CanUse( const OUString & inUri ) override; + + virtual bool UsesProxy() override; + + const DAVRequestEnvironment & getRequestEnvironment() const + { return m_aEnv; } + + // allprop & named + virtual void + PROPFIND( const OUString & inPath, + const Depth inDepth, + const std::vector< OUString > & inPropNames, + std::vector< DAVResource > & ioResources, + const DAVRequestEnvironment & rEnv ) override; + + // propnames + virtual void + PROPFIND( const OUString & inPath, + const Depth inDepth, + std::vector< DAVResourceInfo >& ioResInfo, + const DAVRequestEnvironment & rEnv ) override; + + virtual void + PROPPATCH( const OUString & inPath, + const std::vector< ProppatchValue > & inValues, + const DAVRequestEnvironment & rEnv ) override; + + virtual void + HEAD( const OUString & inPath, + const std::vector< OUString > & inHeaderNames, + DAVResource & ioResource, + const DAVRequestEnvironment & rEnv ) override; + + bool isHeadRequestInProgress(); + + virtual css::uno::Reference< css::io::XInputStream > + GET( const OUString & inPath, + const DAVRequestEnvironment & rEnv ) override; + + virtual void + GET( const OUString & inPath, + css::uno::Reference< css::io::XOutputStream > & ioOutputStream, + const DAVRequestEnvironment & rEnv ) override; + + virtual css::uno::Reference< css::io::XInputStream > + GET( const OUString & inPath, + const std::vector< OUString > & inHeaderNames, + DAVResource & ioResource, + const DAVRequestEnvironment & rEnv ) override; + + virtual void + GET( const OUString & inPath, + css::uno::Reference< css::io::XOutputStream > & ioOutputStream, + const std::vector< OUString > & inHeaderNames, + DAVResource & ioResource, + const DAVRequestEnvironment & rEnv ) override; + + virtual void + PUT( const OUString & inPath, + const css::uno::Reference< css::io::XInputStream > & inInputStream, + const DAVRequestEnvironment & rEnv ) override; + + 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 ) override; + + 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 ) override; + + virtual void + MKCOL( const OUString & inPath, + const DAVRequestEnvironment & rEnv ) override; + + virtual void + COPY( const OUString & inSourceURL, + const OUString & inDestinationURL, + const DAVRequestEnvironment & rEnv, + bool inOverWrite = false ) override; + + virtual void + MOVE( const OUString & inSourceURL, + const OUString & inDestinationURL, + const DAVRequestEnvironment & rEnv, + bool inOverWrite = false ) override; + + virtual void DESTROY( const OUString & inPath, + const DAVRequestEnvironment & rEnv ) override; + + // set new lock. + virtual void LOCK( const OUString & inURL, + css::ucb::Lock & inLock, + const DAVRequestEnvironment & rEnv ) override; + + // refresh existing lock. + virtual sal_Int64 LOCK( const OUString & inURL, + sal_Int64 nTimeout, + const DAVRequestEnvironment & rEnv ) override; + + virtual void UNLOCK( const OUString & inURL, + const DAVRequestEnvironment & rEnv ) override; + + // helpers + virtual void abort() override; + + const OUString & getHostName() const { return m_aUri.GetHost(); } + int getPort() const { return m_aUri.GetPort(); } + + bool isDomainMatch( const OUString & certHostName ); + +private: + friend class SerfLockStore; + + /// @throws DAVException + void Init(); + + /// @throws DAVException + void Init( const DAVRequestEnvironment & rEnv ); + + /// @throws DAVException + void HandleError( std::shared_ptr<SerfRequestProcessor> rReqProc ); + + ucbhelper::InternetProxyServer getProxySettings() const; + + static bool removeExpiredLocktoken( const OUString & inURL, + const DAVRequestEnvironment & rEnv ); + + // refresh lock, called by SerfLockStore::refreshLocks + bool LOCK( const OUString& rLock, sal_Int32 *plastChanceToSendRefreshRequest ); + + // unlock, called by SerfLockStore::~SerfLockStore + void UNLOCK( const OUString& rLock ); + + // Helper: XInputStream -> Sequence< sal_Int8 > + static bool getDataFromInputStream( + const css::uno::Reference< + css::io::XInputStream > & xStream, + css::uno::Sequence< sal_Int8 > & rData, + bool bAppendTrailingZeroByte ); +}; + +} // namespace http_dav_ucp + +#endif // INCLUDED_UCB_SOURCE_UCP_WEBDAV_SERFSESSION_HXX + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/ucb/source/ucp/webdav/SerfUnlockReqProcImpl.cxx b/ucb/source/ucp/webdav/SerfUnlockReqProcImpl.cxx new file mode 100644 index 000000000..53826ace6 --- /dev/null +++ b/ucb/source/ucp/webdav/SerfUnlockReqProcImpl.cxx @@ -0,0 +1,68 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this 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 "SerfUnlockReqProcImpl.hxx" + +namespace http_dav_ucp +{ + +SerfUnlockReqProcImpl::SerfUnlockReqProcImpl( const char* inPath, + const DAVRequestHeaders& inRequestHeaders, + const OUString& sToken) + : SerfRequestProcessorImpl( inPath, inRequestHeaders ) + , m_sToken( sToken ) +{ +} + +SerfUnlockReqProcImpl::~SerfUnlockReqProcImpl() +{ +} + +serf_bucket_t * SerfUnlockReqProcImpl::createSerfRequestBucket( serf_request_t * inSerfRequest ) +{ + // create serf request + serf_bucket_t *req_bkt = serf_request_bucket_request_create( inSerfRequest, + "UNLOCK", + getPathStr(), + nullptr, + serf_request_get_alloc( inSerfRequest ) ); + // set request header fields + serf_bucket_t* hdrs_bkt = serf_bucket_request_get_headers( req_bkt ); + + // general header fields provided by caller + setRequestHeaders( hdrs_bkt ); + + // token header field + serf_bucket_headers_set( hdrs_bkt, "Lock-Token", + OUStringToOString(m_sToken, RTL_TEXTENCODING_UTF8).getStr() ); + + return req_bkt; +} + +void SerfUnlockReqProcImpl::processChunkOfResponseData( const char* , apr_size_t ) +{ +} + +void SerfUnlockReqProcImpl::handleEndOfResponseData( serf_bucket_t * ) +{ +} + +} // namespace http_dav_ucp + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/ucb/source/ucp/webdav/SerfUnlockReqProcImpl.hxx b/ucb/source/ucp/webdav/SerfUnlockReqProcImpl.hxx new file mode 100644 index 000000000..afef3f28b --- /dev/null +++ b/ucb/source/ucp/webdav/SerfUnlockReqProcImpl.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 . + */ + +#ifndef INCLUDED_UCB_SOURCE_UCP_WEBDAV_SERFUNLOCKREQPROCIMPL_HXX +#define INCLUDED_UCB_SOURCE_UCP_WEBDAV_SERFUNLOCKREQPROCIMPL_HXX + +#include "SerfRequestProcessorImpl.hxx" + +namespace http_dav_ucp +{ + +class SerfUnlockReqProcImpl : public SerfRequestProcessorImpl +{ +public: + SerfUnlockReqProcImpl( const char* inPath, + const DAVRequestHeaders& inRequestHeaders, + const OUString& sToken); + + virtual ~SerfUnlockReqProcImpl() override; + + virtual serf_bucket_t *createSerfRequestBucket( + serf_request_t * inSerfRequest ) override; + +private: + virtual void processChunkOfResponseData( + const char* data, apr_size_t len ) override; + + virtual void handleEndOfResponseData( + serf_bucket_t * inSerfResponseBucket ) override; + + OUString m_sToken; +}; + +} // namespace http_dav_ucp + +#endif // INCLUDED_UCB_SOURCE_UCP_WEBDAV_SERFUNLOCKREQPROCIMPL_HXX + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/ucb/source/ucp/webdav/SerfUri.cxx b/ucb/source/ucp/webdav/SerfUri.cxx new file mode 100644 index 000000000..dab11a64c --- /dev/null +++ b/ucb/source/ucp/webdav/SerfUri.cxx @@ -0,0 +1,248 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this 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/ustring.hxx> +#include <rtl/ustrbuf.hxx> +#include "SerfUri.hxx" +#include "DAVException.hxx" +#include "AprEnv.hxx" + +#include <urihelper.hxx> + +using namespace http_dav_ucp; + + +SerfUri::SerfUri( const apr_uri_t * inUri ) + : mAprUri( *inUri ) + , mURI() + , mScheme() + , mUserInfo() + , mHostName() + , mPort() + , mPath() +{ + if ( inUri == nullptr ) + throw DAVException( DAVException::DAV_INVALID_ARG ); + + char * uri = apr_uri_unparse( apr_environment::AprEnv::getAprEnv()->getAprPool(), &mAprUri, 0 ); + + if ( uri == nullptr ) + throw DAVException( DAVException::DAV_INVALID_ARG ); + + init( &mAprUri ); + + calculateURI(); +} + +SerfUri::SerfUri( const OUString & inUri ) + : mAprUri() + , mURI() + , mScheme() + , mUserInfo() + , mHostName() + , mPort() + , mPath() +{ + if ( inUri.getLength() <= 0 ) + throw DAVException( DAVException::DAV_INVALID_ARG ); + + // #i77023# + OUString aEscapedUri( ucb_impl::urihelper::encodeURI( inUri ) ); + + OString theInputUri( + aEscapedUri.getStr(), aEscapedUri.getLength(), RTL_TEXTENCODING_UTF8 ); + + if ( apr_uri_parse( apr_environment::AprEnv::getAprEnv()->getAprPool(), + theInputUri.getStr(), &mAprUri ) != APR_SUCCESS ) + { + throw DAVException( DAVException::DAV_INVALID_ARG ); + } + if ( !mAprUri.port ) + { + mAprUri.port = apr_uri_port_of_scheme( mAprUri.scheme ); + } + if ( !mAprUri.path ) + { + mAprUri.path = const_cast<char *>("/"); + } + + init( &mAprUri ); + + calculateURI(); +} + +void SerfUri::init( const apr_uri_t * pUri ) +{ + mScheme = OStringToOUString( pUri->scheme, RTL_TEXTENCODING_UTF8 ); + mUserInfo = OStringToOUString( pUri->user, RTL_TEXTENCODING_UTF8 ); + mHostName = OStringToOUString( pUri->hostname, RTL_TEXTENCODING_UTF8 ); + mPort = pUri->port; + mPath = OStringToOUString( pUri->path, RTL_TEXTENCODING_UTF8 ); + + if ( pUri->query ) + { + mPath += "?"; + mPath += OStringToOUString( pUri->query, RTL_TEXTENCODING_UTF8 ); + } + + if ( pUri->fragment ) + { + mPath += "#"; + mPath += OStringToOUString( pUri->fragment, RTL_TEXTENCODING_UTF8 ); + } +} + +void SerfUri::calculateURI () +{ + OUStringBuffer aBuf( mScheme ); + aBuf.append( "://" ); + if ( mUserInfo.getLength() > 0 ) + { + aBuf.append( mUserInfo ); + aBuf.append( "@" ); + } + // Is host a numeric IPv6 address? + if ( ( mHostName.indexOf( ':' ) != -1 ) && + ( mHostName[ 0 ] != '[' ) ) + { + aBuf.append( "[" ); + aBuf.append( mHostName ); + aBuf.append( "]" ); + } + else + { + aBuf.append( mHostName ); + } + + // append port, but only, if not default port. + bool bAppendPort = true; + switch ( mPort ) + { + case DEFAULT_HTTP_PORT: + bAppendPort = (mScheme != "http"); + break; + + case DEFAULT_HTTPS_PORT: + bAppendPort = (mScheme != "https"); + break; + } + if ( bAppendPort ) + { + aBuf.append( ":" ); + aBuf.append( OUString::number( mPort ) ); + } + aBuf.append( mPath ); + + mURI = aBuf.makeStringAndClear(); +} + +OUString SerfUri::GetPathBaseName () const +{ + sal_Int32 nPos = mPath.lastIndexOf ('/'); + sal_Int32 nTrail = 0; + if (nPos == mPath.getLength () - 1) + { + // Trailing slash found. Skip. + nTrail = 1; + nPos = mPath.lastIndexOf ('/', nPos); + } + if (nPos != -1) + { + OUString aTemp( + mPath.copy (nPos + 1, mPath.getLength () - nPos - 1 - nTrail) ); + + // query, fragment present? + nPos = aTemp.indexOf( '?' ); + if ( nPos == -1 ) + nPos = aTemp.indexOf( '#' ); + + if ( nPos != -1 ) + aTemp = aTemp.copy( 0, nPos ); + + return aTemp; + } + else + return "/"; +} + +bool SerfUri::operator== ( const SerfUri & rOther ) const +{ + return ( mURI == rOther.mURI ); +} + +OUString SerfUri::GetPathBaseNameUnescaped () const +{ + return unescape( GetPathBaseName() ); +} + +void SerfUri::AppendPath (const OUString& rPath) +{ + if (mPath.lastIndexOf ('/') != mPath.getLength () - 1) + mPath += "/"; + + mPath += rPath; + calculateURI (); +}; + +// static +OUString SerfUri::escapeSegment( const OUString& segment ) +{ + return rtl::Uri::encode( segment, + rtl_UriCharClassPchar, + rtl_UriEncodeIgnoreEscapes, + RTL_TEXTENCODING_UTF8 ); +} + +// static +OUString SerfUri::unescape( const OUString& segment ) +{ + return rtl::Uri::decode( segment, + rtl_UriDecodeWithCharset, + RTL_TEXTENCODING_UTF8 ); +} + +// static +OUString SerfUri::makeConnectionEndPointString( + const OUString & rHostName, int nPort ) +{ + OUStringBuffer aBuf; + + // Is host a numeric IPv6 address? + if ( ( rHostName.indexOf( ':' ) != -1 ) && + ( rHostName[ 0 ] != '[' ) ) + { + aBuf.append( "[" ); + aBuf.append( rHostName ); + aBuf.append( "]" ); + } + else + { + aBuf.append( rHostName ); + } + + if ( ( nPort != DEFAULT_HTTP_PORT ) && ( nPort != DEFAULT_HTTPS_PORT ) ) + { + aBuf.append( ":" ); + aBuf.append( OUString::number( sal_Int32( nPort ) ) ); + } + return aBuf.makeStringAndClear(); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/ucb/source/ucp/webdav/SerfUri.hxx b/ucb/source/ucp/webdav/SerfUri.hxx new file mode 100644 index 000000000..f47b2892e --- /dev/null +++ b/ucb/source/ucp/webdav/SerfUri.hxx @@ -0,0 +1,103 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + + +#ifndef INCLUDED_UCB_SOURCE_UCP_WEBDAV_SERFURI_HXX +#define INCLUDED_UCB_SOURCE_UCP_WEBDAV_SERFURI_HXX + +#include <apr_uri.h> +#include <rtl/ustring.hxx> +#include "DAVException.hxx" + +namespace http_dav_ucp +{ + +#define DEFAULT_HTTP_PORT 80 +#define DEFAULT_HTTPS_PORT 443 + + +// SerfUri +// A URI implementation for use with the neon/expat library + +class SerfUri +{ + private: + apr_uri_t mAprUri; + OUString mURI; + OUString mScheme; + OUString mUserInfo; + OUString mHostName; + sal_Int32 mPort; + OUString mPath; + + void init( const apr_uri_t * pUri ); + void calculateURI (); + + public: + /// @throws DAVException + explicit SerfUri( const OUString & inUri ); + /// @throws DAVException + explicit SerfUri( const apr_uri_t * inUri ); + + bool operator== ( const SerfUri & rOther ) const; + bool operator!= ( const SerfUri & rOther ) const + { return !operator==( rOther ); } + + apr_uri_t& getAprUri() + { + return mAprUri; + } + const OUString & GetURI() const + { return mURI; }; + const OUString & GetScheme() const + { return mScheme; }; + const OUString & GetUserInfo() const + { return mUserInfo; }; + const OUString & GetHost() const + { return mHostName; }; + sal_Int32 GetPort() const + { return mPort; }; + const OUString & GetPath() const + { return mPath; }; + + OUString GetPathBaseName() const; + + OUString GetPathBaseNameUnescaped() const; + + void SetScheme (const OUString& scheme) + { mScheme = scheme; calculateURI (); }; + + void AppendPath (const OUString& rPath); + + static OUString escapeSegment( const OUString& segment ); + static OUString unescape( const OUString& string ); + + // "host:port", omit ":port" for port 80 and 443 + static OUString makeConnectionEndPointString( + const OUString & rHostName, + int nPort ); + OUString makeConnectionEndPointString() const + { return makeConnectionEndPointString( GetHost(), GetPort() ); } +}; + +} // namespace http_dav_ucp + +#endif // INCLUDED_UCB_SOURCE_UCP_WEBDAV_SERFURI_HXX + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/ucb/source/ucp/webdav/UCBDeadPropertyValue.cxx b/ucb/source/ucp/webdav/UCBDeadPropertyValue.cxx new file mode 100644 index 000000000..0f3543012 --- /dev/null +++ b/ucb/source/ucp/webdav/UCBDeadPropertyValue.cxx @@ -0,0 +1,526 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this 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/ustrbuf.hxx> +#include <sal/log.hxx> +#include "UCBDeadPropertyValue.hxx" + +using namespace http_dav_ucp; +using namespace ::com::sun::star; + + +// static +const OUString UCBDeadPropertyValue::aTypeString + = "string"; +const OUString UCBDeadPropertyValue::aTypeLong + = "long"; +const OUString UCBDeadPropertyValue::aTypeShort + = "short"; +const OUString UCBDeadPropertyValue::aTypeBoolean + = "boolean"; +const OUString UCBDeadPropertyValue::aTypeChar + = "char"; +const OUString UCBDeadPropertyValue::aTypeByte + = "byte"; +const OUString UCBDeadPropertyValue::aTypeHyper + = "hyper"; +const OUString UCBDeadPropertyValue::aTypeFloat + = "float"; +const OUString UCBDeadPropertyValue::aTypeDouble + = "double"; + +// static +const OUString UCBDeadPropertyValue::aXMLPre + = "<ucbprop><type>"; +const OUString UCBDeadPropertyValue::aXMLMid + = "</type><value>"; +const OUString UCBDeadPropertyValue::aXMLEnd + = "</value></ucbprop>"; + +/* + +#define STATE_TOP (1) + +#define STATE_UCBPROP (STATE_TOP) +#define STATE_TYPE (STATE_TOP + 1) +#define STATE_VALUE (STATE_TOP + 2) + +extern "C" int UCBDeadPropertyValue_startelement_callback( + void *, + int parent, + const char * nspace, + const char *name, + const char ** ) +{ + if ( name != 0 ) + { + switch ( parent ) + { + case NE_XML_STATEROOT: + if ( strcmp( name, "ucbprop" ) == 0 ) + return STATE_UCBPROP; + break; + + case STATE_UCBPROP: + if ( strcmp( name, "type" ) == 0 ) + return STATE_TYPE; + else if ( strcmp( name, "value" ) == 0 ) + return STATE_VALUE; + break; + } + } + return NE_XML_DECLINE; +} + + +extern "C" int UCBDeadPropertyValue_chardata_callback( + void *userdata, + int state, + const char *buf, + size_t len ) +{ + UCBDeadPropertyValueParseContext * pCtx + = static_cast< UCBDeadPropertyValueParseContext * >( userdata ); + + switch ( state ) + { + case STATE_TYPE: + SAL_WARN_IF( pCtx->pType, "ucb.ucp.webdav", + "UCBDeadPropertyValue_endelement_callback - " + "Type already set!" ); + pCtx->pType + = new OUString( buf, len, RTL_TEXTENCODING_ASCII_US ); + break; + + case STATE_VALUE: + SAL_WARN_IF( pCtx->pValue, "ucb.ucp.webdav", + "UCBDeadPropertyValue_endelement_callback - " + "Value already set!" ); + pCtx->pValue + = new OUString( buf, len, RTL_TEXTENCODING_ASCII_US ); + break; + } + return 0; // zero to continue, non-zero to abort parsing +} + + +extern "C" int UCBDeadPropertyValue_endelement_callback( + void *userdata, + int state, + const char *, + const char * ) +{ + UCBDeadPropertyValueParseContext * pCtx + = static_cast< UCBDeadPropertyValueParseContext * >( userdata ); + + switch ( state ) + { + case STATE_TYPE: + if ( !pCtx->pType ) + return 1; // abort + break; + + case STATE_VALUE: + if ( !pCtx->pValue ) + return 1; // abort + break; + + case STATE_UCBPROP: + if ( !pCtx->pType || ! pCtx->pValue ) + return 1; // abort + break; + } + return 0; // zero to continue, non-zero to abort parsing +} +*/ + + +static OUString encodeValue( const OUString & rValue ) +{ + // Note: I do not use the usual & + < + > encoding, because + // I want to prevent any XML parser from trying to 'understand' + // the value. This caused problems: + + // Example: + // - Unencoded property value: x<z + // PROPPATCH: + // - Encoded property value: x<z + // - UCBDeadPropertyValue::toXML result: + // <ucbprop><type>string</type><value>x<z</value></ucbprop> + // PROPFIND: + // - parser replaces < by > ==> error (not well formed) + + OUStringBuffer aResult; + const sal_Unicode * pValue = rValue.getStr(); + + sal_Int32 nCount = rValue.getLength(); + for ( sal_Int32 n = 0; n < nCount; ++n ) + { + const sal_Unicode c = pValue[ n ]; + + if ( '%' == c ) + aResult.append( "%per;" ); + else if ( '<' == c ) + aResult.append( "%lt;" ); + else if ( '>' == c ) + aResult.append( "%gt;" ); + else + aResult.append( c ); + } + return aResult.makeStringAndClear(); +} + +/* + +static OUString decodeValue( const OUString & rValue ) +{ + OUStringBuffer aResult; + const sal_Unicode * pValue = rValue.getStr(); + + sal_Int32 nPos = 0; + sal_Int32 nEnd = rValue.getLength(); + + while ( nPos < nEnd ) + { + sal_Unicode c = pValue[ nPos ]; + + if ( '%' == c ) + { + nPos++; + + if ( nPos == nEnd ) + { + SAL_WARN( "ucb.ucp.webdav", + "UCBDeadPropertyValue::decodeValue - syntax error!" ); + return OUString(); + } + + c = pValue[ nPos ]; + + if ( 'p' == c ) + { + // %per; + + if ( nPos > nEnd - 4 ) + { + SAL_WARN( "ucb.ucp.webdav", + "UCBDeadPropertyValue::decodeValue - syntax error!" ); + return OUString(); + } + + if ( ( 'e' == pValue[ nPos + 1 ] ) + && + ( 'r' == pValue[ nPos + 2 ] ) + && + ( ';' == pValue[ nPos + 3 ] ) ) + { + aResult.append( '%' ); + nPos += 3; + } + else + { + SAL_WARN( "ucb.ucp.webdav", + "UCBDeadPropertyValue::decodeValue - syntax error!" ); + return OUString(); + } + } + else if ( 'l' == c ) + { + // %lt; + + if ( nPos > nEnd - 3 ) + { + SAL_WARN( "ucb.ucp.webdav", + "UCBDeadPropertyValue::decodeValue - syntax error!" ); + return OUString(); + } + + if ( ( 't' == pValue[ nPos + 1 ] ) + && + ( ';' == pValue[ nPos + 2 ] ) ) + { + aResult.append( '<' ); + nPos += 2; + } + else + { + SAL_WARN( "ucb.ucp.webdav", + "UCBDeadPropertyValue::decodeValue - syntax error!" ); + return OUString(); + } + } + else if ( 'g' == c ) + { + // %gt; + + if ( nPos > nEnd - 3 ) + { + SAL_WARN( "ucb.ucp.webdav", + "UCBDeadPropertyValue::decodeValue - syntax error!" ); + return OUString(); + } + + if ( ( 't' == pValue[ nPos + 1 ] ) + && + ( ';' == pValue[ nPos + 2 ] ) ) + { + aResult.append( '>' ); + nPos += 2; + } + else + { + SAL_WARN( "ucb.ucp.webdav", + "UCBDeadPropertyValue::decodeValue - syntax error!" ); + return OUString(); + } + } + else + { + SAL_WARN( "ucb.ucp.webdav", + "UCBDeadPropertyValue::decodeValue - syntax error!" ); + return OUString(); + } + } + else + aResult.append( c ); + + nPos++; + } + + return OUString( aResult ); +} +*/ + + +// 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( const OString & /*rInData*/, + uno::Any & /*rOutData*/ ) +{ + bool success = false; + + /* + ne_xml_parser * parser = ne_xml_create(); + if ( parser ) + { + UCBDeadPropertyValueParseContext aCtx; + ne_xml_push_handler( parser, + UCBDeadPropertyValue_startelement_callback, + UCBDeadPropertyValue_chardata_callback, + UCBDeadPropertyValue_endelement_callback, + &aCtx ); + + ne_xml_parse( parser, rInData.getStr(), rInData.getLength() ); + + success = !ne_xml_failed( parser ); + + ne_xml_destroy( parser ); + + if ( success ) + { + if ( aCtx.pType && aCtx.pValue ) + { + // Decode aCtx.pValue! It may contain XML reserved chars. + OUString aStringValue = decodeValue( *aCtx.pValue ); + if ( aCtx.pType->equalsIgnoreAsciiCase( aTypeString ) ) + { + rOutData <<= aStringValue; + } + else if ( aCtx.pType->equalsIgnoreAsciiCase( aTypeLong ) ) + { + rOutData <<= aStringValue.toInt32(); + } + else if ( aCtx.pType->equalsIgnoreAsciiCase( aTypeShort ) ) + { + rOutData <<= sal_Int16( aStringValue.toInt32() ); + } + else if ( aCtx.pType->equalsIgnoreAsciiCase( aTypeBoolean ) ) + { + if ( aStringValue.equalsIgnoreAsciiCase( + OUString( "true" ) ) ) + rOutData <<= sal_Bool( sal_True ); + else + rOutData <<= sal_Bool( sal_False ); + } + else if ( aCtx.pType->equalsIgnoreAsciiCase( aTypeChar ) ) + { + rOutData <<= aStringValue.toChar(); + } + else if ( aCtx.pType->equalsIgnoreAsciiCase( aTypeByte ) ) + { + rOutData <<= sal_Int8( aStringValue.toChar() ); + } + else if ( aCtx.pType->equalsIgnoreAsciiCase( aTypeHyper ) ) + { + rOutData <<= aStringValue.toInt64(); + } + else if ( aCtx.pType->equalsIgnoreAsciiCase( aTypeFloat ) ) + { + rOutData <<= aStringValue.toFloat(); + } + else if ( aCtx.pType->equalsIgnoreAsciiCase( aTypeDouble ) ) + { + rOutData <<= aStringValue.toDouble(); + } + else + { + SAL_WARN( "ucb.ucp.webdav", + "UCBDeadPropertyValue::createFromXML - " + "Unsupported property type!" ); + success = false; + } + } + else + success = false; + } + } + */ + return success; +} + + +// static +bool UCBDeadPropertyValue::toXML( const uno::Any & rInData, + OUString & rOutData ) +{ + // <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 false; + } + + // Encode value! It must not contain XML reserved chars! + aStringValue = encodeValue( aStringValue ); + + rOutData = aXMLPre; + rOutData += aStringType; + rOutData += aXMLMid; + rOutData += aStringValue; + rOutData += aXMLEnd; + return true; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/ucb/source/ucp/webdav/UCBDeadPropertyValue.hxx b/ucb/source/ucp/webdav/UCBDeadPropertyValue.hxx new file mode 100644 index 000000000..dc066f48a --- /dev/null +++ b/ucb/source/ucp/webdav/UCBDeadPropertyValue.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 . + */ + + +#ifndef INCLUDED_UCB_SOURCE_UCP_WEBDAV_UCBDEADPROPERTYVALUE_HXX +#define INCLUDED_UCB_SOURCE_UCP_WEBDAV_UCBDEADPROPERTYVALUE_HXX + +#include <rtl/string.hxx> +#include <com/sun/star/uno/Any.hxx> + +namespace http_dav_ucp +{ + +class UCBDeadPropertyValue +{ +private: + static const OUString aTypeString; + static const OUString aTypeLong; + static const OUString aTypeShort; + static const OUString aTypeBoolean; + static const OUString aTypeChar; + static const OUString aTypeByte; + static const OUString aTypeHyper; + static const OUString aTypeFloat; + static const OUString aTypeDouble; + + static const OUString aXMLPre; + static const OUString aXMLMid; + static const OUString aXMLEnd; + +public: + static bool supportsType( const css::uno::Type & rType ); + + static bool createFromXML( const OString & rInData, + css::uno::Any & rOutData ); + static bool toXML( const css::uno::Any & rInData, + OUString & rOutData ); +}; + +} + +#endif // INCLUDED_UCB_SOURCE_UCP_WEBDAV_UCBDEADPROPERTYVALUE_HXX + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/ucb/source/ucp/webdav/ucpdav1.component b/ucb/source/ucp/webdav/ucpdav1.component new file mode 100644 index 000000000..50a3d87b2 --- /dev/null +++ b/ucb/source/ucp/webdav/ucpdav1.component @@ -0,0 +1,27 @@ +<?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" + xmlns="http://openoffice.org/2010/uno-components"> + <implementation name="com.sun.star.comp.WebDAVContentProvider"> + <service name="com.sun.star.ucb.WebDAVContentProvider"/> + </implementation> +</component> diff --git a/ucb/source/ucp/webdav/webdavcontent.cxx b/ucb/source/ucp/webdav/webdavcontent.cxx new file mode 100644 index 000000000..6c0198199 --- /dev/null +++ b/ucb/source/ucp/webdav/webdavcontent.cxx @@ -0,0 +1,3292 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this 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 <ucbhelper/contentidentifier.hxx> +#include <ucbhelper/macros.hxx> +#include <ucbhelper/propertyvalueset.hxx> +#include <ucbhelper/simpleinteractionrequest.hxx> +#include <ucbhelper/cancelcommandexecution.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/OpenCommandArgument2.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 "SerfUri.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" ), + 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" ) ); + aHeaderNames.push_back( OUString( "Content-Range" ) ); + } + try + { + uno::Reference< io::XInputStream > xIn = xResAccess->GET( 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.get() ) + xProps->addProperties( + rProps, + ContentProperties( aResource ) ); + else + xProps.reset ( new ContentProperties( aResource ) ); + } + catch ( DAVException const & ex ) + { + aLastException = ex; + } +} +} + + +// 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_pProvider( pProvider ), + m_bTransient( false ), + m_bLocked( false ), + m_bCollection( false ), + m_bDidGetOrHead( false ) +{ + try + { + m_xResAccess.reset( new DAVResourceAccess( + rxContext, + rSessionFactory, + Identifier->getContentIdentifier() ) ); + + SerfUri 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_pProvider( pProvider ), + m_bTransient( true ), + m_bLocked( false ), + m_bCollection( isCollection ), + m_bDidGetOrHead( false ) +{ + try + { + 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() +{ + if (m_bLocked) + unlock(uno::Reference< ucb::XCommandEnvironment >()); +} + + +// XInterface methods. + + +// virtual +void SAL_CALL Content::acquire() + throw( ) +{ + ContentImplHelper::acquire(); +} + + +// virtual +void SAL_CALL Content::release() + throw( ) +{ + 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< beans::XPropertySet > const xProps( + m_xContext, uno::UNO_QUERY_THROW ); + uno::Reference< uno::XComponentContext > xCtx; + xCtx.set( xProps->getPropertyValue( "DefaultContext" ), + uno::UNO_QUERY_THROW ); + + uno::Reference< task::XInteractionHandler > xIH( + task::PasswordContainerInteractionHandler::create( xCtx ) ); + + // 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( + xCtx, + 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::makeAny( lang::IllegalArgumentException( + "Wrong argument type!", + static_cast< cppu::OWeakObject * >( this ), + -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::makeAny( lang::IllegalArgumentException( + "Wrong argument type!", + static_cast< cppu::OWeakObject * >( this ), + -1 ) ), + Environment ); + // Unreachable + } + + if ( !aProperties.getLength() ) + { + ucbhelper::cancelCommandExecution( + uno::makeAny( lang::IllegalArgumentException( + "No properties!", + static_cast< cppu::OWeakObject * >( this ), + -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::OpenCommandArgument2 aOpenCommand; + if ( !( aCommand.Argument >>= aOpenCommand ) ) + { + ucbhelper::cancelCommandExecution( + uno::makeAny( lang::IllegalArgumentException( + "Wrong argument type!", + static_cast< cppu::OWeakObject * >( this ), + -1 ) ), + Environment ); + // Unreachable + } + + aRet = open( aOpenCommand, Environment ); + + if ( (aOpenCommand.Mode == ucb::OpenMode::DOCUMENT || + aOpenCommand.Mode == ucb::OpenMode::DOCUMENT_SHARE_DENY_WRITE) && + supportsExclusiveWriteLock( Environment ) ) + lock( Environment ); + } + else if ( aCommand.Name == "insert" ) + { + + // insert + + + ucb::InsertCommandArgument arg; + if ( !( aCommand.Argument >>= arg ) ) + { + ucbhelper::cancelCommandExecution( + uno::makeAny( lang::IllegalArgumentException( + "Wrong argument type!", + static_cast< cppu::OWeakObject * >( this ), + -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 ) ); + } + 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::makeAny( lang::IllegalArgumentException( + "Wrong argument type!", + static_cast< cppu::OWeakObject * >( this ), + -1 ) ), + Environment ); + // Unreachable + } + + transfer( transferArgs, Environment ); + } + else if ( aCommand.Name == "post" ) + { + + // post + + + ucb::PostCommandArgument2 aArg; + if ( !( aCommand.Argument >>= aArg ) ) + { + ucbhelper::cancelCommandExecution( + uno::makeAny( lang::IllegalArgumentException( + "Wrong argument type!", + static_cast< cppu::OWeakObject * >( this ), + -1 ) ), + Environment ); + // Unreachable + } + + post( aArg, Environment ); + } + else if ( aCommand.Name == "lock" && + supportsExclusiveWriteLock( Environment ) ) + { + + // lock + + + lock( Environment ); + } + else if ( aCommand.Name == "unlock" && + supportsExclusiveWriteLock( Environment ) ) + { + + // unlock + + + unlock( Environment ); + } + else if ( aCommand.Name == "createNewContent" && + isFolder( Environment ) ) + { + + // createNewContent + + + ucb::ContentInfo aArg; + if ( !( aCommand.Argument >>= aArg ) ) + { + ucbhelper::cancelCommandExecution( + uno::makeAny( lang::IllegalArgumentException( + "Wrong argument type!", + static_cast< cppu::OWeakObject * >( this ), + -1 ) ), + Environment ); + // Unreachable + } + + aRet <<= createNewContent( aArg ); + } + else if ( aCommand.Name == "addProperty" ) + { + ucb::PropertyCommandArgument aPropArg; + if ( !( aCommand.Argument >>= aPropArg )) + { + ucbhelper::cancelCommandExecution( + uno::makeAny( lang::IllegalArgumentException( + "Wrong argument type!", + static_cast< cppu::OWeakObject * >( this ), + -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::makeAny( e ), Environment ); + } + catch ( const beans::IllegalTypeException&e ) + { + ucbhelper::cancelCommandExecution( uno::makeAny( e ), Environment ); + } + catch ( const lang::IllegalArgumentException&e ) + { + ucbhelper::cancelCommandExecution( uno::makeAny( e ), Environment ); + } + } + else if ( aCommand.Name == "removeProperty" ) + { + OUString sPropName; + if ( !( aCommand.Argument >>= sPropName ) ) + { + ucbhelper::cancelCommandExecution( + uno::makeAny( lang::IllegalArgumentException( + "Wrong argument type!", + static_cast< cppu::OWeakObject * >( this ), + -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::makeAny( e ), Environment ); + } + catch( const beans::NotRemoveableException &e ) + { + ucbhelper::cancelCommandExecution( uno::makeAny( e ), Environment ); + } + } + else + { + + // Unsupported command + + + ucbhelper::cancelCommandExecution( + uno::makeAny( ucb::UnsupportedCommandException( + aCommand.Name, + static_cast< cppu::OWeakObject * >( this ) ) ), + 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 ) ); + } + DAVResourceAccess::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", + static_cast< ::cppu::OWeakObject * >( this ), + -1 ); + + // Check property type. + if ( !UCBDeadPropertyValue::supportsType( aProperty.Type ) ) + throw beans::IllegalTypeException( + "\"addProperty\" unsupported Property.Type", + static_cast< ::cppu::OWeakObject * >( this ) ); + + // check default value + if ( aDefaultValue.hasValue() && aDefaultValue.getValueType() != aProperty.Type ) + throw beans::IllegalTypeException( + "\"addProperty\" DefaultValue does not match Property.Type", + static_cast< ::cppu::OWeakObject * >( this ) ); + + + // 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 ) ); + } + 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( + static_cast< cppu::OWeakObject * >( this ), + 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 & rType = getResourceType( xEnv ); + switch ( rType ) + { + 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 ) ); + } + 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( + static_cast< cppu::OWeakObject * >( this ), + 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 & rType = getResourceType( xEnv ); + switch ( rType ) + { + 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.get() ); +} + + +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 = SerfUri::unescape( m_aEscapedTitle ); + xContext.set( m_xContext ); + xIdentifier.set( m_xIdentifier ); + xProvider.set( m_xProvider.get() ); + xResAccess.reset( new DAVResourceAccess( *m_xResAccess ) ); + + // First, ask cache... + if ( m_xCachedProps.get() ) + { + 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 ( !m_bTransient && !bHasAll ) + { + // Obtain values from server... + + + // First, identify whether resource is DAV or not + bool bNetworkAccessAllowed = true; + const ResourceType & rType = getResourceType( xEnv, xResAccess, &bNetworkAccessAllowed ); + + if ( DAV == rType ) + { + // cache lookup... getResourceType may fill the props cache via + // PROPFIND! + if ( m_xCachedProps.get() ) + { + 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.getLength() ); + + if ( !m_aFailedPropNames.empty() ) + { + sal_Int32 nProps = 0; + sal_Int32 nCount = rProperties.getLength(); + for ( sal_Int32 n = 0; n < nCount; ++n ) + { + const OUString & rName = rProperties[ n ].Name; + + if ( std::none_of(m_aFailedPropNames.begin(), m_aFailedPropNames.end(), + [&rName](const OUString& rPropName) { return rPropName == rName; }) ) + { + aProperties[ nProps ] = rProperties[ n ]; + nProps++; + } + } + + aProperties.realloc( nProps ); + } + else + { + 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 ( xProps.get()) + 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.get() + && xProps->containsAllNames( + rProperties, aMissingProps ) ) + || !m_bDidGetOrHead ) + { + // Possibly the missing props can be obtained using a HEAD + // request. + + std::vector< OUString > aHeaderNames; + ContentProperties::UCBNamesToHTTPNames( + rProperties, + aHeaderNames, + true /* bIncludeUnmatched */ ); + + if ( !aHeaderNames.empty() ) + { + try + { + DAVResource resource; + xResAccess->HEAD( aHeaderNames, resource, xEnv ); + m_bDidGetOrHead = true; + + if ( xProps.get() ) + 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; + + // 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 + // TODO SC_NOT_FOUND is only for google-code server + if ( aLastException.getStatus() == SC_NOT_IMPLEMENTED || + aLastException.getStatus() == SC_METHOD_NOT_ALLOWED || + aLastException.getStatus() == SC_NOT_FOUND ) + { + lcl_sendPartialGETRequest( bError, + aLastException, + aMissingProps, + aHeaderNames, + xResAccess, + xProps, + xEnv ); + m_bDidGetOrHead = !bError; + } + + if ( bError ) + { + if ( !shouldAccessNetworkAfterException( aLastException ) ) + { + cancelCommandExecution( aLastException, xEnv ); + // unreachable + } + } + } + } + } + } + + // might trigger HTTP redirect. + // Therefore, title must be updated here. + SerfUri aUri( xResAccess->getURL() ); + aUnescapedTitle = aUri.GetPathBaseNameUnescaped(); + + if ( rType == 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. + + if ( rType == DAV ) + { + //xProps.reset( + // new ContentProperties( aUnescapedTitle ) ); + xProps->addProperty( + "Title", + uno::makeAny( aUnescapedTitle ), + true ); + } + else + { + if ( !xProps.get() ) + xProps.reset( new ContentProperties( aUnescapedTitle, false ) ); + else + xProps->addProperty( + "Title", + uno::makeAny( aUnescapedTitle ), + true ); + + xProps->addProperty( + "IsFolder", + uno::makeAny( false ), + true ); + xProps->addProperty( + "IsDocument", + uno::makeAny( true ), + true ); + xProps->addProperty( + "ContentType", + uno::makeAny( OUString(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 ) ); + } + + 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::makeAny( getBaseURI( xResAccess ) ), + true ); + } + else if ( rName == "CreatableContentsInfo" ) + { + // Add CreatableContentsInfo property, if requested. + bool bFolder = false; + xProps->getValue( "IsFolder" ) + >>= bFolder; + xProps->addProperty( + "CreatableContentsInfo", + uno::makeAny( 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.get() ) + m_xCachedProps.reset( new CachableContentProperties( *xProps ) ); + else + m_xCachedProps->addProperties( *xProps ); + + m_xResAccess.reset( new DAVResourceAccess( *xResAccess ) ); + m_aEscapedTitle = SerfUri::escapeSegment( 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() ); + uno::Sequence< beans::PropertyChangeEvent > aChanges( rValues.getLength() ); + sal_Int32 nChanged = 0; + + beans::PropertyChangeEvent aEvent; + aEvent.Source = static_cast< cppu::OWeakObject * >( this ); + 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! + aRet[ n ] <<= lang::IllegalAccessException( + "Property is read-only!", + static_cast< cppu::OWeakObject * >( this ) ); + continue; + } + + + // Mandatory props. + + + if ( rName == "ContentType" ) + { + // Read-only property! + aRet[ n ] <<= lang::IllegalAccessException( + "Property is read-only!", + static_cast< cppu::OWeakObject * >( this ) ); + } + else if ( rName == "IsDocument" ) + { + // Read-only property! + aRet[ n ] <<= lang::IllegalAccessException( + "Property is read-only!", + static_cast< cppu::OWeakObject * >( this ) ); + } + else if ( rName == "IsFolder" ) + { + // Read-only property! + aRet[ n ] <<= lang::IllegalAccessException( + "Property is read-only!", + static_cast< cppu::OWeakObject * >( this ) ); + } + else if ( rName == "Title" ) + { + OUString aNewValue; + if ( rValue.Value >>= aNewValue ) + { + // No empty titles! + if ( aNewValue.getLength() > 0 ) + { + try + { + SerfUri 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 & ) + { + aRet[ n ] <<= lang::IllegalArgumentException( + "Invalid content identifier!", + static_cast< cppu::OWeakObject * >( this ), + -1 ); + } + } + else + { + aRet[ n ] <<= lang::IllegalArgumentException( + "Empty title not allowed!", + static_cast< cppu::OWeakObject * >( this ), + -1 ); + } + } + else + { + aRet[ n ] <<= beans::IllegalTypeException( + "Property value has wrong type!", + static_cast< cppu::OWeakObject * >( this ) ); + } + } + 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! + aRet[ n ] <<= beans::UnknownPropertyException( + "Property is unknown!", + static_cast< cppu::OWeakObject * >( this ) ); + continue; + } + + if ( rName == "Size" ) + { + // Read-only property! + aRet[ n ] <<= lang::IllegalAccessException( + "Property is read-only!", + static_cast< cppu::OWeakObject * >( this ) ); + } + else if ( rName == "DateCreated" ) + { + // Read-only property! + aRet[ n ] <<= lang::IllegalAccessException( + "Property is read-only!", + static_cast< cppu::OWeakObject * >( this ) ); + } + else if ( rName == "DateModified" ) + { + // Read-only property! + aRet[ n ] <<= lang::IllegalAccessException( + "Property is read-only!", + static_cast< cppu::OWeakObject * >( this ) ); + } + else if ( rName == "MediaType" ) + { + // Read-only property! + // (but could be writable, if 'getcontenttype' would be) + aRet[ n ] <<= lang::IllegalAccessException( + "Property is read-only!", + static_cast< cppu::OWeakObject * >( this ) ); + } + if ( rName == "CreatableContentsInfo" ) + { + // Read-only property! + aRet[ n ] <<= lang::IllegalAccessException( + "Property is read-only!", + static_cast< cppu::OWeakObject * >( this ) ); + } + 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 ) + { + aRet[ n ] <<= e; + } + catch ( lang::WrappedTargetException const & e ) + { + aRet[ n ] <<= e; + } + catch ( beans::PropertyVetoException const & e ) + { + aRet[ n ] <<= e; + } + catch ( lang::IllegalArgumentException const & e ) + { + aRet[ n ] <<= e; + } + } + else + { + aRet[ n ] <<= uno::Exception( + "No property set for storing the value!", + static_cast< cppu::OWeakObject * >( this ) ); + } + } + } + } + } // for + + if ( !bTransient && (!aProppatchValues.empty()) ) + { + try + { + // Set property values at server. + 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. + aRet[ (*it) ] <<= MapDAVException( e, true ); + ++it; + } +#endif + } + } + + if ( bExchange ) + { + // Assemble new content identifier... + + OUString aNewURL = getParentURL(); + if ( aNewURL.lastIndexOf( '/' ) != ( aNewURL.getLength() - 1 ) ) + aNewURL += "/"; + + aNewURL += SerfUri::escapeSegment( aNewTitle ); + + uno::Reference< ucb::XContentIdentifier > xNewId + = new ::ucbhelper::ContentIdentifier( aNewURL ); + uno::Reference< ucb::XContentIdentifier > xOldId = xIdentifier; + + try + { + SerfUri sourceURI( xOldId->getContentIdentifier() ); + SerfUri targetURI( xNewId->getContentIdentifier() ); + targetURI.SetScheme( sourceURI.GetScheme() ); + + xResAccess->MOVE( + sourceURI.GetPath(), 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 . + aRet[ nTitlePos ] <<= uno::Exception( + "Exchange failed!", + static_cast< cppu::OWeakObject * >( this ) ); + } + } + catch ( DAVException const & e ) + { + // Do not set new title! + aNewTitle.clear(); + + // Set error . + aRet[ 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 = SerfUri::escapeSegment( 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::OpenCommandArgument2 & 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! + + OUString aMsg( "Non-folder resource cannot be opened as folder! Wrong Open Mode!" ); + + ucbhelper::cancelCommandExecution( + uno::makeAny( + lang::IllegalArgumentException( + aMsg, + static_cast< cppu::OWeakObject * >( this ), + -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::makeAny( + ucb::UnsupportedOpenModeException( + OUString(), + static_cast< cppu::OWeakObject * >( this ), + 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 ) ); + } + + DAVResource aResource; + std::vector< OUString > aHeaders; + + xResAccess->GET( xOut, aHeaders, aResource, xEnv ); + m_bDidGetOrHead = true; + + { + osl::MutexGuard aGuard( m_aMutex ); + + // cache headers. + if ( !m_xCachedProps.get()) + 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 + try + { + std::unique_ptr< DAVResourceAccess > xResAccess; + { + osl::MutexGuard aGuard( m_aMutex ); + + xResAccess.reset( + new DAVResourceAccess( *m_xResAccess ) ); + } + + // fill inputstream sync; return if all data present + DAVResource aResource; + std::vector< OUString > aHeaders; + + uno::Reference< io::XInputStream > xIn + = xResAccess->GET( aHeaders, aResource, xEnv ); + m_bDidGetOrHead = true; + + { + osl::MutexGuard aGuard( m_aMutex ); + + // cache headers. + if ( !m_xCachedProps.get()) + 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 ) + { + cancelCommandExecution( e, xEnv ); + // Unreachable + } + } + else + { + // Note: aOpenCommand.Sink may contain an XStream + // implementation. Support for this type of + // sink is optional... + ucbhelper::cancelCommandExecution( + uno::makeAny( + ucb::UnsupportedDataSinkException( + OUString(), + static_cast< cppu::OWeakObject * >( this ), + 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 ) ); + } + + 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 ) ); + } + + 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::makeAny( + ucb::UnsupportedDataSinkException( + OUString(), + static_cast< cppu::OWeakObject * >( this ), + 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::makeAny( ucb::MissingPropertiesException( + OUString(), + static_cast< cppu::OWeakObject * >( this ), + 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!", + static_cast< cppu::OWeakObject * >( this ), + ucb::NameClash::ERROR ); + + uno::Reference< task::XInteractionHandler > xIH; + + if ( Environment.is() ) + xIH = Environment->getInteractionHandler(); + + if ( xIH.is() ) + { + uno::Any aExAsAny( uno::makeAny( aEx ) ); + + rtl::Reference< ucbhelper::SimpleInteractionRequest > xRequest + = new ucbhelper::SimpleInteractionRequest( + aExAsAny, + ContinuationFlags::Approve + | ContinuationFlags::Disapprove ); + xIH->handle( xRequest.get() ); + + 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 ) + xResAccess->MKCOL( Environment ); + else + xResAccess->PUT( xInputStream, Environment ); + } + 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 + { + 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 + { + SerfUri aURI( aURL ); + aTitle = aURI.GetPathBaseNameUnescaped(); + } + catch ( DAVException const & ) + { + } + + ucbhelper::cancelCommandExecution( + uno::makeAny( + ucb::NameClashException( + OUString(), + static_cast< cppu::OWeakObject * >( this ), + 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::makeAny( + ucb::MissingInputStreamException( + OUString(), + static_cast< cppu::OWeakObject * >( this ) ) ), + Environment ); + // Unreachable + } + + try + { + 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.get() ); + xResAccess.reset( new DAVResourceAccess( *m_xResAccess ) ); + } + + OUString aTargetURI; + try + { + SerfUri sourceURI( rArgs.SourceURL ); + SerfUri 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::makeAny( + ucb::InteractiveBadTransferURLException( + "Unsupported URL scheme!", + static_cast< cppu::OWeakObject * >( this ) ) ), + 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::makeAny( ucb::InteractiveBadTransferURLException( + "Different hosts!", + static_cast< cppu::OWeakObject * >( this ) ) ), + 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. + + aSourceAccess.MOVE( sourceURI.GetPath(), + 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. + + aSourceAccess.COPY( sourceURI.GetPath(), + 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::makeAny( + ucb::NameClashException( + OUString(), + static_cast< cppu::OWeakObject * >( this ), + 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::makeAny( + ucb::UnsupportedNameClashException( + OUString(), + static_cast< cppu::OWeakObject * >( this ), + 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 ); + } +} + + +bool Content::supportsExclusiveWriteLock( + const uno::Reference< ucb::XCommandEnvironment >& Environment ) +{ + if ( getResourceType( Environment ) == DAV ) + { + if ( m_xCachedProps.get() ) + { + uno::Sequence< ucb::LockEntry > aSupportedLocks; + if ( m_xCachedProps->getValue( DAVProperties::SUPPORTEDLOCK ) + >>= aSupportedLocks ) + { + for ( sal_Int32 n = 0; n < aSupportedLocks.getLength(); ++n ) + { + if ( aSupportedLocks[ n ].Scope + == ucb::LockScope_EXCLUSIVE && + aSupportedLocks[ n ].Type + == ucb::LockType_WRITE ) + return true; + } + } + } + } + return false; +} + + +void Content::lock( + 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 ) ); + } + + uno::Any aOwnerAny; + aOwnerAny <<= OUString( "http://ucb.openoffice.org" ); + + ucb::Lock aLock( + ucb::LockScope_EXCLUSIVE, + ucb::LockType_WRITE, + ucb::LockDepth_ZERO, + aOwnerAny, + 180, // lock timeout in secs + //-1, // infinite lock + uno::Sequence< OUString >() ); + + xResAccess->LOCK( aLock, Environment ); + m_bLocked = true; + + { + osl::Guard< osl::Mutex > aGuard( m_aMutex ); + m_xResAccess.reset( new DAVResourceAccess( *xResAccess ) ); + } + } + catch ( DAVException const & e ) + { + 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 ) ); + } + + xResAccess->UNLOCK( Environment ); + m_bLocked = false; + + { + osl::Guard< osl::Mutex > aGuard( m_aMutex ); + m_xResAccess.reset( new DAVResourceAccess( *xResAccess ) ); + } + } + catch ( DAVException const & e ) + { + 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 identitity. + + // 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 ); + aProperties[ 0 ].Name = "IsFolder"; + aProperties[ 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( 1 ); + aArgs[ 0 ] <<= beans::PropertyValue( + "Uri", -1, + uno::makeAny(aURL), + beans::PropertyState_DIRECT_VALUE); + + aException <<= + ucb::InteractiveAugmentedIOException( + "Not found!", + static_cast< cppu::OWeakObject * >( this ), + 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(), + static_cast< cppu::OWeakObject * >( this ), + task::InteractionClassification_ERROR, + e.getData() ); + else + aException <<= + ucb::InteractiveNetworkReadException( + e.getData(), + static_cast< cppu::OWeakObject * >( this ), + task::InteractionClassification_ERROR, + e.getData() ); + break; + } + + case DAVException::DAV_HTTP_LOOKUP: + aException <<= + ucb::InteractiveNetworkResolveNameException( + OUString(), + static_cast< cppu::OWeakObject * >( this ), + 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_CONNECT: + aException <<= + ucb::InteractiveNetworkConnectException( + OUString(), + static_cast< cppu::OWeakObject * >( this ), + task::InteractionClassification_ERROR, + e.getData() ); + break; + +// @@@ No matching InteractiveNetwork*Exception +// case DAVException::DAV_HTTP_TIMEOUT: +// 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(), + static_cast< cppu::OWeakObject * >( this ), + -1 ); + break; + + case DAVException::DAV_LOCKED: +#if 1 + aException <<= + ucb::InteractiveLockingLockedException( + "Locked!", + static_cast< cppu::OWeakObject * >( this ), + 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!" ), + static_cast< cppu::OWeakObject * >( this ), + task::InteractionClassification_ERROR, + ucb::IOErrorCode_LOCKING_VIOLATION, + aArgs ); + } +#endif + break; + + case DAVException::DAV_LOCKED_SELF: + aException <<= + ucb::InteractiveLockingLockedException( + "Locked (self)!", + static_cast< cppu::OWeakObject * >( this ), + task::InteractionClassification_ERROR, + aURL, + true ); // SelfOwned + break; + + case DAVException::DAV_NOT_LOCKED: + aException <<= + ucb::InteractiveLockingNotLockedException( + "Not locked!", + static_cast< cppu::OWeakObject * >( this ), + task::InteractionClassification_ERROR, + aURL ); + break; + + case DAVException::DAV_LOCK_EXPIRED: + aException <<= + ucb::InteractiveLockingLockExpiredException( + "Lock expired!", + static_cast< cppu::OWeakObject * >( this ), + task::InteractionClassification_ERROR, + aURL ); + break; + + default: + aException <<= + ucb::InteractiveNetworkGeneralException( + OUString(), + static_cast< cppu::OWeakObject * >( this ), + task::InteractionClassification_ERROR ); + break; + } + + return aException; +} + + +// static +bool Content::shouldAccessNetworkAfterException( const DAVException & e ) +{ + if ( ( e.getStatus() == SC_NOT_FOUND ) || + ( e.getError() == DAVException::DAV_HTTP_LOOKUP ) || + ( e.getError() == DAVException::DAV_HTTP_CONNECT ) || + ( 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.get() ) + { + 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(); +} + + +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; + + 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 ); + aProperties[ 0 ].Name = "IsFolder"; + aProperties[ 1 ].Name = "IsDocument"; + aProperties[ 2 ].Name = "IsReadOnly"; + aProperties[ 3 ].Name = "MediaType"; + aProperties[ 4 ].Name = DAVProperties::SUPPORTEDLOCK; + + ContentProperties::UCBNamesToDAVNames( + aProperties, aPropNames ); + + rResAccess->PROPFIND( + DAVZERO, aPropNames, resources, xEnv ); + + // TODO - is this really only one? + if ( resources.size() == 1 ) + { + 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(); + + 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); + } + + // 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 >() ); + } + } + + 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); + } + 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; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/ucb/source/ucp/webdav/webdavcontent.hxx b/ucb/source/ucp/webdav/webdavcontent.hxx new file mode 100644 index 000000000..ad4e93229 --- /dev/null +++ b/ucb/source/ucp/webdav/webdavcontent.hxx @@ -0,0 +1,269 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + + +#ifndef INCLUDED_UCB_SOURCE_UCP_WEBDAV_WEBDAVCONTENT_HXX +#define INCLUDED_UCB_SOURCE_UCP_WEBDAV_WEBDAVCONTENT_HXX + +#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 OpenCommandArgument2; + struct PropertyCommandArgument; + struct PostCommandArgument2; + struct TransferInfo; +} + +namespace http_dav_ucp +{ + + +// UNO service name for the content. +#define WEBDAV_CONTENT_SERVICE_NAME "com.sun.star.ucb.WebDAVContent" + + +class ContentProvider; +class ContentProperties; +class CachableContentProperties; + +class Content : public ::ucbhelper::ContentImplHelper, + public css::ucb::XContentCreator +{ + enum ResourceType + { + UNKNOWN, + NON_DAV, + DAV + }; + + std::unique_ptr< DAVResourceAccess > m_xResAccess; + std::unique_ptr< CachableContentProperties > m_xCachedProps; // locally cached props + OUString m_aEscapedTitle; + ResourceType m_eResourceType; + ContentProvider* m_pProvider; // No need for a ref, base class holds object + bool m_bTransient; + bool m_bLocked; + bool m_bCollection; + bool m_bDidGetOrHead; + std::vector< OUString > m_aFailedPropNames; + +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::OpenCommandArgument2 & 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 ); + + bool supportsExclusiveWriteLock( + const css::uno::Reference< css::ucb::XCommandEnvironment >& Environment ); + + // 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() + throw() override; + virtual void SAL_CALL release() + throw() 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 ); +}; + +} + +#endif + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/ucb/source/ucp/webdav/webdavcontentcaps.cxx b/ucb/source/ucp/webdav/webdavcontentcaps.cxx new file mode 100644 index 000000000..14050e2a1 --- /dev/null +++ b/ucb/source/ucp/webdav/webdavcontentcaps.cxx @@ -0,0 +1,602 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this 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" + +using namespace com::sun::star; +using namespace http_dav_ucp; + + +// ContentProvider implementation. + + +bool ContentProvider::getProperty( + const OUString & rPropName, beans::Property & rProp, bool bStrict ) +{ + 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 + { + if ( bStrict ) + return false; + + // All unknown props are treated as: + rProp = beans::Property( + rPropName, + - 1, + cppu::UnoType<OUString>::get(), + beans::PropertyAttribute::BOUND ); + } + + return true; +} + + +// 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.get() ) + 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. + try + { + std::vector< DAVResourceInfo > props; + xResAccess->PROPFIND( DAVZERO, props, xEnv ); + + // Note: vector always contains exactly one resource info, because + // we used a depth of DAVZERO for PROPFIND. + 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.get() ) + { + 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 ); + + beans::Property aProp; + sal_Int32 n = 0; + + for ( const auto& rProp : aPropSet ) + { + xProvider->getProperty( rProp, aProp ); + aProperties[ 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 ); + + + // Mandatory commands + + + aCmdInfo[ 0 ] = + ucb::CommandInfo( + "getCommandInfo", + -1, + cppu::UnoType<void>::get() ); + aCmdInfo[ 1 ] = + ucb::CommandInfo( + "getPropertySetInfo", + -1, + cppu::UnoType<void>::get() ); + aCmdInfo[ 2 ] = + ucb::CommandInfo( + "getPropertyValues", + -1, + cppu::UnoType<uno::Sequence< beans::Property >>::get()); + aCmdInfo[ 3 ] = + ucb::CommandInfo( + "setPropertyValues", + -1, + cppu::UnoType<uno::Sequence< beans::PropertyValue >>::get()); + + + // Optional standard commands + + + aCmdInfo[ 4 ] = + ucb::CommandInfo( + "delete", + -1, + cppu::UnoType<bool>::get() ); + aCmdInfo[ 5 ] = + ucb::CommandInfo( + "insert", + -1, + cppu::UnoType<ucb::InsertCommandArgument>::get() ); + aCmdInfo[ 6 ] = + ucb::CommandInfo( + "open", + -1, + cppu::UnoType<ucb::OpenCommandArgument2>::get() ); + + + // New commands + + + aCmdInfo[ 7 ] = + ucb::CommandInfo( + "post", + -1, + cppu::UnoType<ucb::PostCommandArgument2>::get() ); + aCmdInfo[ 8 ] = + ucb::CommandInfo( + "addProperty", + -1, + cppu::UnoType<ucb::PropertyCommandArgument>::get() ); + aCmdInfo[ 9 ] = + ucb::CommandInfo( + "removeProperty", + -1, + cppu::UnoType<OUString>::get() ); + + bool bFolder = false; + + try + { + bFolder = isFolder( xEnv ); + } + catch ( uno::Exception const & ) + { + return aCmdInfo; + } + + bool bSupportsLocking = supportsExclusiveWriteLock( xEnv ); + + sal_Int32 nPos = aCmdInfo.getLength(); + sal_Int32 nMoreCmds = ( bFolder ? 2 : 0 ) + ( bSupportsLocking ? 2 : 0 ); + if ( nMoreCmds ) + aCmdInfo.realloc( nPos + nMoreCmds ); + else + return aCmdInfo; + + if ( bFolder ) + { + + // Optional standard commands + + + aCmdInfo[ nPos ] = + ucb::CommandInfo( + "transfer", + -1, + cppu::UnoType<ucb::TransferInfo>::get() ); + nPos++; + aCmdInfo[ nPos ] = + ucb::CommandInfo( + "createNewContent", + -1, + cppu::UnoType<ucb::ContentInfo>::get() ); + nPos++; + } + else + { + // no document-only commands at the moment. + } + + if ( bSupportsLocking ) + { + aCmdInfo[ nPos ] = + ucb::CommandInfo( + "lock", + -1, + cppu::UnoType<void>::get() ); + nPos++; + aCmdInfo[ 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/webdavdatasupplier.cxx b/ucb/source/ucp/webdav/webdavdatasupplier.cxx new file mode 100644 index 000000000..6001b8665 --- /dev/null +++ b/ucb/source/ucp/webdav/webdavdatasupplier.cxx @@ -0,0 +1,466 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this 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 <memory> +#include <utility> + +#include <com/sun/star/ucb/OpenMode.hpp> +#include <ucbhelper/contentidentifier.hxx> +#include <ucbhelper/providerhelper.hxx> +#include "webdavdatasupplier.hxx" +#include "webdavcontent.hxx" +#include "ContentProperties.hxx" +#include "DAVProperties.hxx" +#include "SerfUri.hxx" +#include <com/sun/star/ucb/IllegalIdentifierException.hpp> +#include <com/sun/star/ucb/ResultSetException.hpp> + +using namespace com::sun::star; +using namespace http_dav_ucp; + +namespace http_dav_ucp +{ + + +// struct ResultListEntry. + +namespace { + +struct ResultListEntry +{ + OUString aId; + uno::Reference< ucb::XContentIdentifier > xId; + uno::Reference< ucb::XContent > xContent; + uno::Reference< sdbc::XRow > xRow; + std::unique_ptr<ContentProperties> pData; + + explicit ResultListEntry( std::unique_ptr<ContentProperties> && pEntry ) : pData( std::move(pEntry) ) {} +}; + +} + +// ResultList. + + +typedef std::vector< ResultListEntry* > ResultList; + + +// struct DataSupplier_Impl. + + +struct DataSupplier_Impl +{ + osl::Mutex m_aMutex; + ResultList m_aResults; + rtl::Reference< Content > m_xContent; + uno::Reference< uno::XComponentContext > m_xContext; + sal_Int32 m_nOpenMode; + bool m_bCountFinal; + bool m_bThrowException; + + DataSupplier_Impl( + 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 ) {} + ~DataSupplier_Impl(); +}; + + +DataSupplier_Impl::~DataSupplier_Impl() +{ + for ( auto& rResultPtr : m_aResults ) + { + delete rResultPtr; + } +} + +} + + +// DataSupplier Implementation. + + +DataSupplier::DataSupplier( + const uno::Reference< uno::XComponentContext >& rxContext, + const rtl::Reference< Content >& rContent, + sal_Int32 nOpenMode ) +: m_pImpl(std::make_unique<DataSupplier_Impl>(rxContext, rContent, nOpenMode)) +{ +} + + +// virtual +DataSupplier::~DataSupplier() +{} + + +// virtual +OUString DataSupplier::queryContentIdentifierString( sal_uInt32 nIndex ) +{ + osl::Guard< osl::Mutex > aGuard( m_pImpl->m_aMutex ); + + if ( nIndex < m_pImpl->m_aResults.size() ) + { + OUString aId = m_pImpl->m_aResults[ nIndex ]->aId; + if ( aId.getLength() ) + { + // Already cached. + return aId; + } + } + + if ( getResult( nIndex ) ) + { + OUString aId = m_pImpl->m_xContent->getResourceAccess().getURL(); + + const ContentProperties& props + = *( m_pImpl->m_aResults[ nIndex ]->pData ); + + if ( ( aId.lastIndexOf( '/' ) + 1 ) != aId.getLength() ) + aId += "/"; + + aId += props.getEscapedTitle(); + + if ( props.isTrailingSlash() ) + aId += "/"; + + m_pImpl->m_aResults[ nIndex ]->aId = aId; + return aId; + } + return OUString(); +} + + +// virtual +uno::Reference< ucb::XContentIdentifier > +DataSupplier::queryContentIdentifier( sal_uInt32 nIndex ) +{ + osl::Guard< osl::Mutex > aGuard( m_pImpl->m_aMutex ); + + if ( nIndex < m_pImpl->m_aResults.size() ) + { + uno::Reference< ucb::XContentIdentifier > xId + = m_pImpl->m_aResults[ 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_pImpl->m_aResults[ 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_pImpl->m_aMutex ); + + if ( nIndex < m_pImpl->m_aResults.size() ) + { + uno::Reference< ucb::XContent > xContent + = m_pImpl->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_pImpl->m_xContent->getProvider()->queryContent( xId ); + m_pImpl->m_aResults[ 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_pImpl->m_aMutex ); + + if ( m_pImpl->m_aResults.size() > nIndex ) + { + // Result already present. + return true; + } + + // Obtain values... + if ( getData() ) + { + if ( m_pImpl->m_aResults.size() > nIndex ) + { + // Result already present. + return true; + } + } + + return false; +} + + +// virtual +sal_uInt32 DataSupplier::totalCount() +{ + // Obtain values... + getData(); + + return m_pImpl->m_aResults.size(); +} + + +// virtual +sal_uInt32 DataSupplier::currentCount() +{ + return m_pImpl->m_aResults.size(); +} + + +// virtual +bool DataSupplier::isCountFinal() +{ + return m_pImpl->m_bCountFinal; +} + + +// virtual +uno::Reference< sdbc::XRow > DataSupplier::queryPropertyValues( + sal_uInt32 nIndex ) +{ + osl::Guard< osl::Mutex > aGuard( m_pImpl->m_aMutex ); + + if ( nIndex < m_pImpl->m_aResults.size() ) + { + uno::Reference< sdbc::XRow > xRow = m_pImpl->m_aResults[ nIndex ]->xRow; + if ( xRow.is() ) + { + // Already cached. + return xRow; + } + } + + if ( getResult( nIndex ) ) + { + uno::Reference< sdbc::XRow > xRow + = Content::getPropertyValues( + m_pImpl->m_xContext, + getResultSet()->getProperties(), + *(m_pImpl->m_aResults[ nIndex ]->pData), + rtl::Reference< ::ucbhelper::ContentProviderImplHelper >( + m_pImpl->m_xContent->getProvider().get() ), + queryContentIdentifierString( nIndex ) ); + m_pImpl->m_aResults[ nIndex ]->xRow = xRow; + return xRow; + } + + return uno::Reference< sdbc::XRow >(); +} + + +// virtual +void DataSupplier::releasePropertyValues( sal_uInt32 nIndex ) +{ + osl::Guard< osl::Mutex > aGuard( m_pImpl->m_aMutex ); + + if ( nIndex < m_pImpl->m_aResults.size() ) + m_pImpl->m_aResults[ nIndex ]->xRow.clear(); +} + + +// virtual +void DataSupplier::close() +{ +} + + +// virtual +void DataSupplier::validate() +{ + if ( m_pImpl->m_bThrowException ) + throw ucb::ResultSetException(); +} + +bool DataSupplier::getData() +{ + osl::ClearableGuard< osl::Mutex > aGuard( m_pImpl->m_aMutex ); + + if ( !m_pImpl->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_pImpl->m_xContent->getResourceAccess() + .PROPFIND( DAVONE, + propertyNames, + resources, + getResultSet()->getEnvironment() ); + } + catch ( DAVException & ) + { + SAL_WARN( "ucb.ucp.webdav", "PROPFIND : DAVException" ); + m_pImpl->m_bThrowException = true; + } + + if ( !m_pImpl->m_bThrowException ) + { + try + { + SerfUri aURI( + m_pImpl->m_xContent->getResourceAccess().getURL() ); + OUString aPath = aURI.GetPath(); + + if ( aPath.endsWith("/") ) + aPath = aPath.copy( 0, aPath.getLength() - 1 ); + + aPath = SerfUri::unescape( 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 + { + SerfUri aCurrURI( rRes.uri ); + OUString aCurrPath = aCurrURI.GetPath(); + if ( aCurrPath.endsWith("/") ) + aCurrPath + = aCurrPath.copy( + 0, + aCurrPath.getLength() - 1 ); + + aCurrPath = SerfUri::unescape( 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_pImpl->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_pImpl->m_aResults.push_back( + new ResultListEntry( std::move(pContentProperties) ) ); + } + } + catch ( DAVException const & ) + { + } + } + + m_pImpl->m_bCountFinal = true; + + // Callback possible, because listeners may be informed! + aGuard.clear(); + getResultSet()->rowCountFinal(); + } + return !m_pImpl->m_bThrowException; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/ucb/source/ucp/webdav/webdavdatasupplier.hxx b/ucb/source/ucp/webdav/webdavdatasupplier.hxx new file mode 100644 index 000000000..ecd22000e --- /dev/null +++ b/ucb/source/ucp/webdav/webdavdatasupplier.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 . + */ + + +#ifndef INCLUDED_UCB_SOURCE_UCP_WEBDAV_WEBDAVDATASUPPLIER_HXX +#define INCLUDED_UCB_SOURCE_UCP_WEBDAV_WEBDAVDATASUPPLIER_HXX + +#include <sal/config.h> + +#include <memory> +#include <rtl/ref.hxx> +#include <ucbhelper/resultset.hxx> + +namespace http_dav_ucp { + +struct DataSupplier_Impl; +class Content; +struct DAVResource; +class ContentProperties; + +class DataSupplier : public ucbhelper::ResultSetDataSupplier +{ + std::unique_ptr<DataSupplier_Impl> m_pImpl; + +private: + 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; +}; + +} + +#endif + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/ucb/source/ucp/webdav/webdavprovider.cxx b/ucb/source/ucp/webdav/webdavprovider.cxx new file mode 100644 index 000000000..6c1d77b94 --- /dev/null +++ b/ucb/source/ucp/webdav/webdavprovider.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 <ucbhelper/contentidentifier.hxx> +#include <ucbhelper/getcomponentcontext.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() + throw() +{ + OWeakObject::acquire(); +} + +void SAL_CALL ContentProvider::release() + throw() +{ + 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. + +XSERVICEINFO_COMMOM_IMPL( ContentProvider, + "com.sun.star.comp.WebDAVContentProvider" ) +/// @throws css::uno::Exception +static css::uno::Reference< css::uno::XInterface > +ContentProvider_CreateInstance( const css::uno::Reference< css::lang::XMultiServiceFactory> & rSMgr ) +{ + css::lang::XServiceInfo* pX = + static_cast<css::lang::XServiceInfo*>(new ContentProvider( ucbhelper::getComponentContext(rSMgr) )); + return css::uno::Reference< css::uno::XInterface >::query( pX ); +} + +css::uno::Sequence< OUString > +ContentProvider::getSupportedServiceNames_Static() +{ + css::uno::Sequence< OUString > aSNS { WEBDAV_CONTENT_PROVIDER_SERVICE_NAME }; + return aSNS; +} + +// Service factory implementation. + + +ONE_INSTANCE_SERVICE_FACTORY_IMPL( ContentProvider ); + + +// 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 ).get(); + 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; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/ucb/source/ucp/webdav/webdavprovider.hxx b/ucb/source/ucp/webdav/webdavprovider.hxx new file mode 100644 index 000000000..e399178a9 --- /dev/null +++ b/ucb/source/ucp/webdav/webdavprovider.hxx @@ -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 . + */ + + +#ifndef INCLUDED_UCB_SOURCE_UCP_WEBDAV_WEBDAVPROVIDER_HXX +#define INCLUDED_UCB_SOURCE_UCP_WEBDAV_WEBDAVPROVIDER_HXX + +#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. +#define WEBDAV_CONTENT_PROVIDER_SERVICE_NAME "com.sun.star.ucb.WebDAVContentProvider" + +// 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 "http" +#define HTTPS_URL_SCHEME "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" + +#define HTTP_CONTENT_TYPE "application/" HTTP_URL_SCHEME "-content" + +#define WEBDAV_CONTENT_TYPE HTTP_CONTENT_TYPE +#define WEBDAV_COLLECTION_TYPE "application/" VNDSUNSTARWEBDAV_URL_SCHEME "-collection" + + +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() + throw() override; + virtual void SAL_CALL release() + throw() 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; + + static OUString getImplementationName_Static(); + static css::uno::Sequence< OUString > getSupportedServiceNames_Static(); + + static css::uno::Reference< css::lang::XSingleServiceFactory > + createServiceFactory( const css::uno::Reference< + css::lang::XMultiServiceFactory >& rxServiceMgr ); + + // 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, + bool bStrict = false ); +}; + +} + +#endif + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/ucb/source/ucp/webdav/webdavresponseparser.cxx b/ucb/source/ucp/webdav/webdavresponseparser.cxx new file mode 100644 index 000000000..b03a392d5 --- /dev/null +++ b/ucb/source/ucp/webdav/webdavresponseparser.cxx @@ -0,0 +1,897 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this 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 <com/sun/star/xml/sax/XDocumentHandler.hpp> +#include <cppuhelper/implbase.hxx> +#include <com/sun/star/xml/sax/Parser.hpp> +#include <com/sun/star/xml/sax/InputSource.hpp> +#include <comphelper/processfactory.hxx> +#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 <sal/log.hxx> + +using namespace com::sun::star; + + +// WebDAVNamespace enum and StringToEnum converter +namespace +{ + enum WebDAVNamespace + { + WebDAVNamespace_unknown = 0, + WebDAVNamespace_DAV, + WebDAVNamespace_ucb_openoffice_org_dav_props, + + WebDAVNamespace_last + }; + + WebDAVNamespace StrToWebDAVNamespace(const OUString& rStr) + { + if(rStr == "DAV:") + { + return WebDAVNamespace_DAV; + } + else if(rStr == "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_multistatus, + WebDAVName_response, + WebDAVName_href, + WebDAVName_propstat, + WebDAVName_prop, + WebDAVName_resourcetype, + WebDAVName_collection, + WebDAVName_getcontenttype, + WebDAVName_supportedlock, + WebDAVName_lockentry, + WebDAVName_lockscope, + WebDAVName_locktoken, + WebDAVName_exclusive, + WebDAVName_locktype, + WebDAVName_owner, + WebDAVName_timeout, + WebDAVName_write, + WebDAVName_shared, + WebDAVName_status, + WebDAVName_getlastmodified, + WebDAVName_creationdate, + WebDAVName_getcontentlength, + + 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("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("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)); + } + + 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; + 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; + } + } + } + } + } + + 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()) + { + // When collecting property names and parent is prop, just append the prop name + // to the collection, no need to parse deeper + maPropStatNames.push_back(mpContext->getNamespace() + mpContext->getName()); + } + 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()) + { + maHref = mpContext->getWhiteSpace(); + } + 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[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 = sTimeout.copy(7).toInt64(); + 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[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_activelock: + { + maLock.Type = maLockType; + maLock.Scope = maLockScope; + maResult_Lock.push_back(maLock); + } + [[fallthrough]]; // I hope intentional? + 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(maHref); + + aDAVResourceInfo.properties = maResponseNames; + maResult_PropName.push_back(aDAVResourceInfo); + } + } + } + break; + } + } + break; + } + case WebDAVNamespace_ucb_openoffice_org_dav_props: + { + 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 ParserInputSrouce + 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 + WebDAVResponseParser* 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->*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/webdavresponseparser.hxx b/ucb/source/ucp/webdav/webdavresponseparser.hxx new file mode 100644 index 000000000..546d44dfb --- /dev/null +++ b/ucb/source/ucp/webdav/webdavresponseparser.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/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#ifndef INCLUDED_UCB_SOURCE_UCP_WEBDAV_WEBDAVRESPONSEPARSER_HXX +#define INCLUDED_UCB_SOURCE_UCP_WEBDAV_WEBDAVRESPONSEPARSER_HXX + +#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 + + +#endif // INCLUDED_UCB_SOURCE_UCP_WEBDAV_WEBDAVRESPONSEPARSER_HXX + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/ucb/source/ucp/webdav/webdavresultset.cxx b/ucb/source/ucp/webdav/webdavresultset.cxx new file mode 100644 index 000000000..e67dd1558 --- /dev/null +++ b/ucb/source/ucp/webdav/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/webdavresultset.hxx b/ucb/source/ucp/webdav/webdavresultset.hxx new file mode 100644 index 000000000..cb1b7c0f5 --- /dev/null +++ b/ucb/source/ucp/webdav/webdavresultset.hxx @@ -0,0 +1,51 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + + +#ifndef INCLUDED_UCB_SOURCE_UCP_WEBDAV_WEBDAVRESULTSET_HXX +#define INCLUDED_UCB_SOURCE_UCP_WEBDAV_WEBDAVRESULTSET_HXX + +#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 ); +}; + +} + +#endif + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/ucb/source/ucp/webdav/webdavservices.cxx b/ucb/source/ucp/webdav/webdavservices.cxx new file mode 100644 index 000000000..419c9740d --- /dev/null +++ b/ucb/source/ucp/webdav/webdavservices.cxx @@ -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/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this 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/XMultiServiceFactory.hpp> +#include <com/sun/star/lang/XSingleServiceFactory.hpp> +#include "webdavprovider.hxx" + +using namespace com::sun::star; + +extern "C" SAL_DLLPUBLIC_EXPORT void * ucpdav1_component_getFactory( + const char * pImplName, void * pServiceManager, void * /*pRegistryKey*/ ) +{ + void * pRet = nullptr; + + uno::Reference< lang::XMultiServiceFactory > xSMgr( + static_cast< lang::XMultiServiceFactory * >( pServiceManager ) ); + uno::Reference< lang::XSingleServiceFactory > xFactory; + + + // WebDAV Content Provider. + + + if ( ::http_dav_ucp::ContentProvider::getImplementationName_Static(). + equalsAscii( pImplName ) ) + { + xFactory = ::http_dav_ucp::ContentProvider::createServiceFactory( xSMgr ); + } + + + if ( xFactory.is() ) + { + xFactory->acquire(); + pRet = xFactory.get(); + } + + return pRet; +} + + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ |