/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/*
 * This file is part of the LibreOffice project.
 *
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
 *
 * This file incorporates work covered by the following license notice:
 *
 *   Licensed to the Apache Software Foundation (ASF) under one or more
 *   contributor license agreements. See the NOTICE file distributed
 *   with this work for additional information regarding copyright
 *   ownership. The ASF licenses this file to you under the Apache
 *   License, Version 2.0 (the "License"); you may not use this file
 *   except in compliance with the License. You may obtain a copy of
 *   the License at http://www.apache.org/licenses/LICENSE-2.0 .
 */

#include <memory>

#include <cppuhelper/queryinterface.hxx>
#include <rtl/uri.hxx>
#include <sal/log.hxx>
#include <officecfg/Office/Common.hxx>
#include <officecfg/Inet.hxx>
#include <ucbhelper/contentidentifier.hxx>
#include <ucbhelper/macros.hxx>
#include <ucbhelper/propertyvalueset.hxx>
#include <ucbhelper/simpleinteractionrequest.hxx>
#include <ucbhelper/cancelcommandexecution.hxx>
#include <svl/lockfilecommon.hxx>

#include <com/sun/star/beans/IllegalTypeException.hpp>
#include <com/sun/star/beans/NotRemoveableException.hpp>
#include <com/sun/star/beans/PropertyAttribute.hpp>
#include <com/sun/star/beans/PropertyExistException.hpp>
#include <com/sun/star/beans/PropertySetInfoChange.hpp>
#include <com/sun/star/beans/PropertySetInfoChangeEvent.hpp>
#include <com/sun/star/beans/PropertyValue.hpp>
#include <com/sun/star/io/XActiveDataSink.hpp>
#include <com/sun/star/io/XOutputStream.hpp>
#include <com/sun/star/lang/IllegalAccessException.hpp>
#include <com/sun/star/lang/IllegalArgumentException.hpp>
#include <com/sun/star/sdbc/SQLException.hpp>
#include <com/sun/star/task/PasswordContainerInteractionHandler.hpp>
#include <com/sun/star/ucb/CommandEnvironment.hpp>
#include <com/sun/star/ucb/CommandFailedException.hpp>
#include <com/sun/star/ucb/ContentInfoAttribute.hpp>
#include <com/sun/star/ucb/IllegalIdentifierException.hpp>
#include <com/sun/star/ucb/InsertCommandArgument.hpp>
#include <com/sun/star/ucb/InteractiveBadTransferURLException.hpp>
#include <com/sun/star/ucb/InteractiveAugmentedIOException.hpp>
#include <com/sun/star/ucb/InteractiveLockingLockedException.hpp>
#include <com/sun/star/ucb/InteractiveLockingLockExpiredException.hpp>
#include <com/sun/star/ucb/InteractiveLockingNotLockedException.hpp>
#include <com/sun/star/ucb/InteractiveNetworkConnectException.hpp>
#include <com/sun/star/ucb/InteractiveNetworkGeneralException.hpp>
#include <com/sun/star/ucb/InteractiveNetworkReadException.hpp>
#include <com/sun/star/ucb/InteractiveNetworkResolveNameException.hpp>
#include <com/sun/star/ucb/InteractiveNetworkWriteException.hpp>
#include <com/sun/star/ucb/MissingInputStreamException.hpp>
#include <com/sun/star/ucb/MissingPropertiesException.hpp>
#include <com/sun/star/ucb/NameClash.hpp>
#include <com/sun/star/ucb/NameClashException.hpp>
#include <com/sun/star/ucb/OpenCommandArgument3.hpp>
#include <com/sun/star/ucb/OpenMode.hpp>
#include <com/sun/star/ucb/PostCommandArgument2.hpp>
#include <com/sun/star/ucb/PropertyCommandArgument.hpp>
#include <com/sun/star/ucb/TransferInfo.hpp>
#include <com/sun/star/ucb/UnsupportedCommandException.hpp>
#include <com/sun/star/ucb/UnsupportedDataSinkException.hpp>
#include <com/sun/star/ucb/UnsupportedNameClashException.hpp>
#include <com/sun/star/ucb/UnsupportedOpenModeException.hpp>
#include <com/sun/star/ucb/XCommandInfo.hpp>
#include <com/sun/star/ucb/XPersistentPropertySet.hpp>
#include <com/sun/star/uno/XComponentContext.hpp>

#include "webdavcontent.hxx"
#include "webdavprovider.hxx"
#include "webdavresultset.hxx"
#include "ContentProperties.hxx"
#include "CurlUri.hxx"
#include "UCBDeadPropertyValue.hxx"
#include "DAVException.hxx"
#include "DAVProperties.hxx"

using namespace com::sun::star;
using namespace http_dav_ucp;

namespace
{
void lcl_sendPartialGETRequest( bool &bError,
                                DAVException &aLastException,
                                const std::vector< OUString >& rProps,
                                std::vector< OUString > &aHeaderNames,
                                const std::unique_ptr< DAVResourceAccess > &xResAccess,
                                std::unique_ptr< ContentProperties > &xProps,
                                const uno::Reference< ucb::XCommandEnvironment >& xEnv )
{
    DAVResource aResource;
    DAVRequestHeaders aPartialGet;
    aPartialGet.push_back(
        DAVRequestHeader(
            OUString( "Range" ), // see <https://tools.ietf.org/html/rfc7233#section-3.1>
            OUString( "bytes=0-0" )));

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

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

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

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

            // according to http://tools.ietf.org/html/rfc2616#section-3.12
            // the only range unit defined is "bytes" and implementations
            // MAY ignore ranges specified using other units.
            if ( nSize == 1 &&
                    aContentRange.getLength() &&
                    aAcceptRanges == "bytes" )
            {
                // Parse the Content-Range to get the size
                // vid. http://tools.ietf.org/html/rfc2616#section-14.16
                // Content-Range: <range unit> <bytes range>/<size>
                sal_Int32 nSlash = aContentRange.lastIndexOf( '/' );
                if ( nSlash != -1 )
                {
                    OUString aSize = aContentRange.copy( nSlash + 1 );
                    // "*" means that the instance-length is unknown at the time when the response was generated
                    if ( aSize != "*" )
                    {
                        auto it = std::find_if(aResponseProps.begin(), aResponseProps.end(),
                            [](const DAVPropertyValue& rProp) { return rProp.Name == "Content-Length"; });
                        if (it != aResponseProps.end())
                        {
                            it->Value <<= aSize;
                        }
                    }
                }
            }
        }

        if (xProps)
            xProps->addProperties(
                rProps,
                ContentProperties( aResource ) );
        else
            xProps.reset ( new ContentProperties( aResource ) );
    }
    catch ( DAVException const & ex )
    {
        aLastException = ex;
    }
}
}

// Static value, to manage a simple OPTIONS cache
// Key is the URL, element is the DAVOptions resulting from an OPTIONS call.
// Cached DAVOptions have a lifetime that depends on the errors received or not received
// and on the value of received options.
static DAVOptionsCache aStaticDAVOptionsCache;


// Content Implementation.


// ctr for content on an existing webdav resource
Content::Content(
          const uno::Reference< uno::XComponentContext >& rxContext,
          ContentProvider* pProvider,
          const uno::Reference< ucb::XContentIdentifier >& Identifier,
          rtl::Reference< DAVSessionFactory > const & rSessionFactory )
: ContentImplHelper( rxContext, pProvider, Identifier ),
  m_eResourceType( UNKNOWN ),
  m_eResourceTypeForLocks( UNKNOWN ),
  m_pProvider( pProvider ),
  m_bTransient( false ),
  m_bCollection( false ),
  m_bDidGetOrHead( false )
{
    try
    {
        initOptsCacheLifeTime();
        m_xResAccess.reset( new DAVResourceAccess(
                rxContext,
                rSessionFactory,
                Identifier->getContentIdentifier() ) );

        CurlUri const aURI( Identifier->getContentIdentifier() );
        m_aEscapedTitle = aURI.GetPathBaseName();
    }
    catch ( DAVException const & )
    {
        throw ucb::ContentCreationException();
    }
}


// ctr for content on a non-existing webdav resource
Content::Content(
            const uno::Reference< uno::XComponentContext >& rxContext,
            ContentProvider* pProvider,
            const uno::Reference< ucb::XContentIdentifier >& Identifier,
            rtl::Reference< DAVSessionFactory > const & rSessionFactory,
            bool isCollection )
: ContentImplHelper( rxContext, pProvider, Identifier ),
  m_eResourceType( UNKNOWN ),
  m_eResourceTypeForLocks( UNKNOWN ),
  m_pProvider( pProvider ),
  m_bTransient( true ),
  m_bCollection( isCollection ),
  m_bDidGetOrHead( false )
{
    try
    {
        initOptsCacheLifeTime();
        m_xResAccess.reset( new DAVResourceAccess(
            rxContext, rSessionFactory, Identifier->getContentIdentifier() ) );
    }
    catch ( DAVException const & )
    {
        throw ucb::ContentCreationException();
    }

    // Do not set m_aEscapedTitle here! Content::insert relays on this!!!
}


// virtual
Content::~Content()
{
}


// XInterface methods.


// virtual
void SAL_CALL Content::acquire() noexcept
{
    ContentImplHelper::acquire();
}


// virtual
void SAL_CALL Content::release() noexcept
{
    ContentImplHelper::release();
}


// virtual
uno::Any SAL_CALL Content::queryInterface( const uno::Type & rType )
{
    // Note: isFolder may require network activities! So call it only
    //       if it is really necessary!!!
    uno::Any aRet = cppu::queryInterface(
        rType,
        static_cast< ucb::XContentCreator * >( this ) );
    if ( aRet.hasValue() )
    {
        try
        {
            uno::Reference< task::XInteractionHandler > xIH(
                task::PasswordContainerInteractionHandler::create(m_xContext) );

            // Supply a command env to isFolder() that contains an interaction
            // handler that uses the password container service to obtain
            // credentials without displaying a password gui.

            uno::Reference< ucb::XCommandEnvironment > xCmdEnv(
                ucb::CommandEnvironment::create(
                   m_xContext,
                   xIH,
                   uno::Reference< ucb::XProgressHandler >() ) );

            return isFolder( xCmdEnv ) ? aRet : uno::Any();
        }
        catch ( uno::RuntimeException const & )
        {
            throw;
        }
        catch ( uno::Exception const & )
        {
            return uno::Any();
        }
    }
    return aRet.hasValue() ? aRet : ContentImplHelper::queryInterface( rType );
}


