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

Quelle  CurlSession.cxx   Sprache: C

 
/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4; fill-column: 100 -*- */
/*
 * This file is part of the LibreOffice project.
 *
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
 */


#include "CurlSession.hxx"

#include "SerfLockStore.hxx"
#include "DAVProperties.hxx"
#include "UCBDeadPropertyValue.hxx"
#include "webdavresponseparser.hxx"

#include <cppuhelper/implbase.hxx>
#include <comphelper/processfactory.hxx>
#include <comphelper/attributelist.hxx>
#include <comphelper/scopeguard.hxx>
#include <comphelper/string.hxx>
#include <cppuhelper/queryinterface.hxx>
#include <cppuhelper/supportsservice.hxx>

#include <o3tl/safeint.hxx>
#include <o3tl/string_view.hxx>

#include <officecfg/Inet.hxx>
#include <officecfg/Office/Security.hxx>

#include <com/sun/star/beans/NamedValue.hpp>
#include <com/sun/star/io/Pipe.hpp>
#include <com/sun/star/lang/XServiceInfo.hpp>
#include <com/sun/star/io/SequenceInputStream.hpp>
#include <com/sun/star/io/SequenceOutputStream.hpp>
#include <com/sun/star/xml/sax/Writer.hpp>

#include <osl/time.h>
#include <sal/log.hxx>
#include <rtl/uri.hxx>
#include <rtl/strbuf.hxx>
#include <rtl/ustrbuf.hxx>
#include <systools/curlinit.hxx>
#include <tools/hostfilter.hxx>
#include <config_version.h>

#include <map>
#include <optional>
#include <tuple>
#include <utility>

#ifndef _WIN32
#include <arpa/inet.h>
#include <netinet/in.h>
#endif

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

namespace
{
void lock_cb(CURL*, curl_lock_data, curl_lock_access, void*);
void unlock_cb(CURL*, curl_lock_data, void*);

/// globals container
struct Init
{
    /// note: LockStore has its own mutex and calls CurlSession from its thread
    ///       so don't call LockStore with m_Mutex held to prevent deadlock.
    ::http_dav_ucp::SerfLockStore LockStore;

    /// libcurl shared data - to store cookies beyond one connection
    ::std::mutex ShareLock[CURL_LOCK_DATA_LAST];
    ::std::unique_ptr<CURLSH, http_dav_ucp::deleter_from_fn<CURLSH, curl_share_cleanup>> pShare;

    Init()
    {
        if (curl_global_init(CURL_GLOBAL_ALL) != CURLE_OK)
        {
            assert(!"curl_global_init failed");
            ::std::abort(); // can't handle error here
        }
        pShare.reset(curl_share_init());
        if (!pShare)
        {
            assert(!"curl_share_init failed");
            ::std::abort(); // can't handle error here
        }
        CURLSHcode sh = curl_share_setopt(pShare.get(), CURLSHOPT_LOCKFUNC, lock_cb);
        if (sh != CURLSHE_OK)
        {
            assert(!"curl_share_setopt failed");
            ::std::abort(); // can't handle error here
        }
        sh = curl_share_setopt(pShare.get(), CURLSHOPT_UNLOCKFUNC, unlock_cb);
        if (sh != CURLSHE_OK)
        {
            assert(!"curl_share_setopt failed");
            ::std::abort(); // can't handle error here
        }
        sh = curl_share_setopt(pShare.get(), CURLSHOPT_SHARE, CURL_LOCK_DATA_COOKIE);
        if (sh != CURLSHE_OK)
        {
            assert(!"curl_share_setopt failed");
            ::std::abort(); // can't handle error here
        }
        sh = curl_share_setopt(pShare.get(), CURLSHOPT_SHARE, CURL_LOCK_DATA_DNS);
        // might fail but this is just a perf improvement
        SAL_WARN_IF(sh != CURLSHE_OK, "ucb.ucp.webdav.curl""curl_share_setopt failed");
        sh = curl_share_setopt(pShare.get(), CURLSHOPT_SHARE, CURL_LOCK_DATA_SSL_SESSION);
        // might fail but this is just a perf improvement
        SAL_WARN_IF(sh != CURLSHE_OK, "ucb.ucp.webdav.curl""curl_share_setopt failed");
        // note: CURL_LOCK_DATA_CONNECT isn't safe in a multi threaded program.
    }
    // do not call curl_global_cleanup() - this is not the only client of curl
};
Init g_Init;

// global callbacks

void lock_cb(CURL* /*handle*/, curl_lock_data const data, curl_lock_access /*access*/,
             void/*userptr*/)
{
    assert(0 <= data && data < CURL_LOCK_DATA_LAST);
    try
    {
        g_Init.ShareLock[data].lock();
    }
    catch (std::exception const&)
    {
        ::std::abort();
    }
}

void unlock_cb(CURL* /*handle*/, curl_lock_data const data, void* /*userptr*/)
{
    assert(0 <= data && data < CURL_LOCK_DATA_LAST);
    g_Init.ShareLock[data].unlock();
}

struct ResponseHeaders
{
    ::std::vector<::std::pair<::std::vector<OString>, ::std::optional<long>>> HeaderFields;
    CURL* pCurl;
    ResponseHeaders(CURL* const i_pCurl)
        : pCurl(i_pCurl)
    {
    }
};

auto GetResponseCode(ResponseHeaders const& rHeaders) -> ::std::optional<long>
{
    return (rHeaders.HeaderFields.empty()) ? ::std::optional<long>{}
                                           : rHeaders.HeaderFields.back().second;
}

struct DownloadTarget
{
    uno::Reference<io::XOutputStream> xOutStream;
    ResponseHeaders const& rHeaders;
    DownloadTarget(uno::Reference<io::XOutputStream> i_xOutStream,
                   ResponseHeaders const& i_rHeaders)
        : xOutStream(std::move(i_xOutStream))
        , rHeaders(i_rHeaders)
    {
    }
};

struct UploadSource
{
    uno::Sequence<sal_Int8> const& rInData;
    size_t nPosition;
    UploadSource(uno::Sequence<sal_Int8> const& i_rInData)
        : rInData(i_rInData)
        , nPosition(0)
    {
    }
};

auto GetErrorString(CURLcode const rc, char constconst pErrorBuffer = nullptr) -> OString
{
    char constconst pMessage( // static fallback
        (pErrorBuffer && pErrorBuffer[0] != '\0') ? pErrorBuffer : curl_easy_strerror(rc));
    return OString::Concat("(") + OString::number(sal_Int32(rc)) + ") " + pMessage;
}

auto GetErrorStringMulti(CURLMcode const mc) -> OString
{
    return OString::Concat("(") + OString::number(sal_Int32(mc)) + ") " + curl_multi_strerror(mc);
}

/// represent an option to be passed to curl_easy_setopt()
struct CurlOption
{
    CURLoption const Option;
    enum class Type
    {
        Pointer,
        Long,
        CurlOffT
    };
    Type const Tag;
    union {
        void constconst pValue;
        long /*const*/ lValue;
        curl_off_t /*const*/ cValue;
    };
    char constconst pExceptionString;

    CurlOption(CURLoption const i_Option, void constconst i_Value,
               char constconst i_pExceptionString)
        : Option(i_Option)
        , Tag(Type::Pointer)
        , pValue(i_Value)
        , pExceptionString(i_pExceptionString)
    {
    }
    // Depending on platform, curl_off_t may be "long" or a larger type
    // so cannot use overloading to distinguish these cases.
    CurlOption(CURLoption const i_Option, curl_off_t const i_Value,
               char constconst i_pExceptionString, Type const type = Type::Long)
        : Option(i_Option)
        , Tag(type)
        , pExceptionString(i_pExceptionString)
    {
        static_assert(sizeof(long) <= sizeof(curl_off_t));
        switch (type)
        {
            case Type::Long:
                lValue = i_Value;
                break;
            case Type::CurlOffT:
                cValue = i_Value;
                break;
            default:
                assert(false);
        }
    }
};

/// combined guard class to ensure things are released in correct order,
/// particularly in ProcessRequest() error handling
class Guard
{
private:
    /// mutex *first* because m_oGuard requires it
    ::std::unique_lock<::std::mutex> m_Lock;
    ::std::vector<CurlOption> const m_Options;
    ::http_dav_ucp::CurlUri const& m_rURI;
    CURL* const m_pCurl;

public:
    explicit Guard(::std::mutex& rMutex, ::std::vector<CurlOption> aOptions,
                   ::http_dav_ucp::CurlUri const& rURI, CURL* const pCurl)
        : m_Lock(rMutex, ::std::defer_lock)
        , m_Options(std::move(aOptions))
        , m_rURI(rURI)
        , m_pCurl(pCurl)
    {
        Acquire();
    }
    ~Guard()
    {
        if (m_Lock.owns_lock())
        {
            Release();
        }
    }

