Quellcodebibliothek Statistik Leitseite products/Sources/formale Sprachen/C/LibreOffice/ucb/source/ucp/webdav-curl/   (Office von Apache Version 25.8.3.2©)  Datei vom 5.10.2025 mit Größe 162 kB image not shown  

Quelle  webdavcontent.cxx

  Sprache: C
 

/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/*
 * This file is part of the LibreOffice project.
 *
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
 *
 * This file incorporates work covered by the following license notice:
 *
 *   Licensed to the Apache Software Foundation (ASF) under one or more
 *   contributor license agreements. See the NOTICE file distributed
 *   with this work for additional information regarding copyright
 *   ownership. The ASF licenses this file to you under the Apache
 *   License, Version 2.0 (the "License"); you may not use this 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.emplace_back(
            u"Range"_ustr, // see <https://tools.ietf.org/html/rfc7233#section-3.1>
            u"bytes=0-0"_ustr);

    bool bIsRequestSize = std::any_of(aHeaderNames.begin(), aHeaderNames.end(),
        [](const OUString& rHeaderName) { return rHeaderName == "Content-Length"; });

    if ( bIsRequestSize )
    {
        // we need to know if the server accepts range requests for a resource
        // and the range unit it uses
        aHeaderNames.emplace_back("Accept-Ranges"); // see <https://tools.ietf.org/html/rfc7233#section-2.3>
        aHeaderNames.emplace_back("Content-Range"); // see <https://tools.ietf.org/html/rfc7233#section-4.2>
    }
    try
    {
        xResAccess->GET0( aPartialGet, aHeaderNames, aResource, xEnv );
        bError = false;

        if ( bIsRequestSize )
        {
            // the ContentProperties maps "Content-Length" to the UCB "Size" property
            // This would have an unrealistic value of 1 byte because we did only a partial GET
            // Solution: if "Content-Range" is present, map it with UCB "Size" property
            OUString aAcceptRanges, aContentRange, aContentLength;
            std::vector< DAVPropertyValue > &aResponseProps = aResource.properties;
            for ( const auto& rResponseProp : aResponseProps )
            {
                if ( rResponseProp.Name == "Accept-Ranges" )
                    rResponseProp.Value >>= aAcceptRanges;
                else if ( rResponseProp.Name == "Content-Range" )
                    rResponseProp.Value >>= aContentRange;
                else if ( rResponseProp.Name == "Content-Length" )
                    rResponseProp.Value >>= aContentLength;
            }

            sal_Int64 nSize = 1;
            if ( aContentLength.getLength() )
            {
                nSize = aContentLength.toInt64();
            }

            // according to http://tools.ietf.org/html/rfc2616#section-3.12
            // 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 u"com.sun.star.comp.ucb.WebDAVContent"_ustr;
}


// 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(
                                    u"Wrong argument type!"_ustr,
                                    getXWeak(),
                                    -1 ) ),
                Environment );
            // Unreachable
        }

        aRet <<= getPropertyValues( Properties, Environment );
    }
    else if ( aCommand.Name == "setPropertyValues" )
    {

        // setPropertyValues


        uno::Sequence< beans::PropertyValue > aProperties;
        if ( !( aCommand.Argument >>= aProperties ) )
        {
            ucbhelper::cancelCommandExecution(
                uno::Any( lang::IllegalArgumentException(
                                    u"Wrong argument type!"_ustr,
                                    getXWeak(),
                                    -1 ) ),
                Environment );
            // Unreachable
        }

        if ( !aProperties.getLength() )
        {
            ucbhelper::cancelCommandExecution(
                uno::Any( lang::IllegalArgumentException(
                                    u"No properties!"_ustr,
                                    getXWeak(),
                                    -1 ) ),
                Environment );
            // Unreachable
        }

        aRet <<= setPropertyValues( aProperties, Environment );
    }
    else if ( aCommand.Name == "getPropertySetInfo" )
    {

        // getPropertySetInfo


        // Note: Implemented by base class.
        aRet <<= getPropertySetInfo( Environment,
                                     false /* don't cache data */ );
    }
    else if ( aCommand.Name == "getCommandInfo" )
    {

        // getCommandInfo


        // Note: Implemented by base class.
        aRet <<= getCommandInfo( Environment, false );
    }
    else if ( aCommand.Name == "open" )
    {

        // open


        ucb::OpenCommandArgument3 aOpenCommand;
        ucb::OpenCommandArgument2 aTmp;
        if ( !( aCommand.Argument >>= aTmp ) )
        {
            ucbhelper::cancelCommandExecution(
                uno::Any( lang::IllegalArgumentException(
                                    u"Wrong argument type!"_ustr,
                                    getXWeak(),
                                    -1 ) ),
                Environment );
            // Unreachable
        }
        if ( !( aCommand.Argument >>= aOpenCommand ) )
        {
            // compat mode, extract Arg2 info into newer structure
            aOpenCommand.Mode = aTmp.Mode;
            aOpenCommand.Priority = aTmp.Priority;
            aOpenCommand.Sink = aTmp.Sink;
            aOpenCommand.Properties = aTmp.Properties;
            aOpenCommand.SortingInfo = aTmp.SortingInfo;
        }

        aRet = open( aOpenCommand, Environment );

    }
    else if ( aCommand.Name == "insert" )
    {

        // insert


        ucb::InsertCommandArgument arg;
        if ( !( aCommand.Argument >>= arg ) )
        {
            ucbhelper::cancelCommandExecution(
                uno::Any( lang::IllegalArgumentException(
                                    u"Wrong argument type!"_ustr,
                                    getXWeak(),
                                    -1 ) ),
                Environment );
            // Unreachable
        }

        insert( arg.Data, arg.ReplaceExisting, Environment );
    }
    else if ( aCommand.Name == "delete" )
    {

        // delete


        bool bDeletePhysical = false;
        aCommand.Argument >>= bDeletePhysical;

//  KSO: Ignore parameter and destroy the content, if you don't support
//       putting objects into trashcan. ( Since we do not have a trash can
//       service yet (src603), you actually have no other choice. )
//      if ( bDeletePhysical )
//  {
        try
        {
            std::unique_ptr< DAVResourceAccess > xResAccess;
            {
                osl::Guard< osl::Mutex > aGuard( m_aMutex );
                xResAccess.reset( new DAVResourceAccess( *m_xResAccess ) );
            }
            aStaticDAVOptionsCache.removeDAVOptions( xResAccess->getURL() );
            // clean cached value of PROPFIND property names
            removeCachedPropertyNames( xResAccess->getURL() );
            xResAccess->DESTROY( Environment );
            {
                osl::Guard< osl::Mutex > aGuard( m_aMutex );
                m_xResAccess.reset( new DAVResourceAccess( *xResAccess ) );
            }
        }
        catch ( DAVException const & e )
        {
            cancelCommandExecution( e, Environment, true );
            // Unreachable
        }
//      }

        // Propagate destruction.
        destroy( bDeletePhysical );

        // Remove own and all children's Additional Core Properties.
        removeAdditionalPropertySet();
    }
    else if ( aCommand.Name == "transfer" && isFolder( Environment ) )
    {

        // transfer
        //  ( Not available at documents )


        ucb::TransferInfo transferArgs;
        if ( !( aCommand.Argument >>= transferArgs ) )
        {
            ucbhelper::cancelCommandExecution(
                uno::Any( lang::IllegalArgumentException(
                                  u"Wrong argument type!"_ustr,
                                  getXWeak(),
                                  -1 ) ),
                Environment );
            // Unreachable
        }

        transfer( transferArgs, Environment );
    }
    else if ( aCommand.Name == "post" )
    {

        // post


        ucb::PostCommandArgument2 aArg;
        if ( !( aCommand.Argument >>= aArg ) )
        {
            ucbhelper::cancelCommandExecution(
                uno::Any( lang::IllegalArgumentException(
                                    u"Wrong argument type!"_ustr,
                                    getXWeak(),
                                    -1 ) ),
                Environment );
            // Unreachable
        }

        post( aArg, Environment );
    }
    else if ( aCommand.Name == "lock" )
    {

        // lock

        ResourceType eType = resourceTypeForLocks( Environment );
        // when the resource is not yet present the lock is used to create it
        // see: http://tools.ietf.org/html/rfc4918#section-7.3
        // If the resource doesn't exists and the lock is not enabled (DAV with
        // no lock or a simple web) the error will be dealt with inside lock() method
        if ( eType == NOT_FOUND ||
            eType == DAV )
        {
            lock( Environment );
            if ( eType == NOT_FOUND )
            {
                m_eResourceType = UNKNOWN;  // lock may have created it, need to check again
                m_eResourceTypeForLocks = UNKNOWN;
            }
        }
    }
    else if ( aCommand.Name == "unlock" )
    {

        // unlock
        // do not check for a DAV resource
        // the lock store will be checked before sending
        unlock( Environment );
    }
    else if ( aCommand.Name == "createNewContent" &&
              isFolder( Environment ) )
    {

        // createNewContent


        ucb::ContentInfo aArg;
        if ( !( aCommand.Argument >>= aArg ) )
        {
            ucbhelper::cancelCommandExecution(
                uno::Any( lang::IllegalArgumentException(
                                    u"Wrong argument type!"_ustr,
                                    getXWeak(),
                                    -1 ) ),
                Environment );
            // Unreachable
        }

        aRet <<= createNewContent( aArg );
    }
    else if ( aCommand.Name == "addProperty" )
    {
        ucb::PropertyCommandArgument aPropArg;
        if ( !( aCommand.Argument >>= aPropArg ))
        {
            ucbhelper::cancelCommandExecution(
                uno::Any( lang::IllegalArgumentException(
                                    u"Wrong argument type!"_ustr,
                                    getXWeak(),
                                    -1 ) ),
                Environment );
        }

        // TODO when/if XPropertyContainer is removed,
        // the command execution can be canceled in addProperty
        try
        {
            addProperty( aPropArg, Environment );
        }
        catch ( const beans::PropertyExistException &e )
        {
            ucbhelper::cancelCommandExecution( uno::Any( e ), Environment );
        }
        catch ( const beans::IllegalTypeException&e )
        {
            ucbhelper::cancelCommandExecution( uno::Any( e ), Environment );
        }
        catch ( const lang::IllegalArgumentException&e )
        {
            ucbhelper::cancelCommandExecution( uno::Any( e ), Environment );
        }
    }
    else if ( aCommand.Name == "removeProperty" )
    {
        OUString sPropName;
        if ( !( aCommand.Argument >>= sPropName ) )
        {
            ucbhelper::cancelCommandExecution(
                uno::Any( lang::IllegalArgumentException(
                                    u"Wrong argument type!"_ustr,
                                    getXWeak(),
                                    -1 ) ),
                Environment );
        }

        // TODO when/if XPropertyContainer is removed,
        // the command execution can be canceled in removeProperty
        try
        {
            removeProperty( sPropName, Environment );
        }
        catchconst beans::UnknownPropertyException &e )
        {
            ucbhelper::cancelCommandExecution( uno::Any( e ), Environment );
        }
        catchconst beans::NotRemoveableException &e )
        {
            ucbhelper::cancelCommandExecution( uno::Any( e ), Environment );
        }
    }
    else
    {

        // Unsupported command


        ucbhelper::cancelCommandExecution(
            uno::Any( ucb::UnsupportedCommandException(
                              aCommand.Name,
                              getXWeak() ) ),
            Environment );
        // Unreachable
    }

    SAL_INFO("ucb.ucp.webdav",  "<<<<< Content::execute: end: command: " << aCommand.Name);

    return aRet;
}