// XTypeProvider methods.


XTYPEPROVIDER_COMMON_IMPL( Content );


// virtual
uno::Sequence< uno::Type > SAL_CALL Content::getTypes()
{
    bool bFolder = false;
    try
    {
        bFolder
            = isFolder( uno::Reference< ucb::XCommandEnvironment >() );
    }
    catch ( uno::RuntimeException const & )
    {
        throw;
    }
    catch ( uno::Exception const & )
    {
    }

    if ( bFolder )
    {
        static cppu::OTypeCollection s_aFolderTypes(
                        CPPU_TYPE_REF( lang::XTypeProvider ),
                        CPPU_TYPE_REF( lang::XServiceInfo ),
                        CPPU_TYPE_REF( lang::XComponent ),
                        CPPU_TYPE_REF( ucb::XContent ),
                        CPPU_TYPE_REF( ucb::XCommandProcessor ),
                        CPPU_TYPE_REF( beans::XPropertiesChangeNotifier ),
                        CPPU_TYPE_REF( ucb::XCommandInfoChangeNotifier ),
                        CPPU_TYPE_REF( beans::XPropertyContainer ),
                        CPPU_TYPE_REF( beans::XPropertySetInfoChangeNotifier ),
                        CPPU_TYPE_REF( container::XChild ),
                        CPPU_TYPE_REF( ucb::XContentCreator ) );

        return s_aFolderTypes.getTypes();
    }
    else
    {
        static cppu::OTypeCollection s_aDocumentTypes(
                        CPPU_TYPE_REF( lang::XTypeProvider ),
                        CPPU_TYPE_REF( lang::XServiceInfo ),
                        CPPU_TYPE_REF( lang::XComponent ),
                        CPPU_TYPE_REF( ucb::XContent ),
                        CPPU_TYPE_REF( ucb::XCommandProcessor ),
                        CPPU_TYPE_REF( beans::XPropertiesChangeNotifier ),
                        CPPU_TYPE_REF( ucb::XCommandInfoChangeNotifier ),
                        CPPU_TYPE_REF( beans::XPropertyContainer ),
                        CPPU_TYPE_REF( beans::XPropertySetInfoChangeNotifier ),
                        CPPU_TYPE_REF( container::XChild ) );

        return  s_aDocumentTypes.getTypes();
    }
}


// XServiceInfo methods.


// virtual
OUString SAL_CALL Content::getImplementationName()
{
    return "com.sun.star.comp.ucb.WebDAVContent";
}


// virtual
uno::Sequence< OUString > SAL_CALL Content::getSupportedServiceNames()
{
    uno::Sequence<OUString> aSNS { WEBDAV_CONTENT_SERVICE_NAME };
    return aSNS;
}


// XContent methods.


// virtual
OUString SAL_CALL Content::getContentType()
{
    bool bFolder = false;
    try
    {
        bFolder
            = isFolder( uno::Reference< ucb::XCommandEnvironment >() );
    }
    catch ( uno::RuntimeException const & )
    {
        throw;
    }
    catch ( uno::Exception const & )
    {
    }

    if ( bFolder )
        return WEBDAV_COLLECTION_TYPE;

    return WEBDAV_CONTENT_TYPE;
}


// XCommandProcessor methods.


// virtual
uno::Any SAL_CALL Content::execute(
        const ucb::Command& aCommand,
        sal_Int32 /*CommandId*/,
        const uno::Reference< ucb::XCommandEnvironment >& Environment )
{
    SAL_INFO("ucb.ucp.webdav",  ">>>>> Content::execute: start: command: " << aCommand.Name
            << ", env: " << (Environment.is() ? "present" : "missing") );

    uno::Any aRet;

    if ( aCommand.Name == "getPropertyValues" )
    {

        // getPropertyValues


        uno::Sequence< beans::Property > Properties;
        if ( !( aCommand.Argument >>= Properties ) )
        {
            ucbhelper::cancelCommandExecution(
                uno::Any( lang::IllegalArgumentException(
                                    "Wrong argument type!",
                                    static_cast< cppu::OWeakObject * >( this ),
                                    -1 ) ),
                Environment );
            // Unreachable
        }

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

        // setPropertyValues


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

        if ( !aProperties.getLength() )
        {
            ucbhelper::cancelCommandExecution(
                uno::Any( lang::IllegalArgumentException(
                                    "No properties!",
                                    static_cast< cppu::OWeakObject * >( this ),
                                    -1 ) ),
                Environment );
            // Unreachable
        }

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

        // getPropertySetInfo


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

        // getCommandInfo


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

        // open


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

        aRet = open( aOpenCommand, Environment );

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

        // insert


        ucb::InsertCommandArgument arg;
        if ( !( aCommand.Argument >>= arg ) )
        {
            ucbhelper::cancelCommandExecution(
                uno::Any( lang::IllegalArgumentException(
                                    "Wrong argument type!",
                                    static_cast< cppu::OWeakObject * >( this ),
                                    -1 ) ),
                Environment );
            // Unreachable
        }

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

        // delete


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

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

        // Propagate destruction.
        destroy( bDeletePhysical );

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

        // transfer
        //  ( Not available at documents )


        ucb::TransferInfo transferArgs;
        if ( !( aCommand.Argument >>= transferArgs ) )
        {
            ucbhelper::cancelCommandExecution(
                uno::Any( lang::IllegalArgumentException(
                                  "Wrong argument type!",
                                  static_cast< cppu::OWeakObject * >( this ),
                                  -1 ) ),
                Environment );
            // Unreachable
        }

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

        // post


        ucb::PostCommandArgument2 aArg;
        if ( !( aCommand.Argument >>= aArg ) )
        {
            ucbhelper::cancelCommandExecution(
                uno::Any( lang::IllegalArgumentException(
                                    "Wrong argument type!",
                                    static_cast< cppu::OWeakObject * >( this ),
                                    -1 ) ),
                Environment );
            // Unreachable
        }

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

        // lock

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

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

        // createNewContent


        ucb::ContentInfo aArg;
        if ( !( aCommand.Argument >>= aArg ) )
        {
            ucbhelper::cancelCommandExecution(
                uno::Any( lang::IllegalArgumentException(
                                    "Wrong argument type!",
                                    static_cast< cppu::OWeakObject * >( this ),
                                    -1 ) ),
                Environment );
            // Unreachable
        }

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

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

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

        // Unsupported command


        ucbhelper::cancelCommandExecution(
            uno::Any( ucb::UnsupportedCommandException(
                              aCommand.Name,
                              static_cast< cppu::OWeakObject * >( this ) ) ),
            Environment );
        // Unreachable
    }

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

    return aRet;
}


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


// XPropertyContainer methods.


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

    // check property Name
    if ( !aProperty.Name.getLength() )
        throw lang::IllegalArgumentException(
            "\"addProperty\" with empty Property.Name",
            static_cast< ::cppu::OWeakObject * >( this ),
            -1 );

    // Check property type.
    if ( !UCBDeadPropertyValue::supportsType( aProperty.Type ) )
        throw beans::IllegalTypeException(
            "\"addProperty\" unsupported Property.Type",
            static_cast< ::cppu::OWeakObject * >( this ) );

    // check default value
    if ( aDefaultValue.hasValue() && aDefaultValue.getValueType() != aProperty.Type )
        throw beans::IllegalTypeException(
            "\"addProperty\" DefaultValue does not match Property.Type",
            static_cast< ::cppu::OWeakObject * >( this ) );


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


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

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


    // Add a new dynamic property.


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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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


// XContentCreator methods.


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

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

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

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

    uno::Sequence< beans::Property > aDocProps( 1 );
    aDocProps.getArray()[ 0 ] = aProp;
    aSeq.getArray()[ 0 ].Properties = aDocProps;

    // folder.
    aSeq.getArray()[ 1 ].Type = WEBDAV_COLLECTION_TYPE;
    aSeq.getArray()[ 1 ].Attributes
        = ucb::ContentInfoAttribute::KIND_FOLDER;

    uno::Sequence< beans::Property > aFolderProps( 1 );
    aFolderProps.getArray()[ 0 ] = aProp;
    aSeq.getArray()[ 1 ].Properties = aFolderProps;
    return aSeq;
}


// virtual
uno::Reference< ucb::XContent > SAL_CALL
Content::createNewContent( const ucb::ContentInfo& Info )
{
    osl::Guard< osl::Mutex > aGuard( m_aMutex );

    if ( !Info.Type.getLength() )
        return uno::Reference< ucb::XContent >();

    if ( ( Info.Type != WEBDAV_COLLECTION_TYPE )
         &&
         ( Info.Type != WEBDAV_CONTENT_TYPE ) )
        return uno::Reference< ucb::XContent >();

    OUString aURL = m_xIdentifier->getContentIdentifier();

    SAL_WARN_IF( aURL.isEmpty(), "ucb.ucp.webdav",
                "WebdavContent::createNewContent - empty identifier!" );

    if ( ( aURL.lastIndexOf( '/' ) + 1 ) != aURL.getLength() )
        aURL += "/";

    bool isCollection;
    if ( Info.Type == WEBDAV_COLLECTION_TYPE )
    {
        aURL += "New_Collection";
        isCollection = true;
    }
    else
    {
        aURL += "New_Content";
        isCollection = false;
    }

    uno::Reference< ucb::XContentIdentifier > xId(
                    new ::ucbhelper::ContentIdentifier( aURL ) );

    // create the local content
    try
    {
        return new ::http_dav_ucp::Content( m_xContext,
                                          m_pProvider,
                                          xId,
                                          m_xResAccess->getSessionFactory(),
                                          isCollection );
    }
    catch ( ucb::ContentCreationException & )
    {
        return uno::Reference< ucb::XContent >();
    }
}


