2620 lines
104 KiB
C++
2620 lines
104 KiB
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>
|
|
|
|
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 const* const pErrorBuffer = nullptr) -> OString
|
|
{
|
|
char const* const pMessage( // static fallback
|
|
(pErrorBuffer && pErrorBuffer[0] != '\0') ? pErrorBuffer : curl_easy_strerror(rc));
|
|
return OString::Concat("(") + OString::number(sal_Int32(rc)) + ") " + pMessage;
|
|
}
|
|
|
|
auto GetErrorStringMulti(CURLMcode const mc) -> OString
|
|
{
|
|
return OString::Concat("(") + OString::number(sal_Int32(mc)) + ") " + curl_multi_strerror(mc);
|
|
}
|
|
|
|
/// represent an option to be passed to curl_easy_setopt()
|
|
struct CurlOption
|
|
{
|
|
CURLoption const Option;
|
|
enum class Type
|
|
{
|
|
Pointer,
|
|
Long,
|
|
CurlOffT
|
|
};
|
|
Type const Tag;
|
|
union {
|
|
void const* const pValue;
|
|
long /*const*/ lValue;
|
|
curl_off_t /*const*/ cValue;
|
|
};
|
|
char const* const pExceptionString;
|
|
|
|
CurlOption(CURLoption const i_Option, void const* const i_Value,
|
|
char const* const i_pExceptionString)
|
|
: Option(i_Option)
|
|
, Tag(Type::Pointer)
|
|
, pValue(i_Value)
|
|
, pExceptionString(i_pExceptionString)
|
|
{
|
|
}
|
|
// Depending on platform, curl_off_t may be "long" or a larger type
|
|
// so cannot use overloading to distinguish these cases.
|
|
CurlOption(CURLoption const i_Option, curl_off_t const i_Value,
|
|
char const* const i_pExceptionString, Type const type = Type::Long)
|
|
: Option(i_Option)
|
|
, Tag(type)
|
|
, pExceptionString(i_pExceptionString)
|
|
{
|
|
static_assert(sizeof(long) <= sizeof(curl_off_t));
|
|
switch (type)
|
|
{
|
|
case Type::Long:
|
|
lValue = i_Value;
|
|
break;
|
|
case Type::CurlOffT:
|
|
cValue = i_Value;
|
|
break;
|
|
default:
|
|
assert(false);
|
|
}
|
|
}
|
|
};
|
|
|
|
/// 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(char* const ptr, size_t const size, size_t const nmemb,
|
|
void* const userdata)
|
|
{
|
|
auto* const pTarget(static_cast<DownloadTarget*>(userdata));
|
|
if (!pTarget) // looks like ~every request may have a response body
|
|
{
|
|
return nmemb;
|
|
}
|
|
assert(size == 1); // says the man page
|
|
(void)size;
|
|
assert(pTarget->xOutStream.is());
|
|
auto const oResponseCode(GetResponseCode(pTarget->rHeaders));
|
|
if (!oResponseCode)
|
|
{
|
|
return 0; // that is an error
|
|
}
|
|
// always write, for exception handler in ProcessRequest()
|
|
// if (200 <= *oResponseCode && *oResponseCode < 300)
|
|
{
|
|
try
|
|
{
|
|
uno::Sequence<sal_Int8> const data(reinterpret_cast<sal_Int8*>(ptr), nmemb);
|
|
pTarget->xOutStream->writeBytes(data);
|
|
}
|
|
catch (...)
|
|
{
|
|
SAL_WARN("ucb.ucp.webdav.curl", "exception in write_callback");
|
|
return 0; // error
|
|
}
|
|
}
|
|
// else: ignore the body? CurlSession will check the status eventually
|
|
return nmemb;
|
|
}
|
|
|
|
static size_t read_callback(char* const buffer, size_t const size, size_t const nitems,
|
|
void* const userdata)
|
|
{
|
|
auto* const pSource(static_cast<UploadSource*>(userdata));
|
|
assert(pSource);
|
|
size_t const nBytes(size * nitems);
|
|
size_t nRet(0);
|
|
try
|
|
{
|
|
assert(pSource->nPosition <= o3tl::make_unsigned(pSource->rInData.getLength()));
|
|
nRet = ::std::min<size_t>(pSource->rInData.getLength() - pSource->nPosition, nBytes);
|
|
::std::memcpy(buffer, pSource->rInData.getConstArray() + pSource->nPosition, nRet);
|
|
pSource->nPosition += nRet;
|
|
}
|
|
catch (...)
|
|
{
|
|
SAL_WARN("ucb.ucp.webdav.curl", "exception in read_callback");
|
|
return CURL_READFUNC_ABORT; // error
|
|
}
|
|
return nRet;
|
|
}
|
|
|
|
static size_t header_callback(char* const buffer, size_t const size, size_t const nitems,
|
|
void* const userdata)
|
|
{
|
|
auto* const pHeaders(static_cast<ResponseHeaders*>(userdata));
|
|
assert(pHeaders);
|
|
#if 0
|
|
if (!pHeaders) // TODO maybe not needed in every request? not sure
|
|
{
|
|
return nitems;
|
|
}
|
|
#endif
|
|
assert(size == 1); // says the man page
|
|
(void)size;
|
|
try
|
|
{
|
|
if (nitems <= 2)
|
|
{
|
|
// end of header, body follows...
|
|
if (pHeaders->HeaderFields.empty())
|
|
{
|
|
SAL_WARN("ucb.ucp.webdav.curl", "header_callback: empty header?");
|
|
return 0; // error
|
|
}
|
|
// unfortunately there's no separate callback when the body begins,
|
|
// so have to manually retrieve the status code here
|
|
long statusCode(SC_NONE);
|
|
auto rc = curl_easy_getinfo(pHeaders->pCurl, CURLINFO_RESPONSE_CODE, &statusCode);
|
|
assert(rc == CURLE_OK);
|
|
(void)rc;
|
|
// always put the current response code here - wasn't necessarily in this header
|
|
pHeaders->HeaderFields.back().second.emplace(statusCode);
|
|
}
|
|
else if (buffer[0] == ' ' || buffer[0] == '\t') // folded header field?
|
|
{
|
|
size_t i(0);
|
|
do
|
|
{
|
|
++i;
|
|
} while (i == ' ' || i == '\t');
|
|
if (pHeaders->HeaderFields.empty() || pHeaders->HeaderFields.back().second
|
|
|| pHeaders->HeaderFields.back().first.empty())
|
|
{
|
|
SAL_WARN("ucb.ucp.webdav.curl",
|
|
"header_callback: folded header field without start");
|
|
return 0; // error
|
|
}
|
|
pHeaders->HeaderFields.back().first.back()
|
|
+= OString::Concat(" ") + ::std::string_view(&buffer[i], nitems - i);
|
|
}
|
|
else
|
|
{
|
|
if (pHeaders->HeaderFields.empty() || pHeaders->HeaderFields.back().second)
|
|
{
|
|
pHeaders->HeaderFields.emplace_back();
|
|
}
|
|
pHeaders->HeaderFields.back().first.emplace_back(OString(buffer, nitems));
|
|
}
|
|
}
|
|
catch (...)
|
|
{
|
|
SAL_WARN("ucb.ucp.webdav.curl", "exception in header_callback");
|
|
return 0; // error
|
|
}
|
|
return nitems;
|
|
}
|
|
|
|
static auto ProcessHeaders(::std::vector<OString> const& rHeaders) -> ::std::map<OUString, OUString>
|
|
{
|
|
::std::map<OUString, OUString> ret;
|
|
for (OString const& rLine : rHeaders)
|
|
{
|
|
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&> const* const pRequestedHeaders)
|
|
-> void
|
|
{
|
|
::std::map<OUString, OUString> const headerMap(
|
|
ProcessHeaders(rHeaders.HeaderFields.back().first));
|
|
if (pRequestedHeaders)
|
|
{
|
|
for (OUString const& rHeader : pRequestedHeaders->first)
|
|
{
|
|
auto const it(headerMap.find(rHeader.toAsciiLowerCase()));
|
|
if (it != headerMap.end())
|
|
{
|
|
DAVPropertyValue value;
|
|
value.IsCaseSensitive = false;
|
|
value.Name = it->first;
|
|
value.Value <<= it->second;
|
|
pRequestedHeaders->second.properties.push_back(value);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// this appears to be the only way to get the "realm" from libcurl
|
|
static auto ExtractRealm(ResponseHeaders const& rHeaders, char const* const pAuthHeaderName)
|
|
-> ::std::optional<OUString>
|
|
{
|
|
::std::map<OUString, OUString> const headerMap(
|
|
ProcessHeaders(rHeaders.HeaderFields.back().first));
|
|
auto const it(headerMap.find(OUString::createFromAscii(pAuthHeaderName).toAsciiLowerCase()));
|
|
if (it == headerMap.end())
|
|
{
|
|
SAL_WARN("ucb.ucp.webdav.curl", "cannot find auth header");
|
|
return {};
|
|
}
|
|
// It may be possible that the header contains multiple methods each with
|
|
// a different realm - extract only the first one bc the downstream API
|
|
// only supports one anyway.
|
|
// case insensitive!
|
|
auto i(it->second.toAsciiLowerCase().indexOf("realm="));
|
|
// is optional
|
|
if (i == -1)
|
|
{
|
|
SAL_INFO("ucb.ucp.webdav.curl", "auth header has no realm");
|
|
return {};
|
|
}
|
|
// no whitespace allowed before or after =
|
|
i += ::std::strlen("realm=");
|
|
if (it->second.getLength() < i + 2 || it->second[i] != '\"')
|
|
{
|
|
SAL_WARN("ucb.ucp.webdav.curl", "no realm value");
|
|
return {};
|
|
}
|
|
++i;
|
|
OUStringBuffer buf;
|
|
while (i < it->second.getLength() && it->second[i] != '\"')
|
|
{
|
|
if (it->second[i] == '\\') // quoted-pair escape
|
|
{
|
|
++i;
|
|
if (it->second.getLength() <= i)
|
|
{
|
|
SAL_WARN("ucb.ucp.webdav.curl", "unterminated quoted-pair");
|
|
return {};
|
|
}
|
|
}
|
|
buf.append(it->second[i]);
|
|
++i;
|
|
}
|
|
if (it->second.getLength() <= i)
|
|
{
|
|
SAL_WARN("ucb.ucp.webdav.curl", "unterminated realm");
|
|
return {};
|
|
}
|
|
return buf.makeStringAndClear();
|
|
}
|
|
|
|
CurlSession::CurlSession(uno::Reference<uno::XComponentContext> 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);
|
|
}
|
|
}
|
|
|
|
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>* const> const* 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> const* const pxOutStream,
|
|
uno::Sequence<sal_Int8> const* const pInData,
|
|
::std::pair<::std::vector<OUString> const&, DAVResource&> const* const pRequestedHeaders,
|
|
ResponseHeaders& rHeaders) -> void
|
|
{
|
|
::comphelper::ScopeGuard const g([&]() {
|
|
auto rc = curl_easy_setopt(rSession.m_pCurl.get(), CURLOPT_HEADERDATA, nullptr);
|
|
assert(rc == CURLE_OK);
|
|
(void)rc;
|
|
if (pxOutStream)
|
|
{
|
|
rc = curl_easy_setopt(rSession.m_pCurl.get(), CURLOPT_WRITEDATA, nullptr);
|
|
assert(rc == CURLE_OK);
|
|
}
|
|
if (pInData)
|
|
{
|
|
rc = curl_easy_setopt(rSession.m_pCurl.get(), CURLOPT_READDATA, nullptr);
|
|
assert(rc == CURLE_OK);
|
|
rc = curl_easy_setopt(rSession.m_pCurl.get(), CURLOPT_UPLOAD, 0L);
|
|
assert(rc == CURLE_OK);
|
|
}
|
|
if (pRequestHeaderList)
|
|
{
|
|
rc = curl_easy_setopt(rSession.m_pCurl.get(), CURLOPT_HTTPHEADER, nullptr);
|
|
assert(rc == CURLE_OK);
|
|
}
|
|
});
|
|
|
|
if (pRequestHeaderList)
|
|
{
|
|
auto rc = curl_easy_setopt(rSession.m_pCurl.get(), CURLOPT_HTTPHEADER, pRequestHeaderList);
|
|
assert(rc == CURLE_OK);
|
|
(void)rc;
|
|
}
|
|
|
|
auto rc = curl_easy_setopt(rSession.m_pCurl.get(), CURLOPT_CURLU, rURI.GetCURLU());
|
|
assert(rc == CURLE_OK); // can't fail since 7.63.0
|
|
|
|
rc = curl_easy_setopt(rSession.m_pCurl.get(), CURLOPT_HEADERDATA, &rHeaders);
|
|
assert(rc == CURLE_OK);
|
|
::std::optional<DownloadTarget> oDownloadTarget;
|
|
if (pxOutStream)
|
|
{
|
|
oDownloadTarget.emplace(*pxOutStream, rHeaders);
|
|
rc = curl_easy_setopt(rSession.m_pCurl.get(), CURLOPT_WRITEDATA, &*oDownloadTarget);
|
|
assert(rc == CURLE_OK);
|
|
}
|
|
::std::optional<UploadSource> oUploadSource;
|
|
if (pInData)
|
|
{
|
|
oUploadSource.emplace(*pInData);
|
|
rc = curl_easy_setopt(rSession.m_pCurl.get(), CURLOPT_READDATA, &*oUploadSource);
|
|
assert(rc == CURLE_OK);
|
|
}
|
|
rSession.m_ErrorBuffer[0] = '\0';
|
|
|
|
// note: easy handle must be added for *every* transfer!
|
|
// otherwise it gets stuck in MSTATE_MSGSENT forever after 1st transfer
|
|
auto mc = curl_multi_add_handle(rSession.m_pCurlMulti.get(), rSession.m_pCurl.get());
|
|
if (mc != CURLM_OK)
|
|
{
|
|
SAL_WARN("ucb.ucp.webdav.curl",
|
|
"curl_multi_add_handle failed: " << GetErrorStringMulti(mc));
|
|
throw DAVException(
|
|
DAVException::DAV_SESSION_CREATE,
|
|
ConnectionEndPointString(rSession.m_URI.GetHost(), rSession.m_URI.GetPort()));
|
|
}
|
|
::comphelper::ScopeGuard const gg([&]() {
|
|
mc = curl_multi_remove_handle(rSession.m_pCurlMulti.get(), rSession.m_pCurl.get());
|
|
if (mc != CURLM_OK)
|
|
{
|
|
SAL_WARN("ucb.ucp.webdav.curl",
|
|
"curl_multi_remove_handle failed: " << GetErrorStringMulti(mc));
|
|
}
|
|
});
|
|
|
|
// this is where libcurl actually does something
|
|
rc = CURL_LAST; // clear current value
|
|
int nRunning;
|
|
do
|
|
{
|
|
mc = curl_multi_perform(rSession.m_pCurlMulti.get(), &nRunning);
|
|
if (mc != CURLM_OK)
|
|
{
|
|
SAL_WARN("ucb.ucp.webdav.curl",
|
|
"curl_multi_perform failed: " << GetErrorStringMulti(mc));
|
|
throw DAVException(
|
|
DAVException::DAV_HTTP_CONNECT,
|
|
ConnectionEndPointString(rSession.m_URI.GetHost(), rSession.m_URI.GetPort()));
|
|
}
|
|
if (nRunning == 0)
|
|
{ // short request like HEAD on loopback could be done in first call
|
|
break;
|
|
}
|
|
int nFDs;
|
|
mc = curl_multi_poll(rSession.m_pCurlMulti.get(), nullptr, 0, rSession.m_nReadTimeout,
|
|
&nFDs);
|
|
if (mc != CURLM_OK)
|
|
{
|
|
SAL_WARN("ucb.ucp.webdav.curl", "curl_multi_poll failed: " << GetErrorStringMulti(mc));
|
|
throw DAVException(
|
|
DAVException::DAV_HTTP_CONNECT,
|
|
ConnectionEndPointString(rSession.m_URI.GetHost(), rSession.m_URI.GetPort()));
|
|
}
|
|
if (rSession.m_AbortFlag.load())
|
|
{ // flag was set by abort() -> not sure what exception to throw?
|
|
throw DAVException(DAVException::DAV_HTTP_ERROR, 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 const* const pMsg = curl_multi_info_read(rSession.m_pCurlMulti.get(), &nRunning);
|
|
if (pMsg && pMsg->msg == CURLMSG_DONE)
|
|
{
|
|
assert(pMsg->easy_handle == rSession.m_pCurl.get());
|
|
rc = pMsg->data.result;
|
|
}
|
|
else
|
|
{
|
|
SAL_WARN("ucb.ucp.webdav.curl", "curl_multi_info_read unexpected result");
|
|
}
|
|
} while (nRunning != 0);
|
|
|
|
// error handling part 1: libcurl errors
|
|
if (rc != CURLE_OK)
|
|
{
|
|
// TODO: is there any value in extracting CURLINFO_OS_ERRNO
|
|
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 const* const pEnv) -> bool
|
|
{
|
|
if (!pEnv)
|
|
{
|
|
// caller was a NonInteractive_*LOCK function anyway, its caller is LockStore
|
|
return false;
|
|
}
|
|
OUString const* const pToken(g_Init.LockStore.getLockTokenForURI(rURI.GetURI(), nullptr));
|
|
if (!pToken)
|
|
{
|
|
return false;
|
|
}
|
|
try
|
|
{
|
|
// determine validity of existing lock via lockdiscovery request
|
|
::std::vector<OUString> const propertyNames{ DAVProperties::LOCKDISCOVERY };
|
|
::std::vector<ucb::Lock> locks;
|
|
::std::tuple<::std::vector<OUString> const&, ::std::vector<DAVResource>* const,
|
|
::std::vector<ucb::Lock>* const> const args(propertyNames, nullptr, &locks);
|
|
|
|
CurlProcessor::PropFind(rSession, rURI, DAVZERO, &args, nullptr, *pEnv);
|
|
|
|
// https://datatracker.ietf.org/doc/html/rfc4918#section-15.8
|
|
// The response MAY not contain tokens, but hopefully it
|
|
// will if client is properly authenticated.
|
|
if (::std::any_of(locks.begin(), locks.end(), [pToken](ucb::Lock const& rLock) {
|
|
return ::std::any_of(
|
|
rLock.LockTokens.begin(), rLock.LockTokens.end(),
|
|
[pToken](OUString const& rToken) { return *pToken == rToken; });
|
|
}))
|
|
{
|
|
return false; // still have the lock
|
|
}
|
|
|
|
SAL_INFO("ucb.ucp.webdav.curl",
|
|
"lock token expired, removing: " << rURI.GetURI() << " " << *pToken);
|
|
g_Init.LockStore.removeLock(rURI.GetURI());
|
|
return true;
|
|
}
|
|
catch (DAVException const&)
|
|
{
|
|
return false; // ignore, the caller already has a better exception
|
|
}
|
|
}
|
|
|
|
auto CurlProcessor::ProcessRequest(
|
|
CurlSession& rSession, CurlUri const& rURI, OUString const& rMethod,
|
|
::std::vector<CurlOption> const& rOptions, DAVRequestEnvironment const* const pEnv,
|
|
::std::unique_ptr<curl_slist, deleter_from_fn<curl_slist, curl_slist_free_all>>
|
|
pRequestHeaderList,
|
|
uno::Reference<io::XOutputStream> const* const pxOutStream,
|
|
uno::Reference<io::XInputStream> const* const pxInStream,
|
|
::std::pair<::std::vector<OUString> const&, DAVResource&> const* const pRequestedHeaders)
|
|
-> void
|
|
{
|
|
if (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,
|
|
statusCode != SC_PROXY_AUTHENTICATION_REQUIRED
|
|
? rSession.m_URI.GetHost()
|
|
: rSession.m_Proxy,
|
|
userName, passWord, isSystemCredSupported);
|
|
|
|
if (ret == 0)
|
|
{
|
|
// NTLM may either use a password requested
|
|
// from the user, or from the system via SSPI
|
|
// so i guess it should not be disabled here
|
|
// regardless of the state of the system auth
|
|
// checkbox, particularly since SSPI is only
|
|
// available on WNT.
|
|
// Additionally, "Negotiate" has a "legacy"
|
|
// mode that is actually just NTLM according to
|
|
// https://curl.se/rfc/ntlm.html#ntlmHttpAuthentication
|
|
// so there's nothing in authSystem that can be
|
|
// disabled here.
|
|
roAuth.emplace(userName, passWord,
|
|
((userName.isEmpty() && passWord.isEmpty())
|
|
? (authAvail & authSystem)
|
|
: authAvail));
|
|
isRetry = true;
|
|
// Acquire is only necessary in case of success.
|
|
guard.Acquire();
|
|
break; // break out of switch
|
|
}
|
|
// else: throw
|
|
}
|
|
SAL_INFO("ucb.ucp.webdav.curl", "no auth credentials provided");
|
|
throw DAVException(DAVException::DAV_HTTP_NOAUTH,
|
|
ConnectionEndPointString(rSession.m_URI.GetHost(),
|
|
rSession.m_URI.GetPort()));
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
else if (rException.getError() == DAVException::DAV_UNSUPPORTED)
|
|
{
|
|
// tdf#152493 libcurl can't handle "Transfer-Encoding: chunked"
|
|
// in HTTP/1.1 100 Continue response.
|
|
// workaround: if HTTP/1.1 didn't work, try HTTP/1.0
|
|
// (but fallback only once - to prevent infinite loop)
|
|
if (isFallbackHTTP10)
|
|
{
|
|
throw DAVException(DAVException::DAV_HTTP_ERROR);
|
|
}
|
|
isFallbackHTTP10 = true;
|
|
// note: this is not reset - future requests to this URI use it!
|
|
auto rc = curl_easy_setopt(rSession.m_pCurl.get(), CURLOPT_HTTP_VERSION,
|
|
CURL_HTTP_VERSION_1_0);
|
|
if (rc != CURLE_OK)
|
|
{
|
|
throw DAVException(DAVException::DAV_HTTP_ERROR);
|
|
}
|
|
SAL_INFO("ucb.ucp.webdav.curl", "attempting fallback to HTTP/1.0");
|
|
isRetry = true;
|
|
}
|
|
if (!isRetry)
|
|
{
|
|
throw; // everything else: re-throw
|
|
}
|
|
}
|
|
} while (isRetry);
|
|
|
|
if (oAuth)
|
|
{
|
|
// assume this worked, leave auth data as stored in m_pCurl
|
|
rSession.m_isAuthenticated = true;
|
|
}
|
|
if (oAuthProxy)
|
|
{
|
|
// assume this worked, leave auth data as stored in m_pCurl
|
|
rSession.m_isAuthenticatedProxy = true;
|
|
}
|
|
}
|
|
|
|
auto CurlSession::OPTIONS(OUString const& rURIReference,
|
|
|
|
DAVOptions& rOptions, DAVRequestEnvironment const& rEnv) -> void
|
|
{
|
|
SAL_INFO("ucb.ucp.webdav.curl", "OPTIONS: " << rURIReference);
|
|
|
|
rOptions.init();
|
|
|
|
CurlUri const uri(CurlProcessor::URIReferenceToURI(*this, rURIReference));
|
|
|
|
::std::vector<OUString> const headerNames{ u"allow"_ustr, u"dav"_ustr };
|
|
DAVResource result;
|
|
::std::pair<::std::vector<OUString> const&, DAVResource&> const headers(headerNames, result);
|
|
|
|
::std::vector<CurlOption> const options{ { CURLOPT_CUSTOMREQUEST, "OPTIONS",
|
|
"CURLOPT_CUSTOMREQUEST" } };
|
|
|
|
CurlProcessor::ProcessRequest(*this, uri, u"OPTIONS"_ustr, options, &rEnv, nullptr, nullptr,
|
|
nullptr, &headers);
|
|
|
|
for (auto const& it : result.properties)
|
|
{
|
|
OUString value;
|
|
it.Value >>= value;
|
|
SAL_INFO("ucb.ucp.webdav.curl", "OPTIONS: header: " << it.Name << ": " << value);
|
|
if (it.Name.equalsIgnoreAsciiCase("allow"))
|
|
{
|
|
rOptions.setAllowedMethods(value);
|
|
}
|
|
else if (it.Name.equalsIgnoreAsciiCase("dav"))
|
|
{
|
|
// see <http://tools.ietf.org/html/rfc4918#section-10.1>,
|
|
// <http://tools.ietf.org/html/rfc4918#section-18>,
|
|
// and <http://tools.ietf.org/html/rfc7230#section-3.2>
|
|
// we detect the class (1, 2 and 3), other elements (token, URL)
|
|
// are not used for now
|
|
auto const list(::comphelper::string::convertCommaSeparated(value));
|
|
for (OUString const& v : list)
|
|
{
|
|
if (v == "1")
|
|
{
|
|
rOptions.setClass1();
|
|
}
|
|
else if (v == "2")
|
|
{
|
|
rOptions.setClass2();
|
|
}
|
|
else if (v == "3")
|
|
{
|
|
rOptions.setClass3();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if (rOptions.isClass2() || rOptions.isClass3())
|
|
{
|
|
if (g_Init.LockStore.getLockTokenForURI(uri.GetURI(), nullptr))
|
|
{
|
|
rOptions.setLocked();
|
|
}
|
|
}
|
|
}
|
|
|
|
auto CurlProcessor::PropFind(
|
|
CurlSession& rSession, CurlUri const& rURI, Depth const nDepth,
|
|
::std::tuple<::std::vector<OUString> const&, ::std::vector<DAVResource>* const,
|
|
::std::vector<ucb::Lock>* const> const* const o_pRequestedProperties,
|
|
::std::vector<DAVResourceInfo>* const o_pResourceInfos, DAVRequestEnvironment const& rEnv)
|
|
-> void
|
|
{
|
|
assert((o_pRequestedProperties != nullptr) != (o_pResourceInfos != nullptr));
|
|
assert((o_pRequestedProperties == nullptr)
|
|
|| (::std::get<1>(*o_pRequestedProperties) != nullptr)
|
|
!= (::std::get<2>(*o_pRequestedProperties) != nullptr));
|
|
|
|
::std::unique_ptr<curl_slist, deleter_from_fn<curl_slist, curl_slist_free_all>> pList;
|
|
pList.reset(curl_slist_append(pList.release(), "Content-Type: application/xml"));
|
|
if (!pList)
|
|
{
|
|
throw uno::RuntimeException(u"curl_slist_append failed"_ustr);
|
|
}
|
|
OString depth;
|
|
switch (nDepth)
|
|
{
|
|
case DAVZERO:
|
|
depth = "Depth: 0"_ostr;
|
|
break;
|
|
case DAVONE:
|
|
depth = "Depth: 1"_ostr;
|
|
break;
|
|
case DAVINFINITY:
|
|
depth = "Depth: infinity"_ostr;
|
|
break;
|
|
default:
|
|
assert(false);
|
|
}
|
|
pList.reset(curl_slist_append(pList.release(), depth.getStr()));
|
|
if (!pList)
|
|
{
|
|
throw uno::RuntimeException(u"curl_slist_append failed"_ustr);
|
|
}
|
|
|
|
uno::Reference<io::XSequenceOutputStream> const xSeqOutStream(
|
|
io::SequenceOutputStream::create(rSession.m_xContext));
|
|
uno::Reference<io::XOutputStream> const xRequestOutStream(xSeqOutStream);
|
|
assert(xRequestOutStream.is());
|
|
|
|
uno::Reference<xml::sax::XWriter> const xWriter(xml::sax::Writer::create(rSession.m_xContext));
|
|
xWriter->setOutputStream(xRequestOutStream);
|
|
xWriter->startDocument();
|
|
rtl::Reference<::comphelper::AttributeList> const pAttrList(new ::comphelper::AttributeList);
|
|
pAttrList->AddAttribute(u"xmlns"_ustr, u"DAV:"_ustr);
|
|
xWriter->startElement(u"propfind"_ustr, pAttrList);
|
|
if (o_pResourceInfos)
|
|
{
|
|
xWriter->startElement(u"propname"_ustr, nullptr);
|
|
xWriter->endElement(u"propname"_ustr);
|
|
}
|
|
else
|
|
{
|
|
if (::std::get<0>(*o_pRequestedProperties).empty())
|
|
{
|
|
xWriter->startElement(u"allprop"_ustr, nullptr);
|
|
xWriter->endElement(u"allprop"_ustr);
|
|
}
|
|
else
|
|
{
|
|
xWriter->startElement(u"prop"_ustr, nullptr);
|
|
for (OUString const& rName : ::std::get<0>(*o_pRequestedProperties))
|
|
{
|
|
SerfPropName name;
|
|
DAVProperties::createSerfPropName(rName, name);
|
|
pAttrList->Clear();
|
|
pAttrList->AddAttribute(u"xmlns"_ustr, OUString::createFromAscii(name.nspace));
|
|
xWriter->startElement(OUString::createFromAscii(name.name), pAttrList);
|
|
xWriter->endElement(OUString::createFromAscii(name.name));
|
|
}
|
|
xWriter->endElement(u"prop"_ustr);
|
|
}
|
|
}
|
|
xWriter->endElement(u"propfind"_ustr);
|
|
xWriter->endDocument();
|
|
|
|
uno::Reference<io::XInputStream> const xRequestInStream(
|
|
io::SequenceInputStream::createStreamFromSequence(rSession.m_xContext,
|
|
xSeqOutStream->getWrittenBytes()));
|
|
assert(xRequestInStream.is());
|
|
|
|
curl_off_t const len(xSeqOutStream->getWrittenBytes().getLength());
|
|
|
|
::std::vector<CurlOption> const options{
|
|
{ CURLOPT_UPLOAD, 1L, nullptr },
|
|
{ CURLOPT_CUSTOMREQUEST, "PROPFIND", "CURLOPT_CUSTOMREQUEST" },
|
|
// note: Sharepoint cannot handle "Transfer-Encoding: chunked"
|
|
{ CURLOPT_INFILESIZE_LARGE, len, nullptr, CurlOption::Type::CurlOffT }
|
|
};
|
|
|
|
// stream for response
|
|
uno::Reference<io::XInputStream> const xResponseInStream(io::Pipe::create(rSession.m_xContext));
|
|
uno::Reference<io::XOutputStream> const xResponseOutStream(xResponseInStream, uno::UNO_QUERY);
|
|
assert(xResponseInStream.is());
|
|
assert(xResponseOutStream.is());
|
|
|
|
CurlProcessor::ProcessRequest(rSession, rURI, u"PROPFIND"_ustr, options, &rEnv,
|
|
::std::move(pList), &xResponseOutStream, &xRequestInStream,
|
|
nullptr);
|
|
|
|
if (o_pResourceInfos)
|
|
{
|
|
*o_pResourceInfos = parseWebDAVPropNameResponse(xResponseInStream);
|
|
}
|
|
else
|
|
{
|
|
if (::std::get<1>(*o_pRequestedProperties) != nullptr)
|
|
{
|
|
*::std::get<1>(*o_pRequestedProperties)
|
|
= parseWebDAVPropFindResponse(xResponseInStream);
|
|
for (DAVResource& it : *::std::get<1>(*o_pRequestedProperties))
|
|
{
|
|
// caller will give these uris to CurlUri so can't be relative
|
|
if (it.uri.startsWith("/"))
|
|
{
|
|
try
|
|
{
|
|
it.uri = rSession.m_URI.CloneWithRelativeRefPathAbsolute(it.uri).GetURI();
|
|
}
|
|
catch (DAVException const&)
|
|
{
|
|
SAL_INFO("ucb.ucp.webdav.curl",
|
|
"PROPFIND: exception parsing uri " << it.uri);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
*::std::get<2>(*o_pRequestedProperties) = parseWebDAVLockResponse(xResponseInStream);
|
|
}
|
|
}
|
|
}
|
|
|
|
// DAV methods
|
|
auto CurlSession::PROPFIND(OUString const& rURIReference, Depth const depth,
|
|
::std::vector<OUString> const& rPropertyNames,
|
|
::std::vector<DAVResource>& o_rResources,
|
|
DAVRequestEnvironment const& rEnv) -> void
|
|
{
|
|
SAL_INFO("ucb.ucp.webdav.curl", "PROPFIND: " << rURIReference << " " << depth);
|
|
|
|
CurlUri const uri(CurlProcessor::URIReferenceToURI(*this, rURIReference));
|
|
|
|
::std::tuple<::std::vector<OUString> const&, ::std::vector<DAVResource>* const,
|
|
::std::vector<ucb::Lock>* const> const args(rPropertyNames, &o_rResources,
|
|
nullptr);
|
|
return CurlProcessor::PropFind(*this, uri, depth, &args, nullptr, rEnv);
|
|
}
|
|
|
|
auto CurlSession::PROPFIND(OUString const& rURIReference, Depth const depth,
|
|
::std::vector<DAVResourceInfo>& o_rResourceInfos,
|
|
DAVRequestEnvironment const& rEnv) -> void
|
|
{
|
|
SAL_INFO("ucb.ucp.webdav.curl", "PROPFIND: " << rURIReference << " " << depth);
|
|
|
|
CurlUri const uri(CurlProcessor::URIReferenceToURI(*this, rURIReference));
|
|
|
|
return CurlProcessor::PropFind(*this, uri, depth, nullptr, &o_rResourceInfos, rEnv);
|
|
}
|
|
|
|
auto CurlSession::PROPPATCH(OUString const& rURIReference,
|
|
::std::vector<ProppatchValue> const& rValues,
|
|
DAVRequestEnvironment const& rEnv) -> void
|
|
{
|
|
SAL_INFO("ucb.ucp.webdav.curl", "PROPPATCH: " << rURIReference);
|
|
|
|
CurlUri const uri(CurlProcessor::URIReferenceToURI(*this, rURIReference));
|
|
|
|
// TODO: either set CURLOPT_INFILESIZE_LARGE or chunked?
|
|
::std::unique_ptr<curl_slist, deleter_from_fn<curl_slist, curl_slist_free_all>> pList;
|
|
pList.reset(curl_slist_append(pList.release(), "Content-Type: application/xml"));
|
|
if (!pList)
|
|
{
|
|
throw uno::RuntimeException(u"curl_slist_append failed"_ustr);
|
|
}
|
|
|
|
// generate XML document for PROPPATCH
|
|
uno::Reference<io::XSequenceOutputStream> const xSeqOutStream(
|
|
io::SequenceOutputStream::create(m_xContext));
|
|
uno::Reference<io::XOutputStream> const xRequestOutStream(xSeqOutStream);
|
|
assert(xRequestOutStream.is());
|
|
uno::Reference<xml::sax::XWriter> const xWriter(xml::sax::Writer::create(m_xContext));
|
|
xWriter->setOutputStream(xRequestOutStream);
|
|
xWriter->startDocument();
|
|
rtl::Reference<::comphelper::AttributeList> const pAttrList(new ::comphelper::AttributeList);
|
|
pAttrList->AddAttribute(u"xmlns"_ustr, u"DAV:"_ustr);
|
|
xWriter->startElement(u"propertyupdate"_ustr, pAttrList);
|
|
for (ProppatchValue const& rPropValue : rValues)
|
|
{
|
|
assert(rPropValue.operation == PROPSET || rPropValue.operation == PROPREMOVE);
|
|
OUString const operation((rPropValue.operation == PROPSET) ? u"set"_ustr : u"remove"_ustr);
|
|
xWriter->startElement(operation, nullptr);
|
|
xWriter->startElement(u"prop"_ustr, nullptr);
|
|
SerfPropName name;
|
|
DAVProperties::createSerfPropName(rPropValue.name, name);
|
|
pAttrList->Clear();
|
|
pAttrList->AddAttribute(u"xmlns"_ustr, OUString::createFromAscii(name.nspace));
|
|
xWriter->startElement(OUString::createFromAscii(name.name), pAttrList);
|
|
if (rPropValue.operation == PROPSET)
|
|
{
|
|
if (DAVProperties::isUCBDeadProperty(name))
|
|
{
|
|
::std::optional<::std::pair<OUString, OUString>> const oProp(
|
|
UCBDeadPropertyValue::toXML(rPropValue.value));
|
|
if (oProp)
|
|
{
|
|
xWriter->startElement(u"ucbprop"_ustr, nullptr);
|
|
xWriter->startElement(u"type"_ustr, nullptr);
|
|
xWriter->characters(oProp->first);
|
|
xWriter->endElement(u"type"_ustr);
|
|
xWriter->startElement(u"value"_ustr, nullptr);
|
|
xWriter->characters(oProp->second);
|
|
xWriter->endElement(u"value"_ustr);
|
|
xWriter->endElement(u"ucbprop"_ustr);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
OUString value;
|
|
rPropValue.value >>= value;
|
|
xWriter->characters(value);
|
|
}
|
|
}
|
|
xWriter->endElement(OUString::createFromAscii(name.name));
|
|
xWriter->endElement(u"prop"_ustr);
|
|
xWriter->endElement(operation);
|
|
}
|
|
xWriter->endElement(u"propertyupdate"_ustr);
|
|
xWriter->endDocument();
|
|
|
|
uno::Reference<io::XInputStream> const xRequestInStream(
|
|
io::SequenceInputStream::createStreamFromSequence(m_xContext,
|
|
xSeqOutStream->getWrittenBytes()));
|
|
assert(xRequestInStream.is());
|
|
|
|
curl_off_t const len(xSeqOutStream->getWrittenBytes().getLength());
|
|
|
|
::std::vector<CurlOption> const options{
|
|
{ CURLOPT_UPLOAD, 1L, nullptr },
|
|
{ CURLOPT_CUSTOMREQUEST, "PROPPATCH", "CURLOPT_CUSTOMREQUEST" },
|
|
// note: Sharepoint cannot handle "Transfer-Encoding: chunked"
|
|
{ CURLOPT_INFILESIZE_LARGE, len, nullptr, CurlOption::Type::CurlOffT }
|
|
};
|
|
|
|
CurlProcessor::ProcessRequest(*this, uri, u"PROPPATCH"_ustr, options, &rEnv, ::std::move(pList),
|
|
nullptr, &xRequestInStream, nullptr);
|
|
}
|
|
|
|
auto CurlSession::HEAD(OUString const& rURIReference, ::std::vector<OUString> const& rHeaderNames,
|
|
DAVResource& io_rResource, DAVRequestEnvironment const& rEnv) -> void
|
|
{
|
|
SAL_INFO("ucb.ucp.webdav.curl", "HEAD: " << rURIReference);
|
|
|
|
CurlUri const uri(CurlProcessor::URIReferenceToURI(*this, rURIReference));
|
|
|
|
::std::vector<CurlOption> const options{
|
|
// NOBODY will prevent logging the response body in ProcessRequest()
|
|
// exception, but omitting it here results in a long timeout until the
|
|
// server closes the connection, which is worse
|
|
{ CURLOPT_NOBODY, 1L, nullptr },
|
|
{ CURLOPT_CUSTOMREQUEST, "HEAD", "CURLOPT_CUSTOMREQUEST" }
|
|
};
|
|
|
|
::std::pair<::std::vector<OUString> const&, DAVResource&> const headers(rHeaderNames,
|
|
io_rResource);
|
|
|
|
CurlProcessor::ProcessRequest(*this, uri, u"HEAD"_ustr, options, &rEnv, nullptr, nullptr,
|
|
nullptr, &headers);
|
|
}
|
|
|
|
auto CurlSession::GET(OUString const& rURIReference, DAVRequestEnvironment const& rEnv)
|
|
-> uno::Reference<io::XInputStream>
|
|
{
|
|
SAL_INFO("ucb.ucp.webdav.curl", "GET: " << rURIReference);
|
|
|
|
CurlUri const uri(CurlProcessor::URIReferenceToURI(*this, rURIReference));
|
|
|
|
// could use either com.sun.star.io.Pipe or com.sun.star.io.SequenceInputStream?
|
|
// Pipe can just write into its XOuputStream, which is simpler.
|
|
// Both resize exponentially, so performance should be fine.
|
|
// However, Pipe doesn't implement XSeekable, which is required by filters.
|
|
|
|
uno::Reference<io::XSequenceOutputStream> const xSeqOutStream(
|
|
io::SequenceOutputStream::create(m_xContext));
|
|
uno::Reference<io::XOutputStream> const xResponseOutStream(xSeqOutStream);
|
|
assert(xResponseOutStream.is());
|
|
|
|
::std::vector<CurlOption> const options{ { CURLOPT_HTTPGET, 1L, nullptr } };
|
|
|
|
CurlProcessor::ProcessRequest(*this, uri, u"GET"_ustr, options, &rEnv, nullptr,
|
|
&xResponseOutStream, nullptr, nullptr);
|
|
|
|
uno::Reference<io::XInputStream> const xResponseInStream(
|
|
io::SequenceInputStream::createStreamFromSequence(m_xContext,
|
|
xSeqOutStream->getWrittenBytes()));
|
|
assert(xResponseInStream.is());
|
|
|
|
return xResponseInStream;
|
|
}
|
|
|
|
auto CurlSession::GET(OUString const& rURIReference, uno::Reference<io::XOutputStream>& rxOutStream,
|
|
DAVRequestEnvironment const& rEnv) -> void
|
|
{
|
|
SAL_INFO("ucb.ucp.webdav.curl", "GET: " << rURIReference);
|
|
|
|
CurlUri const uri(CurlProcessor::URIReferenceToURI(*this, rURIReference));
|
|
|
|
::std::vector<CurlOption> const options{ { CURLOPT_HTTPGET, 1L, nullptr } };
|
|
|
|
CurlProcessor::ProcessRequest(*this, uri, u"GET"_ustr, options, &rEnv, nullptr, &rxOutStream,
|
|
nullptr, nullptr);
|
|
}
|
|
|
|
auto CurlSession::GET(OUString const& rURIReference, ::std::vector<OUString> const& rHeaderNames,
|
|
DAVResource& io_rResource, DAVRequestEnvironment const& rEnv)
|
|
-> uno::Reference<io::XInputStream>
|
|
{
|
|
SAL_INFO("ucb.ucp.webdav.curl", "GET: " << rURIReference);
|
|
|
|
CurlUri const uri(CurlProcessor::URIReferenceToURI(*this, rURIReference));
|
|
|
|
::std::vector<CurlOption> const options{ { CURLOPT_HTTPGET, 1L, nullptr } };
|
|
|
|
uno::Reference<io::XSequenceOutputStream> const xSeqOutStream(
|
|
io::SequenceOutputStream::create(m_xContext));
|
|
uno::Reference<io::XOutputStream> const xResponseOutStream(xSeqOutStream);
|
|
assert(xResponseOutStream.is());
|
|
|
|
::std::pair<::std::vector<OUString> const&, DAVResource&> const headers(rHeaderNames,
|
|
io_rResource);
|
|
|
|
CurlProcessor::ProcessRequest(*this, uri, u"GET"_ustr, options, &rEnv, nullptr,
|
|
&xResponseOutStream, nullptr, &headers);
|
|
|
|
uno::Reference<io::XInputStream> const xResponseInStream(
|
|
io::SequenceInputStream::createStreamFromSequence(m_xContext,
|
|
xSeqOutStream->getWrittenBytes()));
|
|
assert(xResponseInStream.is());
|
|
|
|
return xResponseInStream;
|
|
}
|
|
|
|
auto CurlSession::GET(OUString const& rURIReference, uno::Reference<io::XOutputStream>& rxOutStream,
|
|
::std::vector<OUString> const& rHeaderNames, DAVResource& io_rResource,
|
|
DAVRequestEnvironment const& rEnv) -> void
|
|
{
|
|
SAL_INFO("ucb.ucp.webdav.curl", "GET: " << rURIReference);
|
|
|
|
CurlUri const uri(CurlProcessor::URIReferenceToURI(*this, rURIReference));
|
|
|
|
::std::vector<CurlOption> const options{ { CURLOPT_HTTPGET, 1L, nullptr } };
|
|
|
|
::std::pair<::std::vector<OUString> const&, DAVResource&> const headers(rHeaderNames,
|
|
io_rResource);
|
|
|
|
CurlProcessor::ProcessRequest(*this, uri, u"GET"_ustr, options, &rEnv, nullptr, &rxOutStream,
|
|
nullptr, &headers);
|
|
}
|
|
|
|
auto CurlSession::PUT(OUString const& rURIReference,
|
|
uno::Reference<io::XInputStream> const& rxInStream,
|
|
DAVRequestEnvironment const& rEnv) -> void
|
|
{
|
|
SAL_INFO("ucb.ucp.webdav.curl", "PUT: " << rURIReference);
|
|
|
|
CurlUri const uri(CurlProcessor::URIReferenceToURI(*this, rURIReference));
|
|
|
|
// NextCloud silently fails with chunked encoding
|
|
uno::Reference<io::XSeekable> const xSeekable(rxInStream, uno::UNO_QUERY);
|
|
if (!xSeekable.is())
|
|
{
|
|
throw uno::RuntimeException(u"TODO: not seekable"_ustr);
|
|
}
|
|
curl_off_t const len(xSeekable->getLength() - xSeekable->getPosition());
|
|
|
|
::std::unique_ptr<curl_slist, deleter_from_fn<curl_slist, curl_slist_free_all>> pList;
|
|
OUString const* const pToken(g_Init.LockStore.getLockTokenForURI(uri.GetURI(), nullptr));
|
|
if (pToken)
|
|
{
|
|
OString const utf8If("If: "
|
|
// disabled as Sharepoint 2013 workaround, it accepts only
|
|
// "No-Tag-List", see fed2984281a85a5a2f308841ec810f218c75f2ab
|
|
#if 0
|
|
"<" + OUStringToOString(rURIReference, RTL_TEXTENCODING_ASCII_US)
|
|
+ "> "
|
|
#endif
|
|
"(<"
|
|
+ OUStringToOString(*pToken, RTL_TEXTENCODING_ASCII_US) + ">)");
|
|
pList.reset(curl_slist_append(pList.release(), utf8If.getStr()));
|
|
if (!pList)
|
|
{
|
|
throw uno::RuntimeException(u"curl_slist_append failed"_ustr);
|
|
}
|
|
}
|
|
|
|
// lock m_Mutex after accessing global LockStore to avoid deadlock
|
|
|
|
// note: Nextcloud 20 cannot handle "Transfer-Encoding: chunked"
|
|
::std::vector<CurlOption> const options{
|
|
{ CURLOPT_UPLOAD, 1L, nullptr }, // libcurl won't upload without setting this
|
|
{ CURLOPT_INFILESIZE_LARGE, len, nullptr, CurlOption::Type::CurlOffT }
|
|
};
|
|
|
|
CurlProcessor::ProcessRequest(*this, uri, u"PUT"_ustr, options, &rEnv, ::std::move(pList),
|
|
nullptr, &rxInStream, nullptr);
|
|
}
|
|
|
|
auto CurlSession::POST(OUString const& rURIReference, OUString const& rContentType,
|
|
OUString const& rReferer, uno::Reference<io::XInputStream> const& rxInStream,
|
|
DAVRequestEnvironment const& rEnv) -> uno::Reference<io::XInputStream>
|
|
{
|
|
SAL_INFO("ucb.ucp.webdav.curl", "POST: " << rURIReference);
|
|
|
|
CurlUri const uri(CurlProcessor::URIReferenceToURI(*this, rURIReference));
|
|
|
|
// TODO: either set CURLOPT_POSTFIELDSIZE_LARGE or chunked?
|
|
::std::unique_ptr<curl_slist, deleter_from_fn<curl_slist, curl_slist_free_all>> pList(
|
|
curl_slist_append(nullptr, "Transfer-Encoding: chunked"));
|
|
if (!pList)
|
|
{
|
|
throw uno::RuntimeException(u"curl_slist_append failed"_ustr);
|
|
}
|
|
OString const utf8ContentType("Content-Type: "
|
|
+ OUStringToOString(rContentType, RTL_TEXTENCODING_ASCII_US));
|
|
pList.reset(curl_slist_append(pList.release(), utf8ContentType.getStr()));
|
|
if (!pList)
|
|
{
|
|
throw uno::RuntimeException(u"curl_slist_append failed"_ustr);
|
|
}
|
|
OString const utf8Referer("Referer: " + OUStringToOString(rReferer, RTL_TEXTENCODING_ASCII_US));
|
|
pList.reset(curl_slist_append(pList.release(), utf8Referer.getStr()));
|
|
if (!pList)
|
|
{
|
|
throw uno::RuntimeException(u"curl_slist_append failed"_ustr);
|
|
}
|
|
|
|
::std::vector<CurlOption> const options{ { CURLOPT_POST, 1L, nullptr } };
|
|
|
|
uno::Reference<io::XSequenceOutputStream> const xSeqOutStream(
|
|
io::SequenceOutputStream::create(m_xContext));
|
|
uno::Reference<io::XOutputStream> const xResponseOutStream(xSeqOutStream);
|
|
assert(xResponseOutStream.is());
|
|
|
|
CurlProcessor::ProcessRequest(*this, uri, u"POST"_ustr, options, &rEnv, ::std::move(pList),
|
|
&xResponseOutStream, &rxInStream, nullptr);
|
|
|
|
uno::Reference<io::XInputStream> const xResponseInStream(
|
|
io::SequenceInputStream::createStreamFromSequence(m_xContext,
|
|
xSeqOutStream->getWrittenBytes()));
|
|
assert(xResponseInStream.is());
|
|
|
|
return xResponseInStream;
|
|
}
|
|
|
|
auto CurlSession::POST(OUString const& rURIReference, OUString const& rContentType,
|
|
OUString const& rReferer, uno::Reference<io::XInputStream> const& rxInStream,
|
|
uno::Reference<io::XOutputStream>& rxOutStream,
|
|
DAVRequestEnvironment const& rEnv) -> void
|
|
{
|
|
SAL_INFO("ucb.ucp.webdav.curl", "POST: " << rURIReference);
|
|
|
|
CurlUri const uri(CurlProcessor::URIReferenceToURI(*this, rURIReference));
|
|
|
|
// TODO: either set CURLOPT_POSTFIELDSIZE_LARGE or chunked?
|
|
::std::unique_ptr<curl_slist, deleter_from_fn<curl_slist, curl_slist_free_all>> pList(
|
|
curl_slist_append(nullptr, "Transfer-Encoding: chunked"));
|
|
if (!pList)
|
|
{
|
|
throw uno::RuntimeException(u"curl_slist_append failed"_ustr);
|
|
}
|
|
OString const utf8ContentType("Content-Type: "
|
|
+ OUStringToOString(rContentType, RTL_TEXTENCODING_ASCII_US));
|
|
pList.reset(curl_slist_append(pList.release(), utf8ContentType.getStr()));
|
|
if (!pList)
|
|
{
|
|
throw uno::RuntimeException(u"curl_slist_append failed"_ustr);
|
|
}
|
|
OString const utf8Referer("Referer: " + OUStringToOString(rReferer, RTL_TEXTENCODING_ASCII_US));
|
|
pList.reset(curl_slist_append(pList.release(), utf8Referer.getStr()));
|
|
if (!pList)
|
|
{
|
|
throw uno::RuntimeException(u"curl_slist_append failed"_ustr);
|
|
}
|
|
|
|
::std::vector<CurlOption> const options{ { CURLOPT_POST, 1L, nullptr } };
|
|
|
|
CurlProcessor::ProcessRequest(*this, uri, u"POST"_ustr, options, &rEnv, ::std::move(pList),
|
|
&rxOutStream, &rxInStream, nullptr);
|
|
}
|
|
|
|
auto CurlSession::MKCOL(OUString const& rURIReference, DAVRequestEnvironment const& rEnv) -> void
|
|
{
|
|
SAL_INFO("ucb.ucp.webdav.curl", "MKCOL: " << rURIReference);
|
|
|
|
CurlUri const uri(CurlProcessor::URIReferenceToURI(*this, rURIReference));
|
|
|
|
::std::vector<CurlOption> const options{ { CURLOPT_CUSTOMREQUEST, "MKCOL",
|
|
"CURLOPT_CUSTOMREQUEST" } };
|
|
|
|
CurlProcessor::ProcessRequest(*this, uri, u"MKCOL"_ustr, options, &rEnv, nullptr, nullptr,
|
|
nullptr, nullptr);
|
|
}
|
|
|
|
auto CurlProcessor::MoveOrCopy(CurlSession& rSession, std::u16string_view rSourceURIReference,
|
|
::std::u16string_view const rDestinationURI,
|
|
DAVRequestEnvironment const& rEnv, bool const isOverwrite,
|
|
char const* const pMethod) -> void
|
|
{
|
|
CurlUri const uriSource(CurlProcessor::URIReferenceToURI(rSession, rSourceURIReference));
|
|
|
|
OString const utf8Destination("Destination: "
|
|
+ OUStringToOString(rDestinationURI, RTL_TEXTENCODING_ASCII_US));
|
|
::std::unique_ptr<curl_slist, deleter_from_fn<curl_slist, curl_slist_free_all>> pList(
|
|
curl_slist_append(nullptr, utf8Destination.getStr()));
|
|
if (!pList)
|
|
{
|
|
throw uno::RuntimeException(u"curl_slist_append failed"_ustr);
|
|
}
|
|
OString const utf8Overwrite(OString::Concat("Overwrite: ") + (isOverwrite ? "T" : "F"));
|
|
pList.reset(curl_slist_append(pList.release(), utf8Overwrite.getStr()));
|
|
if (!pList)
|
|
{
|
|
throw uno::RuntimeException(u"curl_slist_append failed"_ustr);
|
|
}
|
|
|
|
::std::vector<CurlOption> const options{ { CURLOPT_CUSTOMREQUEST, pMethod,
|
|
"CURLOPT_CUSTOMREQUEST" } };
|
|
|
|
CurlProcessor::ProcessRequest(rSession, uriSource, OUString::createFromAscii(pMethod), options,
|
|
&rEnv, ::std::move(pList), nullptr, nullptr, nullptr);
|
|
}
|
|
|
|
auto CurlSession::COPY(OUString const& rSourceURIReference, OUString const& rDestinationURI,
|
|
DAVRequestEnvironment const& rEnv, bool const isOverwrite) -> void
|
|
{
|
|
SAL_INFO("ucb.ucp.webdav.curl", "COPY: " << rSourceURIReference);
|
|
|
|
return CurlProcessor::MoveOrCopy(*this, rSourceURIReference, rDestinationURI, rEnv, isOverwrite,
|
|
"COPY");
|
|
}
|
|
|
|
auto CurlSession::MOVE(OUString const& rSourceURIReference, OUString const& rDestinationURI,
|
|
DAVRequestEnvironment const& rEnv, bool const isOverwrite) -> void
|
|
{
|
|
SAL_INFO("ucb.ucp.webdav.curl", "MOVE: " << rSourceURIReference);
|
|
|
|
return CurlProcessor::MoveOrCopy(*this, rSourceURIReference, rDestinationURI, rEnv, isOverwrite,
|
|
"MOVE");
|
|
}
|
|
|
|
auto CurlSession::DESTROY(OUString const& rURIReference, DAVRequestEnvironment const& rEnv) -> void
|
|
{
|
|
SAL_INFO("ucb.ucp.webdav.curl", "DESTROY: " << rURIReference);
|
|
|
|
CurlUri const uri(CurlProcessor::URIReferenceToURI(*this, rURIReference));
|
|
|
|
::std::vector<CurlOption> const options{ { CURLOPT_CUSTOMREQUEST, "DELETE",
|
|
"CURLOPT_CUSTOMREQUEST" } };
|
|
|
|
CurlProcessor::ProcessRequest(*this, uri, u"DESTROY"_ustr, options, &rEnv, nullptr, nullptr,
|
|
nullptr, nullptr);
|
|
}
|
|
|
|
auto CurlProcessor::Lock(
|
|
CurlSession& rSession, CurlUri const& rURI, DAVRequestEnvironment const* const pEnv,
|
|
::std::unique_ptr<curl_slist, deleter_from_fn<curl_slist, curl_slist_free_all>>
|
|
pRequestHeaderList,
|
|
uno::Reference<io::XInputStream> const* const pxRequestInStream)
|
|
-> ::std::vector<::std::pair<ucb::Lock, sal_Int32>>
|
|
{
|
|
curl_off_t len(0);
|
|
if (pxRequestInStream)
|
|
{
|
|
uno::Reference<io::XSeekable> const xSeekable(*pxRequestInStream, uno::UNO_QUERY);
|
|
assert(xSeekable.is());
|
|
len = xSeekable->getLength();
|
|
}
|
|
|
|
::std::vector<CurlOption> const options{
|
|
{ CURLOPT_UPLOAD, 1L, nullptr },
|
|
{ CURLOPT_CUSTOMREQUEST, "LOCK", "CURLOPT_CUSTOMREQUEST" },
|
|
// note: Sharepoint cannot handle "Transfer-Encoding: chunked"
|
|
{ CURLOPT_INFILESIZE_LARGE, len, nullptr, CurlOption::Type::CurlOffT }
|
|
};
|
|
|
|
// stream for response
|
|
uno::Reference<io::XInputStream> const xResponseInStream(io::Pipe::create(rSession.m_xContext));
|
|
uno::Reference<io::XOutputStream> const xResponseOutStream(xResponseInStream, uno::UNO_QUERY);
|
|
assert(xResponseInStream.is());
|
|
assert(xResponseOutStream.is());
|
|
|
|
TimeValue startTime;
|
|
osl_getSystemTime(&startTime);
|
|
|
|
CurlProcessor::ProcessRequest(rSession, rURI, u"LOCK"_ustr, options, pEnv,
|
|
::std::move(pRequestHeaderList), &xResponseOutStream,
|
|
pxRequestInStream, nullptr);
|
|
|
|
::std::vector<ucb::Lock> const acquiredLocks(parseWebDAVLockResponse(xResponseInStream));
|
|
SAL_WARN_IF(acquiredLocks.empty(), "ucb.ucp.webdav.curl",
|
|
"could not get LOCK for " << rURI.GetURI());
|
|
|
|
TimeValue endTime;
|
|
osl_getSystemTime(&endTime);
|
|
auto const elapsedSeconds(endTime.Seconds - startTime.Seconds);
|
|
|
|
// determine expiration time (seconds from endTime) for each acquired lock
|
|
::std::vector<::std::pair<ucb::Lock, sal_Int32>> ret;
|
|
ret.reserve(acquiredLocks.size());
|
|
for (auto const& rLock : acquiredLocks)
|
|
{
|
|
sal_Int32 lockExpirationTimeSeconds;
|
|
if (rLock.Timeout == -1)
|
|
{
|
|
lockExpirationTimeSeconds = -1;
|
|
}
|
|
else if (rLock.Timeout <= elapsedSeconds)
|
|
{
|
|
SAL_WARN("ucb.ucp.webdav.curl",
|
|
"LOCK timeout already expired when receiving LOCK response for "
|
|
<< rURI.GetURI());
|
|
lockExpirationTimeSeconds = 0;
|
|
}
|
|
else
|
|
{
|
|
lockExpirationTimeSeconds = startTime.Seconds + rLock.Timeout;
|
|
}
|
|
ret.emplace_back(rLock, lockExpirationTimeSeconds);
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
auto CurlSession::LOCK(OUString const& rURIReference, ucb::Lock /*const*/& rLock,
|
|
DAVRequestEnvironment const& rEnv) -> void
|
|
{
|
|
SAL_INFO("ucb.ucp.webdav.curl", "LOCK: " << rURIReference);
|
|
|
|
CurlUri const uri(CurlProcessor::URIReferenceToURI(*this, rURIReference));
|
|
|
|
if (g_Init.LockStore.getLockTokenForURI(uri.GetURI(), &rLock))
|
|
{
|
|
// already have a lock that covers the requirement
|
|
// TODO: maybe use DAV:lockdiscovery to ensure it's valid
|
|
return;
|
|
}
|
|
|
|
// note: no m_Mutex lock needed here, only in CurlProcessor::Lock()
|
|
|
|
// generate XML document for acquiring new LOCK
|
|
uno::Reference<io::XSequenceOutputStream> const xSeqOutStream(
|
|
io::SequenceOutputStream::create(m_xContext));
|
|
uno::Reference<io::XOutputStream> const xRequestOutStream(xSeqOutStream);
|
|
assert(xRequestOutStream.is());
|
|
uno::Reference<xml::sax::XWriter> const xWriter(xml::sax::Writer::create(m_xContext));
|
|
xWriter->setOutputStream(xRequestOutStream);
|
|
xWriter->startDocument();
|
|
rtl::Reference<::comphelper::AttributeList> const pAttrList(new ::comphelper::AttributeList);
|
|
pAttrList->AddAttribute(u"xmlns"_ustr, u"DAV:"_ustr);
|
|
xWriter->startElement(u"lockinfo"_ustr, pAttrList);
|
|
xWriter->startElement(u"lockscope"_ustr, nullptr);
|
|
switch (rLock.Scope)
|
|
{
|
|
case ucb::LockScope_EXCLUSIVE:
|
|
xWriter->startElement(u"exclusive"_ustr, nullptr);
|
|
xWriter->endElement(u"exclusive"_ustr);
|
|
break;
|
|
case ucb::LockScope_SHARED:
|
|
xWriter->startElement(u"shared"_ustr, nullptr);
|
|
xWriter->endElement(u"shared"_ustr);
|
|
break;
|
|
default:
|
|
assert(false);
|
|
}
|
|
xWriter->endElement(u"lockscope"_ustr);
|
|
xWriter->startElement(u"locktype"_ustr, nullptr);
|
|
xWriter->startElement(u"write"_ustr, nullptr);
|
|
xWriter->endElement(u"write"_ustr);
|
|
xWriter->endElement(u"locktype"_ustr);
|
|
OUString owner;
|
|
if ((rLock.Owner >>= owner) && !owner.isEmpty())
|
|
{
|
|
xWriter->startElement(u"owner"_ustr, nullptr);
|
|
xWriter->characters(owner);
|
|
xWriter->endElement(u"owner"_ustr);
|
|
}
|
|
xWriter->endElement(u"lockinfo"_ustr);
|
|
xWriter->endDocument();
|
|
|
|
uno::Reference<io::XInputStream> const xRequestInStream(
|
|
io::SequenceInputStream::createStreamFromSequence(m_xContext,
|
|
xSeqOutStream->getWrittenBytes()));
|
|
assert(xRequestInStream.is());
|
|
|
|
::std::unique_ptr<curl_slist, deleter_from_fn<curl_slist, curl_slist_free_all>> pList;
|
|
pList.reset(curl_slist_append(pList.release(), "Content-Type: application/xml"));
|
|
if (!pList)
|
|
{
|
|
throw uno::RuntimeException(u"curl_slist_append failed"_ustr);
|
|
}
|
|
OString depth;
|
|
switch (rLock.Depth)
|
|
{
|
|
case ucb::LockDepth_ZERO:
|
|
depth = "Depth: 0"_ostr;
|
|
break;
|
|
case ucb::LockDepth_ONE:
|
|
depth = "Depth: 1"_ostr;
|
|
break;
|
|
case ucb::LockDepth_INFINITY:
|
|
depth = "Depth: infinity"_ostr;
|
|
break;
|
|
default:
|
|
assert(false);
|
|
}
|
|
pList.reset(curl_slist_append(pList.release(), depth.getStr()));
|
|
if (!pList)
|
|
{
|
|
throw uno::RuntimeException(u"curl_slist_append failed"_ustr);
|
|
}
|
|
OString timeout;
|
|
switch (rLock.Timeout)
|
|
{
|
|
case -1:
|
|
timeout = "Timeout: Infinite"_ostr;
|
|
break;
|
|
case 0:
|
|
timeout = "Timeout: Second-180"_ostr;
|
|
break;
|
|
default:
|
|
timeout = "Timeout: Second-" + OString::number(rLock.Timeout);
|
|
assert(0 < rLock.Timeout);
|
|
break;
|
|
}
|
|
pList.reset(curl_slist_append(pList.release(), timeout.getStr()));
|
|
if (!pList)
|
|
{
|
|
throw uno::RuntimeException(u"curl_slist_append failed"_ustr);
|
|
}
|
|
|
|
auto const acquiredLocks
|
|
= CurlProcessor::Lock(*this, uri, &rEnv, ::std::move(pList), &xRequestInStream);
|
|
|
|
for (auto const& rAcquiredLock : acquiredLocks)
|
|
{
|
|
g_Init.LockStore.addLock(uri.GetURI(), rAcquiredLock.first,
|
|
rAcquiredLock.first.LockTokens[0], this, rAcquiredLock.second);
|
|
SAL_INFO("ucb.ucp.webdav.curl", "created LOCK for " << rURIReference);
|
|
}
|
|
}
|
|
|
|
auto CurlProcessor::Unlock(CurlSession& rSession, CurlUri const& rURI,
|
|
DAVRequestEnvironment const* const pEnv) -> void
|
|
{
|
|
OUString const* const pToken(g_Init.LockStore.getLockTokenForURI(rURI.GetURI(), nullptr));
|
|
if (!pToken)
|
|
{
|
|
SAL_WARN("ucb.ucp.webdav.curl", "attempt to unlock but not locked");
|
|
throw DAVException(DAVException::DAV_NOT_LOCKED);
|
|
}
|
|
OString const utf8LockToken("Lock-Token: <"
|
|
+ OUStringToOString(*pToken, RTL_TEXTENCODING_ASCII_US) + ">");
|
|
::std::unique_ptr<curl_slist, deleter_from_fn<curl_slist, curl_slist_free_all>> pList(
|
|
curl_slist_append(nullptr, utf8LockToken.getStr()));
|
|
if (!pList)
|
|
{
|
|
throw uno::RuntimeException(u"curl_slist_append failed"_ustr);
|
|
}
|
|
|
|
::std::vector<CurlOption> const options{ { CURLOPT_CUSTOMREQUEST, "UNLOCK",
|
|
"CURLOPT_CUSTOMREQUEST" } };
|
|
|
|
CurlProcessor::ProcessRequest(rSession, rURI, u"UNLOCK"_ustr, options, pEnv, ::std::move(pList),
|
|
nullptr, nullptr, nullptr);
|
|
}
|
|
|
|
auto CurlSession::UNLOCK(OUString const& rURIReference, DAVRequestEnvironment const& rEnv) -> void
|
|
{
|
|
SAL_INFO("ucb.ucp.webdav.curl", "UNLOCK: " << rURIReference);
|
|
|
|
// note: no m_Mutex lock needed here, only in CurlProcessor::Unlock()
|
|
|
|
CurlUri const uri(CurlProcessor::URIReferenceToURI(*this, rURIReference));
|
|
|
|
CurlProcessor::Unlock(*this, uri, &rEnv);
|
|
|
|
g_Init.LockStore.removeLock(uri.GetURI());
|
|
}
|
|
|
|
auto CurlSession::NonInteractive_LOCK(OUString const& rURI, ::std::u16string_view const rLockToken,
|
|
sal_Int32& o_rLastChanceToSendRefreshRequest,
|
|
bool& o_rIsAuthFailed) -> bool
|
|
{
|
|
SAL_INFO("ucb.ucp.webdav.curl", "NonInteractive_LOCK: " << rURI);
|
|
|
|
// note: no m_Mutex lock needed here, only in CurlProcessor::Lock()
|
|
|
|
try
|
|
{
|
|
CurlUri const uri(rURI);
|
|
::std::unique_ptr<curl_slist, deleter_from_fn<curl_slist, curl_slist_free_all>> pList(
|
|
curl_slist_append(nullptr, "Timeout: Second-180"));
|
|
|
|
assert(!rLockToken.empty()); // LockStore is the caller
|
|
OString const utf8If("If: (<" + OUStringToOString(rLockToken, RTL_TEXTENCODING_ASCII_US)
|
|
+ ">)");
|
|
pList.reset(curl_slist_append(pList.release(), utf8If.getStr()));
|
|
if (!pList)
|
|
{
|
|
throw uno::RuntimeException(u"curl_slist_append failed"_ustr);
|
|
}
|
|
|
|
auto const acquiredLocks
|
|
= CurlProcessor::Lock(*this, uri, nullptr, ::std::move(pList), nullptr);
|
|
|
|
SAL_WARN_IF(1 < acquiredLocks.size(), "ucb.ucp.webdav.curl",
|
|
"multiple locks acquired on refresh for " << rURI);
|
|
if (!acquiredLocks.empty())
|
|
{
|
|
o_rLastChanceToSendRefreshRequest = acquiredLocks.begin()->second;
|
|
}
|
|
SAL_INFO("ucb.ucp.webdav.curl", "NonInteractive_LOCK succeeded on " << rURI);
|
|
return true;
|
|
}
|
|
catch (DAVException const& rException)
|
|
{
|
|
SAL_INFO("ucb.ucp.webdav.curl", "NonInteractive_LOCK failed on " << rURI);
|
|
switch (rException.getError())
|
|
{
|
|
case DAVException::DAV_HTTP_AUTH:
|
|
case DAVException::DAV_HTTP_NOAUTH:
|
|
o_rIsAuthFailed = true;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
return false;
|
|
}
|
|
catch (...)
|
|
{
|
|
SAL_INFO("ucb.ucp.webdav.curl", "NonInteractive_LOCK failed on " << rURI);
|
|
return false;
|
|
}
|
|
}
|
|
|
|
auto CurlSession::NonInteractive_UNLOCK(OUString const& rURI) -> void
|
|
{
|
|
SAL_INFO("ucb.ucp.webdav.curl", "NonInteractive_UNLOCK: " << rURI);
|
|
|
|
// note: no m_Mutex lock needed here, only in CurlProcessor::Unlock()
|
|
|
|
try
|
|
{
|
|
CurlUri const uri(rURI);
|
|
|
|
CurlProcessor::Unlock(*this, uri, nullptr);
|
|
|
|
// the only caller is the dtor of the LockStore, don't call remove!
|
|
SAL_INFO("ucb.ucp.webdav.curl", "NonInteractive_UNLOCK succeeded on " << rURI);
|
|
}
|
|
catch (...)
|
|
{
|
|
SAL_INFO("ucb.ucp.webdav.curl", "NonInteractive_UNLOCK failed on " << rURI);
|
|
}
|
|
}
|
|
|
|
} // namespace http_dav_ucp
|
|
|
|
namespace
|
|
{
|
|
/// Manage lifecycle of global DAV worker threads
|
|
class WebDAVManager : public cppu::WeakImplHelper<css::lang::XServiceInfo>,
|
|
public comphelper::LibreOfficeKit::ThreadJoinable
|
|
{
|
|
public:
|
|
WebDAVManager() {}
|
|
|
|
// XServiceInfo
|
|
virtual OUString SAL_CALL getImplementationName() override
|
|
{
|
|
return "com.sun.star.comp.WebDAVManager";
|
|
}
|
|
virtual sal_Bool SAL_CALL supportsService(const OUString& ServiceName) override
|
|
{
|
|
return cppu::supportsService(this, ServiceName);
|
|
}
|
|
virtual css::uno::Sequence<OUString> SAL_CALL getSupportedServiceNames() override
|
|
{
|
|
return { "com.sun.star.ucb.WebDAVManager" };
|
|
}
|
|
|
|
// comphelper::LibreOfficeKit::ThreadJoinable
|
|
virtual bool joinThreads() override { return g_Init.LockStore.joinThreads(); }
|
|
|
|
virtual void startThreads() override { g_Init.LockStore.startThreads(); }
|
|
};
|
|
|
|
} // anonymous namespace
|
|
|
|
extern "C" SAL_DLLPUBLIC_EXPORT css::uno::XInterface*
|
|
ucb_webdav_manager_get_implementation(css::uno::XComponentContext*,
|
|
css::uno::Sequence<css::uno::Any> const&)
|
|
{
|
|
return cppu::acquire(new WebDAVManager());
|
|
}
|
|
|
|
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
|