// virtual
void SAL_CALL Content::abort( sal_Int32 /*CommandId*/ )
{
    try
    {
        std::unique_ptr< DAVResourceAccess > xResAccess;
        {
            osl::MutexGuard aGuard( m_aMutex );
            xResAccess.reset( new DAVResourceAccess( *m_xResAccess ) );
        }
        xResAccess->abort();
        {
            osl::Guard< osl::Mutex > aGuard( m_aMutex );
            m_xResAccess.reset( new DAVResourceAccess( *xResAccess ) );
        }
    }
    catch ( DAVException const & )
    {
        // abort failed!
    }
}


// XPropertyContainer methods.


void Content::addProperty( const css::ucb::PropertyCommandArgument &aCmdArg,
                           const uno::Reference< ucb::XCommandEnvironment >& xEnv  )
{
//    if ( m_bTransient )
//   @@@ ???
    const beans::Property aProperty = aCmdArg.Property;
    const uno::Any aDefaultValue = aCmdArg.DefaultValue;

    // check property Name
    if ( !aProperty.Name.getLength() )
        throw lang::IllegalArgumentException(
            u"\"addProperty\" with empty Property.Name"_ustr,
            getXWeak(),
            -1 );

    // Check property type.
    if ( !UCBDeadPropertyValue::supportsType( aProperty.Type ) )
        throw beans::IllegalTypeException(
            u"\"addProperty\" unsupported Property.Type"_ustr,
            getXWeak() );

    // check default value
    if ( aDefaultValue.hasValue() && aDefaultValue.getValueType() != aProperty.Type )
        throw beans::IllegalTypeException(
            u"\"addProperty\" DefaultValue does not match Property.Type"_ustr,
            getXWeak() );


    // Make sure a property with the requested name does not already
    // exist in dynamic and static(!) properties.


    // Take into account special properties with custom namespace
    // using <prop:the_propname xmlns:prop="the_namespace">
    OUString aSpecialName;
    bool bIsSpecial = DAVProperties::isUCBSpecialProperty( aProperty.Name, aSpecialName );

    // Note: This requires network access!
    if ( getPropertySetInfo( xEnv, false /* don't cache data */ )
            ->hasPropertyByName( bIsSpecial ? aSpecialName : aProperty.Name ) )
    {
        // Property does already exist.
        throw beans::PropertyExistException();
    }


    // Add a new dynamic property.


    ProppatchValue aValue( PROPSET, aProperty.Name, aDefaultValue );

    std::vector< ProppatchValue > aProppatchValues;
    aProppatchValues.push_back( aValue );

    try
    {
        // Set property value at server.
        std::unique_ptr< DAVResourceAccess > xResAccess;
        {
            osl::Guard< osl::Mutex > aGuard( m_aMutex );
            xResAccess.reset( new DAVResourceAccess( *m_xResAccess ) );
        }
        aStaticDAVOptionsCache.removeDAVOptions( xResAccess->getURL() );
        // clean cached value of PROPFIND property names
        // PROPPATCH can change them
        removeCachedPropertyNames( xResAccess->getURL() );
        xResAccess->PROPPATCH( aProppatchValues, xEnv );
        {
            osl::Guard< osl::Mutex > aGuard( m_aMutex );
            m_xResAccess.reset( new DAVResourceAccess( *xResAccess ) );
        }

        // Notify propertyset info change listeners.
        beans::PropertySetInfoChangeEvent evt(
            getXWeak(),
            bIsSpecial ? aSpecialName : aProperty.Name,
            -1, // No handle available
            beans::PropertySetInfoChange::PROPERTY_INSERTED );
        notifyPropertySetInfoChange( evt );
    }
    catch ( DAVException const & e )
    {
        if ( e.getStatus() == SC_FORBIDDEN )
        {
            // Support for setting arbitrary dead properties is optional!

            // Store property locally.
            ContentImplHelper::addProperty( bIsSpecial ? aSpecialName : aProperty.Name,
                                            aProperty.Attributes,
                                            aDefaultValue );
        }
        else
        {
            if ( shouldAccessNetworkAfterException( e ) )
            {
                try
                {
                    const ResourceType eType = getResourceType( xEnv );
                    switch ( eType )
                    {
                    case UNKNOWN:
                    case DAV:
                        throw lang::IllegalArgumentException();

                    case NON_DAV:
                        // Store property locally.
                        ContentImplHelper::addProperty( bIsSpecial ? aSpecialName : aProperty.Name,
                                                        aProperty.Attributes,
                                                        aDefaultValue );
                        break;

                    default:
                        SAL_WARN( "ucb.ucp.webdav",
                                    "Content::addProperty - "
                                    "Unsupported resource type!" );
                        break;
                    }
                }
                catch ( uno::Exception const & )
                {
                    SAL_WARN( "ucb.ucp.webdav",
                                "Content::addProperty - "
                                "Unable to determine resource type!" );
                }
            }
            else
            {
                SAL_WARN( "ucb.ucp.webdav",
                            "Content::addProperty - "
                            "Unable to determine resource type!" );
            }
        }
    }
}

void Content::removeProperty( const OUString& Name,
                              const uno::Reference< ucb::XCommandEnvironment >& xEnv )
{
#if 0
    // @@@ REMOVABLE at the moment not properly set in the PropSetInfo
    try
    {
        beans::Property aProp
        = getPropertySetInfo( xEnv, false /* don't cache data */ )
          ->getPropertyByName( Name );

        if ( !( aProp.Attributes & beans::PropertyAttribute::REMOVABLE ) )
        {
            // Not removable!
            throw beans::NotRemoveableException();
        }
    }
    catch ( beans::UnknownPropertyException const & )
    {
        //SAL_WARN( "ucb.ucp.webdav", "removeProperty - Unknown property!" );
        throw;
    }
#endif

    // Try to remove property from server.
    try
    {
        std::vector< ProppatchValue > aProppatchValues;
        ProppatchValue aValue( PROPREMOVE, Name, uno::Any() );
        aProppatchValues.push_back( aValue );

        // Remove property value from server.
        std::unique_ptr< DAVResourceAccess > xResAccess;
        {
            osl::Guard< osl::Mutex > aGuard( m_aMutex );
            xResAccess.reset( new DAVResourceAccess( *m_xResAccess ) );
        }
        aStaticDAVOptionsCache.removeDAVOptions( xResAccess->getURL() );
        // clean cached value of PROPFIND property names
        // PROPPATCH can change them
        removeCachedPropertyNames( xResAccess->getURL() );
        xResAccess->PROPPATCH( aProppatchValues, xEnv );
        {
            osl::Guard< osl::Mutex > aGuard( m_aMutex );
            m_xResAccess.reset( new DAVResourceAccess( *xResAccess ) );
        }

        // Notify propertyset info change listeners.
        beans::PropertySetInfoChangeEvent evt(
            getXWeak(),
            Name,
            -1, // No handle available
            beans::PropertySetInfoChange::PROPERTY_REMOVED );
        notifyPropertySetInfoChange( evt );
    }
    catch ( DAVException const & e )
    {
        if ( e.getStatus() == SC_FORBIDDEN )
        {
            // Support for setting arbitrary dead properties is optional!

            // Try to remove property from local store.
            ContentImplHelper::removeProperty( Name );
        }
        else
        {
            if ( shouldAccessNetworkAfterException( e ) )
            {
                try
                {
                    const ResourceType eType = getResourceType( xEnv );
                    switch ( eType )
                    {
                    case UNKNOWN:
                    case DAV:
                        throw beans::UnknownPropertyException(Name);

                    case NON_DAV:
                        // Try to remove property from local store.
                        ContentImplHelper::removeProperty( Name );
                        break;

                    default:
                        SAL_WARN( "ucb.ucp.webdav",
                                    "Content::removeProperty - "
                                    "Unsupported resource type!" );
                        break;
                    }
                }
                catch ( uno::Exception const & )
                {
                    SAL_WARN( "ucb.ucp.webdav",
                                "Content::removeProperty - "
                                "Unable to determine resource type!" );
                }
            }
            else
            {
                SAL_WARN( "ucb.ucp.webdav",
                            "Content::removeProperty - "
                            "Unable to determine resource type!" );
//                throw beans::UnknownPropertyException();
            }
        }
    }
}

// virtual
void SAL_CALL Content::addProperty( const OUString& Name,
                                    sal_Int16 Attributes,
                                    const uno::Any& DefaultValue )
{
    beans::Property aProperty;
    aProperty.Name = Name;
    aProperty.Type = DefaultValue.getValueType();
    aProperty.Attributes = Attributes;
    aProperty.Handle = -1;

    addProperty( ucb::PropertyCommandArgument( aProperty, DefaultValue ),
                 uno::Reference< ucb::XCommandEnvironment >());
}

// virtual
void SAL_CALL Content::removeProperty( const OUString& Name )
{
    removeProperty( Name,
                    uno::Reference< ucb::XCommandEnvironment >() );
}


// XContentCreator methods.