// virtual
OUString Content::getParentURL()
{
    // <scheme>://              -> ""
    // <scheme>://foo           -> ""
    // <scheme>://foo/          -> ""
    // <scheme>://foo/bar       -> <scheme>://foo/
    // <scheme>://foo/bar/      -> <scheme>://foo/
    // <scheme>://foo/bar/abc   -> <scheme>://foo/bar/

    OUString aURL = m_xIdentifier->getContentIdentifier();

    sal_Int32 nPos = aURL.lastIndexOf( '/' );
    if ( nPos == ( aURL.getLength() - 1 ) )
    {
        // Trailing slash found. Skip.
        nPos = aURL.lastIndexOf( '/', nPos );
    }

    sal_Int32 nPos1 = aURL.lastIndexOf( '/', nPos );
    if ( nPos1 != -1 )
        nPos1 = aURL.lastIndexOf( '/', nPos1 );

    if ( nPos1 == -1 )
        return OUString();

    return aURL.copy( 0, nPos + 1 );
}


// Non-interface methods.


// static
uno::Reference< sdbc::XRow > Content::getPropertyValues(
    const uno::Reference< uno::XComponentContext >& rxContext,
    const uno::Sequence< beans::Property >& rProperties,
    const ContentProperties& rData,
    const rtl::Reference< ::ucbhelper::ContentProviderImplHelper >& rProvider,
    const OUString& rContentId )
{
    // Note: Empty sequence means "get values of all supported properties".

    rtl::Reference< ::ucbhelper::PropertyValueSet > xRow
        = new ::ucbhelper::PropertyValueSet( rxContext );

    sal_Int32 nCount = rProperties.getLength();
    if ( nCount )
    {
        uno::Reference< beans::XPropertySet > xAdditionalPropSet;
        bool bTriedToGetAdditionalPropSet = false;

        const beans::Property* pProps = rProperties.getConstArray();
        for ( sal_Int32 n = 0; n < nCount; ++n )
        {
            const beans::Property& rProp = pProps[ n ];

            // Process standard UCB, DAV and HTTP properties.
            const uno::Any & rValue = rData.getValue( rProp.Name );
            if ( rValue.hasValue() )
            {
                xRow->appendObject( rProp, rValue );
            }
            else
            {
                // Process local Additional Properties.
                if ( !bTriedToGetAdditionalPropSet && !xAdditionalPropSet.is() )
                {
                    xAdditionalPropSet =
                            rProvider->getAdditionalPropertySet( rContentId,
                                                                 false );
                    bTriedToGetAdditionalPropSet = true;
                }

                if ( !xAdditionalPropSet.is() ||
                     !xRow->appendPropertySetValue(
                                            xAdditionalPropSet, rProp ) )
                {
                    // Append empty entry.
                    xRow->appendVoid( rProp );
                }
            }
        }
    }
    else
    {
        // Append all standard UCB, DAV and HTTP properties.

        const std::unique_ptr< PropertyValueMap > & xProps = rData.getProperties();

        ContentProvider * pProvider
            = static_cast< ContentProvider * >( rProvider.get() );
        beans::Property aProp;

        for ( const auto& rProp : *xProps )
        {
            if ( pProvider->getProperty( rProp.first, aProp ) )
                xRow->appendObject( aProp, rProp.second.value() );
        }

        // Append all local Additional Properties.
        uno::Reference< beans::XPropertySet > xSet =
            rProvider->getAdditionalPropertySet( rContentId, false );
        xRow->appendPropertySet( xSet );
    }

    return uno::Reference<sdbc::XRow>(xRow);
}

namespace {
void GetPropsUsingHeadRequest(DAVResource& resource,
                              const std::unique_ptr< DAVResourceAccess >& xResAccess,
                              const std::vector< OUString >& aHTTPNames,
                              const uno::Reference< ucb::XCommandEnvironment >& xEnv)
{
    if (!aHTTPNames.empty())
    {
        DAVOptions aDAVOptions;
        OUString   aTargetURL = xResAccess->getURL();
        // retrieve the cached options if any
        aStaticDAVOptionsCache.getDAVOptions(aTargetURL, aDAVOptions);

        // clean cached value of PROPFIND property names
        // PROPPATCH can change them
        Content::removeCachedPropertyNames(aTargetURL);
        // test if HEAD allowed, if not, throw, should be caught immediately
        // SC_GONE used internally by us, see comment in Content::getPropertyValues
        // in the catch scope
        if (aDAVOptions.getHttpResponseStatusCode() != SC_GONE &&
            !aDAVOptions.isHeadAllowed())
        {
            throw DAVException(DAVException::DAV_HTTP_ERROR, "405 Not Implemented", SC_METHOD_NOT_ALLOWED);
        }
        // if HEAD is enabled on this site
        // check if there is a relevant HTTP response status code cached
        if (aDAVOptions.getHttpResponseStatusCode() != SC_NONE)
        {
            // throws exception as if there was a server error, a DAV exception
            throw DAVException(DAVException::DAV_HTTP_ERROR,
                aDAVOptions.getHttpResponseStatusText(),
                aDAVOptions.getHttpResponseStatusCode());
            // Unreachable
        }

        xResAccess->HEAD(aHTTPNames, resource, xEnv);
    }
}
}