    void Acquire()
    {
        assert(!m_Lock.owns_lock());
        m_Lock.lock();
        for (auto const& it : m_Options)
        {
            CURLcode rc(CURL_LAST); // warning C4701
            if (it.Tag == CurlOption::Type::Pointer)
            {
                rc = curl_easy_setopt(m_pCurl, it.Option, it.pValue);
            }
            else if (it.Tag == CurlOption::Type::Long)
            {
                rc = curl_easy_setopt(m_pCurl, it.Option, it.lValue);
            }
            else if (it.Tag == CurlOption::Type::CurlOffT)
            {
                rc = curl_easy_setopt(m_pCurl, it.Option, it.cValue);
            }
            else
            {
                assert(false);
            }
            if (it.pExceptionString != nullptr)
            {
                if (rc != CURLE_OK)
                {
                    SAL_WARN("ucb.ucp.webdav.curl",
                             "set " << it.pExceptionString << " failed: " << GetErrorString(rc));
                    throw ::http_dav_ucp::DAVException(
                        ::http_dav_ucp::DAVException::DAV_SESSION_CREATE,
                        ::http_dav_ucp::ConnectionEndPointString(m_rURI.GetHost(),
                                                                 m_rURI.GetPort()));
                }
            }
            else // many of the options cannot fail
            {
                assert(rc == CURLE_OK);
            }
        }
    }
    void Release()
    {
        assert(m_Lock.owns_lock());
        for (auto const& it : m_Options)
        {
            CURLcode rc(CURL_LAST); // warning C4701
            if (it.Tag == CurlOption::Type::Pointer)
            {
                rc = curl_easy_setopt(m_pCurl, it.Option, nullptr);
            }
            else if (it.Tag == CurlOption::Type::Long)
            {
                rc = curl_easy_setopt(m_pCurl, it.Option, 0L);
            }
            else if (it.Tag == CurlOption::Type::CurlOffT)
            {
                rc = curl_easy_setopt(m_pCurl, it.Option, curl_off_t(-1));
            }
            else
            {
                assert(false);
            }
            assert(rc == CURLE_OK);
            (void)rc;
        }
        m_Lock.unlock();
    }
};

// namespace

namespace http_dav_ucp
{
// libcurl callbacks:

static int debug_callback(CURL* handle, curl_infotype type, char* data, size_t size,
                          void/*userdata*/)
{
    char const* pType(nullptr);
    switch (type)
    {
        case CURLINFO_TEXT:
            SAL_INFO("ucb.ucp.webdav.curl""debug log: " << handle << ": " << data);
            return 0;
        case CURLINFO_HEADER_IN:
            SAL_INFO("ucb.ucp.webdav.curl",
                     "CURLINFO_HEADER_IN: " << handle << ": " << OString(data, size));
            return 0;
        case CURLINFO_HEADER_OUT:
        {
            // unlike IN, this is all headers in one call
            OString tmp(data, size);
            sal_Int32 const start(tmp.indexOf("Authorization: "));
            if (start != -1)
            {
                sal_Int32 const end(tmp.indexOf("\r\n", start));
                assert(end != -1);
                sal_Int32 const len(SAL_N_ELEMENTS("Authorization: ") - 1);
                tmp = tmp.replaceAt(
                    start + len, end - start - len,
                    Concat2View(OString::number(end - start - len) + " bytes redacted"));
            }
            SAL_INFO("ucb.ucp.webdav.curl""CURLINFO_HEADER_OUT: " << handle << ": " << tmp);
            return 0;
        }
        case CURLINFO_DATA_IN:
            pType = "CURLINFO_DATA_IN";
            break;
        case CURLINFO_DATA_OUT:
            pType = "CURLINFO_DATA_OUT";
            break;
        case CURLINFO_SSL_DATA_IN:
            pType = "CURLINFO_SSL_DATA_IN";
            break;
        case CURLINFO_SSL_DATA_OUT:
            pType = "CURLINFO_SSL_DATA_OUT";
            break;
        default:
            SAL_WARN("ucb.ucp.webdav.curl""unexpected debug log type");
            return 0;
    }
    SAL_INFO("ucb.ucp.webdav.curl""debug log: " << handle << ": " << pType << " " << size);
    return 0;
}

static size_t write_callback(charconst ptr, size_t const size, size_t const nmemb,
                             voidconst userdata)
{
    autoconst pTarget(static_cast<DownloadTarget*>(userdata));
    if (!pTarget) // looks like ~every request may have a response body
    {
        return nmemb;
    }
    assert(size == 1); // says the man page
    (void)size;
    assert(pTarget->xOutStream.is());
    auto const oResponseCode(GetResponseCode(pTarget->rHeaders));
    if (!oResponseCode)
    {
        return 0; // that is an error
    }
    // always write, for exception handler in ProcessRequest()
    //    if (200 <= *oResponseCode && *oResponseCode < 300)
    {
        try
        {
            uno::Sequence<sal_Int8> const data(reinterpret_cast<sal_Int8*>(ptr), nmemb);
            pTarget->xOutStream->writeBytes(data);
        }
        catch (...)
        {
            SAL_WARN("ucb.ucp.webdav.curl""exception in write_callback");
            return 0; // error
        }
    }
    // else: ignore the body? CurlSession will check the status eventually
    return nmemb;
}

static size_t read_callback(charconst buffer, size_t const size, size_t const nitems,
                            voidconst userdata)
{
    autoconst pSource(static_cast<UploadSource*>(userdata));
    assert(pSource);
    size_t const nBytes(size * nitems);
    size_t nRet(0);
    try
    {
        assert(pSource->nPosition <= o3tl::make_unsigned(pSource->rInData.getLength()));
        nRet = ::std::min<size_t>(pSource->rInData.getLength() - pSource->nPosition, nBytes);
        ::std::memcpy(buffer, pSource->rInData.getConstArray() + pSource->nPosition, nRet);
        pSource->nPosition += nRet;
    }
    catch (...)
    {
        SAL_WARN("ucb.ucp.webdav.curl""exception in read_callback");
        return CURL_READFUNC_ABORT; // error
    }
    return nRet;
}

static size_t header_callback(charconst buffer, size_t const size, size_t const nitems,
                              voidconst userdata)
{
    autoconst pHeaders(static_cast<ResponseHeaders*>(userdata));
    assert(pHeaders);
#if 0
    if (!pHeaders) // TODO maybe not needed in every request? not sure
    {
        return nitems;
    }
#endif
    assert(size == 1); // says the man page
    (void)size;
    try
    {
        if (nitems <= 2)
        {
            // end of header, body follows...
            if (pHeaders->HeaderFields.empty())
            {
                SAL_WARN("ucb.ucp.webdav.curl""header_callback: empty header?");
                return 0; // error
            }
            // unfortunately there's no separate callback when the body begins,
            // so have to manually retrieve the status code here
            long statusCode(SC_NONE);
            auto rc = curl_easy_getinfo(pHeaders->pCurl, CURLINFO_RESPONSE_CODE, &statusCode);
            assert(rc == CURLE_OK);
            (void)rc;
            // always put the current response code here - wasn't necessarily in this header
            pHeaders->HeaderFields.back().second.emplace(statusCode);
        }
        else if (buffer[0] == ' ' || buffer[0] == '\t'// folded header field?
        {
            size_t i(0);
            do
            {
                ++i;
            } while (i == ' ' || i == '\t');
            if (pHeaders->HeaderFields.empty() || pHeaders->HeaderFields.back().second
                || pHeaders->HeaderFields.back().first.empty())
            {
                SAL_WARN("ucb.ucp.webdav.curl",
                         "header_callback: folded header field without start");
                return 0; // error
            }
            pHeaders->HeaderFields.back().first.back()
                += OString::Concat(" ") + ::std::string_view(&buffer[i], nitems - i);
        }
        else
        {
            if (pHeaders->HeaderFields.empty() || pHeaders->HeaderFields.back().second)
            {
                pHeaders->HeaderFields.emplace_back();
            }
            pHeaders->HeaderFields.back().first.emplace_back(OString(buffer, nitems));
        }
    }
    catch (...)
    {
        SAL_WARN("ucb.ucp.webdav.curl""exception in header_callback");
        return 0; // error
    }
    return nitems;
}

static auto ProcessHeaders(::std::vector<OString> const& rHeaders) -> ::std::map<OUString, OUString>
{
    ::std::map<OUString, OUString> ret;
    for (OString const& rLine : rHeaders)
    {
        std::string_view line;
        if (!rLine.endsWith("\r\n", &line))
        {
            SAL_WARN("ucb.ucp.webdav.curl""invalid header field (no CRLF)");
            continue;
        }
        if (o3tl::starts_with(line, "HTTP/"// first line
            || line.empty()) // last line
        {
            continue;
        }
        const std::string_view::size_type nColon(line.find(':'));
        if (nColon == std::string_view::npos)
        {
            {
                SAL_WARN("ucb.ucp.webdav.curl""invalid header field (no :)");
            }
            continue;
        }
        if (nColon == 0)
        {
            SAL_WARN("ucb.ucp.webdav.curl""invalid header field (empty name)");
            continue;
        }
        assert(nColon != std::string_view::npos);
        // case insensitive; must be ASCII
        auto const name(::rtl::OStringToOUString(OString(line.substr(0, nColon)).toAsciiLowerCase(),
                                                 RTL_TEXTENCODING_ASCII_US));
        std::string_view::size_type nStart(nColon + 1);
        while (nStart < line.size() && (line[nStart] == ' ' || line[nStart] == '\t'))
        {
            ++nStart;
        }
        std::string_view::size_type nEnd(line.size());
        while (nStart < nEnd && (line[nEnd - 1] == ' ' || line[nEnd - 1] == '\t'))
        {
            --nEnd;
        }
        // RFC 7230 says that only ASCII works reliably anyway (neon also did this)
        auto const value(::rtl::OStringToOUString(line.substr(nStart, nEnd - nStart),
                                                  RTL_TEXTENCODING_ASCII_US));
        auto const it(ret.find(name));
        if (it != ret.end())
        {
            it->second = it->second + "," + value;
        }
        else
        {
            ret[name] = value;
        }
    }
    return ret;
}

static auto ExtractRequestedHeaders(
    ResponseHeaders const& rHeaders,
    ::std::pair<::std::vector<OUString> const&, DAVResource&> constconst pRequestedHeaders)
    -> void
{
    ::std::map<OUString, OUString> const headerMap(
        ProcessHeaders(rHeaders.HeaderFields.back().first));
    if (pRequestedHeaders)
    {
        for (OUString const& rHeader : pRequestedHeaders->first)
        {
            auto const it(headerMap.find(rHeader.toAsciiLowerCase()));
            if (it != headerMap.end())
            {
                DAVPropertyValue value;
                value.IsCaseSensitive = false;
                value.Name = it->first;
                value.Value <<= it->second;
                pRequestedHeaders->second.properties.push_back(value);
            }
        }
    }
}

// this appears to be the only way to get the "realm" from libcurl
static auto ExtractRealm(ResponseHeaders const& rHeaders, char constconst pAuthHeaderName)
    -> ::std::optional<OUString>
{
    ::std::map<OUString, OUString> const headerMap(
        ProcessHeaders(rHeaders.HeaderFields.back().first));
    auto const it(headerMap.find(OUString::createFromAscii(pAuthHeaderName).toAsciiLowerCase()));
    if (it == headerMap.end())
    {
        SAL_WARN("ucb.ucp.webdav.curl""cannot find auth header");
        return {};
    }
    // It may be possible that the header contains multiple methods each with
    // a different realm - extract only the first one bc the downstream API
    // only supports one anyway.
    // case insensitive!
    auto i(it->second.toAsciiLowerCase().indexOf("realm="));
    // is optional
    if (i == -1)
    {
        SAL_INFO("ucb.ucp.webdav.curl""auth header has no realm");
        return {};
    }
    // no whitespace allowed before or after =
    i += ::std::strlen("realm=");
    if (it->second.getLength() < i + 2 || it->second[i] != '\"')
    {
        SAL_WARN("ucb.ucp.webdav.curl""no realm value");
        return {};
    }
    ++i;
    OUStringBuffer buf;
    while (i < it->second.getLength() && it->second[i] != '\"')
    {
        if (it->second[i] == '\\'// quoted-pair escape
        {
            ++i;
            if (it->second.getLength() <= i)
            {
                SAL_WARN("ucb.ucp.webdav.curl""unterminated quoted-pair");
                return {};
            }
        }
        buf.append(it->second[i]);
        ++i;
    }
    if (it->second.getLength() <= i)
    {
        SAL_WARN("ucb.ucp.webdav.curl""unterminated realm");
        return {};
    }
    return buf.makeStringAndClear();
}

#ifndef _WIN32

static std::string makeIPAddress(const sockaddr& ai_addr)
{
    char addrstr[INET6_ADDRSTRLEN];

    static_assert(INET6_ADDRSTRLEN >= INET_ADDRSTRLEN, "ipv6 addresses are longer than ipv4");
    const void* inAddr = nullptr;
    switch (ai_addr.sa_family)
    {
        case AF_INET:
        {
            auto ipv4 = reinterpret_cast<const sockaddr_in*>(&ai_addr);
            inAddr = &(ipv4->sin_addr);
            break;
        }
        case AF_INET6:
        {
            auto ipv6 = reinterpret_cast<const sockaddr_in6*>(&ai_addr);
            inAddr = &(ipv6->sin6_addr);
            break;
        }
    }

    if (!inAddr)
    {
        SAL_WARN("ucb.ucp.webdav.curl""Unknown sa_family: " << ai_addr.sa_family);
        return std::string();
    }

    const char* result = inet_ntop(ai_addr.sa_family, inAddr, addrstr, sizeof(addrstr));
    if (!result)
    {
        SAL_WARN("ucb.ucp.webdav.curl""inet_ntop failure");
        return std::string();
    }
    return std::string(result);
}

// filter out connections to instance metadata
static curl_socket_t opensocket_callback(void/*clientp*/, curlsocktype purpose,
                                         struct curl_sockaddr* address)
{
    if (purpose == CURLSOCKTYPE_IPCXN)
    {
        if (address->family == AF_INET && makeIPAddress(address->addr) == "169.254.169.254")
        {
            SAL_WARN("ucb.ucp.webdav.curl""ignoring instance metadata ip");
            return CURL_SOCKET_BAD;
        }
        else if (address->family == AF_INET6 && makeIPAddress(address->addr) == "fd00:ec2::254")
        {
            SAL_WARN("ucb.ucp.webdav.curl""ignoring instance metadata ip");
            return CURL_SOCKET_BAD;
        }
    }
    return socket(address->family, address->socktype, address->protocol);
}

#endif

CurlSession::CurlSession(uno::Reference<uno::XComponentContext> xContext,
                         ::rtl::Reference<DAVSessionFactory> const& rpFactory, OUString const& rURI,
                         uno::Sequence<beans::NamedValue> const& rFlags,
                         ::ucbhelper::InternetProxyDecider const& rProxyDecider)
    : DAVSession(rpFactory)
    , m_xContext(std::move(xContext))
    , m_Flags(rFlags)
    , m_URI(rURI)
    , m_Proxy(rProxyDecider.getProxy(m_URI.GetScheme(), m_URI.GetHost(), m_URI.GetPort()))
{
    assert(m_URI.GetScheme() == "http" || m_URI.GetScheme() == "https");
    m_pCurlMulti.reset(curl_multi_init());
    if (!m_pCurlMulti)
    {
        SAL_WARN("ucb.ucp.webdav.curl""curl_multi_init failed");
        throw DAVException(DAVException::DAV_SESSION_CREATE,
                           ConnectionEndPointString(m_URI.GetHost(), m_URI.GetPort()));
    }
    m_pCurl.reset(curl_easy_init());
    if (!m_pCurl)
    {
        SAL_WARN("ucb.ucp.webdav.curl""curl_easy_init failed");
        throw DAVException(DAVException::DAV_SESSION_CREATE,
                           ConnectionEndPointString(m_URI.GetHost(), m_URI.GetPort()));
    }
    m_ErrorBuffer[0] = '\0';
    // this supposedly gives the highest quality error reporting
    auto rc = curl_easy_setopt(m_pCurl.get(), CURLOPT_ERRORBUFFER, m_ErrorBuffer);
    assert(rc == CURLE_OK);
#if 1
    // just for debugging...
    rc = curl_easy_setopt(m_pCurl.get(), CURLOPT_DEBUGFUNCTION, debug_callback);
    assert(rc == CURLE_OK);
#endif
    rc = curl_easy_setopt(m_pCurl.get(), CURLOPT_VERBOSE, 1L);
    assert(rc == CURLE_OK);
    // accept any encoding supported by libcurl
    rc = curl_easy_setopt(m_pCurl.get(), CURLOPT_ACCEPT_ENCODING, "");
    if (rc != CURLE_OK)
    {
        SAL_WARN("ucb.ucp.webdav.curl""CURLOPT_ACCEPT_ENCODING failed: " << GetErrorString(rc));
        throw DAVException(DAVException::DAV_SESSION_CREATE,
                           ConnectionEndPointString(m_URI.GetHost(), m_URI.GetPort()));
    }
    auto const connectTimeout(officecfg::Inet::Settings::ConnectTimeout::get());
    // default is 300s
    rc = curl_easy_setopt(m_pCurl.get(), CURLOPT_CONNECTTIMEOUT,
                          ::std::max<long>(2L, ::std::min<long>(connectTimeout, 180L)));
    if (rc != CURLE_OK)
    {
        SAL_WARN("ucb.ucp.webdav.curl""CURLOPT_CONNECTTIMEOUT failed: " << GetErrorString(rc));
        throw DAVException(DAVException::DAV_SESSION_CREATE,
                           ConnectionEndPointString(m_URI.GetHost(), m_URI.GetPort()));
    }
    auto const readTimeout(officecfg::Inet::Settings::ReadTimeout::get());
    m_nReadTimeout = ::std::max<int>(20, ::std::min<long>(readTimeout, 180)) * 1000;
    // default is infinite
    rc = curl_easy_setopt(m_pCurl.get(), CURLOPT_TIMEOUT, 300L);
    if (rc != CURLE_OK)
    {
        SAL_WARN("ucb.ucp.webdav.curl""CURLOPT_TIMEOUT failed: " << GetErrorString(rc));
        throw DAVException(DAVException::DAV_SESSION_CREATE,
                           ConnectionEndPointString(m_URI.GetHost(), m_URI.GetPort()));
    }
    rc = curl_easy_setopt(m_pCurl.get(), CURLOPT_WRITEFUNCTION, &write_callback);
    assert(rc == CURLE_OK);
    rc = curl_easy_setopt(m_pCurl.get(), CURLOPT_READFUNCTION, &read_callback);
    assert(rc == CURLE_OK);
    rc = curl_easy_setopt(m_pCurl.get(), CURLOPT_HEADERFUNCTION, &header_callback);
    assert(rc == CURLE_OK);
    ::InitCurl_easy(m_pCurl.get());
    if (officecfg::Office::Security::Net::AllowInsecureProtocols::get())
    {
    // tdf#149921 by default, with schannel (WNT) connection fails if revocation
    // lists cannot be checked; try to limit the checking to when revocation
    // lists can actually be retrieved (usually not the case for self-signed CA)
#if CURL_AT_LEAST_VERSION(7, 70, 0)
        rc = curl_easy_setopt(m_pCurl.get(), CURLOPT_SSL_OPTIONS, CURLSSLOPT_REVOKE_BEST_EFFORT);
        assert(rc == CURLE_OK);
        rc = curl_easy_setopt(m_pCurl.get(), CURLOPT_PROXY_SSL_OPTIONS,
                              CURLSSLOPT_REVOKE_BEST_EFFORT);
        assert(rc == CURLE_OK);
#endif
    }
    rc = curl_easy_setopt(m_pCurl.get(), CURLOPT_SHARE, g_Init.pShare.get());
    assert(rc == CURLE_OK);
    // set this initially, may be overwritten during authentication
    rc = curl_easy_setopt(m_pCurl.get(), CURLOPT_HTTPAUTH, CURLAUTH_ANY);
    assert(rc == CURLE_OK); // ANY is always available
    // always set CURLOPT_PROXY to suppress proxy detection in libcurl
    OString const utf8Proxy(OUStringToOString(m_Proxy, RTL_TEXTENCODING_UTF8));
    rc = curl_easy_setopt(m_pCurl.get(), CURLOPT_PROXY, utf8Proxy.getStr());
    if (rc != CURLE_OK)
    {
        SAL_WARN("ucb.ucp.webdav.curl""CURLOPT_PROXY failed: " << GetErrorString(rc));
        throw DAVException(DAVException::DAV_SESSION_CREATE, m_Proxy);
    }
    if (!m_Proxy.isEmpty())
    {
        // set this initially, may be overwritten during authentication
        rc = curl_easy_setopt(m_pCurl.get(), CURLOPT_PROXYAUTH, CURLAUTH_ANY);
        assert(rc == CURLE_OK); // ANY is always available
    }
    auto const it(::std::find_if(m_Flags.begin(), m_Flags.end(),
                                 [](auto const& rFlag) { return rFlag.Name == "KeepAlive"; }));
    if (it != m_Flags.end() && it->Value.get<bool>())
    {
        // neon would close the connection from ne_end_request(), this seems
        // to be the equivalent and not CURLOPT_TCP_KEEPALIVE
        rc = curl_easy_setopt(m_pCurl.get(), CURLOPT_FORBID_REUSE, 1L);
        assert(rc == CURLE_OK);
    }
    if (HostFilter::isExemptVerifyHost(m_URI.GetHost()))
    {
        rc = curl_easy_setopt(m_pCurl.get(), CURLOPT_SSL_VERIFYHOST, 0L);
        assert(rc == CURLE_OK);
    }

#ifndef _WIN32
    if (comphelper::LibreOfficeKit::isActive())
    {
        //See https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/instancedata-data-retrieval.html
        rc = curl_easy_setopt(m_pCurl.get(), CURLOPT_OPENSOCKETFUNCTION, opensocket_callback);
        assert(rc == CURLE_OK);
    }
#endif
}

CurlSession::~CurlSession() {}

auto CurlSession::CanUse(OUString const& rURI, uno::Sequence<beans::NamedValue> const& rFlags)
    -> bool
{
    try
    {
        CurlUri const uri(rURI);

        return m_URI.GetScheme() == uri.GetScheme() && m_URI.GetHost() == uri.GetHost()
               && m_URI.GetPort() == uri.GetPort() && m_Flags == rFlags;
    }
    catch (DAVException const&)
    {
        return false;
    }
}

auto CurlSession::UsesProxy() -> bool
{
    assert(m_URI.GetScheme() == "http" || m_URI.GetScheme() == "https");
    return !m_Proxy.isEmpty();
}

auto CurlSession::abort() -> void
{
    // note: abort() was a no-op since OOo 3.2 and before that it crashed.
    bool expected(false);
    // it would be pointless to lock m_Mutex here as the other thread holds it
    if (m_AbortFlag.compare_exchange_strong(expected, true))
    {
        // This function looks safe to call without m_Mutex as long as the
        // m_pCurlMulti handle is not destroyed, and the caller must own a ref
        // to this object which keeps it alive; it should cause poll to return.
        curl_multi_wakeup(m_pCurlMulti.get());
    }
}

/// this is just a bunch of static member functions called from CurlSession
struct CurlProcessor
{
    static auto URIReferenceToURI(CurlSession& rSession, std::u16string_view rURIReference)
        -> CurlUri;

    static auto ProcessRequestImpl(
        CurlSession& rSession, CurlUri const& rURI, OUString const& rMethod,
        curl_slist* pRequestHeaderList, uno::Reference<io::XOutputStream> const* pxOutStream,
        uno::Sequence<sal_Int8> const* pInData,
        ::std::pair<::std::vector<OUString> const&, DAVResource&> const* pRequestedHeaders,
        ResponseHeaders& rHeaders) -> void;

    static auto ProcessRequest(
        CurlSession& rSession, CurlUri const& rURI, OUString const& rMethod,
        ::std::vector<CurlOption> const& rOptions, DAVRequestEnvironment const* pEnv,
        ::std::unique_ptr<curl_slist, deleter_from_fn<curl_slist, curl_slist_free_all>>
            pRequestHeaderList,
        uno::Reference<io::XOutputStream> const* pxOutStream,
        uno::Reference<io::XInputStream> const* pxInStream,
        ::std::pair<::std::vector<OUString> const&, DAVResource&> const* pRequestedHeaders) -> void;

    static auto
    PropFind(CurlSession& rSession, CurlUri const& rURI, Depth depth,
             ::std::tuple<::std::vector<OUString> const&, ::std::vector<DAVResource>* const,
                          ::std::vector<ucb::Lock>* constconst* o_pRequestedProperties,
             ::std::vector<DAVResourceInfo>* const o_pResourceInfos,
             DAVRequestEnvironment const& rEnv) -> void;

    static auto MoveOrCopy(CurlSession& rSession, std::u16string_view rSourceURIReference,
                           ::std::u16string_view rDestinationURI, DAVRequestEnvironment const& rEnv,
                           bool isOverwrite, char const* pMethod) -> void;

    static auto Lock(CurlSession& rSession, CurlUri const& rURI, DAVRequestEnvironment const* pEnv,
                     ::std::unique_ptr<curl_slist, deleter_from_fn<curl_slist, curl_slist_free_all>>
                         pRequestHeaderList,
                     uno::Reference<io::XInputStream> const* pxInStream)
        -> ::std::vector<::std::pair<ucb::Lock, sal_Int32>>;

    static auto Unlock(CurlSession& rSession, CurlUri const& rURI,
                       DAVRequestEnvironment const* pEnv) -> void;
};

auto CurlProcessor::URIReferenceToURI(CurlSession& rSession, std::u16string_view rURIReference)
    -> CurlUri
{
    // No need to acquire rSession.m_Mutex because accessed members are const.
    if (rSession.UsesProxy())
    // very odd, but see DAVResourceAccess::getRequestURI() :-/
    {
        assert(o3tl::starts_with(rURIReference, u"http://")
               || o3tl::starts_with(rURIReference, u"https://"));
        return CurlUri(rURIReference);
    }
    else
    {
        assert(o3tl::starts_with(rURIReference, u"/"));
        return rSession.m_URI.CloneWithRelativeRefPathAbsolute(rURIReference);
    }
}

/// main function to initiate libcurl requests
auto CurlProcessor::ProcessRequestImpl(
    CurlSession& rSession, CurlUri const& rURI, OUString const& rMethod,
    curl_slist* const pRequestHeaderList,
    uno::Reference<io::XOutputStream> constconst pxOutStream,
    uno::Sequence<sal_Int8> constconst pInData,
    ::std::pair<::std::vector<OUString> const&, DAVResource&> constconst pRequestedHeaders,
    ResponseHeaders& rHeaders) -> void
{
    ::comphelper::ScopeGuard const g([&]() {
        auto rc = curl_easy_setopt(rSession.m_pCurl.get(), CURLOPT_HEADERDATA, nullptr);
        assert(rc == CURLE_OK);
        (void)rc;
        if (pxOutStream)
        {
            rc = curl_easy_setopt(rSession.m_pCurl.get(), CURLOPT_WRITEDATA, nullptr);
            assert(rc == CURLE_OK);
        }
        if (pInData)
        {
            rc = curl_easy_setopt(rSession.m_pCurl.get(), CURLOPT_READDATA, nullptr);
            assert(rc == CURLE_OK);
            rc = curl_easy_setopt(rSession.m_pCurl.get(), CURLOPT_UPLOAD, 0L);
            assert(rc == CURLE_OK);
        }
        if (pRequestHeaderList)
        {
            rc = curl_easy_setopt(rSession.m_pCurl.get(), CURLOPT_HTTPHEADER, nullptr);
            assert(rc == CURLE_OK);
        }
    });

    if (pRequestHeaderList)
    {
        auto rc = curl_easy_setopt(rSession.m_pCurl.get(), CURLOPT_HTTPHEADER, pRequestHeaderList);
        assert(rc == CURLE_OK);
        (void)rc;
    }

    auto rc = curl_easy_setopt(rSession.m_pCurl.get(), CURLOPT_CURLU, rURI.GetCURLU());
    assert(rc == CURLE_OK); // can't fail since 7.63.0

    rc = curl_easy_setopt(rSession.m_pCurl.get(), CURLOPT_HEADERDATA, &rHeaders);
    assert(rc == CURLE_OK);
    ::std::optional<DownloadTarget> oDownloadTarget;
    if (pxOutStream)
    {
        oDownloadTarget.emplace(*pxOutStream, rHeaders);
        rc = curl_easy_setopt(rSession.m_pCurl.get(), CURLOPT_WRITEDATA, &*oDownloadTarget);
        assert(rc == CURLE_OK);
    }
    ::std::optional<UploadSource> oUploadSource;
    if (pInData)
    {
        oUploadSource.emplace(*pInData);
        rc = curl_easy_setopt(rSession.m_pCurl.get(), CURLOPT_READDATA, &*oUploadSource);
        assert(rc == CURLE_OK);
    }
    rSession.m_ErrorBuffer[0] = '\0';

    // note: easy handle must be added for *every* transfer!
    // otherwise it gets stuck in MSTATE_MSGSENT forever after 1st transfer
    auto mc = curl_multi_add_handle(rSession.m_pCurlMulti.get(), rSession.m_pCurl.get());
    if (mc != CURLM_OK)
    {
        SAL_WARN("ucb.ucp.webdav.curl",
                 "curl_multi_add_handle failed: " << GetErrorStringMulti(mc));
        throw DAVException(
            DAVException::DAV_SESSION_CREATE,
            ConnectionEndPointString(rSession.m_URI.GetHost(), rSession.m_URI.GetPort()));
    }
    ::comphelper::ScopeGuard const gg([&]() {
        mc = curl_multi_remove_handle(rSession.m_pCurlMulti.get(), rSession.m_pCurl.get());
        if (mc != CURLM_OK)
        {
            SAL_WARN("ucb.ucp.webdav.curl",
                     "curl_multi_remove_handle failed: " << GetErrorStringMulti(mc));
        }
    });

    // this is where libcurl actually does something
    rc = CURL_LAST; // clear current value
    int nRunning;
    do
    {
        mc = curl_multi_perform(rSession.m_pCurlMulti.get(), &nRunning);
        if (mc != CURLM_OK)
        {
            SAL_WARN("ucb.ucp.webdav.curl",
                     "curl_multi_perform failed: " << GetErrorStringMulti(mc));
            throw DAVException(
                DAVException::DAV_HTTP_CONNECT,
                ConnectionEndPointString(rSession.m_URI.GetHost(), rSession.m_URI.GetPort()));
        }
        if (nRunning == 0)
        { // short request like HEAD on loopback could be done in first call
            break;
        }
        int nFDs;
        mc = curl_multi_poll(rSession.m_pCurlMulti.get(), nullptr, 0, rSession.m_nReadTimeout,
                             &nFDs);
        if (mc != CURLM_OK)
        {
            SAL_WARN("ucb.ucp.webdav.curl""curl_multi_poll failed: " << GetErrorStringMulti(mc));
            throw DAVException(
                DAVException::DAV_HTTP_CONNECT,
                ConnectionEndPointString(rSession.m_URI.GetHost(), rSession.m_URI.GetPort()));
        }
        if (rSession.m_AbortFlag.load())
        { // flag was set by abort() -> not sure what exception to throw?
            throw DAVException(DAVException::DAV_HTTP_ERROR, u"abort() was called"_ustr, 0);
        }
    } while (nRunning != 0);
    // there should be exactly 1 CURLMsg now, but the interface is
    // extensible so future libcurl versions could yield additional things
    do
    {
        CURLMsg constconst pMsg = curl_multi_info_read(rSession.m_pCurlMulti.get(), &nRunning);
        if (pMsg && pMsg->msg == CURLMSG_DONE)
        {
            assert(pMsg->easy_handle == rSession.m_pCurl.get());
            rc = pMsg->data.result;
        }
        else
        {
            SAL_WARN("ucb.ucp.webdav.curl""curl_multi_info_read unexpected result");
        }
    } while (nRunning != 0);

    // error handling part 1: libcurl errors
    if (rc != CURLE_OK)
    {
        // TODO: is there any value in extracting CURLINFO_OS_ERRNO
        auto const errorString(GetErrorString(rc, rSession.m_ErrorBuffer));
        SAL_WARN("ucb.ucp.webdav.curl""curl_easy_perform failed: " << errorString);
        switch (rc)
        {
            case CURLE_UNSUPPORTED_PROTOCOL:
                throw DAVException(DAVException::DAV_UNSUPPORTED, u""_ustr,
                                   rtl::OStringToOUString(errorString, RTL_TEXTENCODING_UTF8));
            case CURLE_COULDNT_RESOLVE_PROXY:
                throw DAVException(DAVException::DAV_HTTP_LOOKUP, rSession.m_Proxy,
                                   rtl::OStringToOUString(errorString, RTL_TEXTENCODING_UTF8));
            case CURLE_COULDNT_RESOLVE_HOST:
                throw DAVException(
                    DAVException::DAV_HTTP_LOOKUP,
                    ConnectionEndPointString(rSession.m_URI.GetHost(), rSession.m_URI.GetPort()),
                    rtl::OStringToOUString(errorString, RTL_TEXTENCODING_UTF8));
            case CURLE_COULDNT_CONNECT:
            case CURLE_SSL_CONNECT_ERROR:
            case CURLE_SSL_CERTPROBLEM:
            case CURLE_SSL_CIPHER:
            case CURLE_PEER_FAILED_VERIFICATION:
            case CURLE_SSL_ISSUER_ERROR:
            case CURLE_SSL_PINNEDPUBKEYNOTMATCH:
            case CURLE_SSL_INVALIDCERTSTATUS:
            case CURLE_FAILED_INIT:
#if CURL_AT_LEAST_VERSION(7, 69, 0)
            case CURLE_QUIC_CONNECT_ERROR:
#endif
                throw DAVException(
                    DAVException::DAV_HTTP_CONNECT,
                    ConnectionEndPointString(rSession.m_URI.GetHost(), rSession.m_URI.GetPort()),
                    rtl::OStringToOUString(errorString, RTL_TEXTENCODING_UTF8));
            case CURLE_REMOTE_ACCESS_DENIED:
            case CURLE_LOGIN_DENIED:
            case CURLE_AUTH_ERROR:
                throw DAVException(
                    DAVException::DAV_HTTP_AUTH, // probably?
                    ConnectionEndPointString(rSession.m_URI.GetHost(), rSession.m_URI.GetPort()),
                    rtl::OStringToOUString(errorString, RTL_TEXTENCODING_UTF8));
            case CURLE_WRITE_ERROR:
            case CURLE_READ_ERROR: // error returned from our callbacks
            case CURLE_OUT_OF_MEMORY:
            case CURLE_ABORTED_BY_CALLBACK:
            case CURLE_BAD_FUNCTION_ARGUMENT:
            case CURLE_SEND_ERROR:
            case CURLE_RECV_ERROR:
            case CURLE_SSL_CACERT_BADFILE:
            case CURLE_SSL_CRL_BADFILE:
            case CURLE_RECURSIVE_API_CALL:
                throw DAVException(
                    DAVException::DAV_HTTP_FAILED,
                    ConnectionEndPointString(rSession.m_URI.GetHost(), rSession.m_URI.GetPort()),
                    rtl::OStringToOUString(errorString, RTL_TEXTENCODING_UTF8));
            case CURLE_OPERATION_TIMEDOUT:
                throw DAVException(
                    DAVException::DAV_HTTP_TIMEOUT,
                    ConnectionEndPointString(rSession.m_URI.GetHost(), rSession.m_URI.GetPort()),
                    rtl::OStringToOUString(errorString, RTL_TEXTENCODING_UTF8));
            default// lots of generic errors
                throw DAVException(DAVException::DAV_HTTP_ERROR, u""_ustr,
                                   rtl::OStringToOUString(errorString, RTL_TEXTENCODING_UTF8));
        }
    }
    // error handling part 2: HTTP status codes
    long statusCode(SC_NONE);
    rc = curl_easy_getinfo(rSession.m_pCurl.get(), CURLINFO_RESPONSE_CODE, &statusCode);
    assert(rc == CURLE_OK);
    assert(statusCode != SC_NONE); // ??? should be error returned from perform?
    SAL_INFO("ucb.ucp.webdav.curl""HTTP status code: " << statusCode);
    if (statusCode < 300)
    {
        // neon did this regardless of status or even error, which seems odd
        ExtractRequestedHeaders(rHeaders, pRequestedHeaders);
    }
    else
    {
        // create message containing the HTTP method and response status line
        OUString statusLine("\n" + rMethod + "\n=>\n");
        if (!rHeaders.HeaderFields.empty() && !rHeaders.HeaderFields.back().first.empty()
            && rHeaders.HeaderFields.back().first.front().startsWith("HTTP"))
        {
            statusLine += ::rtl::OStringToOUString(
                ::o3tl::trim(rHeaders.HeaderFields.back().first.front()),
                RTL_TEXTENCODING_ASCII_US);
        }
        switch (statusCode)
        {
            case SC_REQUEST_TIMEOUT:
            {
                throw DAVException(
                    DAVException::DAV_HTTP_TIMEOUT,
                    ConnectionEndPointString(rSession.m_URI.GetHost(), rSession.m_URI.GetPort()));
                break;
            }
            case SC_MOVED_PERMANENTLY:
            case SC_MOVED_TEMPORARILY:
            case SC_SEE_OTHER:
            case SC_TEMPORARY_REDIRECT:
            {
                // could also use CURLOPT_FOLLOWLOCATION but apparently the
                // upper layer wants to know about redirects?
                char* pRedirectURL(nullptr);
                rc = curl_easy_getinfo(rSession.m_pCurl.get(), CURLINFO_REDIRECT_URL,
                                       &pRedirectURL);
                assert(rc == CURLE_OK);
                if (pRedirectURL)
                {
                    // Sharepoint 2016 workaround: contains unencoded U+0020
                    OUString const redirectURL(::rtl::Uri::encode(
                        OUString(pRedirectURL, strlen(pRedirectURL), RTL_TEXTENCODING_UTF8),
                        rtl_UriCharClassUric, rtl_UriEncodeKeepEscapes, RTL_TEXTENCODING_UTF8));

                    throw DAVException(DAVException::DAV_HTTP_REDIRECT, redirectURL);
                }
                [[fallthrough]];
            }
            default:
                throw DAVException(DAVException::DAV_HTTP_ERROR, statusLine, statusCode);
        }
    }

    if (pxOutStream)
    {
        (*pxOutStream)->closeOutput(); // signal EOF
    }
}

static auto TryRemoveExpiredLockToken(CurlSession& rSession, CurlUri const& rURI,
                                      DAVRequestEnvironment constconst pEnv) -> bool
{
    if (!pEnv)
    {
        // caller was a NonInteractive_*LOCK function anyway, its caller is LockStore
        return false;
    }
    OUString constconst pToken(g_Init.LockStore.getLockTokenForURI(rURI.GetURI(), nullptr));
    if (!pToken)
    {
        return false;
    }
    try
    {
        // determine validity of existing lock via lockdiscovery request
        ::std::vector<OUString> const propertyNames{ DAVProperties::LOCKDISCOVERY };
        ::std::vector<ucb::Lock> locks;
        ::std::tuple<::std::vector<OUString> const&, ::std::vector<DAVResource>* const,
                     ::std::vector<ucb::Lock>* constconst args(propertyNames, nullptr, &locks);

        CurlProcessor::PropFind(rSession, rURI, DAVZERO, &args, nullptr, *pEnv);

        // https://datatracker.ietf.org/doc/html/rfc4918#section-15.8
        // The response MAY not contain tokens, but hopefully it
        // will if client is properly authenticated.
        if (::std::any_of(locks.begin(), locks.end(), [pToken](ucb::Lock const& rLock) {
                return ::std::any_of(
                    rLock.LockTokens.begin(), rLock.LockTokens.end(),
                    [pToken](OUString const& rToken) { return *pToken == rToken; });
            }))
        {
            return false// still have the lock
        }

        SAL_INFO("ucb.ucp.webdav.curl",
                 "lock token expired, removing: " << rURI.GetURI() << " " << *pToken);
        g_Init.LockStore.removeLock(rURI.GetURI());
        return true;
    }
    catch (DAVException const&)
    {
        return false// ignore, the caller already has a better exception
    }
}

auto CurlProcessor::ProcessRequest(
    CurlSession& rSession, CurlUri const& rURI, OUString const& rMethod,
    ::std::vector<CurlOption> const& rOptions, DAVRequestEnvironment constconst pEnv,
    ::std::unique_ptr<curl_slist, deleter_from_fn<curl_slist, curl_slist_free_all>>
        pRequestHeaderList,
    uno::Reference<io::XOutputStream> constconst pxOutStream,
    uno::Reference<io::XInputStream> constconst pxInStream,
    ::std::pair<::std::vector<OUString> const&, DAVResource&> constconst pRequestedHeaders)
    -> void
{
    if (HostFilter::isForbidden(rURI.GetHost()))
    {
        SAL_WARN("ucb.ucp.webdav.curl""Access denied to host: " << rURI.GetHost());
        throw uno::RuntimeException(u"access to host denied"_ustr);
    }

    if (pEnv)
    { // add custom request headers passed by caller
        for (auto const& rHeader : pEnv->m_aRequestHeaders)
        {
            OString const utf8Header(
                OUStringToOString(rHeader.first, RTL_TEXTENCODING_ASCII_US) + ": "
                + OUStringToOString(rHeader.second, RTL_TEXTENCODING_ASCII_US));
            pRequestHeaderList.reset(
                curl_slist_append(pRequestHeaderList.release(), utf8Header.getStr()));
            if (!pRequestHeaderList)
            {
                throw uno::RuntimeException(u"curl_slist_append failed"_ustr);
            }
        }
    }

    uno::Sequence<sal_Int8> data;
    if (pxInStream)
    {
        uno::Reference<io::XSeekable> const xSeekable(*pxInStream, uno::UNO_QUERY);
        if (xSeekable.is())
        {
            auto const len(xSeekable->getLength() - xSeekable->getPosition());
            if ((**pxInStream).readBytes(data, len) != len)
            {
                throw uno::RuntimeException(u"short readBytes"_ustr);
            }
        }
        else
        {
            ::std::vector<uno::Sequence<sal_Int8>> bufs;
            bool isDone(false);
            do
            {
                bufs.emplace_back();
                isDone = (**pxInStream).readSomeBytes(bufs.back(), 65536) == 0;
            } while (!isDone);
            sal_Int32 nSize(0);
            for (auto const& rBuf : bufs)
            {
                if (o3tl::checked_add(nSize, rBuf.getLength(), nSize))
                {
                    throw std::bad_alloc(); // too large for Sequence
                }
            }
            data.realloc(nSize);
            size_t nCopied(0);
            for (auto const& rBuf : bufs)
            {
                ::std::memcpy(data.getArray() + nCopied, rBuf.getConstArray(), rBuf.getLength());
                nCopied += rBuf.getLength(); // can't overflow
            }
        }
    }

    // Clear flag before transfer starts; only a transfer started before
    // calling abort() will be aborted, not one started later.
    rSession.m_AbortFlag.store(false);

    Guard guard(rSession.m_Mutex, rOptions, rURI, rSession.m_pCurl.get());

    // authentication data may be in the URI, or requested via XInteractionHandler
    struct Auth
    {
        OUString UserName;
        OUString PassWord;
        decltype(CURLAUTH_ANY) AuthMask; ///< allowed auth methods
        Auth(OUString aUserName, OUString aPassword, decltype(CURLAUTH_ANY) aAuthMask)
            : UserName(std::move(aUserName))
            , PassWord(std::move(aPassword))
            , AuthMask(aAuthMask)
        {
        }
    };
    ::std::optional<Auth> oAuth;
    ::std::optional<Auth> oAuthProxy;
    if (pEnv && !rSession.m_isAuthenticatedProxy && !rSession.m_Proxy.isEmpty())
    {
        try
        {
            // the hope is that this must be a URI
            CurlUri const uri(rSession.m_Proxy);
            if (!uri.GetUser().isEmpty() || !uri.GetPassword().isEmpty())
            {
                oAuthProxy.emplace(uri.GetUser(), uri.GetPassword(), CURLAUTH_ANY);
            }
        }
        catch (DAVException&)
        {
            // ignore any parsing failure here
        }
    }
    decltype(CURLAUTH_ANY) const authSystem(CURLAUTH_NEGOTIATE | CURLAUTH_NTLM | CURLAUTH_NTLM_WB);
    if (pRequestedHeaders || (pEnv && !rSession.m_isAuthenticated))
    {
    // m_aRequestURI *may* be a path or *may* be URI - wtf
    // TODO: why is there this m_aRequestURI and also rURIReference argument?
    // ... only caller is DAVResourceAccess - always identical except MOVE/COPY
    // which doesn't work if it's just a URI reference so let's just use
    // rURIReference via rURI instead
#if 0
        CurlUri const uri(pEnv->m_aRequestURI);
#endif
        // note: due to parsing bug pwd didn't work in previous webdav ucps
        if (pEnv && !rSession.m_isAuthenticated
            && (!rURI.GetUser().isEmpty() || !rURI.GetPassword().isEmpty()))
        {
            oAuth.emplace(rURI.GetUser(), rURI.GetPassword(), CURLAUTH_ANY);
        }
        if (pRequestedHeaders)
        {
            // note: Previously this would be the rURIReference directly but
            // that ends up in CurlUri anyway and curl is unhappy.
            // But it looks like all consumers of this .uri are interested
            // only in the path, so it shouldn't make a difference to give
            // the entire URI when the caller extracts the path anyway.
            pRequestedHeaders->second.uri = rURI.GetURI();
            pRequestedHeaders->second.properties.clear();
        }
    }
    bool isRetry(false);
    bool isFallbackHTTP10(false);
    int nAuthRequests(0);
    int nAuthRequestsProxy(0);

    // libcurl does not have an authentication callback so handle auth
    // related status codes and requesting credentials via this loop
    do
    {
        isRetry = false;

        // re-check m_isAuthenticated flags every time, could have been set
        // by re-entrant call
        if (oAuth && !rSession.m_isAuthenticated)
        {
            OString const utf8UserName(OUStringToOString(oAuth->UserName, RTL_TEXTENCODING_UTF8));
            auto rc
                = curl_easy_setopt(rSession.m_pCurl.get(), CURLOPT_USERNAME, utf8UserName.getStr());
            if (rc != CURLE_OK)
            {
                SAL_WARN("ucb.ucp.webdav.curl""CURLOPT_USERNAME failed: " << GetErrorString(rc));
                throw DAVException(DAVException::DAV_INVALID_ARG);
            }
            OString const utf8PassWord(OUStringToOString(oAuth->PassWord, RTL_TEXTENCODING_UTF8));
            rc = curl_easy_setopt(rSession.m_pCurl.get(), CURLOPT_PASSWORD, utf8PassWord.getStr());
            if (rc != CURLE_OK)
            {
                SAL_WARN("ucb.ucp.webdav.curl""CURLOPT_PASSWORD failed: " << GetErrorString(rc));
                throw DAVException(DAVException::DAV_INVALID_ARG);
            }
            rc = curl_easy_setopt(rSession.m_pCurl.get(), CURLOPT_HTTPAUTH, oAuth->AuthMask);
            if (rc != CURLE_OK)
            { // NEGOTIATE typically disabled on Linux, NTLM is optional too
                assert(rc == CURLE_NOT_BUILT_IN);
                SAL_INFO("ucb.ucp.webdav.curl""no auth method available");
                throw DAVException(
                    DAVException::DAV_HTTP_NOAUTH,
                    ConnectionEndPointString(rSession.m_URI.GetHost(), rSession.m_URI.GetPort()));
            }
        }

        if (oAuthProxy && !rSession.m_isAuthenticatedProxy)
        {
            OString const utf8UserName(
                OUStringToOString(oAuthProxy->UserName, RTL_TEXTENCODING_UTF8));
            auto rc = curl_easy_setopt(rSession.m_pCurl.get(), CURLOPT_PROXYUSERNAME,
                                       utf8UserName.getStr());
            if (rc != CURLE_OK)
            {
                SAL_WARN("ucb.ucp.webdav.curl",
                         "CURLOPT_PROXYUSERNAME failed: " << GetErrorString(rc));
                throw DAVException(DAVException::DAV_INVALID_ARG);
            }
            OString const utf8PassWord(
                OUStringToOString(oAuthProxy->PassWord, RTL_TEXTENCODING_UTF8));
            rc = curl_easy_setopt(rSession.m_pCurl.get(), CURLOPT_PROXYPASSWORD,
                                  utf8PassWord.getStr());
            if (rc != CURLE_OK)
            {
                SAL_WARN("ucb.ucp.webdav.curl",
                         "CURLOPT_PROXYPASSWORD failed: " << GetErrorString(rc));
                throw DAVException(DAVException::DAV_INVALID_ARG);
            }
            rc = curl_easy_setopt(rSession.m_pCurl.get(), CURLOPT_PROXYAUTH, oAuthProxy->AuthMask);
            if (rc != CURLE_OK)
            { // NEGOTIATE typically disabled on Linux, NTLM is optional too
                assert(rc == CURLE_NOT_BUILT_IN);
                SAL_INFO("ucb.ucp.webdav.curl""no auth method available");
                throw DAVException(
                    DAVException::DAV_HTTP_NOAUTH,
                    ConnectionEndPointString(rSession.m_URI.GetHost(), rSession.m_URI.GetPort()));
            }
        }

        ResponseHeaders headers(rSession.m_pCurl.get());
        // always pass a stream for debug logging, buffer the result body
        uno::Reference<io::XSequenceOutputStream> const xSeqOutStream(
            io::SequenceOutputStream::create(rSession.m_xContext));
        uno::Reference<io::XOutputStream> const xTempOutStream(xSeqOutStream);
        assert(xTempOutStream.is());

        try
        {
            ProcessRequestImpl(rSession, rURI, rMethod, pRequestHeaderList.get(), &xTempOutStream,
                               pxInStream ? &data : nullptr, pRequestedHeaders, headers);
            if (pxOutStream)
            { // only copy to result stream if transfer was successful
                (*pxOutStream)->writeBytes(xSeqOutStream->getWrittenBytes());
                (*pxOutStream)->closeOutput(); // signal EOF
            }
        }
        catch (DAVException const& rException)
        {
            // log start of request body if there was any
            auto const bytes(xSeqOutStream->getWrittenBytes());
            auto const len(::std::min<sal_Int32>(bytes.getLength(), 10000));
            SAL_INFO("ucb.ucp.webdav.curl",
                     "DAVException; (first) " << len << " bytes of data received:");
            if (0 < len)
            {
                OStringBuffer buf(len);
                for (sal_Int32 i = 0; i < len; ++i)
                {
                    if (bytes[i] < 0x20) // also if negative
                    {
                        static char const hexDigit[16] = { '0''1''2''3''4''5''6''7',
                                                           '8''9''A''B''C''D''E''F' };
                        buf.append(OString::Concat("\\x")
                                   + OStringChar(hexDigit[static_cast<sal_uInt8>(bytes[i]) >> 4])
                                   + OStringChar(hexDigit[bytes[i] & 0x0F]));
                    }
                    else
                    {
                        buf.append(static_cast<char>(bytes[i]));
                    }
                }
                SAL_INFO("ucb.ucp.webdav.curl", buf.makeStringAndClear());
            }

            // error handling part 3: special HTTP status codes
            // that require unlocking m_Mutex to handle
            if (rException.getError() == DAVException::DAV_HTTP_ERROR)
            {
                auto const statusCode(rException.getStatus());
                switch (statusCode)
                {
                    case SC_LOCKED:
                    {
                        guard.Release(); // release m_Mutex before accessing LockStore
                        if (g_Init.LockStore.getLockTokenForURI(rURI.GetURI(), nullptr))
                        {
                            throw DAVException(DAVException::DAV_LOCKED_SELF);
                        }
                        else // locked by third party
                        {
                            throw DAVException(DAVException::DAV_LOCKED);
                        }
                        break;
                    }
                    case SC_PRECONDITION_FAILED:
                    case SC_BAD_REQUEST:
                    {
                        guard.Release(); // release m_Mutex before accessing LockStore
                        // Not obvious but apparently these codes may indicate
                        // the expiration of a lock.
                        // Initiate a new request *outside* ProcessRequestImpl
                        // *after* rGuard.unlock() to avoid messing up m_pCurl state.
                        if (TryRemoveExpiredLockToken(rSession, rURI, pEnv))
                        {
                            throw DAVException(DAVException::DAV_LOCK_EXPIRED);
                        }
                        break;
                    }
                    case SC_FORBIDDEN:
                    {
                        ::std::map<OUString, OUString> const headerMap(
                            ProcessHeaders(headers.HeaderFields.back().first));
                        // X-MSDAVEXT_Error see [MS-WEBDAVE] 2.2.3.1.9
                        auto const it(headerMap.find(u"x-msdavext_error"_ustr));
                        if (it == headerMap.end() || !it->second.startsWith("917656;"))
                        {
                            break;
                        }
                        // fallback needs cookie engine enabled
                        CURLcode rc
                            = curl_easy_setopt(rSession.m_pCurl.get(), CURLOPT_COOKIEFILE, "");
                        assert(rc == CURLE_OK);
                        (void)rc;
                        SAL_INFO("ucb.ucp.webdav.curl""403 fallback authentication");
                        // disable 302 redirect
                        pRequestHeaderList.reset(curl_slist_append(
                            pRequestHeaderList.release(), "X-FORMS_BASED_AUTH_ACCEPTED: f"));
                        if (!pRequestHeaderList)
                        {
                            throw uno::RuntimeException(u"curl_slist_append failed"_ustr);
                        }
                    }
                        [[fallthrough]]; // SP, no cookie, or cookie failed: try NTLM/Negotiate
                    case SC_UNAUTHORIZED:
                    case SC_PROXY_AUTHENTICATION_REQUIRED:
                    {
                        (statusCode != SC_PROXY_AUTHENTICATION_REQUIRED
                             ? rSession.m_isAuthenticated
                             : rSession.m_isAuthenticatedProxy)
                            = false// any auth data in m_pCurl is invalid
                        auto& rnAuthRequests(statusCode != SC_PROXY_AUTHENTICATION_REQUIRED
                                                 ? nAuthRequests
                                                 : nAuthRequestsProxy);
                        if (rnAuthRequests == 10)
                        {
                            SAL_INFO("ucb.ucp.webdav.curl""aborting authentication after "
                                                                << rnAuthRequests << " attempts");
                        }
                        else if (pEnv && pEnv->m_xAuthListener)
                        {
                            ::std::optional<OUString> const oRealm(
                                ExtractRealm(headers, statusCode != SC_PROXY_AUTHENTICATION_REQUIRED
                                                          ? "WWW-Authenticate"
                                                          : "Proxy-Authenticate"));

                            ::std::optional<Auth>& roAuth(
                                statusCode != SC_PROXY_AUTHENTICATION_REQUIRED ? oAuth
                                                                               : oAuthProxy);
                            OUString userName(roAuth ? roAuth->UserName : OUString());
                            OUString passWord(roAuth ? roAuth->PassWord : OUString());
                            long authAvail(0);
                            auto rc
                                = curl_easy_getinfo(rSession.m_pCurl.get(),
                                                    statusCode != SC_PROXY_AUTHENTICATION_REQUIRED
                                                        ? CURLINFO_HTTPAUTH_AVAIL
                                                        : CURLINFO_PROXYAUTH_AVAIL,
                                                    &authAvail);
                            assert(rc == CURLE_OK);
                            if (statusCode == SC_FORBIDDEN)
                            { // SharePoint fallback: try Negotiate auth
                                assert(authAvail == 0);
                                // note: this must be a single value!
                                // would need 2 iterations to try CURLAUTH_NTLM too
                                rc = curl_easy_setopt(rSession.m_pCurl.get(), CURLOPT_HTTPAUTH,
                                                      CURLAUTH_NEGOTIATE);
                                if (rc == CURLE_OK)
                                {
                                    authAvail = CURLAUTH_NEGOTIATE;
                                }
                                else
                                {
                                    rc = curl_easy_setopt(rSession.m_pCurl.get(), CURLOPT_HTTPAUTH,
                                                          CURLAUTH_NTLM);
                                    if (rc == CURLE_OK)
                                    {
                                        authAvail = CURLAUTH_NTLM;
                                    }
                                    else
                                    { // can't work
                                        SAL_INFO("ucb.ucp.webdav.curl",
                                                 "no SP fallback auth method available");
                                        throw DAVException(
                                            DAVException::DAV_HTTP_NOAUTH,
                                            ConnectionEndPointString(rSession.m_URI.GetHost(),
                                                                     rSession.m_URI.GetPort()));
                                    }
                                }
                            }
                            // only allow SystemCredentials once - the
                            // PasswordContainer may have stored it in the
                            // Config (TrySystemCredentialsFirst or
                            // AuthenticateUsingSystemCredentials) and then it
                            // will always force its use no matter how hopeless
                            bool const isSystemCredSupported((authAvail & authSystem) != 0
                                                             && rnAuthRequests == 0);
                            ++rnAuthRequests;

                            // Ask user via XInteractionHandler.
                            // Warning: This likely runs an event loop which may
                            // end up calling back into this instance, so all
                            // changes to m_pCurl must be undone now and
                            // restored after return.
                            guard.Release();

                            auto const ret = pEnv->m_xAuthListener->authenticate(
                                oRealm ? *oRealm : u""_ustr,
--> --------------------

--> maximum size reached

--> --------------------

Messung V0.5
C=90 H=97 G=93

¤ Dauer der Verarbeitung: 0.24 Sekunden  (vorverarbeitet)  ¤

*© Formatika GbR, Deutschland






Wurzel

Suchen

Beweissystem der NASA

Beweissystem Isabelle

NIST Cobol Testsuite

Cephes Mathematical Library

Wiener Entwicklungsmethode

Haftungshinweis

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

Bemerkung:

Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.