// virtual
uno::Sequence< ucb::ContentInfo > SAL_CALL
Content::queryCreatableContentsInfo()
{
    osl::Guard< osl::Mutex > aGuard( m_aMutex );

    uno::Sequence< ucb::ContentInfo > aSeq( 2 );

    // document.
    aSeq.getArray()[ 0 ].Type = WEBDAV_CONTENT_TYPE;
    aSeq.getArray()[ 0 ].Attributes
        = ucb::ContentInfoAttribute::INSERT_WITH_INPUTSTREAM
          | ucb::ContentInfoAttribute::KIND_DOCUMENT;

    beans::Property aProp;
    m_pProvider->getProperty( u"Title"_ustr, aProp );

    uno::Sequence< beans::Property > aDocProps( 1 );
    aDocProps.getArray()[ 0 ] = aProp;
    aSeq.getArray()[ 0 ].Properties = std::move(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] = std::move(aProp);
    aSeq.getArray()[ 1 ].Properties = std::move(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, u"405 Not Implemented"_ustr, 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;

                const 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.emplace_back("Content-Type");
                }

                if (!aHeaderNames.empty()) try
                {
                    DAVResource resource;
                    GetPropsUsingHeadRequest(resource, xResAccess, aHeaderNames, xEnv);
                    m_bDidGetOrHead = true;

                    if (xProps)
                        xProps->addProperties(
                            aMissingProps,
                            ContentProperties( resource ) );
                    else
                        xProps.reset ( new ContentProperties( resource ) );

                    if (m_eResourceType == NON_DAV)
                        xProps->addProperties(aMissingProps,
                            ContentProperties(
                                aUnescapedTitle,
                                false));
                }
                catch ( DAVException const & e )
                {
                    // non "general-purpose servers" may not support HEAD requests
                    // see http://www.w3.org/Protocols/rfc2616/rfc2616-sec5.html#sec5.1.1
                    // In this case, perform a partial GET only to get the header info
                    // vid. http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.35
                    // WARNING if the server does not support partial GETs,
                    // the GET will transfer the whole content
                    bool bError = true;
                    DAVException aLastException = e;
                    OUString aTargetURL = xResAccess->getURL();

                    if ( e.getError() == DAVException::DAV_HTTP_ERROR )
                    {
                        // According to the spec. the origin server SHOULD return
                        // * 405 (Method Not Allowed):
                        //      the method is known but not allowed for the requested resource
                        // * 501 (Not Implemented):
                        //      the method is unrecognized or not implemented
                        // * 404 (SC_NOT_FOUND)
                        //      is for google-code server and for MS IIS 10.0 Web server
                        //      when only GET is enabled
                        if ( aLastException.getStatus() == SC_NOT_IMPLEMENTED ||
                             aLastException.getStatus() == SC_METHOD_NOT_ALLOWED ||
                             aLastException.getStatus() == SC_NOT_FOUND )
                        {
                            SAL_WARN( "ucb.ucp.webdav""HEAD probably not implemented: fall back to a partial GET" );
                            aStaticDAVOptionsCache.setHeadAllowed( aTargetURL, false );
                            lcl_sendPartialGETRequest( bError,
                                                       aLastException,
                                                       aMissingProps,
                                                       aHeaderNames,
                                                       xResAccess,
                                                       xProps,
                                                       xEnv );
                            m_bDidGetOrHead = !bError;
                        }
                    }

                    if ( bError )
                    {
                        DAVOptions aDAVOptionsException;

                        aDAVOptionsException.setURL( aTargetURL );
                        // check if the error was SC_NOT_FOUND, meaning that the
                        // GET fall back didn't succeeded and the element is really missing
                        // we will consider the resource SC_GONE (410) for some time
                        // we use SC_GONE because has the same meaning of SC_NOT_FOUND (404)
                        // see:
                        // <https://tools.ietf.org/html/rfc7231#section-6.5.9> (retrieved 2016-10-09)
                        // apparently it's not used to mark the missing HEAD method (so far...)
                        sal_uInt16 ResponseStatusCode =
                            ( aLastException.getStatus() == SC_NOT_FOUND ) ?
                            SC_GONE :
                            aLastException.getStatus();
                        aDAVOptionsException.setHttpResponseStatusCode( ResponseStatusCode );
                        aDAVOptionsException.setHttpResponseStatusText( aLastException.getData() );
                        aStaticDAVOptionsCache.addDAVOptions( aDAVOptionsException,
                                                              m_nOptsCacheLifeNotFound );

                        if ( !shouldAccessNetworkAfterException( aLastException ) )
                        {
                            cancelCommandExecution( aLastException, xEnv );
                            // unreachable
                        }
                    }
                }
            }
        }

        // might trigger HTTP redirect.
        // Therefore, title must be updated here.
        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(u"Title"_ustr, uno::Any(aUnescapedTitle), true);
        }
        else
        {
            if (!xProps)
                xProps.reset( new ContentProperties( aUnescapedTitle, false ) );
            else
                xProps->addProperty(
                    u"Title"_ustr,
                    uno::Any( aUnescapedTitle ),
                    true );

            xProps->addProperty(
                u"IsFolder"_ustr,
                uno::Any( false ),
                true );
            xProps->addProperty(
                u"IsDocument"_ustr,
                uno::Any( true ),
                true );
            xProps->addProperty(
                u"ContentType"_ustr,
                uno::Any( WEBDAV_CONTENT_TYPE ),
                true );
        }
    }
    else
    {
        // No server access for just created (not yet committed) objects.
        // Only a minimal set of properties supported at this stage.
        if (m_bTransient)
            xProps.reset( new ContentProperties( aUnescapedTitle,
                                                 m_bCollection ) );
    }

    // Add a default for the properties requested but not found.
    // Determine still missing properties, add a default.
    // Some client function doesn't expect a void uno::Any,
    // but instead wants some sort of default.
    std::vector< OUString > aMissingProps;
    if ( !xProps->containsAllNames(
                rProperties, aMissingProps ) )
    {
        //
        for ( std::vector< rtl::OUString >::const_iterator it = aMissingProps.begin();
              it != aMissingProps.end(); ++it )
        {
            // For the time being only a couple of properties need to be added
            if ( (*it) == "DateModified"  || (*it) == "DateCreated" )
            {
                util::DateTime aDate;
                xProps->addProperty(
                    (*it),
                    uno::Any( aDate ),
                    true );
            }
            else if (bNetworkAccessAllowed) // don't set these if connection failed
            {
                // If WebDAV didn't return the resource type, assume default
                // This happens e.g. for lists exported by SharePoint
                if ((*it) == "IsFolder")
                {
                    xProps->addProperty(
                        (*it),
                        uno::Any( false ),
                        true );
                }
                else if ((*it) == "IsDocument")
                {
                    xProps->addProperty(
                        (*it),
                        uno::Any( true ),
                        true );
                }
            }
        }
    }

    sal_Int32 nCount = rProperties.getLength();
    for ( sal_Int32 n = 0; n < nCount; ++n )
    {
        const OUString rName = rProperties[ n ].Name;
        if ( rName == "BaseURI" )
        {
            // Add BaseURI property, if requested.
            xProps->addProperty(
                 u"BaseURI"_ustr,
                 uno::Any( getBaseURI( xResAccess ) ),
                 true );
        }
        else if ( rName == "CreatableContentsInfo" )
        {
            // Add CreatableContentsInfo property, if requested.
            bool bFolder = false;
            xProps->getValue( u"IsFolder"_ustr )
                    >>= bFolder;
            xProps->addProperty(
                u"CreatableContentsInfo"_ustr,
                uno::Any( bFolder
                                  ? queryCreatableContentsInfo()
                                  : uno::Sequence< ucb::ContentInfo >() ),
                true );
        }
    }

    uno::Reference< sdbc::XRow > xResultRow
        = getPropertyValues( xContext,
                             rProperties,
                             *xProps,
                             xProvider,
                             xIdentifier->getContentIdentifier() );

    {
        osl::Guard< osl::Mutex > aGuard( m_aMutex );

        if (!m_xCachedProps)
            m_xCachedProps.reset( new CachableContentProperties( *xProps ) );
        else
            m_xCachedProps->addProperties( *xProps );

        m_xResAccess.reset( new DAVResourceAccess( *xResAccess ) );
        m_aEscapedTitle = EncodeSegment(aUnescapedTitle);
    }

    return xResultRow;
}


