/* -*- 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/.
*/
/// 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;
/// represent an option to be passed to curl_easy_setopt() struct CurlOption
{
CURLoption const Option; enumclass Type
{
Pointer, Long,
CurlOffT
};
Type const Tag; union { voidconst* const pValue; long/*const*/ lValue;
curl_off_t /*const*/ cValue;
}; charconst* const pExceptionString;
CurlOption(CURLoption const i_Option, voidconst* const i_Value, charconst* 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, charconst* 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;
// this appears to be the only way to get the "realm" from libcurl staticauto ExtractRealm(ResponseHeaders const& rHeaders, charconst* const pAuthHeaderName)
-> ::std::optional<OUString>
{
::std::map<OUString, OUString> const headerMap(
ProcessHeaders(rHeaders.HeaderFields.back().first)); autoconst 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();
}
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
{ staticauto URIReferenceToURI(CurlSession& rSession, std::u16string_view rURIReference)
-> CurlUri;
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);
}
}
// 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 autoconst 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));
// 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; });
}))
{ returnfalse; // still have the lock
}
if (pEnv)
{ // add custom request headers passed by caller for (autoconst& 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())
{ autoconst 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 (autoconst& 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 (autoconst& 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);
// 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 autoconst bytes(xSeqOutStream->getWrittenBytes()); autoconst 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
{ staticcharconst 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)
{ autoconst 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 autoconst 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");
} elseif (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 boolconst 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();
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.