uno::Reference< sdbc::XRow > Content::getPropertyValues(
                const uno::Sequence< beans::Property >& rProperties,
                const uno::Reference< ucb::XCommandEnvironment >& xEnv )
{
    std::unique_ptr< ContentProperties > xProps;
    std::unique_ptr< ContentProperties > xCachedProps;
    std::unique_ptr< DAVResourceAccess > xResAccess;
    OUString aUnescapedTitle;
    bool bHasAll = false;
    uno::Reference< uno::XComponentContext > xContext;
    uno::Reference< ucb::XContentIdentifier > xIdentifier;
    rtl::Reference< ::ucbhelper::ContentProviderImplHelper > xProvider;

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

        aUnescapedTitle = DecodeURI(m_aEscapedTitle);
        xContext.set( m_xContext );
        xIdentifier.set( m_xIdentifier );
        xProvider = m_xProvider;
        xResAccess.reset( new DAVResourceAccess( *m_xResAccess ) );

        // First, ask cache...
        if (m_xCachedProps)
        {
            xCachedProps.reset( new ContentProperties( *m_xCachedProps ) );

            std::vector< OUString > aMissingProps;
            if ( xCachedProps->containsAllNames( rProperties, aMissingProps ) )
            {
                // All properties are already in cache! No server access needed.
                bHasAll = true;
            }

            // use the cached ContentProperties instance
            xProps.reset( new ContentProperties( *xCachedProps ) );
        }
    }

    bool bNetworkAccessAllowed = true;

    if ( !m_bTransient && !bHasAll )
    {
        // Obtain values from server...


        // First, identify whether resource is DAV or not
        const ResourceType eType = getResourceType(
                xEnv, xResAccess, &bNetworkAccessAllowed );

        if ( eType == DAV )
        {
            // cache lookup... getResourceType may fill the props cache via
            // PROPFIND!
            if (m_xCachedProps)
            {
                xCachedProps.reset(
                    new ContentProperties( *m_xCachedProps ) );

                std::vector< OUString > aMissingProps;
                if ( xCachedProps->containsAllNames(
                         rProperties, aMissingProps ) )
                {
                    // All properties are already in cache! No server access
                    // needed.
                    bHasAll = true;
                }

                // use the cached ContentProperties instance
                xProps.reset( new ContentProperties( *xCachedProps ) );
            }

            if ( !bHasAll )
            {
                // Only DAV resources support PROPFIND
                std::vector< OUString > aPropNames;

                uno::Sequence< beans::Property > aProperties(rProperties);

                if ( aProperties.getLength() > 0 )
                    ContentProperties::UCBNamesToDAVNames(
                        aProperties, aPropNames );

                if ( !aPropNames.empty() )
                {
                    std::vector< DAVResource > resources;
                    try
                    {
                        xResAccess->PROPFIND(
                            DAVZERO, aPropNames, resources, xEnv );

                        if ( 1 == resources.size() )
                        {
#if defined SAL_LOG_INFO
                            {//debug
                                // print received resources
                                std::vector< DAVPropertyValue >::const_iterator it = resources[0].properties.begin();
                                std::vector< DAVPropertyValue >::const_iterator end = resources[0].properties.end();
                                while ( it != end )
                                {
                                    OUString aPropValue;
                                    bool    bValue;
                                    uno::Sequence< ucb::LockEntry > aSupportedLocks;
                                    if( (*it).Value >>= aPropValue )
                                        SAL_INFO( "ucb.ucp.webdav", "PROPFIND (getPropertyValues) - returned property: " << (*it).Name << ":" << aPropValue );
                                    else if( (*it).Value >>= bValue )
                                        SAL_INFO( "ucb.ucp.webdav", "PROPFIND (getPropertyValues) - returned property: " << (*it).Name << ":" <<
                                                  ( bValue ? "true" : "false" ) );
                                    else if( (*it).Value >>= aSupportedLocks )
                                    {
                                        SAL_INFO( "ucb.ucp.webdav", "PROPFIND (getPropertyValues) - returned property: " << (*it).Name << ":" );
                                        for ( sal_Int32 n = 0; n < aSupportedLocks.getLength(); ++n )
                                        {
                                            SAL_INFO( "ucb.ucp.webdav","      scope: "
                                                      << (aSupportedLocks[n].Scope == ucb::LockScope_SHARED ? "shared" : "exclusive")
                                                      << ", type: "
                                                      << (aSupportedLocks[n].Type != ucb::LockType_WRITE ? "" : "write") );
                                        }
                                    }
                                    ++it;
                                }
                            }
#endif
                            if (xProps)
                                xProps->addProperties(
                                    aPropNames,
                                    ContentProperties( resources[ 0 ] ));
                            else
                                xProps.reset(
                                    new ContentProperties( resources[ 0 ] ) );
                        }
                    }
                    catch ( DAVException const & e )
                    {
                        bNetworkAccessAllowed = bNetworkAccessAllowed
                            && shouldAccessNetworkAfterException( e );

                        if ( !bNetworkAccessAllowed )
                        {
                            cancelCommandExecution( e, xEnv );
                            // unreachable
                        }
                    }
                }
            }
        }

        if ( bNetworkAccessAllowed )
        {
            // All properties obtained already?
            std::vector< OUString > aMissingProps;
            if ( !( xProps
                    && xProps->containsAllNames(rProperties, aMissingProps))
                // i#121922 for non-DAV, uncacheable properties must be fetched
                // regardless of m_bDidGetOrHead.
                // But SharePoint may do weird things on HEAD so for DAV
                // only do this if required.
                && (eType != DAV || !m_bDidGetOrHead))
            {
                // Possibly the missing props can be obtained using a HEAD
                // request.

                std::vector< OUString > aHeaderNames;
                ContentProperties::UCBNamesToHTTPNames(
                    rProperties,
                    aHeaderNames );

                if( eType != DAV )
                {
                    // in case of not DAV PROFIND (previously in program flow) failed
                    // so we need to add the only prop that's common
                    // to DAV and NON_DAV: MediaType, that maps to Content-Type
                    aHeaderNames.push_back( "Content-Type" );
                }

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

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

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

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

                    if ( bError )
                    {
                        DAVOptions aDAVOptionsException;

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

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

        // might trigger HTTP redirect.
        // Therefore, title must be updated here.
        CurlUri const aUri( xResAccess->getURL() );
        aUnescapedTitle = aUri.GetPathBaseNameUnescaped();

        if ( eType == UNKNOWN )
        {
            xProps.reset( new ContentProperties( aUnescapedTitle ) );
        }

        // For DAV resources we only know the Title, for non-DAV
        // resources we additionally know that it is a document.

        else if ( eType == DAV )
        {
            if (!xProps)
                xProps.reset(new ContentProperties(aUnescapedTitle));
            else
                xProps->addProperty("Title", uno::Any(aUnescapedTitle), true);
        }
        else
        {
            if (!xProps)
                xProps.reset( new ContentProperties( aUnescapedTitle, false ) );
            else
                xProps->addProperty(
                    "Title",
                    uno::Any( aUnescapedTitle ),
                    true );

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

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

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

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

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

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

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

    return xResultRow;
}


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

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

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

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

    beans::PropertyChangeEvent aEvent;
    aEvent.Source         = static_cast< cppu::OWeakObject * >( this );
    aEvent.Further        = false;
    // aEvent.PropertyName =
    aEvent.PropertyHandle = -1;
    // aEvent.OldValue   =
    // aEvent.NewValue   =

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

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

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

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

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

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

        if ( aTmpProp.Attributes & beans::PropertyAttribute::READONLY )
        {
            // Read-only property!
            aRetRange[ n ] <<= lang::IllegalAccessException(
                            "Property is read-only!",
                            static_cast< cppu::OWeakObject * >( this ) );
            continue;
        }


        // Mandatory props.


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

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

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

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

            // Optional props.


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

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

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

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

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

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

                                aEvent.PropertyName = rName;
                                aEvent.OldValue     = aOldValue;
                                aEvent.NewValue     = rValue.Value;

                                aChanges.getArray()[ nChanged ] = aEvent;
                                nChanged++;
                            }
                        }
                        catch ( beans::UnknownPropertyException const & e )
                        {
                            aRetRange[ n ] <<= e;
                        }
                        catch ( lang::WrappedTargetException const & e )
                        {
                            aRetRange[ n ] <<= e;
                        }
                        catch ( beans::PropertyVetoException const & e )
                        {
                            aRetRange[ n ] <<= e;
                        }
                        catch ( lang::IllegalArgumentException const & e )
                        {
                            aRetRange[ n ] <<= e;
                        }
                    }
                    else
                    {
                        aRetRange[ n ] <<= uno::Exception(
                                "No property set for storing the value!",
                                static_cast< cppu::OWeakObject * >( this ) );
                    }
                }
            }
        }
    } // for

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

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

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

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

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

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

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

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

        aNewURL += EncodeSegment(aNewTitle);

        uno::Reference< ucb::XContentIdentifier > xNewId
            = new ::ucbhelper::ContentIdentifier( aNewURL );
        uno::Reference< ucb::XContentIdentifier > xOldId = xIdentifier;

        try
        {
            CurlUri const sourceURI( xOldId->getContentIdentifier() );
            CurlUri targetURI( xNewId->getContentIdentifier() );

            targetURI.SetScheme( sourceURI.GetScheme() );

            // clean cached value of PROPFIND property names
            removeCachedPropertyNames( sourceURI.GetURI() );
            removeCachedPropertyNames( targetURI.GetURI() );
            aStaticDAVOptionsCache.removeDAVOptions( sourceURI.GetURI() );
            aStaticDAVOptionsCache.removeDAVOptions( targetURI.GetURI() );
            xResAccess->MOVE(
                sourceURI.GetRelativeReference(), targetURI.GetURI(), false, xEnv );

            // @@@ Should check for resources that could not be moved
            //     (due to source access or target overwrite) and send
            //     this information through the interaction handler.

            // @@@ Existing content should be checked to see if it needs
            //     to be deleted at the source

            // @@@ Existing content should be checked to see if it has
            //     been overwritten at the target

            if ( exchangeIdentity( xNewId ) )
            {
                xResAccess->setURL( aNewURL );

// DAV resources store all additional props on server!
//              // Adapt Additional Core Properties.
//              renameAdditionalPropertySet( xOldId->getContentIdentifier(),
//                                           xNewId->getContentIdentifier(),
//                                           true );
            }
            else
            {
                // Do not set new title!
                aNewTitle.clear();

                // Set error .
                aRetRange[ nTitlePos ] <<= uno::Exception(
                    "Exchange failed!",
                    static_cast< cppu::OWeakObject * >( this ) );
            }
        }
        catch ( DAVException const & e )
        {
            // Do not set new title!
            aNewTitle.clear();

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

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

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

        m_aEscapedTitle     = EncodeSegment(aNewTitle);

        aChanges.getArray()[ nChanged ] = aEvent;
        nChanged++;
    }

    if ( nChanged > 0 )
    {
        aChanges.realloc( nChanged );
        notifyPropertiesChange( aChanges );
    }

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

    return aRet;
}


uno::Any Content::open(
                const ucb::OpenCommandArgument3 & rArg,
                const uno::Reference< ucb::XCommandEnvironment > & xEnv )
{
    uno::Any aRet;

    bool bOpenFolder = ( ( rArg.Mode == ucb::OpenMode::ALL ) ||
                             ( rArg.Mode == ucb::OpenMode::FOLDERS ) ||
                             ( rArg.Mode == ucb::OpenMode::DOCUMENTS ) );
    if ( bOpenFolder )
    {
        if ( isFolder( xEnv ) )
        {
            // Open collection.

            uno::Reference< ucb::XDynamicResultSet > xSet
                = new DynamicResultSet( m_xContext, this, rArg, xEnv );
            aRet <<= xSet;
        }
        else
        {
            // Error: Not a folder!

            ucbhelper::cancelCommandExecution(
                uno::Any(
                    lang::IllegalArgumentException(
                        "Non-folder resource cannot be opened as folder! Wrong Open Mode!",
                        static_cast< cppu::OWeakObject * >( this ),
                        -1 ) ),
                xEnv );
            // Unreachable
        }
    }

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

        if ( ( rArg.Mode == ucb::OpenMode::DOCUMENT_SHARE_DENY_NONE ) ||
             ( rArg.Mode == ucb::OpenMode::DOCUMENT_SHARE_DENY_WRITE ) )
        {
            // Currently(?) unsupported.
            ucbhelper::cancelCommandExecution(
                uno::Any(
                    ucb::UnsupportedOpenModeException(
                            OUString(),
                            static_cast< cppu::OWeakObject * >( this ),
                            sal_Int16( rArg.Mode ) ) ),
                xEnv );
            // Unreachable
        }

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

                {
                    osl::MutexGuard aGuard( m_aMutex );

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

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

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

                {
                    osl::MutexGuard aGuard( m_aMutex );

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

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

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

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

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

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

                    {
                        osl::MutexGuard aGuard( m_aMutex );

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

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

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

    return aRet;
}


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

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

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

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

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

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


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

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

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

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

    sal_Int32 nLen = aURL.getLength();

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

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

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


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

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

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

    // Check, if all required properties are present.

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

        uno::Sequence<OUString> aProps { "Title" };
        ucbhelper::cancelCommandExecution(
            uno::Any( ucb::MissingPropertiesException(
                                OUString(),
                                static_cast< cppu::OWeakObject * >( this ),
                                aProps ) ),
            Environment );
        // Unreachable
    }

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

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

        /* [RFC 2518] - WebDAV

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

        // ==> Complain on PUT, continue on MKCOL.
        if ( !bTransient || !bCollection )
        {
#undef ERROR
            ucb::UnsupportedNameClashException aEx(
                "Unable to write without overwrite!",
                static_cast< cppu::OWeakObject * >( this ),
                ucb::NameClash::ERROR );

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

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

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

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

                const ContinuationFlags nResp = xRequest->getResponse();

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

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

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

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

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

        aURL += aEscapedTitle;

        try
        {
            xResAccess->setURL( aURL );

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

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

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

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

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

                        ucbhelper::cancelCommandExecution(
                            uno::Any(
                                ucb::NameClashException(
                                    OUString(),
                                    static_cast< cppu::OWeakObject * >( this ),
                                    task::InteractionClassification_ERROR,
                                    aTitle ) ),
                            Environment );
                        // Unreachable
                    }
                }
            }

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

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

        inserted();

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

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

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


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

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

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

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

        aTargetURI = targetURI.GetPathBaseNameUnescaped();

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

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

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

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

        // Check for same host

        if ( sourceURI.GetHost().getLength() &&
             ( sourceURI.GetHost() != targetURI.GetHost() ) )
        {
            ucbhelper::cancelCommandExecution(
                uno::Any( ucb::InteractiveBadTransferURLException(
                                "Different hosts!",
                                static_cast< cppu::OWeakObject * >( this ) ) ),
                Environment );
            // Unreachable
        }

        OUString aTitle = rArgs.NewTitle;

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

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

        targetURI.AppendPath( aTitle );

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

        aTargetURL += aTitle;

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

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

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

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

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

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

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

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

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

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

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

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

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

                case ucb::NameClash::OVERWRITE:
                    break;

                case ucb::NameClash::KEEP: // deprecated
                case ucb::NameClash::RENAME:
                case ucb::NameClash::ASK:
                default:
                {
                    ucbhelper::cancelCommandExecution(
                        uno::Any(
                            ucb::UnsupportedNameClashException(
                                OUString(),
                                static_cast< cppu::OWeakObject * >( this ),
                                rArgs.NameClash ) ),
                        Environment );
                    // Unreachable
                }
            }
        }

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

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


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

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

    deleted();

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

    // Process instantiated children...

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

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

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

    const OUString & rURL = m_xIdentifier->getContentIdentifier();

    if ( eResourceTypeForLocks == UNKNOWN )
    {
        // resource type for lock/unlock operations still unknown, need to ask the server

        //{
            DAVOptions aDAVOptions;
            getResourceOptions( Environment, aDAVOptions, rResAccess );
            if( aDAVOptions.isClass1() ||
                aDAVOptions.isClass2() ||
                aDAVOptions.isClass3() )
            {
                // this is at least a DAV, lock to be confirmed
                // class 2 is needed for full lock support
                // see
                // <https://tools.ietf.org/html/rfc4918#section-18.2>
                eResourceTypeForLocks = DAV_NOLOCK;
                if( aDAVOptions.isClass2() )
                {
                    // ok, possible lock, check for it
                    try
                    {
                        // we need only DAV:supportedlock
                        std::vector< DAVResource > resources;
                        std::vector< OUString > aPropNames;
                        uno::Sequence< beans::Property > aProperties( 1 );
                        aProperties.getArray()[ 0 ].Name = DAVProperties::SUPPORTEDLOCK;

                        ContentProperties::UCBNamesToDAVNames( aProperties, aPropNames );
                        rResAccess->PROPFIND( DAVZERO, aPropNames, resources, Environment );

                        bool wasSupportedlockFound = false;

                        // only one resource should be returned
                        if ( resources.size() == 1 )
                        {
                            // we may have received a bunch of other properties
                            // (some servers seems to do so)
                            // but we need only supported lock for this check
                            // all returned properties are in
                            // resources.properties[n].Name/.Value

                            std::vector< DAVPropertyValue >::iterator it;

                            for ( it = resources[0].properties.begin();
                                  it != resources[0].properties.end(); ++it)
                            {
                                if ( (*it).Name ==  DAVProperties::SUPPORTEDLOCK )
                                {
                                    wasSupportedlockFound = true;
                                    uno::Sequence< ucb::LockEntry > aSupportedLocks;
                                    if ( (*it).Value >>= aSupportedLocks )
                                    {
                                        for ( sal_Int32 n = 0; n < aSupportedLocks.getLength(); ++n )
                                        {
                                            // TODO: if the lock type is changed from 'exclusive write' to 'shared write'
                                            // e.g. to implement 'Calc shared file feature', the ucb::LockScope_EXCLUSIVE
                                            // value should be checked as well, adaptation the code may be needed
                                            if ( aSupportedLocks[ n ].Scope == ucb::LockScope_EXCLUSIVE &&
                                                 aSupportedLocks[ n ].Type == ucb::LockType_WRITE )
                                            {
                                                // requested locking mode is supported
                                                eResourceTypeForLocks = DAV;
                                                SAL_INFO( "ucb.ucp.webdav", "resourceTypeForLocks - URL: <"
                                                          << m_xIdentifier->getContentIdentifier() << ">, DAV lock/unlock supported");
                                                break;
                                            }
                                        }
                                        break;
                                    }
                                }
                            }
                        }
                        else
                        {
                            // PROPFIND failed; check if HEAD contains Content-Disposition: attachment (RFC1806, HTTP/1.1 19.5.1),
                            // which supposedly means no lock for the resource (happens e.g. with SharePoint exported lists)
                            OUString sContentDisposition;
                            // First, check cached properties
                            if (m_xCachedProps)
                            {
                                if ((m_xCachedProps->getValue("Content-Disposition") >>= sContentDisposition)
                                    && sContentDisposition.startsWithIgnoreAsciiCase("attachment"))
                                {
                                    eResourceTypeForLocks = DAV_NOLOCK;
                                    wasSupportedlockFound = true;
                                }
                            }
                            // If no data in cache, try HEAD request
                            if (sContentDisposition.isEmpty() && !m_bDidGetOrHead) try
                            {
                                DAVResource resource;
                                GetPropsUsingHeadRequest(resource, rResAccess, {"Content-Disposition"}, Environment);
                                m_bDidGetOrHead = true;
                                for (const auto& it : resource.properties)
                                {
                                    if (it.Name.equalsIgnoreAsciiCase("Content-Disposition"))
                                    {
                                        if ((it.Value >>= sContentDisposition) && sContentDisposition.equalsIgnoreAsciiCase("attachment"))
                                        {
                                            eResourceTypeForLocks = DAV_NOLOCK;
                                            wasSupportedlockFound = true;
                                        }
                                        break;
                                    }
                                }
                            }
                            catch (...){}
                        }
                        // check if this is still only a DAV_NOLOCK
                        // a fallback for resources that do not have DAVProperties::SUPPORTEDLOCK property
                        // we check for the returned OPTION if LOCK is allowed on the resource
                        if ( !wasSupportedlockFound && eResourceTypeForLocks == DAV_NOLOCK )
                        {
                            SAL_INFO( "ucb.ucp.webdav", "This WebDAV server has no supportedlock property, check for allowed LOCK method in OPTIONS" );
                            // ATTENTION: if the lock type is changed from 'exclusive write' to 'shared write'
                            // e.g. to implement 'Calc shared file feature' on WebDAV directly, and we arrive to this fallback
                            // and the LOCK is allowed, we should assume that only exclusive write lock is available
                            // this is just a reminder...
                            if ( aDAVOptions.isLockAllowed() )
                                eResourceTypeForLocks = DAV;
                        }
                    }
                    catch ( DAVException const & e )
                    {
                        rResAccess->resetUri();
                        //grab the error code
                        switch( e.getStatus() )
                        {
                            case SC_NOT_FOUND:
                                SAL_WARN( "ucb.ucp.webdav", "resourceTypeForLocks() - URL: <"
                                          << m_xIdentifier->getContentIdentifier() << "> was not found. ");
                                eResourceTypeForLocks = NOT_FOUND;
                                break;
                                // some servers returns SC_FORBIDDEN, instead
                                // the meaning of SC_FORBIDDEN is, according to <http://tools.ietf.org/html/rfc7231#section-6.5.3>:
                                // The 403 (Forbidden) status code indicates that the server understood
                                // the request but refuses to authorize it
                            case SC_FORBIDDEN:
                                // Errors SC_NOT_IMPLEMENTED and SC_METHOD_NOT_ALLOWED are
                                // part of base http 1.1 RFCs
                            case SC_NOT_IMPLEMENTED:        // <http://tools.ietf.org/html/rfc7231#section-6.6.2>
                            case SC_METHOD_NOT_ALLOWED:     // <http://tools.ietf.org/html/rfc7231#section-6.5.5>
                                // they all mean the resource is NON_DAV
                                SAL_WARN( "ucb.ucp.webdav", "resourceTypeForLocks() DAVException (SC_FORBIDDEN, SC_NOT_IMPLEMENTED or SC_METHOD_NOT_ALLOWED) - URL: <"
                                          << m_xIdentifier->getContentIdentifier() << ">, DAV ExceptionCode: " << e.getError() << ", HTTP error: " << e.getStatus() );
                                eResourceTypeForLocks = NON_DAV;
                                break;
                            default:
                                //fallthrough
                                SAL_WARN( "ucb.ucp.webdav", "resourceTypeForLocks() DAVException - URL: <"
                                          << m_xIdentifier->getContentIdentifier() << ">, DAV ExceptionCode: " << e.getError() << ", HTTP error: " << e.getStatus() );
                                eResourceTypeForLocks = UNKNOWN;
                        }
                    }
                }
            }
            else
                eResourceTypeForLocks = NON_DAV;

        //}
    }
    osl::MutexGuard g(m_aMutex);
    if (m_eResourceTypeForLocks == UNKNOWN)
    {
        m_eResourceTypeForLocks = eResourceTypeForLocks;
    }
    else
    {
        SAL_WARN_IF(
            eResourceTypeForLocks != m_eResourceTypeForLocks, "ucb.ucp.webdav",
            "different resource types for <" << rURL << ">: "
            << +eResourceTypeForLocks << " vs. " << +m_eResourceTypeForLocks);
    }
    SAL_INFO( "ucb.ucp.webdav", "resourceTypeForLocks() - URL: <"
              << m_xIdentifier->getContentIdentifier() << ">, m_eResourceTypeForLocks: " << m_eResourceTypeForLocks );
    return m_eResourceTypeForLocks;
}

Content::ResourceType Content::resourceTypeForLocks(
    const uno::Reference< ucb::XCommandEnvironment >& Environment )
{
    std::unique_ptr< DAVResourceAccess > xResAccess;
    {
        osl::MutexGuard aGuard( m_aMutex );
        xResAccess.reset( new DAVResourceAccess( *m_xResAccess ) );
    }
    Content::ResourceType ret = resourceTypeForLocks( Environment, xResAccess );
    {
        osl::Guard< osl::Mutex > aGuard( m_aMutex );
        m_xResAccess.reset( new DAVResourceAccess( *xResAccess ) );
    }
    return ret;
}

void Content::lock(
        const uno::Reference< ucb::XCommandEnvironment >& Environment )
{
// prepare aURL to be used in exception, see below
    OUString aURL;
    if ( m_bTransient )
    {
        aURL = getParentURL();
        if ( aURL.lastIndexOf('/') != ( aURL.getLength() - 1 ) )
            aURL += "/";

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

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

        uno::Any aOwnerAny;
        OUString const user(officecfg::Office::Common::Save::Document::UseUserData::get()
                ? " - " + ::svt::LockFileCommon::GetOOOUserName()
                : OUString());
        aOwnerAny <<= OUString("LibreOffice" + user);

        ucb::Lock aLock(
            ucb::LockScope_EXCLUSIVE,
            ucb::LockType_WRITE,
            ucb::LockDepth_ZERO,
            aOwnerAny,
            180, // lock timeout in secs
            //-1, // infinite lock
            uno::Sequence< OUString >() );

        // OPTIONS may change as a consequence of the lock operation
        aStaticDAVOptionsCache.removeDAVOptions( xResAccess->getURL() );
        removeCachedPropertyNames( xResAccess->getURL() );
        xResAccess->LOCK( aLock, Environment );

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

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


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

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

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

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

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


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

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

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

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

    // Exchange own identity.

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

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

            ContentRefList aChildren;
            queryChildren( aChildren );

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

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

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

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


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

        if ( m_bTransient )
            return m_bCollection;
    }

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

    return false;
}


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

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

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

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

            aException <<=
                ucb::InteractiveAugmentedIOException(
                    "Not found!",
                    static_cast< cppu::OWeakObject * >( this ),
                    task::InteractionClassification_ERROR,
                    ucb::IOErrorCode_NOT_EXISTING,
                    aArgs );
            return aException;
        }
        default:
            break;
    }

    switch ( e.getError() )
    {
    case DAVException::DAV_HTTP_ERROR:
        {
            if ( bWrite )
                aException <<=
                    ucb::InteractiveNetworkWriteException(
                        e.getData(),
                        static_cast< cppu::OWeakObject * >( this ),
                        task::InteractionClassification_ERROR,
                        e.getData() );
            else
                aException <<=
                    ucb::InteractiveNetworkReadException(
                        e.getData(),
                        static_cast< cppu::OWeakObject * >( this ),
                        task::InteractionClassification_ERROR,
                        e.getData() );
            break;
        }

    case DAVException::DAV_HTTP_LOOKUP:
        aException <<=
            ucb::InteractiveNetworkResolveNameException(
                OUString(),
                static_cast< cppu::OWeakObject * >( this ),
                task::InteractionClassification_ERROR,
                e.getData() );
        break;

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

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

    case DAVException::DAV_HTTP_TIMEOUT:
    case DAVException::DAV_HTTP_CONNECT:
        aException <<=
            ucb::InteractiveNetworkConnectException(
                OUString(),
                static_cast< cppu::OWeakObject * >( this ),
                task::InteractionClassification_ERROR,
                e.getData() );
        break;

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

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

    case DAVException::DAV_INVALID_ARG:
        aException <<=
            lang::IllegalArgumentException(
                OUString(),
                static_cast< cppu::OWeakObject * >( this ),
                -1 );
        break;

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

            aException <<=
                ucb::InteractiveAugmentedIOException(
                    OUString( "Locked!" ),
                    static_cast< cppu::OWeakObject * >( this ),
                    task::InteractionClassification_ERROR,
                    ucb::IOErrorCode_LOCKING_VIOLATION,
                    aArgs );
        }
#endif
        break;

    case DAVException::DAV_LOCKED_SELF:
        aException <<=
            ucb::InteractiveLockingLockedException(
                "Locked (self)!",
                static_cast< cppu::OWeakObject * >( this ),
                task::InteractionClassification_ERROR,
                aURL,
                true ); // SelfOwned
        break;

    case DAVException::DAV_NOT_LOCKED:
        aException <<=
            ucb::InteractiveLockingNotLockedException(
                "Not locked!",
                static_cast< cppu::OWeakObject * >( this ),
                task::InteractionClassification_ERROR,
                aURL );
        break;

    case DAVException::DAV_LOCK_EXPIRED:
        aException <<=
            ucb::InteractiveLockingLockExpiredException(
                "Lock expired!",
                static_cast< cppu::OWeakObject * >( this ),
                task::InteractionClassification_ERROR,
                aURL );
        break;

    default:
        aException <<=
            ucb::InteractiveNetworkGeneralException(
                OUString(),
                static_cast< cppu::OWeakObject * >( this ),
                task::InteractionClassification_ERROR );
        break;
    }

    return aException;
}


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

    return true;
}


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


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

    // First, try to obtain value of response header "Content-Location".
    if (m_xCachedProps)
    {
        OUString aLocation;
        m_xCachedProps->getValue( "Content-Location" ) >>= aLocation;
        if ( aLocation.getLength() )
        {
            try
            {
                // Do not use m_xIdentifier->getContentIdentifier() because it
                // for example does not reflect redirects applied to requests
                // done using the original URI but m_xResAccess' URI does.
                return rtl::Uri::convertRelToAbs( rResAccess->getURL(),
                                                  aLocation );
            }
            catch ( rtl::MalformedUriException const & )
            {
            }
        }
    }

    return rResAccess->getURL();
}

// resource type is the type of the WebDAV resource
Content::ResourceType Content::getResourceType(
                    const uno::Reference< ucb::XCommandEnvironment >& xEnv,
                    const std::unique_ptr< DAVResourceAccess > & rResAccess,
                    bool * networkAccessAllowed )
{
    {
        osl::MutexGuard g(m_aMutex);
        if (m_eResourceType != UNKNOWN) {
            return m_eResourceType;
        }
    }

    ResourceType eResourceType = UNKNOWN;
    DAVOptions aDAVOptions;

    {
        getResourceOptions( xEnv, aDAVOptions, rResAccess, networkAccessAllowed );

        // at least class one is needed
        if( aDAVOptions.isClass1() )
        {
            try
            {
                // Try to fetch some frequently used property value, e.g. those
                // used when loading documents... along with identifying whether
                // this is a DAV resource.
                std::vector< DAVResource > resources;
                std::vector< OUString > aPropNames;
                uno::Sequence< beans::Property > aProperties( 5 );
                auto pProperties = aProperties.getArray();
                pProperties[ 0 ].Name = "IsFolder";
                pProperties[ 1 ].Name = "IsDocument";
                pProperties[ 2 ].Name = "IsReadOnly";
                pProperties[ 3 ].Name = "MediaType";
                pProperties[ 4 ].Name = DAVProperties::SUPPORTEDLOCK;

                ContentProperties::UCBNamesToDAVNames( aProperties, aPropNames );

                rResAccess->PROPFIND( DAVZERO, aPropNames, resources, xEnv );

                if ( resources.size() == 1 )
                {
#if defined SAL_LOG_INFO
                    {//debug
                        // print received resources
                        std::vector< DAVPropertyValue >::const_iterator it = resources[0].properties.begin();
                        std::vector< DAVPropertyValue >::const_iterator end = resources[0].properties.end();
                        while ( it != end )
                        {
                            OUString aPropValue;
                            bool    bValue;
                            uno::Sequence< ucb::LockEntry > aSupportedLocks;
                            if((*it).Value >>= aPropValue )
                                SAL_INFO( "ucb.ucp.webdav", "PROPFIND (getResourceType) - ret'd prop: " << (*it).Name << ":" << aPropValue );
                            else if( (*it).Value >>= bValue )
                                SAL_INFO( "ucb.ucp.webdav", "PROPFIND (getResourceType) - ret'd prop: " << (*it).Name << ":" <<
                                          ( bValue ? "true" : "false" ) );
                            else if( (*it).Value >>= aSupportedLocks )
                            {
                                SAL_INFO( "ucb.ucp.webdav", "PROPFIND (getResourceType) - ret'd prop: " << (*it).Name << ":" );
                                for ( sal_Int32 n = 0; n < aSupportedLocks.getLength(); ++n )
                                {
                                    SAL_INFO( "ucb.ucp.webdav","PROPFIND (getResourceType) -       supportedlock[" << n <<"]: scope: "
                                              << (aSupportedLocks[n].Scope == ucb::LockScope_SHARED ? "shared" : "exclusive")
                                              << ", type: "
                                              << (aSupportedLocks[n].Type != ucb::LockType_WRITE ? "" : "write") );
                                }
                            }
                            ++it;
                        }
                    }
#endif
                    osl::MutexGuard g(m_aMutex);
                    m_xCachedProps.reset(
                        new CachableContentProperties( ContentProperties( resources[ 0 ] ) ) );
                    m_xCachedProps->containsAllNames(
                        aProperties, m_aFailedPropNames );
                }
                eResourceType = DAV;
            }
            catch ( DAVException const & e )
            {
                rResAccess->resetUri();

                SAL_WARN( "ucb.ucp.webdav", "Content::getResourceType returned errors, DAV ExceptionCode: " << e.getError() << ", HTTP error: "  << e.getStatus() );

                if ( e.getStatus() == SC_METHOD_NOT_ALLOWED )
                {
                    // Status SC_METHOD_NOT_ALLOWED is a safe indicator that the
                    // resource is NON_DAV
                    eResourceType = NON_DAV;
                }
                else if (networkAccessAllowed != nullptr)
                {
                    *networkAccessAllowed = *networkAccessAllowed
                        && shouldAccessNetworkAfterException(e);
                }
                if ( e.getStatus() == SC_NOT_FOUND )
                {
                    // arrives here if OPTIONS is still cached for a resource previously available
                    // operate on the OPTIONS cache:
                    // if OPTIONS was not found, do nothing
                    // else OPTIONS returned on a resource not existent  (example a server that allows lock on null resource) set
                    // not found and adjust lifetime accordingly
                    DAVOptions aDAVOptionsInner;
                    if (aStaticDAVOptionsCache.getDAVOptions(rResAccess->getURL(), aDAVOptionsInner))
                    {
                        // TODO? get redirected url
                        aDAVOptionsInner.setHttpResponseStatusCode( e.getStatus() );
                        aDAVOptionsInner.setHttpResponseStatusText( e.getData() );
                        aStaticDAVOptionsCache.addDAVOptions( aDAVOptionsInner,
                                                              m_nOptsCacheLifeNotFound );
                    }
                }
                // if the two net events below happen, something
                // is going on to the connection so break the command flow
                if ( ( e.getError() == DAVException::DAV_HTTP_TIMEOUT ) ||
                     ( e.getError() == DAVException::DAV_HTTP_CONNECT ) )
                {
                    cancelCommandExecution( e, xEnv );
                    // unreachable
                }

                // cancel command execution is case that no user authentication data has been provided.
                if ( e.getError() == DAVException::DAV_HTTP_NOAUTH )
                {
                    cancelCommandExecution( e, uno::Reference< ucb::XCommandEnvironment >() );
                }
            }
        }
        else
        {
            rResAccess->resetUri();

            // first check if the cached error can be mapped to DAVException::DAV_HTTP_TIMEOUT or mapped to DAVException::DAV_HTTP_CONNECT
            if (aDAVOptions.getHttpResponseStatusCode() == USC_CONNECTION_TIMED_OUT
                // can't get any reliable info without auth => cancel request
                || aDAVOptions.getHttpResponseStatusCode() == USC_AUTH_FAILED
                || aDAVOptions.getHttpResponseStatusCode() == USC_AUTHPROXY_FAILED)
            {
                // behave same as DAVException::DAV_HTTP_TIMEOUT or DAVException::DAV_HTTP_CONNECT was thrown
                try
                {
                    // extract host name and connection port
                    CurlUri   theUri( rResAccess->getURL() );
                    OUString  aHostName  = theUri.GetHost();
                    sal_Int32 nPort      = theUri.GetPort();
                    DAVException::ExceptionCode e{};
                    switch (aDAVOptions.getHttpResponseStatusCode())
                    {
                        case USC_CONNECTION_TIMED_OUT:
                            e = DAVException::DAV_HTTP_TIMEOUT;
                            break;
                        case USC_AUTH_FAILED:
                            e = DAVException::DAV_HTTP_AUTH;
                            break;
                        case USC_AUTHPROXY_FAILED:
                            e = DAVException::DAV_HTTP_AUTHPROXY;
                            break;
                        default:
                            assert(false);
                    }
                    throw DAVException( e,
                                        ConnectionEndPointString(aHostName, nPort) );
                }
                catch ( DAVException& exp )
                {
                    cancelCommandExecution( exp, xEnv );
                }
            }

            if ( aDAVOptions.getHttpResponseStatusCode() != SC_NOT_FOUND &&
                 aDAVOptions.getHttpResponseStatusCode() != SC_GONE ) // the cached OPTIONS can have SC_GONE
            {
                eResourceType = NON_DAV;
            }
            else
            {
                //resource doesn't exist
                if ( networkAccessAllowed != nullptr )
                    *networkAccessAllowed = false;
            }
        }
    }

    osl::MutexGuard g(m_aMutex);
    if (m_eResourceType == UNKNOWN) {
        m_eResourceType = eResourceType;
    } else {
        SAL_WARN_IF(
            eResourceType != m_eResourceType, "ucb.ucp.webdav",
            "different resource types for <" << rResAccess->getURL() << ">: "
            << +eResourceType << " vs. " << +m_eResourceType);
    }
    SAL_INFO( "ucb.ucp.webdav", "m_eResourceType for <" << rResAccess->getURL() << ">: " << m_eResourceType );
    return m_eResourceType;
}


Content::ResourceType Content::getResourceType(
                    const uno::Reference< ucb::XCommandEnvironment >& xEnv )
{
    std::unique_ptr< DAVResourceAccess > xResAccess;
    {
        osl::MutexGuard aGuard( m_aMutex );
        xResAccess.reset( new DAVResourceAccess( *m_xResAccess ) );
    }
    const Content::ResourceType & ret = getResourceType( xEnv, xResAccess );
    {
        osl::Guard< osl::Mutex > aGuard( m_aMutex );
        m_xResAccess.reset( new DAVResourceAccess( *xResAccess ) );
    }
    return ret;
}


void Content::initOptsCacheLifeTime()
{
    // see description in
    // officecfg/registry/schema/org/openoffice/Inet.xcs
    // for use of these field values.
    sal_uInt32 nAtime;
    nAtime = officecfg::Inet::Settings::OptsCacheLifeImplWeb::get();
    m_nOptsCacheLifeImplWeb = std::max( sal_uInt32( 0 ),
                                        std::min( nAtime, sal_uInt32( 3600 ) ) );

    nAtime = officecfg::Inet::Settings::OptsCacheLifeDAV::get();
    m_nOptsCacheLifeDAV = std::max( sal_uInt32( 0 ),
                                    std::min( nAtime, sal_uInt32( 3600 ) ) );

    nAtime = officecfg::Inet::Settings::OptsCacheLifeDAVLocked::get();
    m_nOptsCacheLifeDAVLocked = std::max( sal_uInt32( 0 ),
                                    std::min( nAtime, sal_uInt32( 3600 ) ) );

    nAtime = officecfg::Inet::Settings::OptsCacheLifeNotImpl::get();
    m_nOptsCacheLifeNotImpl = std::max( sal_uInt32( 0 ),
                                              std::min( nAtime, sal_uInt32( 43200 ) ) );

    nAtime = officecfg::Inet::Settings::OptsCacheLifeNotFound::get();
    m_nOptsCacheLifeNotFound = std::max( sal_uInt32( 0 ),
                                              std::min( nAtime, sal_uInt32( 30 ) ) );
}


void Content::getResourceOptions(
                    const css::uno::Reference< css::ucb::XCommandEnvironment >& xEnv,
                    DAVOptions& rDAVOptions,
                    const std::unique_ptr< DAVResourceAccess > & rResAccess,
                    bool * networkAccessAllowed )
{
    OUString aRedirURL;
    OUString aTargetURL = rResAccess->getURL();
    DAVOptions aDAVOptions;
    // first check if in cache, if not, then send method to server
    if ( !aStaticDAVOptionsCache.getDAVOptions( aTargetURL, aDAVOptions ) )
    {
        try
        {
            rResAccess->OPTIONS( aDAVOptions, xEnv );
            // IMPORTANT:the correctly implemented server will answer without errors, even if the resource is not present
            sal_uInt32 nLifeTime = ( aDAVOptions.isClass1() ||
                                     aDAVOptions.isClass2() ||
                                     aDAVOptions.isClass3() ) ?
                m_nOptsCacheLifeDAV : // a WebDAV site
                m_nOptsCacheLifeImplWeb;  // a site implementing OPTIONS but
                                          // it's not DAV
            // if resource is locked, will use a
            // different lifetime
            if( aDAVOptions.isLocked() )
                nLifeTime = m_nOptsCacheLifeDAVLocked;

            // check if redirected
            aRedirURL = rResAccess->getURL();
            if( aRedirURL == aTargetURL)
            { // no redirection
                aRedirURL.clear();
            }
            // cache this URL's option
            aDAVOptions.setURL( aTargetURL );
            aDAVOptions.setRedirectedURL( aRedirURL );
            aStaticDAVOptionsCache.addDAVOptions( aDAVOptions,
                                                  nLifeTime );
        }
        catch ( DAVException const & e )
        {
            // first, remove from cache, will be added if needed, depending on the error received
            aStaticDAVOptionsCache.removeDAVOptions( aTargetURL );
            rResAccess->resetUri();

            aDAVOptions.setURL( aTargetURL );
            aDAVOptions.setRedirectedURL( aRedirURL );
            switch( e.getError() )
            {
                case DAVException::DAV_HTTP_TIMEOUT:
                case DAVException::DAV_HTTP_CONNECT:
                {
                    // something bad happened to the connection
                    // not same as not found, this instead happens when the server doesn't exist or doesn't answer at all
                    // probably a new bit stating 'timed out' should be added to opts var?
                    // in any case abort the command
                    SAL_WARN( "ucb.ucp.webdav", "OPTIONS - DAVException: DAV_HTTP_TIMEOUT or DAV_HTTP_CONNECT for URL <" << m_xIdentifier->getContentIdentifier() << ">" );
                    // cache the internal unofficial status code

                    aDAVOptions.setHttpResponseStatusCode( USC_CONNECTION_TIMED_OUT );
                    // used only internally, so the text doesn't really matter..
                    aStaticDAVOptionsCache.addDAVOptions( aDAVOptions,
                                                          m_nOptsCacheLifeNotFound );
                    if ( networkAccessAllowed != nullptr )
                    {
                        *networkAccessAllowed = *networkAccessAllowed
                            && shouldAccessNetworkAfterException(e);
                    }
                }
                break;
                case DAVException::DAV_HTTP_LOOKUP:
                {
                    SAL_WARN( "ucb.ucp.webdav", "OPTIONS - DAVException: DAV_HTTP_LOOKUP for URL <" << m_xIdentifier->getContentIdentifier() << ">" );
                    aDAVOptions.setHttpResponseStatusCode( USC_LOOKUP_FAILED );
                    // used only internally, so the text doesn't really matter..
                    aStaticDAVOptionsCache.addDAVOptions( aDAVOptions,
                                                          m_nOptsCacheLifeNotFound );
                    if ( networkAccessAllowed != nullptr )
                    {
                        *networkAccessAllowed = *networkAccessAllowed
                            && shouldAccessNetworkAfterException(e);
                    }
                }
                break;
                case DAVException::DAV_HTTP_NOAUTH:
                case DAVException::DAV_HTTP_AUTH:
                {
                    SAL_WARN( "ucb.ucp.webdav", "OPTIONS - DAVException: DAV_HTTP_AUTH for URL <" << m_xIdentifier->getContentIdentifier() << ">" );
                    // - the remote site is a WebDAV with special configuration: read/only for read operations
                    //   and read/write for write operations, the user is not allowed to lock/write and
                    //   she cancelled the credentials request.
                    //   this is not actually an error, it means only that for current user this is a standard web,
                    //   though possibly DAV enabled
                    aDAVOptions.setHttpResponseStatusCode( USC_AUTH_FAILED );
                    // used only internally, so the text doesn't really matter..
                    if (xEnv && xEnv->getInteractionHandler())
                    {   // only cache if there actually was a chance to request auth
                        aStaticDAVOptionsCache.addDAVOptions( aDAVOptions,
                                                              m_nOptsCacheLifeNotFound );
                    }
                    if ( networkAccessAllowed != nullptr )
                    {
                        *networkAccessAllowed = *networkAccessAllowed
                            && shouldAccessNetworkAfterException(e);
                    }
                }
                break;
                case DAVException::DAV_HTTP_AUTHPROXY:
                {
                    SAL_WARN( "ucb.ucp.webdav", "OPTIONS - DAVException: DAV_HTTP_AUTHPROXY for URL <" << m_xIdentifier->getContentIdentifier() << ">" );
                    aDAVOptions.setHttpResponseStatusCode( USC_AUTHPROXY_FAILED );
                    // used only internally, so the text doesn't really matter..
                    aStaticDAVOptionsCache.addDAVOptions( aDAVOptions,
                                                          m_nOptsCacheLifeNotFound );
                    if ( networkAccessAllowed != nullptr )
                    {
                        *networkAccessAllowed = *networkAccessAllowed
                            && shouldAccessNetworkAfterException(e);
                    }
                }
                break;
                case DAVException::DAV_HTTP_ERROR:
                {
                    switch( e.getStatus() )
                    {
                        case SC_FORBIDDEN:
                        {
                            SAL_WARN( "ucb.ucp.webdav","OPTIONS - SC_FORBIDDEN for URL <" << m_xIdentifier->getContentIdentifier() << ">" );
                            // cache it, so OPTIONS won't be called again, this URL does not support it
                            aStaticDAVOptionsCache.addDAVOptions( aDAVOptions,
                                                                  m_nOptsCacheLifeNotImpl );
                        }
                        break;
                        case SC_BAD_REQUEST:
                        case SC_INTERNAL_SERVER_ERROR:
                        {
                            SAL_WARN( "ucb.ucp.webdav","OPTIONS - SC_BAD_REQUEST or SC_INTERNAL_SERVER_ERROR for URL <" << m_xIdentifier->getContentIdentifier() << ">, HTTP error: "<< e.getStatus()
                                      << ", '" << e.getData() << "'" );
                            // cache it, so OPTIONS won't be called again, this URL detect some problem while answering the method
                            aDAVOptions.setHttpResponseStatusCode( e.getStatus() );
                            aDAVOptions.setHttpResponseStatusText( e.getData() );
                            aStaticDAVOptionsCache.addDAVOptions( aDAVOptions,
                                                                  m_nOptsCacheLifeNotFound );
                        }
                        break;
                        case SC_NOT_IMPLEMENTED:
                        case SC_METHOD_NOT_ALLOWED:
                        {
                            // OPTIONS method must be implemented in DAV
                            // resource is NON_DAV, or not advertising it
                            SAL_WARN( "ucb.ucp.webdav","OPTIONS - SC_NOT_IMPLEMENTED or SC_METHOD_NOT_ALLOWED for URL <" << m_xIdentifier->getContentIdentifier() << ">, HTTP error: "<< e.getStatus()
                                      << ", '" << e.getData() << "'" );
                            // cache it, so OPTIONS won't be called again, this URL does not support it
                            aStaticDAVOptionsCache.addDAVOptions( aDAVOptions,
                                                                  m_nOptsCacheLifeNotImpl );
                        }
                        break;
                        case SC_NOT_FOUND:
                        {
                            // Apparently on IIS 10.0, if you disabled OPTIONS method, this error is the one reported,
                            // instead of SC_NOT_IMPLEMENTED or SC_METHOD_NOT_ALLOWED.
                            // So check if this is an available resource, or a real 'Not Found' event.
                            sal_uInt32 nLifeTime = m_nOptsCacheLifeNotFound;
                            if( isResourceAvailable( xEnv, rResAccess, aDAVOptions ) )
                            {
                                SAL_WARN( "ucb.ucp.webdav", "OPTIONS - Got an SC_NOT_FOUND, but the URL <" << m_xIdentifier->getContentIdentifier() << "> resource exists" );
                                nLifeTime = m_nOptsCacheLifeNotImpl;
                            }
                            else
                            {
                                SAL_WARN( "ucb.ucp.webdav", "OPTIONS - SC_NOT_FOUND for URL <" << m_xIdentifier->getContentIdentifier() << ">" );
                                if ( networkAccessAllowed != nullptr )
                                {
                                    *networkAccessAllowed = *networkAccessAllowed
                                        && shouldAccessNetworkAfterException(e);
                                }
                            }
                            aStaticDAVOptionsCache.addDAVOptions( aDAVOptions,
                                                                  nLifeTime );
                        }
                        break;
                        default:
                        {
                            SAL_WARN( "ucb.ucp.webdav", "OPTIONS - DAV_HTTP_ERROR, for URL <" << m_xIdentifier->getContentIdentifier() << ">, HTTP error: "<< e.getStatus()
                                      << ", '" << e.getData() << "'" );
                            aDAVOptions.setHttpResponseStatusCode( e.getStatus() );
                            aDAVOptions.setHttpResponseStatusText( e.getData() );
                            // cache it, so OPTIONS won't be called again, this URL does not support it
                            aStaticDAVOptionsCache.addDAVOptions( aDAVOptions,
                                                                  m_nOptsCacheLifeNotImpl );
                        }
                        break;
                    }
                }
                break;
                // The 'DAVException::DAV_HTTP_REDIRECT' means we reached the maximum
                // number of redirections, consider the resource type as UNKNOWN
                // possibly a normal web site, not DAV
                case DAVException::DAV_HTTP_REDIRECT:
                default:
                {
                    SAL_WARN( "ucb.ucp.webdav","OPTIONS - General DAVException (or max DAV_HTTP_REDIRECT reached) for URL <" << m_xIdentifier->getContentIdentifier() << ">, DAV ExceptionCode: "
                              << e.getError() << ", HTTP error: "<< e.getStatus() );
                    aStaticDAVOptionsCache.addDAVOptions( aDAVOptions,
                                                          m_nOptsCacheLifeNotImpl );
                }
                break;
            }
        }
    }
    else
    {
        // check current response status code, perhaps we need to set networkAccessAllowed
        sal_uInt16 CachedResponseStatusCode = aDAVOptions.getHttpResponseStatusCode();
        if ( networkAccessAllowed != nullptr &&
             ( ( CachedResponseStatusCode == SC_NOT_FOUND ) ||
               ( CachedResponseStatusCode == SC_GONE ) ||
               ( CachedResponseStatusCode == USC_CONNECTION_TIMED_OUT ) ||
               ( CachedResponseStatusCode == USC_LOOKUP_FAILED ) ||
               ( CachedResponseStatusCode == USC_AUTH_FAILED ) ||
               ( CachedResponseStatusCode == USC_AUTHPROXY_FAILED )
                 )
            )
        {
            *networkAccessAllowed = *networkAccessAllowed && false;
        }
    }
    rDAVOptions = aDAVOptions;
}

//static
bool Content::isResourceAvailable( const css::uno::Reference< css::ucb::XCommandEnvironment >& xEnv,
                                  const std::unique_ptr< DAVResourceAccess > & rResAccess,
                                  DAVOptions& rDAVOptions )
{
    std::vector< rtl::OUString > aHeaderNames;
    DAVResource aResource;

    try
    {
        // To check for the physical URL resource availability, first
        // try using a simple HEAD command
        // if HEAD is successful, set element found.
        rResAccess->HEAD( aHeaderNames, aResource, xEnv );
        rDAVOptions.setHttpResponseStatusCode( 0 );
        rDAVOptions.setHttpResponseStatusText("");
        return true;
    }
    catch ( DAVException const & e )
    {
        if ( e.getError() == DAVException::DAV_HTTP_ERROR )
        {
            if ( e.getStatus() == SC_NOT_IMPLEMENTED ||
                 e.getStatus() == SC_METHOD_NOT_ALLOWED ||
                 e.getStatus() == SC_NOT_FOUND )
            {
                SAL_WARN( "ucb.ucp.webdav", "HEAD probably not implemented: fall back to a partial GET" );
                // set in cached OPTIONS "HEAD not implemented"
                // so it won't be used again on this resource
                rDAVOptions.setHeadAllowed( false );
                try
                {
                    // do a GET with a payload of 0, the server does not
                    // support HEAD (or has HEAD disabled)
                    DAVRequestHeaders aPartialGet;
                    aPartialGet.push_back(
                        DAVRequestHeader(
                            OUString( "Range" ),
                            OUString( "bytes=0-0" )));

                    rResAccess->GET0( aPartialGet,
                                     aHeaderNames,
                                     aResource,
                                     xEnv );
                    return true;
                }
                catch ( DAVException const & ex )
                {
                    if ( ex.getError() == DAVException::DAV_HTTP_ERROR )
                    {
                        rDAVOptions.setHttpResponseStatusCode( ex.getStatus() );
                        rDAVOptions.setHttpResponseStatusText( ex.getData() );
                    }
                }
            }
            else
            {
                rDAVOptions.setHttpResponseStatusCode( e.getStatus() );
                rDAVOptions.setHttpResponseStatusText( e.getData() );
            }
        }
        return false;
    }
    catch ( ... )
    {
    }
    // set SC_NOT_IMPLEMENTED since at a minimum GET must be implemented in a basic Web server
    rDAVOptions.setHttpResponseStatusCode( SC_NOT_IMPLEMENTED );
    rDAVOptions.setHttpResponseStatusText("");
    return false;
}


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