uno::Sequence< uno::Any > Content::setPropertyValues(
                const uno::Sequence< beans::PropertyValue >& rValues,
                const uno::Reference< ucb::XCommandEnvironment >& xEnv )
{
    uno::Reference< ucb::XContentIdentifier >    xIdentifier;
    rtl::Reference< ContentProvider >            xProvider;
    bool bTransient;
    std::unique_ptr< DAVResourceAccess > xResAccess;

    {
        osl::Guard< osl::Mutex > aGuard( m_aMutex );

        xProvider.set( m_pProvider );
        xIdentifier.set( m_xIdentifier );
        bTransient = m_bTransient;
        xResAccess.reset( new DAVResourceAccess( *m_xResAccess ) );
    }

    uno::Sequence< uno::Any > aRet( rValues.getLength() );
    auto aRetRange = asNonConstRange(aRet);
    uno::Sequence< beans::PropertyChangeEvent > aChanges( rValues.getLength() );
    sal_Int32 nChanged = 0;

    beans::PropertyChangeEvent aEvent;
    aEvent.Source         = getXWeak();
    aEvent.Further        = false;
    // aEvent.PropertyName =
    aEvent.PropertyHandle = -1;
    // aEvent.OldValue   =
    // aEvent.NewValue   =

    std::vector< ProppatchValue > aProppatchValues;
    std::vector< sal_Int32 > aProppatchPropsPositions;

    uno::Reference< ucb::XPersistentPropertySet > xAdditionalPropSet;
    bool bTriedToGetAdditionalPropSet = false;

    bool bExchange = false;
    OUString aNewTitle;
    OUString aOldTitle;
    sal_Int32 nTitlePos = -1;

    uno::Reference< beans::XPropertySetInfo > xInfo;

    const beans::PropertyValue* pValues = rValues.getConstArray();
    sal_Int32 nCount = rValues.getLength();
    for ( sal_Int32 n = 0; n < nCount; ++n )
    {
        const beans::PropertyValue& rValue = pValues[ n ];
        const OUString & rName = rValue.Name;

        beans::Property aTmpProp;
        xProvider->getProperty( rName, aTmpProp );

        if ( aTmpProp.Attributes & beans::PropertyAttribute::READONLY )
        {
            // Read-only property!
            aRetRange[ n ] <<= lang::IllegalAccessException(
                            u"Property is read-only!"_ustr,
                            getXWeak() );
            continue;
        }


        // Mandatory props.


        if ( rName == "ContentType" )
        {
            // Read-only property!
            aRetRange[ n ] <<= lang::IllegalAccessException(
                u"Property is read-only!"_ustr,
                getXWeak() );
        }
        else if ( rName == "IsDocument" )
        {
            // Read-only property!
            aRetRange[ n ] <<= lang::IllegalAccessException(
                u"Property is read-only!"_ustr,
                getXWeak() );
        }
        else if ( rName == "IsFolder" )
        {
            // Read-only property!
            aRetRange[ n ] <<= lang::IllegalAccessException(
                            u"Property is read-only!"_ustr,
                            getXWeak() );
        }
        else if ( rName == "Title" )
        {
            OUString aNewValue;
            if ( rValue.Value >>= aNewValue )
            {
                // No empty titles!
                if ( aNewValue.getLength() > 0 )
                {
                    try
                    {
                        CurlUri const aURI(xIdentifier->getContentIdentifier());
                        aOldTitle = aURI.GetPathBaseNameUnescaped();

                        if ( aNewValue != aOldTitle )
                        {
                            // modified title -> modified URL -> exchange !
                            if ( !bTransient )
                                bExchange = true;

                            // new value will be set later...
                            aNewTitle = aNewValue;

                            // remember position within sequence of values (for
                            // error handling).
                            nTitlePos = n;
                        }
                    }
                    catch ( DAVException const & )
                    {
                        aRetRange[ n ] <<= lang::IllegalArgumentException(
                            u"Invalid content identifier!"_ustr,
                            getXWeak(),
                            -1 );
                    }
                }
                else
                {
                    aRetRange[ n ] <<= lang::IllegalArgumentException(
                        u"Empty title not allowed!"_ustr,
                        getXWeak(),
                        -1 );
                }
            }
            else
            {
                aRetRange[ n ] <<= beans::IllegalTypeException(
                    u"Property value has wrong type!"_ustr,
                    getXWeak() );
            }
        }
        else
        {

            // Optional props.


            OUString aSpecialName;
            bool bIsSpecial = DAVProperties::isUCBSpecialProperty( rName, aSpecialName );

            if ( !xInfo.is() )
                xInfo = getPropertySetInfo( xEnv,
                                            false /* don't cache data */ );

            if ( !xInfo->hasPropertyByName( bIsSpecial ? aSpecialName : rName ) )
            {
                // Check, whether property exists. Skip otherwise.
                // PROPPATCH::set would add the property automatically, which
                // is not allowed for "setPropertyValues" command!
                aRetRange[ n ] <<= beans::UnknownPropertyException(
                                u"Property is unknown!"_ustr,
                                getXWeak() );
                continue;
            }

            if ( rName == "Size" )
            {
                // Read-only property!
                aRetRange[ n ] <<= lang::IllegalAccessException(
                                u"Property is read-only!"_ustr,
                                getXWeak() );
            }
            else if ( rName == "DateCreated" )
            {
                // Read-only property!
                aRetRange[ n ] <<= lang::IllegalAccessException(
                                u"Property is read-only!"_ustr,
                                getXWeak() );
            }
            else if ( rName == "DateModified" )
            {
                // Read-only property!
                aRetRange[ n ] <<= lang::IllegalAccessException(
                                u"Property is read-only!"_ustr,
                                getXWeak() );
            }
            else if ( rName == "MediaType" )
            {
                // Read-only property!
                // (but could be writable, if 'getcontenttype' would be)
                aRetRange[ n ] <<= lang::IllegalAccessException(
                                u"Property is read-only!"_ustr,
                                getXWeak() );
            }
            if ( rName == "CreatableContentsInfo" )
            {
                // Read-only property!
                aRetRange[ n ] <<= lang::IllegalAccessException(
                                u"Property is read-only!"_ustr,
                                getXWeak() );
            }
            else
            {
                if ( getResourceType( xEnv, xResAccess ) == DAV )
                {
                    // Property value will be set on server.
                    ProppatchValue aValue( PROPSET, rName, rValue.Value );
                    aProppatchValues.push_back( aValue );

                    // remember position within sequence of values (for
                    // error handling).
                    aProppatchPropsPositions.push_back( n );
                }
                else
                {
                    // Property value will be stored in local property store.
                    if ( !bTriedToGetAdditionalPropSet &&
                         !xAdditionalPropSet.is() )
                    {
                        xAdditionalPropSet
                            = getAdditionalPropertySet( false );
                        bTriedToGetAdditionalPropSet = true;
                    }

                    if ( xAdditionalPropSet.is() )
                    {
                        try
                        {
                            uno::Any aOldValue
                                = xAdditionalPropSet->getPropertyValue( rName );
                            if ( aOldValue != rValue.Value )
                            {
                                xAdditionalPropSet->setPropertyValue(
                                                        rName, rValue.Value );

                                aEvent.PropertyName = rName;
                                aEvent.OldValue     = std::move(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(
                                u"No property set for storing the value!"_ustr,
                                getXWeak() );
                    }
                }
            }
        }
    } // for

    if ( !bTransient && (!aProppatchValues.empty()) )
    {
        try
        {
            // clean cached value of PROPFIND property names
            // PROPPATCH can change them
            removeCachedPropertyNames( xResAccess->getURL() );
            // Set property values at server.
            aStaticDAVOptionsCache.removeDAVOptions( xResAccess->getURL() );
            xResAccess->PROPPATCH( aProppatchValues, xEnv );

            for ( const auto& rProppatchValue : aProppatchValues )
            {
                aEvent.PropertyName = rProppatchValue.name;
                aEvent.OldValue     = uno::Any(); // @@@ too expensive to obtain!
                aEvent.NewValue     = rProppatchValue.value;

                aChanges.getArray()[ nChanged ] = aEvent;
                nChanged++;
            }
        }
        catch ( DAVException const & e )
        {
//            SAL_WARN( "ucb.ucp.webdav",
//                        "Content::setPropertyValues - PROPPATCH failed!" );

#if 1
            cancelCommandExecution( e, xEnv );
            // unreachable
#else
            // Note: PROPPATCH either sets ALL property values OR NOTHING.

            std::vector< sal_Int32 >::const_iterator it
                = aProppatchPropsPositions.begin();
            std::vector< sal_Int32 >::const_iterator end
                = aProppatchPropsPositions.end();

            while ( it != end )
            {
                // Set error.
                aRetRange[ (*it) ] <<= MapDAVException( e, true );
                ++it;
            }
#endif
        }
    }

    if ( bExchange )
    {
        // Assemble new content identifier...

        OUString aNewURL = getParentURL();
        if ( aNewURL.lastIndexOf( '/' ) != ( aNewURL.getLength() - 1 ) )
            aNewURL += "/";

        aNewURL += EncodeSegment(aNewTitle);

        uno::Reference< ucb::XContentIdentifier > xNewId
            = new ::ucbhelper::ContentIdentifier( aNewURL );
        const 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(
                    u"Exchange failed!"_ustr,
                    getXWeak() );
            }
        }
        catch ( DAVException const & e )
        {
            // Do not set new title!
            aNewTitle.clear();

            // Set error .
            aRetRange[ nTitlePos ] = MapDAVException( e, true );
        }
    }

    if ( aNewTitle.getLength() )
    {
        osl::Guard< osl::Mutex > aGuard( m_aMutex );

        aEvent.PropertyName = "Title";
        aEvent.OldValue     <<= aOldTitle;
        aEvent.NewValue     <<= aNewTitle;

        m_aEscapedTitle     = EncodeSegment(aNewTitle);

        aChanges.getArray()[ nChanged ] = std::move(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(
                        u"Non-folder resource cannot be opened as folder! Wrong Open Mode!"_ustr,
                        getXWeak(),
                        -1 ) ),
                xEnv );
            // Unreachable
        }
    }

    if ( rArg.Sink.is() )
    {
        // Open document.

        if ( ( rArg.Mode == ucb::OpenMode::DOCUMENT_SHARE_DENY_NONE ) ||
             ( rArg.Mode == ucb::OpenMode::DOCUMENT_SHARE_DENY_WRITE ) )
        {
            // Currently(?) unsupported.
            ucbhelper::cancelCommandExecution(
                uno::Any(
                    ucb::UnsupportedOpenModeException(
                            OUString(),
                            getXWeak(),
                            sal_Int16( rArg.Mode ) ) ),
                xEnv );
            // Unreachable
        }

        uno::Reference< io::XOutputStream > xOut( rArg.Sink, uno::UNO_QUERY );
        if ( xOut.is() )
        {
            // PUSH: write data
            try
            {
                std::unique_ptr< DAVResourceAccess > xResAccess;

                {
                    osl::MutexGuard aGuard( m_aMutex );

                    xResAccess.reset(
                        new DAVResourceAccess( *m_xResAccess ) );
                }

                xResAccess->setFlags( rArg.OpeningFlags );
                DAVResource aResource;
                std::vector< OUString > aHeaders;

                removeCachedPropertyNames( xResAccess->getURL() );
                xResAccess->GET( xOut, aHeaders, aResource, xEnv );
                m_bDidGetOrHead = true;

                {
                    osl::MutexGuard aGuard( m_aMutex );

                    // cache headers.
                    if (!m_xCachedProps)
                        m_xCachedProps.reset(
                            new CachableContentProperties( ContentProperties( aResource ) ) );
                    else
                        m_xCachedProps->addProperties( ContentProperties( aResource ) );

                    m_xResAccess.reset(
                        new DAVResourceAccess( *xResAccess ) );
                }
            }
            catch ( DAVException const & e )
            {
                cancelCommandExecution( e, xEnv );
                // Unreachable
            }
        }
        else
        {
            uno::Reference< io::XActiveDataSink > xDataSink( rArg.Sink, uno::UNO_QUERY );
            if ( xDataSink.is() )
            {
                // PULL: wait for client read
                OUString aTargetURL =  m_xIdentifier->getContentIdentifier();
                try
                {
                    std::unique_ptr< DAVResourceAccess > xResAccess;
                    {
                        osl::MutexGuard aGuard( m_aMutex );

                        xResAccess.reset(
                            new DAVResourceAccess( *m_xResAccess ) );
                    }
                    xResAccess->setFlags( rArg.OpeningFlags );

                    // fill inputstream sync; return if all data present
                    DAVResource aResource;
                    std::vector< OUString > aHeaders;

                    aTargetURL = xResAccess->getURL();
                    removeCachedPropertyNames( aTargetURL );
                    // check if the resource was present on the server
                    // first update it, if necessary
                    // if the open is called directly, without the default open sequence,
                    // e.g. the one used when opening a file looking for properties
                    // first this call will have no effect, since OPTIONS would have already been called
                    // as a consequence of getPropertyValues()
                    DAVOptions aDAVOptions;
                    getResourceOptions( xEnv, aDAVOptions, xResAccess );

                    if (aDAVOptions.getHttpResponseStatusCode() != SC_NONE
                        // tdf#148426 fall back to GET in case of 500
                        && aDAVOptions.getHttpResponseStatusCode() != SC_INTERNAL_SERVER_ERROR)
                    {
                        // throws exception as if there was a server error, a DAV exception
                        switch (aDAVOptions.getHttpResponseStatusCode())
                        {
                            case USC_CONNECT_FAILED:
                            {
                                CurlUri aUri(aTargetURL);
                                throw DAVException(
                                    DAVException::DAV_HTTP_CONNECT,
                                    ConnectionEndPointString(aUri.GetHost(), aUri.GetPort()),
                                    aDAVOptions.getHttpResponseStatusText());
                            }
                            default:
                                break;
                        }
                        throw DAVException( DAVException::DAV_HTTP_ERROR,
                                            aDAVOptions.getHttpResponseStatusText(),
                                            aDAVOptions.getHttpResponseStatusCode() );
                    }
                    uno::Reference< io::XInputStream > xIn
                        = xResAccess->GET( aHeaders, aResource, xEnv );
                    m_bDidGetOrHead = true;

                    {
                        osl::MutexGuard aGuard( m_aMutex );

                        // cache headers.
                        if (!m_xCachedProps)
                            m_xCachedProps.reset(
                                new CachableContentProperties( ContentProperties( aResource ) ) );
                        else
                            m_xCachedProps->addProperties(
                                aResource.properties );

                        m_xResAccess.reset(
                            new DAVResourceAccess( *xResAccess ) );
                    }

                    xDataSink->setInputStream( xIn );
                }
                catch ( DAVException const & e )
                {
                    //TODO cache the http error if not yet cached
                    cancelCommandExecution( e, xEnv );
                    // Unreachable
                }
            }
            else
            {
                // Note: aOpenCommand.Sink may contain an XStream
                //       implementation. Support for this type of
                //       sink is optional...
                ucbhelper::cancelCommandExecution(
                    uno::Any(
                        ucb::UnsupportedDataSinkException(
                            OUString(),
                            getXWeak(),
                            rArg.Sink ) ),
                    xEnv );
                // Unreachable
            }
        }
    }

    return aRet;
}


void Content::post(
                const ucb::PostCommandArgument2 & rArg,
                const uno::Reference< ucb::XCommandEnvironment > & xEnv )
{
    uno::Reference< io::XActiveDataSink > xSink( rArg.Sink, uno::UNO_QUERY );
    if ( xSink.is() )
    {
        try
        {
            std::unique_ptr< DAVResourceAccess > xResAccess;
            {
                osl::MutexGuard aGuard( m_aMutex );
                xResAccess.reset(
                    new DAVResourceAccess( *m_xResAccess ) );
            }

            removeCachedPropertyNames( xResAccess->getURL() );
            uno::Reference< io::XInputStream > xResult
                = xResAccess->POST( rArg.MediaType,
                                    rArg.Referer,
                                    rArg.Source,
                                    xEnv );

            {
                 osl::MutexGuard aGuard( m_aMutex );
                 m_xResAccess.reset(
                     new DAVResourceAccess( *xResAccess ) );
            }

            xSink->setInputStream( xResult );
        }
        catch ( DAVException const & e )
        {
            cancelCommandExecution( e, xEnv, true );
            // Unreachable
        }
    }
    else
    {
        uno::Reference< io::XOutputStream > xResult( rArg.Sink, uno::UNO_QUERY );
        if ( xResult.is() )
        {
            try
            {
                std::unique_ptr< DAVResourceAccess > xResAccess;
                {
                    osl::MutexGuard aGuard( m_aMutex );
                    xResAccess.reset(
                        new DAVResourceAccess( *m_xResAccess ) );
                }

                removeCachedPropertyNames( xResAccess->getURL() );
                xResAccess->POST( rArg.MediaType,
                                  rArg.Referer,
                                  rArg.Source,
                                  xResult,
                                  xEnv );

                {
                    osl::MutexGuard aGuard( m_aMutex );
                    m_xResAccess.reset(
                        new DAVResourceAccess( *xResAccess ) );
                }
            }
            catch ( DAVException const & e )
            {
                cancelCommandExecution( e, xEnv, true );
                // Unreachable
            }
        }
        else
        {
            ucbhelper::cancelCommandExecution(
                uno::Any(
                    ucb::UnsupportedDataSinkException(
                        OUString(),
                        getXWeak(),
                        rArg.Sink ) ),
                xEnv );
            // Unreachable
        }
    }
}


void Content::queryChildren( ContentRefList& rChildren )
{
    // Obtain a list with a snapshot of all currently instantiated contents
    // from provider and extract the contents which are direct children
    // of this content.

    ::ucbhelper::ContentRefList aAllContents;
    m_xProvider->queryExistingContents( aAllContents );

    OUString aURL = m_xIdentifier->getContentIdentifier();
    sal_Int32 nURLPos = aURL.lastIndexOf( '/' );

    if ( nURLPos != ( aURL.getLength() - 1 ) )
    {
        // No trailing slash found. Append.
        aURL += "/";
    }

    sal_Int32 nLen = aURL.getLength();

    for ( const auto& rChild : aAllContents )
    {
        ::ucbhelper::ContentImplHelperRef xChild = rChild;
        OUString aChildURL
            = xChild->getIdentifier()->getContentIdentifier();

        // Is aURL a prefix of aChildURL?
        if ( ( aChildURL.getLength() > nLen ) &&
             ( aChildURL.startsWith( aURL ) ) )
        {
            sal_Int32 nPos = nLen;
            nPos = aChildURL.indexOf( '/', nPos );

            if ( ( nPos == -1 ) ||
                 ( nPos == ( aChildURL.getLength() - 1 ) ) )
            {
                // No further slashes / only a final slash. It's a child!
                rChildren.push_back(
                    ::http_dav_ucp::Content::ContentRef(
                        static_cast< ::http_dav_ucp::Content * >(
                            xChild.get() ) ) );
            }
        }
    }
}


void Content::insert(
        const uno::Reference< io::XInputStream > & xInputStream,
        bool bReplaceExisting,
        const uno::Reference< ucb::XCommandEnvironment >& Environment )
{
    bool bTransient, bCollection;
    OUString aEscapedTitle;
    std::unique_ptr< DAVResourceAccess > xResAccess;

    {
        osl::Guard< osl::Mutex > aGuard( m_aMutex );

        bTransient    = m_bTransient;
        bCollection   = m_bCollection;
        aEscapedTitle = m_aEscapedTitle;
        xResAccess.reset( new DAVResourceAccess( *m_xResAccess ) );
    }

    // Check, if all required properties are present.

    if ( aEscapedTitle.isEmpty() )
    {
        SAL_WARN( "ucb.ucp.webdav""Content::insert - Title missing!" );

        uno::Sequence<OUString> aProps { u"Title"_ustr };
        ucbhelper::cancelCommandExecution(
            uno::Any( ucb::MissingPropertiesException(
                                OUString(),
                                getXWeak(),
                                aProps ) ),
            Environment );
        // Unreachable
    }

    if ( !bReplaceExisting )
    {
        /* [RFC 2616] - HTTP

           The PUT method requests that the enclosed entity be stored under the
           supplied Request-URI. If the Request-URI refers to an already
           existing resource, the enclosed entity SHOULD be considered as a
           modified version of the one residing on the origin server.
        */


        /* [RFC 2518] - WebDAV

           MKCOL creates a new collection resource at the location specified by
           the Request-URI.  If the resource identified by the Request-URI is
           non-null then the MKCOL MUST fail.
        */


        // ==> Complain on PUT, continue on MKCOL.
        if ( !bTransient || !bCollection )
        {
#undef ERROR
            ucb::UnsupportedNameClashException aEx(
                u"Unable to write without overwrite!"_ustr,
                getXWeak(),
                ucb::NameClash::ERROR );

            uno::Reference< task::XInteractionHandler > xIH;

            if ( Environment.is() )
                xIH = Environment->getInteractionHandler();

            if ( xIH.is() )
            {
                uno::Any aExAsAny( aEx );

                rtl::Reference< ucbhelper::SimpleInteractionRequest > xRequest
                    = new ucbhelper::SimpleInteractionRequest(
                        aExAsAny,
                        ContinuationFlags::Approve
                            | ContinuationFlags::Disapprove );
                xIH->handle( xRequest );

                const ContinuationFlags nResp = xRequest->getResponse();

                switch ( nResp )
                {
                    case ContinuationFlags::NONE:
                        // Not handled; throw.
                        throw aEx;
//                            break;

                    case ContinuationFlags::Approve:
                        // Continue -> Overwrite.
                        bReplaceExisting = true;
                        break;

                    case ContinuationFlags::Disapprove:
                        // Abort.
                        throw ucb::CommandFailedException(
                                    OUString(),
                                    uno::Reference< uno::XInterface >(),
                                    aExAsAny );
//                            break;

                    default:
                        SAL_WARN( "ucb.ucp.webdav",
                                    "Content::insert - "
                                    "Unknown interaction selection!" );
                        throw ucb::CommandFailedException(
                                    u"Unknown interaction selection!"_ustr,
                                    uno::Reference< uno::XInterface >(),
                                    aExAsAny );
//                            break;
                }
            }
            else
            {
                // No IH; throw.
                throw aEx;
            }
        }
    }

    if ( bTransient )
    {
        // Assemble new content identifier...
        OUString aURL = getParentURL();
        if ( aURL.lastIndexOf( '/' ) != ( aURL.getLength() - 1 ) )
            aURL += "/";

        aURL += aEscapedTitle;

        try
        {
            xResAccess->setURL( aURL );

            if ( bCollection )
            {
                aStaticDAVOptionsCache.removeDAVOptions( xResAccess->getURL() );
                removeCachedPropertyNames( xResAccess->getURL() );
                xResAccess->MKCOL( Environment );
            }
            else
            {
                // remove options from cache, PUT may change it
                // it will be refreshed when needed
                aStaticDAVOptionsCache.removeDAVOptions( xResAccess->getURL() );
                removeCachedPropertyNames( xResAccess->getURL() );
                xResAccess->PUT( xInputStream, Environment );
                // clean cached value of PROPFIND properties names
            }
            // no error , set the resourcetype to unknown type
            // the resource may have transitioned from NOT FOUND or UNKNOWN to something else
            // depending on the server behaviour
            // this will force a recheck of the resource type
            m_eResourceType = UNKNOWN;
            m_eResourceTypeForLocks = UNKNOWN;
        }
        catch ( DAVException const & except )
        {
            if ( bCollection )
            {
                if ( except.getStatus() == SC_METHOD_NOT_ALLOWED )
                {
                    // [RFC 2518] - WebDAV
                    // 405 (Method Not Allowed) - MKCOL can only be
                    // executed on a deleted/non-existent resource.

                    if ( bReplaceExisting )
                    {
                        // Destroy old resource.
                        try
                        {
                            removeCachedPropertyNames( xResAccess->getURL() );
                            xResAccess->DESTROY( Environment );
                        }
                        catch ( DAVException const & e )
                        {
                            cancelCommandExecution( e, Environment, true );
                            // Unreachable
                        }

                        // Insert (recursion!).
                        insert( xInputStream, bReplaceExisting, Environment );

                        {
                            osl::Guard< osl::Mutex > aGuard( m_aMutex );
                            m_xResAccess.reset(
                                new DAVResourceAccess( *xResAccess ) );
                        }

                        // Success!
                        return;
                    }
                    else
                    {
                        OUString aTitle;
                        try
                        {
                            CurlUri const aURI( aURL );
                            aTitle = aURI.GetPathBaseNameUnescaped();
                        }
                        catch ( DAVException const & )
                        {
                        }

                        ucbhelper::cancelCommandExecution(
                            uno::Any(
                                ucb::NameClashException(
                                    OUString(),
                                    getXWeak(),
                                    task::InteractionClassification_ERROR,
                                    aTitle ) ),
                            Environment );
                        // Unreachable
                    }
                }
            }

            cancelCommandExecution( except, Environment, true );
            // Unreachable
        }

        {
            osl::Guard< osl::Mutex > aGuard( m_aMutex );
            m_xIdentifier
                = new ::ucbhelper::ContentIdentifier( aURL );
        }

        inserted();

        {
            osl::Guard< osl::Mutex > aGuard( m_aMutex );
            m_bTransient = false;
        }
    }
    else
    {
        if ( !xInputStream.is() )
        {
            ucbhelper::cancelCommandExecution(
                uno::Any(
                    ucb::MissingInputStreamException(
                        OUString(),
                        getXWeak() ) ),
                Environment );
            // Unreachable
        }

        // save the URL since it may change due to redirection
        OUString    aTargetUrl = xResAccess->getURL();
        try
        {
            removeCachedPropertyNames( xResAccess->getURL() );
            // remove options from cache, PUT may change it
            // it will be refreshed when needed
            aStaticDAVOptionsCache.removeDAVOptions( aTargetUrl );
            xResAccess->PUT( xInputStream, Environment );
        }
        catch ( DAVException const & e )
        {
            cancelCommandExecution( e, Environment, true );
            // Unreachable
        }
    }

    {
        osl::Guard< osl::Mutex > aGuard( m_aMutex );
        m_xResAccess.reset( new DAVResourceAccess( *xResAccess ) );
    }
}


void Content::transfer(
        const ucb::TransferInfo & rArgs,
        const uno::Reference< ucb::XCommandEnvironment >& Environment )
{
    uno::Reference< uno::XComponentContext > xContext;
    uno::Reference< ucb::XContentIdentifier >    xIdentifier;
    uno::Reference< ucb::XContentProvider >      xProvider;
    std::unique_ptr< DAVResourceAccess > xResAccess;

    {
        osl::Guard< osl::Mutex > aGuard( m_aMutex );

        xContext.set( m_xContext );
        xIdentifier.set( m_xIdentifier );
        xProvider.set( m_xProvider );
        xResAccess.reset( new DAVResourceAccess( *m_xResAccess ) );
    }

    OUString aTargetURI;
    try
    {
        CurlUri sourceURI( rArgs.SourceURL );
        CurlUri targetURI( xIdentifier->getContentIdentifier() );

        aTargetURI = targetURI.GetPathBaseNameUnescaped();

        // Check source's and target's URL scheme

        OUString aScheme = sourceURI.GetScheme().toAsciiLowerCase();
        if ( aScheme == VNDSUNSTARWEBDAV_URL_SCHEME)
        {
            sourceURI.SetScheme( HTTP_URL_SCHEME );
        }
        else if ( aScheme == VNDSUNSTARWEBDAVS_URL_SCHEME)
        {
            sourceURI.SetScheme( HTTPS_URL_SCHEME );
        }
        else if ( aScheme == DAV_URL_SCHEME )
        {
            sourceURI.SetScheme( HTTP_URL_SCHEME );
        }
        else if ( aScheme == DAVS_URL_SCHEME )
        {
            sourceURI.SetScheme( HTTPS_URL_SCHEME );
        }
        else if (aScheme == WEBDAV_URL_SCHEME)
        {
            sourceURI.SetScheme(HTTP_URL_SCHEME);
        }
        else if (aScheme == WEBDAVS_URL_SCHEME)
        {
            sourceURI.SetScheme(HTTPS_URL_SCHEME);
        }
        else
        {
            if ( aScheme != HTTP_URL_SCHEME && aScheme != HTTPS_URL_SCHEME )
            {
                ucbhelper::cancelCommandExecution(
                    uno::Any(
                        ucb::InteractiveBadTransferURLException(
                            u"Unsupported URL scheme!"_ustr,
                            getXWeak() ) ),
                    Environment );
                // Unreachable
            }
        }

        aScheme = targetURI.GetScheme().toAsciiLowerCase();
        if ( aScheme == VNDSUNSTARWEBDAV_URL_SCHEME)
            targetURI.SetScheme( HTTP_URL_SCHEME );
        else if ( aScheme == VNDSUNSTARWEBDAVS_URL_SCHEME)
            targetURI.SetScheme( HTTPS_URL_SCHEME );
        else if ( aScheme == DAV_URL_SCHEME )
            targetURI.SetScheme( HTTP_URL_SCHEME );
        else if ( aScheme == DAVS_URL_SCHEME )
            targetURI.SetScheme( HTTPS_URL_SCHEME );
        else if (aScheme == WEBDAV_URL_SCHEME)
            targetURI.SetScheme(HTTP_URL_SCHEME);
        else if (aScheme == WEBDAVS_URL_SCHEME)
            targetURI.SetScheme(HTTPS_URL_SCHEME);

        // @@@ This implementation of 'transfer' only works
        //     if the source and target are located at same host.
        //     (Neon does not support cross-server copy/move)

        // Check for same host

        if ( sourceURI.GetHost().getLength() &&
             ( sourceURI.GetHost() != targetURI.GetHost() ) )
        {
            ucbhelper::cancelCommandExecution(
                uno::Any( ucb::InteractiveBadTransferURLException(
                                u"Different hosts!"_ustr,
                                getXWeak() ) ),
                Environment );
            // Unreachable
        }

        OUString aTitle = rArgs.NewTitle;

        if ( aTitle.isEmpty() )
            aTitle = sourceURI.GetPathBaseNameUnescaped();

        if ( aTitle == "/" )
        {
            // kso: ???
            aTitle.clear();
        }

        targetURI.AppendPath( aTitle );

        OUString aTargetURL = xIdentifier->getContentIdentifier();
        if ( ( aTargetURL.lastIndexOf( '/' ) + 1 )
                != aTargetURL.getLength() )
            aTargetURL += "/";

        aTargetURL += aTitle;

        uno::Reference< ucb::XContentIdentifier > xTargetId
            = new ::ucbhelper::ContentIdentifier( aTargetURL );

        DAVResourceAccess aSourceAccess( xContext,
                                         xResAccess->getSessionFactory(),
                                         sourceURI.GetURI() );

        if ( rArgs.MoveData )
        {
            uno::Reference< ucb::XContentIdentifier > xId
                = new ::ucbhelper::ContentIdentifier( rArgs.SourceURL );

            // Note: The static cast is okay here, because its sure that
            //       xProvider is always the WebDAVContentProvider.
            rtl::Reference< Content > xSource
                = static_cast< Content * >(
                    xProvider->queryContent( xId ).get() );

            // [RFC 2518] - WebDAV
            // If a resource exists at the destination and the Overwrite
            // header is "T" then prior to performing the move the server
            // MUST perform a DELETE with "Depth: infinity" on the
            // destination resource.  If the Overwrite header is set to
            // "F" then the operation will fail.

            aStaticDAVOptionsCache.removeDAVOptions( sourceURI.GetURI() );
            aStaticDAVOptionsCache.removeDAVOptions( targetURI.GetURI() );
            aSourceAccess.MOVE( sourceURI.GetRelativeReference(),
                                targetURI.GetURI(),
                                rArgs.NameClash
                                    == ucb::NameClash::OVERWRITE,
                                Environment );

            if ( xSource.is() )
            {
                // Propagate destruction to listeners.
                xSource->destroy( true );
            }

// DAV resources store all additional props on server!
//              // Rename own and all children's Additional Core Properties.
//              renameAdditionalPropertySet( xId->getContentIdentifier(),
//                                           xTargetId->getContentIdentifier(),
//                                           true );
        }
        else
        {
            // [RFC 2518] - WebDAV
            // If a resource exists at the destination and the Overwrite
            // header is "T" then prior to performing the copy the server
            // MUST perform a DELETE with "Depth: infinity" on the
            // destination resource.  If the Overwrite header is set to
            // "F" then the operation will fail.

            aStaticDAVOptionsCache.removeDAVOptions( sourceURI.GetURI() );
            aStaticDAVOptionsCache.removeDAVOptions( targetURI.GetURI() );
            aSourceAccess.COPY( sourceURI.GetRelativeReference(),
                                targetURI.GetURI(),
                                rArgs.NameClash
                                    == ucb::NameClash::OVERWRITE,
                                Environment );

// DAV resources store all additional props on server!
//              // Copy own and all children's Additional Core Properties.
//              copyAdditionalPropertySet( xId->getContentIdentifier(),
//                                         xTargetId->getContentIdentifier(),
//                                         true );
        }

        // Note: The static cast is okay here, because its sure that
        //       xProvider is always the WebDAVContentProvider.
        rtl::Reference< Content > xTarget
            = static_cast< Content * >(
                    xProvider->queryContent( xTargetId ).get() );

        // Announce transferred content in its new folder.
        xTarget->inserted();
    }
    catch ( ucb::IllegalIdentifierException const & )
    {
        // queryContent
    }
    catch ( DAVException const & e )
    {
        // [RFC 2518] - WebDAV
        // 412 (Precondition Failed) - The server was unable to maintain
        // the liveness of the properties listed in the propertybehavior
        // XML element or the Overwrite header is "F" and the state of
        // the destination resource is non-null.

        if ( e.getStatus() == SC_PRECONDITION_FAILED )
        {
            switch ( rArgs.NameClash )
            {
                case 0/*ucb::NameClash::ERROR*/:
                {
                    ucbhelper::cancelCommandExecution(
                        uno::Any(
                            ucb::NameClashException(
                                OUString(),
                                getXWeak(),
                                task::InteractionClassification_ERROR,
                                aTargetURI ) ),
                        Environment );
                    // Unreachable
                }
                [[fallthrough]];

                case ucb::NameClash::OVERWRITE:
                    break;

                case ucb::NameClash::KEEP: // deprecated
                case ucb::NameClash::RENAME:
                case ucb::NameClash::ASK:
                default:
                {
                    ucbhelper::cancelCommandExecution(
                        uno::Any(
                            ucb::UnsupportedNameClashException(
                                OUString(),
                                getXWeak(),
                                rArgs.NameClash ) ),
                        Environment );
                    // Unreachable
                }
            }
        }

        cancelCommandExecution( e, Environment, true );
        // Unreachable
    }

    {
        osl::Guard< osl::Mutex > aGuard( m_aMutex );
        m_xResAccess.reset( new DAVResourceAccess( *xResAccess ) );
    }
}


void Content::destroy( bool bDeletePhysical )
{
    // @@@ take care about bDeletePhysical -> trashcan support

    uno::Reference< ucb::XContent > xThis = this;

    deleted();

    osl::Guard< osl::Mutex > aGuard( m_aMutex );

    // Process instantiated children...

    ::http_dav_ucp::Content::ContentRefList aChildren;
    queryChildren( aChildren );

    for ( auto& rChild : aChildren )
    {
        rChild->destroy( bDeletePhysical );
    }
}

// returns the resource type, to be checked for locks
Content::ResourceType Content::resourceTypeForLocks(
    const uno::Reference< ucb::XCommandEnvironment >& Environment,
    const std::unique_ptr< DAVResourceAccess > & rResAccess)
{
    ResourceType eResourceTypeForLocks = UNKNOWN;
    {
        osl::MutexGuard g(m_aMutex);
        //check if cache contains what we need, usually the first PROPFIND on the URI has supported lock
        if (m_xCachedProps)
        {
            uno::Sequence< ucb::LockEntry > aSupportedLocks;
            if ( m_xCachedProps->getValue( DAVProperties::SUPPORTEDLOCK )
                 >>= aSupportedLocks )            //get the cached value for supportedlock
            {
                for ( sal_Int32 n = 0; n < aSupportedLocks.getLength(); ++n )
                {
                    if ( aSupportedLocks[ n ].Scope
                         == ucb::LockScope_EXCLUSIVE &&
                         aSupportedLocks[ n ].Type
                         == ucb::LockType_WRITE )
                        eResourceTypeForLocks = DAV;
                }
            }
        }
    }

    const OUString aURL = 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(u"Content-Disposition"_ustr) >>= 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, {u"Content-Disposition"_ustr}, 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 <" << aURL << ">: "
            << +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(
                        u"Locked!"_ustr,
                        getXWeak(),
                        task::InteractionClassification_ERROR,
                        aURL,
                        false );
            }
            break;
            case DAVException::DAV_HTTP_NOAUTH:
            case DAVException::DAV_HTTP_AUTH:
            {
                SAL_WARN( "ucb.ucp.webdav""lock(): DAVException Authentication error - URL: <"
                          << m_xIdentifier->getContentIdentifier() << ">" );
                // DAVException::DAV_HTTP_AUTH exception can mean:
                // - interaction handler for credential management not present (happens, depending
                //   on the LO framework processing)
                // - the remote site is a WebDAV with special configuration: read/only for read operations
                //   and read/write for write operations, the user is not allowed to lock/write and
                //   she cancelled the credentials request.
                //   this is not actually an error, but the exception is sent directly from here, avoiding the automatic
                //   management that takes part in cancelCommandExecution() below
                // Unfortunately there is no InteractiveNetwork*Exception available to signal this
                // since it mostly happens on read/only part of webdav, this appears to be the most correct exception available
                throw
                    ucb::InteractiveNetworkWriteException(
                        u"Authentication error while trying to lock! Write only WebDAV perhaps?"_ustr,
                        getXWeak(),
                        task::InteractionClassification_ERROR,
                        e.getData() );
            }
            break;
            case DAVException::DAV_HTTP_ERROR:
                //grab the error code
                switch( e.getStatus() )
                {
                    // The 'case SC_NOT_FOUND' just below tries to solve a problem in eXo Platform
                    // WebDAV connector which apparently fail on resource first creation
                    // rfc4918 section-7.3 (see link below)
                    case SC_NOT_FOUND:              // <http://tools.ietf.org/html/rfc7231#section-6.5.4>
                    // The 'case SC_PRECONDITION_FAILED' just below tries to solve a problem
                    // in SharePoint when locking the resource on first creation fails due to this:
                    // <https://msdn.microsoft.com/en-us/library/jj575265%28v=office.12%29.aspx#id15>
                    // (retrieved on 2015-08-14)
                    case SC_PRECONDITION_FAILED:    // <http://tools.ietf.org/html/rfc7232#section-4.2>
                        // Errors SC_NOT_IMPLEMENTED and SC_METHOD_NOT_ALLOWED are
                        // part of base http 1.1 RFCs
                    case SC_NOT_IMPLEMENTED:        // <http://tools.ietf.org/html/rfc7231#section-6.6.2>
                    case SC_METHOD_NOT_ALLOWED:     // <http://tools.ietf.org/html/rfc7231#section-6.5.5>
                        SAL_WARN( "ucb.ucp.webdav""lock() DAVException (SC_NOT_FOUND, SC_PRECONDITION_FAILED, SC_NOT_IMPLEMENTED or SC_METHOD_NOT_ALLOWED) - URL: <"
                                  << m_xIdentifier->getContentIdentifier() << ">, DAV ExceptionCode: " << e.getError() << ", HTTP error: " << e.getStatus() );
                        // act as nothing happened
                        // that's because when a resource is first created
                        // the lock is sent before the put, so the resource
                        // is actually created by LOCK, locking it before
                        // the first PUT, but if LOCK is not supported
                        // (simple web or DAV with lock disabled) we end with one of these http
                        // errors.
                        // These same errors may be reported when the LOCK on an unmapped
                        // (i.e. non existent) resource is not implemented.
                        // Detailed specification in:
                        // <http://tools.ietf.org/html/rfc4918#section-7.3>
                        return;
                    default:
                        //fallthrough
                        ;
                }
                break;
            case DAVException::DAV_LOCKED_SELF:
                // we already hold the lock and it is in our internal lockstore
                // just return as if the lock was successful
                return;
            default:
                //fallthrough
                ;
        }

        SAL_WARN( "ucb.ucp.webdav","lock() DAVException - URL: <"
                  << m_xIdentifier->getContentIdentifier() << ">, DAV ExceptionCode: " << e.getError() << ", HTTP error: " << e.getStatus() );
        cancelCommandExecution( e, Environment, false );
        // Unreachable
    }
}


void Content::unlock(
        const uno::Reference< ucb::XCommandEnvironment >& Environment )
{

    try
    {
        std::unique_ptr< DAVResourceAccess > xResAccess;
        {
            osl::Guard< osl::Mutex > aGuard( m_aMutex );
            xResAccess.reset( new DAVResourceAccess( *m_xResAccess ) );
        }

        // check if the target URL is a Class1 DAV
        DAVOptions aDAVOptions;
        getResourceOptions( Environment, aDAVOptions, xResAccess );

        // at least class one is needed
        if( aDAVOptions.isClass1() )
        {
            // remove options from cache, unlock may change it
            // it will be refreshed when needed
            aStaticDAVOptionsCache.removeDAVOptions( xResAccess->getURL() );
            // clean cached value of PROPFIND properties names
            removeCachedPropertyNames( xResAccess->getURL() );
            xResAccess->UNLOCK( Environment );
        }

        {
            osl::Guard< osl::Mutex > aGuard( m_aMutex );
            m_xResAccess.reset( new DAVResourceAccess( *xResAccess ) );
        }
    }
    catch ( DAVException const & e )
    {
        switch( e.getError() )
        {
            case DAVException::DAV_NOT_LOCKED:
                SAL_WARN( "ucb.ucp.webdav""unlock(): DAVException::DAV_NOT_LOCKED - URL: <"
                          << m_xIdentifier->getContentIdentifier() << ">");
                // means that we don't own any lock on this resource
                // intercepted here to remove a confusing indication to the user
                // unfortunately this happens in some WebDAV server configuration
                // acting as WebDAV and having lock/unlock enabled only
                // for authorized user.
                return;
            case DAVException::DAV_HTTP_ERROR:
                //grab the error code
                switch( e.getStatus() )
                {
                    // Errors SC_NOT_IMPLEMENTED and SC_METHOD_NOT_ALLOWED are
                    // part of base http 1.1 RFCs
                    case SC_NOT_IMPLEMENTED:        // <http://tools.ietf.org/html/rfc7231#section-6.6.2>
                    case SC_METHOD_NOT_ALLOWED:     // <http://tools.ietf.org/html/rfc7231#section-6.5.5>
                        SAL_WARN( "ucb.ucp.webdav""unlock() DAVException (SC_NOT_IMPLEMENTED or SC_METHOD_NOT_ALLOWED) - URL: <"
                                  << m_xIdentifier->getContentIdentifier() << ">, DAV ExceptionCode: " << e.getError() << ", HTTP error: " << e.getStatus() );
                        return;
                    default:
                        //fallthrough
                        ;
                }
                break;
            default:
                //fallthrough
                ;
        }
        SAL_WARN( "ucb.ucp.webdav","unlock() DAVException - URL: <"
                  << m_xIdentifier->getContentIdentifier() << ">, DAV ExceptionCode: " << e.getError() << ", HTTP error: " << e.getStatus() );
        cancelCommandExecution( e, Environment, false );
        // Unreachable
    }
}


bool Content::exchangeIdentity(
    const uno::Reference< ucb::XContentIdentifier >& xNewId )
{
    if ( !xNewId.is() )
        return false;

    osl::ClearableGuard< osl::Mutex > aGuard( m_aMutex );

    uno::Reference< ucb::XContent > xThis = this;

    // Already persistent?
    if ( m_bTransient )
    {
        SAL_WARN( "ucb.ucp.webdav""Content::exchangeIdentity - Not persistent!" );
        return false;
    }

    // Exchange own identity.

    // Fail, if a content with given id already exists.
//  if ( !hasData( xNewId ) )
    {
        OUString aOldURL = m_xIdentifier->getContentIdentifier();

        aGuard.clear();
        if ( exchange( xNewId ) )
        {
            // Process instantiated children...

            ContentRefList aChildren;
            queryChildren( aChildren );

            for ( const auto& rChild : aChildren )
            {
                ContentRef xChild = rChild;

                // Create new content identifier for the child...
                uno::Reference< ucb::XContentIdentifier >
                    xOldChildId = xChild->getIdentifier();
                OUString aOldChildURL
                    = xOldChildId->getContentIdentifier();
                OUString aNewChildURL
                    = aOldChildURL.replaceAt(
                        0,
                        aOldURL.getLength(),
                        xNewId->getContentIdentifier() );
                uno::Reference< ucb::XContentIdentifier > xNewChildId
                    = new ::ucbhelper::ContentIdentifier( aNewChildURL );

                if ( !xChild->exchangeIdentity( xNewChildId ) )
                    return false;
            }
            return true;
        }
    }

    SAL_WARN( "ucb.ucp.webdav",
                "Content::exchangeIdentity - "
                "Panic! Cannot exchange identity!" );
    return false;
}


bool Content::isFolder(
            const uno::Reference< ucb::XCommandEnvironment >& xEnv )
{
    {
        osl::MutexGuard aGuard( m_aMutex );

        if ( m_bTransient )
            return m_bCollection;
    }

    uno::Sequence< beans::Property > aProperties( 1 );
    auto pProperties = aProperties.getArray();
    pProperties[ 0 ].Name   = "IsFolder";
    pProperties[ 0 ].Handle = -1;
    uno::Reference< sdbc::XRow > xRow( getPropertyValues( aProperties, xEnv ) );
    if ( xRow.is() )
    {
        try
        {
            return xRow->getBoolean( 1 );
        }
        catch ( sdbc::SQLException const & )
        {
        }
    }

    return false;
}


uno::Any Content::MapDAVException( const DAVException & e, bool bWrite )
{
    // Map DAVException...
    uno::Any aException;

    OUString aURL;
    if ( m_bTransient )
    {
        aURL = getParentURL();
        if ( aURL.lastIndexOf( '/' ) != ( aURL.getLength() - 1 ) )
            aURL += "/";

        aURL += m_aEscapedTitle;
    }
    else
    {
        aURL = m_xIdentifier->getContentIdentifier();
    }

    switch ( e.getStatus() )
    {
        case SC_NOT_FOUND:
        {
            uno::Sequence<uno::Any> aArgs{ uno::Any(beans::PropertyValue(
                u"Uri"_ustr, -1, uno::Any(aURL), beans::PropertyState_DIRECT_VALUE)) };

            aException <<=
                ucb::InteractiveAugmentedIOException(
                    u"Not found!"_ustr,
                    getXWeak(),
                    task::InteractionClassification_ERROR,
                    ucb::IOErrorCode_NOT_EXISTING,
                    aArgs );
            return aException;
        }
        default:
            break;
    }

    switch ( e.getError() )
    {
    case DAVException::DAV_HTTP_ERROR:
        {
            if ( bWrite )
                aException <<=
                    ucb::InteractiveNetworkWriteException(
                        e.getData(),
                        getXWeak(),
                        task::InteractionClassification_ERROR,
                        e.getData() );
            else
                aException <<=
                    ucb::InteractiveNetworkReadException(
                        e.getData(),
                        getXWeak(),
                        task::InteractionClassification_ERROR,
                        e.getData() );
            break;
        }

    case DAVException::DAV_HTTP_LOOKUP:
        aException <<=
            ucb::InteractiveNetworkResolveNameException(
                OUString(),
                getXWeak(),
                task::InteractionClassification_ERROR,
                e.getData() );
        break;

// @@@ No matching InteractiveNetwork*Exception
//    case DAVException::DAV_HTTP_AUTH:
//        break;

// @@@ No matching InteractiveNetwork*Exception
//    case DAVException::DAV_HTTP_AUTHPROXY:
//        break;

    case DAVException::DAV_HTTP_TIMEOUT:
    case DAVException::DAV_HTTP_CONNECT:
        aException <<=
            ucb::InteractiveNetworkConnectException(
                e.getMessage(),
                getXWeak(),
                task::InteractionClassification_ERROR,
                e.getData() );
        break;

// @@@ No matching InteractiveNetwork*Exception
//     case DAVException::DAV_HTTP_REDIRECT:
//         break;

// @@@ No matching InteractiveNetwork*Exception
//     case DAVException::DAV_SESSION_CREATE:
//         break;

    case DAVException::DAV_INVALID_ARG:
        aException <<=
            lang::IllegalArgumentException(
                OUString(),
                getXWeak(),
                -1 );
        break;

    case DAVException::DAV_LOCKED:
#if 1
        aException <<=
            ucb::InteractiveLockingLockedException(
                u"Locked!"_ustr,
                getXWeak(),
                task::InteractionClassification_ERROR,
                aURL,
                false ); // not SelfOwned
#else
        {
            uno::Sequence< uno::Any > aArgs( 1 );
            aArgs[ 0 ] <<= beans::PropertyValue(
                OUString("Uri"), -1,
                uno::makeAny(aURL),
                beans::PropertyState_DIRECT_VALUE);

            aException <<=
                ucb::InteractiveAugmentedIOException(
                    OUString( "Locked!" ),
                    getXWeak(),
                    task::InteractionClassification_ERROR,
                    ucb::IOErrorCode_LOCKING_VIOLATION,
                    aArgs );
        }
#endif
        break;

    case DAVException::DAV_LOCKED_SELF:
        aException <<=
            ucb::InteractiveLockingLockedException(
                u"Locked (self)!"_ustr,
                getXWeak(),
                task::InteractionClassification_ERROR,
                aURL,
                true ); // SelfOwned
        break;

    case DAVException::DAV_NOT_LOCKED:
        aException <<=
            ucb::InteractiveLockingNotLockedException(
                u"Not locked!"_ustr,
                getXWeak(),
                task::InteractionClassification_ERROR,
                aURL );
        break;

    case DAVException::DAV_LOCK_EXPIRED:
        aException <<=
            ucb::InteractiveLockingLockExpiredException(
                u"Lock expired!"_ustr,
                getXWeak(),
                task::InteractionClassification_ERROR,
                aURL );
        break;

    default:
        aException <<=
            ucb::InteractiveNetworkGeneralException(
                OUString(),
                getXWeak(),
                task::InteractionClassification_ERROR );
        break;
    }

    return aException;
}


// static
bool Content::shouldAccessNetworkAfterException( const DAVException & e )
{
    if ( ( e.getStatus() == SC_NOT_FOUND ) ||
         ( e.getStatus() == SC_GONE ) ||
         ( e.getError() == DAVException::DAV_HTTP_TIMEOUT ) ||
         ( e.getError() == DAVException::DAV_HTTP_LOOKUP ) ||
         ( e.getError() == DAVException::DAV_HTTP_CONNECT ) ||
         ( e.getError() == DAVException::DAV_HTTP_NOAUTH ) ||
         ( e.getError() == DAVException::DAV_HTTP_AUTH ) ||
         ( e.getError() == DAVException::DAV_HTTP_AUTHPROXY ) )
        return false;

    return true;
}


void Content::cancelCommandExecution(
                const DAVException & e,
                const uno::Reference< ucb::XCommandEnvironment > & xEnv,
                bool bWrite /* = false */ )
{
    ucbhelper::cancelCommandExecution( MapDAVException( e, bWrite ), xEnv );
    // Unreachable
}


OUString
Content::getBaseURI( const std::unique_ptr< DAVResourceAccess > & rResAccess )
{
    osl::Guard< osl::Mutex > aGuard( m_aMutex );

    // First, try to obtain value of response header "Content-Location".
    if (m_xCachedProps)
    {
        OUString aLocation;
        m_xCachedProps->getValue( u"Content-Location"_ustr ) >>= 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_CONNECT_FAILED
                || 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() );
                    const OUString&  aHostName  = theUri.GetHost();
                    sal_Int32 nPort      = theUri.GetPort();
                    DAVException::ExceptionCode e{};
                    switch (aDAVOptions.getHttpResponseStatusCode())
                    {
                        case USC_CONNECT_FAILED:
                            e = DAVException::DAV_HTTP_CONNECT;
                            throw DAVException(e,
                                    ConnectionEndPointString(aHostName, nPort),
                                    aDAVOptions.getHttpResponseStatusText());
                            break;
                        case USC_CONNECTION_TIMED_OUT:
                            e = DAVException::DAV_HTTP_TIMEOUT;
                            throw DAVException(e,
                                    ConnectionEndPointString(aHostName, nPort),
                                    aDAVOptions.getHttpResponseStatusText());
                            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_" << (e.getError() == DAVException::DAV_HTTP_TIMEOUT ? "TIMEOUT" : "CONNECT") << " for URL <" << m_xIdentifier->getContentIdentifier() << ">" );
                    // cache the internal unofficial status code

                    aDAVOptions.setHttpResponseStatusCode(e.getError() == DAVException::DAV_HTTP_CONNECT ? USC_CONNECT_FAILED : USC_CONNECTION_TIMED_OUT);
                    // ugly: this is not a HTTP status from the server but message
                    // from libcurl but the string member is unused...
                    aDAVOptions.setHttpResponseStatusText(e.getMessage());
                    // 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_CONNECT_FAILED ) ||
               ( 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(u""_ustr);
        return true;
    }
    catch ( DAVException const & e )
    {
        if ( e.getError() == DAVException::DAV_HTTP_ERROR )
        {
            if ( e.getStatus() == SC_NOT_IMPLEMENTED ||
                 e.getStatus() == SC_METHOD_NOT_ALLOWED ||
                 e.getStatus() == SC_NOT_FOUND )
            {
                SAL_WARN( "ucb.ucp.webdav""HEAD probably not implemented: fall back to a partial GET" );
                // set in cached OPTIONS "HEAD not implemented"
                // so it won't be used again on this resource
                rDAVOptions.setHeadAllowed( false );
                try
                {
                    // do a GET with a payload of 0, the server does not
                    // support HEAD (or has HEAD disabled)
                    DAVRequestHeaders aPartialGet;
                    aPartialGet.emplace_back(
                            u"Range"_ustr,
                            u"bytes=0-0"_ustr);

                    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(u""_ustr);
    return false;
}


/* vim:set shiftwidth=4 softtabstop=4 expandtab: */

Messung V0.5 in Prozent
C=86 H=97 G=91

¤ Dauer der Verarbeitung: 0.92 Sekunden  (vorverarbeitet am  2026-05-05) ¤

*© Formatika GbR, Deutschland






Wurzel

Suchen

Beweissystem der NASA

Beweissystem Isabelle

NIST Cobol Testsuite

Cephes Mathematical Library

Wiener Entwicklungsmethode

Haftungshinweis

Die Informationen auf dieser Webseite wurden nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit, noch Qualität der bereit gestellten Informationen zugesichert.

Bemerkung:

Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.