diff options
Diffstat (limited to '')
41 files changed, 14933 insertions, 0 deletions
diff --git a/ucb/source/ucp/webdav-curl/ContentProperties.cxx b/ucb/source/ucp/webdav-curl/ContentProperties.cxx new file mode 100644 index 000000000..d78d138ab --- /dev/null +++ b/ucb/source/ucp/webdav-curl/ContentProperties.cxx @@ -0,0 +1,566 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include <memory> +#include <com/sun/star/util/DateTime.hpp> +#include "CurlUri.hxx" +#include "DAVResource.hxx" +#include "DAVProperties.hxx" +#include "DateTimeHelper.hxx" +#include "webdavprovider.hxx" +#include "ContentProperties.hxx" + +using namespace com::sun::star; +using namespace http_dav_ucp; + +/* +============================================================================= + + Property Mapping + +============================================================================= +HTTP (entity header) WebDAV (property) UCB (property) +============================================================================= + +Allow +Content-Encoding +Content-Language getcontentlanguage +Content-Length getcontentlength Size +Content-Location +Content-MD5 +Content-Range +Content-Type getcontenttype MediaType +Expires +Last-Modified getlastmodified DateModified + creationdate DateCreated + resourcetype IsFolder,IsDocument,ContentType + displayname +ETag (actually getetag +a response header ) + lockdiscovery + supportedlock + source + Title (always taken from URI) + +============================================================================= + +Important: HTTP headers will not be mapped to DAV properties; only to UCB + properties. (Content-Length,Content-Type,Last-Modified) +*/ + + +// ContentProperties Implementation. + + +// static member! +uno::Any ContentProperties::m_aEmptyAny; + +ContentProperties::ContentProperties( const DAVResource& rResource ) +: m_xProps( new PropertyValueMap ), + m_bTrailingSlash( false ) +{ + assert(!rResource.uri.isEmpty() && + "ContentProperties ctor - Empty resource URI!"); + + // Title + try + { + CurlUri const aURI( rResource.uri ); + m_aEscapedTitle = aURI.GetPathBaseName(); + + (*m_xProps)[ OUString( "Title" ) ] + = PropertyValue( + uno::Any( aURI.GetPathBaseNameUnescaped() ), true ); + } + catch ( DAVException const & ) + { + (*m_xProps)[ OUString( "Title" ) ] + = PropertyValue( + uno::Any( + OUString( "*** unknown ***" ) ), + true ); + } + + for ( const auto& rProp : rResource.properties ) + { + addProperty( rProp ); + } + + if ( rResource.uri.endsWith("/") ) + m_bTrailingSlash = true; +} + + +ContentProperties::ContentProperties( + const OUString & rTitle, bool bFolder ) +: m_xProps( new PropertyValueMap ), + m_bTrailingSlash( false ) +{ + (*m_xProps)[ OUString( "Title" ) ] + = PropertyValue( uno::Any( rTitle ), true ); + (*m_xProps)[ OUString( "IsFolder" ) ] + = PropertyValue( uno::Any( bFolder ), true ); + (*m_xProps)[ OUString( "IsDocument" ) ] + = PropertyValue( uno::Any( bool( !bFolder ) ), true ); +} + + +ContentProperties::ContentProperties( const OUString & rTitle ) +: m_xProps( new PropertyValueMap ), + m_bTrailingSlash( false ) +{ + (*m_xProps)[ OUString( "Title" ) ] + = PropertyValue( uno::Any( rTitle ), true ); +} + + +ContentProperties::ContentProperties() +: m_xProps( new PropertyValueMap ), + m_bTrailingSlash( false ) +{ +} + + +ContentProperties::ContentProperties( const ContentProperties & rOther ) +: m_aEscapedTitle( rOther.m_aEscapedTitle ), + m_xProps( rOther.m_xProps + ? new PropertyValueMap( *rOther.m_xProps ) + : new PropertyValueMap ), + m_bTrailingSlash( rOther.m_bTrailingSlash ) +{ +} + + +bool ContentProperties::contains( const OUString & rName ) const +{ + if ( get( rName ) ) + return true; + else + return false; +} + + +const uno::Any & ContentProperties::getValue( + const OUString & rName ) const +{ + const PropertyValue * pProp = get( rName ); + if ( pProp ) + return pProp->value(); + else + return m_aEmptyAny; +} + + +const PropertyValue * ContentProperties::get( + const OUString & rName ) const +{ + PropertyValueMap::const_iterator it = m_xProps->find( rName ); + const PropertyValueMap::const_iterator end = m_xProps->end(); + + if ( it == end ) + { + it = std::find_if(m_xProps->cbegin(), end, + [&rName](const PropertyValueMap::value_type& rEntry) { + return rEntry.first.equalsIgnoreAsciiCase( rName ); + }); + if ( it != end ) + return &(*it).second; + + return nullptr; + } + else + return &(*it).second; +} + + +// static +void ContentProperties::UCBNamesToDAVNames( + const uno::Sequence< beans::Property > & rProps, + std::vector< OUString > & propertyNames ) +{ + + // Assemble list of DAV properties to obtain from server. + // Append DAV properties needed to obtain requested UCB props. + + + // DAV UCB + // creationdate <- DateCreated + // getlastmodified <- DateModified + // getcontenttype <- MediaType + // getcontentlength <- Size + // resourcetype <- IsFolder, IsDocument, ContentType + // (taken from URI) <- Title + + bool bCreationDate = false; + bool bLastModified = false; + bool bContentType = false; + bool bContentLength = false; + bool bResourceType = false; + + sal_Int32 nCount = rProps.getLength(); + for ( sal_Int32 n = 0; n < nCount; ++n ) + { + const beans::Property & rProp = rProps[ n ]; + + if ( rProp.Name == "Title" ) + { + // Title is always obtained from resource's URI. + continue; + } + else if ( rProp.Name == "DateCreated" || + ( rProp.Name == DAVProperties::CREATIONDATE ) ) + { + if ( !bCreationDate ) + { + propertyNames.push_back( DAVProperties::CREATIONDATE ); + bCreationDate = true; + } + } + else if ( rProp.Name == "DateModified" || + ( rProp.Name == DAVProperties::GETLASTMODIFIED ) ) + { + if ( !bLastModified ) + { + propertyNames.push_back( + DAVProperties::GETLASTMODIFIED ); + bLastModified = true; + } + } + else if ( rProp.Name == "MediaType" || + ( rProp.Name == DAVProperties::GETCONTENTTYPE ) ) + { + if ( !bContentType ) + { + propertyNames.push_back( + DAVProperties::GETCONTENTTYPE ); + bContentType = true; + } + } + else if ( rProp.Name == "Size" || + ( rProp.Name == DAVProperties::GETCONTENTLENGTH ) ) + { + if ( !bContentLength ) + { + propertyNames.push_back( + DAVProperties::GETCONTENTLENGTH ); + bContentLength = true; + } + } + else if ( rProp.Name == "ContentType" || + rProp.Name == "IsDocument" || + rProp.Name == "IsFolder" || + ( rProp.Name == DAVProperties::RESOURCETYPE ) ) + { + if ( !bResourceType ) + { + propertyNames.push_back( DAVProperties::RESOURCETYPE ); + bResourceType = true; + } + } + else + { + propertyNames.push_back( rProp.Name ); + } + } +} + + +// static +void ContentProperties::UCBNamesToHTTPNames( + const uno::Sequence< beans::Property > & rProps, + std::vector< OUString > & propertyNames ) +{ + + // Assemble list of HTTP header names to obtain from server. + // Append HTTP headers needed to obtain requested UCB props. + + + // HTTP UCB + // Last-Modified <- DateModified + // Content-Type <- MediaType + // Content-Length <- Size + + sal_Int32 nCount = rProps.getLength(); + for ( sal_Int32 n = 0; n < nCount; ++n ) + { + const beans::Property & rProp = rProps[ n ]; + + if ( rProp.Name == "DateModified" ) + { + propertyNames.push_back( OUString( "Last-Modified" ) ); + } + else if ( rProp.Name == "MediaType" ) + { + propertyNames.push_back( OUString( "Content-Type" ) ); + } + else if ( rProp.Name == "Size" ) + { + propertyNames.push_back( OUString( "Content-Length" ) ); + } + else + { + propertyNames.push_back( rProp.Name ); + } + } +} + + +bool ContentProperties::containsAllNames( + const uno::Sequence< beans::Property >& rProps, + std::vector< OUString > & rNamesNotContained ) const +{ + rNamesNotContained.clear(); + + sal_Int32 nCount = rProps.getLength(); + for ( sal_Int32 n = 0; n < nCount; ++n ) + { + const OUString & rName = rProps[ n ].Name; + if ( !contains( rName ) ) + { + // Not found. + rNamesNotContained.push_back( rName ); + } + } + + return ( rNamesNotContained.size() == 0 ); +} + + +void ContentProperties::addProperties( + const std::vector< OUString > & rProps, + const ContentProperties & rContentProps ) +{ + for ( const OUString & rName : rProps ) + { + if ( !contains( rName ) ) // ignore duplicates + { + const PropertyValue * pProp = rContentProps.get( rName ); + if ( pProp ) + { + // Add it. + addProperty( rName, pProp->value(), pProp->isCaseSensitive() ); + } + else + { + addProperty( rName, uno::Any(), false ); + } + } + } +} + +void ContentProperties::addProperty( const DAVPropertyValue & rProp ) +{ + addProperty( rProp.Name, rProp.Value, rProp.IsCaseSensitive ); +} + + +void ContentProperties::addProperty( const OUString & rName, + const css::uno::Any & rValue, + bool bIsCaseSensitive ) +{ + if ( rName == DAVProperties::CREATIONDATE ) + { + // Map DAV:creationdate to UCP:DateCreated + OUString aValue; + rValue >>= aValue; + util::DateTime aDate; + DateTimeHelper::convert( aValue, aDate ); + + (*m_xProps)[ OUString( "DateCreated" ) ] + = PropertyValue( uno::Any( aDate ), true ); + } + // else if ( rName.equals( DAVProperties::DISPLAYNAME ) ) + // { + // } + // else if ( rName.equals( DAVProperties::GETCONTENTLANGUAGE ) ) + // { + // } + else if ( rName == DAVProperties::GETCONTENTLENGTH ) + { + // Map DAV:getcontentlength to UCP:Size + OUString aValue; + rValue >>= aValue; + + (*m_xProps)[ OUString( "Size" ) ] + = PropertyValue( uno::Any( aValue.toInt64() ), true ); + } + else if ( rName.equalsIgnoreAsciiCase( "Content-Length" ) ) + { + // Do NOT map Content-Length entity header to DAV:getcontentlength! + // Only DAV resources have this property. + + // Map Content-Length entity header to UCP:Size + OUString aValue; + rValue >>= aValue; + + (*m_xProps)[ OUString( "Size" ) ] + = PropertyValue( uno::Any( aValue.toInt64() ), true ); + } + else if ( rName == DAVProperties::GETCONTENTTYPE ) + { + // Map DAV:getcontenttype to UCP:MediaType (1:1) + (*m_xProps)[ OUString( "MediaType" ) ] + = PropertyValue( rValue, true ); + } + else if ( rName.equalsIgnoreAsciiCase( "Content-Type" ) ) + { + // Do NOT map Content-Type entity header to DAV:getcontenttype! + // Only DAV resources have this property. + + // Map DAV:getcontenttype to UCP:MediaType (1:1) + (*m_xProps)[ OUString( "MediaType" ) ] + = PropertyValue( rValue, true ); + } + // else if ( rName.equals( DAVProperties::GETETAG ) ) + // { + // } + else if ( rName == DAVProperties::GETLASTMODIFIED ) + { + // Map the DAV:getlastmodified entity header to UCP:DateModified + OUString aValue; + rValue >>= aValue; + util::DateTime aDate; + DateTimeHelper::convert( aValue, aDate ); + + (*m_xProps)[ OUString( "DateModified" ) ] + = PropertyValue( uno::Any( aDate ), true ); + } + else if ( rName.equalsIgnoreAsciiCase( "Last-Modified" ) ) + { + // Do not map Last-Modified entity header to DAV:getlastmodified! + // Only DAV resources have this property. + + // Map the Last-Modified entity header to UCP:DateModified + OUString aValue; + rValue >>= aValue; + util::DateTime aDate; + DateTimeHelper::convert( aValue, aDate ); + + (*m_xProps)[ OUString( "DateModified" ) ] + = PropertyValue( uno::Any( aDate ), true ); + } + // else if ( rName.equals( DAVProperties::LOCKDISCOVERY ) ) + // { + // } + else if ( rName == DAVProperties::RESOURCETYPE ) + { + OUString aValue; + rValue >>= aValue; + + // Map DAV:resourcetype to UCP:IsFolder, UCP:IsDocument, UCP:ContentType + bool bFolder = + aValue.equalsIgnoreAsciiCase( "collection" ); + + (*m_xProps)[ OUString( "IsFolder" ) ] + = PropertyValue( uno::Any( bFolder ), true ); + (*m_xProps)[ OUString( "IsDocument" ) ] + = PropertyValue( uno::Any( bool( !bFolder ) ), true ); + (*m_xProps)[ OUString( "ContentType" ) ] + = PropertyValue( uno::Any( bFolder + ? 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-curl/ContentProperties.hxx b/ucb/source/ucp/webdav-curl/ContentProperties.hxx new file mode 100644 index 000000000..0256450c2 --- /dev/null +++ b/ucb/source/ucp/webdav-curl/ContentProperties.hxx @@ -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 . + */ + + +#pragma once + +#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. + // The vector filled by this method can directly be handed over to + // DAVResourceAccess::PROPFIND. The result from PROPFIND + // (vector< DAVResource >) can be used to create a ContentProperties + // instance which can map DAV properties back to UCB properties. + static void UCBNamesToDAVNames( const css::uno::Sequence< css::beans::Property > & rProps, + std::vector< OUString > & resources ); + + // Maps the UCB property names contained in rProps with their HTTP header + // counterparts, if possible. All unmappable properties will be included + // unchanged in resulting vector. + // The vector filled by this method can directly be handed over to + // DAVResourceAccess::HEAD. The result from HEAD (vector< DAVResource >) + // can be used to create a ContentProperties instance which can map header + // names back to UCB properties. + static void UCBNamesToHTTPNames( const css::uno::Sequence< css::beans::Property > & rProps, + std::vector< OUString > & resources ); + + // return true, if all properties contained in rProps are contained in + // this ContentProperties instance. Otherwise, false will be returned. + // rNamesNotContained contain the missing names. + bool containsAllNames( + const css::uno::Sequence< css::beans::Property >& rProps, + std::vector< OUString > & rNamesNotContained ) const; + + // adds all properties described by rProps that are actually contained in + // rContentProps to this instance. In case of duplicates the value + // already contained in this will left unchanged. + void addProperties( const std::vector< OUString > & rProps, + const ContentProperties & rContentProps ); + + // overwrites probably existing entry. + void addProperty( const OUString & rName, + const css::uno::Any & rValue, + bool bIsCaseSensitive ); + + // overwrites probably existing entry. + void addProperty( const DAVPropertyValue & rProp ); + + bool isTrailingSlash() const { return m_bTrailingSlash; } + + const OUString & getEscapedTitle() const { return m_aEscapedTitle; } + + // Not good to expose implementation details, but this is actually an + // internal class. + const std::unique_ptr< PropertyValueMap > & getProperties() const + { return m_xProps; } + +private: + OUString m_aEscapedTitle; + std::unique_ptr< PropertyValueMap > m_xProps; + bool m_bTrailingSlash; + + static css::uno::Any m_aEmptyAny; + + ContentProperties & operator=( const ContentProperties & ); // n.i. + + const PropertyValue * get( const OUString & rName ) const; +}; + +class CachableContentProperties +{ +private: + ContentProperties m_aProps; + + CachableContentProperties & operator=( const CachableContentProperties & ); // n.i. + CachableContentProperties( const CachableContentProperties & ); // n.i. + +public: + explicit CachableContentProperties( const ContentProperties & rProps ); + + void addProperties( const ContentProperties & rProps ); + + void addProperties( const std::vector< DAVPropertyValue > & rProps ); + + bool containsAllNames( + const css::uno::Sequence< css::beans::Property >& rProps, + std::vector< OUString > & rNamesNotContained ) const + { return m_aProps.containsAllNames( rProps, rNamesNotContained ); } + + const css::uno::Any & + getValue( const OUString & rName ) const + { return m_aProps.getValue( rName ); } + + operator const ContentProperties & () const { return m_aProps; } +}; + +} // namespace http_dav_ucp + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/ucb/source/ucp/webdav-curl/CurlSession.cxx b/ucb/source/ucp/webdav-curl/CurlSession.cxx new file mode 100644 index 000000000..f05797b76 --- /dev/null +++ b/ucb/source/ucp/webdav-curl/CurlSession.cxx @@ -0,0 +1,2442 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4; fill-column: 100 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ + +#include "CurlSession.hxx" + +#include "SerfLockStore.hxx" +#include "DAVProperties.hxx" +#include "UCBDeadPropertyValue.hxx" +#include "webdavresponseparser.hxx" + +#include <comphelper/attributelist.hxx> +#include <comphelper/scopeguard.hxx> +#include <comphelper/string.hxx> + +#include <o3tl/safeint.hxx> +#include <o3tl/string_view.hxx> + +#include <officecfg/Inet.hxx> + +#include <com/sun/star/beans/NamedValue.hpp> +#include <com/sun/star/io/Pipe.hpp> +#include <com/sun/star/io/SequenceInputStream.hpp> +#include <com/sun/star/io/SequenceOutputStream.hpp> +#include <com/sun/star/xml/sax/Writer.hpp> + +#include <osl/time.h> +#include <sal/log.hxx> +#include <rtl/uri.hxx> +#include <rtl/strbuf.hxx> +#include <rtl/ustrbuf.hxx> +#include <config_version.h> + +#include <map> +#include <optional> +#include <tuple> + +using namespace ::com::sun::star; + +namespace +{ +/// globals container +struct Init +{ + /// note: LockStore has its own mutex and calls CurlSession from its thread + /// so don't call LockStore with m_Mutex held to prevent deadlock. + ::http_dav_ucp::SerfLockStore LockStore; + + Init() + { + if (curl_global_init(CURL_GLOBAL_ALL) != CURLE_OK) + { + assert(!"curl_global_init failed"); + } + } + // do not call curl_global_cleanup() - this is not the only client of curl +}; +Init g_Init; + +struct ResponseHeaders +{ + ::std::vector<::std::pair<::std::vector<OString>, ::std::optional<long>>> HeaderFields; + CURL* pCurl; + ResponseHeaders(CURL* const i_pCurl) + : pCurl(i_pCurl) + { + } +}; + +auto GetResponseCode(ResponseHeaders const& rHeaders) -> ::std::optional<long> +{ + return (rHeaders.HeaderFields.empty()) ? ::std::optional<long>{} + : rHeaders.HeaderFields.back().second; +} + +struct DownloadTarget +{ + uno::Reference<io::XOutputStream> xOutStream; + ResponseHeaders const& rHeaders; + DownloadTarget(uno::Reference<io::XOutputStream> const& i_xOutStream, + ResponseHeaders const& i_rHeaders) + : xOutStream(i_xOutStream) + , rHeaders(i_rHeaders) + { + } +}; + +struct UploadSource +{ + uno::Sequence<sal_Int8> const& rInData; + size_t nPosition; + UploadSource(uno::Sequence<sal_Int8> const& i_rInData) + : rInData(i_rInData) + , nPosition(0) + { + } +}; + +auto GetErrorString(CURLcode const rc, char const* const pErrorBuffer = nullptr) -> OString +{ + char const* const pMessage( // static fallback + (pErrorBuffer && pErrorBuffer[0] != '\0') ? pErrorBuffer : curl_easy_strerror(rc)); + return OString::Concat("(") + OString::number(sal_Int32(rc)) + ") " + pMessage; +} + +auto GetErrorStringMulti(CURLMcode const mc) -> OString +{ + return OString::Concat("(") + OString::number(sal_Int32(mc)) + ") " + curl_multi_strerror(mc); +} + +/// represent an option to be passed to curl_easy_setopt() +struct CurlOption +{ + CURLoption const Option; + enum class Type + { + Pointer, + Long, + CurlOffT + }; + Type const Tag; + union { + void const* const pValue; + long /*const*/ lValue; + curl_off_t /*const*/ cValue; + }; + char const* const pExceptionString; + + CurlOption(CURLoption const i_Option, void const* const i_Value, + char const* const i_pExceptionString) + : Option(i_Option) + , Tag(Type::Pointer) + , pValue(i_Value) + , pExceptionString(i_pExceptionString) + { + } + // Depending on platform, curl_off_t may be "long" or a larger type + // so cannot use overloading to distinguish these cases. + CurlOption(CURLoption const i_Option, curl_off_t const i_Value, + char const* const i_pExceptionString, Type const type = Type::Long) + : Option(i_Option) + , Tag(type) + , pExceptionString(i_pExceptionString) + { + static_assert(sizeof(long) <= sizeof(curl_off_t)); + switch (type) + { + case Type::Long: + lValue = i_Value; + break; + case Type::CurlOffT: + cValue = i_Value; + break; + default: + assert(false); + } + } +}; + +// NOBODY will prevent logging the response body in ProcessRequest() exception +// handler, so only use it if logging is disabled +const CurlOption g_NoBody{ CURLOPT_NOBODY, + sal_detail_log_report(SAL_DETAIL_LOG_LEVEL_INFO, "ucb.ucp.webdav.curl") + == SAL_DETAIL_LOG_ACTION_IGNORE + ? 1L + : 0L, + nullptr }; + +/// combined guard class to ensure things are released in correct order, +/// particularly in ProcessRequest() error handling +class Guard +{ +private: + /// mutex *first* because m_oGuard requires it + ::std::unique_lock<::std::mutex> m_Lock; + ::std::vector<CurlOption> const m_Options; + ::http_dav_ucp::CurlUri const& m_rURI; + CURL* const m_pCurl; + +public: + explicit Guard(::std::mutex& rMutex, ::std::vector<CurlOption> const& rOptions, + ::http_dav_ucp::CurlUri const& rURI, CURL* const pCurl) + : m_Lock(rMutex, ::std::defer_lock) + , m_Options(rOptions) + , m_rURI(rURI) + , m_pCurl(pCurl) + { + Acquire(); + } + ~Guard() + { + if (m_Lock.owns_lock()) + { + Release(); + } + } + + void Acquire() + { + assert(!m_Lock.owns_lock()); + m_Lock.lock(); + for (auto const& it : m_Options) + { + CURLcode rc(CURL_LAST); // warning C4701 + if (it.Tag == CurlOption::Type::Pointer) + { + rc = curl_easy_setopt(m_pCurl, it.Option, it.pValue); + } + else if (it.Tag == CurlOption::Type::Long) + { + rc = curl_easy_setopt(m_pCurl, it.Option, it.lValue); + } + else if (it.Tag == CurlOption::Type::CurlOffT) + { + rc = curl_easy_setopt(m_pCurl, it.Option, it.cValue); + } + else + { + assert(false); + } + if (it.pExceptionString != nullptr) + { + if (rc != CURLE_OK) + { + SAL_WARN("ucb.ucp.webdav.curl", + "set " << it.pExceptionString << " failed: " << GetErrorString(rc)); + throw ::http_dav_ucp::DAVException( + ::http_dav_ucp::DAVException::DAV_SESSION_CREATE, + ::http_dav_ucp::ConnectionEndPointString(m_rURI.GetHost(), + m_rURI.GetPort())); + } + } + else // many of the options cannot fail + { + assert(rc == CURLE_OK); + } + } + } + void Release() + { + assert(m_Lock.owns_lock()); + for (auto const& it : m_Options) + { + CURLcode rc(CURL_LAST); // warning C4701 + if (it.Tag == CurlOption::Type::Pointer) + { + rc = curl_easy_setopt(m_pCurl, it.Option, nullptr); + } + else if (it.Tag == CurlOption::Type::Long) + { + rc = curl_easy_setopt(m_pCurl, it.Option, 0L); + } + else if (it.Tag == CurlOption::Type::CurlOffT) + { + rc = curl_easy_setopt(m_pCurl, it.Option, curl_off_t(-1)); + } + else + { + assert(false); + } + assert(rc == CURLE_OK); + (void)rc; + } + m_Lock.unlock(); + } +}; + +} // namespace + +namespace http_dav_ucp +{ +// libcurl callbacks: + +static int debug_callback(CURL* handle, curl_infotype type, char* data, size_t size, + void* /*userdata*/) +{ + char const* pType(nullptr); + switch (type) + { + case CURLINFO_TEXT: + SAL_INFO("ucb.ucp.webdav.curl", "debug log: " << handle << ": " << data); + return 0; + case CURLINFO_HEADER_IN: + SAL_INFO("ucb.ucp.webdav.curl", + "CURLINFO_HEADER_IN: " << handle << ": " << OString(data, size)); + return 0; + case CURLINFO_HEADER_OUT: + { + // unlike IN, this is all headers in one call + OString tmp(data, size); + sal_Int32 const start(tmp.indexOf("Authorization: ")); + if (start != -1) + { + sal_Int32 const end(tmp.indexOf("\r\n", start)); + assert(end != -1); + sal_Int32 const len(SAL_N_ELEMENTS("Authorization: ") - 1); + tmp = tmp.replaceAt( + start + len, end - start - len, + OStringConcatenation(OString::number(end - start - len) + " bytes redacted")); + } + SAL_INFO("ucb.ucp.webdav.curl", "CURLINFO_HEADER_OUT: " << handle << ": " << tmp); + return 0; + } + case CURLINFO_DATA_IN: + pType = "CURLINFO_DATA_IN"; + break; + case CURLINFO_DATA_OUT: + pType = "CURLINFO_DATA_OUT"; + break; + case CURLINFO_SSL_DATA_IN: + pType = "CURLINFO_SSL_DATA_IN"; + break; + case CURLINFO_SSL_DATA_OUT: + pType = "CURLINFO_SSL_DATA_OUT"; + break; + default: + SAL_WARN("ucb.ucp.webdav.curl", "unexpected debug log type"); + return 0; + } + SAL_INFO("ucb.ucp.webdav.curl", "debug log: " << handle << ": " << pType << " " << size); + return 0; +} + +static size_t write_callback(char* const ptr, size_t const size, size_t const nmemb, + void* const userdata) +{ + auto* const pTarget(static_cast<DownloadTarget*>(userdata)); + if (!pTarget) // looks like ~every request may have a response body + { + return nmemb; + } + assert(size == 1); // says the man page + (void)size; + assert(pTarget->xOutStream.is()); + auto const oResponseCode(GetResponseCode(pTarget->rHeaders)); + if (!oResponseCode) + { + return 0; // that is an error + } + // always write, for exception handler in ProcessRequest() + // if (200 <= *oResponseCode && *oResponseCode < 300) + { + try + { + uno::Sequence<sal_Int8> const data(reinterpret_cast<sal_Int8*>(ptr), nmemb); + pTarget->xOutStream->writeBytes(data); + } + catch (...) + { + SAL_WARN("ucb.ucp.webdav.curl", "exception in write_callback"); + return 0; // error + } + } + // else: ignore the body? CurlSession will check the status eventually + return nmemb; +} + +static size_t read_callback(char* const buffer, size_t const size, size_t const nitems, + void* const userdata) +{ + auto* const pSource(static_cast<UploadSource*>(userdata)); + assert(pSource); + size_t const nBytes(size * nitems); + size_t nRet(0); + try + { + assert(pSource->nPosition <= o3tl::make_unsigned(pSource->rInData.getLength())); + nRet = ::std::min<size_t>(pSource->rInData.getLength() - pSource->nPosition, nBytes); + ::std::memcpy(buffer, pSource->rInData.getConstArray() + pSource->nPosition, nRet); + pSource->nPosition += nRet; + } + catch (...) + { + SAL_WARN("ucb.ucp.webdav.curl", "exception in read_callback"); + return CURL_READFUNC_ABORT; // error + } + return nRet; +} + +static size_t header_callback(char* const buffer, size_t const size, size_t const nitems, + void* const userdata) +{ + auto* const pHeaders(static_cast<ResponseHeaders*>(userdata)); + assert(pHeaders); +#if 0 + if (!pHeaders) // TODO maybe not needed in every request? not sure + { + return nitems; + } +#endif + assert(size == 1); // says the man page + (void)size; + try + { + if (nitems <= 2) + { + // end of header, body follows... + if (pHeaders->HeaderFields.empty()) + { + SAL_WARN("ucb.ucp.webdav.curl", "header_callback: empty header?"); + return 0; // error + } + // unfortunately there's no separate callback when the body begins, + // so have to manually retrieve the status code here + long statusCode(SC_NONE); + auto rc = curl_easy_getinfo(pHeaders->pCurl, CURLINFO_RESPONSE_CODE, &statusCode); + assert(rc == CURLE_OK); + (void)rc; + // always put the current response code here - wasn't necessarily in this header + pHeaders->HeaderFields.back().second.emplace(statusCode); + } + else if (buffer[0] == ' ' || buffer[0] == '\t') // folded header field? + { + size_t i(0); + do + { + ++i; + } while (i == ' ' || i == '\t'); + if (pHeaders->HeaderFields.empty() || pHeaders->HeaderFields.back().second + || pHeaders->HeaderFields.back().first.empty()) + { + SAL_WARN("ucb.ucp.webdav.curl", + "header_callback: folded header field without start"); + return 0; // error + } + pHeaders->HeaderFields.back().first.back() + += OString::Concat(" ") + ::std::string_view(&buffer[i], nitems - i); + } + else + { + if (pHeaders->HeaderFields.empty() || pHeaders->HeaderFields.back().second) + { + pHeaders->HeaderFields.emplace_back(); + } + pHeaders->HeaderFields.back().first.emplace_back(OString(buffer, nitems)); + } + } + catch (...) + { + SAL_WARN("ucb.ucp.webdav.curl", "exception in header_callback"); + return 0; // error + } + return nitems; +} + +static auto ProcessHeaders(::std::vector<OString> const& rHeaders) -> ::std::map<OUString, OUString> +{ + ::std::map<OUString, OUString> ret; + for (OString const& rLine : rHeaders) + { + OString line; + if (!rLine.endsWith("\r\n", &line)) + { + SAL_WARN("ucb.ucp.webdav.curl", "invalid header field (no CRLF)"); + continue; + } + if (line.startsWith("HTTP/") // first line + || line.isEmpty()) // last line + { + continue; + } + auto const nColon(line.indexOf(':')); + if (nColon == -1) + { + { + SAL_WARN("ucb.ucp.webdav.curl", "invalid header field (no :)"); + } + continue; + } + if (nColon == 0) + { + SAL_WARN("ucb.ucp.webdav.curl", "invalid header field (empty name)"); + continue; + } + // case insensitive; must be ASCII + auto const name(::rtl::OStringToOUString(line.copy(0, nColon).toAsciiLowerCase(), + RTL_TEXTENCODING_ASCII_US)); + sal_Int32 nStart(nColon + 1); + while (nStart < line.getLength() && (line[nStart] == ' ' || line[nStart] == '\t')) + { + ++nStart; + } + sal_Int32 nEnd(line.getLength()); + while (nStart < nEnd && (line[nEnd - 1] == ' ' || line[nEnd - 1] == '\t')) + { + --nEnd; + } + // RFC 7230 says that only ASCII works reliably anyway (neon also did this) + auto const value(::rtl::OStringToOUString(line.subView(nStart, nEnd - nStart), + RTL_TEXTENCODING_ASCII_US)); + auto const it(ret.find(name)); + if (it != ret.end()) + { + it->second = it->second + "," + value; + } + else + { + ret[name] = value; + } + } + return ret; +} + +static auto ExtractRequestedHeaders( + ResponseHeaders const& rHeaders, + ::std::pair<::std::vector<OUString> const&, DAVResource&> const* const pRequestedHeaders) + -> void +{ + ::std::map<OUString, OUString> const headerMap( + ProcessHeaders(rHeaders.HeaderFields.back().first)); + if (pRequestedHeaders) + { + for (OUString const& rHeader : pRequestedHeaders->first) + { + auto const it(headerMap.find(rHeader.toAsciiLowerCase())); + if (it != headerMap.end()) + { + DAVPropertyValue value; + value.IsCaseSensitive = false; + value.Name = it->first; + value.Value <<= it->second; + pRequestedHeaders->second.properties.push_back(value); + } + } + } +} + +// this appears to be the only way to get the "realm" from libcurl +static auto ExtractRealm(ResponseHeaders const& rHeaders, char const* const pAuthHeaderName) + -> ::std::optional<OUString> +{ + ::std::map<OUString, OUString> const headerMap( + ProcessHeaders(rHeaders.HeaderFields.back().first)); + auto const it(headerMap.find(OUString::createFromAscii(pAuthHeaderName).toAsciiLowerCase())); + if (it == headerMap.end()) + { + SAL_WARN("ucb.ucp.webdav.curl", "cannot find auth header"); + return {}; + } + // It may be possible that the header contains multiple methods each with + // a different realm - extract only the first one bc the downstream API + // only supports one anyway. + // case insensitive! + auto i(it->second.toAsciiLowerCase().indexOf("realm=")); + // is optional + if (i == -1) + { + SAL_INFO("ucb.ucp.webdav.curl", "auth header has no realm"); + return {}; + } + // no whitespace allowed before or after = + i += ::std::strlen("realm="); + if (it->second.getLength() < i + 2 || it->second[i] != '\"') + { + SAL_WARN("ucb.ucp.webdav.curl", "no realm value"); + return {}; + } + ++i; + OUStringBuffer buf; + while (i < it->second.getLength() && it->second[i] != '\"') + { + if (it->second[i] == '\\') // quoted-pair escape + { + ++i; + if (it->second.getLength() <= i) + { + SAL_WARN("ucb.ucp.webdav.curl", "unterminated quoted-pair"); + return {}; + } + } + buf.append(it->second[i]); + ++i; + } + if (it->second.getLength() <= i) + { + SAL_WARN("ucb.ucp.webdav.curl", "unterminated realm"); + return {}; + } + return buf.makeStringAndClear(); +} + +CurlSession::CurlSession(uno::Reference<uno::XComponentContext> const& xContext, + ::rtl::Reference<DAVSessionFactory> const& rpFactory, OUString const& rURI, + uno::Sequence<beans::NamedValue> const& rFlags, + ::ucbhelper::InternetProxyDecider const& rProxyDecider) + : DAVSession(rpFactory) + , m_xContext(xContext) + , m_Flags(rFlags) + , m_URI(rURI) + , m_Proxy(rProxyDecider.getProxy(m_URI.GetScheme(), m_URI.GetHost(), m_URI.GetPort())) +{ + assert(m_URI.GetScheme() == "http" || m_URI.GetScheme() == "https"); + m_pCurlMulti.reset(curl_multi_init()); + if (!m_pCurlMulti) + { + SAL_WARN("ucb.ucp.webdav.curl", "curl_multi_init failed"); + throw DAVException(DAVException::DAV_SESSION_CREATE, + ConnectionEndPointString(m_URI.GetHost(), m_URI.GetPort())); + } + m_pCurl.reset(curl_easy_init()); + if (!m_pCurl) + { + SAL_WARN("ucb.ucp.webdav.curl", "curl_easy_init failed"); + throw DAVException(DAVException::DAV_SESSION_CREATE, + ConnectionEndPointString(m_URI.GetHost(), m_URI.GetPort())); + } + curl_version_info_data const* const pVersion(curl_version_info(CURLVERSION_NOW)); + assert(pVersion); + SAL_INFO("ucb.ucp.webdav.curl", + "curl version: " << pVersion->version << " " << pVersion->host + << " features: " << ::std::hex << pVersion->features << " ssl: " + << pVersion->ssl_version << " libz: " << pVersion->libz_version); + // 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: + OString const useragent( + OString::Concat("LibreOffice " LIBO_VERSION_DOTTED " denylistedbackend/") + + ::std::string_view(pVersion->version, strlen(pVersion->version)) + " " + + pVersion->ssl_version); + // looks like an explicit "User-Agent" header in CURLOPT_HTTPHEADER + // will override CURLOPT_USERAGENT, see Curl_http_useragent(), so no need + // to check anything here + auto rc = curl_easy_setopt(m_pCurl.get(), CURLOPT_USERAGENT, useragent.getStr()); + if (rc != CURLE_OK) + { + SAL_WARN("ucb.ucp.webdav.curl", "CURLOPT_USERAGENT failed: " << GetErrorString(rc)); + throw DAVException(DAVException::DAV_SESSION_CREATE, + ConnectionEndPointString(m_URI.GetHost(), m_URI.GetPort())); + } + m_ErrorBuffer[0] = '\0'; + // this supposedly gives the highest quality error reporting + rc = curl_easy_setopt(m_pCurl.get(), CURLOPT_ERRORBUFFER, m_ErrorBuffer); + assert(rc == CURLE_OK); +#if 1 + // just for debugging... + rc = curl_easy_setopt(m_pCurl.get(), CURLOPT_DEBUGFUNCTION, debug_callback); + assert(rc == CURLE_OK); +#endif + rc = curl_easy_setopt(m_pCurl.get(), CURLOPT_VERBOSE, 1L); + assert(rc == CURLE_OK); + // accept any encoding supported by libcurl + rc = curl_easy_setopt(m_pCurl.get(), CURLOPT_ACCEPT_ENCODING, ""); + if (rc != CURLE_OK) + { + SAL_WARN("ucb.ucp.webdav.curl", "CURLOPT_ACCEPT_ENCODING failed: " << GetErrorString(rc)); + throw DAVException(DAVException::DAV_SESSION_CREATE, + ConnectionEndPointString(m_URI.GetHost(), m_URI.GetPort())); + } + auto const connectTimeout(officecfg::Inet::Settings::ConnectTimeout::get()); + // default is 300s + rc = curl_easy_setopt(m_pCurl.get(), CURLOPT_CONNECTTIMEOUT, + ::std::max<long>(2L, ::std::min<long>(connectTimeout, 180L))); + if (rc != CURLE_OK) + { + SAL_WARN("ucb.ucp.webdav.curl", "CURLOPT_CONNECTTIMEOUT failed: " << GetErrorString(rc)); + throw DAVException(DAVException::DAV_SESSION_CREATE, + ConnectionEndPointString(m_URI.GetHost(), m_URI.GetPort())); + } + auto const readTimeout(officecfg::Inet::Settings::ReadTimeout::get()); + m_nReadTimeout = ::std::max<int>(20, ::std::min<long>(readTimeout, 180)) * 1000; + // default is infinite + rc = curl_easy_setopt(m_pCurl.get(), CURLOPT_TIMEOUT, 300L); + if (rc != CURLE_OK) + { + SAL_WARN("ucb.ucp.webdav.curl", "CURLOPT_TIMEOUT failed: " << GetErrorString(rc)); + throw DAVException(DAVException::DAV_SESSION_CREATE, + ConnectionEndPointString(m_URI.GetHost(), m_URI.GetPort())); + } + rc = curl_easy_setopt(m_pCurl.get(), CURLOPT_WRITEFUNCTION, &write_callback); + assert(rc == CURLE_OK); + rc = curl_easy_setopt(m_pCurl.get(), CURLOPT_READFUNCTION, &read_callback); + assert(rc == CURLE_OK); + rc = curl_easy_setopt(m_pCurl.get(), CURLOPT_HEADERFUNCTION, &header_callback); + assert(rc == CURLE_OK); + // tdf#149921 by default, with schannel (WNT) connection fails if revocation + // lists cannot be checked; try to limit the checking to when revocation + // lists can actually be retrieved (usually not the case for self-signed CA) +#if CURL_AT_LEAST_VERSION(7, 70, 0) + rc = curl_easy_setopt(m_pCurl.get(), CURLOPT_SSL_OPTIONS, CURLSSLOPT_REVOKE_BEST_EFFORT); + assert(rc == CURLE_OK); + rc = curl_easy_setopt(m_pCurl.get(), CURLOPT_PROXY_SSL_OPTIONS, CURLSSLOPT_REVOKE_BEST_EFFORT); + assert(rc == CURLE_OK); +#endif + // set this initially, may be overwritten during authentication + rc = curl_easy_setopt(m_pCurl.get(), CURLOPT_HTTPAUTH, CURLAUTH_ANY); + assert(rc == CURLE_OK); // ANY is always available + // always set CURLOPT_PROXY to suppress proxy detection in libcurl + OString const utf8Proxy(OUStringToOString(m_Proxy.aName, RTL_TEXTENCODING_UTF8)); + rc = curl_easy_setopt(m_pCurl.get(), CURLOPT_PROXY, utf8Proxy.getStr()); + if (rc != CURLE_OK) + { + SAL_WARN("ucb.ucp.webdav.curl", "CURLOPT_PROXY failed: " << GetErrorString(rc)); + throw DAVException(DAVException::DAV_SESSION_CREATE, + ConnectionEndPointString(m_Proxy.aName, m_Proxy.nPort)); + } + if (!m_Proxy.aName.isEmpty()) + { + rc = curl_easy_setopt(m_pCurl.get(), CURLOPT_PROXYPORT, static_cast<long>(m_Proxy.nPort)); + assert(rc == CURLE_OK); + // set this initially, may be overwritten during authentication + rc = curl_easy_setopt(m_pCurl.get(), CURLOPT_PROXYAUTH, CURLAUTH_ANY); + assert(rc == CURLE_OK); // ANY is always available + } + auto const it(::std::find_if(m_Flags.begin(), m_Flags.end(), + [](auto const& rFlag) { return rFlag.Name == "KeepAlive"; })); + if (it != m_Flags.end() && it->Value.get<bool>()) + { + // neon would close the connection from ne_end_request(), this seems + // to be the equivalent and not CURLOPT_TCP_KEEPALIVE + rc = curl_easy_setopt(m_pCurl.get(), CURLOPT_FORBID_REUSE, 1L); + assert(rc == CURLE_OK); + } +} + +CurlSession::~CurlSession() {} + +auto CurlSession::CanUse(OUString const& rURI, uno::Sequence<beans::NamedValue> const& rFlags) + -> bool +{ + try + { + CurlUri const uri(rURI); + + return m_URI.GetScheme() == uri.GetScheme() && m_URI.GetHost() == uri.GetHost() + && m_URI.GetPort() == uri.GetPort() && m_Flags == rFlags; + } + catch (DAVException const&) + { + return false; + } +} + +auto CurlSession::UsesProxy() -> bool +{ + assert(m_URI.GetScheme() == "http" || m_URI.GetScheme() == "https"); + return !m_Proxy.aName.isEmpty(); +} + +auto CurlSession::abort() -> void +{ + // note: abort() was a no-op since OOo 3.2 and before that it crashed. + bool expected(false); + // it would be pointless to lock m_Mutex here as the other thread holds it + if (m_AbortFlag.compare_exchange_strong(expected, true)) + { + // This function looks safe to call without m_Mutex as long as the + // m_pCurlMulti handle is not destroyed, and the caller must own a ref + // to this object which keeps it alive; it should cause poll to return. + curl_multi_wakeup(m_pCurlMulti.get()); + } +} + +/// this is just a bunch of static member functions called from CurlSession +struct CurlProcessor +{ + static auto URIReferenceToURI(CurlSession& rSession, OUString const& rURIReference) -> CurlUri; + + static auto ProcessRequestImpl( + CurlSession& rSession, CurlUri const& rURI, OUString const& rMethod, + curl_slist* pRequestHeaderList, uno::Reference<io::XOutputStream> const* pxOutStream, + uno::Sequence<sal_Int8> const* pInData, + ::std::pair<::std::vector<OUString> const&, DAVResource&> const* pRequestedHeaders, + ResponseHeaders& rHeaders) -> void; + + static auto ProcessRequest( + CurlSession& rSession, CurlUri const& rURI, OUString const& rMethod, + ::std::vector<CurlOption> const& rOptions, DAVRequestEnvironment const* pEnv, + ::std::unique_ptr<curl_slist, deleter_from_fn<curl_slist, curl_slist_free_all>> + pRequestHeaderList, + uno::Reference<io::XOutputStream> const* pxOutStream, + uno::Reference<io::XInputStream> const* pxInStream, + ::std::pair<::std::vector<OUString> const&, DAVResource&> const* pRequestedHeaders) -> void; + + static auto + PropFind(CurlSession& rSession, CurlUri const& rURI, Depth depth, + ::std::tuple<::std::vector<OUString> const&, ::std::vector<DAVResource>* const, + ::std::vector<ucb::Lock>* const> const* o_pRequestedProperties, + ::std::vector<DAVResourceInfo>* const o_pResourceInfos, + DAVRequestEnvironment const& rEnv) -> void; + + static auto MoveOrCopy(CurlSession& rSession, OUString const& rSourceURIReference, + ::std::u16string_view rDestinationURI, DAVRequestEnvironment const& rEnv, + bool isOverwrite, char const* pMethod) -> void; + + static auto Lock(CurlSession& rSession, CurlUri const& rURI, DAVRequestEnvironment const* pEnv, + ::std::unique_ptr<curl_slist, deleter_from_fn<curl_slist, curl_slist_free_all>> + pRequestHeaderList, + uno::Reference<io::XInputStream> const* pxInStream) + -> ::std::vector<::std::pair<ucb::Lock, sal_Int32>>; + + static auto Unlock(CurlSession& rSession, CurlUri const& rURI, + DAVRequestEnvironment const* pEnv) -> void; +}; + +auto CurlProcessor::URIReferenceToURI(CurlSession& rSession, OUString const& rURIReference) + -> CurlUri +{ + // No need to acquire rSession.m_Mutex because accessed members are const. + if (rSession.UsesProxy()) + // very odd, but see DAVResourceAccess::getRequestURI() :-/ + { + assert(rURIReference.startsWith("http://") || rURIReference.startsWith("https://")); + return CurlUri(rURIReference); + } + else + { + assert(rURIReference.startsWith("/")); + return rSession.m_URI.CloneWithRelativeRefPathAbsolute(rURIReference); + } +} + +/// main function to initiate libcurl requests +auto CurlProcessor::ProcessRequestImpl( + CurlSession& rSession, CurlUri const& rURI, OUString const& rMethod, + curl_slist* const pRequestHeaderList, + uno::Reference<io::XOutputStream> const* const pxOutStream, + uno::Sequence<sal_Int8> const* const pInData, + ::std::pair<::std::vector<OUString> const&, DAVResource&> const* const pRequestedHeaders, + ResponseHeaders& rHeaders) -> void +{ + ::comphelper::ScopeGuard const g([&]() { + auto rc = curl_easy_setopt(rSession.m_pCurl.get(), CURLOPT_HEADERDATA, nullptr); + assert(rc == CURLE_OK); + (void)rc; + if (pxOutStream) + { + rc = curl_easy_setopt(rSession.m_pCurl.get(), CURLOPT_WRITEDATA, nullptr); + assert(rc == CURLE_OK); + } + if (pInData) + { + rc = curl_easy_setopt(rSession.m_pCurl.get(), CURLOPT_READDATA, nullptr); + assert(rc == CURLE_OK); + rc = curl_easy_setopt(rSession.m_pCurl.get(), CURLOPT_UPLOAD, 0L); + assert(rc == CURLE_OK); + } + if (pRequestHeaderList) + { + rc = curl_easy_setopt(rSession.m_pCurl.get(), CURLOPT_HTTPHEADER, nullptr); + assert(rc == CURLE_OK); + } + }); + + if (pRequestHeaderList) + { + auto rc = curl_easy_setopt(rSession.m_pCurl.get(), CURLOPT_HTTPHEADER, pRequestHeaderList); + assert(rc == CURLE_OK); + (void)rc; + } + + auto rc = curl_easy_setopt(rSession.m_pCurl.get(), CURLOPT_CURLU, rURI.GetCURLU()); + assert(rc == CURLE_OK); // can't fail since 7.63.0 + + rc = curl_easy_setopt(rSession.m_pCurl.get(), CURLOPT_HEADERDATA, &rHeaders); + assert(rc == CURLE_OK); + ::std::optional<DownloadTarget> oDownloadTarget; + if (pxOutStream) + { + oDownloadTarget.emplace(*pxOutStream, rHeaders); + rc = curl_easy_setopt(rSession.m_pCurl.get(), CURLOPT_WRITEDATA, &*oDownloadTarget); + assert(rc == CURLE_OK); + } + ::std::optional<UploadSource> oUploadSource; + if (pInData) + { + oUploadSource.emplace(*pInData); + rc = curl_easy_setopt(rSession.m_pCurl.get(), CURLOPT_READDATA, &*oUploadSource); + assert(rc == CURLE_OK); + } + rSession.m_ErrorBuffer[0] = '\0'; + + // note: easy handle must be added for *every* transfer! + // otherwise it gets stuck in MSTATE_MSGSENT forever after 1st transfer + auto mc = curl_multi_add_handle(rSession.m_pCurlMulti.get(), rSession.m_pCurl.get()); + if (mc != CURLM_OK) + { + SAL_WARN("ucb.ucp.webdav.curl", + "curl_multi_add_handle failed: " << GetErrorStringMulti(mc)); + throw DAVException( + DAVException::DAV_SESSION_CREATE, + ConnectionEndPointString(rSession.m_URI.GetHost(), rSession.m_URI.GetPort())); + } + ::comphelper::ScopeGuard const gg([&]() { + mc = curl_multi_remove_handle(rSession.m_pCurlMulti.get(), rSession.m_pCurl.get()); + if (mc != CURLM_OK) + { + SAL_WARN("ucb.ucp.webdav.curl", + "curl_multi_remove_handle failed: " << GetErrorStringMulti(mc)); + } + }); + + // this is where libcurl actually does something + rc = CURL_LAST; // clear current value + int nRunning; + do + { + mc = curl_multi_perform(rSession.m_pCurlMulti.get(), &nRunning); + if (mc != CURLM_OK) + { + SAL_WARN("ucb.ucp.webdav.curl", + "curl_multi_perform failed: " << GetErrorStringMulti(mc)); + throw DAVException( + DAVException::DAV_HTTP_CONNECT, + ConnectionEndPointString(rSession.m_URI.GetHost(), rSession.m_URI.GetPort())); + } + if (nRunning == 0) + { // short request like HEAD on loopback could be done in first call + break; + } + int nFDs; + mc = curl_multi_poll(rSession.m_pCurlMulti.get(), nullptr, 0, rSession.m_nReadTimeout, + &nFDs); + if (mc != CURLM_OK) + { + SAL_WARN("ucb.ucp.webdav.curl", "curl_multi_poll failed: " << GetErrorStringMulti(mc)); + throw DAVException( + DAVException::DAV_HTTP_CONNECT, + ConnectionEndPointString(rSession.m_URI.GetHost(), rSession.m_URI.GetPort())); + } + if (rSession.m_AbortFlag.load()) + { // flag was set by abort() -> not sure what exception to throw? + throw DAVException(DAVException::DAV_HTTP_ERROR, "abort() was called", 0); + } + } while (nRunning != 0); + // there should be exactly 1 CURLMsg now, but the interface is + // extensible so future libcurl versions could yield additional things + do + { + CURLMsg const* const pMsg = curl_multi_info_read(rSession.m_pCurlMulti.get(), &nRunning); + if (pMsg && pMsg->msg == CURLMSG_DONE) + { + assert(pMsg->easy_handle == rSession.m_pCurl.get()); + rc = pMsg->data.result; + } + else + { + SAL_WARN("ucb.ucp.webdav.curl", "curl_multi_info_read unexpected result"); + } + } while (nRunning != 0); + + // error handling part 1: libcurl errors + if (rc != CURLE_OK) + { + // TODO: is there any value in extracting CURLINFO_OS_ERRNO + SAL_WARN("ucb.ucp.webdav.curl", + "curl_easy_perform failed: " << GetErrorString(rc, rSession.m_ErrorBuffer)); + switch (rc) + { + case CURLE_UNSUPPORTED_PROTOCOL: + throw DAVException(DAVException::DAV_UNSUPPORTED); + case CURLE_COULDNT_RESOLVE_PROXY: + throw DAVException( + DAVException::DAV_HTTP_LOOKUP, + ConnectionEndPointString(rSession.m_Proxy.aName, rSession.m_Proxy.nPort)); + case CURLE_COULDNT_RESOLVE_HOST: + throw DAVException( + DAVException::DAV_HTTP_LOOKUP, + ConnectionEndPointString(rSession.m_URI.GetHost(), rSession.m_URI.GetPort())); + case CURLE_COULDNT_CONNECT: + case CURLE_SSL_CONNECT_ERROR: + case CURLE_SSL_CERTPROBLEM: + case CURLE_SSL_CIPHER: + case CURLE_PEER_FAILED_VERIFICATION: + case CURLE_SSL_ISSUER_ERROR: + case CURLE_SSL_PINNEDPUBKEYNOTMATCH: + case CURLE_SSL_INVALIDCERTSTATUS: + case CURLE_FAILED_INIT: +#if CURL_AT_LEAST_VERSION(7, 69, 0) + case CURLE_QUIC_CONNECT_ERROR: +#endif + throw DAVException( + DAVException::DAV_HTTP_CONNECT, + ConnectionEndPointString(rSession.m_URI.GetHost(), rSession.m_URI.GetPort())); + case CURLE_REMOTE_ACCESS_DENIED: + case CURLE_LOGIN_DENIED: + case CURLE_AUTH_ERROR: + throw DAVException( + DAVException::DAV_HTTP_AUTH, // probably? + ConnectionEndPointString(rSession.m_URI.GetHost(), rSession.m_URI.GetPort())); + case CURLE_WRITE_ERROR: + case CURLE_READ_ERROR: // error returned from our callbacks + case CURLE_OUT_OF_MEMORY: + case CURLE_ABORTED_BY_CALLBACK: + case CURLE_BAD_FUNCTION_ARGUMENT: + case CURLE_SEND_ERROR: + case CURLE_RECV_ERROR: + case CURLE_SSL_CACERT_BADFILE: + case CURLE_SSL_CRL_BADFILE: + case CURLE_RECURSIVE_API_CALL: + throw DAVException( + DAVException::DAV_HTTP_FAILED, + ConnectionEndPointString(rSession.m_URI.GetHost(), rSession.m_URI.GetPort())); + case CURLE_OPERATION_TIMEDOUT: + throw DAVException( + DAVException::DAV_HTTP_TIMEOUT, + ConnectionEndPointString(rSession.m_URI.GetHost(), rSession.m_URI.GetPort())); + default: // lots of generic errors + throw DAVException(DAVException::DAV_HTTP_ERROR, "", 0); + } + } + // error handling part 2: HTTP status codes + long statusCode(SC_NONE); + rc = curl_easy_getinfo(rSession.m_pCurl.get(), CURLINFO_RESPONSE_CODE, &statusCode); + assert(rc == CURLE_OK); + assert(statusCode != SC_NONE); // ??? should be error returned from perform? + SAL_INFO("ucb.ucp.webdav.curl", "HTTP status code: " << statusCode); + if (statusCode < 300) + { + // neon did this regardless of status or even error, which seems odd + ExtractRequestedHeaders(rHeaders, pRequestedHeaders); + } + else + { + // create message containing the HTTP method and response status line + OUString statusLine("\n" + rMethod + "\n=>\n"); + if (!rHeaders.HeaderFields.empty() && !rHeaders.HeaderFields.back().first.empty() + && rHeaders.HeaderFields.back().first.front().startsWith("HTTP")) + { + statusLine += ::rtl::OStringToOUString( + ::o3tl::trim(rHeaders.HeaderFields.back().first.front()), + RTL_TEXTENCODING_ASCII_US); + } + switch (statusCode) + { + case SC_REQUEST_TIMEOUT: + { + throw DAVException( + DAVException::DAV_HTTP_TIMEOUT, + ConnectionEndPointString(rSession.m_URI.GetHost(), rSession.m_URI.GetPort())); + break; + } + case SC_MOVED_PERMANENTLY: + case SC_MOVED_TEMPORARILY: + case SC_SEE_OTHER: + case SC_TEMPORARY_REDIRECT: + { + // could also use CURLOPT_FOLLOWLOCATION but apparently the + // upper layer wants to know about redirects? + char* pRedirectURL(nullptr); + rc = curl_easy_getinfo(rSession.m_pCurl.get(), CURLINFO_REDIRECT_URL, + &pRedirectURL); + assert(rc == CURLE_OK); + if (pRedirectURL) + { + // Sharepoint 2016 workaround: contains unencoded U+0020 + OUString const redirectURL(::rtl::Uri::encode( + pRedirectURL + ? OUString(pRedirectURL, strlen(pRedirectURL), RTL_TEXTENCODING_UTF8) + : OUString(), + rtl_UriCharClassUric, rtl_UriEncodeKeepEscapes, RTL_TEXTENCODING_UTF8)); + + throw DAVException(DAVException::DAV_HTTP_REDIRECT, redirectURL); + } + [[fallthrough]]; + } + default: + throw DAVException(DAVException::DAV_HTTP_ERROR, statusLine, statusCode); + } + } + + if (pxOutStream) + { + (*pxOutStream)->closeOutput(); // signal EOF + } +} + +static auto TryRemoveExpiredLockToken(CurlSession& rSession, CurlUri const& rURI, + DAVRequestEnvironment const* const pEnv) -> bool +{ + if (!pEnv) + { + // caller was a NonInteractive_*LOCK function anyway, its caller is LockStore + return false; + } + OUString const* const pToken(g_Init.LockStore.getLockTokenForURI(rURI.GetURI(), nullptr)); + if (!pToken) + { + return false; + } + try + { + // determine validity of existing lock via lockdiscovery request + ::std::vector<OUString> const propertyNames{ DAVProperties::LOCKDISCOVERY }; + ::std::vector<ucb::Lock> locks; + ::std::tuple<::std::vector<OUString> const&, ::std::vector<DAVResource>* const, + ::std::vector<ucb::Lock>* const> const args(propertyNames, nullptr, &locks); + + CurlProcessor::PropFind(rSession, rURI, DAVZERO, &args, nullptr, *pEnv); + + // https://datatracker.ietf.org/doc/html/rfc4918#section-15.8 + // The response MAY not contain tokens, but hopefully it + // will if client is properly authenticated. + if (::std::any_of(locks.begin(), locks.end(), [pToken](ucb::Lock const& rLock) { + return ::std::any_of( + rLock.LockTokens.begin(), rLock.LockTokens.end(), + [pToken](OUString const& rToken) { return *pToken == rToken; }); + })) + { + return false; // still have the lock + } + + SAL_INFO("ucb.ucp.webdav.curl", + "lock token expired, removing: " << rURI.GetURI() << " " << *pToken); + g_Init.LockStore.removeLock(rURI.GetURI()); + return true; + } + catch (DAVException const&) + { + return false; // ignore, the caller already has a better exception + } +} + +auto CurlProcessor::ProcessRequest( + CurlSession& rSession, CurlUri const& rURI, OUString const& rMethod, + ::std::vector<CurlOption> const& rOptions, DAVRequestEnvironment const* const pEnv, + ::std::unique_ptr<curl_slist, deleter_from_fn<curl_slist, curl_slist_free_all>> + pRequestHeaderList, + uno::Reference<io::XOutputStream> const* const pxOutStream, + uno::Reference<io::XInputStream> const* const pxInStream, + ::std::pair<::std::vector<OUString> const&, DAVResource&> const* const pRequestedHeaders) + -> void +{ + if (pEnv) + { // add custom request headers passed by caller + for (auto const& rHeader : pEnv->m_aRequestHeaders) + { + OString const utf8Header( + OUStringToOString(rHeader.first, RTL_TEXTENCODING_ASCII_US) + ": " + + OUStringToOString(rHeader.second, RTL_TEXTENCODING_ASCII_US)); + pRequestHeaderList.reset( + curl_slist_append(pRequestHeaderList.release(), utf8Header.getStr())); + if (!pRequestHeaderList) + { + throw uno::RuntimeException("curl_slist_append failed"); + } + } + } + + uno::Sequence<sal_Int8> data; + if (pxInStream) + { + uno::Reference<io::XSeekable> const xSeekable(*pxInStream, uno::UNO_QUERY); + if (xSeekable.is()) + { + auto const len(xSeekable->getLength() - xSeekable->getPosition()); + if ((**pxInStream).readBytes(data, len) != len) + { + throw uno::RuntimeException("short readBytes"); + } + } + else + { + ::std::vector<uno::Sequence<sal_Int8>> bufs; + bool isDone(false); + do + { + bufs.emplace_back(); + isDone = (**pxInStream).readSomeBytes(bufs.back(), 65536) == 0; + } while (!isDone); + sal_Int32 nSize(0); + for (auto const& rBuf : bufs) + { + if (o3tl::checked_add(nSize, rBuf.getLength(), nSize)) + { + throw std::bad_alloc(); // too large for Sequence + } + } + data.realloc(nSize); + size_t nCopied(0); + for (auto const& rBuf : bufs) + { + ::std::memcpy(data.getArray() + nCopied, rBuf.getConstArray(), rBuf.getLength()); + nCopied += rBuf.getLength(); // can't overflow + } + } + } + + // Clear flag before transfer starts; only a transfer started before + // calling abort() will be aborted, not one started later. + rSession.m_AbortFlag.store(false); + + Guard guard(rSession.m_Mutex, rOptions, rURI, rSession.m_pCurl.get()); + + // authentication data may be in the URI, or requested via XInteractionHandler + struct Auth + { + OUString UserName; + OUString PassWord; + decltype(CURLAUTH_ANY) AuthMask; ///< allowed auth methods + Auth(OUString const& rUserName, OUString const& rPassword, + decltype(CURLAUTH_ANY) const & rAuthMask) + : UserName(rUserName) + , PassWord(rPassword) + , AuthMask(rAuthMask) + { + } + }; + ::std::optional<Auth> oAuth; + ::std::optional<Auth> oAuthProxy; + if (pEnv && !rSession.m_isAuthenticatedProxy && !rSession.m_Proxy.aName.isEmpty()) + { + // the hope is that this must be a URI + CurlUri const uri(rSession.m_Proxy.aName); + if (!uri.GetUser().isEmpty() || !uri.GetPassword().isEmpty()) + { + oAuthProxy.emplace(uri.GetUser(), uri.GetPassword(), CURLAUTH_ANY); + } + } + decltype(CURLAUTH_ANY) const authSystem(CURLAUTH_NEGOTIATE | CURLAUTH_NTLM | CURLAUTH_NTLM_WB); + if (pRequestedHeaders || (pEnv && !rSession.m_isAuthenticated)) + { + // m_aRequestURI *may* be a path or *may* be URI - wtf + // TODO: why is there this m_aRequestURI and also rURIReference argument? + // ... only caller is DAVResourceAccess - always identical except MOVE/COPY + // which doesn't work if it's just a URI reference so let's just use + // rURIReference via rURI instead +#if 0 + CurlUri const uri(pEnv->m_aRequestURI); +#endif + // note: due to parsing bug pwd didn't work in previous webdav ucps + if (pEnv && !rSession.m_isAuthenticated + && (!rURI.GetUser().isEmpty() || !rURI.GetPassword().isEmpty())) + { + oAuth.emplace(rURI.GetUser(), rURI.GetPassword(), CURLAUTH_ANY); + } + if (pRequestedHeaders) + { + // note: Previously this would be the rURIReference directly but + // that ends up in CurlUri anyway and curl is unhappy. + // But it looks like all consumers of this .uri are interested + // only in the path, so it shouldn't make a difference to give + // the entire URI when the caller extracts the path anyway. + pRequestedHeaders->second.uri = rURI.GetURI(); + pRequestedHeaders->second.properties.clear(); + } + } + bool isRetry(false); + bool isFallbackHTTP10(false); + int nAuthRequests(0); + int nAuthRequestsProxy(0); + + // libcurl does not have an authentication callback so handle auth + // related status codes and requesting credentials via this loop + do + { + isRetry = false; + + // re-check m_isAuthenticated flags every time, could have been set + // by re-entrant call + if (oAuth && !rSession.m_isAuthenticated) + { + OString const utf8UserName(OUStringToOString(oAuth->UserName, RTL_TEXTENCODING_UTF8)); + auto rc + = curl_easy_setopt(rSession.m_pCurl.get(), CURLOPT_USERNAME, utf8UserName.getStr()); + if (rc != CURLE_OK) + { + SAL_WARN("ucb.ucp.webdav.curl", "CURLOPT_USERNAME failed: " << GetErrorString(rc)); + throw DAVException(DAVException::DAV_INVALID_ARG); + } + OString const utf8PassWord(OUStringToOString(oAuth->PassWord, RTL_TEXTENCODING_UTF8)); + rc = curl_easy_setopt(rSession.m_pCurl.get(), CURLOPT_PASSWORD, utf8PassWord.getStr()); + if (rc != CURLE_OK) + { + SAL_WARN("ucb.ucp.webdav.curl", "CURLOPT_PASSWORD failed: " << GetErrorString(rc)); + throw DAVException(DAVException::DAV_INVALID_ARG); + } + rc = curl_easy_setopt(rSession.m_pCurl.get(), CURLOPT_HTTPAUTH, oAuth->AuthMask); + assert( + rc + == CURLE_OK); // it shouldn't be possible to reduce auth to 0 via the authSystem masks + } + + if (oAuthProxy && !rSession.m_isAuthenticatedProxy) + { + OString const utf8UserName( + OUStringToOString(oAuthProxy->UserName, RTL_TEXTENCODING_UTF8)); + auto rc = curl_easy_setopt(rSession.m_pCurl.get(), CURLOPT_PROXYUSERNAME, + utf8UserName.getStr()); + if (rc != CURLE_OK) + { + SAL_WARN("ucb.ucp.webdav.curl", + "CURLOPT_PROXYUSERNAME failed: " << GetErrorString(rc)); + throw DAVException(DAVException::DAV_INVALID_ARG); + } + OString const utf8PassWord( + OUStringToOString(oAuthProxy->PassWord, RTL_TEXTENCODING_UTF8)); + rc = curl_easy_setopt(rSession.m_pCurl.get(), CURLOPT_PROXYPASSWORD, + utf8PassWord.getStr()); + if (rc != CURLE_OK) + { + SAL_WARN("ucb.ucp.webdav.curl", + "CURLOPT_PROXYPASSWORD failed: " << GetErrorString(rc)); + throw DAVException(DAVException::DAV_INVALID_ARG); + } + rc = curl_easy_setopt(rSession.m_pCurl.get(), CURLOPT_PROXYAUTH, oAuthProxy->AuthMask); + assert( + rc + == CURLE_OK); // it shouldn't be possible to reduce auth to 0 via the authSystem masks + } + + ResponseHeaders headers(rSession.m_pCurl.get()); + // always pass a stream for debug logging, buffer the result body + uno::Reference<io::XSequenceOutputStream> const xSeqOutStream( + io::SequenceOutputStream::create(rSession.m_xContext)); + uno::Reference<io::XOutputStream> const xTempOutStream(xSeqOutStream); + assert(xTempOutStream.is()); + + try + { + ProcessRequestImpl(rSession, rURI, rMethod, pRequestHeaderList.get(), &xTempOutStream, + pxInStream ? &data : nullptr, pRequestedHeaders, headers); + if (pxOutStream) + { // only copy to result stream if transfer was successful + (*pxOutStream)->writeBytes(xSeqOutStream->getWrittenBytes()); + (*pxOutStream)->closeOutput(); // signal EOF + } + } + catch (DAVException const& rException) + { + // log start of request body if there was any + auto const bytes(xSeqOutStream->getWrittenBytes()); + auto const len(::std::min<sal_Int32>(bytes.getLength(), 10000)); + SAL_INFO("ucb.ucp.webdav.curl", + "DAVException; (first) " << len << " bytes of data received:"); + if (0 < len) + { + OStringBuffer buf(len); + for (sal_Int32 i = 0; i < len; ++i) + { + if (bytes[i] < 0x20) // also if negative + { + static char const hexDigit[16] = { '0', '1', '2', '3', '4', '5', '6', '7', + '8', '9', 'A', 'B', 'C', 'D', 'E', 'F' }; + buf.append("\\x"); + buf.append(hexDigit[static_cast<sal_uInt8>(bytes[i]) >> 4]); + buf.append(hexDigit[bytes[i] & 0x0F]); + } + else + { + buf.append(static_cast<char>(bytes[i])); + } + } + SAL_INFO("ucb.ucp.webdav.curl", buf.makeStringAndClear()); + } + + // error handling part 3: special HTTP status codes + // that require unlocking m_Mutex to handle + if (rException.getError() == DAVException::DAV_HTTP_ERROR) + { + auto const statusCode(rException.getStatus()); + switch (statusCode) + { + case SC_LOCKED: + { + guard.Release(); // release m_Mutex before accessing LockStore + if (g_Init.LockStore.getLockTokenForURI(rURI.GetURI(), nullptr)) + { + throw DAVException(DAVException::DAV_LOCKED_SELF); + } + else // locked by third party + { + throw DAVException(DAVException::DAV_LOCKED); + } + break; + } + case SC_PRECONDITION_FAILED: + case SC_BAD_REQUEST: + { + guard.Release(); // release m_Mutex before accessing LockStore + // Not obvious but apparently these codes may indicate + // the expiration of a lock. + // Initiate a new request *outside* ProcessRequestImpl + // *after* rGuard.unlock() to avoid messing up m_pCurl state. + if (TryRemoveExpiredLockToken(rSession, rURI, pEnv)) + { + throw DAVException(DAVException::DAV_LOCK_EXPIRED); + } + break; + } + case SC_UNAUTHORIZED: + case SC_PROXY_AUTHENTICATION_REQUIRED: + { + auto& rnAuthRequests(statusCode == SC_UNAUTHORIZED ? nAuthRequests + : nAuthRequestsProxy); + if (rnAuthRequests == 10) + { + SAL_INFO("ucb.ucp.webdav.curl", "aborting authentication after " + << rnAuthRequests << " attempts"); + } + else if (pEnv && pEnv->m_xAuthListener) + { + ::std::optional<OUString> const oRealm(ExtractRealm( + headers, statusCode == SC_UNAUTHORIZED ? "WWW-Authenticate" + : "Proxy-Authenticate")); + + ::std::optional<Auth>& roAuth( + statusCode == SC_UNAUTHORIZED ? oAuth : oAuthProxy); + OUString userName(roAuth ? roAuth->UserName : OUString()); + OUString passWord(roAuth ? roAuth->PassWord : OUString()); + long authAvail(0); + auto const rc = curl_easy_getinfo(rSession.m_pCurl.get(), + statusCode == SC_UNAUTHORIZED + ? CURLINFO_HTTPAUTH_AVAIL + : CURLINFO_PROXYAUTH_AVAIL, + &authAvail); + assert(rc == CURLE_OK); + (void)rc; + // only allow SystemCredentials once - the + // PasswordContainer may have stored it in the + // Config (TrySystemCredentialsFirst or + // AuthenticateUsingSystemCredentials) and then it + // will always force its use no matter how hopeless + bool const isSystemCredSupported((authAvail & authSystem) != 0 + && rnAuthRequests == 0); + ++rnAuthRequests; + + // Ask user via XInteractionHandler. + // Warning: This likely runs an event loop which may + // end up calling back into this instance, so all + // changes to m_pCurl must be undone now and + // restored after return. + guard.Release(); + + auto const ret = pEnv->m_xAuthListener->authenticate( + oRealm ? *oRealm : "", + statusCode == SC_UNAUTHORIZED ? rSession.m_URI.GetHost() + : rSession.m_Proxy.aName, + userName, passWord, isSystemCredSupported); + + if (ret == 0) + { + // NTLM may either use a password requested + // from the user, or from the system via SSPI + // so i guess it should not be disabled here + // regardless of the state of the system auth + // checkbox, particularly since SSPI is only + // available on WNT. + // Additionally, "Negotiate" has a "legacy" + // mode that is actually just NTLM according to + // https://curl.se/rfc/ntlm.html#ntlmHttpAuthentication + // so there's nothing in authSystem that can be + // disabled here. + roAuth.emplace(userName, passWord, + ((userName.isEmpty() && passWord.isEmpty()) + ? (authAvail & authSystem) + : authAvail)); + isRetry = true; + // Acquire is only necessary in case of success. + guard.Acquire(); + break; // break out of switch + } + // else: throw + } + SAL_INFO("ucb.ucp.webdav.curl", "no auth credentials provided"); + throw DAVException(DAVException::DAV_HTTP_NOAUTH, + ConnectionEndPointString(rSession.m_URI.GetHost(), + rSession.m_URI.GetPort())); + break; + } + } + } + else if (rException.getError() == DAVException::DAV_UNSUPPORTED) + { + // tdf#152493 libcurl can't handle "Transfer-Encoding: chunked" + // in HTTP/1.1 100 Continue response. + // workaround: if HTTP/1.1 didn't work, try HTTP/1.0 + // (but fallback only once - to prevent infinite loop) + if (isFallbackHTTP10) + { + throw DAVException(DAVException::DAV_HTTP_ERROR); + } + isFallbackHTTP10 = true; + // note: this is not reset - future requests to this URI use it! + auto rc = curl_easy_setopt(rSession.m_pCurl.get(), CURLOPT_HTTP_VERSION, + CURL_HTTP_VERSION_1_0); + if (rc != CURLE_OK) + { + throw DAVException(DAVException::DAV_HTTP_ERROR); + } + SAL_INFO("ucb.ucp.webdav.curl", "attempting fallback to HTTP/1.0"); + isRetry = true; + } + if (!isRetry) + { + throw; // everything else: re-throw + } + } + } while (isRetry); + + if (oAuth) + { + // assume this worked, leave auth data as stored in m_pCurl + rSession.m_isAuthenticated = true; + } + if (oAuthProxy) + { + // assume this worked, leave auth data as stored in m_pCurl + rSession.m_isAuthenticatedProxy = true; + } +} + +auto CurlSession::OPTIONS(OUString const& rURIReference, + + DAVOptions& rOptions, DAVRequestEnvironment const& rEnv) -> void +{ + SAL_INFO("ucb.ucp.webdav.curl", "OPTIONS: " << rURIReference); + + rOptions.init(); + + CurlUri const uri(CurlProcessor::URIReferenceToURI(*this, rURIReference)); + + ::std::vector<OUString> const headerNames{ "allow", "dav" }; + DAVResource result; + ::std::pair<::std::vector<OUString> const&, DAVResource&> const headers(headerNames, result); + + ::std::vector<CurlOption> const options{ + g_NoBody, { CURLOPT_CUSTOMREQUEST, "OPTIONS", "CURLOPT_CUSTOMREQUEST" } + }; + + CurlProcessor::ProcessRequest(*this, uri, "OPTIONS", options, &rEnv, nullptr, nullptr, nullptr, + &headers); + + for (auto const& it : result.properties) + { + OUString value; + it.Value >>= value; + SAL_INFO("ucb.ucp.webdav.curl", "OPTIONS: header: " << it.Name << ": " << value); + if (it.Name.equalsIgnoreAsciiCase("allow")) + { + rOptions.setAllowedMethods(value); + } + else if (it.Name.equalsIgnoreAsciiCase("dav")) + { + // see <http://tools.ietf.org/html/rfc4918#section-10.1>, + // <http://tools.ietf.org/html/rfc4918#section-18>, + // and <http://tools.ietf.org/html/rfc7230#section-3.2> + // we detect the class (1, 2 and 3), other elements (token, URL) + // are not used for now + auto const list(::comphelper::string::convertCommaSeparated(value)); + for (OUString const& v : list) + { + if (v == "1") + { + rOptions.setClass1(); + } + else if (v == "2") + { + rOptions.setClass2(); + } + else if (v == "3") + { + rOptions.setClass3(); + } + } + } + } + if (rOptions.isClass2() || rOptions.isClass3()) + { + if (g_Init.LockStore.getLockTokenForURI(uri.GetURI(), nullptr)) + { + rOptions.setLocked(); + } + } +} + +auto CurlProcessor::PropFind( + CurlSession& rSession, CurlUri const& rURI, Depth const nDepth, + ::std::tuple<::std::vector<OUString> const&, ::std::vector<DAVResource>* const, + ::std::vector<ucb::Lock>* const> const* const o_pRequestedProperties, + ::std::vector<DAVResourceInfo>* const o_pResourceInfos, DAVRequestEnvironment const& rEnv) + -> void +{ + assert((o_pRequestedProperties != nullptr) != (o_pResourceInfos != nullptr)); + assert((o_pRequestedProperties == nullptr) + || (::std::get<1>(*o_pRequestedProperties) != nullptr) + != (::std::get<2>(*o_pRequestedProperties) != nullptr)); + + ::std::unique_ptr<curl_slist, deleter_from_fn<curl_slist, curl_slist_free_all>> pList; + pList.reset(curl_slist_append(pList.release(), "Content-Type: application/xml")); + if (!pList) + { + throw uno::RuntimeException("curl_slist_append failed"); + } + OString depth; + switch (nDepth) + { + case DAVZERO: + depth = "Depth: 0"; + break; + case DAVONE: + depth = "Depth: 1"; + break; + case DAVINFINITY: + depth = "Depth: infinity"; + break; + default: + assert(false); + } + pList.reset(curl_slist_append(pList.release(), depth.getStr())); + if (!pList) + { + throw uno::RuntimeException("curl_slist_append failed"); + } + + uno::Reference<io::XSequenceOutputStream> const xSeqOutStream( + io::SequenceOutputStream::create(rSession.m_xContext)); + uno::Reference<io::XOutputStream> const xRequestOutStream(xSeqOutStream); + assert(xRequestOutStream.is()); + + uno::Reference<xml::sax::XWriter> const xWriter(xml::sax::Writer::create(rSession.m_xContext)); + xWriter->setOutputStream(xRequestOutStream); + xWriter->startDocument(); + rtl::Reference<::comphelper::AttributeList> const pAttrList(new ::comphelper::AttributeList); + pAttrList->AddAttribute("xmlns", "CDATA", "DAV:"); + xWriter->startElement("propfind", pAttrList); + if (o_pResourceInfos) + { + xWriter->startElement("propname", nullptr); + xWriter->endElement("propname"); + } + else + { + if (::std::get<0>(*o_pRequestedProperties).empty()) + { + xWriter->startElement("allprop", nullptr); + xWriter->endElement("allprop"); + } + else + { + xWriter->startElement("prop", nullptr); + for (OUString const& rName : ::std::get<0>(*o_pRequestedProperties)) + { + SerfPropName name; + DAVProperties::createSerfPropName(rName, name); + pAttrList->Clear(); + pAttrList->AddAttribute("xmlns", "CDATA", OUString::createFromAscii(name.nspace)); + xWriter->startElement(OUString::createFromAscii(name.name), pAttrList); + xWriter->endElement(OUString::createFromAscii(name.name)); + } + xWriter->endElement("prop"); + } + } + xWriter->endElement("propfind"); + xWriter->endDocument(); + + uno::Reference<io::XInputStream> const xRequestInStream( + io::SequenceInputStream::createStreamFromSequence(rSession.m_xContext, + xSeqOutStream->getWrittenBytes())); + assert(xRequestInStream.is()); + + curl_off_t const len(xSeqOutStream->getWrittenBytes().getLength()); + + ::std::vector<CurlOption> const options{ + { CURLOPT_UPLOAD, 1L, nullptr }, + { CURLOPT_CUSTOMREQUEST, "PROPFIND", "CURLOPT_CUSTOMREQUEST" }, + // note: Sharepoint cannot handle "Transfer-Encoding: chunked" + { CURLOPT_INFILESIZE_LARGE, len, nullptr, CurlOption::Type::CurlOffT } + }; + + // stream for response + uno::Reference<io::XInputStream> const xResponseInStream(io::Pipe::create(rSession.m_xContext)); + uno::Reference<io::XOutputStream> const xResponseOutStream(xResponseInStream, uno::UNO_QUERY); + assert(xResponseInStream.is()); + assert(xResponseOutStream.is()); + + CurlProcessor::ProcessRequest(rSession, rURI, "PROPFIND", options, &rEnv, ::std::move(pList), + &xResponseOutStream, &xRequestInStream, nullptr); + + if (o_pResourceInfos) + { + *o_pResourceInfos = parseWebDAVPropNameResponse(xResponseInStream); + } + else + { + if (::std::get<1>(*o_pRequestedProperties) != nullptr) + { + *::std::get<1>(*o_pRequestedProperties) + = parseWebDAVPropFindResponse(xResponseInStream); + for (DAVResource& it : *::std::get<1>(*o_pRequestedProperties)) + { + // caller will give these uris to CurlUri so can't be relative + if (it.uri.startsWith("/")) + { + try + { + it.uri = rSession.m_URI.CloneWithRelativeRefPathAbsolute(it.uri).GetURI(); + } + catch (DAVException const&) + { + SAL_INFO("ucb.ucp.webdav.curl", + "PROPFIND: exception parsing uri " << it.uri); + } + } + } + } + else + { + *::std::get<2>(*o_pRequestedProperties) = parseWebDAVLockResponse(xResponseInStream); + } + } +} + +// DAV methods +auto CurlSession::PROPFIND(OUString const& rURIReference, Depth const depth, + ::std::vector<OUString> const& rPropertyNames, + ::std::vector<DAVResource>& o_rResources, + DAVRequestEnvironment const& rEnv) -> void +{ + SAL_INFO("ucb.ucp.webdav.curl", "PROPFIND: " << rURIReference << " " << depth); + + CurlUri const uri(CurlProcessor::URIReferenceToURI(*this, rURIReference)); + + ::std::tuple<::std::vector<OUString> const&, ::std::vector<DAVResource>* const, + ::std::vector<ucb::Lock>* const> const args(rPropertyNames, &o_rResources, + nullptr); + return CurlProcessor::PropFind(*this, uri, depth, &args, nullptr, rEnv); +} + +auto CurlSession::PROPFIND(OUString const& rURIReference, Depth const depth, + ::std::vector<DAVResourceInfo>& o_rResourceInfos, + DAVRequestEnvironment const& rEnv) -> void +{ + SAL_INFO("ucb.ucp.webdav.curl", "PROPFIND: " << rURIReference << " " << depth); + + CurlUri const uri(CurlProcessor::URIReferenceToURI(*this, rURIReference)); + + return CurlProcessor::PropFind(*this, uri, depth, nullptr, &o_rResourceInfos, rEnv); +} + +auto CurlSession::PROPPATCH(OUString const& rURIReference, + ::std::vector<ProppatchValue> const& rValues, + DAVRequestEnvironment const& rEnv) -> void +{ + SAL_INFO("ucb.ucp.webdav.curl", "PROPPATCH: " << rURIReference); + + CurlUri const uri(CurlProcessor::URIReferenceToURI(*this, rURIReference)); + + // TODO: either set CURLOPT_INFILESIZE_LARGE or chunked? + ::std::unique_ptr<curl_slist, deleter_from_fn<curl_slist, curl_slist_free_all>> pList; + pList.reset(curl_slist_append(pList.release(), "Content-Type: application/xml")); + if (!pList) + { + throw uno::RuntimeException("curl_slist_append failed"); + } + + // generate XML document for PROPPATCH + uno::Reference<io::XSequenceOutputStream> const xSeqOutStream( + io::SequenceOutputStream::create(m_xContext)); + uno::Reference<io::XOutputStream> const xRequestOutStream(xSeqOutStream); + assert(xRequestOutStream.is()); + uno::Reference<xml::sax::XWriter> const xWriter(xml::sax::Writer::create(m_xContext)); + xWriter->setOutputStream(xRequestOutStream); + xWriter->startDocument(); + rtl::Reference<::comphelper::AttributeList> const pAttrList(new ::comphelper::AttributeList); + pAttrList->AddAttribute("xmlns", "CDATA", "DAV:"); + xWriter->startElement("propertyupdate", pAttrList); + for (ProppatchValue const& rPropValue : rValues) + { + assert(rPropValue.operation == PROPSET || rPropValue.operation == PROPREMOVE); + OUString const operation((rPropValue.operation == PROPSET) ? OUString("set") + : OUString("remove")); + xWriter->startElement(operation, nullptr); + xWriter->startElement("prop", nullptr); + SerfPropName name; + DAVProperties::createSerfPropName(rPropValue.name, name); + pAttrList->Clear(); + pAttrList->AddAttribute("xmlns", "CDATA", OUString::createFromAscii(name.nspace)); + xWriter->startElement(OUString::createFromAscii(name.name), pAttrList); + if (rPropValue.operation == PROPSET) + { + if (DAVProperties::isUCBDeadProperty(name)) + { + ::std::optional<::std::pair<OUString, OUString>> const oProp( + UCBDeadPropertyValue::toXML(rPropValue.value)); + if (oProp) + { + xWriter->startElement("ucbprop", nullptr); + xWriter->startElement("type", nullptr); + xWriter->characters(oProp->first); + xWriter->endElement("type"); + xWriter->startElement("value", nullptr); + xWriter->characters(oProp->second); + xWriter->endElement("value"); + xWriter->endElement("ucbprop"); + } + } + else + { + OUString value; + rPropValue.value >>= value; + xWriter->characters(value); + } + } + xWriter->endElement(OUString::createFromAscii(name.name)); + xWriter->endElement("prop"); + xWriter->endElement(operation); + } + xWriter->endElement("propertyupdate"); + xWriter->endDocument(); + + uno::Reference<io::XInputStream> const xRequestInStream( + io::SequenceInputStream::createStreamFromSequence(m_xContext, + xSeqOutStream->getWrittenBytes())); + assert(xRequestInStream.is()); + + curl_off_t const len(xSeqOutStream->getWrittenBytes().getLength()); + + ::std::vector<CurlOption> const options{ + { CURLOPT_UPLOAD, 1L, nullptr }, + { CURLOPT_CUSTOMREQUEST, "PROPPATCH", "CURLOPT_CUSTOMREQUEST" }, + // note: Sharepoint cannot handle "Transfer-Encoding: chunked" + { CURLOPT_INFILESIZE_LARGE, len, nullptr, CurlOption::Type::CurlOffT } + }; + + CurlProcessor::ProcessRequest(*this, uri, "PROPPATCH", options, &rEnv, ::std::move(pList), + nullptr, &xRequestInStream, nullptr); +} + +auto CurlSession::HEAD(OUString const& rURIReference, ::std::vector<OUString> const& rHeaderNames, + DAVResource& io_rResource, DAVRequestEnvironment const& rEnv) -> void +{ + SAL_INFO("ucb.ucp.webdav.curl", "HEAD: " << rURIReference); + + CurlUri const uri(CurlProcessor::URIReferenceToURI(*this, rURIReference)); + + ::std::vector<CurlOption> const options{ g_NoBody }; + + ::std::pair<::std::vector<OUString> const&, DAVResource&> const headers(rHeaderNames, + io_rResource); + + CurlProcessor::ProcessRequest(*this, uri, "HEAD", options, &rEnv, nullptr, nullptr, nullptr, + &headers); +} + +auto CurlSession::GET(OUString const& rURIReference, DAVRequestEnvironment const& rEnv) + -> uno::Reference<io::XInputStream> +{ + SAL_INFO("ucb.ucp.webdav.curl", "GET: " << rURIReference); + + CurlUri const uri(CurlProcessor::URIReferenceToURI(*this, rURIReference)); + + // could use either com.sun.star.io.Pipe or com.sun.star.io.SequenceInputStream? + // Pipe can just write into its XOuputStream, which is simpler. + // Both resize exponentially, so performance should be fine. + // However, Pipe doesn't implement XSeekable, which is required by filters. + + uno::Reference<io::XSequenceOutputStream> const xSeqOutStream( + io::SequenceOutputStream::create(m_xContext)); + uno::Reference<io::XOutputStream> const xResponseOutStream(xSeqOutStream); + assert(xResponseOutStream.is()); + + ::std::vector<CurlOption> const options{ { CURLOPT_HTTPGET, 1L, nullptr } }; + + CurlProcessor::ProcessRequest(*this, uri, "GET", options, &rEnv, nullptr, &xResponseOutStream, + nullptr, nullptr); + + uno::Reference<io::XInputStream> const xResponseInStream( + io::SequenceInputStream::createStreamFromSequence(m_xContext, + xSeqOutStream->getWrittenBytes())); + assert(xResponseInStream.is()); + + return xResponseInStream; +} + +auto CurlSession::GET(OUString const& rURIReference, uno::Reference<io::XOutputStream>& rxOutStream, + DAVRequestEnvironment const& rEnv) -> void +{ + SAL_INFO("ucb.ucp.webdav.curl", "GET: " << rURIReference); + + CurlUri const uri(CurlProcessor::URIReferenceToURI(*this, rURIReference)); + + ::std::vector<CurlOption> const options{ { CURLOPT_HTTPGET, 1L, nullptr } }; + + CurlProcessor::ProcessRequest(*this, uri, "GET", options, &rEnv, nullptr, &rxOutStream, nullptr, + nullptr); +} + +auto CurlSession::GET(OUString const& rURIReference, ::std::vector<OUString> const& rHeaderNames, + DAVResource& io_rResource, DAVRequestEnvironment const& rEnv) + -> uno::Reference<io::XInputStream> +{ + SAL_INFO("ucb.ucp.webdav.curl", "GET: " << rURIReference); + + CurlUri const uri(CurlProcessor::URIReferenceToURI(*this, rURIReference)); + + ::std::vector<CurlOption> const options{ { CURLOPT_HTTPGET, 1L, nullptr } }; + + uno::Reference<io::XSequenceOutputStream> const xSeqOutStream( + io::SequenceOutputStream::create(m_xContext)); + uno::Reference<io::XOutputStream> const xResponseOutStream(xSeqOutStream); + assert(xResponseOutStream.is()); + + ::std::pair<::std::vector<OUString> const&, DAVResource&> const headers(rHeaderNames, + io_rResource); + + CurlProcessor::ProcessRequest(*this, uri, "GET", options, &rEnv, nullptr, &xResponseOutStream, + nullptr, &headers); + + uno::Reference<io::XInputStream> const xResponseInStream( + io::SequenceInputStream::createStreamFromSequence(m_xContext, + xSeqOutStream->getWrittenBytes())); + assert(xResponseInStream.is()); + + return xResponseInStream; +} + +auto CurlSession::GET(OUString const& rURIReference, uno::Reference<io::XOutputStream>& rxOutStream, + ::std::vector<OUString> const& rHeaderNames, DAVResource& io_rResource, + DAVRequestEnvironment const& rEnv) -> void +{ + SAL_INFO("ucb.ucp.webdav.curl", "GET: " << rURIReference); + + CurlUri const uri(CurlProcessor::URIReferenceToURI(*this, rURIReference)); + + ::std::vector<CurlOption> const options{ { CURLOPT_HTTPGET, 1L, nullptr } }; + + ::std::pair<::std::vector<OUString> const&, DAVResource&> const headers(rHeaderNames, + io_rResource); + + CurlProcessor::ProcessRequest(*this, uri, "GET", options, &rEnv, nullptr, &rxOutStream, nullptr, + &headers); +} + +auto CurlSession::PUT(OUString const& rURIReference, + uno::Reference<io::XInputStream> const& rxInStream, + DAVRequestEnvironment const& rEnv) -> void +{ + SAL_INFO("ucb.ucp.webdav.curl", "PUT: " << rURIReference); + + CurlUri const uri(CurlProcessor::URIReferenceToURI(*this, rURIReference)); + + // NextCloud silently fails with chunked encoding + uno::Reference<io::XSeekable> const xSeekable(rxInStream, uno::UNO_QUERY); + if (!xSeekable.is()) + { + throw uno::RuntimeException("TODO: not seekable"); + } + curl_off_t const len(xSeekable->getLength() - xSeekable->getPosition()); + + ::std::unique_ptr<curl_slist, deleter_from_fn<curl_slist, curl_slist_free_all>> pList; + OUString const* const pToken(g_Init.LockStore.getLockTokenForURI(uri.GetURI(), nullptr)); + if (pToken) + { + OString const utf8If("If: " + // disabled as Sharepoint 2013 workaround, it accepts only + // "No-Tag-List", see fed2984281a85a5a2f308841ec810f218c75f2ab +#if 0 + "<" + OUStringToOString(rURIReference, RTL_TEXTENCODING_ASCII_US) + + "> " +#endif + "(<" + + OUStringToOString(*pToken, RTL_TEXTENCODING_ASCII_US) + ">)"); + pList.reset(curl_slist_append(pList.release(), utf8If.getStr())); + if (!pList) + { + throw uno::RuntimeException("curl_slist_append failed"); + } + } + + // lock m_Mutex after accessing global LockStore to avoid deadlock + + // note: Nextcloud 20 cannot handle "Transfer-Encoding: chunked" + ::std::vector<CurlOption> const options{ + { CURLOPT_UPLOAD, 1L, nullptr }, // libcurl won't upload without setting this + { CURLOPT_INFILESIZE_LARGE, len, nullptr, CurlOption::Type::CurlOffT } + }; + + CurlProcessor::ProcessRequest(*this, uri, "PUT", options, &rEnv, ::std::move(pList), nullptr, + &rxInStream, nullptr); +} + +auto CurlSession::POST(OUString const& rURIReference, OUString const& rContentType, + OUString const& rReferer, uno::Reference<io::XInputStream> const& rxInStream, + DAVRequestEnvironment const& rEnv) -> uno::Reference<io::XInputStream> +{ + SAL_INFO("ucb.ucp.webdav.curl", "POST: " << rURIReference); + + CurlUri const uri(CurlProcessor::URIReferenceToURI(*this, rURIReference)); + + // TODO: either set CURLOPT_POSTFIELDSIZE_LARGE or chunked? + ::std::unique_ptr<curl_slist, deleter_from_fn<curl_slist, curl_slist_free_all>> pList( + curl_slist_append(nullptr, "Transfer-Encoding: chunked")); + if (!pList) + { + throw uno::RuntimeException("curl_slist_append failed"); + } + OString const utf8ContentType("Content-Type: " + + OUStringToOString(rContentType, RTL_TEXTENCODING_ASCII_US)); + pList.reset(curl_slist_append(pList.release(), utf8ContentType.getStr())); + if (!pList) + { + throw uno::RuntimeException("curl_slist_append failed"); + } + OString const utf8Referer("Referer: " + OUStringToOString(rReferer, RTL_TEXTENCODING_ASCII_US)); + pList.reset(curl_slist_append(pList.release(), utf8Referer.getStr())); + if (!pList) + { + throw uno::RuntimeException("curl_slist_append failed"); + } + + ::std::vector<CurlOption> const options{ { CURLOPT_POST, 1L, nullptr } }; + + uno::Reference<io::XSequenceOutputStream> const xSeqOutStream( + io::SequenceOutputStream::create(m_xContext)); + uno::Reference<io::XOutputStream> const xResponseOutStream(xSeqOutStream); + assert(xResponseOutStream.is()); + + CurlProcessor::ProcessRequest(*this, uri, "POST", options, &rEnv, ::std::move(pList), + &xResponseOutStream, &rxInStream, nullptr); + + uno::Reference<io::XInputStream> const xResponseInStream( + io::SequenceInputStream::createStreamFromSequence(m_xContext, + xSeqOutStream->getWrittenBytes())); + assert(xResponseInStream.is()); + + return xResponseInStream; +} + +auto CurlSession::POST(OUString const& rURIReference, OUString const& rContentType, + OUString const& rReferer, uno::Reference<io::XInputStream> const& rxInStream, + uno::Reference<io::XOutputStream>& rxOutStream, + DAVRequestEnvironment const& rEnv) -> void +{ + SAL_INFO("ucb.ucp.webdav.curl", "POST: " << rURIReference); + + CurlUri const uri(CurlProcessor::URIReferenceToURI(*this, rURIReference)); + + // TODO: either set CURLOPT_POSTFIELDSIZE_LARGE or chunked? + ::std::unique_ptr<curl_slist, deleter_from_fn<curl_slist, curl_slist_free_all>> pList( + curl_slist_append(nullptr, "Transfer-Encoding: chunked")); + if (!pList) + { + throw uno::RuntimeException("curl_slist_append failed"); + } + OString const utf8ContentType("Content-Type: " + + OUStringToOString(rContentType, RTL_TEXTENCODING_ASCII_US)); + pList.reset(curl_slist_append(pList.release(), utf8ContentType.getStr())); + if (!pList) + { + throw uno::RuntimeException("curl_slist_append failed"); + } + OString const utf8Referer("Referer: " + OUStringToOString(rReferer, RTL_TEXTENCODING_ASCII_US)); + pList.reset(curl_slist_append(pList.release(), utf8Referer.getStr())); + if (!pList) + { + throw uno::RuntimeException("curl_slist_append failed"); + } + + ::std::vector<CurlOption> const options{ { CURLOPT_POST, 1L, nullptr } }; + + CurlProcessor::ProcessRequest(*this, uri, "POST", options, &rEnv, ::std::move(pList), + &rxOutStream, &rxInStream, nullptr); +} + +auto CurlSession::MKCOL(OUString const& rURIReference, DAVRequestEnvironment const& rEnv) -> void +{ + SAL_INFO("ucb.ucp.webdav.curl", "MKCOL: " << rURIReference); + + CurlUri const uri(CurlProcessor::URIReferenceToURI(*this, rURIReference)); + + ::std::vector<CurlOption> const options{ + g_NoBody, { CURLOPT_CUSTOMREQUEST, "MKCOL", "CURLOPT_CUSTOMREQUEST" } + }; + + CurlProcessor::ProcessRequest(*this, uri, "MKCOL", options, &rEnv, nullptr, nullptr, nullptr, + nullptr); +} + +auto CurlProcessor::MoveOrCopy(CurlSession& rSession, OUString const& rSourceURIReference, + ::std::u16string_view const rDestinationURI, + DAVRequestEnvironment const& rEnv, bool const isOverwrite, + char const* const pMethod) -> void +{ + CurlUri const uriSource(CurlProcessor::URIReferenceToURI(rSession, rSourceURIReference)); + + OString const utf8Destination("Destination: " + + OUStringToOString(rDestinationURI, RTL_TEXTENCODING_ASCII_US)); + ::std::unique_ptr<curl_slist, deleter_from_fn<curl_slist, curl_slist_free_all>> pList( + curl_slist_append(nullptr, utf8Destination.getStr())); + if (!pList) + { + throw uno::RuntimeException("curl_slist_append failed"); + } + OString const utf8Overwrite(OString::Concat("Overwrite: ") + (isOverwrite ? "T" : "F")); + pList.reset(curl_slist_append(pList.release(), utf8Overwrite.getStr())); + if (!pList) + { + throw uno::RuntimeException("curl_slist_append failed"); + } + + ::std::vector<CurlOption> const options{ + g_NoBody, { CURLOPT_CUSTOMREQUEST, pMethod, "CURLOPT_CUSTOMREQUEST" } + }; + + CurlProcessor::ProcessRequest(rSession, uriSource, OUString::createFromAscii(pMethod), options, + &rEnv, ::std::move(pList), nullptr, nullptr, nullptr); +} + +auto CurlSession::COPY(OUString const& rSourceURIReference, OUString const& rDestinationURI, + DAVRequestEnvironment const& rEnv, bool const isOverwrite) -> void +{ + SAL_INFO("ucb.ucp.webdav.curl", "COPY: " << rSourceURIReference); + + return CurlProcessor::MoveOrCopy(*this, rSourceURIReference, rDestinationURI, rEnv, isOverwrite, + "COPY"); +} + +auto CurlSession::MOVE(OUString const& rSourceURIReference, OUString const& rDestinationURI, + DAVRequestEnvironment const& rEnv, bool const isOverwrite) -> void +{ + SAL_INFO("ucb.ucp.webdav.curl", "MOVE: " << rSourceURIReference); + + return CurlProcessor::MoveOrCopy(*this, rSourceURIReference, rDestinationURI, rEnv, isOverwrite, + "MOVE"); +} + +auto CurlSession::DESTROY(OUString const& rURIReference, DAVRequestEnvironment const& rEnv) -> void +{ + SAL_INFO("ucb.ucp.webdav.curl", "DESTROY: " << rURIReference); + + CurlUri const uri(CurlProcessor::URIReferenceToURI(*this, rURIReference)); + + ::std::vector<CurlOption> const options{ + g_NoBody, { CURLOPT_CUSTOMREQUEST, "DELETE", "CURLOPT_CUSTOMREQUEST" } + }; + + CurlProcessor::ProcessRequest(*this, uri, "DESTROY", options, &rEnv, nullptr, nullptr, nullptr, + nullptr); +} + +auto CurlProcessor::Lock( + CurlSession& rSession, CurlUri const& rURI, DAVRequestEnvironment const* const pEnv, + ::std::unique_ptr<curl_slist, deleter_from_fn<curl_slist, curl_slist_free_all>> + pRequestHeaderList, + uno::Reference<io::XInputStream> const* const pxRequestInStream) + -> ::std::vector<::std::pair<ucb::Lock, sal_Int32>> +{ + curl_off_t len(0); + if (pxRequestInStream) + { + uno::Reference<io::XSeekable> const xSeekable(*pxRequestInStream, uno::UNO_QUERY); + assert(xSeekable.is()); + len = xSeekable->getLength(); + } + + ::std::vector<CurlOption> const options{ + { CURLOPT_UPLOAD, 1L, nullptr }, + { CURLOPT_CUSTOMREQUEST, "LOCK", "CURLOPT_CUSTOMREQUEST" }, + // note: Sharepoint cannot handle "Transfer-Encoding: chunked" + { CURLOPT_INFILESIZE_LARGE, len, nullptr, CurlOption::Type::CurlOffT } + }; + + // stream for response + uno::Reference<io::XInputStream> const xResponseInStream(io::Pipe::create(rSession.m_xContext)); + uno::Reference<io::XOutputStream> const xResponseOutStream(xResponseInStream, uno::UNO_QUERY); + assert(xResponseInStream.is()); + assert(xResponseOutStream.is()); + + TimeValue startTime; + osl_getSystemTime(&startTime); + + CurlProcessor::ProcessRequest(rSession, rURI, "LOCK", options, pEnv, + ::std::move(pRequestHeaderList), &xResponseOutStream, + pxRequestInStream, nullptr); + + ::std::vector<ucb::Lock> const acquiredLocks(parseWebDAVLockResponse(xResponseInStream)); + SAL_WARN_IF(acquiredLocks.empty(), "ucb.ucp.webdav.curl", + "could not get LOCK for " << rURI.GetURI()); + + TimeValue endTime; + osl_getSystemTime(&endTime); + auto const elapsedSeconds(endTime.Seconds - startTime.Seconds); + + // determine expiration time (seconds from endTime) for each acquired lock + ::std::vector<::std::pair<ucb::Lock, sal_Int32>> ret; + ret.reserve(acquiredLocks.size()); + for (auto const& rLock : acquiredLocks) + { + sal_Int32 lockExpirationTimeSeconds; + if (rLock.Timeout == -1) + { + lockExpirationTimeSeconds = -1; + } + else if (rLock.Timeout <= elapsedSeconds) + { + SAL_WARN("ucb.ucp.webdav.curl", + "LOCK timeout already expired when receiving LOCK response for " + << rURI.GetURI()); + lockExpirationTimeSeconds = 0; + } + else + { + lockExpirationTimeSeconds = startTime.Seconds + rLock.Timeout; + } + ret.emplace_back(rLock, lockExpirationTimeSeconds); + } + + return ret; +} + +auto CurlSession::LOCK(OUString const& rURIReference, ucb::Lock /*const*/& rLock, + DAVRequestEnvironment const& rEnv) -> void +{ + SAL_INFO("ucb.ucp.webdav.curl", "LOCK: " << rURIReference); + + CurlUri const uri(CurlProcessor::URIReferenceToURI(*this, rURIReference)); + + if (g_Init.LockStore.getLockTokenForURI(uri.GetURI(), &rLock)) + { + // already have a lock that covers the requirement + // TODO: maybe use DAV:lockdiscovery to ensure it's valid + return; + } + + // note: no m_Mutex lock needed here, only in CurlProcessor::Lock() + + // generate XML document for acquiring new LOCK + uno::Reference<io::XSequenceOutputStream> const xSeqOutStream( + io::SequenceOutputStream::create(m_xContext)); + uno::Reference<io::XOutputStream> const xRequestOutStream(xSeqOutStream); + assert(xRequestOutStream.is()); + uno::Reference<xml::sax::XWriter> const xWriter(xml::sax::Writer::create(m_xContext)); + xWriter->setOutputStream(xRequestOutStream); + xWriter->startDocument(); + rtl::Reference<::comphelper::AttributeList> const pAttrList(new ::comphelper::AttributeList); + pAttrList->AddAttribute("xmlns", "CDATA", "DAV:"); + xWriter->startElement("lockinfo", pAttrList); + xWriter->startElement("lockscope", nullptr); + switch (rLock.Scope) + { + case ucb::LockScope_EXCLUSIVE: + xWriter->startElement("exclusive", nullptr); + xWriter->endElement("exclusive"); + break; + case ucb::LockScope_SHARED: + xWriter->startElement("shared", nullptr); + xWriter->endElement("shared"); + break; + default: + assert(false); + } + xWriter->endElement("lockscope"); + xWriter->startElement("locktype", nullptr); + xWriter->startElement("write", nullptr); + xWriter->endElement("write"); + xWriter->endElement("locktype"); + OUString owner; + if ((rLock.Owner >>= owner) && !owner.isEmpty()) + { + xWriter->startElement("owner", nullptr); + xWriter->characters(owner); + xWriter->endElement("owner"); + } + xWriter->endElement("lockinfo"); + xWriter->endDocument(); + + uno::Reference<io::XInputStream> const xRequestInStream( + io::SequenceInputStream::createStreamFromSequence(m_xContext, + xSeqOutStream->getWrittenBytes())); + assert(xRequestInStream.is()); + + ::std::unique_ptr<curl_slist, deleter_from_fn<curl_slist, curl_slist_free_all>> pList; + pList.reset(curl_slist_append(pList.release(), "Content-Type: application/xml")); + if (!pList) + { + throw uno::RuntimeException("curl_slist_append failed"); + } + OString depth; + switch (rLock.Depth) + { + case ucb::LockDepth_ZERO: + depth = "Depth: 0"; + break; + case ucb::LockDepth_ONE: + depth = "Depth: 1"; + break; + case ucb::LockDepth_INFINITY: + depth = "Depth: infinity"; + break; + default: + assert(false); + } + pList.reset(curl_slist_append(pList.release(), depth.getStr())); + if (!pList) + { + throw uno::RuntimeException("curl_slist_append failed"); + } + OString timeout; + switch (rLock.Timeout) + { + case -1: + timeout = "Timeout: Infinite"; + break; + case 0: + timeout = "Timeout: Second-180"; + break; + default: + timeout = "Timeout: Second-" + OString::number(rLock.Timeout); + assert(0 < rLock.Timeout); + break; + } + pList.reset(curl_slist_append(pList.release(), timeout.getStr())); + if (!pList) + { + throw uno::RuntimeException("curl_slist_append failed"); + } + + auto const acquiredLocks + = CurlProcessor::Lock(*this, uri, &rEnv, ::std::move(pList), &xRequestInStream); + + for (auto const& rAcquiredLock : acquiredLocks) + { + g_Init.LockStore.addLock(uri.GetURI(), rAcquiredLock.first, + rAcquiredLock.first.LockTokens[0], this, rAcquiredLock.second); + SAL_INFO("ucb.ucp.webdav.curl", "created LOCK for " << rURIReference); + } +} + +auto CurlProcessor::Unlock(CurlSession& rSession, CurlUri const& rURI, + DAVRequestEnvironment const* const pEnv) -> void +{ + OUString const* const pToken(g_Init.LockStore.getLockTokenForURI(rURI.GetURI(), nullptr)); + if (!pToken) + { + SAL_WARN("ucb.ucp.webdav.curl", "attempt to unlock but not locked"); + throw DAVException(DAVException::DAV_NOT_LOCKED); + } + OString const utf8LockToken("Lock-Token: <" + + OUStringToOString(*pToken, RTL_TEXTENCODING_ASCII_US) + ">"); + ::std::unique_ptr<curl_slist, deleter_from_fn<curl_slist, curl_slist_free_all>> pList( + curl_slist_append(nullptr, utf8LockToken.getStr())); + if (!pList) + { + throw uno::RuntimeException("curl_slist_append failed"); + } + + ::std::vector<CurlOption> const options{ { CURLOPT_CUSTOMREQUEST, "UNLOCK", + "CURLOPT_CUSTOMREQUEST" } }; + + CurlProcessor::ProcessRequest(rSession, rURI, "UNLOCK", options, pEnv, ::std::move(pList), + nullptr, nullptr, nullptr); +} + +auto CurlSession::UNLOCK(OUString const& rURIReference, DAVRequestEnvironment const& rEnv) -> void +{ + SAL_INFO("ucb.ucp.webdav.curl", "UNLOCK: " << rURIReference); + + // note: no m_Mutex lock needed here, only in CurlProcessor::Unlock() + + CurlUri const uri(CurlProcessor::URIReferenceToURI(*this, rURIReference)); + + CurlProcessor::Unlock(*this, uri, &rEnv); + + g_Init.LockStore.removeLock(uri.GetURI()); +} + +auto CurlSession::NonInteractive_LOCK(OUString const& rURI, ::std::u16string_view const rLockToken, + sal_Int32& o_rLastChanceToSendRefreshRequest, + bool& o_rIsAuthFailed) -> bool +{ + SAL_INFO("ucb.ucp.webdav.curl", "NonInteractive_LOCK: " << rURI); + + // note: no m_Mutex lock needed here, only in CurlProcessor::Lock() + + try + { + CurlUri const uri(rURI); + ::std::unique_ptr<curl_slist, deleter_from_fn<curl_slist, curl_slist_free_all>> pList( + curl_slist_append(nullptr, "Timeout: Second-180")); + + assert(!rLockToken.empty()); // LockStore is the caller + OString const utf8If("If: (<" + OUStringToOString(rLockToken, RTL_TEXTENCODING_ASCII_US) + + ">)"); + pList.reset(curl_slist_append(pList.release(), utf8If.getStr())); + if (!pList) + { + throw uno::RuntimeException("curl_slist_append failed"); + } + + auto const acquiredLocks + = CurlProcessor::Lock(*this, uri, nullptr, ::std::move(pList), nullptr); + + SAL_WARN_IF(1 < acquiredLocks.size(), "ucb.ucp.webdav.curl", + "multiple locks acquired on refresh for " << rURI); + if (!acquiredLocks.empty()) + { + o_rLastChanceToSendRefreshRequest = acquiredLocks.begin()->second; + } + SAL_INFO("ucb.ucp.webdav.curl", "NonInteractive_LOCK succeeded on " << rURI); + return true; + } + catch (DAVException const& rException) + { + SAL_INFO("ucb.ucp.webdav.curl", "NonInteractive_LOCK failed on " << rURI); + switch (rException.getError()) + { + case DAVException::DAV_HTTP_AUTH: + case DAVException::DAV_HTTP_NOAUTH: + o_rIsAuthFailed = true; + break; + default: + break; + } + return false; + } + catch (...) + { + SAL_INFO("ucb.ucp.webdav.curl", "NonInteractive_LOCK failed on " << rURI); + return false; + } +} + +auto CurlSession::NonInteractive_UNLOCK(OUString const& rURI) -> void +{ + SAL_INFO("ucb.ucp.webdav.curl", "NonInteractive_UNLOCK: " << rURI); + + // note: no m_Mutex lock needed here, only in CurlProcessor::Unlock() + + try + { + CurlUri const uri(rURI); + + CurlProcessor::Unlock(*this, uri, nullptr); + + // the only caller is the dtor of the LockStore, don't call remove! + SAL_INFO("ucb.ucp.webdav.curl", "NonInteractive_UNLOCK succeeded on " << rURI); + } + catch (...) + { + SAL_INFO("ucb.ucp.webdav.curl", "NonInteractive_UNLOCK failed on " << rURI); + } +} + +} // namespace http_dav_ucp + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/ucb/source/ucp/webdav-curl/CurlSession.hxx b/ucb/source/ucp/webdav-curl/CurlSession.hxx new file mode 100644 index 000000000..75ccf6826 --- /dev/null +++ b/ucb/source/ucp/webdav-curl/CurlSession.hxx @@ -0,0 +1,145 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4; fill-column: 100 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ + +#pragma once + +#include "DAVSession.hxx" +#include "CurlUri.hxx" + +#include <curl/curl.h> + +#include <atomic> +#include <mutex> + +namespace http_dav_ucp +{ +/// implementation of libcurl HTTP/DAV back-end +class CurlSession : public DAVSession +{ +private: + /// mutex required to access all other non-const members + ::std::mutex m_Mutex; + css::uno::Reference<css::uno::XComponentContext> const m_xContext; + /// flags may be passed to constructor, e.g. "KeepAlive" + css::uno::Sequence<css::beans::NamedValue> const m_Flags; + CurlUri const m_URI; + /// buffer for libcurl detailed error messages + char m_ErrorBuffer[CURL_ERROR_SIZE]; + /// proxy is used if aName is non-empty + ::ucbhelper::InternetProxyServer const m_Proxy; + /// once authentication was successful, rely on m_pCurl's data + bool m_isAuthenticated = false; + bool m_isAuthenticatedProxy = false; + /// read timeout in milliseconds (connection timeout is stored in m_pCurl) + int m_nReadTimeout = 0; + /// flag to signal abort to transferring thread + ::std::atomic<bool> m_AbortFlag = false; + + /// libcurl multi handle + ::std::unique_ptr<CURLM, deleter_from_fn<CURLM, curl_multi_cleanup>> m_pCurlMulti; + /// libcurl easy handle + ::std::unique_ptr<CURL, deleter_from_fn<CURL, curl_easy_cleanup>> m_pCurl; + + // this class exists just to hide the implementation details in cxx file + friend struct CurlProcessor; + +public: + explicit CurlSession(css::uno::Reference<css::uno::XComponentContext> const& xContext, + ::rtl::Reference<DAVSessionFactory> const& rpFactory, OUString const& rURI, + css::uno::Sequence<css::beans::NamedValue> const& rFlags, + ::ucbhelper::InternetProxyDecider const& rProxyDecider); + virtual ~CurlSession() override; + + virtual auto CanUse(OUString const& rURI, + css::uno::Sequence<css::beans::NamedValue> const& rFlags) -> bool override; + + virtual auto UsesProxy() -> bool override; + + // DAV methods + virtual auto OPTIONS(OUString const& rURIReference, DAVOptions& rOptions, + DAVRequestEnvironment const& rEnv) -> void override; + + virtual auto PROPFIND(OUString const& rURIReference, Depth depth, + ::std::vector<OUString> const& rPropertyNames, + ::std::vector<DAVResource>& o_rResources, + DAVRequestEnvironment const& rEnv) -> void override; + + virtual auto PROPFIND(OUString const& rURIReference, Depth depth, + ::std::vector<DAVResourceInfo>& o_rResourceInfos, + DAVRequestEnvironment const& rEnv) -> void override; + + virtual auto PROPPATCH(OUString const& rURIReference, + ::std::vector<ProppatchValue> const& rValues, + DAVRequestEnvironment const& rEnv) -> void override; + + virtual auto HEAD(OUString const& rURIReference, ::std::vector<OUString> const& rHeaderNames, + DAVResource& io_rResource, DAVRequestEnvironment const& rEnv) + -> void override; + + virtual auto GET(OUString const& rURIReference, DAVRequestEnvironment const& rEnv) + -> css::uno::Reference<css::io::XInputStream> override; + + virtual auto GET(OUString const& rURIReference, + css::uno::Reference<css::io::XOutputStream>& rxOutStream, + DAVRequestEnvironment const& rEnv) -> void override; + + virtual auto GET(OUString const& rURIReference, ::std::vector<OUString> const& rHeaderNames, + DAVResource& io_rResource, DAVRequestEnvironment const& rEnv) + -> css::uno::Reference<css::io::XInputStream> override; + + virtual auto GET(OUString const& rURIReference, + css::uno::Reference<css::io::XOutputStream>& rxOutStream, + ::std::vector<OUString> const& rHeaderNames, DAVResource& io_rResource, + DAVRequestEnvironment const& rEnv) -> void override; + + virtual auto PUT(OUString const& rURIReference, + css::uno::Reference<css::io::XInputStream> const& rxInStream, + DAVRequestEnvironment const& rEnv) -> void override; + + virtual auto POST(OUString const& rURIReference, OUString const& rContentType, + OUString const& rReferer, + css::uno::Reference<css::io::XInputStream> const& rxInStream, + DAVRequestEnvironment const& rEnv) + -> css::uno::Reference<css::io::XInputStream> override; + + virtual auto POST(OUString const& rURIReference, OUString const& rContentType, + OUString const& rReferer, + css::uno::Reference<css::io::XInputStream> const& rxInStream, + css::uno::Reference<css::io::XOutputStream>& rxOutStream, + DAVRequestEnvironment const& rEnv) -> void override; + + virtual auto MKCOL(OUString const& rURIReference, DAVRequestEnvironment const& rEnv) + -> void override; + + virtual auto COPY(OUString const& rSourceURIReference, OUString const& rDestinationURI, + DAVRequestEnvironment const& rEnv, bool isOverwrite = false) -> void override; + + virtual auto MOVE(OUString const& rSourceURIReference, OUString const& rDestinationURI, + DAVRequestEnvironment const& rEnv, bool isOverwrite = false) -> void override; + + virtual auto DESTROY(OUString const& rURIReference, DAVRequestEnvironment const& rEnv) + -> void override; + + virtual auto LOCK(OUString const& rURIReference, css::ucb::Lock /*const*/& rLock, + DAVRequestEnvironment const& rEnv) -> void override; + + virtual auto UNLOCK(OUString const& rURIReference, DAVRequestEnvironment const& rEnv) + -> void override; + + virtual auto abort() -> void override; + + auto NonInteractive_LOCK(OUString const& rURI, ::std::u16string_view rLockToken, + sal_Int32& o_rLastChanceToSendRefreshRequest, bool& o_rIsAuthFailed) + -> bool; + auto NonInteractive_UNLOCK(OUString const& rURI) -> void; +}; + +} // namespace http_dav_ucp + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/ucb/source/ucp/webdav-curl/CurlUri.cxx b/ucb/source/ucp/webdav-curl/CurlUri.cxx new file mode 100644 index 000000000..4dba6ac22 --- /dev/null +++ b/ucb/source/ucp/webdav-curl/CurlUri.cxx @@ -0,0 +1,328 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4; fill-column: 100 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include "CurlUri.hxx" + +#include <sal/log.hxx> +#include <rtl/uri.hxx> +#include <rtl/ustrbuf.hxx> + +#include <optional> + +namespace http_dav_ucp +{ +const auto DEFAULT_HTTP_PORT = 80; +const auto DEFAULT_HTTPS_PORT = 443; + +static ::std::optional<OUString> GetURLComponent(CURLU& rURI, CURLUPart const what, + CURLUcode const expected, + unsigned int const flags = 0) +{ + char* pPart(nullptr); + auto uc = curl_url_get(&rURI, what, &pPart, flags); + if (expected != CURLUE_OK && uc == expected) + { + return ::std::optional<OUString>(); + } + if (uc != CURLUE_OK) + { + SAL_WARN("ucb.ucp.webdav.curl", "curl_url_get failed: " << what << " " << uc); + throw DAVException(DAVException::DAV_INVALID_ARG); + } + assert(pPart); + CurlUniquePtr<char> pPart2(pPart); + return ::rtl::OStringToOUString(pPart, RTL_TEXTENCODING_UTF8); +} + +void CurlUri::Init() +{ + // looks like the result should be the same as the old calculateURI() + auto const oURI(GetURLComponent(*m_pUrl, CURLUPART_URL, CURLUE_OK, CURLU_NO_DEFAULT_PORT)); + assert(oURI); + m_URI = *oURI; + + auto const oScheme(GetURLComponent(*m_pUrl, CURLUPART_SCHEME, CURLUE_NO_SCHEME)); + if (oScheme) + { + m_Scheme = *oScheme; + } + auto const oUser(GetURLComponent(*m_pUrl, CURLUPART_USER, CURLUE_NO_USER)); + if (oUser) + { + m_User = *oUser; + } + auto const oPassWord(GetURLComponent(*m_pUrl, CURLUPART_PASSWORD, CURLUE_NO_PASSWORD)); + if (oPassWord) + { + m_Password = *oPassWord; + } + auto const oHost(GetURLComponent(*m_pUrl, CURLUPART_HOST, CURLUE_NO_HOST)); + if (oHost) + { + m_Host = *oHost; + } + // DAV schemes always have port but Content::transfer() is called with + // arbitrary URLs so use CURLUE_NO_PORT + auto const oPort(GetURLComponent(*m_pUrl, CURLUPART_PORT, CURLUE_NO_PORT, CURLU_DEFAULT_PORT)); + if (oPort) + { + m_nPort = oPort->toInt32(); + } + + auto const oPath(GetURLComponent(*m_pUrl, CURLUPART_PATH, CURLUE_OK)); + assert(oPath); + m_Path = *oPath; + + // note: this used to be added to m_Path because before 2007, ne_uri path contained query/fragment as well :-/ + auto const oQuery(GetURLComponent(*m_pUrl, CURLUPART_QUERY, CURLUE_NO_QUERY)); + if (oQuery) + { + m_QueryAndFragment += "?" + *oQuery; + } + auto const oFragment(GetURLComponent(*m_pUrl, CURLUPART_FRAGMENT, CURLUE_NO_FRAGMENT)); + if (oFragment) + { + m_QueryAndFragment += "#" + *oFragment; + } +} + +CurlUri::CurlUri(::std::u16string_view const rURI) +{ + // note: in the old implementation, the rURI would be URI-encoded again + // here, apparently because it could actually be an IRI (RFC 3987) and + // neon didn't support that - not clear if this is a good idea + + m_pUrl.reset(curl_url()); + if (!m_pUrl) + { + throw ::std::bad_alloc(); + } + + // use curl to parse the URI, to get a consistent interpretation + OString const utf8URI(OUStringToOString(rURI, RTL_TEXTENCODING_UTF8)); + auto uc = curl_url_set(m_pUrl.get(), CURLUPART_URL, utf8URI.getStr(), 0); + if (uc != CURLUE_OK) + { + SAL_WARN("ucb.ucp.webdav.curl", "curl_url_set failed: " << uc); + throw DAVException(DAVException::DAV_INVALID_ARG); + } + + Init(); +} + +CurlUri::CurlUri(CURLU /*const*/& rUrl) + : m_pUrl(curl_url_dup(&rUrl)) +{ + if (!m_pUrl) + { + throw ::std::bad_alloc(); + } + + Init(); +} + +CurlUri::CurlUri(CurlUri const& rOther) + : m_pUrl(curl_url_dup(rOther.m_pUrl.get())) + , m_URI(rOther.m_URI) + , m_Scheme(rOther.m_Scheme) + , m_User(rOther.m_User) + , m_Password(rOther.m_Password) + , m_Host(rOther.m_Host) + , m_nPort(rOther.m_nPort) + , m_Path(rOther.m_Path) + , m_QueryAndFragment(rOther.m_QueryAndFragment) +{ + assert(rOther.m_pUrl); + if (!m_pUrl) + { + throw ::std::bad_alloc(); + } +} + +void CurlUri::operator=(CurlUri const& rOther) +{ + assert(rOther.m_pUrl); + m_pUrl.reset(curl_url_dup(rOther.m_pUrl.get())); + if (!m_pUrl) + { + throw ::std::bad_alloc(); + } + m_URI = rOther.m_URI; + m_Scheme = rOther.m_Scheme; + m_User = rOther.m_User; + m_Password = rOther.m_Password; + m_Host = rOther.m_Host; + m_nPort = rOther.m_nPort; + m_Path = rOther.m_Path; + m_QueryAndFragment = rOther.m_QueryAndFragment; +} + +bool CurlUri::operator==(CurlUri const& rOther) const { return m_URI == rOther.m_URI; } + +OUString CurlUri::GetPathBaseName() const +{ + sal_Int32 nPos = m_Path.lastIndexOf('/'); + sal_Int32 nTrail = 0; + if (nPos == m_Path.getLength() - 1) + { + // Trailing slash found. Skip. + nTrail = 1; + nPos = m_Path.lastIndexOf('/', nPos); + } + if (nPos == -1) + { + return "/"; + } + return m_Path.copy(nPos + 1, m_Path.getLength() - nPos - 1 - nTrail); +} + +OUString CurlUri::GetPathBaseNameUnescaped() const { return DecodeURI(GetPathBaseName()); } + +void CurlUri::SetScheme(::std::u16string_view const rScheme) +{ + OString const utf8URI(OUStringToOString(rScheme, RTL_TEXTENCODING_UTF8)); + auto uc = curl_url_set(m_pUrl.get(), CURLUPART_SCHEME, utf8URI.getStr(), 0); + if (uc != CURLUE_OK) + { + SAL_WARN("ucb.ucp.webdav.curl", "curl_url_set failed: " << uc); + throw DAVException(DAVException::DAV_INVALID_ARG); + } + auto const oURI(GetURLComponent(*m_pUrl, CURLUPART_URL, CURLUE_OK, CURLU_NO_DEFAULT_PORT)); + assert(oURI); + m_URI = *oURI; + auto const oScheme(GetURLComponent(*m_pUrl, CURLUPART_SCHEME, CURLUE_NO_SCHEME)); + if (oScheme) + { + m_Scheme = *oScheme; + } +} + +void CurlUri::AppendPath(::std::u16string_view const rPath) +{ + OUStringBuffer path(m_Path); + if (path.lastIndexOf('/') != path.getLength() - 1) + { + path.append("/"); + } + path.append(rPath); + OString const utf8Path(OUStringToOString(path.makeStringAndClear(), RTL_TEXTENCODING_UTF8)); + auto uc = curl_url_set(m_pUrl.get(), CURLUPART_PATH, utf8Path.getStr(), 0); + if (uc != CURLUE_OK) + { + SAL_WARN("ucb.ucp.webdav.curl", "curl_url_set failed: " << uc); + throw DAVException(DAVException::DAV_INVALID_ARG); + } + auto const oURI(GetURLComponent(*m_pUrl, CURLUPART_URL, CURLUE_OK, CURLU_NO_DEFAULT_PORT)); + assert(oURI); + m_URI = *oURI; + auto const oPath(GetURLComponent(*m_pUrl, CURLUPART_PATH, CURLUE_OK)); + assert(oPath); + m_Path = *oPath; +} + +CurlUri CurlUri::CloneWithRelativeRefPathAbsolute(OUString const& rRelativeRef) const +{ + ::std::unique_ptr<CURLU, deleter_from_fn<CURLU, curl_url_cleanup>> pUrl( + curl_url_dup(m_pUrl.get())); + sal_Int32 indexEnd(rRelativeRef.getLength()); + auto const indexQuery(rRelativeRef.indexOf('?')); + auto const indexFragment(rRelativeRef.indexOf('#')); + CURLUcode uc; + if (indexFragment != -1) + { + std::u16string_view const fragment(rRelativeRef.subView(indexFragment + 1)); + indexEnd = indexFragment; + OString const utf8Fragment(OUStringToOString(fragment, RTL_TEXTENCODING_UTF8)); + uc = curl_url_set(pUrl.get(), CURLUPART_FRAGMENT, utf8Fragment.getStr(), 0); + } + else + { + uc = curl_url_set(pUrl.get(), CURLUPART_FRAGMENT, nullptr, 0); + } + if (uc != CURLUE_OK) + { + SAL_WARN("ucb.ucp.webdav.curl", "curl_url_set failed: " << uc); + throw DAVException(DAVException::DAV_INVALID_ARG); + } + if (indexQuery != -1 && (indexFragment == -1 || indexQuery < indexFragment)) + { + std::u16string_view const query( + rRelativeRef.subView(indexQuery + 1, indexEnd - indexQuery - 1)); + indexEnd = indexQuery; + OString const utf8Query(OUStringToOString(query, RTL_TEXTENCODING_UTF8)); + uc = curl_url_set(pUrl.get(), CURLUPART_QUERY, utf8Query.getStr(), 0); + } + else + { + uc = curl_url_set(pUrl.get(), CURLUPART_QUERY, nullptr, 0); + } + if (uc != CURLUE_OK) + { + SAL_WARN("ucb.ucp.webdav.curl", "curl_url_set failed: " << uc); + throw DAVException(DAVException::DAV_INVALID_ARG); + } + std::u16string_view const path(rRelativeRef.subView(0, indexEnd)); + OString const utf8Path(OUStringToOString(path, RTL_TEXTENCODING_UTF8)); + uc = curl_url_set(pUrl.get(), CURLUPART_PATH, utf8Path.getStr(), 0); + if (uc != CURLUE_OK) + { + SAL_WARN("ucb.ucp.webdav.curl", "curl_url_set failed: " << uc); + throw DAVException(DAVException::DAV_INVALID_ARG); + } + return CurlUri(*pUrl.release()); +} + +OUString EncodeSegment(OUString const& rSegment) +{ + return rtl::Uri::encode(rSegment, rtl_UriCharClassPchar, rtl_UriEncodeIgnoreEscapes, + RTL_TEXTENCODING_UTF8); +} + +OUString DecodeURI(OUString const& rURI) +{ + return rtl::Uri::decode(rURI, rtl_UriDecodeWithCharset, RTL_TEXTENCODING_UTF8); +} + +OUString ConnectionEndPointString(std::u16string_view rHostName, sal_uInt16 const nPort) +{ + OUStringBuffer aBuf; + + // Is host a numeric IPv6 address? + if ((rHostName.find(':') != std::u16string_view::npos) && (rHostName[0] != '[')) + { + aBuf.append("["); + aBuf.append(rHostName); + aBuf.append("]"); + } + else + { + aBuf.append(rHostName); + } + + if ((nPort != DEFAULT_HTTP_PORT) && (nPort != DEFAULT_HTTPS_PORT)) + { + aBuf.append(":"); + aBuf.append(sal_Int32(nPort)); + } + return aBuf.makeStringAndClear(); +} + +} // namespace http_dav_ucp + +/* vim:set shiftwidth=4 softtabstop=4 expandtab cinoptions=b1,g0,N-s cinkeys+=0=break: */ diff --git a/ucb/source/ucp/webdav-curl/CurlUri.hxx b/ucb/source/ucp/webdav-curl/CurlUri.hxx new file mode 100644 index 000000000..56336af78 --- /dev/null +++ b/ucb/source/ucp/webdav-curl/CurlUri.hxx @@ -0,0 +1,94 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4; fill-column: 100 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#pragma once + +#include <curl/curl.h> + +#include <memory> + +#include <rtl/ustring.hxx> + +#include "DAVException.hxx" + +namespace http_dav_ucp +{ +template <typename T, auto fn> struct deleter_from_fn +{ + void operator()(T* p) const { fn(p); } +}; +template <typename T> using CurlUniquePtr = ::std::unique_ptr<T, deleter_from_fn<T, curl_free>>; + +class CurlUri +{ +private: + /// native curl representation of parsed URI + ::std::unique_ptr<CURLU, deleter_from_fn<CURLU, curl_url_cleanup>> m_pUrl; + /// duplicate state for quick access to some components + OUString m_URI; + OUString m_Scheme; + OUString m_User; + OUString m_Password; + OUString m_Host; + sal_uInt16 m_nPort = 0; + OUString m_Path; + OUString m_QueryAndFragment; + + void Init(); + +public: + CurlUri(CurlUri const& rUri); + CurlUri(CURLU /*const*/& rUrl); + void operator=(CurlUri const& rOther); + + /// @throws DAVException + explicit CurlUri(::std::u16string_view rURI); + + bool operator==(CurlUri const& rOther) const; + + CURLU const* GetCURLU() const { return m_pUrl.get(); } + OUString const& GetURI() const { return m_URI; } + OUString const& GetScheme() const { return m_Scheme; } + OUString const& GetUser() const { return m_User; } + OUString const& GetPassword() const { return m_Password; } + OUString const& GetHost() const { return m_Host; } + sal_uInt16 GetPort() const { return m_nPort; } + OUString const& GetPath() const { return m_Path; } + OUString GetRelativeReference() const { return m_Path + m_QueryAndFragment; } + + OUString GetPathBaseName() const; + + OUString GetPathBaseNameUnescaped() const; + + /// @throws DAVException + void SetScheme(::std::u16string_view rScheme); + /// @throws DAVException + void AppendPath(::std::u16string_view rPath); + /// @param matches: relative-ref = path-absolute [ "?" query ] [ "#" fragment ] + /// @throws DAVException + CurlUri CloneWithRelativeRefPathAbsolute(OUString const& rRelativeRef) const; +}; + +OUString EncodeSegment(OUString const& rSegment); +OUString DecodeURI(OUString const& rURI); +OUString ConnectionEndPointString(std::u16string_view rHost, sal_uInt16 nPort); + +} // namespace http_dav_ucp + +/* vim:set shiftwidth=4 softtabstop=4 expandtab cinoptions=b1,g0,N-s cinkeys+=0=break: */ diff --git a/ucb/source/ucp/webdav-curl/DAVAuthListener.hxx b/ucb/source/ucp/webdav-curl/DAVAuthListener.hxx new file mode 100644 index 000000000..c3d643bc7 --- /dev/null +++ b/ucb/source/ucp/webdav-curl/DAVAuthListener.hxx @@ -0,0 +1,43 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + + +#pragma once + +#include <salhelper/simplereferenceobject.hxx> +#include <rtl/ustring.hxx> + +namespace http_dav_ucp +{ + +class DAVAuthListener : public salhelper::SimpleReferenceObject +{ + public: + virtual int authenticate( + const OUString & inRealm, + const OUString & inHostName, + OUString & inoutUserName, + OUString & outPassWord, + bool bCanUseSystemCredentials, + bool bUsePreviousCredentials = true ) = 0; +}; + +} // namespace http_dav_ucp + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/ucb/source/ucp/webdav-curl/DAVAuthListenerImpl.hxx b/ucb/source/ucp/webdav-curl/DAVAuthListenerImpl.hxx new file mode 100644 index 000000000..219a64906 --- /dev/null +++ b/ucb/source/ucp/webdav-curl/DAVAuthListenerImpl.hxx @@ -0,0 +1,61 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + + +#pragma once + +#include "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; + }; + +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/ucb/source/ucp/webdav-curl/DAVException.hxx b/ucb/source/ucp/webdav-curl/DAVException.hxx new file mode 100644 index 000000000..24f8349d3 --- /dev/null +++ b/ucb/source/ucp/webdav-curl/DAVException.hxx @@ -0,0 +1,175 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + + +#pragma once + +#include <rtl/ustring.hxx> + +namespace http_dav_ucp +{ + + +// HTTP/WebDAV status codes + + +const sal_uInt16 SC_NONE = 0; + +// 1xx (Informational - no errors) +const sal_uInt16 SC_CONTINUE = 100; +const sal_uInt16 SC_SWITCHING_PROTOCOLS = 101; +// DAV extensions +const sal_uInt16 SC_PROCESSING = 102; + +//2xx (Successful - no errors) +const sal_uInt16 SC_OK = 200; +const sal_uInt16 SC_CREATED = 201; +const sal_uInt16 SC_ACCEPTED = 202; +const sal_uInt16 SC_NON_AUTHORITATIVE_INFORMATION = 203; +const sal_uInt16 SC_NO_CONTENT = 204; +const sal_uInt16 SC_RESET_CONTENT = 205; +const sal_uInt16 SC_PARTIAL_CONTENT = 206; +// DAV extensions +const sal_uInt16 SC_MULTISTATUS = 207; + +//3xx (Redirection) +const sal_uInt16 SC_MULTIPLE_CHOICES = 300; +const sal_uInt16 SC_MOVED_PERMANENTLY = 301; +const sal_uInt16 SC_MOVED_TEMPORARILY = 302; +const sal_uInt16 SC_SEE_OTHER = 303; +const sal_uInt16 SC_NOT_MODIFIED = 304; +const sal_uInt16 SC_USE_PROXY = 305; +const sal_uInt16 SC_TEMPORARY_REDIRECT = 307; + +//4xx (Client error) +const sal_uInt16 SC_BAD_REQUEST = 400; +const sal_uInt16 SC_UNAUTHORIZED = 401; +const sal_uInt16 SC_PAYMENT_REQUIRED = 402; +const sal_uInt16 SC_FORBIDDEN = 403; +const sal_uInt16 SC_NOT_FOUND = 404; +const sal_uInt16 SC_METHOD_NOT_ALLOWED = 405; +const sal_uInt16 SC_NOT_ACCEPTABLE = 406; +const sal_uInt16 SC_PROXY_AUTHENTICATION_REQUIRED = 407; +const sal_uInt16 SC_REQUEST_TIMEOUT = 408; +const sal_uInt16 SC_CONFLICT = 409; +const sal_uInt16 SC_GONE = 410; +const sal_uInt16 SC_LENGTH_REQUIRED = 411; +const sal_uInt16 SC_PRECONDITION_FAILED = 412; +const sal_uInt16 SC_REQUEST_ENTITY_TOO_LARGE = 413; +const sal_uInt16 SC_REQUEST_URI_TOO_LONG = 414; +const sal_uInt16 SC_UNSUPPORTED_MEDIA_TYPE = 415; +const sal_uInt16 SC_REQUESTED_RANGE_NOT_SATISFIABLE = 416; +const sal_uInt16 SC_EXPECTATION_FAILED = 417; +// DAV extensions +const sal_uInt16 SC_UNPROCESSABLE_ENTITY = 422; +const sal_uInt16 SC_LOCKED = 423; +const sal_uInt16 SC_FAILED_DEPENDENCY = 424; + +//5xx (Server error, general <https://tools.ietf.org/html/rfc7231#section-6.6>) +const sal_uInt16 SC_INTERNAL_SERVER_ERROR = 500; +const sal_uInt16 SC_NOT_IMPLEMENTED = 501; +const sal_uInt16 SC_BAD_GATEWAY = 502; +const sal_uInt16 SC_SERVICE_UNAVAILABLE = 503; +const sal_uInt16 SC_GATEWAY_TIMEOUT = 504; +const sal_uInt16 SC_HTTP_VERSION_NOT_SUPPORTED = 505; +// DAV extensions (<https://tools.ietf.org/html/rfc4918#section-11>) +const sal_uInt16 SC_INSUFFICIENT_STORAGE = 507; + +// unofficial status codes only used internally by LO +// used to cache the connection time out event +const sal_uInt16 USC_CONNECTION_TIMED_OUT = 908; + // name resolution failed +const sal_uInt16 USC_LOOKUP_FAILED = 909; +const sal_uInt16 USC_AUTH_FAILED = 910; +const sal_uInt16 USC_AUTHPROXY_FAILED = 911; + + + +class DAVException : public std::exception +{ + public: + enum ExceptionCode { + DAV_HTTP_ERROR = 0, // Generic error, + // mData = server error message, + // mStatusCode = HTTP status code + DAV_HTTP_LOOKUP, // Name lookup failed, + // mData = server[:port] + DAV_HTTP_NOAUTH, // No User authentication data provided - e.g., user aborts corresponding dialog + // mData = server[:port] + DAV_HTTP_AUTH, // User authentication failed on server, + // mData = server[:port] + DAV_HTTP_AUTHPROXY, // User authentication failed on proxy, + // mData = proxy server[:port] + DAV_HTTP_CONNECT, // Could not connect to server, + // mData = server[:port] + DAV_HTTP_TIMEOUT, // Connection timed out + // mData = server[:port] + DAV_HTTP_FAILED, // The precondition failed + // mData = server[:port] + DAV_HTTP_RETRY, // Retry request + // mData = server[:port] + DAV_HTTP_REDIRECT, // Request was redirected, + // mData = new URL + DAV_SESSION_CREATE, // session creation error, + // mData = server[:port] + DAV_INVALID_ARG, // invalid argument + DAV_UNSUPPORTED, // internal to CurlSession + + DAV_LOCK_EXPIRED, // DAV lock expired + + DAV_NOT_LOCKED, // not locked + + DAV_LOCKED_SELF, // locked by this OOo session + + DAV_LOCKED // locked by third party + }; + + private: + ExceptionCode mExceptionCode; + OUString mData; + sal_uInt16 mStatusCode; + + public: + explicit DAVException( ExceptionCode inExceptionCode ) + : mExceptionCode( inExceptionCode ) + , mData() + , mStatusCode( SC_NONE ) + {}; + DAVException( ExceptionCode inExceptionCode, + 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 + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/ucb/source/ucp/webdav-curl/DAVProperties.cxx b/ucb/source/ucp/webdav-curl/DAVProperties.cxx new file mode 100644 index 000000000..8b3dce369 --- /dev/null +++ b/ucb/source/ucp/webdav-curl/DAVProperties.cxx @@ -0,0 +1,202 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include "DAVProperties.hxx" +#include <rtl/ustrbuf.hxx> +#include <o3tl/string_view.hxx> +#include <string.h> + +using namespace http_dav_ucp; + + +// static +void DAVProperties::createSerfPropName( ::std::u16string_view const rFullName, + SerfPropName & rName ) +{ + if (o3tl::starts_with(rFullName, u"DAV:")) + { + rName.nspace = "DAV:"; + rName.name + = strdup( OUStringToOString( + rFullName.substr(RTL_CONSTASCII_LENGTH("DAV:")), + RTL_TEXTENCODING_UTF8 ).getStr() ); + } + else if (o3tl::starts_with(rFullName, u"http://apache.org/dav/props/")) + { + rName.nspace = "http://apache.org/dav/props/"; + rName.name + = strdup( OUStringToOString( + rFullName.substr( + RTL_CONSTASCII_LENGTH( + "http://apache.org/dav/props/" ) ), + RTL_TEXTENCODING_UTF8 ).getStr() ); + } + else if (o3tl::starts_with(rFullName, u"http://ucb.openoffice.org/dav/props/")) + { + rName.nspace = "http://ucb.openoffice.org/dav/props/"; + rName.name + = strdup( OUStringToOString( + rFullName.substr( + RTL_CONSTASCII_LENGTH( + "http://ucb.openoffice.org/dav/props/" ) ), + RTL_TEXTENCODING_UTF8 ).getStr() ); + } + else if (o3tl::starts_with(rFullName, u"<prop:")) + { + // Support for 3rd party namespaces/props + + OString aFullName + = OUStringToOString( rFullName, RTL_TEXTENCODING_UTF8 ); + + // Format: <prop:the_propname xmlns:prop="the_namespace"> + + sal_Int32 nStart = RTL_CONSTASCII_LENGTH( "<prop:" ); + sal_Int32 nLen = aFullName.indexOf( ' ' ) - nStart; + rName.name = strdup( aFullName.copy( nStart, nLen ).getStr() ); + + nStart = aFullName.indexOf( '=', nStart + nLen ) + 2; // after =" + nLen = aFullName.getLength() - RTL_CONSTASCII_LENGTH( "\">" ) - nStart; + rName.nspace = strdup( aFullName.copy( nStart, nLen ).getStr() ); + } + else + { + // this must not be a URI - WebDAVResponseParser must have converted it + // to the "<prop:" form above + assert(rFullName.find(':') == ::std::u16string_view::npos); + // Add our namespace to our own properties. + rName.nspace = "http://ucb.openoffice.org/dav/props/"; + rName.name + = strdup( OUStringToOString( rFullName, + RTL_TEXTENCODING_UTF8 ).getStr() ); + } +} + + +// static +void DAVProperties::createUCBPropName( const char * nspace, + const char * name, + OUString & rFullName ) +{ + OUString aNameSpace + = OStringToOUString( nspace, RTL_TEXTENCODING_UTF8 ); + OUString aName + = OStringToOUString( name, RTL_TEXTENCODING_UTF8 ); + + if ( !aNameSpace.getLength() ) + { + // Some servers send XML without proper namespaces. Assume "DAV:" + // in this case, if name is a well-known dav property name. + // Although this is not 100% correct, it solves many problems. + + if (o3tl::equalsIgnoreAsciiCase(aName, std::u16string_view(DAVProperties::RESOURCETYPE).substr(4)) || + o3tl::equalsIgnoreAsciiCase(aName, std::u16string_view(DAVProperties::SUPPORTEDLOCK).substr(4)) || + o3tl::equalsIgnoreAsciiCase(aName, std::u16string_view(DAVProperties::LOCKDISCOVERY).substr(4)) || + o3tl::equalsIgnoreAsciiCase(aName, std::u16string_view(DAVProperties::CREATIONDATE).substr(4)) || + o3tl::equalsIgnoreAsciiCase(aName, std::u16string_view(DAVProperties::DISPLAYNAME).substr(4)) || + o3tl::equalsIgnoreAsciiCase(aName, std::u16string_view(DAVProperties::GETCONTENTLANGUAGE).substr(4)) || + o3tl::equalsIgnoreAsciiCase(aName, std::u16string_view(DAVProperties::GETCONTENTLENGTH).substr(4)) || + o3tl::equalsIgnoreAsciiCase(aName, std::u16string_view(DAVProperties::GETCONTENTTYPE).substr(4)) || + o3tl::equalsIgnoreAsciiCase(aName, std::u16string_view(DAVProperties::GETETAG).substr(4)) || + o3tl::equalsIgnoreAsciiCase(aName, std::u16string_view(DAVProperties::GETLASTMODIFIED).substr(4))) + { + aNameSpace = "DAV:"; + } + } + + // Note: Concatenating strings BEFORE comparing against known namespaces + // is important. See RFC 2815 ( 23.4.2 Meaning of Qualified Names ). + rFullName = aNameSpace; + rFullName += aName; + + if ( rFullName.startsWith( "DAV:" ) ) + { + // Okay, Just concat strings. + } + else if ( rFullName.startsWith( "http://apache.org/dav/props/" ) ) + { + // Okay, Just concat strings. + } + else if ( rFullName.startsWith( "http://ucb.openoffice.org/dav/props/" ) ) + { + // Remove namespace from our own properties. + rFullName = rFullName.copy( + RTL_CONSTASCII_LENGTH( + "http://ucb.openoffice.org/dav/props/" ) ); + } + else + { + // Create property name that encodes, namespace and name ( XML ). + rFullName = "<prop:"; + rFullName += aName; + rFullName += " xmlns:prop=\""; + rFullName += aNameSpace; + rFullName += "\">"; + } +} + + +// static +bool DAVProperties::isUCBDeadProperty( const SerfPropName & rName ) +{ + return ( rName.nspace && + ( rtl_str_compareIgnoreAsciiCase( + rName.nspace, "http://ucb.openoffice.org/dav/props/" ) + == 0 ) ); +} + +bool DAVProperties::isUCBSpecialProperty(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-curl/DAVProperties.hxx b/ucb/source/ucp/webdav-curl/DAVProperties.hxx new file mode 100644 index 000000000..af005300c --- /dev/null +++ b/ucb/source/ucp/webdav-curl/DAVProperties.hxx @@ -0,0 +1,57 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + + +#pragma once + +#include <rtl/ustring.hxx> + +namespace http_dav_ucp +{ + +typedef struct { const char *nspace, *name; } SerfPropName; + +struct DAVProperties +{ + static constexpr OUStringLiteral CREATIONDATE = u"DAV:creationdate"; + static constexpr OUStringLiteral DISPLAYNAME = u"DAV:displayname"; + static constexpr OUStringLiteral GETCONTENTLANGUAGE = u"DAV:getcontentlanguage"; + static constexpr OUStringLiteral GETCONTENTLENGTH = u"DAV:getcontentlength"; + static constexpr OUStringLiteral GETCONTENTTYPE = u"DAV:getcontenttype"; + static constexpr OUStringLiteral GETETAG = u"DAV:getetag"; + static constexpr OUStringLiteral GETLASTMODIFIED = u"DAV:getlastmodified"; + static constexpr OUStringLiteral LOCKDISCOVERY = u"DAV:lockdiscovery"; + static constexpr OUStringLiteral RESOURCETYPE = u"DAV:resourcetype"; + static constexpr OUStringLiteral SUPPORTEDLOCK = u"DAV:supportedlock"; + static constexpr OUStringLiteral EXECUTABLE = u"http://apache.org/dav/props/executable"; + + static void createSerfPropName( ::std::u16string_view rFullName, + SerfPropName & rName ); + static void createUCBPropName ( const char * nspace, + const char * name, + OUString & rFullName ); + + static bool isUCBDeadProperty( const SerfPropName & rName ); + static bool isUCBSpecialProperty( const OUString & rFullName, + OUString & rParsedName ); +}; + +} // namespace http_dav_ucp + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/ucb/source/ucp/webdav-curl/DAVRequestEnvironment.hxx b/ucb/source/ucp/webdav-curl/DAVRequestEnvironment.hxx new file mode 100644 index 000000000..88cdebde5 --- /dev/null +++ b/ucb/source/ucp/webdav-curl/DAVRequestEnvironment.hxx @@ -0,0 +1,45 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + + +#pragma once + +#include <vector> +#include <rtl/ref.hxx> +#include "DAVAuthListener.hxx" + +namespace http_dav_ucp +{ + typedef std::pair< OUString, OUString > DAVRequestHeader; + typedef std::vector< DAVRequestHeader > DAVRequestHeaders; + +struct DAVRequestEnvironment +{ + rtl::Reference< DAVAuthListener > m_xAuthListener; + DAVRequestHeaders m_aRequestHeaders; + + DAVRequestEnvironment( const rtl::Reference< DAVAuthListener > & xListener, + const DAVRequestHeaders & rRequestHeaders) + : m_xAuthListener( xListener ), + m_aRequestHeaders( rRequestHeaders ) {} +}; + +} // namespace http_dav_ucp + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/ucb/source/ucp/webdav-curl/DAVResource.hxx b/ucb/source/ucp/webdav-curl/DAVResource.hxx new file mode 100644 index 000000000..93cdd743f --- /dev/null +++ b/ucb/source/ucp/webdav-curl/DAVResource.hxx @@ -0,0 +1,58 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + + +#pragma once + +#include <vector> + +#include <rtl/ustring.hxx> +#include <com/sun/star/uno/Any.hxx> + +namespace http_dav_ucp +{ + +struct DAVPropertyValue +{ + OUString Name; + css::uno::Any Value; + bool IsCaseSensitive; + + DAVPropertyValue() : IsCaseSensitive( true ) {} +}; + +struct DAVResource +{ + OUString uri; + std::vector< DAVPropertyValue > properties; +}; + +struct DAVResourceInfo +{ + std::vector < OUString > properties; + + bool operator==( const struct DAVResourceInfo& a ) const + { + return (properties == a.properties ); + } +}; + +} // namespace http_dav_ucp + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/ucb/source/ucp/webdav-curl/DAVResourceAccess.cxx b/ucb/source/ucp/webdav-curl/DAVResourceAccess.cxx new file mode 100644 index 000000000..0119495e3 --- /dev/null +++ b/ucb/source/ucp/webdav-curl/DAVResourceAccess.cxx @@ -0,0 +1,1171 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + + +#include <com/sun/star/task/XInteractionAbort.hpp> +#include <com/sun/star/ucb/XWebDAVCommandEnvironment.hpp> + +#include <ucbhelper/simpleauthenticationrequest.hxx> +#include <comphelper/seekableinput.hxx> + +#include "DAVAuthListenerImpl.hxx" +#include "DAVResourceAccess.hxx" + +#include <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, + bCanUseSystemCredentials ); + xIH->handle( xRequest ); + + rtl::Reference< ucbhelper::InteractionContinuation > xSelection + = xRequest->getSelection(); + + if ( xSelection.is() ) + { + // Handler handled the request. + uno::Reference< task::XInteractionAbort > xAbort( + xSelection.get(), uno::UNO_QUERY ); + if ( !xAbort.is() ) + { + const rtl::Reference< + ucbhelper::InteractionSupplyAuthentication > & xSupp + = xRequest->getAuthenticationSupplier(); + + bool bUseSystemCredentials = false; + + if ( bCanUseSystemCredentials ) + bUseSystemCredentials + = xSupp->getUseSystemCredentials(); + + if ( bUseSystemCredentials ) + { + // This is the (strange) way to tell neon to use + // system credentials. + inoutUserName.clear(); + outPassWord.clear(); + } + else + { + inoutUserName = xSupp->getUserName(); + outPassWord = xSupp->getPassword(); + } + + // #102871# - Remember username and password. + m_aPrevUsername = inoutUserName; + m_aPrevPassword = outPassWord; + + // go on. + return 0; + } + } + } + } + // Abort. + return -1; +} + + +// DAVResourceAccess Implementation. + + +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 ) +, m_nRedirectLimit( 5 ) +{ +} + + +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 ), + m_nRedirectLimit( rOther.m_nRedirectLimit ) +{ +} + + +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; + m_nRedirectLimit = rOther.m_nRedirectLimit; + + return *this; +} + +void DAVResourceAccess::OPTIONS( + DAVOptions & rOptions, + const uno::Reference< ucb::XCommandEnvironment > & xEnv ) +{ + initialize(); + + int errorCount = 0; + bool bRetry; + do + { + bRetry = false; + try + { + DAVRequestHeaders aHeaders; + + getUserRequestHeaders( xEnv, + getRequestURI(), + ucb::WebDAVHTTPMethod_OPTIONS, + aHeaders ); + + m_xSession->OPTIONS( getRequestURI(), + rOptions, + DAVRequestEnvironment( + new DAVAuthListener_Impl( xEnv, m_aURL ), + aHeaders ) ); + } + catch (DAVException const& e) + { + errorCount++; + bRetry = handleException( e, errorCount ); + if ( !bRetry ) + throw; + } + } + while ( bRetry ); +} + +void DAVResourceAccess::PROPFIND( + const Depth nDepth, + const std::vector< OUString > & rPropertyNames, + std::vector< DAVResource > & rResources, + const uno::Reference< ucb::XCommandEnvironment > & xEnv ) +{ + initialize(); + + int errorCount = 0; + bool bRetry; + do + { + bRetry = false; + try + { + DAVRequestHeaders aHeaders; + + getUserRequestHeaders( xEnv, + getRequestURI(), + ucb::WebDAVHTTPMethod_PROPFIND, + aHeaders ); + + m_xSession->PROPFIND( getRequestURI(), + nDepth, + rPropertyNames, + rResources, + DAVRequestEnvironment( + new DAVAuthListener_Impl( xEnv, m_aURL ), + aHeaders ) ); + } + catch (DAVException const& e) + { + errorCount++; + bRetry = handleException( e, errorCount ); + if ( !bRetry ) + throw; + } + } + while ( bRetry ); +} + + +void DAVResourceAccess::PROPFIND( + const Depth nDepth, + std::vector< DAVResourceInfo > & rResInfo, + const uno::Reference< ucb::XCommandEnvironment > & xEnv ) +{ + initialize(); + + int errorCount = 0; + bool bRetry; + do + { + bRetry = false; + try + { + DAVRequestHeaders aHeaders; + getUserRequestHeaders( xEnv, + getRequestURI(), + ucb::WebDAVHTTPMethod_PROPFIND, + aHeaders ); + + m_xSession->PROPFIND( getRequestURI(), + nDepth, + rResInfo, + DAVRequestEnvironment( + new DAVAuthListener_Impl( xEnv, m_aURL ), + aHeaders ) ) ; + } + catch (DAVException const& e) + { + errorCount++; + bRetry = handleException( e, errorCount ); + if ( !bRetry ) + throw; + } + } + while ( bRetry ); +} + + +void DAVResourceAccess::PROPPATCH( + const std::vector< ProppatchValue >& rValues, + const uno::Reference< ucb::XCommandEnvironment >& xEnv ) +{ + initialize(); + + int errorCount = 0; + bool bRetry; + do + { + bRetry = false; + try + { + DAVRequestHeaders aHeaders; + getUserRequestHeaders( xEnv, + getRequestURI(), + ucb::WebDAVHTTPMethod_PROPPATCH, + aHeaders ); + + m_xSession->PROPPATCH( getRequestURI(), + rValues, + DAVRequestEnvironment( + new DAVAuthListener_Impl( xEnv, m_aURL ), + aHeaders ) ); + } + catch (DAVException const& e) + { + errorCount++; + bRetry = handleException( e, errorCount ); + if ( !bRetry ) + throw; + } + } + while ( bRetry ); +} + + +void DAVResourceAccess::HEAD( + const std::vector< OUString > & rHeaderNames, + DAVResource & rResource, + const uno::Reference< ucb::XCommandEnvironment >& xEnv ) +{ + initialize(); + + int errorCount = 0; + bool bRetry; + do + { + bRetry = false; + try + { + DAVRequestHeaders aHeaders; + getUserRequestHeaders( xEnv, + getRequestURI(), + ucb::WebDAVHTTPMethod_HEAD, + aHeaders ); + + m_xSession->HEAD( getRequestURI(), + rHeaderNames, + rResource, + DAVRequestEnvironment( + new DAVAuthListener_Impl( xEnv, m_aURL ), + aHeaders ) ); + } + catch (DAVException const& e) + { + errorCount++; + bRetry = handleException( e, errorCount ); + if ( !bRetry ) + throw; + } + } + while ( bRetry ); +} + + +uno::Reference< io::XInputStream > DAVResourceAccess::GET( + const uno::Reference< ucb::XCommandEnvironment > & xEnv ) +{ + initialize(); + + uno::Reference< io::XInputStream > xStream; + int errorCount = 0; + bool bRetry; + do + { + bRetry = false; + try + { + DAVRequestHeaders aHeaders; + getUserRequestHeaders( xEnv, + getRequestURI(), + ucb::WebDAVHTTPMethod_GET, + aHeaders ); + + xStream = m_xSession->GET( getRequestURI(), + DAVRequestEnvironment( + new DAVAuthListener_Impl( + xEnv, m_aURL ), + aHeaders ) ); + } + catch (DAVException const& e) + { + errorCount++; + bRetry = handleException( e, errorCount ); + if ( !bRetry ) + throw; + } + } + while ( bRetry ); + + return xStream; +} + + +void DAVResourceAccess::GET( + uno::Reference< io::XOutputStream > & rStream, + const uno::Reference< ucb::XCommandEnvironment > & xEnv ) +{ + initialize(); + + int errorCount = 0; + bool bRetry; + do + { + bRetry = false; + try + { + DAVRequestHeaders aHeaders; + getUserRequestHeaders( xEnv, + getRequestURI(), + ucb::WebDAVHTTPMethod_GET, + aHeaders ); + + m_xSession->GET( getRequestURI(), + rStream, + DAVRequestEnvironment( + new DAVAuthListener_Impl( xEnv, m_aURL ), + aHeaders ) ); + } + catch (DAVException const& e) + { + errorCount++; + bRetry = handleException( e, errorCount ); + if ( !bRetry ) + throw; + } + } + while ( bRetry ); +} + + +uno::Reference< io::XInputStream > DAVResourceAccess::GET( + const std::vector< OUString > & rHeaderNames, + DAVResource & rResource, + const uno::Reference< ucb::XCommandEnvironment > & xEnv ) +{ + initialize(); + + uno::Reference< io::XInputStream > xStream; + int errorCount = 0; + bool bRetry; + do + { + bRetry = false; + try + { + DAVRequestHeaders aHeaders; + getUserRequestHeaders( xEnv, + getRequestURI(), + ucb::WebDAVHTTPMethod_GET, + aHeaders ); + + xStream = m_xSession->GET( getRequestURI(), + rHeaderNames, + rResource, + DAVRequestEnvironment( + new DAVAuthListener_Impl( + xEnv, m_aURL ), + aHeaders ) ); + } + catch (DAVException const& e) + { + errorCount++; + bRetry = handleException( e, errorCount ); + if ( !bRetry ) + throw; + } + } + while ( bRetry ); + + return xStream; +} + + +// used as HEAD substitute when HEAD is not implemented on server +void DAVResourceAccess::GET0( + DAVRequestHeaders &rRequestHeaders, + const std::vector< OUString > & rHeaderNames, + DAVResource & rResource, + const uno::Reference< ucb::XCommandEnvironment > & xEnv ) +{ + initialize(); + + int errorCount = 0; + bool bRetry; + do + { + bRetry = false; + try + { + getUserRequestHeaders( xEnv, + getRequestURI(), + ucb::WebDAVHTTPMethod_GET, + rRequestHeaders ); + + m_xSession->GET( getRequestURI(), + rHeaderNames, + rResource, + DAVRequestEnvironment( + new DAVAuthListener_Impl( + xEnv, m_aURL ), + rRequestHeaders ) ); + } + catch (DAVException const& e) + { + errorCount++; + bRetry = handleException( e, errorCount ); + if ( !bRetry ) + throw; + } + } + while ( bRetry ); +} + + +void DAVResourceAccess::GET( + uno::Reference< io::XOutputStream > & rStream, + const std::vector< OUString > & rHeaderNames, + DAVResource & rResource, + const uno::Reference< ucb::XCommandEnvironment > & xEnv ) +{ + initialize(); + + bool bRetry; + int errorCount = 0; + do + { + bRetry = false; + try + { + DAVRequestHeaders aHeaders; + getUserRequestHeaders( xEnv, + getRequestURI(), + ucb::WebDAVHTTPMethod_GET, + aHeaders ); + + m_xSession->GET( getRequestURI(), + rStream, + rHeaderNames, + rResource, + DAVRequestEnvironment( + new DAVAuthListener_Impl( xEnv, m_aURL ), + aHeaders ) ); + } + catch (DAVException const& e) + { + errorCount++; + bRetry = handleException( e, errorCount ); + if ( !bRetry ) + throw; + } + } + while ( bRetry ); +} + + +void DAVResourceAccess::abort() +{ + // seems pointless to call initialize() here, but prepare for nullptr + decltype(m_xSession) xSession; + { + osl::Guard<osl::Mutex> const g(m_aMutex); + xSession = m_xSession; + } + if (xSession.is()) + { + xSession->abort(); + } +} + + +namespace { + + /// @throws DAVException + void resetInputStream( const uno::Reference< io::XInputStream > & rStream ) + { + try + { + uno::Reference< io::XSeekable > xSeekable( + rStream, uno::UNO_QUERY ); + if ( xSeekable.is() ) + { + xSeekable->seek( 0 ); + return; + } + } + catch ( lang::IllegalArgumentException const & ) + { + } + catch ( io::IOException const & ) + { + } + + throw DAVException( DAVException::DAV_INVALID_ARG ); + } + +} // namespace + + +void DAVResourceAccess::PUT( + const uno::Reference< io::XInputStream > & rStream, + const uno::Reference< ucb::XCommandEnvironment > & xEnv ) +{ + initialize(); + + // Make stream seekable, if it not. Needed, if request must be retried. + uno::Reference< io::XInputStream > xSeekableStream + = comphelper::OSeekableInputWrapper::CheckSeekableCanWrap( + rStream, m_xContext ); + + int errorCount = 0; + bool bRetry = false; + do + { + if ( bRetry ) + resetInputStream( xSeekableStream ); + + bRetry = false; + try + { + DAVRequestHeaders aHeaders; + getUserRequestHeaders( xEnv, + getRequestURI(), + ucb::WebDAVHTTPMethod_PUT, + aHeaders ); + + m_xSession->PUT( getRequestURI(), + xSeekableStream, + DAVRequestEnvironment( + new DAVAuthListener_Impl( xEnv, m_aURL ), + aHeaders ) ); + } + catch (DAVException const& e) + { + errorCount++; + bRetry = handleException( e, errorCount ); + if ( !bRetry ) + throw; + } + } + while ( bRetry ); +} + + +uno::Reference< io::XInputStream > DAVResourceAccess::POST( + const OUString & rContentType, + const OUString & rReferer, + const uno::Reference< io::XInputStream > & rInputStream, + const uno::Reference< ucb::XCommandEnvironment >& xEnv ) +{ + initialize(); + + // Make stream seekable, if it not. Needed, if request must be retried. + uno::Reference< io::XInputStream > xSeekableStream + = comphelper::OSeekableInputWrapper::CheckSeekableCanWrap( + rInputStream, m_xContext ); + + uno::Reference< io::XInputStream > xStream; + int errorCount = 0; + bool bRetry = false; + do + { + if ( bRetry ) + { + resetInputStream( xSeekableStream ); + bRetry = false; + } + + try + { + DAVRequestHeaders aHeaders; + getUserRequestHeaders( xEnv, + getRequestURI(), + ucb::WebDAVHTTPMethod_POST, + aHeaders ); + + xStream = m_xSession->POST( getRequestURI(), + rContentType, + rReferer, + xSeekableStream, + DAVRequestEnvironment( + new DAVAuthListener_Impl( + xEnv, m_aURL ), + aHeaders ) ); + } + catch (DAVException const& e) + { + errorCount++; + bRetry = handleException( e, errorCount ); + if ( !bRetry ) + throw; + + if ( e.getError() == DAVException::DAV_HTTP_REDIRECT ) + { + // #i74980# - Upon POST redirect, do a GET. + return GET( xEnv ); + } + } + } + while ( bRetry ); + + return xStream; +} + + +void DAVResourceAccess::POST( + const OUString & rContentType, + const OUString & rReferer, + const uno::Reference< io::XInputStream > & rInputStream, + uno::Reference< io::XOutputStream > & rOutputStream, + const uno::Reference< ucb::XCommandEnvironment >& xEnv ) +{ + initialize(); + + // Make stream seekable, if it not. Needed, if request must be retried. + uno::Reference< io::XInputStream > xSeekableStream + = comphelper::OSeekableInputWrapper::CheckSeekableCanWrap( + rInputStream, m_xContext ); + + int errorCount = 0; + bool bRetry = false; + do + { + if ( bRetry ) + { + resetInputStream( xSeekableStream ); + bRetry = false; + } + + try + { + DAVRequestHeaders aHeaders; + getUserRequestHeaders( xEnv, + getRequestURI(), + ucb::WebDAVHTTPMethod_POST, + aHeaders ); + + m_xSession->POST( getRequestURI(), + rContentType, + rReferer, + xSeekableStream, + rOutputStream, + DAVRequestEnvironment( + new DAVAuthListener_Impl( xEnv, m_aURL ), + aHeaders ) ); + } + catch (DAVException const& e) + { + errorCount++; + bRetry = handleException( e, errorCount ); + if ( !bRetry ) + throw; + + if ( e.getError() == DAVException::DAV_HTTP_REDIRECT ) + { + // #i74980# - Upon POST redirect, do a GET. + GET( rOutputStream, xEnv ); + return; + } + } + } + while ( bRetry ); +} + + +void DAVResourceAccess::MKCOL( + const uno::Reference< ucb::XCommandEnvironment > & xEnv ) +{ + initialize(); + + int errorCount = 0; + bool bRetry; + do + { + bRetry = false; + try + { + DAVRequestHeaders aHeaders; + getUserRequestHeaders( xEnv, + getRequestURI(), + ucb::WebDAVHTTPMethod_MKCOL, + aHeaders ); + + m_xSession->MKCOL( getRequestURI(), + DAVRequestEnvironment( + new DAVAuthListener_Impl( xEnv, m_aURL ), + aHeaders ) ); + } + catch (DAVException const& e) + { + errorCount++; + bRetry = handleException( e, errorCount ); + if ( !bRetry ) + throw; + } + } + while ( bRetry ); +} + + +void DAVResourceAccess::COPY( + const OUString & rSourcePath, + const OUString & rDestinationURI, + bool bOverwrite, + const uno::Reference< ucb::XCommandEnvironment > & xEnv ) +{ + initialize(); + + int errorCount = 0; + bool bRetry; + do + { + bRetry = false; + try + { + DAVRequestHeaders aHeaders; + getUserRequestHeaders( xEnv, + getRequestURI(), + ucb::WebDAVHTTPMethod_COPY, + aHeaders ); + + m_xSession->COPY( rSourcePath, + rDestinationURI, + DAVRequestEnvironment( + new DAVAuthListener_Impl( xEnv, m_aURL ), + aHeaders ), + bOverwrite ); + } + catch (DAVException const& e) + { + errorCount++; + bRetry = handleException( e, errorCount ); + if ( !bRetry ) + throw; + } + } + while ( bRetry ); +} + + +void DAVResourceAccess::MOVE( + const OUString & rSourcePath, + const OUString & rDestinationURI, + bool bOverwrite, + const uno::Reference< ucb::XCommandEnvironment > & xEnv ) +{ + initialize(); + + int errorCount = 0; + bool bRetry; + do + { + bRetry = false; + try + { + DAVRequestHeaders aHeaders; + getUserRequestHeaders( xEnv, + getRequestURI(), + ucb::WebDAVHTTPMethod_MOVE, + aHeaders ); + + m_xSession->MOVE( rSourcePath, + rDestinationURI, + DAVRequestEnvironment( + new DAVAuthListener_Impl( xEnv, m_aURL ), + aHeaders ), + bOverwrite ); + } + catch (DAVException const& e) + { + errorCount++; + bRetry = handleException( e, errorCount ); + if ( !bRetry ) + throw; + } + } + while ( bRetry ); +} + + +void DAVResourceAccess::DESTROY( + const uno::Reference< ucb::XCommandEnvironment > & xEnv ) +{ + initialize(); + + int errorCount = 0; + bool bRetry; + do + { + bRetry = false; + try + { + DAVRequestHeaders aHeaders; + getUserRequestHeaders( xEnv, + getRequestURI(), + ucb::WebDAVHTTPMethod_DELETE, + aHeaders ); + + m_xSession->DESTROY( getRequestURI(), + DAVRequestEnvironment( + new DAVAuthListener_Impl( xEnv, m_aURL ), + aHeaders ) ); + } + catch (DAVException const& e) + { + errorCount++; + bRetry = handleException( e, errorCount ); + if ( !bRetry ) + throw; + } + } + while ( bRetry ); +} + + +// set new lock. +void DAVResourceAccess::LOCK( + ucb::Lock & inLock, + const uno::Reference< ucb::XCommandEnvironment > & xEnv ) +{ + initialize(); + + int errorCount = 0; + bool bRetry; + do + { + bRetry = false; + try + { + DAVRequestHeaders aHeaders; + getUserRequestHeaders( xEnv, + getRequestURI(), + ucb::WebDAVHTTPMethod_LOCK, + aHeaders ); + + m_xSession->LOCK( getRequestURI(), + inLock, + DAVRequestEnvironment( + new DAVAuthListener_Impl( xEnv, m_aURL ), + aHeaders ) ); + } + catch (DAVException const& e) + { + errorCount++; + bRetry = handleException( e, errorCount ); + if ( !bRetry ) + throw; + } + } + while ( bRetry ); +} + +void DAVResourceAccess::UNLOCK( + const uno::Reference< ucb::XCommandEnvironment > & xEnv ) +{ + initialize(); + + int errorCount = 0; + bool bRetry; + do + { + bRetry = false; + try + { + DAVRequestHeaders aHeaders; + getUserRequestHeaders( xEnv, + getRequestURI(), + ucb::WebDAVHTTPMethod_UNLOCK, + aHeaders ); + + m_xSession->UNLOCK( getRequestURI(), + DAVRequestEnvironment( + new DAVAuthListener_Impl( xEnv, m_aURL ), + aHeaders ) ); + } + catch (DAVException const& e) + { + errorCount++; + bRetry = handleException( e, errorCount ); + if ( !bRetry ) + throw; + } + } + while ( bRetry ); +} + +void DAVResourceAccess::setFlags( const uno::Sequence< beans::NamedValue >& rFlags ) +{ + osl::Guard< osl::Mutex > aGuard( m_aMutex ); + m_aFlags = rFlags; +} + +void DAVResourceAccess::setURL( const OUString & rNewURL ) +{ + osl::Guard< osl::Mutex > aGuard( m_aMutex ); + m_aURL = rNewURL; + m_aPath.clear(); // Next initialize() will create new session. +} + + +// init dav session and path +void DAVResourceAccess::initialize() +{ + osl::Guard< osl::Mutex > aGuard( m_aMutex ); + if ( m_aPath.isEmpty() ) + { + CurlUri const aURI( m_aURL ); + OUString aPath( aURI.GetRelativeReference() ); + + /* #134089# - Check URI */ + if ( aPath.isEmpty() ) + throw DAVException( DAVException::DAV_INVALID_ARG ); + + /* #134089# - Check URI */ + if ( aURI.GetHost().isEmpty() ) + throw DAVException( DAVException::DAV_INVALID_ARG ); + + if ( !m_xSession.is() || !m_xSession->CanUse( m_aURL, m_aFlags ) ) + { + m_xSession.clear(); + + // create new webdav session + m_xSession + = m_xSessionFactory->createDAVSession( 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() ) + return; + + uno::Reference< ucb::XWebDAVCommandEnvironment > xDAVEnv( + xEnv, uno::UNO_QUERY ); + + if ( !xDAVEnv.is() ) + return; + + uno::Sequence< beans::StringPair > aRequestHeaders + = xDAVEnv->getUserRequestHeaders( rURI, eMethod ); + + for ( sal_Int32 n = 0; n < aRequestHeaders.getLength(); ++n ) + { + rRequestHeaders.push_back( + DAVRequestHeader( aRequestHeaders[ n ].First, + aRequestHeaders[ n ].Second ) ); + } +} + +// This function member implements the control on cyclical redirections +bool DAVResourceAccess::detectRedirectCycle( + ::std::u16string_view const rRedirectURL) +{ + osl::Guard< osl::Mutex > aGuard( m_aMutex ); + + CurlUri const aUri( rRedirectURL ); + + // Check for maximum number of redirections + // according to <https://tools.ietf.org/html/rfc7231#section-6.4>. + // A practical limit may be 5, due to earlier specifications: + // <https://tools.ietf.org/html/rfc2068#section-10.3> + // it can be raised keeping in mind the added net activity. + if( static_cast< size_t >( m_nRedirectLimit ) <= m_aRedirectURIs.size() ) + return true; + + // try to detect a cyclical redirection + return std::any_of(m_aRedirectURIs.begin(), m_aRedirectURIs.end(), + [&aUri](const CurlUri& rUri) { return aUri == rUri; }); +} + + +void DAVResourceAccess::resetUri() +{ + osl::Guard< osl::Mutex > aGuard( m_aMutex ); + if ( ! m_aRedirectURIs.empty() ) + { + auto const it = m_aRedirectURIs.begin(); + + CurlUri const aUri( *it ); + m_aRedirectURIs.clear(); + setURL ( aUri.GetURI() ); + initialize(); + } +} + + +bool DAVResourceAccess::handleException(DAVException const& e, int const errorCount) +{ + switch ( e.getError() ) + { + case DAVException::DAV_HTTP_REDIRECT: + if ( !detectRedirectCycle( e.getData() ) ) + { + // set new URL and path. + setURL( e.getData() ); + initialize(); + return true; + } + return false; + // i#67048 copy & paste images doesn't display. This bug refers + // to an old OOo problem about getting resources from sites with a bad connection. + // If we have a bad connection try again. Up to three times. + case DAVException::DAV_HTTP_ERROR: + // retry up to three times, if not a client-side error (4xx error codes) + if ( e.getStatus() < SC_BAD_REQUEST && errorCount < 3 ) + return true; + // check the server side errors + switch( e.getStatus() ) + { + // the HTTP server side response status codes that can be retried + // [Serf TODO? i#119036] case SC_REQUEST_ENTITY_TOO_LARGE: + case SC_BAD_GATEWAY: // retry, can be an excessive load + case SC_GATEWAY_TIMEOUT: // retry, may be we get lucky + case SC_SERVICE_UNAVAILABLE: // retry, the service may become available + case SC_INSUFFICIENT_STORAGE: // space may be freed, retry + { + if ( errorCount < 3 ) + return true; + else + return false; + } + break; + // all the other HTTP server response status codes are NOT retry + default: + return false; + } + break; + // if connection has said retry then retry! + case DAVException::DAV_HTTP_RETRY: + return true; + // <-- + default: + return false; // Abort + } +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/ucb/source/ucp/webdav-curl/DAVResourceAccess.hxx b/ucb/source/ucp/webdav-curl/DAVResourceAccess.hxx new file mode 100644 index 000000000..0e5231934 --- /dev/null +++ b/ucb/source/ucp/webdav-curl/DAVResourceAccess.hxx @@ -0,0 +1,220 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + + +#pragma once + +#include <vector> +#include <rtl/ustring.hxx> +#include <rtl/ref.hxx> +#include <osl/mutex.hxx> +#include <com/sun/star/io/XInputStream.hpp> +#include <com/sun/star/io/XOutputStream.hpp> +#include <com/sun/star/beans/NamedValue.hpp> +#include <com/sun/star/ucb/Lock.hpp> +#include <com/sun/star/ucb/XCommandEnvironment.hpp> +#include <com/sun/star/ucb/WebDAVHTTPMethod.hpp> +#include "DAVAuthListener.hxx" +#include "DAVException.hxx" +#include "DAVSession.hxx" +#include "DAVResource.hxx" +#include "DAVTypes.hxx" +#include "CurlUri.hxx" + +namespace http_dav_ucp +{ + +class DAVSessionFactory; + +class DAVResourceAccess +{ + osl::Mutex m_aMutex; + OUString m_aURL; + OUString m_aPath; + ::com::sun::star::uno::Sequence< ::com::sun::star::beans::NamedValue > m_aFlags; + rtl::Reference< DAVSession > m_xSession; + rtl::Reference< DAVSessionFactory > m_xSessionFactory; + css::uno::Reference< css::uno::XComponentContext > m_xContext; + std::vector<CurlUri> m_aRedirectURIs; + sal_uInt32 m_nRedirectLimit; + +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 setFlags( const ::com::sun::star::uno::Sequence< ::com::sun::star::beans::NamedValue >& rFlags ); + + /// @throws DAVException + void setURL( const OUString & rNewURL ); + + void resetUri(); + + const OUString & getURL() const { return m_aURL; } + + const rtl::Reference< DAVSessionFactory > & getSessionFactory() const + { return m_xSessionFactory; } + + // DAV methods + + /// @throws DAVException + void + OPTIONS( + DAVOptions & rOptions, + const css::uno::Reference< css::ucb::XCommandEnvironment > & xEnv ); + + // allprop & named + /// @throws DAVException + void + PROPFIND( const Depth nDepth, + const std::vector< OUString > & rPropertyNames, + std::vector< DAVResource > & rResources, + const css::uno::Reference< css::ucb::XCommandEnvironment > & xEnv ); + + // propnames + /// @throws DAVException + void + PROPFIND( const Depth nDepth, + std::vector< DAVResourceInfo > & rResInfo, + const css::uno::Reference< css::ucb::XCommandEnvironment > & xEnv ); + + /// @throws DAVException + void + PROPPATCH( const std::vector< ProppatchValue > & rValues, + const css::uno::Reference< css::ucb::XCommandEnvironment >& xEnv ); + + /// @throws DAVException + void + HEAD( const std::vector< OUString > & rHeaderNames, // empty == 'all' + DAVResource & rResource, + const css::uno::Reference< css::ucb::XCommandEnvironment >& xEnv ); + + /// @throws DAVException + css::uno::Reference< css::io::XInputStream > + GET( const css::uno::Reference< css::ucb::XCommandEnvironment > & xEnv ); + + /// @throws DAVException + void + GET( css::uno::Reference< css::io::XOutputStream > & rStream, + const css::uno::Reference< css::ucb::XCommandEnvironment > & xEnv ); + + /// @throws DAVException + css::uno::Reference< css::io::XInputStream > + GET( const std::vector< OUString > & rHeaderNames, // empty == 'all' + DAVResource & rResource, + const css::uno::Reference< css::ucb::XCommandEnvironment > & xEnv ); + + /// used as HEAD substitute when HEAD is not implemented on server + /// @throws DAVException + void + GET0( DAVRequestHeaders & rRequestHeaders, + const std::vector< OUString > & rHeaderNames, // empty == 'all' + DAVResource & rResource, + const css::uno::Reference< css::ucb::XCommandEnvironment > & xEnv ); + + /// @throws DAVException + void + GET( css::uno::Reference< css::io::XOutputStream > & rStream, + const std::vector< OUString > & rHeaderNames, // empty == 'all' + DAVResource & rResource, + const css::uno::Reference< css::ucb::XCommandEnvironment > & xEnv ); + + /// @throws DAVException + void + PUT( const css::uno::Reference< css::io::XInputStream > & rStream, + const css::uno::Reference< css::ucb::XCommandEnvironment > & xEnv ); + + /// @throws DAVException + css::uno::Reference< css::io::XInputStream > + POST( const OUString & rContentType, + const OUString & rReferer, + const css::uno::Reference< css::io::XInputStream > & rInputStream, + const css::uno::Reference< css::ucb::XCommandEnvironment >& xEnv ); + + /// @throws DAVException + void + POST( const OUString & rContentType, + const OUString & rReferer, + const css::uno::Reference< css::io::XInputStream > & rInputStream, + css::uno::Reference< css::io::XOutputStream > & rOutputStream, + const css::uno::Reference< css::ucb::XCommandEnvironment >& xEnv ); + + /// @throws DAVException + void + MKCOL( const css::uno::Reference< css::ucb::XCommandEnvironment > & xEnv ); + + /// @throws DAVException + void + COPY( const OUString & rSourcePath, + const OUString & rDestinationURI, + bool bOverwrite, + const css::uno::Reference< css::ucb::XCommandEnvironment > & xEnv ); + + /// @throws DAVException + void + MOVE( const OUString & rSourcePath, + const OUString & rDestinationURI, + bool bOverwrite, + const css::uno::Reference< css::ucb::XCommandEnvironment > & xEnv ); + + /// @throws DAVException + void + DESTROY( const css::uno::Reference< css::ucb::XCommandEnvironment > & xEnv ); + + // set new lock. + /// @throws DAVException + void + LOCK( css::ucb::Lock & inLock, + const css::uno::Reference< css::ucb::XCommandEnvironment > & xEnv ); + + /// @throws DAVException + void + UNLOCK( const css::uno::Reference< css::ucb::XCommandEnvironment > & xEnv ); + + /// @throws DAVException + void + abort(); + + // helper + static void + getUserRequestHeaders( + const css::uno::Reference< css::ucb::XCommandEnvironment > & xEnv, + const OUString & rURI, + css::ucb::WebDAVHTTPMethod eMethod, + DAVRequestHeaders & rRequestHeaders ); + + /// @throws DAVException + bool handleException(DAVException const& e, int errorCount); + +private: + const OUString & getRequestURI() const; + /// @throws DAVException + bool detectRedirectCycle(::std::u16string_view rRedirectURL); + /// @throws DAVException + void initialize(); +}; + +} // namespace http_dav_ucp + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/ucb/source/ucp/webdav-curl/DAVSession.hxx b/ucb/source/ucp/webdav-curl/DAVSession.hxx new file mode 100644 index 000000000..cb2987831 --- /dev/null +++ b/ucb/source/ucp/webdav-curl/DAVSession.hxx @@ -0,0 +1,196 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + + +#pragma once + +#include <memory> +#include <rtl/ustring.hxx> +#include <com/sun/star/io/XInputStream.hpp> +#include <com/sun/star/io/XOutputStream.hpp> +#include "DAVResource.hxx" +#include "DAVSessionFactory.hxx" +#include "DAVTypes.hxx" +#include "DAVRequestEnvironment.hxx" + +namespace com { namespace sun { namespace star { namespace beans { + struct NamedValue; +} } } } + +namespace com::sun::star::ucb { + struct Lock; +} + +namespace http_dav_ucp +{ + +class DAVAuthListener; + +class DAVSession +{ +public: + void acquire() + { + osl_atomic_increment( &m_nRefCount ); + } + + void release() + { + if ( osl_atomic_decrement( &m_nRefCount ) == 0 ) + { + m_xFactory->releaseElement( this ); + delete this; + } + } + + virtual bool CanUse( const OUString & rURI, + const ::com::sun::star::uno::Sequence<::com::sun::star::beans::NamedValue>& rFlags ) = 0; + + virtual bool UsesProxy() = 0; + + // DAV methods + + virtual void OPTIONS( const OUString & inPath, + DAVOptions & rOptions, + const DAVRequestEnvironment & rEnv ) = 0; + + // allprop & named + /// @throws DAVException + virtual void PROPFIND( const OUString & inPath, + const Depth inDepth, + const std::vector< OUString > & inPropertyNames, + std::vector< DAVResource > & ioResources, + const DAVRequestEnvironment & rEnv ) = 0; + + // propnames + /// @throws DAVException + virtual void PROPFIND( const OUString & inPath, + const Depth inDepth, + std::vector< DAVResourceInfo > & ioResInfo, + const DAVRequestEnvironment & rEnv ) = 0; + + /// @throws DAVException + virtual void PROPPATCH( const OUString & inPath, + const std::vector< ProppatchValue > & inValues, + const DAVRequestEnvironment & rEnv ) = 0; + + /// @throws DAVException + virtual void HEAD( const OUString & inPath, + const std::vector< OUString > & inHeaderNames, + DAVResource & ioResource, + const DAVRequestEnvironment & rEnv ) = 0; + + /// @throws DAVException + virtual css::uno::Reference< css::io::XInputStream > + GET( const OUString & inPath, + const DAVRequestEnvironment & rEnv ) = 0; + + /// @throws DAVException + virtual void GET( const OUString & inPath, + css::uno::Reference< css::io::XOutputStream >& o, + const DAVRequestEnvironment & rEnv ) = 0; + + /// @throws DAVException + virtual css::uno::Reference< css::io::XInputStream > + GET( const OUString & inPath, + const std::vector< OUString > & inHeaderNames, + DAVResource & ioResource, + const DAVRequestEnvironment & rEnv ) = 0; + + /// @throws DAVException + virtual void + GET( const OUString & inPath, + css::uno::Reference< css::io::XOutputStream >& o, + const std::vector< OUString > & inHeaderNames, + DAVResource & ioResource, + const DAVRequestEnvironment & rEnv ) = 0; + + /// @throws DAVException + virtual void PUT( const OUString & inPath, + const css::uno::Reference< css::io::XInputStream >& s, + const DAVRequestEnvironment & rEnv ) = 0; + + /// @throws DAVException + virtual css::uno::Reference< css::io::XInputStream > + POST( const OUString & inPath, + const OUString & rContentType, + const OUString & rReferer, + const css::uno::Reference< css::io::XInputStream > & inInputStream, + const DAVRequestEnvironment & rEnv ) = 0; + + /// @throws DAVException + virtual void POST( const OUString & inPath, + const OUString & rContentType, + const OUString & rReferer, + const css::uno::Reference< css::io::XInputStream > & inInputStream, + css::uno::Reference< css::io::XOutputStream > & oOutputStream, + const DAVRequestEnvironment & rEnv ) = 0; + + /// @throws DAVException + virtual void MKCOL( const OUString & inPath, + const DAVRequestEnvironment & rEnv ) = 0; + + /// @throws DAVException + virtual void COPY( const OUString & inSource, + const OUString & inDestination, + const DAVRequestEnvironment & rEnv, + bool inOverwrite = false ) = 0; + + /// @throws DAVException + virtual void MOVE( const OUString & inSource, + const OUString & inDestination, + const DAVRequestEnvironment & rEnv, + bool inOverwrite = false ) = 0; + + /// @throws DAVException + virtual void DESTROY( const OUString & inPath, + const DAVRequestEnvironment & rEnv ) = 0; + + // set new lock. + /// @throws DAVException + virtual void LOCK( const OUString & inPath, + css::ucb::Lock & inLock, + const DAVRequestEnvironment & rEnv ) = 0; + + /// @throws DAVException + virtual void UNLOCK( const OUString & inPath, + const DAVRequestEnvironment & rEnv ) = 0; + + /// @throws DAVException + virtual void abort() = 0; + +protected: + 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 + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/ucb/source/ucp/webdav-curl/DAVSessionFactory.cxx b/ucb/source/ucp/webdav-curl/DAVSessionFactory.cxx new file mode 100644 index 000000000..f2379d2cc --- /dev/null +++ b/ucb/source/ucp/webdav-curl/DAVSessionFactory.cxx @@ -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 . + */ + +#include <memory> +#include "DAVSessionFactory.hxx" +#include "CurlSession.hxx" +#include "CurlUri.hxx" + +using namespace http_dav_ucp; +using namespace com::sun::star; + +DAVSessionFactory::~DAVSessionFactory() +{ +} + +rtl::Reference< DAVSession > DAVSessionFactory::createDAVSession( + const OUString & inUri, + const uno::Sequence< beans::NamedValue >& rFlags, + const uno::Reference< uno::XComponentContext > & rxContext ) +{ + std::unique_lock aGuard( m_aMutex ); + + if (!m_xProxyDecider) + m_xProxyDecider.reset( new ucbhelper::InternetProxyDecider( rxContext ) ); + + Map::iterator aIt = std::find_if(m_aMap.begin(), m_aMap.end(), + [&inUri, &rFlags](const Map::value_type& rEntry) { return rEntry.second->CanUse( inUri, rFlags ); }); + + if ( aIt == m_aMap.end() ) + { + std::unique_ptr< DAVSession > xElement( + new CurlSession(rxContext, 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(); + + aIt->second = new CurlSession(rxContext, this, inUri, rFlags, *m_xProxyDecider); + aIt->second->m_aContainerIt = aIt; + return aIt->second; + } +} + +void DAVSessionFactory::releaseElement( const DAVSession * pElement ) +{ + assert( pElement ); + std::unique_lock aGuard( m_aMutex ); + if ( pElement->m_aContainerIt != m_aMap.end() ) + m_aMap.erase( pElement->m_aContainerIt ); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/ucb/source/ucp/webdav-curl/DAVSessionFactory.hxx b/ucb/source/ucp/webdav-curl/DAVSessionFactory.hxx new file mode 100644 index 000000000..2699035eb --- /dev/null +++ b/ucb/source/ucp/webdav-curl/DAVSessionFactory.hxx @@ -0,0 +1,74 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#pragma once + +#ifdef min +#undef min // GNU libstdc++ <memory> includes <limit> which defines methods called min... +#endif +#include <map> +#include <memory> +#include <mutex> +#include <salhelper/simplereferenceobject.hxx> +#include <rtl/ref.hxx> +#include <com/sun/star/uno/Reference.hxx> +#include <ucbhelper/proxydecider.hxx> + +using namespace com::sun::star; + +namespace com::sun::star::beans +{ +struct NamedValue; +} + +namespace com::sun::star::lang +{ +class XMultiServiceFactory; +} + +namespace http_dav_ucp +{ +class DAVSession; + +class DAVSessionFactory : public salhelper::SimpleReferenceObject +{ +public: + virtual ~DAVSessionFactory() override; + + /// @throws DAVException + rtl::Reference<DAVSession> createDAVSession( + const OUString& inUri, + const ::com::sun::star::uno::Sequence<::com::sun::star::beans::NamedValue>& rFlags, + const css::uno::Reference<css::uno::XComponentContext>& rxContext); + +private: + typedef std::map<OUString, DAVSession*> Map; + + Map m_aMap; + std::mutex m_aMutex; + std::unique_ptr<ucbhelper::InternetProxyDecider> m_xProxyDecider; + + void releaseElement(const DAVSession* pElement); + + friend class DAVSession; +}; + +} // namespace http_dav_ucp + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/ucb/source/ucp/webdav-curl/DAVTypes.cxx b/ucb/source/ucp/webdav-curl/DAVTypes.cxx new file mode 100644 index 000000000..e41c5426e --- /dev/null +++ b/ucb/source/ucp/webdav-curl/DAVTypes.cxx @@ -0,0 +1,207 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ + + +#include "DAVTypes.hxx" + +#include "CurlUri.hxx" +#include "../inc/urihelper.hxx" + +#include <osl/time.h> + + +using namespace http_dav_ucp; +using namespace com::sun::star; + +// DAVOptions implementation + +DAVOptions::DAVOptions() : + m_isClass1( false ), + m_isClass2( false ), + m_isClass3( false ), + m_isHeadAllowed( true ), + m_isLocked( false ), + m_aAllowedMethods(), + m_nStaleTime( 0 ), + m_nRequestedTimeLife( 0 ), + m_sURL(), + m_sRedirectedURL(), + m_nHttpResponseStatusCode( 0 ), + m_sHttpResponseStatusText() +{ +} + +DAVOptions::DAVOptions( const DAVOptions & rOther ) : + m_isClass1( rOther.m_isClass1 ), + m_isClass2( rOther.m_isClass2 ), + m_isClass3( rOther.m_isClass3 ), + m_isHeadAllowed( rOther.m_isHeadAllowed ), + m_isLocked( rOther.m_isLocked ), + m_aAllowedMethods( rOther.m_aAllowedMethods ), + m_nStaleTime( rOther.m_nStaleTime ), + m_nRequestedTimeLife( rOther.m_nRequestedTimeLife ), + m_sURL( rOther.m_sURL ), + m_sRedirectedURL( rOther.m_sRedirectedURL), + m_nHttpResponseStatusCode( rOther.m_nHttpResponseStatusCode ), + m_sHttpResponseStatusText( rOther.m_sHttpResponseStatusText ) +{ +} + +DAVOptions::~DAVOptions() +{ +} + +DAVOptions & DAVOptions::operator=( const DAVOptions& rOpts ) +{ + m_isClass1 = rOpts.m_isClass1; + m_isClass2 = rOpts.m_isClass2; + m_isClass3 = rOpts.m_isClass3; + m_isLocked = rOpts.m_isLocked; + m_isHeadAllowed = rOpts.m_isHeadAllowed; + m_aAllowedMethods = rOpts.m_aAllowedMethods; + m_nStaleTime = rOpts.m_nStaleTime; + m_nRequestedTimeLife = rOpts.m_nRequestedTimeLife; + m_sURL = rOpts.m_sURL; + m_sRedirectedURL = rOpts.m_sRedirectedURL; + m_nHttpResponseStatusCode = rOpts.m_nHttpResponseStatusCode; + m_sHttpResponseStatusText = rOpts.m_sHttpResponseStatusText; + return *this; +} + +bool DAVOptions::operator==( const DAVOptions& rOpts ) const +{ + return + m_isClass1 == rOpts.m_isClass1 && + m_isClass2 == rOpts.m_isClass2 && + m_isClass3 == rOpts.m_isClass3 && + m_isLocked == rOpts.m_isLocked && + m_isHeadAllowed == rOpts.m_isHeadAllowed && + m_aAllowedMethods == rOpts.m_aAllowedMethods && + m_nStaleTime == rOpts.m_nStaleTime && + m_nRequestedTimeLife == rOpts.m_nRequestedTimeLife && + m_sURL == rOpts.m_sURL && + m_sRedirectedURL == rOpts.m_sRedirectedURL && + m_nHttpResponseStatusCode == rOpts.m_nHttpResponseStatusCode && + m_sHttpResponseStatusText == rOpts.m_sHttpResponseStatusText; +} + + +// DAVOptionsCache implementation + +DAVOptionsCache::DAVOptionsCache() +{ +} + +DAVOptionsCache::~DAVOptionsCache() +{ +} + +bool DAVOptionsCache::getDAVOptions( const OUString & rURL, DAVOptions & rDAVOptions ) +{ + std::unique_lock aGuard( m_aMutex ); + OUString aEncodedUrl( ucb_impl::urihelper::encodeURI( DecodeURI(rURL) ) ); + normalizeURLLastChar( aEncodedUrl ); + + // search the URL in the static map + DAVOptionsMap::iterator it; + it = m_aTheCache.find( aEncodedUrl ); + if ( it == m_aTheCache.end() ) + return false; + else + { + // check if the capabilities are stale, before restoring + TimeValue t1; + osl_getSystemTime( &t1 ); + if ( (*it).second.getStaleTime() < t1.Seconds ) + { + // if stale, remove from cache, do not restore + m_aTheCache.erase( it ); + return false; + // return false instead + } + rDAVOptions = (*it).second; + return true; + } +} + +void DAVOptionsCache::removeDAVOptions( const OUString & rURL ) +{ + std::unique_lock aGuard( m_aMutex ); + OUString aEncodedUrl( ucb_impl::urihelper::encodeURI( DecodeURI(rURL) ) ); + normalizeURLLastChar( aEncodedUrl ); + + DAVOptionsMap::iterator it; + it = m_aTheCache.find( aEncodedUrl ); + if ( it != m_aTheCache.end() ) + { + m_aTheCache.erase( it ); + } +} + +void DAVOptionsCache::addDAVOptions( DAVOptions & rDAVOptions, const sal_uInt32 nLifeTime ) +{ + std::unique_lock aGuard( m_aMutex ); + OUString aURL( rDAVOptions.getURL() ); + + OUString aEncodedUrl( ucb_impl::urihelper::encodeURI( DecodeURI(aURL) ) ); + normalizeURLLastChar( aEncodedUrl ); + rDAVOptions.setURL( aEncodedUrl ); + +// unchanged, it may be used to access a server + OUString aRedirURL( rDAVOptions.getRedirectedURL() ); + rDAVOptions.setRedirectedURL( aRedirURL ); + + // check if already cached + DAVOptionsMap::iterator it; + it = m_aTheCache.find( aEncodedUrl ); + if ( it != m_aTheCache.end() ) + { // already in cache, check LifeTime + if ( (*it).second.getRequestedTimeLife() == nLifeTime ) + return; // same lifetime, do nothing + + // tdf#153642 keep cached Class1 bit at aDAVOptionsException to avoid of + // losing the ability to resave the document within the lifetime because + // of disabled DAV detection in getResourceType() + if ((*it).second.isClass1()) + { + rDAVOptions.setClass1( (*it).second.isClass1() ); + } + } + // not in cache, add it + TimeValue t1; + osl_getSystemTime( &t1 ); + rDAVOptions.setStaleTime( t1.Seconds + nLifeTime ); + + m_aTheCache[ aEncodedUrl ] = rDAVOptions; +} + +void DAVOptionsCache::setHeadAllowed( const OUString & rURL, const bool HeadAllowed ) +{ + std::unique_lock aGuard( m_aMutex ); + OUString aEncodedUrl( ucb_impl::urihelper::encodeURI( DecodeURI(rURL) ) ); + normalizeURLLastChar( aEncodedUrl ); + + DAVOptionsMap::iterator it; + it = m_aTheCache.find( aEncodedUrl ); + if ( it != m_aTheCache.end() ) + { + // first check for stale + TimeValue t1; + osl_getSystemTime( &t1 ); + if( (*it).second.getStaleTime() < t1.Seconds ) + { + m_aTheCache.erase( it ); + return; + } + // check if the resource was present on server + (*it).second.setHeadAllowed( HeadAllowed ); + } +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab cinoptions=b1,g0,N-s cinkeys+=0=break: */ diff --git a/ucb/source/ucp/webdav-curl/DAVTypes.hxx b/ucb/source/ucp/webdav-curl/DAVTypes.hxx new file mode 100644 index 000000000..8e6160333 --- /dev/null +++ b/ucb/source/ucp/webdav-curl/DAVTypes.hxx @@ -0,0 +1,200 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + + +#pragma once + +#include <memory> +#include <map> +#include <mutex> +#include <rtl/ustring.hxx> +#include <com/sun/star/uno/Any.hxx> + +namespace http_dav_ucp +{ +/* Excerpt from RFC 4918 + <https://tools.ietf.org/html/rfc4918#section-18> + + 18.1 Class 1 + + A class 1 compliant resource MUST meet all "MUST" requirements in all + sections of this document. + + Class 1 compliant resources MUST return, at minimum, the value "1" in + the DAV header on all responses to the OPTIONS method. + + 18.2 Class 2 + + A class 2 compliant resource MUST meet all class 1 requirements and + support the LOCK method, the DAV:supportedlock property, the DAV: + lockdiscovery property, the Time-Out response header and the Lock- + Token request header. A class 2 compliant resource SHOULD also + support the Timeout request header and the 'owner' XML element. + + Class 2 compliant resources MUST return, at minimum, the values "1" + and "2" in the DAV header on all responses to the OPTIONS method. + + 18.3. Class 3 + + A resource can explicitly advertise its support for the revisions to + [RFC2518] made in this document. Class 1 MUST be supported as well. + Class 2 MAY be supported. Advertising class 3 support in addition to + class 1 and 2 means that the server supports all the requirements in + this specification. Advertising class 3 and class 1 support, but not + class 2, means that the server supports all the requirements in this + specification except possibly those that involve locking support. + +*/ + + class DAVOptions + { + private: + bool m_isClass1; + bool m_isClass2; + bool m_isClass3; + /// for server that do not implement it + bool m_isHeadAllowed; + /// Internally used to maintain the locked state of the resource, only if it's a Class 2 resource + bool m_isLocked; + /// contains the methods allowed on this resource + OUString m_aAllowedMethods; + + /// target time when this capability becomes stale + sal_uInt32 m_nStaleTime; + sal_uInt32 m_nRequestedTimeLife; + OUString m_sURL; + OUString m_sRedirectedURL; + + /// The cached HTT response status code. It's 0 if the code was dealt with and there is no need to cache it + sal_uInt16 m_nHttpResponseStatusCode; + /// The cached string with the server returned HTTP response status code string, corresponds to m_nHttpResponseStatusCode. + OUString m_sHttpResponseStatusText; + + public: + DAVOptions(); + + DAVOptions( const DAVOptions & rOther ); + + ~DAVOptions(); + + bool isClass1() const { return m_isClass1; }; + void setClass1( bool Class1 = true ) { m_isClass1 = Class1; }; + + bool isClass2() const { return m_isClass2; }; + void setClass2( bool Class2 = true ) { m_isClass2 = Class2; }; + + bool isClass3() const { return m_isClass3; }; + void setClass3( bool Class3 = true ) { m_isClass3 = Class3; }; + + bool isHeadAllowed() const { return m_isHeadAllowed; }; + void setHeadAllowed( bool HeadAllowed = true ) { m_isHeadAllowed = HeadAllowed; }; + + sal_uInt32 getStaleTime() const { return m_nStaleTime ; }; + void setStaleTime( const sal_uInt32 nStaleTime ) { m_nStaleTime = nStaleTime; }; + + sal_uInt32 getRequestedTimeLife() const { return m_nRequestedTimeLife; }; + void setRequestedTimeLife( const sal_uInt32 nRequestedTimeLife ) { m_nRequestedTimeLife = nRequestedTimeLife; }; + + const OUString & getURL() const { return m_sURL; }; + void setURL( const OUString & sURL ) { m_sURL = sURL; }; + + const OUString & getRedirectedURL() const { return m_sRedirectedURL; }; + void setRedirectedURL( const OUString & sRedirectedURL ) { m_sRedirectedURL = sRedirectedURL; }; + + void setAllowedMethods( const OUString & aAllowedMethods ) { m_aAllowedMethods = aAllowedMethods; } ; + const OUString & getAllowedMethods() const { return m_aAllowedMethods; } ; + bool isLockAllowed() const { return ( m_aAllowedMethods.indexOf( "LOCK" ) != -1 ); }; + + void setLocked( bool locked = true ) { m_isLocked = locked; } ; + bool isLocked() const { return m_isLocked; }; + + sal_uInt16 getHttpResponseStatusCode() const { return m_nHttpResponseStatusCode; }; + void setHttpResponseStatusCode( const sal_uInt16 nHttpResponseStatusCode ) { m_nHttpResponseStatusCode = nHttpResponseStatusCode; }; + + const OUString & getHttpResponseStatusText() const { return m_sHttpResponseStatusText; }; + void setHttpResponseStatusText( const OUString & rHttpResponseStatusText ) { m_sHttpResponseStatusText = rHttpResponseStatusText; }; + + void init() { + m_isClass1 = false; + m_isClass2 = false; + m_isClass3 = false; + m_isHeadAllowed = true; + m_isLocked = false; + m_aAllowedMethods.clear(); + m_nStaleTime = 0; + m_nRequestedTimeLife = 0; + m_sURL.clear(); + m_sRedirectedURL.clear(); + m_nHttpResponseStatusCode = 0; + m_sHttpResponseStatusText.clear(); + }; + + DAVOptions & operator=( const DAVOptions& rOpts ); + bool operator==( const DAVOptions& rOpts ) const; + + }; + + // TODO: the OUString key element in std::map needs to be changed with a URI representation + // along with a specific compare (std::less) implementation, as suggested in + // <https://tools.ietf.org/html/rfc3986#section-6>, to find by URI and not by string comparison + typedef std::map< OUString, DAVOptions, + std::less< OUString > > DAVOptionsMap; + + class DAVOptionsCache + { + DAVOptionsMap m_aTheCache; + std::mutex m_aMutex; + public: + explicit DAVOptionsCache(); + ~DAVOptionsCache(); + + bool getDAVOptions( const OUString & rURL, DAVOptions & rDAVOptions ); + void removeDAVOptions( const OUString & rURL ); + void addDAVOptions( DAVOptions & rDAVOptions, const sal_uInt32 nLifeTime ); + + void setHeadAllowed( const OUString & rURL, bool HeadAllowed = true ); + + private: + + /// remove the last '/' in aUrl, if it exists + static void normalizeURLLastChar( OUString& aUrl ) { + if ( aUrl.getLength() > 1 && + ( ( aUrl.lastIndexOf( '/' ) + 1 ) == aUrl.getLength() ) ) + aUrl = aUrl.copy(0, aUrl.getLength() - 1 ); + }; + }; + + enum Depth { DAVZERO = 0, DAVONE = 1, DAVINFINITY = -1 }; + + enum ProppatchOperation { PROPSET = 0, PROPREMOVE = 1 }; + + struct ProppatchValue + { + ProppatchOperation const operation; + OUString const name; + css::uno::Any const value; + + ProppatchValue( const ProppatchOperation o, + const OUString & n, + const css::uno::Any & v ) + : operation( o ), name( n ), value( v ) {} + }; +} // namespace http_dav_ucp + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/ucb/source/ucp/webdav-curl/DateTimeHelper.cxx b/ucb/source/ucp/webdav-curl/DateTimeHelper.cxx new file mode 100644 index 000000000..06514d682 --- /dev/null +++ b/ucb/source/ucp/webdav-curl/DateTimeHelper.cxx @@ -0,0 +1,258 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include <osl/time.h> +#include <com/sun/star/util/DateTime.hpp> +#include "DateTimeHelper.hxx" + +using namespace com::sun::star::util; + +using namespace http_dav_ucp; + +bool DateTimeHelper::ISO8601_To_DateTime (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(std::u16string_view month) +{ + if (month == u"Jan") + return 1; + else if (month == u"Feb") + return 2; + else if (month == u"Mar") + return 3; + else if (month == u"Apr") + return 4; + else if (month == u"May") + return 5; + else if (month == u"Jun") + return 6; + else if (month == u"Jul") + return 7; + else if (month == u"Aug") + return 8; + else if (month == u"Sep") + return 9; + else if (month == u"Oct") + return 10; + else if (month == u"Nov") + return 11; + else if (month == u"Dec") + return 12; + else + return 0; +} + +bool DateTimeHelper::RFC2068_To_DateTime (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-curl/DateTimeHelper.hxx b/ucb/source/ucp/webdav-curl/DateTimeHelper.hxx new file mode 100644 index 000000000..22a8e9519 --- /dev/null +++ b/ucb/source/ucp/webdav-curl/DateTimeHelper.hxx @@ -0,0 +1,55 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + + +#pragma once + +#include <sal/types.h> +#include <rtl/ustring.hxx> + +namespace com::sun::star::util { + struct DateTime; +} + +namespace rtl { + class OUString; +} + +namespace http_dav_ucp +{ + +class DateTimeHelper +{ +private: + static sal_Int32 convertMonthToInt(std::u16string_view month); + + static bool ISO8601_To_DateTime (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 + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/ucb/source/ucp/webdav-curl/PropertyMap.hxx b/ucb/source/ucp/webdav-curl/PropertyMap.hxx new file mode 100644 index 000000000..e38ca5c44 --- /dev/null +++ b/ucb/source/ucp/webdav-curl/PropertyMap.hxx @@ -0,0 +1,55 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + + +#pragma once + +#include <com/sun/star/beans/Property.hpp> +#include <unordered_set> + +namespace http_dav_ucp { + +struct equalPropertyName +{ + bool operator()( const css::beans::Property & p1, + const css::beans::Property & p2 ) const + { + return p1.Name == p2.Name; + } +}; + +struct hashPropertyName +{ + size_t operator()( const css::beans::Property & p ) const + { + return p.Name.hashCode(); + } +}; + +typedef std::unordered_set +< + css::beans::Property, + hashPropertyName, + equalPropertyName +> +PropertyMap; + +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/ucb/source/ucp/webdav-curl/PropfindCache.cxx b/ucb/source/ucp/webdav-curl/PropfindCache.cxx new file mode 100644 index 000000000..7196bd791 --- /dev/null +++ b/ucb/source/ucp/webdav-curl/PropfindCache.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/. + */ + +#include <osl/time.h> +#include "PropfindCache.hxx" + +namespace http_dav_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 + std::unique_lock aGuard( m_aMutex ); + PropNameCache::const_iterator it; + it = m_aTheCache.find( rURL ); + if ( it == m_aTheCache.end() ) + return false; + else + { + // check if the element is stale, before restoring + TimeValue t1; + osl_getSystemTime( &t1 ); + if ( (*it).second.getStaleTime() < t1.Seconds ) + { + // if stale, remove from cache, do not restore + m_aTheCache.erase( it ); + return false; + // return false instead + } + rCacheElement = (*it).second; + return true; + } + } + + void PropertyNamesCache::removeCachedPropertyNames( const OUString& rURL ) + { + std::unique_lock aGuard( m_aMutex ); + PropNameCache::const_iterator it; + it = m_aTheCache.find( rURL ); + if ( it != m_aTheCache.end() ) + { + m_aTheCache.erase( it ); + } + } + + void PropertyNamesCache::addCachePropertyNames( PropertyNames& rCacheElement, const sal_uInt32 nLifeTime ) + { + std::unique_lock aGuard( m_aMutex ); + OUString aURL( rCacheElement.getURL() ); + TimeValue t1; + osl_getSystemTime( &t1 ); + rCacheElement.setStaleTime( t1.Seconds + nLifeTime ); + + m_aTheCache[ aURL ] = rCacheElement; + } + +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab cinoptions=b1,g0,N-s cinkeys+=0=break: */ diff --git a/ucb/source/ucp/webdav-curl/PropfindCache.hxx b/ucb/source/ucp/webdav-curl/PropfindCache.hxx new file mode 100644 index 000000000..b55ef2a51 --- /dev/null +++ b/ucb/source/ucp/webdav-curl/PropfindCache.hxx @@ -0,0 +1,80 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ + +#ifndef INCLUDED_UCB_SOURCE_UCP_WEBDAV_NEON_PROPFINDCACHE_HXX +#define INCLUDED_UCB_SOURCE_UCP_WEBDAV_NEON_PROPFINDCACHE_HXX + +#include <sal/types.h> +#include <rtl/ustring.hxx> +#include <mutex> +#include <map> +#include <vector> + +#include "DAVResource.hxx" + +namespace http_dav_ucp +{ + // A property names cache mechanism, URL driven. + // It is used to cache the property names received + // from the WebDAV server, to minimize the need of + // net transactions (e.g. PROPFIND). + // The cache lifetime should be short + // just to remove the annoying slowness when + // typing text or moving cursor around when the + // net link is slow. + + // Define the properties cache element + class PropertyNames + { + /// target time when this element becomes stale + sal_uInt32 m_nStaleTime; + OUString m_sURL; + // the property name list received from WebDAV server + std::vector< DAVResourceInfo > m_aPropertiesNames; + + public: + PropertyNames(); + explicit PropertyNames( 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() { return m_aPropertiesNames; }; + void setPropertiesNames( const std::vector< DAVResourceInfo >& aPropertiesNames ) { m_aPropertiesNames = aPropertiesNames; }; + }; + + // Define the PropertyNames cache + // TODO: the OUString key element in std::map needs to be changed with a URI representation + // with a specific compare (std::less) implementation, this last one implementing + // as suggested in <https://tools.ietf.org/html/rfc3986#section-6>. + // To find by URI and not by string equality. + typedef std::map< OUString, PropertyNames, + std::less< OUString > >PropNameCache; + + class PropertyNamesCache + { + PropNameCache m_aTheCache; + std::mutex m_aMutex; + + public: + PropertyNamesCache(); + ~PropertyNamesCache(); + + bool getCachedPropertyNames( const OUString& URL, PropertyNames& rCacheElement ); + void removeCachedPropertyNames( const OUString& URL ); + void addCachePropertyNames( PropertyNames& rCacheElement, const sal_uInt32 nLifeTime ); + }; + +} + +#endif + +/* vim:set shiftwidth=4 softtabstop=4 expandtab cinoptions=b1,g0,N-s cinkeys+=0=break: */ diff --git a/ucb/source/ucp/webdav-curl/SerfLockStore.cxx b/ucb/source/ucp/webdav-curl/SerfLockStore.cxx new file mode 100644 index 000000000..005e7c5f1 --- /dev/null +++ b/ucb/source/ucp/webdav-curl/SerfLockStore.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 <rtl/ustring.hxx> +#include <sal/log.hxx> +#include <osl/time.h> +#include <osl/thread.hxx> +#include <salhelper/thread.hxx> + +#include <com/sun/star/ucb/LockScope.hpp> + +#include "CurlSession.hxx" +#include "SerfLockStore.hxx" + +using namespace http_dav_ucp; + +namespace http_dav_ucp { + +class TickerThread : public salhelper::Thread +{ + bool m_bFinish; + SerfLockStore & m_rLockStore; + +public: + + explicit TickerThread( SerfLockStore & rLockStore ) + : Thread( "WebDavTickerThread" ), m_bFinish( false ), + m_rLockStore( rLockStore ) {} + + void finish() { m_bFinish = true; } + +private: + + virtual void execute(); +}; + +} // namespace http_dav_ucp + + +void TickerThread::execute() +{ + osl_setThreadName("http_dav_ucp::TickerThread"); + + SAL_INFO("ucb.ucp.webdav", "TickerThread: start." ); + + // we have to go through the loop more often to be able to finish ~quickly + const int nNth = 25; + + int nCount = nNth; + while ( !m_bFinish ) + { + if ( nCount-- <= 0 ) + { + m_rLockStore.refreshLocks(); + nCount = nNth; + } + + TimeValue aTV; + aTV.Seconds = 0; + aTV.Nanosec = 1000000000 / nNth; + salhelper::Thread::wait( aTV ); + } + + SAL_INFO("ucb.ucp.webdav", "TickerThread: stop." ); +} + + +SerfLockStore::SerfLockStore() +{ +} + + +SerfLockStore::~SerfLockStore() +{ + std::unique_lock aGuard(m_aMutex); + stopTicker(aGuard); + aGuard.lock(); // actually no threads should even try to access members now + + // release active locks, if any. + SAL_WARN_IF( !m_aLockInfoMap.empty(), "ucb.ucp.webdav", + "SerfLockStore::~SerfLockStore - Releasing active locks!" ); + + for ( auto& rLockInfo : m_aLockInfoMap ) + { + rLockInfo.second.m_xSession->NonInteractive_UNLOCK(rLockInfo.first); + } +} + +void SerfLockStore::startTicker() +{ + std::unique_lock aGuard( m_aMutex ); + + if ( !m_pTickerThread.is() ) + { + m_pTickerThread = new TickerThread( *this ); + m_pTickerThread->launch(); + } +} + + +void SerfLockStore::stopTicker(std::unique_lock<std::mutex> & rGuard) +{ + rtl::Reference<TickerThread> pTickerThread; + + if (m_pTickerThread.is()) + { + m_pTickerThread->finish(); // needs mutex + // the TickerThread may run refreshLocks() at most once after this + pTickerThread = m_pTickerThread; + m_pTickerThread.clear(); + } + + rGuard.unlock(); + + if (pTickerThread.is() && pTickerThread->getIdentifier() != osl::Thread::getCurrentIdentifier()) + { + pTickerThread->join(); // without m_aMutex locked (to prevent deadlock) + } +} + +OUString const* +SerfLockStore::getLockTokenForURI(OUString const& rURI, css::ucb::Lock const*const pLock) +{ + assert(rURI.startsWith("http://") || rURI.startsWith("https://")); + + std::unique_lock aGuard( m_aMutex ); + + auto const it(m_aLockInfoMap.find(rURI)); + + if (it == m_aLockInfoMap.end()) + { + return nullptr; + } + if (!pLock) // any lock will do + { + return &it->second.m_sToken; + } + // 0: EXCLUSIVE 1: SHARED + if (it->second.m_Lock.Scope == ucb::LockScope_SHARED && pLock->Scope == ucb::LockScope_EXCLUSIVE) + { + return nullptr; + } + assert(it->second.m_Lock.Type == pLock->Type); // only WRITE possible + if (it->second.m_Lock.Depth < pLock->Depth) + { + return nullptr; + } + // Only own locks are expected in the lock store, but depending on the + // server it->second.m_Lock.Owner may contain the string this UCP passed in + // the LOCK request, or a user identifier generated by the server (happens + // with Sharepoint), so just ignore it here. + // ignore Timeout ? + return &it->second.m_sToken; +} + +void SerfLockStore::addLock( const OUString& rURI, + ucb::Lock const& rLock, + const OUString& sToken, + rtl::Reference<CurlSession> const & xSession, + sal_Int32 nLastChanceToSendRefreshRequest ) +{ + assert(rURI.startsWith("http://") || rURI.startsWith("https://")); + { + std::unique_lock aGuard( m_aMutex ); + + m_aLockInfoMap[ rURI ] + = LockInfo(sToken, rLock, xSession, nLastChanceToSendRefreshRequest); + } + + startTicker(); +} + + +void SerfLockStore::removeLock(const OUString& rURI) +{ + std::unique_lock aGuard( m_aMutex ); + + removeLockImpl(aGuard, rURI); +} + +void SerfLockStore::removeLockImpl(std::unique_lock<std::mutex> & rGuard, const OUString& rURI) +{ + assert(rURI.startsWith("http://") || rURI.startsWith("https://")); + + m_aLockInfoMap.erase(rURI); + + if ( m_aLockInfoMap.empty() ) + { + stopTicker(rGuard); + } +} + +void SerfLockStore::refreshLocks() +{ + std::unique_lock aGuard( m_aMutex ); + + ::std::vector<OUString> authFailedLocks; + + for ( auto& rLockInfo : m_aLockInfoMap ) + { + LockInfo & rInfo = rLockInfo.second; + if ( rInfo.m_nLastChanceToSendRefreshRequest != -1 ) + { + // 30 seconds or less remaining until lock expires? + TimeValue t1; + osl_getSystemTime( &t1 ); + if ( rInfo.m_nLastChanceToSendRefreshRequest - 30 + <= sal_Int32( t1.Seconds ) ) + { + // refresh the lock. + sal_Int32 nlastChanceToSendRefreshRequest = -1; + bool isAuthFailed(false); + if (rInfo.m_xSession->NonInteractive_LOCK( + rLockInfo.first, rLockInfo.second.m_sToken, + nlastChanceToSendRefreshRequest, + isAuthFailed)) + { + rInfo.m_nLastChanceToSendRefreshRequest + = nlastChanceToSendRefreshRequest; + } + else + { + if (isAuthFailed) + { + authFailedLocks.push_back(rLockInfo.first); + } + // refresh failed. stop auto-refresh. + rInfo.m_nLastChanceToSendRefreshRequest = -1; + } + } + } + } + + for (auto const& rLock : authFailedLocks) + { + removeLockImpl(aGuard, rLock); + } +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/ucb/source/ucp/webdav-curl/SerfLockStore.hxx b/ucb/source/ucp/webdav-curl/SerfLockStore.hxx new file mode 100644 index 000000000..6765c7990 --- /dev/null +++ b/ucb/source/ucp/webdav-curl/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 . + */ + + +#pragma once + +#include <map> +#include <mutex> +#include <rtl/ref.hxx> +#include <rtl/ustring.hxx> +#include <com/sun/star/ucb/Lock.hpp> + +#include "CurlSession.hxx" + +namespace http_dav_ucp +{ + +class TickerThread; + +struct LockInfo +{ + OUString m_sToken; + css::ucb::Lock m_Lock; + rtl::Reference<CurlSession> m_xSession; + sal_Int32 m_nLastChanceToSendRefreshRequest; + + LockInfo() + : m_nLastChanceToSendRefreshRequest( -1 ) {} + + LockInfo( const OUString& sToken, + css::ucb::Lock const& rLock, + rtl::Reference<CurlSession> const & xSession, + sal_Int32 nLastChanceToSendRefreshRequest ) + : m_sToken(sToken) + , m_Lock(rLock) + , m_xSession(xSession) + , m_nLastChanceToSendRefreshRequest(nLastChanceToSendRefreshRequest) + {} +}; + +typedef std::map< OUString, LockInfo > LockInfoMap; + +class SerfLockStore +{ + std::mutex m_aMutex; + rtl::Reference< TickerThread > m_pTickerThread; + LockInfoMap m_aLockInfoMap; + +public: + SerfLockStore(); + ~SerfLockStore(); + + OUString const* getLockTokenForURI(OUString const& rURI, css::ucb::Lock const* pLock); + + void addLock( const OUString& rURI, + css::ucb::Lock const& rLock, + const OUString& sToken, + rtl::Reference<CurlSession> const & xSession, + // time in seconds since Jan 1 1970 + // -1: infinite lock, no refresh + sal_Int32 nLastChanceToSendRefreshRequest ); + + void removeLock(const OUString& rURI); + + void refreshLocks(); + +private: + void removeLockImpl(std::unique_lock<std::mutex> & rGuard, const OUString& rURI); + void startTicker(); + void stopTicker(std::unique_lock<std::mutex> & rGuard); +}; + +} // namespace http_dav_ucp + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/ucb/source/ucp/webdav-curl/UCBDeadPropertyValue.cxx b/ucb/source/ucp/webdav-curl/UCBDeadPropertyValue.cxx new file mode 100644 index 000000000..1bcf87eac --- /dev/null +++ b/ucb/source/ucp/webdav-curl/UCBDeadPropertyValue.cxx @@ -0,0 +1,220 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include <sal/log.hxx> +#include <o3tl/string_view.hxx> +#include "UCBDeadPropertyValue.hxx" + +using namespace http_dav_ucp; +using namespace ::com::sun::star; + + +// static +constexpr OUStringLiteral aTypeString = u"string"; +constexpr OUStringLiteral aTypeLong = u"long"; +constexpr OUStringLiteral aTypeShort = u"short"; +constexpr OUStringLiteral aTypeBoolean = u"boolean"; +constexpr OUStringLiteral aTypeChar = u"char"; +constexpr OUStringLiteral aTypeByte = u"byte"; +constexpr OUStringLiteral aTypeHyper = u"hyper"; +constexpr OUStringLiteral aTypeFloat = u"float"; +constexpr OUStringLiteral aTypeDouble = u"double"; + +// static +bool UCBDeadPropertyValue::supportsType( const uno::Type & rType ) +{ + if ( ( rType != cppu::UnoType<OUString>::get() ) + && + ( rType != cppu::UnoType<sal_Int32>::get() ) + && + ( rType != cppu::UnoType<sal_Int16>::get() ) + && + ( rType != cppu::UnoType<bool>::get() ) + && + ( rType != cppu::UnoType<cppu::UnoCharType>::get() ) + && + ( rType != cppu::UnoType<sal_Int8>::get() ) + && + ( rType != cppu::UnoType<sal_Int64>::get() ) + && + ( rType != cppu::UnoType<float>::get() ) + && + ( rType != cppu::UnoType<double>::get() ) ) + { + return false; + } + + return true; +} + + +// static +bool UCBDeadPropertyValue::createFromXML(std::u16string_view rType, + OUString const& rValue, + uno::Any & rOutData) +{ + bool success = true; + + if (o3tl::equalsIgnoreAsciiCase(rType, aTypeString)) + { + rOutData <<= rValue; + } + else if (o3tl::equalsIgnoreAsciiCase(rType, aTypeLong)) + { + rOutData <<= rValue.toInt32(); + } + else if (o3tl::equalsIgnoreAsciiCase(rType, aTypeShort)) + { + rOutData <<= sal_Int16( rValue.toInt32() ); + } + else if (o3tl::equalsIgnoreAsciiCase(rType, aTypeBoolean)) + { + if (rValue.equalsIgnoreAsciiCase(u"true")) + { + rOutData <<= true; + } + else + { + rOutData <<= false; + } + } + else if (o3tl::equalsIgnoreAsciiCase(rType, aTypeChar)) + { + rOutData <<= rValue.toChar(); + } + else if (o3tl::equalsIgnoreAsciiCase(rType, aTypeByte)) + { + rOutData <<= sal_Int8( rValue.toChar() ); + } + else if (o3tl::equalsIgnoreAsciiCase(rType, aTypeHyper)) + { + rOutData <<= rValue.toInt64(); + } + else if (o3tl::equalsIgnoreAsciiCase(rType, aTypeFloat)) + { + rOutData <<= rValue.toFloat(); + } + else if (o3tl::equalsIgnoreAsciiCase(rType, aTypeDouble)) + { + rOutData <<= rValue.toDouble(); + } + else + { + SAL_WARN( "ucb.ucp.webdav", + "UCBDeadPropertyValue::createFromXML - " + "Unsupported property type!" ); + success = false; + } + return success; +} + +// static +::std::optional<::std::pair<OUString, OUString>> +UCBDeadPropertyValue::toXML(const uno::Any & rInData) +{ + // <ucbprop><type>the_type</type><value>the_value</value></ucbprop> + + // Check property type. Extract type and value as string. + + const uno::Type& rType = rInData.getValueType(); + OUString aStringValue; + OUString aStringType; + + if ( rType == cppu::UnoType<OUString>::get() ) + { + // string + rInData >>= aStringValue; + aStringType = aTypeString; + } + else if ( rType == cppu::UnoType<sal_Int32>::get() ) + { + // long + sal_Int32 nValue = 0; + rInData >>= nValue; + aStringValue = OUString::number( nValue ); + aStringType = aTypeLong; + } + else if ( rType == cppu::UnoType<sal_Int16>::get() ) + { + // short + sal_Int32 nValue = 0; + rInData >>= nValue; + aStringValue = OUString::number( nValue ); + aStringType = aTypeShort; + } + else if ( rType == cppu::UnoType<bool>::get() ) + { + // boolean + bool bValue = false; + rInData >>= bValue; + aStringValue = OUString::boolean( bValue ); + aStringType = aTypeBoolean; + } + else if ( rType == cppu::UnoType<cppu::UnoCharType>::get() ) + { + // char + sal_Unicode cValue = 0; + rInData >>= cValue; + aStringValue = OUString( cValue ); + aStringType = aTypeChar; + } + else if ( rType == cppu::UnoType<sal_Int8>::get() ) + { + // byte + sal_Int8 nValue = 0; + rInData >>= nValue; + aStringValue = OUString( sal_Unicode( nValue ) ); + aStringType = aTypeByte; + } + else if ( rType == cppu::UnoType<sal_Int64>::get() ) + { + // hyper + sal_Int64 nValue = 0; + rInData >>= nValue; + aStringValue = OUString::number( nValue ); + aStringType = aTypeHyper; + } + else if ( rType == cppu::UnoType<float>::get() ) + { + // float + float nValue = 0; + rInData >>= nValue; + aStringValue = OUString::number( nValue ); + aStringType = aTypeFloat; + } + else if ( rType == cppu::UnoType<double>::get() ) + { + // double + double nValue = 0; + rInData >>= nValue; + aStringValue = OUString::number( nValue ); + aStringType = aTypeDouble; + } + else + { + SAL_WARN( "ucb.ucp.webdav", + "UCBDeadPropertyValue::toXML - " + "Unsupported property type!" ); + return {}; + } + + return { { aStringType, aStringValue } }; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/ucb/source/ucp/webdav-curl/UCBDeadPropertyValue.hxx b/ucb/source/ucp/webdav-curl/UCBDeadPropertyValue.hxx new file mode 100644 index 000000000..12574e0a9 --- /dev/null +++ b/ucb/source/ucp/webdav-curl/UCBDeadPropertyValue.hxx @@ -0,0 +1,45 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + + +#pragma once + +#include <optional> +#include <utility> + +#include <com/sun/star/uno/Any.hxx> + +namespace http_dav_ucp +{ + +class UCBDeadPropertyValue +{ +public: + static bool supportsType( const css::uno::Type & rType ); + + static bool createFromXML(std::u16string_view rType, + OUString const& rValue, + css::uno::Any & rOutData); + static ::std::optional<::std::pair<OUString, OUString>> + toXML(const css::uno::Any & rInData); +}; + +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/ucb/source/ucp/webdav-curl/ucpdav1.component b/ucb/source/ucp/webdav-curl/ucpdav1.component new file mode 100644 index 000000000..bb16e3b39 --- /dev/null +++ b/ucb/source/ucp/webdav-curl/ucpdav1.component @@ -0,0 +1,26 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + --> + +<component loader="com.sun.star.loader.SharedLibrary" environment="@CPPU_ENV@" + xmlns="http://openoffice.org/2010/uno-components"> + <implementation name="com.sun.star.comp.WebDAVContentProvider" + constructor="ucb_webdav_ContentProvider_get_implementation"> + <service name="com.sun.star.ucb.WebDAVContentProvider"/> + </implementation> +</component> diff --git a/ucb/source/ucp/webdav-curl/webdavcontent.cxx b/ucb/source/ucp/webdav-curl/webdavcontent.cxx new file mode 100644 index 000000000..acd850a24 --- /dev/null +++ b/ucb/source/ucp/webdav-curl/webdavcontent.cxx @@ -0,0 +1,4305 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include <memory> + +#include <cppuhelper/queryinterface.hxx> +#include <rtl/uri.hxx> +#include <sal/log.hxx> +#include <officecfg/Office/Common.hxx> +#include <officecfg/Inet.hxx> +#include <ucbhelper/contentidentifier.hxx> +#include <ucbhelper/macros.hxx> +#include <ucbhelper/propertyvalueset.hxx> +#include <ucbhelper/simpleinteractionrequest.hxx> +#include <ucbhelper/cancelcommandexecution.hxx> +#include <svl/lockfilecommon.hxx> + +#include <com/sun/star/beans/IllegalTypeException.hpp> +#include <com/sun/star/beans/NotRemoveableException.hpp> +#include <com/sun/star/beans/PropertyAttribute.hpp> +#include <com/sun/star/beans/PropertyExistException.hpp> +#include <com/sun/star/beans/PropertySetInfoChange.hpp> +#include <com/sun/star/beans/PropertySetInfoChangeEvent.hpp> +#include <com/sun/star/beans/PropertyValue.hpp> +#include <com/sun/star/io/XActiveDataSink.hpp> +#include <com/sun/star/io/XOutputStream.hpp> +#include <com/sun/star/lang/IllegalAccessException.hpp> +#include <com/sun/star/lang/IllegalArgumentException.hpp> +#include <com/sun/star/sdbc/SQLException.hpp> +#include <com/sun/star/task/PasswordContainerInteractionHandler.hpp> +#include <com/sun/star/ucb/CommandEnvironment.hpp> +#include <com/sun/star/ucb/CommandFailedException.hpp> +#include <com/sun/star/ucb/ContentInfoAttribute.hpp> +#include <com/sun/star/ucb/IllegalIdentifierException.hpp> +#include <com/sun/star/ucb/InsertCommandArgument.hpp> +#include <com/sun/star/ucb/InteractiveBadTransferURLException.hpp> +#include <com/sun/star/ucb/InteractiveAugmentedIOException.hpp> +#include <com/sun/star/ucb/InteractiveLockingLockedException.hpp> +#include <com/sun/star/ucb/InteractiveLockingLockExpiredException.hpp> +#include <com/sun/star/ucb/InteractiveLockingNotLockedException.hpp> +#include <com/sun/star/ucb/InteractiveNetworkConnectException.hpp> +#include <com/sun/star/ucb/InteractiveNetworkGeneralException.hpp> +#include <com/sun/star/ucb/InteractiveNetworkReadException.hpp> +#include <com/sun/star/ucb/InteractiveNetworkResolveNameException.hpp> +#include <com/sun/star/ucb/InteractiveNetworkWriteException.hpp> +#include <com/sun/star/ucb/MissingInputStreamException.hpp> +#include <com/sun/star/ucb/MissingPropertiesException.hpp> +#include <com/sun/star/ucb/NameClash.hpp> +#include <com/sun/star/ucb/NameClashException.hpp> +#include <com/sun/star/ucb/OpenCommandArgument3.hpp> +#include <com/sun/star/ucb/OpenMode.hpp> +#include <com/sun/star/ucb/PostCommandArgument2.hpp> +#include <com/sun/star/ucb/PropertyCommandArgument.hpp> +#include <com/sun/star/ucb/TransferInfo.hpp> +#include <com/sun/star/ucb/UnsupportedCommandException.hpp> +#include <com/sun/star/ucb/UnsupportedDataSinkException.hpp> +#include <com/sun/star/ucb/UnsupportedNameClashException.hpp> +#include <com/sun/star/ucb/UnsupportedOpenModeException.hpp> +#include <com/sun/star/ucb/XCommandInfo.hpp> +#include <com/sun/star/ucb/XPersistentPropertySet.hpp> +#include <com/sun/star/uno/XComponentContext.hpp> + +#include "webdavcontent.hxx" +#include "webdavprovider.hxx" +#include "webdavresultset.hxx" +#include "ContentProperties.hxx" +#include "CurlUri.hxx" +#include "UCBDeadPropertyValue.hxx" +#include "DAVException.hxx" +#include "DAVProperties.hxx" + +using namespace com::sun::star; +using namespace http_dav_ucp; + +namespace +{ +void lcl_sendPartialGETRequest( bool &bError, + DAVException &aLastException, + const std::vector< OUString >& rProps, + std::vector< OUString > &aHeaderNames, + const std::unique_ptr< DAVResourceAccess > &xResAccess, + std::unique_ptr< ContentProperties > &xProps, + const uno::Reference< ucb::XCommandEnvironment >& xEnv ) +{ + DAVResource aResource; + DAVRequestHeaders aPartialGet; + aPartialGet.push_back( + DAVRequestHeader( + OUString( "Range" ), // see <https://tools.ietf.org/html/rfc7233#section-3.1> + OUString( "bytes=0-0" ))); + + bool bIsRequestSize = std::any_of(aHeaderNames.begin(), aHeaderNames.end(), + [](const OUString& rHeaderName) { return rHeaderName == "Content-Length"; }); + + if ( bIsRequestSize ) + { + // we need to know if the server accepts range requests for a resource + // and the range unit it uses + aHeaderNames.push_back( OUString( "Accept-Ranges" ) ); // see <https://tools.ietf.org/html/rfc7233#section-2.3> + aHeaderNames.push_back( OUString( "Content-Range" ) ); // see <https://tools.ietf.org/html/rfc7233#section-4.2> + } + try + { + xResAccess->GET0( aPartialGet, aHeaderNames, aResource, xEnv ); + bError = false; + + if ( bIsRequestSize ) + { + // the ContentProperties maps "Content-Length" to the UCB "Size" property + // This would have an unrealistic value of 1 byte because we did only a partial GET + // Solution: if "Content-Range" is present, map it with UCB "Size" property + OUString aAcceptRanges, aContentRange, aContentLength; + std::vector< DAVPropertyValue > &aResponseProps = aResource.properties; + for ( const auto& rResponseProp : aResponseProps ) + { + if ( rResponseProp.Name == "Accept-Ranges" ) + rResponseProp.Value >>= aAcceptRanges; + else if ( rResponseProp.Name == "Content-Range" ) + rResponseProp.Value >>= aContentRange; + else if ( rResponseProp.Name == "Content-Length" ) + rResponseProp.Value >>= aContentLength; + } + + sal_Int64 nSize = 1; + if ( aContentLength.getLength() ) + { + nSize = aContentLength.toInt64(); + } + + // according to http://tools.ietf.org/html/rfc2616#section-3.12 + // the only range unit defined is "bytes" and implementations + // MAY ignore ranges specified using other units. + if ( nSize == 1 && + aContentRange.getLength() && + aAcceptRanges == "bytes" ) + { + // Parse the Content-Range to get the size + // vid. http://tools.ietf.org/html/rfc2616#section-14.16 + // Content-Range: <range unit> <bytes range>/<size> + sal_Int32 nSlash = aContentRange.lastIndexOf( '/' ); + if ( nSlash != -1 ) + { + OUString aSize = aContentRange.copy( nSlash + 1 ); + // "*" means that the instance-length is unknown at the time when the response was generated + if ( aSize != "*" ) + { + auto it = std::find_if(aResponseProps.begin(), aResponseProps.end(), + [](const DAVPropertyValue& rProp) { return rProp.Name == "Content-Length"; }); + if (it != aResponseProps.end()) + { + it->Value <<= aSize; + } + } + } + } + } + + if (xProps) + xProps->addProperties( + rProps, + ContentProperties( aResource ) ); + else + xProps.reset ( new ContentProperties( aResource ) ); + } + catch ( DAVException const & ex ) + { + aLastException = ex; + } +} +} + +// Static value, to manage a simple OPTIONS cache +// Key is the URL, element is the DAVOptions resulting from an OPTIONS call. +// Cached DAVOptions have a lifetime that depends on the errors received or not received +// and on the value of received options. +static DAVOptionsCache aStaticDAVOptionsCache; + + +// Content Implementation. + + +// ctr for content on an existing webdav resource +Content::Content( + const uno::Reference< uno::XComponentContext >& rxContext, + ContentProvider* pProvider, + const uno::Reference< ucb::XContentIdentifier >& Identifier, + rtl::Reference< DAVSessionFactory > const & rSessionFactory ) +: ContentImplHelper( rxContext, pProvider, Identifier ), + m_eResourceType( UNKNOWN ), + m_eResourceTypeForLocks( UNKNOWN ), + m_pProvider( pProvider ), + m_bTransient( false ), + m_bCollection( false ), + m_bDidGetOrHead( false ) +{ + try + { + initOptsCacheLifeTime(); + m_xResAccess.reset( new DAVResourceAccess( + rxContext, + rSessionFactory, + Identifier->getContentIdentifier() ) ); + + CurlUri const aURI( Identifier->getContentIdentifier() ); + m_aEscapedTitle = aURI.GetPathBaseName(); + } + catch ( DAVException const & ) + { + throw ucb::ContentCreationException(); + } +} + + +// ctr for content on a non-existing webdav resource +Content::Content( + const uno::Reference< uno::XComponentContext >& rxContext, + ContentProvider* pProvider, + const uno::Reference< ucb::XContentIdentifier >& Identifier, + rtl::Reference< DAVSessionFactory > const & rSessionFactory, + bool isCollection ) +: ContentImplHelper( rxContext, pProvider, Identifier ), + m_eResourceType( UNKNOWN ), + m_eResourceTypeForLocks( UNKNOWN ), + m_pProvider( pProvider ), + m_bTransient( true ), + m_bCollection( isCollection ), + m_bDidGetOrHead( false ) +{ + try + { + initOptsCacheLifeTime(); + m_xResAccess.reset( new DAVResourceAccess( + rxContext, rSessionFactory, Identifier->getContentIdentifier() ) ); + } + catch ( DAVException const & ) + { + throw ucb::ContentCreationException(); + } + + // Do not set m_aEscapedTitle here! Content::insert relays on this!!! +} + + +// virtual +Content::~Content() +{ +} + + +// XInterface methods. + + +// virtual +void SAL_CALL Content::acquire() noexcept +{ + ContentImplHelper::acquire(); +} + + +// virtual +void SAL_CALL Content::release() noexcept +{ + ContentImplHelper::release(); +} + + +// virtual +uno::Any SAL_CALL Content::queryInterface( const uno::Type & rType ) +{ + // Note: isFolder may require network activities! So call it only + // if it is really necessary!!! + uno::Any aRet = cppu::queryInterface( + rType, + static_cast< ucb::XContentCreator * >( this ) ); + if ( aRet.hasValue() ) + { + try + { + uno::Reference< task::XInteractionHandler > xIH( + task::PasswordContainerInteractionHandler::create(m_xContext) ); + + // Supply a command env to isFolder() that contains an interaction + // handler that uses the password container service to obtain + // credentials without displaying a password gui. + + uno::Reference< ucb::XCommandEnvironment > xCmdEnv( + ucb::CommandEnvironment::create( + m_xContext, + xIH, + uno::Reference< ucb::XProgressHandler >() ) ); + + return isFolder( xCmdEnv ) ? aRet : uno::Any(); + } + catch ( uno::RuntimeException const & ) + { + throw; + } + catch ( uno::Exception const & ) + { + return uno::Any(); + } + } + return aRet.hasValue() ? aRet : ContentImplHelper::queryInterface( rType ); +} + + +// XTypeProvider methods. + + +XTYPEPROVIDER_COMMON_IMPL( Content ); + + +// virtual +uno::Sequence< uno::Type > SAL_CALL Content::getTypes() +{ + bool bFolder = false; + try + { + bFolder + = isFolder( uno::Reference< ucb::XCommandEnvironment >() ); + } + catch ( uno::RuntimeException const & ) + { + throw; + } + catch ( uno::Exception const & ) + { + } + + if ( bFolder ) + { + static cppu::OTypeCollection s_aFolderTypes( + CPPU_TYPE_REF( lang::XTypeProvider ), + CPPU_TYPE_REF( lang::XServiceInfo ), + CPPU_TYPE_REF( lang::XComponent ), + CPPU_TYPE_REF( ucb::XContent ), + CPPU_TYPE_REF( ucb::XCommandProcessor ), + CPPU_TYPE_REF( beans::XPropertiesChangeNotifier ), + CPPU_TYPE_REF( ucb::XCommandInfoChangeNotifier ), + CPPU_TYPE_REF( beans::XPropertyContainer ), + CPPU_TYPE_REF( beans::XPropertySetInfoChangeNotifier ), + CPPU_TYPE_REF( container::XChild ), + CPPU_TYPE_REF( ucb::XContentCreator ) ); + + return s_aFolderTypes.getTypes(); + } + else + { + static cppu::OTypeCollection s_aDocumentTypes( + CPPU_TYPE_REF( lang::XTypeProvider ), + CPPU_TYPE_REF( lang::XServiceInfo ), + CPPU_TYPE_REF( lang::XComponent ), + CPPU_TYPE_REF( ucb::XContent ), + CPPU_TYPE_REF( ucb::XCommandProcessor ), + CPPU_TYPE_REF( beans::XPropertiesChangeNotifier ), + CPPU_TYPE_REF( ucb::XCommandInfoChangeNotifier ), + CPPU_TYPE_REF( beans::XPropertyContainer ), + CPPU_TYPE_REF( beans::XPropertySetInfoChangeNotifier ), + CPPU_TYPE_REF( container::XChild ) ); + + return s_aDocumentTypes.getTypes(); + } +} + + +// XServiceInfo methods. + + +// virtual +OUString SAL_CALL Content::getImplementationName() +{ + return "com.sun.star.comp.ucb.WebDAVContent"; +} + + +// virtual +uno::Sequence< OUString > SAL_CALL Content::getSupportedServiceNames() +{ + uno::Sequence<OUString> aSNS { WEBDAV_CONTENT_SERVICE_NAME }; + return aSNS; +} + + +// XContent methods. + + +// virtual +OUString SAL_CALL Content::getContentType() +{ + bool bFolder = false; + try + { + bFolder + = isFolder( uno::Reference< ucb::XCommandEnvironment >() ); + } + catch ( uno::RuntimeException const & ) + { + throw; + } + catch ( uno::Exception const & ) + { + } + + if ( bFolder ) + return WEBDAV_COLLECTION_TYPE; + + return WEBDAV_CONTENT_TYPE; +} + + +// XCommandProcessor methods. + + +// virtual +uno::Any SAL_CALL Content::execute( + const ucb::Command& aCommand, + sal_Int32 /*CommandId*/, + const uno::Reference< ucb::XCommandEnvironment >& Environment ) +{ + SAL_INFO("ucb.ucp.webdav", ">>>>> Content::execute: start: command: " << aCommand.Name + << ", env: " << (Environment.is() ? "present" : "missing") ); + + uno::Any aRet; + + if ( aCommand.Name == "getPropertyValues" ) + { + + // getPropertyValues + + + uno::Sequence< beans::Property > Properties; + if ( !( aCommand.Argument >>= Properties ) ) + { + ucbhelper::cancelCommandExecution( + uno::Any( lang::IllegalArgumentException( + "Wrong argument type!", + 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::Any( lang::IllegalArgumentException( + "Wrong argument type!", + static_cast< cppu::OWeakObject * >( this ), + -1 ) ), + Environment ); + // Unreachable + } + + if ( !aProperties.getLength() ) + { + ucbhelper::cancelCommandExecution( + uno::Any( 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::Any( 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::Any( 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::Any( 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::Any( 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::Any( 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::Any( 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::Any( e ), Environment ); + } + catch ( const beans::IllegalTypeException&e ) + { + ucbhelper::cancelCommandExecution( uno::Any( e ), Environment ); + } + catch ( const lang::IllegalArgumentException&e ) + { + ucbhelper::cancelCommandExecution( uno::Any( e ), Environment ); + } + } + else if ( aCommand.Name == "removeProperty" ) + { + OUString sPropName; + if ( !( aCommand.Argument >>= sPropName ) ) + { + ucbhelper::cancelCommandExecution( + uno::Any( lang::IllegalArgumentException( + "Wrong argument type!", + 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::Any( e ), Environment ); + } + catch( const beans::NotRemoveableException &e ) + { + ucbhelper::cancelCommandExecution( uno::Any( e ), Environment ); + } + } + else + { + + // Unsupported command + + + ucbhelper::cancelCommandExecution( + uno::Any( ucb::UnsupportedCommandException( + aCommand.Name, + 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 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 ) ); + } + 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 : aProperty.Name, + -1, // No handle available + beans::PropertySetInfoChange::PROPERTY_INSERTED ); + notifyPropertySetInfoChange( evt ); + } + catch ( DAVException const & e ) + { + if ( e.getStatus() == SC_FORBIDDEN ) + { + // Support for setting arbitrary dead properties is optional! + + // Store property locally. + ContentImplHelper::addProperty( bIsSpecial ? aSpecialName : aProperty.Name, + aProperty.Attributes, + aDefaultValue ); + } + else + { + if ( shouldAccessNetworkAfterException( e ) ) + { + try + { + const ResourceType eType = getResourceType( xEnv ); + switch ( eType ) + { + case UNKNOWN: + case DAV: + throw lang::IllegalArgumentException(); + + case NON_DAV: + // Store property locally. + ContentImplHelper::addProperty( bIsSpecial ? aSpecialName : aProperty.Name, + aProperty.Attributes, + aDefaultValue ); + break; + + default: + SAL_WARN( "ucb.ucp.webdav", + "Content::addProperty - " + "Unsupported resource type!" ); + break; + } + } + catch ( uno::Exception const & ) + { + SAL_WARN( "ucb.ucp.webdav", + "Content::addProperty - " + "Unable to determine resource type!" ); + } + } + else + { + SAL_WARN( "ucb.ucp.webdav", + "Content::addProperty - " + "Unable to determine resource type!" ); + } + } + } +} + +void Content::removeProperty( const OUString& Name, + const uno::Reference< ucb::XCommandEnvironment >& xEnv ) +{ +#if 0 + // @@@ REMOVABLE at the moment not properly set in the PropSetInfo + try + { + beans::Property aProp + = getPropertySetInfo( xEnv, false /* don't cache data */ ) + ->getPropertyByName( Name ); + + if ( !( aProp.Attributes & beans::PropertyAttribute::REMOVABLE ) ) + { + // Not removable! + throw beans::NotRemoveableException(); + } + } + catch ( beans::UnknownPropertyException const & ) + { + //SAL_WARN( "ucb.ucp.webdav", "removeProperty - Unknown property!" ); + throw; + } +#endif + + // Try to remove property from server. + try + { + std::vector< ProppatchValue > aProppatchValues; + ProppatchValue aValue( PROPREMOVE, Name, uno::Any() ); + aProppatchValues.push_back( aValue ); + + // Remove property value from server. + std::unique_ptr< DAVResourceAccess > xResAccess; + { + osl::Guard< osl::Mutex > aGuard( m_aMutex ); + xResAccess.reset( new DAVResourceAccess( *m_xResAccess ) ); + } + aStaticDAVOptionsCache.removeDAVOptions( xResAccess->getURL() ); + // clean cached value of PROPFIND property names + // PROPPATCH can change them + removeCachedPropertyNames( xResAccess->getURL() ); + xResAccess->PROPPATCH( aProppatchValues, xEnv ); + { + osl::Guard< osl::Mutex > aGuard( m_aMutex ); + m_xResAccess.reset( new DAVResourceAccess( *xResAccess ) ); + } + + // Notify propertyset info change listeners. + beans::PropertySetInfoChangeEvent evt( + 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 eType = getResourceType( xEnv ); + switch ( eType ) + { + case UNKNOWN: + case DAV: + throw beans::UnknownPropertyException(Name); + + case NON_DAV: + // Try to remove property from local store. + ContentImplHelper::removeProperty( Name ); + break; + + default: + SAL_WARN( "ucb.ucp.webdav", + "Content::removeProperty - " + "Unsupported resource type!" ); + break; + } + } + catch ( uno::Exception const & ) + { + SAL_WARN( "ucb.ucp.webdav", + "Content::removeProperty - " + "Unable to determine resource type!" ); + } + } + else + { + SAL_WARN( "ucb.ucp.webdav", + "Content::removeProperty - " + "Unable to determine resource type!" ); +// throw beans::UnknownPropertyException(); + } + } + } +} + +// virtual +void SAL_CALL Content::addProperty( const OUString& Name, + sal_Int16 Attributes, + const uno::Any& DefaultValue ) +{ + beans::Property aProperty; + aProperty.Name = Name; + aProperty.Type = DefaultValue.getValueType(); + aProperty.Attributes = Attributes; + aProperty.Handle = -1; + + addProperty( ucb::PropertyCommandArgument( aProperty, DefaultValue ), + uno::Reference< ucb::XCommandEnvironment >()); +} + +// virtual +void SAL_CALL Content::removeProperty( const OUString& Name ) +{ + removeProperty( Name, + uno::Reference< ucb::XCommandEnvironment >() ); +} + + +// XContentCreator methods. + + +// virtual +uno::Sequence< ucb::ContentInfo > SAL_CALL +Content::queryCreatableContentsInfo() +{ + osl::Guard< osl::Mutex > aGuard( m_aMutex ); + + uno::Sequence< ucb::ContentInfo > aSeq( 2 ); + + // document. + aSeq.getArray()[ 0 ].Type = WEBDAV_CONTENT_TYPE; + aSeq.getArray()[ 0 ].Attributes + = ucb::ContentInfoAttribute::INSERT_WITH_INPUTSTREAM + | ucb::ContentInfoAttribute::KIND_DOCUMENT; + + beans::Property aProp; + m_pProvider->getProperty( "Title", aProp ); + + uno::Sequence< beans::Property > aDocProps( 1 ); + aDocProps.getArray()[ 0 ] = aProp; + aSeq.getArray()[ 0 ].Properties = aDocProps; + + // folder. + aSeq.getArray()[ 1 ].Type = WEBDAV_COLLECTION_TYPE; + aSeq.getArray()[ 1 ].Attributes + = ucb::ContentInfoAttribute::KIND_FOLDER; + + uno::Sequence< beans::Property > aFolderProps( 1 ); + aFolderProps.getArray()[ 0 ] = aProp; + aSeq.getArray()[ 1 ].Properties = aFolderProps; + return aSeq; +} + + +// virtual +uno::Reference< ucb::XContent > SAL_CALL +Content::createNewContent( const ucb::ContentInfo& Info ) +{ + osl::Guard< osl::Mutex > aGuard( m_aMutex ); + + if ( !Info.Type.getLength() ) + return uno::Reference< ucb::XContent >(); + + if ( ( Info.Type != WEBDAV_COLLECTION_TYPE ) + && + ( Info.Type != WEBDAV_CONTENT_TYPE ) ) + return uno::Reference< ucb::XContent >(); + + OUString aURL = m_xIdentifier->getContentIdentifier(); + + SAL_WARN_IF( aURL.isEmpty(), "ucb.ucp.webdav", + "WebdavContent::createNewContent - empty identifier!" ); + + if ( ( aURL.lastIndexOf( '/' ) + 1 ) != aURL.getLength() ) + aURL += "/"; + + bool isCollection; + if ( Info.Type == WEBDAV_COLLECTION_TYPE ) + { + aURL += "New_Collection"; + isCollection = true; + } + else + { + aURL += "New_Content"; + isCollection = false; + } + + uno::Reference< ucb::XContentIdentifier > xId( + new ::ucbhelper::ContentIdentifier( aURL ) ); + + // create the local content + try + { + return new ::http_dav_ucp::Content( m_xContext, + m_pProvider, + xId, + m_xResAccess->getSessionFactory(), + isCollection ); + } + catch ( ucb::ContentCreationException & ) + { + return uno::Reference< ucb::XContent >(); + } +} + + +// virtual +OUString Content::getParentURL() +{ + // <scheme>:// -> "" + // <scheme>://foo -> "" + // <scheme>://foo/ -> "" + // <scheme>://foo/bar -> <scheme>://foo/ + // <scheme>://foo/bar/ -> <scheme>://foo/ + // <scheme>://foo/bar/abc -> <scheme>://foo/bar/ + + OUString aURL = m_xIdentifier->getContentIdentifier(); + + sal_Int32 nPos = aURL.lastIndexOf( '/' ); + if ( nPos == ( aURL.getLength() - 1 ) ) + { + // Trailing slash found. Skip. + nPos = aURL.lastIndexOf( '/', nPos ); + } + + sal_Int32 nPos1 = aURL.lastIndexOf( '/', nPos ); + if ( nPos1 != -1 ) + nPos1 = aURL.lastIndexOf( '/', nPos1 ); + + if ( nPos1 == -1 ) + return OUString(); + + return aURL.copy( 0, nPos + 1 ); +} + + +// Non-interface methods. + + +// static +uno::Reference< sdbc::XRow > Content::getPropertyValues( + const uno::Reference< uno::XComponentContext >& rxContext, + const uno::Sequence< beans::Property >& rProperties, + const ContentProperties& rData, + const rtl::Reference< ::ucbhelper::ContentProviderImplHelper >& rProvider, + const OUString& rContentId ) +{ + // Note: Empty sequence means "get values of all supported properties". + + rtl::Reference< ::ucbhelper::PropertyValueSet > xRow + = new ::ucbhelper::PropertyValueSet( rxContext ); + + sal_Int32 nCount = rProperties.getLength(); + if ( nCount ) + { + uno::Reference< beans::XPropertySet > xAdditionalPropSet; + bool bTriedToGetAdditionalPropSet = false; + + const beans::Property* pProps = rProperties.getConstArray(); + for ( sal_Int32 n = 0; n < nCount; ++n ) + { + const beans::Property& rProp = pProps[ n ]; + + // Process standard UCB, DAV and HTTP properties. + const uno::Any & rValue = rData.getValue( rProp.Name ); + if ( rValue.hasValue() ) + { + xRow->appendObject( rProp, rValue ); + } + else + { + // Process local Additional Properties. + if ( !bTriedToGetAdditionalPropSet && !xAdditionalPropSet.is() ) + { + xAdditionalPropSet = + rProvider->getAdditionalPropertySet( rContentId, + false ); + bTriedToGetAdditionalPropSet = true; + } + + if ( !xAdditionalPropSet.is() || + !xRow->appendPropertySetValue( + xAdditionalPropSet, rProp ) ) + { + // Append empty entry. + xRow->appendVoid( rProp ); + } + } + } + } + else + { + // Append all standard UCB, DAV and HTTP properties. + + const std::unique_ptr< PropertyValueMap > & xProps = rData.getProperties(); + + ContentProvider * pProvider + = static_cast< ContentProvider * >( rProvider.get() ); + beans::Property aProp; + + for ( const auto& rProp : *xProps ) + { + if ( pProvider->getProperty( rProp.first, aProp ) ) + xRow->appendObject( aProp, rProp.second.value() ); + } + + // Append all local Additional Properties. + uno::Reference< beans::XPropertySet > xSet = + rProvider->getAdditionalPropertySet( rContentId, false ); + xRow->appendPropertySet( xSet ); + } + + return uno::Reference<sdbc::XRow>(xRow); +} + +namespace { +void GetPropsUsingHeadRequest(DAVResource& resource, + const std::unique_ptr< DAVResourceAccess >& xResAccess, + const std::vector< OUString >& aHTTPNames, + const uno::Reference< ucb::XCommandEnvironment >& xEnv) +{ + if (!aHTTPNames.empty()) + { + DAVOptions aDAVOptions; + OUString aTargetURL = xResAccess->getURL(); + // retrieve the cached options if any + aStaticDAVOptionsCache.getDAVOptions(aTargetURL, aDAVOptions); + + // clean cached value of PROPFIND property names + // PROPPATCH can change them + Content::removeCachedPropertyNames(aTargetURL); + // test if HEAD allowed, if not, throw, should be caught immediately + // SC_GONE used internally by us, see comment in Content::getPropertyValues + // in the catch scope + if (aDAVOptions.getHttpResponseStatusCode() != SC_GONE && + !aDAVOptions.isHeadAllowed()) + { + throw DAVException(DAVException::DAV_HTTP_ERROR, "405 Not Implemented", SC_METHOD_NOT_ALLOWED); + } + // if HEAD is enabled on this site + // check if there is a relevant HTTP response status code cached + if (aDAVOptions.getHttpResponseStatusCode() != SC_NONE) + { + // throws exception as if there was a server error, a DAV exception + throw DAVException(DAVException::DAV_HTTP_ERROR, + aDAVOptions.getHttpResponseStatusText(), + aDAVOptions.getHttpResponseStatusCode()); + // Unreachable + } + + xResAccess->HEAD(aHTTPNames, resource, xEnv); + } +} +} + +uno::Reference< sdbc::XRow > Content::getPropertyValues( + const uno::Sequence< beans::Property >& rProperties, + const uno::Reference< ucb::XCommandEnvironment >& xEnv ) +{ + std::unique_ptr< ContentProperties > xProps; + std::unique_ptr< ContentProperties > xCachedProps; + std::unique_ptr< DAVResourceAccess > xResAccess; + OUString aUnescapedTitle; + bool bHasAll = false; + uno::Reference< uno::XComponentContext > xContext; + uno::Reference< ucb::XContentIdentifier > xIdentifier; + rtl::Reference< ::ucbhelper::ContentProviderImplHelper > xProvider; + + { + osl::Guard< osl::Mutex > aGuard( m_aMutex ); + + aUnescapedTitle = DecodeURI(m_aEscapedTitle); + xContext.set( m_xContext ); + xIdentifier.set( m_xIdentifier ); + xProvider = m_xProvider; + xResAccess.reset( new DAVResourceAccess( *m_xResAccess ) ); + + // First, ask cache... + if (m_xCachedProps) + { + xCachedProps.reset( new ContentProperties( *m_xCachedProps ) ); + + std::vector< OUString > aMissingProps; + if ( xCachedProps->containsAllNames( rProperties, aMissingProps ) ) + { + // All properties are already in cache! No server access needed. + bHasAll = true; + } + + // use the cached ContentProperties instance + xProps.reset( new ContentProperties( *xCachedProps ) ); + } + } + + bool bNetworkAccessAllowed = true; + + if ( !m_bTransient && !bHasAll ) + { + // Obtain values from server... + + + // First, identify whether resource is DAV or not + const ResourceType eType = getResourceType( + xEnv, xResAccess, &bNetworkAccessAllowed ); + + if ( eType == DAV ) + { + // cache lookup... getResourceType may fill the props cache via + // PROPFIND! + if (m_xCachedProps) + { + xCachedProps.reset( + new ContentProperties( *m_xCachedProps ) ); + + std::vector< OUString > aMissingProps; + if ( xCachedProps->containsAllNames( + rProperties, aMissingProps ) ) + { + // All properties are already in cache! No server access + // needed. + bHasAll = true; + } + + // use the cached ContentProperties instance + xProps.reset( new ContentProperties( *xCachedProps ) ); + } + + if ( !bHasAll ) + { + // Only DAV resources support PROPFIND + std::vector< OUString > aPropNames; + + uno::Sequence< beans::Property > aProperties(rProperties); + + if ( aProperties.getLength() > 0 ) + ContentProperties::UCBNamesToDAVNames( + aProperties, aPropNames ); + + if ( !aPropNames.empty() ) + { + std::vector< DAVResource > resources; + try + { + xResAccess->PROPFIND( + DAVZERO, aPropNames, resources, xEnv ); + + if ( 1 == resources.size() ) + { +#if defined SAL_LOG_INFO + {//debug + // print received resources + std::vector< DAVPropertyValue >::const_iterator it = resources[0].properties.begin(); + std::vector< DAVPropertyValue >::const_iterator end = resources[0].properties.end(); + while ( it != end ) + { + OUString aPropValue; + bool bValue; + uno::Sequence< ucb::LockEntry > aSupportedLocks; + if( (*it).Value >>= aPropValue ) + SAL_INFO( "ucb.ucp.webdav", "PROPFIND (getPropertyValues) - returned property: " << (*it).Name << ":" << aPropValue ); + else if( (*it).Value >>= bValue ) + SAL_INFO( "ucb.ucp.webdav", "PROPFIND (getPropertyValues) - returned property: " << (*it).Name << ":" << + ( bValue ? "true" : "false" ) ); + else if( (*it).Value >>= aSupportedLocks ) + { + SAL_INFO( "ucb.ucp.webdav", "PROPFIND (getPropertyValues) - returned property: " << (*it).Name << ":" ); + for ( sal_Int32 n = 0; n < aSupportedLocks.getLength(); ++n ) + { + SAL_INFO( "ucb.ucp.webdav"," scope: " + << (aSupportedLocks[n].Scope == ucb::LockScope_SHARED ? "shared" : "exclusive") + << ", type: " + << (aSupportedLocks[n].Type != ucb::LockType_WRITE ? "" : "write") ); + } + } + ++it; + } + } +#endif + if (xProps) + xProps->addProperties( + aPropNames, + ContentProperties( resources[ 0 ] )); + else + xProps.reset( + new ContentProperties( resources[ 0 ] ) ); + } + } + catch ( DAVException const & e ) + { + bNetworkAccessAllowed = bNetworkAccessAllowed + && shouldAccessNetworkAfterException( e ); + + if ( !bNetworkAccessAllowed ) + { + cancelCommandExecution( e, xEnv ); + // unreachable + } + } + } + } + } + + if ( bNetworkAccessAllowed ) + { + // All properties obtained already? + std::vector< OUString > aMissingProps; + if ( !( xProps + && xProps->containsAllNames(rProperties, aMissingProps)) + // i#121922 for non-DAV, uncacheable properties must be fetched + // regardless of m_bDidGetOrHead. + // But SharePoint may do weird things on HEAD so for DAV + // only do this if required. + && (eType != DAV || !m_bDidGetOrHead)) + { + // Possibly the missing props can be obtained using a HEAD + // request. + + std::vector< OUString > aHeaderNames; + ContentProperties::UCBNamesToHTTPNames( + rProperties, + aHeaderNames ); + + if( eType != DAV ) + { + // in case of not DAV PROFIND (previously in program flow) failed + // so we need to add the only prop that's common + // to DAV and NON_DAV: MediaType, that maps to Content-Type + aHeaderNames.push_back( "Content-Type" ); + } + + if (!aHeaderNames.empty()) try + { + DAVResource resource; + GetPropsUsingHeadRequest(resource, xResAccess, aHeaderNames, xEnv); + m_bDidGetOrHead = true; + + if (xProps) + xProps->addProperties( + aMissingProps, + ContentProperties( resource ) ); + else + xProps.reset ( new ContentProperties( resource ) ); + + if (m_eResourceType == NON_DAV) + xProps->addProperties(aMissingProps, + ContentProperties( + aUnescapedTitle, + false)); + } + catch ( DAVException const & e ) + { + // non "general-purpose servers" may not support HEAD requests + // see http://www.w3.org/Protocols/rfc2616/rfc2616-sec5.html#sec5.1.1 + // In this case, perform a partial GET only to get the header info + // vid. http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.35 + // WARNING if the server does not support partial GETs, + // the GET will transfer the whole content + bool bError = true; + DAVException aLastException = e; + OUString aTargetURL = xResAccess->getURL(); + + if ( e.getError() == DAVException::DAV_HTTP_ERROR ) + { + // According to the spec. the origin server SHOULD return + // * 405 (Method Not Allowed): + // the method is known but not allowed for the requested resource + // * 501 (Not Implemented): + // the method is unrecognized or not implemented + // * 404 (SC_NOT_FOUND) + // is for google-code server and for MS IIS 10.0 Web server + // when only GET is enabled + if ( aLastException.getStatus() == SC_NOT_IMPLEMENTED || + aLastException.getStatus() == SC_METHOD_NOT_ALLOWED || + aLastException.getStatus() == SC_NOT_FOUND ) + { + SAL_WARN( "ucb.ucp.webdav", "HEAD probably not implemented: fall back to a partial GET" ); + aStaticDAVOptionsCache.setHeadAllowed( aTargetURL, false ); + lcl_sendPartialGETRequest( bError, + aLastException, + aMissingProps, + aHeaderNames, + xResAccess, + xProps, + xEnv ); + m_bDidGetOrHead = !bError; + } + } + + if ( bError ) + { + DAVOptions aDAVOptionsException; + + aDAVOptionsException.setURL( aTargetURL ); + // check if the error was SC_NOT_FOUND, meaning that the + // GET fall back didn't succeeded and the element is really missing + // we will consider the resource SC_GONE (410) for some time + // we use SC_GONE because has the same meaning of SC_NOT_FOUND (404) + // see: + // <https://tools.ietf.org/html/rfc7231#section-6.5.9> (retrieved 2016-10-09) + // apparently it's not used to mark the missing HEAD method (so far...) + sal_uInt16 ResponseStatusCode = + ( aLastException.getStatus() == SC_NOT_FOUND ) ? + SC_GONE : + aLastException.getStatus(); + aDAVOptionsException.setHttpResponseStatusCode( ResponseStatusCode ); + aDAVOptionsException.setHttpResponseStatusText( aLastException.getData() ); + aStaticDAVOptionsCache.addDAVOptions( aDAVOptionsException, + m_nOptsCacheLifeNotFound ); + + if ( !shouldAccessNetworkAfterException( aLastException ) ) + { + cancelCommandExecution( aLastException, xEnv ); + // unreachable + } + } + } + } + } + + // might trigger HTTP redirect. + // Therefore, title must be updated here. + CurlUri const aUri( xResAccess->getURL() ); + aUnescapedTitle = aUri.GetPathBaseNameUnescaped(); + + if ( eType == UNKNOWN ) + { + xProps.reset( new ContentProperties( aUnescapedTitle ) ); + } + + // For DAV resources we only know the Title, for non-DAV + // resources we additionally know that it is a document. + + else if ( eType == DAV ) + { + if (!xProps) + xProps.reset(new ContentProperties(aUnescapedTitle)); + else + xProps->addProperty("Title", uno::Any(aUnescapedTitle), true); + } + else + { + if (!xProps) + xProps.reset( new ContentProperties( aUnescapedTitle, false ) ); + else + xProps->addProperty( + "Title", + uno::Any( aUnescapedTitle ), + true ); + + xProps->addProperty( + "IsFolder", + uno::Any( false ), + true ); + xProps->addProperty( + "IsDocument", + uno::Any( true ), + true ); + xProps->addProperty( + "ContentType", + uno::Any( 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 ) ); + } + + // Add a default for the properties requested but not found. + // Determine still missing properties, add a default. + // Some client function doesn't expect a void uno::Any, + // but instead wants some sort of default. + std::vector< OUString > aMissingProps; + if ( !xProps->containsAllNames( + rProperties, aMissingProps ) ) + { + // + for ( std::vector< rtl::OUString >::const_iterator it = aMissingProps.begin(); + it != aMissingProps.end(); ++it ) + { + // For the time being only a couple of properties need to be added + if ( (*it) == "DateModified" || (*it) == "DateCreated" ) + { + util::DateTime aDate; + xProps->addProperty( + (*it), + uno::Any( aDate ), + true ); + } + else if (bNetworkAccessAllowed) // don't set these if connection failed + { + // If WebDAV didn't return the resource type, assume default + // This happens e.g. for lists exported by SharePoint + if ((*it) == "IsFolder") + { + xProps->addProperty( + (*it), + uno::Any( false ), + true ); + } + else if ((*it) == "IsDocument") + { + xProps->addProperty( + (*it), + uno::Any( true ), + true ); + } + } + } + } + + sal_Int32 nCount = rProperties.getLength(); + for ( sal_Int32 n = 0; n < nCount; ++n ) + { + const OUString rName = rProperties[ n ].Name; + if ( rName == "BaseURI" ) + { + // Add BaseURI property, if requested. + xProps->addProperty( + "BaseURI", + uno::Any( getBaseURI( xResAccess ) ), + true ); + } + else if ( rName == "CreatableContentsInfo" ) + { + // Add CreatableContentsInfo property, if requested. + bool bFolder = false; + xProps->getValue( "IsFolder" ) + >>= bFolder; + xProps->addProperty( + "CreatableContentsInfo", + uno::Any( bFolder + ? queryCreatableContentsInfo() + : uno::Sequence< ucb::ContentInfo >() ), + true ); + } + } + + uno::Reference< sdbc::XRow > xResultRow + = getPropertyValues( xContext, + rProperties, + *xProps, + xProvider, + xIdentifier->getContentIdentifier() ); + + { + osl::Guard< osl::Mutex > aGuard( m_aMutex ); + + if (!m_xCachedProps) + m_xCachedProps.reset( new CachableContentProperties( *xProps ) ); + else + m_xCachedProps->addProperties( *xProps ); + + m_xResAccess.reset( new DAVResourceAccess( *xResAccess ) ); + m_aEscapedTitle = EncodeSegment(aUnescapedTitle); + } + + return xResultRow; +} + + +uno::Sequence< uno::Any > Content::setPropertyValues( + const uno::Sequence< beans::PropertyValue >& rValues, + const uno::Reference< ucb::XCommandEnvironment >& xEnv ) +{ + uno::Reference< ucb::XContentIdentifier > xIdentifier; + rtl::Reference< ContentProvider > xProvider; + bool bTransient; + std::unique_ptr< DAVResourceAccess > xResAccess; + + { + osl::Guard< osl::Mutex > aGuard( m_aMutex ); + + xProvider.set( m_pProvider ); + xIdentifier.set( m_xIdentifier ); + bTransient = m_bTransient; + xResAccess.reset( new DAVResourceAccess( *m_xResAccess ) ); + } + + uno::Sequence< uno::Any > aRet( rValues.getLength() ); + auto aRetRange = asNonConstRange(aRet); + uno::Sequence< beans::PropertyChangeEvent > aChanges( rValues.getLength() ); + sal_Int32 nChanged = 0; + + beans::PropertyChangeEvent aEvent; + aEvent.Source = 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! + aRetRange[ n ] <<= lang::IllegalAccessException( + "Property is read-only!", + static_cast< cppu::OWeakObject * >( this ) ); + continue; + } + + + // Mandatory props. + + + if ( rName == "ContentType" ) + { + // Read-only property! + aRetRange[ n ] <<= lang::IllegalAccessException( + "Property is read-only!", + static_cast< cppu::OWeakObject * >( this ) ); + } + else if ( rName == "IsDocument" ) + { + // Read-only property! + aRetRange[ n ] <<= lang::IllegalAccessException( + "Property is read-only!", + static_cast< cppu::OWeakObject * >( this ) ); + } + else if ( rName == "IsFolder" ) + { + // Read-only property! + aRetRange[ 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 + { + CurlUri const aURI(xIdentifier->getContentIdentifier()); + aOldTitle = aURI.GetPathBaseNameUnescaped(); + + if ( aNewValue != aOldTitle ) + { + // modified title -> modified URL -> exchange ! + if ( !bTransient ) + bExchange = true; + + // new value will be set later... + aNewTitle = aNewValue; + + // remember position within sequence of values (for + // error handling). + nTitlePos = n; + } + } + catch ( DAVException const & ) + { + aRetRange[ n ] <<= lang::IllegalArgumentException( + "Invalid content identifier!", + static_cast< cppu::OWeakObject * >( this ), + -1 ); + } + } + else + { + aRetRange[ n ] <<= lang::IllegalArgumentException( + "Empty title not allowed!", + static_cast< cppu::OWeakObject * >( this ), + -1 ); + } + } + else + { + aRetRange[ 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! + aRetRange[ n ] <<= beans::UnknownPropertyException( + "Property is unknown!", + static_cast< cppu::OWeakObject * >( this ) ); + continue; + } + + if ( rName == "Size" ) + { + // Read-only property! + aRetRange[ n ] <<= lang::IllegalAccessException( + "Property is read-only!", + static_cast< cppu::OWeakObject * >( this ) ); + } + else if ( rName == "DateCreated" ) + { + // Read-only property! + aRetRange[ n ] <<= lang::IllegalAccessException( + "Property is read-only!", + static_cast< cppu::OWeakObject * >( this ) ); + } + else if ( rName == "DateModified" ) + { + // Read-only property! + aRetRange[ 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) + aRetRange[ n ] <<= lang::IllegalAccessException( + "Property is read-only!", + static_cast< cppu::OWeakObject * >( this ) ); + } + if ( rName == "CreatableContentsInfo" ) + { + // Read-only property! + aRetRange[ 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 ) + { + aRetRange[ n ] <<= e; + } + catch ( lang::WrappedTargetException const & e ) + { + aRetRange[ n ] <<= e; + } + catch ( beans::PropertyVetoException const & e ) + { + aRetRange[ n ] <<= e; + } + catch ( lang::IllegalArgumentException const & e ) + { + aRetRange[ n ] <<= e; + } + } + else + { + aRetRange[ n ] <<= uno::Exception( + "No property set for storing the value!", + 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!" ); + +#if 1 + cancelCommandExecution( e, xEnv ); + // unreachable +#else + // Note: PROPPATCH either sets ALL property values OR NOTHING. + + std::vector< sal_Int32 >::const_iterator it + = aProppatchPropsPositions.begin(); + std::vector< sal_Int32 >::const_iterator end + = aProppatchPropsPositions.end(); + + while ( it != end ) + { + // Set error. + aRetRange[ (*it) ] <<= MapDAVException( e, true ); + ++it; + } +#endif + } + } + + if ( bExchange ) + { + // Assemble new content identifier... + + OUString aNewURL = getParentURL(); + if ( aNewURL.lastIndexOf( '/' ) != ( aNewURL.getLength() - 1 ) ) + aNewURL += "/"; + + aNewURL += EncodeSegment(aNewTitle); + + uno::Reference< ucb::XContentIdentifier > xNewId + = new ::ucbhelper::ContentIdentifier( aNewURL ); + uno::Reference< ucb::XContentIdentifier > xOldId = xIdentifier; + + try + { + CurlUri const sourceURI( xOldId->getContentIdentifier() ); + CurlUri targetURI( xNewId->getContentIdentifier() ); + + targetURI.SetScheme( sourceURI.GetScheme() ); + + // clean cached value of PROPFIND property names + removeCachedPropertyNames( sourceURI.GetURI() ); + removeCachedPropertyNames( targetURI.GetURI() ); + aStaticDAVOptionsCache.removeDAVOptions( sourceURI.GetURI() ); + aStaticDAVOptionsCache.removeDAVOptions( targetURI.GetURI() ); + xResAccess->MOVE( + sourceURI.GetRelativeReference(), targetURI.GetURI(), false, xEnv ); + + // @@@ Should check for resources that could not be moved + // (due to source access or target overwrite) and send + // this information through the interaction handler. + + // @@@ Existing content should be checked to see if it needs + // to be deleted at the source + + // @@@ Existing content should be checked to see if it has + // been overwritten at the target + + if ( exchangeIdentity( xNewId ) ) + { + xResAccess->setURL( aNewURL ); + +// DAV resources store all additional props on server! +// // Adapt Additional Core Properties. +// renameAdditionalPropertySet( xOldId->getContentIdentifier(), +// xNewId->getContentIdentifier(), +// true ); + } + else + { + // Do not set new title! + aNewTitle.clear(); + + // Set error . + aRetRange[ nTitlePos ] <<= uno::Exception( + "Exchange failed!", + static_cast< cppu::OWeakObject * >( this ) ); + } + } + catch ( DAVException const & e ) + { + // Do not set new title! + aNewTitle.clear(); + + // Set error . + aRetRange[ nTitlePos ] = MapDAVException( e, true ); + } + } + + if ( aNewTitle.getLength() ) + { + osl::Guard< osl::Mutex > aGuard( m_aMutex ); + + aEvent.PropertyName = "Title"; + aEvent.OldValue <<= aOldTitle; + aEvent.NewValue <<= aNewTitle; + + m_aEscapedTitle = EncodeSegment(aNewTitle); + + aChanges.getArray()[ nChanged ] = aEvent; + nChanged++; + } + + if ( nChanged > 0 ) + { + aChanges.realloc( nChanged ); + notifyPropertiesChange( aChanges ); + } + + { + osl::Guard< osl::Mutex > aGuard( m_aMutex ); + m_xResAccess.reset( new DAVResourceAccess( *xResAccess ) ); + } + + return aRet; +} + + +uno::Any Content::open( + const ucb::OpenCommandArgument3 & rArg, + const uno::Reference< ucb::XCommandEnvironment > & xEnv ) +{ + uno::Any aRet; + + bool bOpenFolder = ( ( rArg.Mode == ucb::OpenMode::ALL ) || + ( rArg.Mode == ucb::OpenMode::FOLDERS ) || + ( rArg.Mode == ucb::OpenMode::DOCUMENTS ) ); + if ( bOpenFolder ) + { + if ( isFolder( xEnv ) ) + { + // Open collection. + + uno::Reference< ucb::XDynamicResultSet > xSet + = new DynamicResultSet( m_xContext, this, rArg, xEnv ); + aRet <<= xSet; + } + else + { + // Error: Not a folder! + + ucbhelper::cancelCommandExecution( + uno::Any( + lang::IllegalArgumentException( + "Non-folder resource cannot be opened as folder! Wrong Open Mode!", + 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::Any( + 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 + // tdf#148426 fall back to GET in case of 500 + && aDAVOptions.getHttpResponseStatusCode() != SC_INTERNAL_SERVER_ERROR) + { + // throws exception as if there was a server error, a DAV exception + throw DAVException( DAVException::DAV_HTTP_ERROR, + aDAVOptions.getHttpResponseStatusText(), + aDAVOptions.getHttpResponseStatusCode() ); + } + uno::Reference< io::XInputStream > xIn + = xResAccess->GET( aHeaders, aResource, xEnv ); + m_bDidGetOrHead = true; + + { + osl::MutexGuard aGuard( m_aMutex ); + + // cache headers. + if (!m_xCachedProps) + m_xCachedProps.reset( + new CachableContentProperties( ContentProperties( aResource ) ) ); + else + m_xCachedProps->addProperties( + aResource.properties ); + + m_xResAccess.reset( + new DAVResourceAccess( *xResAccess ) ); + } + + xDataSink->setInputStream( xIn ); + } + catch ( DAVException const & e ) + { + //TODO cache the http error if not yet cached + cancelCommandExecution( e, xEnv ); + // Unreachable + } + } + else + { + // Note: aOpenCommand.Sink may contain an XStream + // implementation. Support for this type of + // sink is optional... + ucbhelper::cancelCommandExecution( + uno::Any( + ucb::UnsupportedDataSinkException( + OUString(), + 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::Any( + 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::Any( 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( aEx ); + + rtl::Reference< ucbhelper::SimpleInteractionRequest > xRequest + = new ucbhelper::SimpleInteractionRequest( + aExAsAny, + ContinuationFlags::Approve + | ContinuationFlags::Disapprove ); + xIH->handle( xRequest ); + + const ContinuationFlags nResp = xRequest->getResponse(); + + switch ( nResp ) + { + case ContinuationFlags::NONE: + // Not handled; throw. + throw aEx; +// break; + + case ContinuationFlags::Approve: + // Continue -> Overwrite. + bReplaceExisting = true; + break; + + case ContinuationFlags::Disapprove: + // Abort. + throw ucb::CommandFailedException( + OUString(), + uno::Reference< uno::XInterface >(), + aExAsAny ); +// break; + + default: + SAL_WARN( "ucb.ucp.webdav", + "Content::insert - " + "Unknown interaction selection!" ); + throw ucb::CommandFailedException( + "Unknown interaction selection!", + uno::Reference< uno::XInterface >(), + aExAsAny ); +// break; + } + } + else + { + // No IH; throw. + throw aEx; + } + } + } + + if ( bTransient ) + { + // Assemble new content identifier... + OUString aURL = getParentURL(); + if ( aURL.lastIndexOf( '/' ) != ( aURL.getLength() - 1 ) ) + aURL += "/"; + + aURL += aEscapedTitle; + + try + { + xResAccess->setURL( aURL ); + + if ( bCollection ) + { + aStaticDAVOptionsCache.removeDAVOptions( xResAccess->getURL() ); + removeCachedPropertyNames( xResAccess->getURL() ); + xResAccess->MKCOL( Environment ); + } + else + { + // remove options from cache, PUT may change it + // it will be refreshed when needed + aStaticDAVOptionsCache.removeDAVOptions( xResAccess->getURL() ); + removeCachedPropertyNames( xResAccess->getURL() ); + xResAccess->PUT( xInputStream, Environment ); + // clean cached value of PROPFIND properties names + } + // no error , set the resourcetype to unknown type + // the resource may have transitioned from NOT FOUND or UNKNOWN to something else + // depending on the server behaviour + // this will force a recheck of the resource type + m_eResourceType = UNKNOWN; + m_eResourceTypeForLocks = UNKNOWN; + } + catch ( DAVException const & except ) + { + if ( bCollection ) + { + if ( except.getStatus() == SC_METHOD_NOT_ALLOWED ) + { + // [RFC 2518] - WebDAV + // 405 (Method Not Allowed) - MKCOL can only be + // executed on a deleted/non-existent resource. + + if ( bReplaceExisting ) + { + // Destroy old resource. + try + { + removeCachedPropertyNames( xResAccess->getURL() ); + xResAccess->DESTROY( Environment ); + } + catch ( DAVException const & e ) + { + cancelCommandExecution( e, Environment, true ); + // Unreachable + } + + // Insert (recursion!). + insert( xInputStream, bReplaceExisting, Environment ); + + { + osl::Guard< osl::Mutex > aGuard( m_aMutex ); + m_xResAccess.reset( + new DAVResourceAccess( *xResAccess ) ); + } + + // Success! + return; + } + else + { + OUString aTitle; + try + { + CurlUri const aURI( aURL ); + aTitle = aURI.GetPathBaseNameUnescaped(); + } + catch ( DAVException const & ) + { + } + + ucbhelper::cancelCommandExecution( + uno::Any( + ucb::NameClashException( + OUString(), + 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::Any( + 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< uno::XComponentContext > xContext; + uno::Reference< ucb::XContentIdentifier > xIdentifier; + uno::Reference< ucb::XContentProvider > xProvider; + std::unique_ptr< DAVResourceAccess > xResAccess; + + { + osl::Guard< osl::Mutex > aGuard( m_aMutex ); + + xContext.set( m_xContext ); + xIdentifier.set( m_xIdentifier ); + xProvider.set( m_xProvider ); + xResAccess.reset( new DAVResourceAccess( *m_xResAccess ) ); + } + + OUString aTargetURI; + try + { + CurlUri sourceURI( rArgs.SourceURL ); + CurlUri targetURI( xIdentifier->getContentIdentifier() ); + + aTargetURI = targetURI.GetPathBaseNameUnescaped(); + + // Check source's and target's URL scheme + + OUString aScheme = sourceURI.GetScheme().toAsciiLowerCase(); + if ( aScheme == VNDSUNSTARWEBDAV_URL_SCHEME) + { + sourceURI.SetScheme( HTTP_URL_SCHEME ); + } + else if ( aScheme == VNDSUNSTARWEBDAVS_URL_SCHEME) + { + sourceURI.SetScheme( HTTPS_URL_SCHEME ); + } + else if ( aScheme == DAV_URL_SCHEME ) + { + sourceURI.SetScheme( HTTP_URL_SCHEME ); + } + else if ( aScheme == DAVS_URL_SCHEME ) + { + sourceURI.SetScheme( HTTPS_URL_SCHEME ); + } + else if (aScheme == WEBDAV_URL_SCHEME) + { + sourceURI.SetScheme(HTTP_URL_SCHEME); + } + else if (aScheme == WEBDAVS_URL_SCHEME) + { + sourceURI.SetScheme(HTTPS_URL_SCHEME); + } + else + { + if ( aScheme != HTTP_URL_SCHEME && aScheme != HTTPS_URL_SCHEME ) + { + ucbhelper::cancelCommandExecution( + uno::Any( + ucb::InteractiveBadTransferURLException( + "Unsupported URL scheme!", + 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::Any( 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. + + aStaticDAVOptionsCache.removeDAVOptions( sourceURI.GetURI() ); + aStaticDAVOptionsCache.removeDAVOptions( targetURI.GetURI() ); + aSourceAccess.MOVE( sourceURI.GetRelativeReference(), + targetURI.GetURI(), + rArgs.NameClash + == ucb::NameClash::OVERWRITE, + Environment ); + + if ( xSource.is() ) + { + // Propagate destruction to listeners. + xSource->destroy( true ); + } + +// DAV resources store all additional props on server! +// // Rename own and all children's Additional Core Properties. +// renameAdditionalPropertySet( xId->getContentIdentifier(), +// xTargetId->getContentIdentifier(), +// true ); + } + else + { + // [RFC 2518] - WebDAV + // If a resource exists at the destination and the Overwrite + // header is "T" then prior to performing the copy the server + // MUST perform a DELETE with "Depth: infinity" on the + // destination resource. If the Overwrite header is set to + // "F" then the operation will fail. + + aStaticDAVOptionsCache.removeDAVOptions( sourceURI.GetURI() ); + aStaticDAVOptionsCache.removeDAVOptions( targetURI.GetURI() ); + aSourceAccess.COPY( sourceURI.GetRelativeReference(), + targetURI.GetURI(), + rArgs.NameClash + == ucb::NameClash::OVERWRITE, + Environment ); + +// DAV resources store all additional props on server! +// // Copy own and all children's Additional Core Properties. +// copyAdditionalPropertySet( xId->getContentIdentifier(), +// xTargetId->getContentIdentifier(), +// true ); + } + + // Note: The static cast is okay here, because its sure that + // xProvider is always the WebDAVContentProvider. + rtl::Reference< Content > xTarget + = static_cast< Content * >( + xProvider->queryContent( xTargetId ).get() ); + + // Announce transferred content in its new folder. + xTarget->inserted(); + } + catch ( ucb::IllegalIdentifierException const & ) + { + // queryContent + } + catch ( DAVException const & e ) + { + // [RFC 2518] - WebDAV + // 412 (Precondition Failed) - The server was unable to maintain + // the liveness of the properties listed in the propertybehavior + // XML element or the Overwrite header is "F" and the state of + // the destination resource is non-null. + + if ( e.getStatus() == SC_PRECONDITION_FAILED ) + { + switch ( rArgs.NameClash ) + { + case 0/*ucb::NameClash::ERROR*/: + { + ucbhelper::cancelCommandExecution( + uno::Any( + ucb::NameClashException( + OUString(), + 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::Any( + 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 ); + } +} + +// returns the resource type, to be checked for locks +Content::ResourceType Content::resourceTypeForLocks( + const uno::Reference< ucb::XCommandEnvironment >& Environment, + const std::unique_ptr< DAVResourceAccess > & rResAccess) +{ + ResourceType eResourceTypeForLocks = UNKNOWN; + { + osl::MutexGuard g(m_aMutex); + //check if cache contains what we need, usually the first PROPFIND on the URI has supported lock + if (m_xCachedProps) + { + uno::Sequence< ucb::LockEntry > aSupportedLocks; + if ( m_xCachedProps->getValue( DAVProperties::SUPPORTEDLOCK ) + >>= aSupportedLocks ) //get the cached value for supportedlock + { + for ( sal_Int32 n = 0; n < aSupportedLocks.getLength(); ++n ) + { + if ( aSupportedLocks[ n ].Scope + == ucb::LockScope_EXCLUSIVE && + aSupportedLocks[ n ].Type + == ucb::LockType_WRITE ) + eResourceTypeForLocks = DAV; + } + } + } + } + + const OUString & rURL = m_xIdentifier->getContentIdentifier(); + + if ( eResourceTypeForLocks == UNKNOWN ) + { + // resource type for lock/unlock operations still unknown, need to ask the server + + //{ + DAVOptions aDAVOptions; + getResourceOptions( Environment, aDAVOptions, rResAccess ); + if( aDAVOptions.isClass1() || + aDAVOptions.isClass2() || + aDAVOptions.isClass3() ) + { + // this is at least a DAV, lock to be confirmed + // class 2 is needed for full lock support + // see + // <https://tools.ietf.org/html/rfc4918#section-18.2> + eResourceTypeForLocks = DAV_NOLOCK; + if( aDAVOptions.isClass2() ) + { + // ok, possible lock, check for it + try + { + // we need only DAV:supportedlock + std::vector< DAVResource > resources; + std::vector< OUString > aPropNames; + uno::Sequence< beans::Property > aProperties( 1 ); + aProperties.getArray()[ 0 ].Name = DAVProperties::SUPPORTEDLOCK; + + ContentProperties::UCBNamesToDAVNames( aProperties, aPropNames ); + rResAccess->PROPFIND( DAVZERO, aPropNames, resources, Environment ); + + bool wasSupportedlockFound = false; + + // only one resource should be returned + if ( resources.size() == 1 ) + { + // we may have received a bunch of other properties + // (some servers seems to do so) + // but we need only supported lock for this check + // all returned properties are in + // resources.properties[n].Name/.Value + + std::vector< DAVPropertyValue >::iterator it; + + for ( it = resources[0].properties.begin(); + it != resources[0].properties.end(); ++it) + { + if ( (*it).Name == DAVProperties::SUPPORTEDLOCK ) + { + wasSupportedlockFound = true; + uno::Sequence< ucb::LockEntry > aSupportedLocks; + if ( (*it).Value >>= aSupportedLocks ) + { + for ( sal_Int32 n = 0; n < aSupportedLocks.getLength(); ++n ) + { + // TODO: if the lock type is changed from 'exclusive write' to 'shared write' + // e.g. to implement 'Calc shared file feature', the ucb::LockScope_EXCLUSIVE + // value should be checked as well, adaptation the code may be needed + if ( aSupportedLocks[ n ].Scope == ucb::LockScope_EXCLUSIVE && + aSupportedLocks[ n ].Type == ucb::LockType_WRITE ) + { + // requested locking mode is supported + eResourceTypeForLocks = DAV; + SAL_INFO( "ucb.ucp.webdav", "resourceTypeForLocks - URL: <" + << m_xIdentifier->getContentIdentifier() << ">, DAV lock/unlock supported"); + break; + } + } + break; + } + } + } + } + else + { + // PROPFIND failed; check if HEAD contains Content-Disposition: attachment (RFC1806, HTTP/1.1 19.5.1), + // which supposedly means no lock for the resource (happens e.g. with SharePoint exported lists) + OUString sContentDisposition; + // First, check cached properties + if (m_xCachedProps) + { + if ((m_xCachedProps->getValue("Content-Disposition") >>= sContentDisposition) + && sContentDisposition.startsWithIgnoreAsciiCase("attachment")) + { + eResourceTypeForLocks = DAV_NOLOCK; + wasSupportedlockFound = true; + } + } + // If no data in cache, try HEAD request + if (sContentDisposition.isEmpty() && !m_bDidGetOrHead) try + { + DAVResource resource; + GetPropsUsingHeadRequest(resource, rResAccess, {"Content-Disposition"}, Environment); + m_bDidGetOrHead = true; + for (const auto& it : resource.properties) + { + if (it.Name.equalsIgnoreAsciiCase("Content-Disposition")) + { + if ((it.Value >>= sContentDisposition) && sContentDisposition.equalsIgnoreAsciiCase("attachment")) + { + eResourceTypeForLocks = DAV_NOLOCK; + wasSupportedlockFound = true; + } + break; + } + } + } + catch (...){} + } + // check if this is still only a DAV_NOLOCK + // a fallback for resources that do not have DAVProperties::SUPPORTEDLOCK property + // we check for the returned OPTION if LOCK is allowed on the resource + if ( !wasSupportedlockFound && eResourceTypeForLocks == DAV_NOLOCK ) + { + SAL_INFO( "ucb.ucp.webdav", "This WebDAV server has no supportedlock property, check for allowed LOCK method in OPTIONS" ); + // ATTENTION: if the lock type is changed from 'exclusive write' to 'shared write' + // e.g. to implement 'Calc shared file feature' on WebDAV directly, and we arrive to this fallback + // and the LOCK is allowed, we should assume that only exclusive write lock is available + // this is just a reminder... + if ( aDAVOptions.isLockAllowed() ) + eResourceTypeForLocks = DAV; + } + } + catch ( DAVException const & e ) + { + rResAccess->resetUri(); + //grab the error code + switch( e.getStatus() ) + { + case SC_NOT_FOUND: + SAL_WARN( "ucb.ucp.webdav", "resourceTypeForLocks() - URL: <" + << m_xIdentifier->getContentIdentifier() << "> was not found. "); + eResourceTypeForLocks = NOT_FOUND; + break; + // some servers returns SC_FORBIDDEN, instead + // the meaning of SC_FORBIDDEN is, according to <http://tools.ietf.org/html/rfc7231#section-6.5.3>: + // The 403 (Forbidden) status code indicates that the server understood + // the request but refuses to authorize it + case SC_FORBIDDEN: + // Errors SC_NOT_IMPLEMENTED and SC_METHOD_NOT_ALLOWED are + // part of base http 1.1 RFCs + case SC_NOT_IMPLEMENTED: // <http://tools.ietf.org/html/rfc7231#section-6.6.2> + case SC_METHOD_NOT_ALLOWED: // <http://tools.ietf.org/html/rfc7231#section-6.5.5> + // they all mean the resource is NON_DAV + SAL_WARN( "ucb.ucp.webdav", "resourceTypeForLocks() DAVException (SC_FORBIDDEN, SC_NOT_IMPLEMENTED or SC_METHOD_NOT_ALLOWED) - URL: <" + << m_xIdentifier->getContentIdentifier() << ">, DAV ExceptionCode: " << e.getError() << ", HTTP error: " << e.getStatus() ); + eResourceTypeForLocks = NON_DAV; + break; + default: + //fallthrough + SAL_WARN( "ucb.ucp.webdav", "resourceTypeForLocks() DAVException - URL: <" + << m_xIdentifier->getContentIdentifier() << ">, DAV ExceptionCode: " << e.getError() << ", HTTP error: " << e.getStatus() ); + eResourceTypeForLocks = UNKNOWN; + } + } + } + } + else + eResourceTypeForLocks = NON_DAV; + + //} + } + osl::MutexGuard g(m_aMutex); + if (m_eResourceTypeForLocks == UNKNOWN) + { + m_eResourceTypeForLocks = eResourceTypeForLocks; + } + else + { + SAL_WARN_IF( + eResourceTypeForLocks != m_eResourceTypeForLocks, "ucb.ucp.webdav", + "different resource types for <" << rURL << ">: " + << +eResourceTypeForLocks << " vs. " << +m_eResourceTypeForLocks); + } + SAL_INFO( "ucb.ucp.webdav", "resourceTypeForLocks() - URL: <" + << m_xIdentifier->getContentIdentifier() << ">, m_eResourceTypeForLocks: " << m_eResourceTypeForLocks ); + return m_eResourceTypeForLocks; +} + +Content::ResourceType Content::resourceTypeForLocks( + const uno::Reference< ucb::XCommandEnvironment >& Environment ) +{ + std::unique_ptr< DAVResourceAccess > xResAccess; + { + osl::MutexGuard aGuard( m_aMutex ); + xResAccess.reset( new DAVResourceAccess( *m_xResAccess ) ); + } + Content::ResourceType ret = resourceTypeForLocks( Environment, xResAccess ); + { + osl::Guard< osl::Mutex > aGuard( m_aMutex ); + m_xResAccess.reset( new DAVResourceAccess( *xResAccess ) ); + } + return ret; +} + +void Content::lock( + const uno::Reference< ucb::XCommandEnvironment >& Environment ) +{ +// prepare aURL to be used in exception, see below + OUString aURL; + if ( m_bTransient ) + { + aURL = getParentURL(); + if ( aURL.lastIndexOf('/') != ( aURL.getLength() - 1 ) ) + aURL += "/"; + + aURL += m_aEscapedTitle; + } + else + { + aURL = m_xIdentifier->getContentIdentifier(); + } + + try + { + std::unique_ptr< DAVResourceAccess > xResAccess; + { + osl::Guard< osl::Mutex > aGuard( m_aMutex ); + xResAccess.reset( new DAVResourceAccess( *m_xResAccess ) ); + } + + uno::Any aOwnerAny; + OUString const user(officecfg::Office::Common::Save::Document::UseUserData::get() + ? " - " + ::svt::LockFileCommon::GetOOOUserName() + : OUString()); + aOwnerAny <<= OUString("LibreOffice" + user); + + ucb::Lock aLock( + ucb::LockScope_EXCLUSIVE, + ucb::LockType_WRITE, + ucb::LockDepth_ZERO, + aOwnerAny, + 180, // lock timeout in secs + //-1, // infinite lock + uno::Sequence< OUString >() ); + + // OPTIONS may change as a consequence of the lock operation + aStaticDAVOptionsCache.removeDAVOptions( xResAccess->getURL() ); + removeCachedPropertyNames( xResAccess->getURL() ); + xResAccess->LOCK( aLock, Environment ); + + { + osl::Guard< osl::Mutex > aGuard( m_aMutex ); + m_xResAccess.reset( new DAVResourceAccess( *xResAccess ) ); + } + } + catch ( DAVException const & e ) + { + // check if the exception thrown is 'already locked' + // this exception is mapped directly to the ucb correct one, without + // going into the cancelCommandExecution() user interaction + // this exception should be managed by the issuer of 'lock' command + switch( e.getError() ) + { + case DAVException::DAV_LOCKED: + { + SAL_WARN( "ucb.ucp.webdav", "lock(): resource already locked - URL: <" + << m_xIdentifier->getContentIdentifier() << ">"); + throw + ucb::InteractiveLockingLockedException( + "Locked!", + static_cast< cppu::OWeakObject * >( this ), + task::InteractionClassification_ERROR, + aURL, + false ); + } + break; + case DAVException::DAV_HTTP_NOAUTH: + case DAVException::DAV_HTTP_AUTH: + { + SAL_WARN( "ucb.ucp.webdav", "lock(): DAVException Authentication error - URL: <" + << m_xIdentifier->getContentIdentifier() << ">" ); + // DAVException::DAV_HTTP_AUTH exception can mean: + // - interaction handler for credential management not present (happens, depending + // on the LO framework processing) + // - the remote site is a WebDAV with special configuration: read/only for read operations + // and read/write for write operations, the user is not allowed to lock/write and + // she cancelled the credentials request. + // this is not actually an error, but the exception is sent directly from here, avoiding the automatic + // management that takes part in cancelCommandExecution() below + // Unfortunately there is no InteractiveNetwork*Exception available to signal this + // since it mostly happens on read/only part of webdav, this appears to be the most correct exception available + throw + ucb::InteractiveNetworkWriteException( + "Authentication error while trying to lock! Write only WebDAV perhaps?", + 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; + default: + //fallthrough + ; + } + break; + case DAVException::DAV_LOCKED_SELF: + // we already hold the lock and it is in our internal lockstore + // just return as if the lock was successful + return; + default: + //fallthrough + ; + } + + SAL_WARN( "ucb.ucp.webdav","lock() DAVException - URL: <" + << m_xIdentifier->getContentIdentifier() << ">, DAV ExceptionCode: " << e.getError() << ", HTTP error: " << e.getStatus() ); + cancelCommandExecution( e, Environment, false ); + // Unreachable + } +} + + +void Content::unlock( + const uno::Reference< ucb::XCommandEnvironment >& Environment ) +{ + + try + { + std::unique_ptr< DAVResourceAccess > xResAccess; + { + osl::Guard< osl::Mutex > aGuard( m_aMutex ); + xResAccess.reset( new DAVResourceAccess( *m_xResAccess ) ); + } + + // check if the target URL is a Class1 DAV + DAVOptions aDAVOptions; + getResourceOptions( Environment, aDAVOptions, xResAccess ); + + // at least class one is needed + if( aDAVOptions.isClass1() ) + { + // remove options from cache, unlock may change it + // it will be refreshed when needed + aStaticDAVOptionsCache.removeDAVOptions( xResAccess->getURL() ); + // clean cached value of PROPFIND properties names + removeCachedPropertyNames( xResAccess->getURL() ); + xResAccess->UNLOCK( Environment ); + } + + { + osl::Guard< osl::Mutex > aGuard( m_aMutex ); + m_xResAccess.reset( new DAVResourceAccess( *xResAccess ) ); + } + } + catch ( DAVException const & e ) + { + switch( e.getError() ) + { + case DAVException::DAV_NOT_LOCKED: + SAL_WARN( "ucb.ucp.webdav", "unlock(): DAVException::DAV_NOT_LOCKED - URL: <" + << m_xIdentifier->getContentIdentifier() << ">"); + // means that we don't own any lock on this resource + // intercepted here to remove a confusing indication to the user + // unfortunately this happens in some WebDAV server configuration + // acting as WebDAV and having lock/unlock enabled only + // for authorized user. + return; + case DAVException::DAV_HTTP_ERROR: + //grab the error code + switch( e.getStatus() ) + { + // Errors SC_NOT_IMPLEMENTED and SC_METHOD_NOT_ALLOWED are + // part of base http 1.1 RFCs + case SC_NOT_IMPLEMENTED: // <http://tools.ietf.org/html/rfc7231#section-6.6.2> + case SC_METHOD_NOT_ALLOWED: // <http://tools.ietf.org/html/rfc7231#section-6.5.5> + SAL_WARN( "ucb.ucp.webdav", "unlock() DAVException (SC_NOT_IMPLEMENTED or SC_METHOD_NOT_ALLOWED) - URL: <" + << m_xIdentifier->getContentIdentifier() << ">, DAV ExceptionCode: " << e.getError() << ", HTTP error: " << e.getStatus() ); + return; + default: + //fallthrough + ; + } + break; + default: + //fallthrough + ; + } + SAL_WARN( "ucb.ucp.webdav","unlock() DAVException - URL: <" + << m_xIdentifier->getContentIdentifier() << ">, DAV ExceptionCode: " << e.getError() << ", HTTP error: " << e.getStatus() ); + cancelCommandExecution( e, Environment, false ); + // Unreachable + } +} + + +bool Content::exchangeIdentity( + const uno::Reference< ucb::XContentIdentifier >& xNewId ) +{ + if ( !xNewId.is() ) + return false; + + osl::ClearableGuard< osl::Mutex > aGuard( m_aMutex ); + + uno::Reference< ucb::XContent > xThis = this; + + // Already persistent? + if ( m_bTransient ) + { + SAL_WARN( "ucb.ucp.webdav", "Content::exchangeIdentity - Not persistent!" ); + return false; + } + + // Exchange own identity. + + // Fail, if a content with given id already exists. +// if ( !hasData( xNewId ) ) + { + OUString aOldURL = m_xIdentifier->getContentIdentifier(); + + aGuard.clear(); + if ( exchange( xNewId ) ) + { + // Process instantiated children... + + ContentRefList aChildren; + queryChildren( aChildren ); + + for ( const auto& rChild : aChildren ) + { + ContentRef xChild = rChild; + + // Create new content identifier for the child... + uno::Reference< ucb::XContentIdentifier > + xOldChildId = xChild->getIdentifier(); + OUString aOldChildURL + = xOldChildId->getContentIdentifier(); + OUString aNewChildURL + = aOldChildURL.replaceAt( + 0, + aOldURL.getLength(), + xNewId->getContentIdentifier() ); + uno::Reference< ucb::XContentIdentifier > xNewChildId + = new ::ucbhelper::ContentIdentifier( aNewChildURL ); + + if ( !xChild->exchangeIdentity( xNewChildId ) ) + return false; + } + return true; + } + } + + SAL_WARN( "ucb.ucp.webdav", + "Content::exchangeIdentity - " + "Panic! Cannot exchange identity!" ); + return false; +} + + +bool Content::isFolder( + const uno::Reference< ucb::XCommandEnvironment >& xEnv ) +{ + { + osl::MutexGuard aGuard( m_aMutex ); + + if ( m_bTransient ) + return m_bCollection; + } + + uno::Sequence< beans::Property > aProperties( 1 ); + auto pProperties = aProperties.getArray(); + pProperties[ 0 ].Name = "IsFolder"; + pProperties[ 0 ].Handle = -1; + uno::Reference< sdbc::XRow > xRow( getPropertyValues( aProperties, xEnv ) ); + if ( xRow.is() ) + { + try + { + return xRow->getBoolean( 1 ); + } + catch ( sdbc::SQLException const & ) + { + } + } + + return false; +} + + +uno::Any Content::MapDAVException( const DAVException & e, bool bWrite ) +{ + // Map DAVException... + uno::Any aException; + + OUString aURL; + if ( m_bTransient ) + { + aURL = getParentURL(); + if ( aURL.lastIndexOf( '/' ) != ( aURL.getLength() - 1 ) ) + aURL += "/"; + + aURL += m_aEscapedTitle; + } + else + { + aURL = m_xIdentifier->getContentIdentifier(); + } + + switch ( e.getStatus() ) + { + case SC_NOT_FOUND: + { + uno::Sequence<uno::Any> aArgs{ uno::Any(beans::PropertyValue( + "Uri", -1, uno::Any(aURL), beans::PropertyState_DIRECT_VALUE)) }; + + aException <<= + ucb::InteractiveAugmentedIOException( + "Not found!", + 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: +#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.getStatus() == SC_GONE ) || + ( e.getError() == DAVException::DAV_HTTP_TIMEOUT ) || + ( e.getError() == DAVException::DAV_HTTP_LOOKUP ) || + ( e.getError() == DAVException::DAV_HTTP_CONNECT ) || + ( e.getError() == DAVException::DAV_HTTP_NOAUTH ) || + ( e.getError() == DAVException::DAV_HTTP_AUTH ) || + ( e.getError() == DAVException::DAV_HTTP_AUTHPROXY ) ) + return false; + + return true; +} + + +void Content::cancelCommandExecution( + const DAVException & e, + const uno::Reference< ucb::XCommandEnvironment > & xEnv, + bool bWrite /* = false */ ) +{ + ucbhelper::cancelCommandExecution( MapDAVException( e, bWrite ), xEnv ); + // Unreachable +} + + +OUString +Content::getBaseURI( const std::unique_ptr< DAVResourceAccess > & rResAccess ) +{ + osl::Guard< osl::Mutex > aGuard( m_aMutex ); + + // First, try to obtain value of response header "Content-Location". + if (m_xCachedProps) + { + OUString aLocation; + m_xCachedProps->getValue( "Content-Location" ) >>= aLocation; + if ( aLocation.getLength() ) + { + try + { + // Do not use m_xIdentifier->getContentIdentifier() because it + // for example does not reflect redirects applied to requests + // done using the original URI but m_xResAccess' URI does. + return rtl::Uri::convertRelToAbs( rResAccess->getURL(), + aLocation ); + } + catch ( rtl::MalformedUriException const & ) + { + } + } + } + + return rResAccess->getURL(); +} + +// resource type is the type of the WebDAV resource +Content::ResourceType Content::getResourceType( + const uno::Reference< ucb::XCommandEnvironment >& xEnv, + const std::unique_ptr< DAVResourceAccess > & rResAccess, + bool * networkAccessAllowed ) +{ + { + osl::MutexGuard g(m_aMutex); + if (m_eResourceType != UNKNOWN) { + return m_eResourceType; + } + } + + ResourceType eResourceType = UNKNOWN; + DAVOptions aDAVOptions; + + { + getResourceOptions( xEnv, aDAVOptions, rResAccess, networkAccessAllowed ); + + // at least class one is needed + if( aDAVOptions.isClass1() ) + { + try + { + // Try to fetch some frequently used property value, e.g. those + // used when loading documents... along with identifying whether + // this is a DAV resource. + std::vector< DAVResource > resources; + std::vector< OUString > aPropNames; + uno::Sequence< beans::Property > aProperties( 5 ); + auto pProperties = aProperties.getArray(); + pProperties[ 0 ].Name = "IsFolder"; + pProperties[ 1 ].Name = "IsDocument"; + pProperties[ 2 ].Name = "IsReadOnly"; + pProperties[ 3 ].Name = "MediaType"; + pProperties[ 4 ].Name = DAVProperties::SUPPORTEDLOCK; + + ContentProperties::UCBNamesToDAVNames( aProperties, aPropNames ); + + rResAccess->PROPFIND( DAVZERO, aPropNames, resources, xEnv ); + + if ( resources.size() == 1 ) + { +#if defined SAL_LOG_INFO + {//debug + // print received resources + std::vector< DAVPropertyValue >::const_iterator it = resources[0].properties.begin(); + std::vector< DAVPropertyValue >::const_iterator end = resources[0].properties.end(); + while ( it != end ) + { + OUString aPropValue; + bool bValue; + uno::Sequence< ucb::LockEntry > aSupportedLocks; + if((*it).Value >>= aPropValue ) + SAL_INFO( "ucb.ucp.webdav", "PROPFIND (getResourceType) - ret'd prop: " << (*it).Name << ":" << aPropValue ); + else if( (*it).Value >>= bValue ) + SAL_INFO( "ucb.ucp.webdav", "PROPFIND (getResourceType) - ret'd prop: " << (*it).Name << ":" << + ( bValue ? "true" : "false" ) ); + else if( (*it).Value >>= aSupportedLocks ) + { + SAL_INFO( "ucb.ucp.webdav", "PROPFIND (getResourceType) - ret'd prop: " << (*it).Name << ":" ); + for ( sal_Int32 n = 0; n < aSupportedLocks.getLength(); ++n ) + { + SAL_INFO( "ucb.ucp.webdav","PROPFIND (getResourceType) - supportedlock[" << n <<"]: scope: " + << (aSupportedLocks[n].Scope == ucb::LockScope_SHARED ? "shared" : "exclusive") + << ", type: " + << (aSupportedLocks[n].Type != ucb::LockType_WRITE ? "" : "write") ); + } + } + ++it; + } + } +#endif + osl::MutexGuard g(m_aMutex); + m_xCachedProps.reset( + new CachableContentProperties( ContentProperties( resources[ 0 ] ) ) ); + m_xCachedProps->containsAllNames( + aProperties, m_aFailedPropNames ); + } + eResourceType = DAV; + } + catch ( DAVException const & e ) + { + rResAccess->resetUri(); + + SAL_WARN( "ucb.ucp.webdav", "Content::getResourceType returned errors, DAV ExceptionCode: " << e.getError() << ", HTTP error: " << e.getStatus() ); + + if ( e.getStatus() == SC_METHOD_NOT_ALLOWED ) + { + // Status SC_METHOD_NOT_ALLOWED is a safe indicator that the + // resource is NON_DAV + eResourceType = NON_DAV; + } + else if (networkAccessAllowed != nullptr) + { + *networkAccessAllowed = *networkAccessAllowed + && shouldAccessNetworkAfterException(e); + } + if ( e.getStatus() == SC_NOT_FOUND ) + { + // arrives here if OPTIONS is still cached for a resource previously available + // operate on the OPTIONS cache: + // if OPTIONS was not found, do nothing + // else OPTIONS returned on a resource not existent (example a server that allows lock on null resource) set + // not found and adjust lifetime accordingly + DAVOptions aDAVOptionsInner; + if (aStaticDAVOptionsCache.getDAVOptions(rResAccess->getURL(), aDAVOptionsInner)) + { + // TODO? get redirected url + aDAVOptionsInner.setHttpResponseStatusCode( e.getStatus() ); + aDAVOptionsInner.setHttpResponseStatusText( e.getData() ); + aStaticDAVOptionsCache.addDAVOptions( aDAVOptionsInner, + m_nOptsCacheLifeNotFound ); + } + } + // if the two net events below happen, something + // is going on to the connection so break the command flow + if ( ( e.getError() == DAVException::DAV_HTTP_TIMEOUT ) || + ( e.getError() == DAVException::DAV_HTTP_CONNECT ) ) + { + cancelCommandExecution( e, xEnv ); + // unreachable + } + + // cancel command execution is case that no user authentication data has been provided. + if ( e.getError() == DAVException::DAV_HTTP_NOAUTH ) + { + cancelCommandExecution( e, uno::Reference< ucb::XCommandEnvironment >() ); + } + } + } + else + { + rResAccess->resetUri(); + + // first check if the cached error can be mapped to DAVException::DAV_HTTP_TIMEOUT or mapped to DAVException::DAV_HTTP_CONNECT + if (aDAVOptions.getHttpResponseStatusCode() == USC_CONNECTION_TIMED_OUT + // can't get any reliable info without auth => cancel request + || aDAVOptions.getHttpResponseStatusCode() == USC_AUTH_FAILED + || aDAVOptions.getHttpResponseStatusCode() == USC_AUTHPROXY_FAILED) + { + // behave same as DAVException::DAV_HTTP_TIMEOUT or DAVException::DAV_HTTP_CONNECT was thrown + try + { + // extract host name and connection port + CurlUri theUri( rResAccess->getURL() ); + OUString aHostName = theUri.GetHost(); + sal_Int32 nPort = theUri.GetPort(); + DAVException::ExceptionCode e{}; + switch (aDAVOptions.getHttpResponseStatusCode()) + { + case USC_CONNECTION_TIMED_OUT: + e = DAVException::DAV_HTTP_TIMEOUT; + break; + case USC_AUTH_FAILED: + e = DAVException::DAV_HTTP_AUTH; + break; + case USC_AUTHPROXY_FAILED: + e = DAVException::DAV_HTTP_AUTHPROXY; + break; + default: + assert(false); + } + throw DAVException( e, + ConnectionEndPointString(aHostName, nPort) ); + } + catch ( DAVException& exp ) + { + cancelCommandExecution( exp, xEnv ); + } + } + + if ( aDAVOptions.getHttpResponseStatusCode() != SC_NOT_FOUND && + aDAVOptions.getHttpResponseStatusCode() != SC_GONE ) // the cached OPTIONS can have SC_GONE + { + eResourceType = NON_DAV; + } + else + { + //resource doesn't exist + if ( networkAccessAllowed != nullptr ) + *networkAccessAllowed = false; + } + } + } + + osl::MutexGuard g(m_aMutex); + if (m_eResourceType == UNKNOWN) { + m_eResourceType = eResourceType; + } else { + SAL_WARN_IF( + eResourceType != m_eResourceType, "ucb.ucp.webdav", + "different resource types for <" << rResAccess->getURL() << ">: " + << +eResourceType << " vs. " << +m_eResourceType); + } + SAL_INFO( "ucb.ucp.webdav", "m_eResourceType for <" << rResAccess->getURL() << ">: " << m_eResourceType ); + return m_eResourceType; +} + + +Content::ResourceType Content::getResourceType( + const uno::Reference< ucb::XCommandEnvironment >& xEnv ) +{ + std::unique_ptr< DAVResourceAccess > xResAccess; + { + osl::MutexGuard aGuard( m_aMutex ); + xResAccess.reset( new DAVResourceAccess( *m_xResAccess ) ); + } + const Content::ResourceType & ret = getResourceType( xEnv, xResAccess ); + { + osl::Guard< osl::Mutex > aGuard( m_aMutex ); + m_xResAccess.reset( new DAVResourceAccess( *xResAccess ) ); + } + return ret; +} + + +void Content::initOptsCacheLifeTime() +{ + // see description in + // officecfg/registry/schema/org/openoffice/Inet.xcs + // for use of these field values. + sal_uInt32 nAtime; + nAtime = officecfg::Inet::Settings::OptsCacheLifeImplWeb::get(); + m_nOptsCacheLifeImplWeb = std::max( sal_uInt32( 0 ), + std::min( nAtime, sal_uInt32( 3600 ) ) ); + + nAtime = officecfg::Inet::Settings::OptsCacheLifeDAV::get(); + m_nOptsCacheLifeDAV = std::max( sal_uInt32( 0 ), + std::min( nAtime, sal_uInt32( 3600 ) ) ); + + nAtime = officecfg::Inet::Settings::OptsCacheLifeDAVLocked::get(); + m_nOptsCacheLifeDAVLocked = std::max( sal_uInt32( 0 ), + std::min( nAtime, sal_uInt32( 3600 ) ) ); + + nAtime = officecfg::Inet::Settings::OptsCacheLifeNotImpl::get(); + m_nOptsCacheLifeNotImpl = std::max( sal_uInt32( 0 ), + std::min( nAtime, sal_uInt32( 43200 ) ) ); + + nAtime = officecfg::Inet::Settings::OptsCacheLifeNotFound::get(); + m_nOptsCacheLifeNotFound = std::max( sal_uInt32( 0 ), + std::min( nAtime, sal_uInt32( 30 ) ) ); +} + + +void Content::getResourceOptions( + const css::uno::Reference< css::ucb::XCommandEnvironment >& xEnv, + DAVOptions& rDAVOptions, + const std::unique_ptr< DAVResourceAccess > & rResAccess, + bool * networkAccessAllowed ) +{ + OUString aRedirURL; + OUString aTargetURL = rResAccess->getURL(); + DAVOptions aDAVOptions; + // first check if in cache, if not, then send method to server + if ( !aStaticDAVOptionsCache.getDAVOptions( aTargetURL, aDAVOptions ) ) + { + try + { + rResAccess->OPTIONS( aDAVOptions, xEnv ); + // IMPORTANT:the correctly implemented server will answer without errors, even if the resource is not present + sal_uInt32 nLifeTime = ( aDAVOptions.isClass1() || + aDAVOptions.isClass2() || + aDAVOptions.isClass3() ) ? + m_nOptsCacheLifeDAV : // a WebDAV site + m_nOptsCacheLifeImplWeb; // a site implementing OPTIONS but + // it's not DAV + // if resource is locked, will use a + // different lifetime + if( aDAVOptions.isLocked() ) + nLifeTime = m_nOptsCacheLifeDAVLocked; + + // check if redirected + aRedirURL = rResAccess->getURL(); + if( aRedirURL == aTargetURL) + { // no redirection + aRedirURL.clear(); + } + // cache this URL's option + aDAVOptions.setURL( aTargetURL ); + aDAVOptions.setRedirectedURL( aRedirURL ); + aStaticDAVOptionsCache.addDAVOptions( aDAVOptions, + nLifeTime ); + } + catch ( DAVException const & e ) + { + // first, remove from cache, will be added if needed, depending on the error received + aStaticDAVOptionsCache.removeDAVOptions( aTargetURL ); + rResAccess->resetUri(); + + aDAVOptions.setURL( aTargetURL ); + aDAVOptions.setRedirectedURL( aRedirURL ); + switch( e.getError() ) + { + case DAVException::DAV_HTTP_TIMEOUT: + case DAVException::DAV_HTTP_CONNECT: + { + // something bad happened to the connection + // not same as not found, this instead happens when the server doesn't exist or doesn't answer at all + // probably a new bit stating 'timed out' should be added to opts var? + // in any case abort the command + SAL_WARN( "ucb.ucp.webdav", "OPTIONS - DAVException: DAV_HTTP_TIMEOUT or DAV_HTTP_CONNECT for URL <" << m_xIdentifier->getContentIdentifier() << ">" ); + // cache the internal unofficial status code + + aDAVOptions.setHttpResponseStatusCode( USC_CONNECTION_TIMED_OUT ); + // used only internally, so the text doesn't really matter.. + aStaticDAVOptionsCache.addDAVOptions( aDAVOptions, + m_nOptsCacheLifeNotFound ); + if ( networkAccessAllowed != nullptr ) + { + *networkAccessAllowed = *networkAccessAllowed + && shouldAccessNetworkAfterException(e); + } + } + break; + case DAVException::DAV_HTTP_LOOKUP: + { + SAL_WARN( "ucb.ucp.webdav", "OPTIONS - DAVException: DAV_HTTP_LOOKUP for URL <" << m_xIdentifier->getContentIdentifier() << ">" ); + aDAVOptions.setHttpResponseStatusCode( USC_LOOKUP_FAILED ); + // used only internally, so the text doesn't really matter.. + aStaticDAVOptionsCache.addDAVOptions( aDAVOptions, + m_nOptsCacheLifeNotFound ); + if ( networkAccessAllowed != nullptr ) + { + *networkAccessAllowed = *networkAccessAllowed + && shouldAccessNetworkAfterException(e); + } + } + break; + case DAVException::DAV_HTTP_NOAUTH: + case DAVException::DAV_HTTP_AUTH: + { + SAL_WARN( "ucb.ucp.webdav", "OPTIONS - DAVException: DAV_HTTP_AUTH for URL <" << m_xIdentifier->getContentIdentifier() << ">" ); + // - the remote site is a WebDAV with special configuration: read/only for read operations + // and read/write for write operations, the user is not allowed to lock/write and + // she cancelled the credentials request. + // this is not actually an error, it means only that for current user this is a standard web, + // though possibly DAV enabled + aDAVOptions.setHttpResponseStatusCode( USC_AUTH_FAILED ); + // used only internally, so the text doesn't really matter.. + if (xEnv && xEnv->getInteractionHandler()) + { // only cache if there actually was a chance to request auth + aStaticDAVOptionsCache.addDAVOptions( aDAVOptions, + m_nOptsCacheLifeNotFound ); + } + if ( networkAccessAllowed != nullptr ) + { + *networkAccessAllowed = *networkAccessAllowed + && shouldAccessNetworkAfterException(e); + } + } + break; + case DAVException::DAV_HTTP_AUTHPROXY: + { + SAL_WARN( "ucb.ucp.webdav", "OPTIONS - DAVException: DAV_HTTP_AUTHPROXY for URL <" << m_xIdentifier->getContentIdentifier() << ">" ); + aDAVOptions.setHttpResponseStatusCode( USC_AUTHPROXY_FAILED ); + // used only internally, so the text doesn't really matter.. + aStaticDAVOptionsCache.addDAVOptions( aDAVOptions, + m_nOptsCacheLifeNotFound ); + if ( networkAccessAllowed != nullptr ) + { + *networkAccessAllowed = *networkAccessAllowed + && shouldAccessNetworkAfterException(e); + } + } + break; + case DAVException::DAV_HTTP_ERROR: + { + switch( e.getStatus() ) + { + case SC_FORBIDDEN: + { + SAL_WARN( "ucb.ucp.webdav","OPTIONS - SC_FORBIDDEN for URL <" << m_xIdentifier->getContentIdentifier() << ">" ); + // cache it, so OPTIONS won't be called again, this URL does not support it + aStaticDAVOptionsCache.addDAVOptions( aDAVOptions, + m_nOptsCacheLifeNotImpl ); + } + break; + case SC_BAD_REQUEST: + case SC_INTERNAL_SERVER_ERROR: + { + SAL_WARN( "ucb.ucp.webdav","OPTIONS - SC_BAD_REQUEST or SC_INTERNAL_SERVER_ERROR for URL <" << m_xIdentifier->getContentIdentifier() << ">, HTTP error: "<< e.getStatus() + << ", '" << e.getData() << "'" ); + // cache it, so OPTIONS won't be called again, this URL detect some problem while answering the method + aDAVOptions.setHttpResponseStatusCode( e.getStatus() ); + aDAVOptions.setHttpResponseStatusText( e.getData() ); + aStaticDAVOptionsCache.addDAVOptions( aDAVOptions, + m_nOptsCacheLifeNotFound ); + } + break; + case SC_NOT_IMPLEMENTED: + case SC_METHOD_NOT_ALLOWED: + { + // OPTIONS method must be implemented in DAV + // resource is NON_DAV, or not advertising it + SAL_WARN( "ucb.ucp.webdav","OPTIONS - SC_NOT_IMPLEMENTED or SC_METHOD_NOT_ALLOWED for URL <" << m_xIdentifier->getContentIdentifier() << ">, HTTP error: "<< e.getStatus() + << ", '" << e.getData() << "'" ); + // cache it, so OPTIONS won't be called again, this URL does not support it + aStaticDAVOptionsCache.addDAVOptions( aDAVOptions, + m_nOptsCacheLifeNotImpl ); + } + break; + case SC_NOT_FOUND: + { + // Apparently on IIS 10.0, if you disabled OPTIONS method, this error is the one reported, + // instead of SC_NOT_IMPLEMENTED or SC_METHOD_NOT_ALLOWED. + // So check if this is an available resource, or a real 'Not Found' event. + sal_uInt32 nLifeTime = m_nOptsCacheLifeNotFound; + if( isResourceAvailable( xEnv, rResAccess, aDAVOptions ) ) + { + SAL_WARN( "ucb.ucp.webdav", "OPTIONS - Got an SC_NOT_FOUND, but the URL <" << m_xIdentifier->getContentIdentifier() << "> resource exists" ); + nLifeTime = m_nOptsCacheLifeNotImpl; + } + else + { + SAL_WARN( "ucb.ucp.webdav", "OPTIONS - SC_NOT_FOUND for URL <" << m_xIdentifier->getContentIdentifier() << ">" ); + if ( networkAccessAllowed != nullptr ) + { + *networkAccessAllowed = *networkAccessAllowed + && shouldAccessNetworkAfterException(e); + } + } + aStaticDAVOptionsCache.addDAVOptions( aDAVOptions, + nLifeTime ); + } + break; + default: + { + SAL_WARN( "ucb.ucp.webdav", "OPTIONS - DAV_HTTP_ERROR, for URL <" << m_xIdentifier->getContentIdentifier() << ">, HTTP error: "<< e.getStatus() + << ", '" << e.getData() << "'" ); + aDAVOptions.setHttpResponseStatusCode( e.getStatus() ); + aDAVOptions.setHttpResponseStatusText( e.getData() ); + // cache it, so OPTIONS won't be called again, this URL does not support it + aStaticDAVOptionsCache.addDAVOptions( aDAVOptions, + m_nOptsCacheLifeNotImpl ); + } + break; + } + } + break; + // The 'DAVException::DAV_HTTP_REDIRECT' means we reached the maximum + // number of redirections, consider the resource type as UNKNOWN + // possibly a normal web site, not DAV + case DAVException::DAV_HTTP_REDIRECT: + default: + { + SAL_WARN( "ucb.ucp.webdav","OPTIONS - General DAVException (or max DAV_HTTP_REDIRECT reached) for URL <" << m_xIdentifier->getContentIdentifier() << ">, DAV ExceptionCode: " + << e.getError() << ", HTTP error: "<< e.getStatus() ); + aStaticDAVOptionsCache.addDAVOptions( aDAVOptions, + m_nOptsCacheLifeNotImpl ); + } + break; + } + } + } + else + { + // check current response status code, perhaps we need to set networkAccessAllowed + sal_uInt16 CachedResponseStatusCode = aDAVOptions.getHttpResponseStatusCode(); + if ( networkAccessAllowed != nullptr && + ( ( CachedResponseStatusCode == SC_NOT_FOUND ) || + ( CachedResponseStatusCode == SC_GONE ) || + ( CachedResponseStatusCode == USC_CONNECTION_TIMED_OUT ) || + ( CachedResponseStatusCode == USC_LOOKUP_FAILED ) || + ( CachedResponseStatusCode == USC_AUTH_FAILED ) || + ( CachedResponseStatusCode == USC_AUTHPROXY_FAILED ) + ) + ) + { + *networkAccessAllowed = *networkAccessAllowed && false; + } + } + rDAVOptions = aDAVOptions; +} + +//static +bool Content::isResourceAvailable( const css::uno::Reference< css::ucb::XCommandEnvironment >& xEnv, + const std::unique_ptr< DAVResourceAccess > & rResAccess, + DAVOptions& rDAVOptions ) +{ + std::vector< rtl::OUString > aHeaderNames; + DAVResource aResource; + + try + { + // To check for the physical URL resource availability, first + // try using a simple HEAD command + // if HEAD is successful, set element found. + rResAccess->HEAD( aHeaderNames, aResource, xEnv ); + rDAVOptions.setHttpResponseStatusCode( 0 ); + rDAVOptions.setHttpResponseStatusText(""); + return true; + } + catch ( DAVException const & e ) + { + if ( e.getError() == DAVException::DAV_HTTP_ERROR ) + { + if ( e.getStatus() == SC_NOT_IMPLEMENTED || + e.getStatus() == SC_METHOD_NOT_ALLOWED || + e.getStatus() == SC_NOT_FOUND ) + { + SAL_WARN( "ucb.ucp.webdav", "HEAD probably not implemented: fall back to a partial GET" ); + // set in cached OPTIONS "HEAD not implemented" + // so it won't be used again on this resource + rDAVOptions.setHeadAllowed( false ); + try + { + // do a GET with a payload of 0, the server does not + // support HEAD (or has HEAD disabled) + DAVRequestHeaders aPartialGet; + aPartialGet.push_back( + DAVRequestHeader( + OUString( "Range" ), + OUString( "bytes=0-0" ))); + + rResAccess->GET0( aPartialGet, + aHeaderNames, + aResource, + xEnv ); + return true; + } + catch ( DAVException const & ex ) + { + if ( ex.getError() == DAVException::DAV_HTTP_ERROR ) + { + rDAVOptions.setHttpResponseStatusCode( ex.getStatus() ); + rDAVOptions.setHttpResponseStatusText( ex.getData() ); + } + } + } + else + { + rDAVOptions.setHttpResponseStatusCode( e.getStatus() ); + rDAVOptions.setHttpResponseStatusText( e.getData() ); + } + } + return false; + } + catch ( ... ) + { + } + // set SC_NOT_IMPLEMENTED since at a minimum GET must be implemented in a basic Web server + rDAVOptions.setHttpResponseStatusCode( SC_NOT_IMPLEMENTED ); + rDAVOptions.setHttpResponseStatusText(""); + return false; +} + + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/ucb/source/ucp/webdav-curl/webdavcontent.hxx b/ucb/source/ucp/webdav-curl/webdavcontent.hxx new file mode 100644 index 000000000..a44eb1c99 --- /dev/null +++ b/ucb/source/ucp/webdav-curl/webdavcontent.hxx @@ -0,0 +1,305 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + + +#pragma once + +#include <memory> +#include <list> +#include <rtl/ref.hxx> +#include <com/sun/star/ucb/ContentCreationException.hpp> +#include <com/sun/star/ucb/XContentCreator.hpp> +#include <ucbhelper/contenthelper.hxx> +#include "DAVResourceAccess.hxx" +#include "PropertyMap.hxx" + +namespace com::sun::star::beans { + struct Property; + struct PropertyValue; +} + +namespace com::sun::star::io { + class XInputStream; +} + +namespace com::sun::star::sdbc { + class XRow; +} + +namespace com::sun::star::ucb { + struct OpenCommandArgument3; + struct PropertyCommandArgument; + struct PostCommandArgument2; + struct TransferInfo; +} + +namespace http_dav_ucp +{ + + +// UNO service name for the content. +inline constexpr OUStringLiteral WEBDAV_CONTENT_SERVICE_NAME = u"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 + NON_DAV, // the Web resource exists but it's not DAV + DAV, // the type of the Web resource is DAV with lock/unlock available + DAV_NOLOCK // the type of the Web resource is DAV with no lock/unlock available + }; + + std::unique_ptr< DAVResourceAccess > m_xResAccess; + std::unique_ptr< CachableContentProperties > m_xCachedProps; // locally cached props + OUString m_aEscapedTitle; + // resource type for general DAV methods + ResourceType m_eResourceType; + // resource type for general LOCK method only + ResourceType m_eResourceTypeForLocks; + ContentProvider* m_pProvider; // No need for a ref, base class holds object + bool m_bTransient; + bool const m_bCollection; + bool m_bDidGetOrHead; + std::vector< OUString > m_aFailedPropNames; + // Options Cache lifetime + // for web site implementing OPTIONS, but not dav + sal_uInt32 m_nOptsCacheLifeImplWeb; + // for WebDAV site where OPTIONS is mandatory + sal_uInt32 m_nOptsCacheLifeDAV; + // same as above, but when the resource is locked by us + sal_uInt32 m_nOptsCacheLifeDAVLocked; +// For web site not implementing OPTIONS + // during this time we assume the site doesn't turn to WebDAV + // but remains a simple Web + sal_uInt32 m_nOptsCacheLifeNotImpl; + // When resource is not found + // may be the resource is unavailable only briefly? + // so better have this small + sal_uInt32 m_nOptsCacheLifeNotFound; + + void initOptsCacheLifeTime(); + +private: + virtual css::uno::Sequence< css::beans::Property > + getProperties( const css::uno::Reference< css::ucb::XCommandEnvironment > & xEnv ) override; + virtual css::uno::Sequence< css::ucb::CommandInfo > + getCommands( const css::uno::Reference< css::ucb::XCommandEnvironment > & xEnv ) override; + virtual OUString getParentURL() override; + + /// @throws css::uno::Exception + bool isFolder( const css::uno::Reference< css::ucb::XCommandEnvironment >& xEnv ); + + /// @throws css::uno::Exception + css::uno::Reference< css::sdbc::XRow > + getPropertyValues( const css::uno::Sequence< css::beans::Property >& rProperties, + const css::uno::Reference< css::ucb::XCommandEnvironment >& xEnv ); + + /// @throws css::uno::Exception + css::uno::Sequence< css::uno::Any > + setPropertyValues( const css::uno::Sequence< css::beans::PropertyValue >& rValues, + const css::uno::Reference< css::ucb::XCommandEnvironment >& xEnv ); + + typedef rtl::Reference< Content > ContentRef; + typedef std::vector< ContentRef > ContentRefList; + void queryChildren( ContentRefList& rChildren); + + bool + exchangeIdentity( const css::uno::Reference< css::ucb::XContentIdentifier >& xNewId ); + + OUString + getBaseURI( const std::unique_ptr< DAVResourceAccess > & rResAccess ); + + /// @throws css::uno::Exception + ResourceType + getResourceType( const css::uno::Reference< css::ucb::XCommandEnvironment >& xEnv ); + + /// @throws css::uno::Exception + ResourceType + getResourceType( const css::uno::Reference< css::ucb::XCommandEnvironment >& xEnv, + const std::unique_ptr< DAVResourceAccess > & rResAccess, + bool * networkAccessAllowed = nullptr ); + + // Command "open" + /// @throws css::uno::Exception + css::uno::Any open( + const css::ucb::OpenCommandArgument3 & rArg, + const css::uno::Reference< + css::ucb::XCommandEnvironment > & xEnv ); + + // Command "post" + /// @throws css::uno::Exception + void post( const css::ucb::PostCommandArgument2 & rArg, + const css::uno::Reference< css::ucb::XCommandEnvironment > & xEnv ); + + // Command "insert" + /// @throws css::uno::Exception + void insert( const css::uno::Reference< css::io::XInputStream > & xInputStream, + bool bReplaceExisting, + const css::uno::Reference< css::ucb::XCommandEnvironment >& Environment ); + + // Command "transfer" + /// @throws css::uno::Exception + void transfer( const css::ucb::TransferInfo & rArgs, + const css::uno::Reference< css::ucb::XCommandEnvironment >& Environment ); + + // Command "delete" + /// @throws css::uno::Exception + void destroy( bool bDeletePhysical ); + + // Command "lock" + /// @throws css::uno::Exception + void lock( const css::uno::Reference< css::ucb::XCommandEnvironment >& Environment ); + + // Command "unlock" + /// @throws css::uno::Exception + void unlock( const css::uno::Reference< css::ucb::XCommandEnvironment >& Environment ); + + css::uno::Any MapDAVException( const DAVException & e, + bool bWrite ); + /// @throws css::uno::Exception + void cancelCommandExecution( + const DAVException & e, + const css::uno::Reference< css::ucb::XCommandEnvironment > & xEnv, + bool bWrite = false ); + + static bool shouldAccessNetworkAfterException( const DAVException & e ); + + ResourceType resourceTypeForLocks( + const css::uno::Reference< css::ucb::XCommandEnvironment >& rEnvironment, + const std::unique_ptr< DAVResourceAccess > & rResAccess ); + + ResourceType resourceTypeForLocks( + const css::uno::Reference< css::ucb::XCommandEnvironment >& rEnvironment ); + + // XPropertyContainer replacement + /// @throws css::beans::PropertyExistException + /// @throws css::beans::IllegalTypeException + /// @throws css::lang::IllegalArgumentException + /// @throws css::uno::RuntimeException + void addProperty( const css::ucb::PropertyCommandArgument &aCmdArg, + const css::uno::Reference< css::ucb::XCommandEnvironment >& Environment ); + + /// @throws css::beans::PropertyExistException + /// @throws css::beans::NotRemoveableException + /// @throws css::uno::RuntimeException + void removeProperty( const OUString& Name, + const css::uno::Reference< css::ucb::XCommandEnvironment >& Environment ); +public: + /// @throws css::ucb::ContentCreationException + Content( const css::uno::Reference< css::uno::XComponentContext >& rxContext, + ContentProvider* pProvider, + const css::uno::Reference< css::ucb::XContentIdentifier >& Identifier, + rtl::Reference< DAVSessionFactory > const & rSessionFactory ); + /// @throws css::ucb::ContentCreationException + Content( const css::uno::Reference< css::uno::XComponentContext >& rxContext, + ContentProvider* pProvider, + const css::uno::Reference< css::ucb::XContentIdentifier >& Identifier, + rtl::Reference< DAVSessionFactory > const & rSessionFactory, + bool isCollection ); + virtual ~Content() override; + + // XInterface + virtual css::uno::Any SAL_CALL queryInterface( const css::uno::Type & rType ) override; + virtual void SAL_CALL acquire() + noexcept override; + virtual void SAL_CALL release() + noexcept override; + + // XTypeProvider + virtual css::uno::Sequence< sal_Int8 > SAL_CALL getImplementationId() override; + virtual css::uno::Sequence< css::uno::Type > SAL_CALL getTypes() override; + + // XServiceInfo + virtual OUString SAL_CALL + getImplementationName() override; + + virtual css::uno::Sequence< OUString > SAL_CALL + getSupportedServiceNames() override; + + // XContent + virtual OUString SAL_CALL + getContentType() override; + + // XCommandProcessor + virtual css::uno::Any SAL_CALL + execute( const css::ucb::Command& aCommand, + sal_Int32 CommandId, + const css::uno::Reference< css::ucb::XCommandEnvironment >& Environment ) override; + virtual void SAL_CALL + abort( sal_Int32 CommandId ) override; + + // XPropertyContainer + virtual void SAL_CALL + addProperty( const OUString& Name, + sal_Int16 Attributes, + const css::uno::Any& DefaultValue ) override; + + virtual void SAL_CALL + removeProperty( const OUString& Name ) override; + + + // Additional interfaces + + + // XContentCreator + virtual css::uno::Sequence< css::ucb::ContentInfo > SAL_CALL + queryCreatableContentsInfo() override; + virtual css::uno::Reference< css::ucb::XContent > SAL_CALL + createNewContent( const css::ucb::ContentInfo& Info ) override; + + + // Non-interface methods. + + + DAVResourceAccess & getResourceAccess() { return *m_xResAccess; } + + // Called from resultset data supplier. + static css::uno::Reference< css::sdbc::XRow > + getPropertyValues( const css::uno::Reference< css::uno::XComponentContext >& rContext, + const css::uno::Sequence< css::beans::Property >& rProperties, + const ContentProperties& rData, + const rtl::Reference< ::ucbhelper::ContentProviderImplHelper >& rProvider, + const OUString& rContentId ); + + /// Use OPTIONS method to retrieve the type of the Web resource + /// @throws css::uno::Exception + void getResourceOptions( const css::uno::Reference< css::ucb::XCommandEnvironment >& xEnv, + DAVOptions& rDAVOptions, + const std::unique_ptr< DAVResourceAccess > & rResAccess, + bool * networkAccessAllowed = nullptr); + + static bool isResourceAvailable( const css::uno::Reference< css::ucb::XCommandEnvironment >& xEnv, + const std::unique_ptr< DAVResourceAccess > & rResAccess, + DAVOptions& rDAVOptions ); + + static void removeCachedPropertyNames( const OUString & rURL ); + +}; + +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/ucb/source/ucp/webdav-curl/webdavcontentcaps.cxx b/ucb/source/ucp/webdav-curl/webdavcontentcaps.cxx new file mode 100644 index 000000000..a7e2bb968 --- /dev/null +++ b/ucb/source/ucp/webdav-curl/webdavcontentcaps.cxx @@ -0,0 +1,636 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include <memory> +#include <set> +#include <com/sun/star/beans/Property.hpp> +#include <com/sun/star/beans/PropertyAttribute.hpp> +#include <com/sun/star/beans/PropertyValue.hpp> +#include <com/sun/star/ucb/CommandInfo.hpp> +#include <com/sun/star/ucb/ContentInfo.hpp> +#include <com/sun/star/ucb/OpenCommandArgument2.hpp> +#include <com/sun/star/ucb/InsertCommandArgument.hpp> +#include <com/sun/star/ucb/PostCommandArgument2.hpp> +#include <com/sun/star/ucb/PropertyCommandArgument.hpp> +#include <com/sun/star/ucb/TransferInfo.hpp> +#include <com/sun/star/uno/Sequence.hxx> +#include <com/sun/star/util/DateTime.hpp> +#include <com/sun/star/ucb/Lock.hpp> +#include <com/sun/star/ucb/LockEntry.hpp> +#include "webdavcontent.hxx" +#include "webdavprovider.hxx" +#include "DAVProperties.hxx" +#include "ContentProperties.hxx" +#include "PropfindCache.hxx" + +using namespace com::sun::star; +using namespace http_dav_ucp; + + +// ContentProvider implementation. + + +bool ContentProvider::getProperty( + const OUString & rPropName, beans::Property & rProp ) +{ + if ( !m_pProps ) + { + osl::MutexGuard aGuard( m_aMutex ); + if ( !m_pProps ) + { + m_pProps = std::make_unique<PropertyMap>(); + + + // Fill map of known properties... + + + // Mandatory UCB properties. + m_pProps->insert( + beans::Property( + "ContentType", + -1, + cppu::UnoType<OUString>::get(), + beans::PropertyAttribute::BOUND + | beans::PropertyAttribute::READONLY ) ); + + m_pProps->insert( + beans::Property( + "IsDocument", + -1, + cppu::UnoType<bool>::get(), + beans::PropertyAttribute::BOUND + | beans::PropertyAttribute::READONLY ) ); + + m_pProps->insert( + beans::Property( + "IsFolder", + -1, + cppu::UnoType<bool>::get(), + beans::PropertyAttribute::BOUND + | beans::PropertyAttribute::READONLY ) ); + + m_pProps->insert( + beans::Property( + "Title", + -1, + cppu::UnoType<OUString>::get(), + beans::PropertyAttribute::BOUND ) ); + + // Optional UCB properties. + + m_pProps->insert( + beans::Property( + "DateCreated", + -1, + cppu::UnoType<util::DateTime>::get(), + beans::PropertyAttribute::BOUND + | beans::PropertyAttribute::READONLY ) ); + + m_pProps->insert( + beans::Property( + "DateModified", + -1, + cppu::UnoType<util::DateTime>::get(), + beans::PropertyAttribute::BOUND + | beans::PropertyAttribute::READONLY ) ); + + m_pProps->insert( + beans::Property( + "MediaType", + -1, + cppu::UnoType<OUString>::get(), + beans::PropertyAttribute::BOUND + | beans::PropertyAttribute::READONLY ) ); + + m_pProps->insert( + beans::Property( + "Size", + -1, + cppu::UnoType<sal_Int64>::get(), + beans::PropertyAttribute::BOUND + | beans::PropertyAttribute::READONLY ) ); + + m_pProps->insert( + beans::Property( + "BaseURI", + -1, + cppu::UnoType<OUString>::get(), + beans::PropertyAttribute::BOUND + | beans::PropertyAttribute::READONLY ) ); + + m_pProps->insert( + beans::Property( + "CreatableContentsInfo", + -1, + cppu::UnoType<uno::Sequence< ucb::ContentInfo >>::get(), + beans::PropertyAttribute::BOUND + | beans::PropertyAttribute::READONLY ) ); + + // Standard DAV properties. + + m_pProps->insert( + beans::Property( + DAVProperties::CREATIONDATE, + -1, + cppu::UnoType<OUString>::get(), + beans::PropertyAttribute::BOUND + | beans::PropertyAttribute::READONLY ) ); + + m_pProps->insert( + beans::Property( + DAVProperties::DISPLAYNAME, + -1, + cppu::UnoType<OUString>::get(), + beans::PropertyAttribute::BOUND ) ); + + m_pProps->insert( + beans::Property( + DAVProperties::GETCONTENTLANGUAGE, + -1, + cppu::UnoType<OUString>::get(), + beans::PropertyAttribute::BOUND + | beans::PropertyAttribute::READONLY ) ); + + m_pProps->insert( + beans::Property( + DAVProperties::GETCONTENTLENGTH, + -1, + cppu::UnoType<OUString>::get(), + beans::PropertyAttribute::BOUND + | beans::PropertyAttribute::READONLY ) ); + + m_pProps->insert( + beans::Property( + DAVProperties::GETCONTENTTYPE , + -1, + cppu::UnoType<OUString>::get(), + beans::PropertyAttribute::BOUND + | beans::PropertyAttribute::READONLY ) ); + + m_pProps->insert( + beans::Property( + DAVProperties::GETETAG, + -1, + cppu::UnoType<OUString>::get(), + beans::PropertyAttribute::BOUND + | beans::PropertyAttribute::READONLY ) ); + + m_pProps->insert( + beans::Property( + DAVProperties::GETLASTMODIFIED, + -1, + cppu::UnoType<OUString>::get(), + beans::PropertyAttribute::BOUND + | beans::PropertyAttribute::READONLY ) ); + + m_pProps->insert( + beans::Property( + DAVProperties::LOCKDISCOVERY, + -1, + cppu::UnoType<uno::Sequence< ucb::Lock >>::get(), + beans::PropertyAttribute::BOUND + | beans::PropertyAttribute::READONLY ) ); + + m_pProps->insert( + beans::Property( + DAVProperties::RESOURCETYPE, + -1, + cppu::UnoType<OUString>::get(), + beans::PropertyAttribute::BOUND + | beans::PropertyAttribute::READONLY ) ); + + m_pProps->insert( + beans::Property( + DAVProperties::SUPPORTEDLOCK, + -1, + cppu::UnoType<uno::Sequence< ucb::LockEntry >>::get(), + beans::PropertyAttribute::BOUND + | beans::PropertyAttribute::READONLY ) ); + + m_pProps->insert( + beans::Property( + DAVProperties::EXECUTABLE, + -1, + cppu::UnoType<OUString>::get(), + beans::PropertyAttribute::BOUND ) ); + } + } + + + // Lookup property. + + + beans::Property aProp; + aProp.Name = rPropName; + const PropertyMap::const_iterator it = m_pProps->find( aProp ); + if ( it != m_pProps->end() ) + { + rProp = *it; + } + else + { + // All unknown props are treated as: + rProp = beans::Property( + rPropName, + - 1, + cppu::UnoType<OUString>::get(), + beans::PropertyAttribute::BOUND ); + } + + return true; +} + + +static PropertyNamesCache aStaticPropertyNamesCache; + +// static +void Content::removeCachedPropertyNames( const OUString & rURL ) +{ + aStaticPropertyNamesCache.removeCachedPropertyNames( rURL ); +} + +// Content implementation. + + +// virtual +uno::Sequence< beans::Property > Content::getProperties( + const uno::Reference< ucb::XCommandEnvironment > & xEnv ) +{ + bool bTransient; + std::unique_ptr< DAVResourceAccess > xResAccess; + std::unique_ptr< ContentProperties > xCachedProps; + rtl::Reference< ContentProvider > xProvider; + + { + osl::Guard< osl::Mutex > aGuard( m_aMutex ); + + bTransient = m_bTransient; + xResAccess.reset( new DAVResourceAccess( *m_xResAccess ) ); + if (m_xCachedProps) + xCachedProps.reset( + new ContentProperties( *m_xCachedProps ) ); + xProvider.set( m_pProvider ); + } + + std::set< OUString > aPropSet; + + // No server access for just created (not yet committed) objects. + // Only a minimal set of properties supported at this stage. + if ( !bTransient ) + { + // Obtain all properties supported for this resource from server. + DAVOptions aDAVOptions; + getResourceOptions( xEnv, aDAVOptions, xResAccess ); + // only Class 1 is needed for PROPFIND + if ( aDAVOptions.isClass1() ) + { + try + { + std::vector< DAVResourceInfo > props; + OUString aTheURL( xResAccess->getURL() ); + PropertyNames aPropsNames( aTheURL ); + + if( !aStaticPropertyNamesCache.getCachedPropertyNames( aTheURL, aPropsNames ) ) + { + + xResAccess->PROPFIND( DAVZERO, props, xEnv ); + aPropsNames.setPropertiesNames( props ); + + aStaticPropertyNamesCache.addCachePropertyNames( aPropsNames, 10 ); + } + else + { + props = aPropsNames.getPropertiesNames(); + } + + // Note: vector should contain exactly one resource info, because + // we used a depth of DAVZERO for PROPFIND. + if (props.size() == 1) + { + aPropSet.insert( (*props.begin()).properties.begin(), + (*props.begin()).properties.end() ); + } + } + catch ( DAVException const & ) + { + } + } + } + + // Add DAV properties, map DAV properties to UCB properties. + bool bHasCreationDate = false; // creationdate <-> DateCreated + bool bHasGetLastModified = false; // getlastmodified <-> DateModified + bool bHasGetContentType = false; // getcontenttype <-> MediaType + bool bHasGetContentLength = false; // getcontentlength <-> Size + + bool bHasContentType = false; + bool bHasIsDocument = false; + bool bHasIsFolder = false; + bool bHasTitle = false; + bool bHasBaseURI = false; + bool bHasDateCreated = false; + bool bHasDateModified = false; + bool bHasMediaType = false; + bool bHasSize = false; + bool bHasCreatableInfos = false; + + { + for ( const auto& rProp : aPropSet ) + { + if ( !bHasCreationDate && + ( rProp == DAVProperties::CREATIONDATE ) ) + { + bHasCreationDate = true; + } + else if ( !bHasGetLastModified && + ( rProp == DAVProperties::GETLASTMODIFIED ) ) + { + bHasGetLastModified = true; + } + else if ( !bHasGetContentType && + ( rProp == DAVProperties::GETCONTENTTYPE ) ) + { + bHasGetContentType = true; + } + else if ( !bHasGetContentLength && + ( rProp == DAVProperties::GETCONTENTLENGTH ) ) + { + bHasGetContentLength = true; + } + else if ( !bHasContentType && rProp == "ContentType" ) + { + bHasContentType = true; + } + else if ( !bHasIsDocument && rProp == "IsDocument" ) + { + bHasIsDocument = true; + } + else if ( !bHasIsFolder && rProp == "IsFolder" ) + { + bHasIsFolder = true; + } + else if ( !bHasTitle && rProp == "Title" ) + { + bHasTitle = true; + } + else if ( !bHasBaseURI && rProp == "BaseURI" ) + { + bHasBaseURI = true; + } + else if ( !bHasDateCreated && rProp == "DateCreated" ) + { + bHasDateCreated = true; + } + else if ( !bHasDateModified && rProp == "DateModified" ) + { + bHasDateModified = true; + } + else if ( !bHasMediaType && rProp == "MediaType" ) + { + bHasMediaType = true; + } + else if ( !bHasSize && rProp == "Size" ) + { + bHasSize = true; + } + else if ( !bHasCreatableInfos && rProp == "CreatableContentsInfo" ) + { + bHasCreatableInfos = true; + } + } + } + + // Add mandatory properties. + if ( !bHasContentType ) + aPropSet.insert( + OUString( "ContentType" ) ); + + if ( !bHasIsDocument ) + aPropSet.insert( + OUString( "IsDocument" ) ); + + if ( !bHasIsFolder ) + aPropSet.insert( + OUString( "IsFolder" ) ); + + if ( !bHasTitle ) + { + // Always present since it can be calculated from content's URI. + aPropSet.insert( + OUString( "Title" ) ); + } + + // Add optional properties. + + if ( !bHasBaseURI ) + { + // Always present since it can be calculated from content's URI. + aPropSet.insert( + OUString( "BaseURI" ) ); + } + + if ( !bHasDateCreated && bHasCreationDate ) + aPropSet.insert( + OUString( "DateCreated" ) ); + + if ( !bHasDateModified && bHasGetLastModified ) + aPropSet.insert( + OUString( "DateModified" ) ); + + if ( !bHasMediaType && bHasGetContentType ) + aPropSet.insert( + OUString( "MediaType" ) ); + + if ( !bHasSize && bHasGetContentLength ) + aPropSet.insert( + OUString( "Size" ) ); + + if ( !bHasCreatableInfos ) + aPropSet.insert( + OUString( + "CreatableContentsInfo" ) ); + + // Add cached properties, if present and still missing. + if (xCachedProps) + { + const std::unique_ptr< PropertyValueMap > & xProps + = xCachedProps->getProperties(); + + for ( const auto& rEntry : *xProps ) + aPropSet.insert( rEntry.first ); + } + + // std::set -> uno::Sequence + sal_Int32 nCount = aPropSet.size(); + uno::Sequence< beans::Property > aProperties( nCount ); + auto aPropertiesRange = asNonConstRange(aProperties); + + beans::Property aProp; + sal_Int32 n = 0; + + for ( const auto& rProp : aPropSet ) + { + xProvider->getProperty( rProp, aProp ); + aPropertiesRange[ n++ ] = aProp; + } + + return aProperties; +} + + +// virtual +uno::Sequence< ucb::CommandInfo > Content::getCommands( + const uno::Reference< ucb::XCommandEnvironment > & xEnv ) +{ + osl::Guard< osl::Mutex > aGuard( m_aMutex ); + + uno::Sequence< ucb::CommandInfo > aCmdInfo( 10 ); + auto pCmdInfo = aCmdInfo.getArray(); + + + // Mandatory commands + + + pCmdInfo[ 0 ] = + ucb::CommandInfo( + "getCommandInfo", + -1, + cppu::UnoType<void>::get() ); + pCmdInfo[ 1 ] = + ucb::CommandInfo( + "getPropertySetInfo", + -1, + cppu::UnoType<void>::get() ); + pCmdInfo[ 2 ] = + ucb::CommandInfo( + "getPropertyValues", + -1, + cppu::UnoType<uno::Sequence< beans::Property >>::get()); + pCmdInfo[ 3 ] = + ucb::CommandInfo( + "setPropertyValues", + -1, + cppu::UnoType<uno::Sequence< beans::PropertyValue >>::get()); + + + // Optional standard commands + + + pCmdInfo[ 4 ] = + ucb::CommandInfo( + "delete", + -1, + cppu::UnoType<bool>::get() ); + pCmdInfo[ 5 ] = + ucb::CommandInfo( + "insert", + -1, + cppu::UnoType<ucb::InsertCommandArgument>::get() ); + pCmdInfo[ 6 ] = + ucb::CommandInfo( + "open", + -1, + cppu::UnoType<ucb::OpenCommandArgument2>::get() ); + + + // New commands + + + pCmdInfo[ 7 ] = + ucb::CommandInfo( + "post", + -1, + cppu::UnoType<ucb::PostCommandArgument2>::get() ); + pCmdInfo[ 8 ] = + ucb::CommandInfo( + "addProperty", + -1, + cppu::UnoType<ucb::PropertyCommandArgument>::get() ); + pCmdInfo[ 9 ] = + ucb::CommandInfo( + "removeProperty", + -1, + cppu::UnoType<OUString>::get() ); + + bool bFolder = false; + + try + { + bFolder = isFolder( xEnv ); + } + catch ( uno::Exception const & ) + { + return aCmdInfo; + } + + ResourceType eType = resourceTypeForLocks( xEnv ); + bool bSupportsLocking = ( eType == NOT_FOUND || eType == DAV ); + + sal_Int32 nPos = aCmdInfo.getLength(); + sal_Int32 nMoreCmds = ( bFolder ? 2 : 0 ) + ( bSupportsLocking ? 2 : 0 ); + if ( nMoreCmds ) + aCmdInfo.realloc( nPos + nMoreCmds ); + else + return aCmdInfo; + + pCmdInfo = aCmdInfo.getArray(); + + if ( bFolder ) + { + + // Optional standard commands + + + pCmdInfo[ nPos ] = + ucb::CommandInfo( + "transfer", + -1, + cppu::UnoType<ucb::TransferInfo>::get() ); + nPos++; + pCmdInfo[ nPos ] = + ucb::CommandInfo( + "createNewContent", + -1, + cppu::UnoType<ucb::ContentInfo>::get() ); + nPos++; + } + else + { + // no document-only commands at the moment. + } + + if ( bSupportsLocking ) + { + pCmdInfo[ nPos ] = + ucb::CommandInfo( + "lock", + -1, + cppu::UnoType<void>::get() ); + nPos++; + pCmdInfo[ nPos ] = + ucb::CommandInfo( + "unlock", + -1, + cppu::UnoType<void>::get() ); + nPos++; + } + return aCmdInfo; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/ucb/source/ucp/webdav-curl/webdavdatasupplier.cxx b/ucb/source/ucp/webdav-curl/webdavdatasupplier.cxx new file mode 100644 index 000000000..c5a008af8 --- /dev/null +++ b/ucb/source/ucp/webdav-curl/webdavdatasupplier.cxx @@ -0,0 +1,444 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include <sal/config.h> +#include <sal/log.hxx> + +#include <utility> + +#include <rtl/ustrbuf.hxx> +#include <com/sun/star/ucb/OpenMode.hpp> +#include <ucbhelper/contentidentifier.hxx> +#include <ucbhelper/providerhelper.hxx> +#include "webdavdatasupplier.hxx" +#include "webdavcontent.hxx" +#include "DAVProperties.hxx" +#include "CurlUri.hxx" +#include <com/sun/star/ucb/IllegalIdentifierException.hpp> +#include <com/sun/star/ucb/ResultSetException.hpp> +#include <tools/diagnose_ex.h> + +using namespace com::sun::star; +using namespace http_dav_ucp; + +namespace http_dav_ucp +{ + +namespace { + +auto DumpResources(std::vector<DAVResource> const& rResources) -> OUString +{ + OUStringBuffer buf; + for (auto const& rResource : rResources) + { + buf.append("resource URL: <"); + buf.append(rResource.uri); + try { + CurlUri const uri(rResource.uri); + buf.append("> parsed URL: <"); + buf.append(DecodeURI(uri.GetRelativeReference())); + buf.append("> "); + } catch (...) { + // parsing uri could fail + buf.append("> parsing URL failed! "); + } + buf.append("properties: "); + for (auto const& it : rResource.properties) + { + buf.append("\""); + buf.append(it.Name); + buf.append("\" "); + } + buf.append("\n"); + } + buf.stripEnd('\n'); // the last newline is superfluous, remove it + return buf.makeStringAndClear(); +} + +} + +} + + +// DataSupplier Implementation. + + +DataSupplier::DataSupplier( + const uno::Reference< uno::XComponentContext >& rxContext, + const rtl::Reference< Content >& rContent, + sal_Int32 nOpenMode ) + : m_xContent( rContent ), m_xContext( rxContext ), m_nOpenMode( nOpenMode ), + m_bCountFinal( false ), m_bThrowException( false ) +{ +} + + +// virtual +DataSupplier::~DataSupplier() +{} + + +// virtual +OUString DataSupplier::queryContentIdentifierString( sal_uInt32 nIndex ) +{ + osl::Guard< osl::Mutex > aGuard( m_aMutex ); + + if (nIndex < m_Results.size()) + { + OUString aId = m_Results[ nIndex ]->aId; + if ( aId.getLength() ) + { + // Already cached. + return aId; + } + } + + if ( getResult( nIndex ) ) + { + OUString aId = m_xContent->getResourceAccess().getURL(); + + const ContentProperties& props(*(m_Results[ nIndex ]->pData)); + + if ( ( aId.lastIndexOf( '/' ) + 1 ) != aId.getLength() ) + aId += "/"; + + aId += props.getEscapedTitle(); + + if ( props.isTrailingSlash() ) + aId += "/"; + + m_Results[ nIndex ]->aId = aId; + return aId; + } + return OUString(); +} + + +// virtual +uno::Reference< ucb::XContentIdentifier > +DataSupplier::queryContentIdentifier( sal_uInt32 nIndex ) +{ + osl::Guard< osl::Mutex > aGuard( m_aMutex ); + + if (nIndex < m_Results.size()) + { + uno::Reference< ucb::XContentIdentifier > xId + = m_Results[ nIndex ]->xId; + if ( xId.is() ) + { + // Already cached. + return xId; + } + } + + OUString aId = queryContentIdentifierString( nIndex ); + if ( aId.getLength() ) + { + uno::Reference< ucb::XContentIdentifier > xId + = new ::ucbhelper::ContentIdentifier( aId ); + m_Results[ nIndex ]->xId = xId; + return xId; + } + return uno::Reference< ucb::XContentIdentifier >(); +} + + +// virtual +uno::Reference< ucb::XContent > +DataSupplier::queryContent( sal_uInt32 nIndex ) +{ + osl::Guard< osl::Mutex > aGuard( m_aMutex ); + + if (nIndex < m_Results.size()) + { + uno::Reference< ucb::XContent > xContent + = m_Results[ nIndex ]->xContent; + if ( xContent.is() ) + { + // Already cached. + return xContent; + } + } + + uno::Reference< ucb::XContentIdentifier > xId + = queryContentIdentifier( nIndex ); + if ( xId.is() ) + { + try + { + uno::Reference< ucb::XContent > xContent + = m_xContent->getProvider()->queryContent( xId ); + m_Results[ nIndex ]->xContent = xContent; + return xContent; + + } + catch ( ucb::IllegalIdentifierException& ) + { + } + } + return uno::Reference< ucb::XContent >(); +} + + +// virtual +bool DataSupplier::getResult( sal_uInt32 nIndex ) +{ + osl::ClearableGuard< osl::Mutex > aGuard( m_aMutex ); + + if (nIndex < m_Results.size()) + { + // Result already present. + return true; + } + + // Obtain values... + if ( getData() ) + { + if (nIndex < m_Results.size()) + { + // Result already present. + return true; + } + } + + return false; +} + + +// virtual +sal_uInt32 DataSupplier::totalCount() +{ + // Obtain values... + getData(); + + return m_Results.size(); +} + + +// virtual +sal_uInt32 DataSupplier::currentCount() +{ + return m_Results.size(); +} + + +// virtual +bool DataSupplier::isCountFinal() +{ + return m_bCountFinal; +} + + +// virtual +uno::Reference< sdbc::XRow > DataSupplier::queryPropertyValues( + sal_uInt32 nIndex ) +{ + osl::Guard< osl::Mutex > aGuard( m_aMutex ); + + if (nIndex < m_Results.size()) + { + uno::Reference< sdbc::XRow > xRow = m_Results[ nIndex ]->xRow; + if ( xRow.is() ) + { + // Already cached. + return xRow; + } + } + + if ( getResult( nIndex ) ) + { + uno::Reference< sdbc::XRow > xRow + = Content::getPropertyValues( + m_xContext, + getResultSet()->getProperties(), + *(m_Results[ nIndex ]->pData), + m_xContent->getProvider(), + queryContentIdentifierString( nIndex ) ); + m_Results[ nIndex ]->xRow = xRow; + return xRow; + } + + return uno::Reference< sdbc::XRow >(); +} + + +// virtual +void DataSupplier::releasePropertyValues( sal_uInt32 nIndex ) +{ + osl::Guard< osl::Mutex > aGuard( m_aMutex ); + + if (nIndex < m_Results.size()) + m_Results[ nIndex ]->xRow.clear(); +} + + +// virtual +void DataSupplier::close() +{ +} + + +// virtual +void DataSupplier::validate() +{ + if ( m_bThrowException ) + throw ucb::ResultSetException(); +} + +bool DataSupplier::getData() +{ + osl::ClearableGuard< osl::Mutex > aGuard( m_aMutex ); + + if ( !m_bCountFinal ) + { + std::vector< OUString > propertyNames; + ContentProperties::UCBNamesToDAVNames( + getResultSet()->getProperties(), propertyNames ); + + // Append "resourcetype", if not already present. It's value is + // needed to get a valid ContentProperties::pIsFolder value, which + // is needed for OpenMode handling. + + bool isNoResourceType = std::none_of(propertyNames.begin(), propertyNames.end(), + [](const OUString& rPropName) { return rPropName.equals(DAVProperties::RESOURCETYPE); }); + + if ( isNoResourceType ) + propertyNames.push_back( DAVProperties::RESOURCETYPE ); + + std::vector< DAVResource > resources; + try + { + // propfind depth 1, get property values for parent AND for each + // child + m_xContent->getResourceAccess() + .PROPFIND( DAVONE, + propertyNames, + resources, + getResultSet()->getEnvironment() ); + SAL_INFO("ucb.ucp.webdav", "getData() - " << DumpResources(resources)); + } + catch ( DAVException & ) + { + TOOLS_WARN_EXCEPTION( "ucb.ucp.webdav", "PROPFIND : DAVException" ); + m_bThrowException = true; + } + + if ( !m_bThrowException ) + { + try + { + CurlUri const aURI( + m_xContent->getResourceAccess().getURL() ); + OUString aPath = aURI.GetPath(); + + if ( aPath.endsWith("/") ) + aPath = aPath.copy( 0, aPath.getLength() - 1 ); + + aPath = DecodeURI(aPath); + bool bFoundParent = false; + + for ( size_t n = 0; n < resources.size(); ++n ) + { + const DAVResource & rRes = resources[ n ]; + + // Filter parent, which is contained somewhere(!) in + // the vector. + if ( !bFoundParent ) + { + try + { + CurlUri const aCurrURI( rRes.uri ); + OUString aCurrPath = aCurrURI.GetPath(); + if ( aCurrPath.endsWith("/") ) + aCurrPath + = aCurrPath.copy( + 0, + aCurrPath.getLength() - 1 ); + + aCurrPath = DecodeURI(aCurrPath); + if ( aPath == aCurrPath ) + { + bFoundParent = true; + continue; + } + } + catch ( DAVException const & ) + { + // do nothing, ignore error. continue. + } + } + + std::unique_ptr<ContentProperties> pContentProperties + = std::make_unique<ContentProperties>( rRes ); + + // Check resource against open mode. + switch ( m_nOpenMode ) + { + case ucb::OpenMode::FOLDERS: + { + bool bFolder = false; + + const uno::Any & rValue + = pContentProperties->getValue( "IsFolder" ); + rValue >>= bFolder; + + if ( !bFolder ) + continue; + + break; + } + + case ucb::OpenMode::DOCUMENTS: + { + bool bDocument = false; + + const uno::Any & rValue + = pContentProperties->getValue( "IsDocument" ); + rValue >>= bDocument; + + if ( !bDocument ) + continue; + + break; + } + + case ucb::OpenMode::ALL: + default: + break; + } + + m_Results.push_back( + std::make_unique<ResultListEntry>(std::move(pContentProperties))); + } + } + catch ( DAVException const & ) + { + } + } + + m_bCountFinal = true; + + // Callback possible, because listeners may be informed! + aGuard.clear(); + getResultSet()->rowCountFinal(); + } + return !m_bThrowException; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/ucb/source/ucp/webdav-curl/webdavdatasupplier.hxx b/ucb/source/ucp/webdav-curl/webdavdatasupplier.hxx new file mode 100644 index 000000000..e1f2efba9 --- /dev/null +++ b/ucb/source/ucp/webdav-curl/webdavdatasupplier.hxx @@ -0,0 +1,92 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + + +#pragma once + +#include <sal/config.h> + +#include "ContentProperties.hxx" +#include <memory> +#include <rtl/ref.hxx> +#include <ucbhelper/resultset.hxx> + +namespace http_dav_ucp { + +struct DataSupplier_Impl; +class Content; +struct DAVResource; + +class DataSupplier : public ucbhelper::ResultSetDataSupplier +{ + bool getData(); + +public: + DataSupplier( const css::uno::Reference< css::uno::XComponentContext >& rxContext, + const rtl::Reference< Content >& rContent, + sal_Int32 nOpenMode); + + virtual ~DataSupplier() override; + + virtual OUString queryContentIdentifierString( sal_uInt32 nIndex ) override; + virtual css::uno::Reference< css::ucb::XContentIdentifier > + queryContentIdentifier( sal_uInt32 nIndex ) override; + virtual css::uno::Reference< css::ucb::XContent > + queryContent( sal_uInt32 nIndex ) override; + + virtual bool getResult( sal_uInt32 nIndex ) override; + + virtual sal_uInt32 totalCount() override; + virtual sal_uInt32 currentCount() override; + virtual bool isCountFinal() override; + + virtual css::uno::Reference< css::sdbc::XRow > + queryPropertyValues( sal_uInt32 nIndex ) override; + virtual void releasePropertyValues( sal_uInt32 nIndex ) override; + + virtual void close() override; + + virtual void validate() override; + +private: + struct ResultListEntry + { + OUString aId; + css::uno::Reference< css::ucb::XContentIdentifier > xId; + css::uno::Reference< css::ucb::XContent > xContent; + css::uno::Reference< css::sdbc::XRow > xRow; + std::unique_ptr<ContentProperties> pData; + + explicit ResultListEntry( std::unique_ptr<ContentProperties> && pEntry ) : pData( std::move(pEntry) ) {} + }; + + typedef std::vector<std::unique_ptr<ResultListEntry>> ResultList; + + osl::Mutex m_aMutex; + ResultList m_Results; + rtl::Reference< Content > m_xContent; + css::uno::Reference< css::uno::XComponentContext > m_xContext; + sal_Int32 m_nOpenMode; + bool m_bCountFinal; + bool m_bThrowException; +}; + +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/ucb/source/ucp/webdav-curl/webdavprovider.cxx b/ucb/source/ucp/webdav-curl/webdavprovider.cxx new file mode 100644 index 000000000..effd6665a --- /dev/null +++ b/ucb/source/ucp/webdav-curl/webdavprovider.cxx @@ -0,0 +1,174 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include <comphelper/processfactory.hxx> +#include <ucbhelper/contentidentifier.hxx> +#include <ucbhelper/macros.hxx> +#include "webdavprovider.hxx" +#include "webdavcontent.hxx" + +#include <cppuhelper/queryinterface.hxx> +#include <osl/mutex.hxx> +#include <com/sun/star/ucb/IllegalIdentifierException.hpp> + +#include <tools/urlobj.hxx> + +using namespace com::sun::star; +using namespace http_dav_ucp; + + +// ContentProvider Implementation. + + +ContentProvider::ContentProvider( + const uno::Reference< uno::XComponentContext >& rContext ) +: ::ucbhelper::ContentProviderImplHelper( rContext ), + m_xDAVSessionFactory( new DAVSessionFactory ) +{ +} + + +// virtual +ContentProvider::~ContentProvider() +{} + + +// XInterface methods. +void SAL_CALL ContentProvider::acquire() noexcept +{ + OWeakObject::acquire(); +} + +void SAL_CALL ContentProvider::release() noexcept +{ + OWeakObject::release(); +} + +css::uno::Any SAL_CALL ContentProvider::queryInterface( const css::uno::Type & rType ) +{ + css::uno::Any aRet = cppu::queryInterface( rType, + static_cast< lang::XTypeProvider* >(this), + static_cast< lang::XServiceInfo* >(this), + static_cast< ucb::XContentProvider* >(this) + ); + return aRet.hasValue() ? aRet : OWeakObject::queryInterface( rType ); +} + +// XTypeProvider methods. + + +XTYPEPROVIDER_IMPL_3( ContentProvider, + lang::XTypeProvider, + lang::XServiceInfo, + ucb::XContentProvider ); + + +// XServiceInfo methods. + +OUString +ContentProvider::getImplementationName() +{ + return "com.sun.star.comp.WebDAVContentProvider"; +} + +css::uno::Sequence< OUString > +ContentProvider::getSupportedServiceNames() +{ + return { WEBDAV_CONTENT_PROVIDER_SERVICE_NAME }; +} + +sal_Bool +ContentProvider::supportsService(const OUString& s) +{ + return cppu::supportsService(this, s); +} + +// XContentProvider methods. + + +// virtual +uno::Reference< ucb::XContent > SAL_CALL +ContentProvider::queryContent( + const uno::Reference< + ucb::XContentIdentifier >& Identifier ) +{ + // Check URL scheme... + INetURLObject aURL(Identifier->getContentIdentifier()); + + if (aURL.isSchemeEqualTo(INetProtocol::NotValid)) + throw ucb::IllegalIdentifierException(); + + if (!aURL.isAnyKnownWebDAVScheme()) + throw ucb::IllegalIdentifierException(); + + uno::Reference< ucb::XContentIdentifier > xCanonicId; + + if (aURL.isSchemeEqualTo(INetProtocol::VndSunStarWebdav) || + aURL.isSchemeEqualTo(DAV_URL_SCHEME) || + aURL.isSchemeEqualTo(WEBDAV_URL_SCHEME)) + { + aURL.changeScheme(INetProtocol::Http); + xCanonicId = new ::ucbhelper::ContentIdentifier( aURL.getExternalURL() ); + } + else if (aURL.isSchemeEqualTo(VNDSUNSTARWEBDAVS_URL_SCHEME) || + aURL.isSchemeEqualTo(DAVS_URL_SCHEME) || + aURL.isSchemeEqualTo(WEBDAVS_URL_SCHEME)) + { + aURL.changeScheme(INetProtocol::Https); + xCanonicId = new ::ucbhelper::ContentIdentifier( aURL.getExternalURL() ); + } + else + { + xCanonicId = Identifier; + } + + osl::MutexGuard aGuard( m_aMutex ); + + // Check, if a content with given id already exists... + uno::Reference<ucb::XContent> xContent = queryExistingContent(xCanonicId); + if ( xContent.is() ) + return xContent; + + // Create a new content. + + try + { + xContent = new ::http_dav_ucp::Content( + m_xContext, this, xCanonicId, m_xDAVSessionFactory ); + registerNewContent( xContent ); + } + catch ( ucb::ContentCreationException const & ) + { + throw ucb::IllegalIdentifierException(); + } + + if ( !xContent->getIdentifier().is() ) + throw ucb::IllegalIdentifierException(); + + return xContent; +} + +extern "C" SAL_DLLPUBLIC_EXPORT css::uno::XInterface* +ucb_webdav_ContentProvider_get_implementation( + css::uno::XComponentContext* context, css::uno::Sequence<css::uno::Any> const&) +{ + return cppu::acquire(new ContentProvider(context)); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/ucb/source/ucp/webdav-curl/webdavprovider.hxx b/ucb/source/ucp/webdav-curl/webdavprovider.hxx new file mode 100644 index 000000000..a05377cfe --- /dev/null +++ b/ucb/source/ucp/webdav-curl/webdavprovider.hxx @@ -0,0 +1,100 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + + +#pragma once + +#include <sal/config.h> + +#include <memory> + +#include <rtl/ref.hxx> +#include <com/sun/star/beans/Property.hpp> +#include "DAVSessionFactory.hxx" +#include <ucbhelper/providerhelper.hxx> +#include "PropertyMap.hxx" + +namespace com::sun::star::lang { +class XSingleServiceFactory; +} + +namespace http_dav_ucp { + + +// UNO service name for the provider. This name will be used by the UCB to +// create instances of the provider. +inline constexpr OUStringLiteral WEBDAV_CONTENT_PROVIDER_SERVICE_NAME = u"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 u"http" +#define HTTPS_URL_SCHEME u"https" +#define DAV_URL_SCHEME u"dav" +#define DAVS_URL_SCHEME u"davs" +#define WEBDAV_URL_SCHEME u"webdav" +#define WEBDAVS_URL_SCHEME u"webdavs" + +inline constexpr OUStringLiteral HTTP_CONTENT_TYPE = u"application/" HTTP_URL_SCHEME "-content"; + +#define WEBDAV_CONTENT_TYPE HTTP_CONTENT_TYPE +inline constexpr OUStringLiteral WEBDAV_COLLECTION_TYPE = u"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() + noexcept override; + virtual void SAL_CALL release() + noexcept override; + + // XTypeProvider + virtual css::uno::Sequence< sal_Int8 > SAL_CALL getImplementationId() override; + virtual css::uno::Sequence< css::uno::Type > SAL_CALL getTypes() override; + + // XServiceInfo + virtual OUString SAL_CALL getImplementationName() override; + virtual sal_Bool SAL_CALL supportsService( const OUString& ServiceName ) override; + virtual css::uno::Sequence< OUString > SAL_CALL getSupportedServiceNames() override; + + // XContentProvider + virtual css::uno::Reference< css::ucb::XContent > SAL_CALL + queryContent( const css::uno::Reference< css::ucb::XContentIdentifier >& Identifier ) override; + + + // Non-interface methods. + + bool getProperty( const OUString & rPropName, + css::beans::Property & rProp ); +}; + +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/ucb/source/ucp/webdav-curl/webdavresponseparser.cxx b/ucb/source/ucp/webdav-curl/webdavresponseparser.cxx new file mode 100644 index 000000000..9a0500d01 --- /dev/null +++ b/ucb/source/ucp/webdav-curl/webdavresponseparser.cxx @@ -0,0 +1,1001 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include "webdavresponseparser.hxx" + +#include "DAVProperties.hxx" +#include "UCBDeadPropertyValue.hxx" + +#include <comphelper/processfactory.hxx> +#include <comphelper/sequence.hxx> + +#include <cppuhelper/implbase.hxx> +#include <com/sun/star/xml/sax/Parser.hpp> +#include <com/sun/star/xml/sax/InputSource.hpp> +#include <com/sun/star/xml/sax/XDocumentHandler.hpp> +#include <com/sun/star/ucb/LockEntry.hpp> +#include <com/sun/star/ucb/LockScope.hpp> +#include <com/sun/star/ucb/LockType.hpp> +#include <com/sun/star/ucb/Lock.hpp> +#include <map> +#include <unordered_map> +#include <rtl/ref.hxx> +#include <rtl/uri.hxx> +#include <sal/log.hxx> +#include <o3tl/string_view.hxx> + +using namespace com::sun::star; +using namespace http_dav_ucp; + + +// WebDAVNamespace enum and StringToEnum converter +namespace +{ + enum WebDAVNamespace + { + WebDAVNamespace_unknown = 0, + WebDAVNamespace_DAV, + WebDAVNamespace_ucb_openoffice_org_dav_props, + + WebDAVNamespace_last + }; + + WebDAVNamespace StrToWebDAVNamespace(::std::u16string_view rStr) + { + if (rStr == u"DAV:") + { + return WebDAVNamespace_DAV; + } + else if (rStr == u"http://ucb.openoffice.org/dav/props/") + { + return WebDAVNamespace_ucb_openoffice_org_dav_props; + } + + return WebDAVNamespace_unknown; + } +} // end of anonymous namespace + +// WebDAVName enum and StringToEnum converter using unordered_map +namespace +{ + enum WebDAVName + { + WebDAVName_unknown = 0, + WebDAVName_activelock, + WebDAVName_lockdiscovery, + WebDAVName_multistatus, + WebDAVName_response, + WebDAVName_href, + WebDAVName_propstat, + WebDAVName_prop, + WebDAVName_resourcetype, + WebDAVName_collection, + WebDAVName_getcontenttype, + WebDAVName_supportedlock, + WebDAVName_lockentry, + WebDAVName_lockscope, + WebDAVName_depth, + WebDAVName_locktoken, + WebDAVName_exclusive, + WebDAVName_locktype, + WebDAVName_owner, + WebDAVName_timeout, + WebDAVName_write, + WebDAVName_shared, + WebDAVName_status, + WebDAVName_getlastmodified, + WebDAVName_creationdate, + WebDAVName_getcontentlength, + WebDAVName_type, + WebDAVName_value, + WebDAVName_ucbprop, + + WebDAVName_last + }; + + WebDAVName StrToWebDAVName(const OUString& rStr) + { + typedef std::unordered_map< OUString, WebDAVName > WebDAVNameMapper; + typedef std::pair< OUString, WebDAVName > WebDAVNameValueType; + static WebDAVNameMapper aWebDAVNameMapperList; + + if(aWebDAVNameMapperList.empty()) + { + aWebDAVNameMapperList.insert(WebDAVNameValueType(OUString("activelock"), WebDAVName_activelock)); + aWebDAVNameMapperList.insert(WebDAVNameValueType(OUString("lockdiscovery"), WebDAVName_lockdiscovery)); + aWebDAVNameMapperList.insert(WebDAVNameValueType(OUString("multistatus"), WebDAVName_multistatus)); + aWebDAVNameMapperList.insert(WebDAVNameValueType(OUString("response"), WebDAVName_response)); + aWebDAVNameMapperList.insert(WebDAVNameValueType(OUString("href"), WebDAVName_href)); + aWebDAVNameMapperList.insert(WebDAVNameValueType(OUString("propstat"), WebDAVName_propstat)); + aWebDAVNameMapperList.insert(WebDAVNameValueType(OUString("prop"), WebDAVName_prop)); + aWebDAVNameMapperList.insert(WebDAVNameValueType(OUString("resourcetype"), WebDAVName_resourcetype)); + aWebDAVNameMapperList.insert(WebDAVNameValueType(OUString("collection"), WebDAVName_collection)); + aWebDAVNameMapperList.insert(WebDAVNameValueType(OUString("getcontenttype"), WebDAVName_getcontenttype)); + aWebDAVNameMapperList.insert(WebDAVNameValueType(OUString("supportedlock"), WebDAVName_supportedlock)); + aWebDAVNameMapperList.insert(WebDAVNameValueType(OUString("lockentry"), WebDAVName_lockentry)); + aWebDAVNameMapperList.insert(WebDAVNameValueType(OUString("lockscope"), WebDAVName_lockscope)); + aWebDAVNameMapperList.insert(WebDAVNameValueType(OUString("depth"), WebDAVName_depth)); + aWebDAVNameMapperList.insert(WebDAVNameValueType(OUString("locktoken"), WebDAVName_locktoken)); + aWebDAVNameMapperList.insert(WebDAVNameValueType(OUString("exclusive"), WebDAVName_exclusive)); + aWebDAVNameMapperList.insert(WebDAVNameValueType(OUString("locktype"), WebDAVName_locktype)); + aWebDAVNameMapperList.insert(WebDAVNameValueType(OUString("owner"), WebDAVName_owner)); + aWebDAVNameMapperList.insert(WebDAVNameValueType(OUString("timeout"), WebDAVName_timeout)); + aWebDAVNameMapperList.insert(WebDAVNameValueType(OUString("write"), WebDAVName_write)); + aWebDAVNameMapperList.insert(WebDAVNameValueType(OUString("shared"), WebDAVName_shared)); + aWebDAVNameMapperList.insert(WebDAVNameValueType(OUString("status"), WebDAVName_status)); + aWebDAVNameMapperList.insert(WebDAVNameValueType(OUString("getlastmodified"), WebDAVName_getlastmodified)); + aWebDAVNameMapperList.insert(WebDAVNameValueType(OUString("creationdate"), WebDAVName_creationdate)); + aWebDAVNameMapperList.insert(WebDAVNameValueType(OUString("getcontentlength"), WebDAVName_getcontentlength)); + aWebDAVNameMapperList.insert(WebDAVNameValueType(OUString("type"), WebDAVName_type)); + aWebDAVNameMapperList.insert(WebDAVNameValueType(OUString("value"), WebDAVName_value)); + aWebDAVNameMapperList.insert(WebDAVNameValueType(OUString("ucbprop"), WebDAVName_ucbprop)); + } + + const WebDAVNameMapper::const_iterator aResult(aWebDAVNameMapperList.find(rStr)); + + if(aResult == aWebDAVNameMapperList.end()) + { + return WebDAVName_unknown; + } + else + { + return aResult->second; + } + } +} // end of anonymous namespace + + +// WebDAVContext, holding information for each start/endElement pair + +namespace +{ + typedef std::map< OUString, OUString > NamespaceMap; + + class WebDAVContext + { + private: + WebDAVContext* mpParent; + NamespaceMap maNamespaceMap; + OUString maWhiteSpace; + + OUString maNamespace; + OUString maName; + + WebDAVNamespace maWebDAVNamespace; + WebDAVName maWebDAVName; + + // local helpers + void parseForNamespaceTokens(const uno::Reference< xml::sax::XAttributeList >& xAttribs); + OUString mapNamespaceToken(const OUString& rToken) const; + void splitName(const OUString& rSource); + + public: + WebDAVContext(WebDAVContext* pParent, const OUString& aName, const uno::Reference< xml::sax::XAttributeList >& xAttribs); + + WebDAVContext* getParent() const { return mpParent; } + OUString& getWhiteSpace() { return maWhiteSpace; } + void setWhiteSpace(const OUString& rNew) { maWhiteSpace = rNew; } + + const OUString& getNamespace() const { return maNamespace; } + const OUString& getName() const { return maName; } + const WebDAVNamespace& getWebDAVNamespace() const { return maWebDAVNamespace; } + const WebDAVName& getWebDAVName() const { return maWebDAVName; } + }; + + void WebDAVContext::parseForNamespaceTokens(const uno::Reference< xml::sax::XAttributeList >& xAttribs) + { + const sal_Int16 nAttributes(xAttribs->getLength()); + + for(sal_Int16 a(0); a < nAttributes; a++) + { + const OUString aName(xAttribs->getNameByIndex(a)); + const sal_Int32 nLen(aName.getLength()); + + if(nLen) + { + if(aName.startsWith("xmlns")) + { + const sal_Int32 nIndex(aName.indexOf(':', 0)); + + if(-1 != nIndex && nIndex + 1 < nLen) + { + const OUString aToken(aName.copy(nIndex + 1)); + + maNamespaceMap.emplace(aToken, xAttribs->getValueByIndex(a)); + } + } + } + } + } + + OUString WebDAVContext::mapNamespaceToken(const OUString& rToken) const + { + NamespaceMap::const_iterator iter = maNamespaceMap.find(rToken); + + if(maNamespaceMap.end() == iter) + { + if(getParent()) + { + return getParent()->mapNamespaceToken(rToken); + } + else + { + return rToken; + } + } + else + { + return (*iter).second; + } + } + + void WebDAVContext::splitName(const OUString& rSource) + { + const sal_Int32 nLen(rSource.getLength()); + maNamespace.clear(); + maName = rSource; + + if(nLen) + { + const sal_Int32 nIndex(rSource.indexOf(':', 0)); + + if(nIndex > 0 && ((nIndex + 1) < nLen)) + { + maNamespace = mapNamespaceToken(rSource.copy(0, nIndex)); + maName = rSource.copy(nIndex + 1); + } + } + } + + WebDAVContext::WebDAVContext(WebDAVContext* pParent, const OUString& aName, const uno::Reference< xml::sax::XAttributeList >& xAttribs) + : mpParent(pParent), + maNamespaceMap(), + maWhiteSpace(), + maNamespace(), + maName(), + maWebDAVNamespace(WebDAVNamespace_unknown), + maWebDAVName(WebDAVName_unknown) + { + const sal_Int16 nAttributes(xAttribs->getLength()); + + if(nAttributes) + { + // parse evtl. namespace entries + parseForNamespaceTokens(xAttribs); + } + + // split name to namespace and name + splitName(aName); + + // evaluate enums for namespace and name + maWebDAVNamespace = StrToWebDAVNamespace(maNamespace); + maWebDAVName = StrToWebDAVName(maName); + } +} // end of anonymous namespace + + +// the Xml parser itself + +namespace +{ + enum WebDAVResponseParserMode + { + WebDAVResponseParserMode_PropFind = 0, + WebDAVResponseParserMode_PropName, + WebDAVResponseParserMode_Lock + }; + + class WebDAVResponseParser : public cppu::WeakImplHelper< css::xml::sax::XDocumentHandler > + { + private: + std::vector< ucb::Lock > maResult_Lock; + std::vector< http_dav_ucp::DAVResource > maResult_PropFind; + std::vector< http_dav_ucp::DAVResourceInfo > maResult_PropName; + + WebDAVContext* mpContext; + OUString maHref; + OUString maStatus; + OUString m_UCBType; + OUString m_UCBValue; + std::vector< http_dav_ucp::DAVPropertyValue > maResponseProperties; + std::vector< http_dav_ucp::DAVPropertyValue > maPropStatProperties; + std::vector< OUString > maResponseNames; + std::vector< OUString > maPropStatNames; + uno::Sequence< ucb::LockEntry > maLockEntries; + ucb::LockScope maLockScope; + ucb::LockType maLockType; + ucb::Lock maLock; + WebDAVResponseParserMode meWebDAVResponseParserMode; + + bool mbResourceTypeCollection : 1; + bool mbLockScopeSet : 1; + bool mbLockTypeSet : 1; + + // local helpers + bool whitespaceIsAvailable() const + { + return mpContext && mpContext->getWhiteSpace().getLength(); + } + bool hasParent(WebDAVName aWebDAVName) const + { + return mpContext && mpContext->getParent() && aWebDAVName == mpContext->getParent()->getWebDAVName(); + } + bool propertyIsReady() const + { + return hasParent(WebDAVName_prop) && whitespaceIsAvailable(); + } + bool isCollectingProperties() const + { + return WebDAVResponseParserMode_PropFind == meWebDAVResponseParserMode; + } + bool isCollectingPropNames() const + { + return WebDAVResponseParserMode_PropName == meWebDAVResponseParserMode; + } + bool collectThisPropertyAsName() const + { + return isCollectingPropNames() && hasParent(WebDAVName_prop); + } + void pop_context() + { + if(mpContext) + { + WebDAVContext* pTemp = mpContext; + mpContext = mpContext->getParent(); + delete pTemp; + } + else + { + SAL_WARN( "ucb.ucp.webdav", "Parser context pop without context (!)"); + } + } + + public: + explicit WebDAVResponseParser(WebDAVResponseParserMode eWebDAVResponseParserMode); + virtual ~WebDAVResponseParser() override; + + // Methods XDocumentHandler + virtual void SAL_CALL startDocument( ) override; + virtual void SAL_CALL endDocument( ) override; + virtual void SAL_CALL startElement( const OUString& aName, const uno::Reference< xml::sax::XAttributeList >& xAttribs ) override; + virtual void SAL_CALL endElement( const OUString& aName ) override; + virtual void SAL_CALL characters( const OUString& aChars ) override; + virtual void SAL_CALL ignorableWhitespace( const OUString& aWhitespaces ) override; + virtual void SAL_CALL processingInstruction( const OUString& aTarget, const OUString& aData ) override; + virtual void SAL_CALL setDocumentLocator( const uno::Reference< xml::sax::XLocator >& xLocator ) override; + + const std::vector< ucb::Lock >& getResult_Lock() const { return maResult_Lock; } + const std::vector< http_dav_ucp::DAVResource >& getResult_PropFind() const { return maResult_PropFind; } + const std::vector< http_dav_ucp::DAVResourceInfo >& getResult_PropName() const { return maResult_PropName; } + }; + + WebDAVResponseParser::WebDAVResponseParser(WebDAVResponseParserMode eWebDAVResponseParserMode) + : maResult_PropFind(), + maResult_PropName(), + mpContext(nullptr), + maHref(), + maStatus(), + maResponseProperties(), + maPropStatProperties(), + maResponseNames(), + maPropStatNames(), + maLockEntries(), + maLockScope(ucb::LockScope_EXCLUSIVE), + maLockType(ucb::LockType_WRITE), + meWebDAVResponseParserMode(eWebDAVResponseParserMode), + mbResourceTypeCollection(false), + mbLockScopeSet(false), + mbLockTypeSet(false) + { + } + + WebDAVResponseParser::~WebDAVResponseParser() + { + SAL_WARN_IF(mpContext, "ucb.ucp.webdav", "Parser destructed with existing content (!)"); + while(mpContext) + { + pop_context(); + } + } + + void SAL_CALL WebDAVResponseParser::startDocument( ) + { + SAL_WARN_IF(mpContext, "ucb.ucp.webdav", "Parser start with existing content (!)"); + } + + void SAL_CALL WebDAVResponseParser::endDocument( ) + { + SAL_WARN_IF(mpContext, "ucb.ucp.webdav", "Parser end with existing content (!)"); + } + + void SAL_CALL WebDAVResponseParser::startElement( const OUString& aName, const uno::Reference< xml::sax::XAttributeList >& xAttribs ) + { + const sal_Int32 nLen(aName.getLength()); + + if(nLen) + { + // create new context (push) + mpContext = new WebDAVContext(mpContext, aName, xAttribs); + + if(collectThisPropertyAsName()) + { + // When collecting property names and parent is prop there is no need + // to handle the content of this property deeper (evtl. preparations) + } + else + { + switch(mpContext->getWebDAVNamespace()) + { + default: // WebDAVNamespace_unknown, WebDAVNamespace_last or unhandled + { + break; + } + case WebDAVNamespace_DAV: + { + switch(mpContext->getWebDAVName()) + { + default: // WebDAVName_unknown, WebDAVName_last or unhandled + { + break; + } + case WebDAVName_propstat: + { + // propstat start + if(isCollectingProperties()) + { + // reset maPropStatProperties + maPropStatProperties.clear(); + } + else + { + // when collecting properties reset maPropStatNames + maPropStatNames.clear(); + } + break; + } + case WebDAVName_response: + { + // response start, reset Href and status and maResponseProperties + maHref.clear(); + maStatus.clear(); + + if(isCollectingProperties()) + { + // reset maResponseProperties + maResponseProperties.clear(); + } + else + { + // reset maResponseNames when collecting properties + maResponseNames.clear(); + } + break; + } + case WebDAVName_resourcetype: + { + // resourcetype start, reset collection + mbResourceTypeCollection = false; + break; + } + case WebDAVName_supportedlock: + { + // supportedlock start, reset maLockEntries + maLockEntries.realloc(0); + break; + } + case WebDAVName_lockentry: + { + // lockentry start, reset maLockEntries + mbLockScopeSet = false; + mbLockTypeSet = false; + break; + } + case WebDAVName_activelock: + { + maLock = ucb::Lock(); + break; + } + } + break; + } + case WebDAVNamespace_ucb_openoffice_org_dav_props: + { + break; + } + } + } + } + } + + OUString MakePropertyName(WebDAVContext const& rContext) + { + OUString ret; + OString const name(OUStringToOString(rContext.getName(), RTL_TEXTENCODING_UTF8)); + OString const nameSpace(OUStringToOString(rContext.getNamespace(), RTL_TEXTENCODING_UTF8)); + DAVProperties::createUCBPropName(nameSpace.getStr(), name.getStr(), ret); + return ret; + } + + void SAL_CALL WebDAVResponseParser::endElement( const OUString& aName ) + { + const sal_Int32 nLen(aName.getLength()); + SAL_WARN_IF(!mpContext, "ucb.ucp.webdav", "Parser EndElement without content (!)"); + + if(mpContext && nLen) + { + if(collectThisPropertyAsName()) + { + // name must be encoded as expected by createSerfPropName() + OUString const name(MakePropertyName(*mpContext)); + maPropStatNames.emplace_back(name); + } + else + { + switch(mpContext->getWebDAVNamespace()) + { + default: // WebDAVNamespace_unknown, WebDAVNamespace_last or unhandled + { + break; + } + case WebDAVNamespace_DAV: + { + switch(mpContext->getWebDAVName()) + { + default: // WebDAVName_unknown, WebDAVName_last or unhandled + { + break; + } + case WebDAVName_href: + { + // href end, save it if we have whitespace + if(whitespaceIsAvailable()) + { + // Sharepoint 2016 workaround: apparently + // the result is an IRI (RFC 3987 possibly?) + // so try to encode the non-ASCII chars + // without changing anything else + maHref = ::rtl::Uri::encode(mpContext->getWhiteSpace(), + rtl_UriCharClassUric, rtl_UriEncodeKeepEscapes, + RTL_TEXTENCODING_UTF8); + } + break; + } + case WebDAVName_status: + { + // status end, save it if we have whitespace + if(whitespaceIsAvailable()) + { + maStatus = mpContext->getWhiteSpace(); + } + break; + } + case WebDAVName_getlastmodified: + { + // getlastmodified end, safe if content is correct + if(propertyIsReady()) + { + http_dav_ucp::DAVPropertyValue aDAVPropertyValue; + + aDAVPropertyValue.Name = "DAV:getlastmodified"; + aDAVPropertyValue.Value <<= mpContext->getWhiteSpace(); + maPropStatProperties.push_back(aDAVPropertyValue); + } + break; + } + case WebDAVName_creationdate: + { + // creationdate end, safe if content is correct + if(propertyIsReady()) + { + http_dav_ucp::DAVPropertyValue aDAVPropertyValue; + + aDAVPropertyValue.Name = "DAV:creationdate"; + aDAVPropertyValue.Value <<= mpContext->getWhiteSpace(); + maPropStatProperties.push_back(aDAVPropertyValue); + } + break; + } + case WebDAVName_collection: + { + // collection end, check and set + if(hasParent(WebDAVName_resourcetype)) + { + mbResourceTypeCollection = true; + } + break; + } + case WebDAVName_resourcetype: + { + // resourcetype end, check for collection + if(hasParent(WebDAVName_prop)) + { + http_dav_ucp::DAVPropertyValue aDAVPropertyValue; + + aDAVPropertyValue.Name = "DAV:resourcetype"; + aDAVPropertyValue.Value <<= (mbResourceTypeCollection ? OUString("collection") : OUString()); + maPropStatProperties.push_back(aDAVPropertyValue); + } + break; + } + case WebDAVName_getcontentlength: + { + // getcontentlength end, safe if content is correct + if(propertyIsReady()) + { + http_dav_ucp::DAVPropertyValue aDAVPropertyValue; + + aDAVPropertyValue.Name = "DAV:getcontentlength"; + aDAVPropertyValue.Value <<= mpContext->getWhiteSpace(); + maPropStatProperties.push_back(aDAVPropertyValue); + } + break; + } + case WebDAVName_getcontenttype: + { + // getcontenttype end, safe if content is correct + if(propertyIsReady()) + { + http_dav_ucp::DAVPropertyValue aDAVPropertyValue; + + aDAVPropertyValue.Name = "DAV:getcontenttype"; + aDAVPropertyValue.Value <<= mpContext->getWhiteSpace(); + maPropStatProperties.push_back(aDAVPropertyValue); + } + break; + } + case WebDAVName_supportedlock: + { + // supportedlock end + if(hasParent(WebDAVName_prop) && maLockEntries.hasElements()) + { + http_dav_ucp::DAVPropertyValue aDAVPropertyValue; + + aDAVPropertyValue.Name = "DAV:supportedlock"; + aDAVPropertyValue.Value <<= maLockEntries; + maPropStatProperties.push_back(aDAVPropertyValue); + } + break; + } + case WebDAVName_lockentry: + { + // lockentry end + if(hasParent(WebDAVName_supportedlock) && (mbLockScopeSet && mbLockTypeSet)) + { + const sal_Int32 nLength(maLockEntries.getLength()); + ucb::LockEntry aEntry; + + aEntry.Scope = maLockScope; + aEntry.Type = maLockType; + maLockEntries.realloc(nLength + 1); + maLockEntries.getArray()[nLength] = aEntry; + } + break; + } + case WebDAVName_owner: + { + maLock.Owner <<= mpContext->getWhiteSpace(); + break; + } + case WebDAVName_timeout: + { + const OUString sTimeout(mpContext->getWhiteSpace()); + if (sTimeout == "Infinite") + maLock.Timeout = -1; + else if (sTimeout.startsWith("Second-")) + maLock.Timeout = o3tl::toInt64(sTimeout.subView(7)); + break; + } + case WebDAVName_locktoken: + { + const OUString sLockToken(maHref); + SAL_WARN_IF(!sLockToken.startsWith("opaquelocktoken:"), "ucb.ucp.webdav", + "Parser error: wrong 'locktoken' value."); + const sal_Int32 nLength(maLock.LockTokens.getLength()); + maLock.LockTokens.realloc(nLength+1); + maLock.LockTokens.getArray()[nLength] = sLockToken; + break; + } + case WebDAVName_exclusive: + { + // exclusive lockscope end + if(hasParent(WebDAVName_lockscope)) + { + maLockScope = ucb::LockScope_EXCLUSIVE; + mbLockScopeSet = true; + } + break; + } + case WebDAVName_shared: + { + // shared lockscope end + if(hasParent(WebDAVName_lockscope)) + { + maLockScope = ucb::LockScope_SHARED; + mbLockScopeSet = true; + } + break; + } + case WebDAVName_write: + { + // write locktype end + if(hasParent(WebDAVName_locktype)) + { + maLockType = ucb::LockType_WRITE; + mbLockTypeSet = true; + } + break; + } + case WebDAVName_depth: + { + OUString const chars(mpContext->getWhiteSpace()); + if (chars == "0") + { + maLock.Depth = ucb::LockDepth_ZERO; + } + else if (chars == "1") + { + maLock.Depth = ucb::LockDepth_ONE; + } + else if (chars == "infinity") + { + maLock.Depth = ucb::LockDepth_INFINITY; + } + break; + } + case WebDAVName_activelock: + { + maLock.Type = maLockType; + maLock.Scope = maLockScope; + maResult_Lock.push_back(maLock); + break; + } + case WebDAVName_lockdiscovery: + { + // lockdiscovery may be requested via PROPFIND, + // in addition to LOCK! so return it 2 ways + if (isCollectingProperties()) + { + http_dav_ucp::DAVPropertyValue aDAVPropertyValue; + + aDAVPropertyValue.Name = "DAV:lockdiscovery"; + aDAVPropertyValue.Value <<= ::comphelper::containerToSequence(maResult_Lock); + maPropStatProperties.push_back(aDAVPropertyValue); + } + break; + } + case WebDAVName_propstat: + { + // propstat end, check status + if(maStatus.getLength()) + { + if(maStatus == "HTTP/1.1 200 OK") + { + if(isCollectingProperties()) + { + if(!maPropStatProperties.empty()) + { + // append to maResponseProperties if okay + maResponseProperties.insert(maResponseProperties.end(), maPropStatProperties.begin(), maPropStatProperties.end()); + } + } + else + { + if(!maPropStatNames.empty()) + { + // when collecting properties append to + maResponseNames.insert(maResponseNames.end(), maPropStatNames.begin(), maPropStatNames.end()); + } + } + } + } + break; + } + case WebDAVName_response: + { + // response end + if(maHref.getLength()) + { + if(isCollectingProperties()) + { + // create DAVResource when we have content + if(!maResponseProperties.empty()) + { + http_dav_ucp::DAVResource aDAVResource; + + aDAVResource.uri = maHref; + aDAVResource.properties = maResponseProperties; + maResult_PropFind.push_back(aDAVResource); + } + } + else + { + // when collecting properties add them to result when there are some + if(!maResponseNames.empty()) + { + http_dav_ucp::DAVResourceInfo aDAVResourceInfo; + + aDAVResourceInfo.properties = maResponseNames; + maResult_PropName.push_back(aDAVResourceInfo); + } + } + } + break; + } + } + break; + } + case WebDAVNamespace_ucb_openoffice_org_dav_props: + { + switch(mpContext->getWebDAVName()) + { + case WebDAVName_type: + { + m_UCBType = mpContext->getWhiteSpace(); + break; + } + case WebDAVName_value: + { + m_UCBValue = mpContext->getWhiteSpace(); + break; + } + case WebDAVName_ucbprop: + { + if (!m_UCBType.isEmpty() + && isCollectingProperties()) + { + http_dav_ucp::DAVPropertyValue aDAVPropertyValue; + aDAVPropertyValue.Name = MakePropertyName(*mpContext->getParent()); + if (UCBDeadPropertyValue::createFromXML(m_UCBType, m_UCBValue, aDAVPropertyValue.Value)) + { + maPropStatProperties.push_back(aDAVPropertyValue); + } + else + { + SAL_INFO("ucb.ucp.webdav.curl", "cannot parse property value"); + } + } + m_UCBType.clear(); + m_UCBValue.clear(); + break; + } + default: + break; + } + break; + } + } + } + + // destroy last context (pop) + pop_context(); + } + } + + void SAL_CALL WebDAVResponseParser::characters( const OUString& aChars ) + { + // collect whitespace over evtl. several calls in mpContext + SAL_WARN_IF(!mpContext, "ucb.ucp.webdav", "Parser characters without content (!)"); + const sal_Int32 nLen(aChars.getLength()); + + if(mpContext && nLen) + { + // remove leading/trailing blanks and CRLF + const OUString aTrimmedChars(aChars.trim()); + + if(aTrimmedChars.getLength()) + { + OUString aNew(mpContext->getWhiteSpace()); + + if(aNew.getLength()) + { + // add one char when appending (see html1.1 spec) + aNew += " "; + } + + aNew += aTrimmedChars; + mpContext->setWhiteSpace(aNew); + } + } + } + + void SAL_CALL WebDAVResponseParser::ignorableWhitespace( const OUString& /*aWhitespaces*/ ) + { + } + + void SAL_CALL WebDAVResponseParser::processingInstruction( const OUString& /*aTarget*/, const OUString& /*aData*/ ) + { + } + + void SAL_CALL WebDAVResponseParser::setDocumentLocator( const uno::Reference< xml::sax::XLocator >& /*xLocator*/ ) + { + } +} // end of anonymous namespace + + +// wrapper for various calls to the parser + +namespace +{ + template<typename T> + void parseWebDAVResponse( + const uno::Reference< io::XInputStream >& xInputStream, + std::vector< T >& rResult, + WebDAVResponseParserMode eWebDAVResponseParserMode, + std::vector<T> const & (WebDAVResponseParser::* fn)() const) + { + if(xInputStream.is()) + { + try + { + // prepare ParserInputSource + xml::sax::InputSource myInputSource; + myInputSource.aInputStream = xInputStream; + + // get parser + uno::Reference< xml::sax::XParser > xParser = xml::sax::Parser::create( + comphelper::getProcessComponentContext() ); + + // create parser; connect parser and filter + rtl::Reference<WebDAVResponseParser> const pWebDAVResponseParser( + new WebDAVResponseParser(eWebDAVResponseParserMode)); + uno::Reference< xml::sax::XDocumentHandler > xWebDAVHdl(pWebDAVResponseParser); + xParser->setDocumentHandler(xWebDAVHdl); + + // finally, parse the stream + xParser->parseStream(myInputSource); + + // get result + rResult = (pWebDAVResponseParser.get()->*fn)(); + } + catch(uno::Exception&) + { + SAL_WARN("ucb.ucp.webdav", "WebDAV Parse error (!)"); + } + } + } +} // end of anonymous namespace + + +// helper to parse a XML WebDAV response + +namespace http_dav_ucp +{ + std::vector< ucb::Lock > parseWebDAVLockResponse(const uno::Reference< io::XInputStream >& xInputStream) + { + std::vector< ucb::Lock > aResult; + parseWebDAVResponse< ucb::Lock >(xInputStream, aResult, WebDAVResponseParserMode_Lock, &WebDAVResponseParser::getResult_Lock); + return aResult; + } + + std::vector< DAVResource > parseWebDAVPropFindResponse(const uno::Reference< io::XInputStream >& xInputStream) + { + std::vector< DAVResource > aResult; + parseWebDAVResponse< DAVResource >(xInputStream, aResult, WebDAVResponseParserMode_PropFind, &WebDAVResponseParser::getResult_PropFind); + return aResult; + } + + std::vector< DAVResourceInfo > parseWebDAVPropNameResponse(const uno::Reference< io::XInputStream >& xInputStream) + { + std::vector< DAVResourceInfo > aResult; + parseWebDAVResponse< DAVResourceInfo >(xInputStream, aResult, WebDAVResponseParserMode_PropName, &WebDAVResponseParser::getResult_PropName); + return aResult; + } +} // namespace http_dav_ucp + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/ucb/source/ucp/webdav-curl/webdavresponseparser.hxx b/ucb/source/ucp/webdav-curl/webdavresponseparser.hxx new file mode 100644 index 000000000..c62cb3205 --- /dev/null +++ b/ucb/source/ucp/webdav-curl/webdavresponseparser.hxx @@ -0,0 +1,38 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#pragma once + +#include <com/sun/star/uno/Reference.hxx> +#include <com/sun/star/io/XInputStream.hpp> +#include <com/sun/star/ucb/Lock.hpp> +#include "DAVResource.hxx" +#include <vector> + +namespace http_dav_ucp +{ +std::vector<css::ucb::Lock> +parseWebDAVLockResponse(const css::uno::Reference<css::io::XInputStream>& xInputStream); +std::vector<DAVResource> +parseWebDAVPropFindResponse(const css::uno::Reference<css::io::XInputStream>& xInputStream); +std::vector<DAVResourceInfo> +parseWebDAVPropNameResponse(const css::uno::Reference<css::io::XInputStream>& xInputStream); +} // namespace http_dav_ucp + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/ucb/source/ucp/webdav-curl/webdavresultset.cxx b/ucb/source/ucp/webdav-curl/webdavresultset.cxx new file mode 100644 index 000000000..e67dd1558 --- /dev/null +++ b/ucb/source/ucp/webdav-curl/webdavresultset.cxx @@ -0,0 +1,76 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +/************************************************************************** + TODO + ************************************************************************** + + - This implementation is not a dynamic result set!!! It only implements + the necessary interfaces, but never recognizes/notifies changes!!! + + *************************************************************************/ +#include "webdavresultset.hxx" + +using namespace com::sun::star; +using namespace http_dav_ucp; + + +// DynamicResultSet Implementation. + + +DynamicResultSet::DynamicResultSet( + const uno::Reference< uno::XComponentContext >& rxContext, + const rtl::Reference< Content >& rxContent, + const ucb::OpenCommandArgument2& rCommand, + const uno::Reference< ucb::XCommandEnvironment >& rxEnv ) +: ResultSetImplHelper( rxContext, rCommand ), + m_xContent( rxContent ), + m_xEnv( rxEnv ) +{ +} + + +// Non-interface methods. + + +void DynamicResultSet::initStatic() +{ + m_xResultSet1 + = new ::ucbhelper::ResultSet( m_xContext, + m_aCommand.Properties, + new DataSupplier( m_xContext, + m_xContent, + m_aCommand.Mode ), + m_xEnv ); +} + + +void DynamicResultSet::initDynamic() +{ + m_xResultSet1 + = new ::ucbhelper::ResultSet( m_xContext, + m_aCommand.Properties, + new DataSupplier( m_xContext, + m_xContent, + m_aCommand.Mode ), + m_xEnv ); + m_xResultSet2 = m_xResultSet1; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/ucb/source/ucp/webdav-curl/webdavresultset.hxx b/ucb/source/ucp/webdav-curl/webdavresultset.hxx new file mode 100644 index 000000000..0a52d7982 --- /dev/null +++ b/ucb/source/ucp/webdav-curl/webdavresultset.hxx @@ -0,0 +1,48 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + + +#pragma once + +#include <rtl/ref.hxx> +#include <ucbhelper/resultsethelper.hxx> +#include "webdavcontent.hxx" +#include "webdavdatasupplier.hxx" + +namespace http_dav_ucp { + +class DynamicResultSet : public ::ucbhelper::ResultSetImplHelper +{ + rtl::Reference< Content > m_xContent; + css::uno::Reference< css::ucb::XCommandEnvironment > m_xEnv; + +private: + virtual void initStatic() override; + virtual void initDynamic() override; + +public: + DynamicResultSet( const css::uno::Reference< css::uno::XComponentContext >& rxContext, + const rtl::Reference< Content >& rxContent, + const css::ucb::OpenCommandArgument2& rCommand, + const css::uno::Reference< css::ucb::XCommandEnvironment >& rxEnv ); +}; + +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ |