Bump version to 24.04.3.4
[LibreOffice.git] / ucb / source / ucp / webdav-curl / CurlSession.cxx
bloba80c19f6d690aa07acb833d4140364bac51b0cdf
1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4; fill-column: 100 -*- */
2 /*
3 * This file is part of the LibreOffice project.
5 * This Source Code Form is subject to the terms of the Mozilla Public
6 * License, v. 2.0. If a copy of the MPL was not distributed with this
7 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
8 */
10 #include "CurlSession.hxx"
12 #include "SerfLockStore.hxx"
13 #include "DAVProperties.hxx"
14 #include "UCBDeadPropertyValue.hxx"
15 #include "webdavresponseparser.hxx"
17 #include <cppuhelper/implbase.hxx>
18 #include <comphelper/processfactory.hxx>
19 #include <comphelper/attributelist.hxx>
20 #include <comphelper/scopeguard.hxx>
21 #include <comphelper/string.hxx>
22 #include <cppuhelper/queryinterface.hxx>
23 #include <cppuhelper/supportsservice.hxx>
25 #include <o3tl/safeint.hxx>
26 #include <o3tl/string_view.hxx>
28 #include <officecfg/Inet.hxx>
29 #include <officecfg/Office/Security.hxx>
31 #include <com/sun/star/beans/NamedValue.hpp>
32 #include <com/sun/star/io/Pipe.hpp>
33 #include <com/sun/star/lang/XServiceInfo.hpp>
34 #include <com/sun/star/io/SequenceInputStream.hpp>
35 #include <com/sun/star/io/SequenceOutputStream.hpp>
36 #include <com/sun/star/xml/sax/Writer.hpp>
38 #include <osl/time.h>
39 #include <sal/log.hxx>
40 #include <rtl/uri.hxx>
41 #include <rtl/strbuf.hxx>
42 #include <rtl/ustrbuf.hxx>
43 #include <systools/curlinit.hxx>
44 #include <tools/hostfilter.hxx>
45 #include <config_version.h>
47 #include <map>
48 #include <optional>
49 #include <tuple>
50 #include <utility>
52 using namespace ::com::sun::star;
54 namespace
56 /// globals container
57 struct Init
59 /// note: LockStore has its own mutex and calls CurlSession from its thread
60 /// so don't call LockStore with m_Mutex held to prevent deadlock.
61 ::http_dav_ucp::SerfLockStore LockStore;
63 Init()
65 if (curl_global_init(CURL_GLOBAL_ALL) != CURLE_OK)
67 assert(!"curl_global_init failed");
70 // do not call curl_global_cleanup() - this is not the only client of curl
72 Init g_Init;
74 struct ResponseHeaders
76 ::std::vector<::std::pair<::std::vector<OString>, ::std::optional<long>>> HeaderFields;
77 CURL* pCurl;
78 ResponseHeaders(CURL* const i_pCurl)
79 : pCurl(i_pCurl)
84 auto GetResponseCode(ResponseHeaders const& rHeaders) -> ::std::optional<long>
86 return (rHeaders.HeaderFields.empty()) ? ::std::optional<long>{}
87 : rHeaders.HeaderFields.back().second;
90 struct DownloadTarget
92 uno::Reference<io::XOutputStream> xOutStream;
93 ResponseHeaders const& rHeaders;
94 DownloadTarget(uno::Reference<io::XOutputStream> i_xOutStream,
95 ResponseHeaders const& i_rHeaders)
96 : xOutStream(std::move(i_xOutStream))
97 , rHeaders(i_rHeaders)
102 struct UploadSource
104 uno::Sequence<sal_Int8> const& rInData;
105 size_t nPosition;
106 UploadSource(uno::Sequence<sal_Int8> const& i_rInData)
107 : rInData(i_rInData)
108 , nPosition(0)
113 auto GetErrorString(CURLcode const rc, char const* const pErrorBuffer = nullptr) -> OString
115 char const* const pMessage( // static fallback
116 (pErrorBuffer && pErrorBuffer[0] != '\0') ? pErrorBuffer : curl_easy_strerror(rc));
117 return OString::Concat("(") + OString::number(sal_Int32(rc)) + ") " + pMessage;
120 auto GetErrorStringMulti(CURLMcode const mc) -> OString
122 return OString::Concat("(") + OString::number(sal_Int32(mc)) + ") " + curl_multi_strerror(mc);
125 /// represent an option to be passed to curl_easy_setopt()
126 struct CurlOption
128 CURLoption const Option;
129 enum class Type
131 Pointer,
132 Long,
133 CurlOffT
135 Type const Tag;
136 union {
137 void const* const pValue;
138 long /*const*/ lValue;
139 curl_off_t /*const*/ cValue;
141 char const* const pExceptionString;
143 CurlOption(CURLoption const i_Option, void const* const i_Value,
144 char const* const i_pExceptionString)
145 : Option(i_Option)
146 , Tag(Type::Pointer)
147 , pValue(i_Value)
148 , pExceptionString(i_pExceptionString)
151 // Depending on platform, curl_off_t may be "long" or a larger type
152 // so cannot use overloading to distinguish these cases.
153 CurlOption(CURLoption const i_Option, curl_off_t const i_Value,
154 char const* const i_pExceptionString, Type const type = Type::Long)
155 : Option(i_Option)
156 , Tag(type)
157 , pExceptionString(i_pExceptionString)
159 static_assert(sizeof(long) <= sizeof(curl_off_t));
160 switch (type)
162 case Type::Long:
163 lValue = i_Value;
164 break;
165 case Type::CurlOffT:
166 cValue = i_Value;
167 break;
168 default:
169 assert(false);
174 /// combined guard class to ensure things are released in correct order,
175 /// particularly in ProcessRequest() error handling
176 class Guard
178 private:
179 /// mutex *first* because m_oGuard requires it
180 ::std::unique_lock<::std::mutex> m_Lock;
181 ::std::vector<CurlOption> const m_Options;
182 ::http_dav_ucp::CurlUri const& m_rURI;
183 CURL* const m_pCurl;
185 public:
186 explicit Guard(::std::mutex& rMutex, ::std::vector<CurlOption> aOptions,
187 ::http_dav_ucp::CurlUri const& rURI, CURL* const pCurl)
188 : m_Lock(rMutex, ::std::defer_lock)
189 , m_Options(std::move(aOptions))
190 , m_rURI(rURI)
191 , m_pCurl(pCurl)
193 Acquire();
195 ~Guard()
197 if (m_Lock.owns_lock())
199 Release();
203 void Acquire()
205 assert(!m_Lock.owns_lock());
206 m_Lock.lock();
207 for (auto const& it : m_Options)
209 CURLcode rc(CURL_LAST); // warning C4701
210 if (it.Tag == CurlOption::Type::Pointer)
212 rc = curl_easy_setopt(m_pCurl, it.Option, it.pValue);
214 else if (it.Tag == CurlOption::Type::Long)
216 rc = curl_easy_setopt(m_pCurl, it.Option, it.lValue);
218 else if (it.Tag == CurlOption::Type::CurlOffT)
220 rc = curl_easy_setopt(m_pCurl, it.Option, it.cValue);
222 else
224 assert(false);
226 if (it.pExceptionString != nullptr)
228 if (rc != CURLE_OK)
230 SAL_WARN("ucb.ucp.webdav.curl",
231 "set " << it.pExceptionString << " failed: " << GetErrorString(rc));
232 throw ::http_dav_ucp::DAVException(
233 ::http_dav_ucp::DAVException::DAV_SESSION_CREATE,
234 ::http_dav_ucp::ConnectionEndPointString(m_rURI.GetHost(),
235 m_rURI.GetPort()));
238 else // many of the options cannot fail
240 assert(rc == CURLE_OK);
244 void Release()
246 assert(m_Lock.owns_lock());
247 for (auto const& it : m_Options)
249 CURLcode rc(CURL_LAST); // warning C4701
250 if (it.Tag == CurlOption::Type::Pointer)
252 rc = curl_easy_setopt(m_pCurl, it.Option, nullptr);
254 else if (it.Tag == CurlOption::Type::Long)
256 rc = curl_easy_setopt(m_pCurl, it.Option, 0L);
258 else if (it.Tag == CurlOption::Type::CurlOffT)
260 rc = curl_easy_setopt(m_pCurl, it.Option, curl_off_t(-1));
262 else
264 assert(false);
266 assert(rc == CURLE_OK);
267 (void)rc;
269 m_Lock.unlock();
273 } // namespace
275 namespace http_dav_ucp
277 // libcurl callbacks:
279 static int debug_callback(CURL* handle, curl_infotype type, char* data, size_t size,
280 void* /*userdata*/)
282 char const* pType(nullptr);
283 switch (type)
285 case CURLINFO_TEXT:
286 SAL_INFO("ucb.ucp.webdav.curl", "debug log: " << handle << ": " << data);
287 return 0;
288 case CURLINFO_HEADER_IN:
289 SAL_INFO("ucb.ucp.webdav.curl",
290 "CURLINFO_HEADER_IN: " << handle << ": " << OString(data, size));
291 return 0;
292 case CURLINFO_HEADER_OUT:
294 // unlike IN, this is all headers in one call
295 OString tmp(data, size);
296 sal_Int32 const start(tmp.indexOf("Authorization: "));
297 if (start != -1)
299 sal_Int32 const end(tmp.indexOf("\r\n", start));
300 assert(end != -1);
301 sal_Int32 const len(SAL_N_ELEMENTS("Authorization: ") - 1);
302 tmp = tmp.replaceAt(
303 start + len, end - start - len,
304 Concat2View(OString::number(end - start - len) + " bytes redacted"));
306 SAL_INFO("ucb.ucp.webdav.curl", "CURLINFO_HEADER_OUT: " << handle << ": " << tmp);
307 return 0;
309 case CURLINFO_DATA_IN:
310 pType = "CURLINFO_DATA_IN";
311 break;
312 case CURLINFO_DATA_OUT:
313 pType = "CURLINFO_DATA_OUT";
314 break;
315 case CURLINFO_SSL_DATA_IN:
316 pType = "CURLINFO_SSL_DATA_IN";
317 break;
318 case CURLINFO_SSL_DATA_OUT:
319 pType = "CURLINFO_SSL_DATA_OUT";
320 break;
321 default:
322 SAL_WARN("ucb.ucp.webdav.curl", "unexpected debug log type");
323 return 0;
325 SAL_INFO("ucb.ucp.webdav.curl", "debug log: " << handle << ": " << pType << " " << size);
326 return 0;
329 static size_t write_callback(char* const ptr, size_t const size, size_t const nmemb,
330 void* const userdata)
332 auto* const pTarget(static_cast<DownloadTarget*>(userdata));
333 if (!pTarget) // looks like ~every request may have a response body
335 return nmemb;
337 assert(size == 1); // says the man page
338 (void)size;
339 assert(pTarget->xOutStream.is());
340 auto const oResponseCode(GetResponseCode(pTarget->rHeaders));
341 if (!oResponseCode)
343 return 0; // that is an error
345 // always write, for exception handler in ProcessRequest()
346 // if (200 <= *oResponseCode && *oResponseCode < 300)
350 uno::Sequence<sal_Int8> const data(reinterpret_cast<sal_Int8*>(ptr), nmemb);
351 pTarget->xOutStream->writeBytes(data);
353 catch (...)
355 SAL_WARN("ucb.ucp.webdav.curl", "exception in write_callback");
356 return 0; // error
359 // else: ignore the body? CurlSession will check the status eventually
360 return nmemb;
363 static size_t read_callback(char* const buffer, size_t const size, size_t const nitems,
364 void* const userdata)
366 auto* const pSource(static_cast<UploadSource*>(userdata));
367 assert(pSource);
368 size_t const nBytes(size * nitems);
369 size_t nRet(0);
372 assert(pSource->nPosition <= o3tl::make_unsigned(pSource->rInData.getLength()));
373 nRet = ::std::min<size_t>(pSource->rInData.getLength() - pSource->nPosition, nBytes);
374 ::std::memcpy(buffer, pSource->rInData.getConstArray() + pSource->nPosition, nRet);
375 pSource->nPosition += nRet;
377 catch (...)
379 SAL_WARN("ucb.ucp.webdav.curl", "exception in read_callback");
380 return CURL_READFUNC_ABORT; // error
382 return nRet;
385 static size_t header_callback(char* const buffer, size_t const size, size_t const nitems,
386 void* const userdata)
388 auto* const pHeaders(static_cast<ResponseHeaders*>(userdata));
389 assert(pHeaders);
390 #if 0
391 if (!pHeaders) // TODO maybe not needed in every request? not sure
393 return nitems;
395 #endif
396 assert(size == 1); // says the man page
397 (void)size;
400 if (nitems <= 2)
402 // end of header, body follows...
403 if (pHeaders->HeaderFields.empty())
405 SAL_WARN("ucb.ucp.webdav.curl", "header_callback: empty header?");
406 return 0; // error
408 // unfortunately there's no separate callback when the body begins,
409 // so have to manually retrieve the status code here
410 long statusCode(SC_NONE);
411 auto rc = curl_easy_getinfo(pHeaders->pCurl, CURLINFO_RESPONSE_CODE, &statusCode);
412 assert(rc == CURLE_OK);
413 (void)rc;
414 // always put the current response code here - wasn't necessarily in this header
415 pHeaders->HeaderFields.back().second.emplace(statusCode);
417 else if (buffer[0] == ' ' || buffer[0] == '\t') // folded header field?
419 size_t i(0);
422 ++i;
423 } while (i == ' ' || i == '\t');
424 if (pHeaders->HeaderFields.empty() || pHeaders->HeaderFields.back().second
425 || pHeaders->HeaderFields.back().first.empty())
427 SAL_WARN("ucb.ucp.webdav.curl",
428 "header_callback: folded header field without start");
429 return 0; // error
431 pHeaders->HeaderFields.back().first.back()
432 += OString::Concat(" ") + ::std::string_view(&buffer[i], nitems - i);
434 else
436 if (pHeaders->HeaderFields.empty() || pHeaders->HeaderFields.back().second)
438 pHeaders->HeaderFields.emplace_back();
440 pHeaders->HeaderFields.back().first.emplace_back(OString(buffer, nitems));
443 catch (...)
445 SAL_WARN("ucb.ucp.webdav.curl", "exception in header_callback");
446 return 0; // error
448 return nitems;
451 static auto ProcessHeaders(::std::vector<OString> const& rHeaders) -> ::std::map<OUString, OUString>
453 ::std::map<OUString, OUString> ret;
454 for (OString const& rLine : rHeaders)
456 OString line;
457 if (!rLine.endsWith("\r\n", &line))
459 SAL_WARN("ucb.ucp.webdav.curl", "invalid header field (no CRLF)");
460 continue;
462 if (line.startsWith("HTTP/") // first line
463 || line.isEmpty()) // last line
465 continue;
467 auto const nColon(line.indexOf(':'));
468 if (nColon == -1)
471 SAL_WARN("ucb.ucp.webdav.curl", "invalid header field (no :)");
473 continue;
475 if (nColon == 0)
477 SAL_WARN("ucb.ucp.webdav.curl", "invalid header field (empty name)");
478 continue;
480 // case insensitive; must be ASCII
481 auto const name(::rtl::OStringToOUString(line.copy(0, nColon).toAsciiLowerCase(),
482 RTL_TEXTENCODING_ASCII_US));
483 sal_Int32 nStart(nColon + 1);
484 while (nStart < line.getLength() && (line[nStart] == ' ' || line[nStart] == '\t'))
486 ++nStart;
488 sal_Int32 nEnd(line.getLength());
489 while (nStart < nEnd && (line[nEnd - 1] == ' ' || line[nEnd - 1] == '\t'))
491 --nEnd;
493 // RFC 7230 says that only ASCII works reliably anyway (neon also did this)
494 auto const value(::rtl::OStringToOUString(line.subView(nStart, nEnd - nStart),
495 RTL_TEXTENCODING_ASCII_US));
496 auto const it(ret.find(name));
497 if (it != ret.end())
499 it->second = it->second + "," + value;
501 else
503 ret[name] = value;
506 return ret;
509 static auto ExtractRequestedHeaders(
510 ResponseHeaders const& rHeaders,
511 ::std::pair<::std::vector<OUString> const&, DAVResource&> const* const pRequestedHeaders)
512 -> void
514 ::std::map<OUString, OUString> const headerMap(
515 ProcessHeaders(rHeaders.HeaderFields.back().first));
516 if (pRequestedHeaders)
518 for (OUString const& rHeader : pRequestedHeaders->first)
520 auto const it(headerMap.find(rHeader.toAsciiLowerCase()));
521 if (it != headerMap.end())
523 DAVPropertyValue value;
524 value.IsCaseSensitive = false;
525 value.Name = it->first;
526 value.Value <<= it->second;
527 pRequestedHeaders->second.properties.push_back(value);
533 // this appears to be the only way to get the "realm" from libcurl
534 static auto ExtractRealm(ResponseHeaders const& rHeaders, char const* const pAuthHeaderName)
535 -> ::std::optional<OUString>
537 ::std::map<OUString, OUString> const headerMap(
538 ProcessHeaders(rHeaders.HeaderFields.back().first));
539 auto const it(headerMap.find(OUString::createFromAscii(pAuthHeaderName).toAsciiLowerCase()));
540 if (it == headerMap.end())
542 SAL_WARN("ucb.ucp.webdav.curl", "cannot find auth header");
543 return {};
545 // It may be possible that the header contains multiple methods each with
546 // a different realm - extract only the first one bc the downstream API
547 // only supports one anyway.
548 // case insensitive!
549 auto i(it->second.toAsciiLowerCase().indexOf("realm="));
550 // is optional
551 if (i == -1)
553 SAL_INFO("ucb.ucp.webdav.curl", "auth header has no realm");
554 return {};
556 // no whitespace allowed before or after =
557 i += ::std::strlen("realm=");
558 if (it->second.getLength() < i + 2 || it->second[i] != '\"')
560 SAL_WARN("ucb.ucp.webdav.curl", "no realm value");
561 return {};
563 ++i;
564 OUStringBuffer buf;
565 while (i < it->second.getLength() && it->second[i] != '\"')
567 if (it->second[i] == '\\') // quoted-pair escape
569 ++i;
570 if (it->second.getLength() <= i)
572 SAL_WARN("ucb.ucp.webdav.curl", "unterminated quoted-pair");
573 return {};
576 buf.append(it->second[i]);
577 ++i;
579 if (it->second.getLength() <= i)
581 SAL_WARN("ucb.ucp.webdav.curl", "unterminated realm");
582 return {};
584 return buf.makeStringAndClear();
587 CurlSession::CurlSession(uno::Reference<uno::XComponentContext> xContext,
588 ::rtl::Reference<DAVSessionFactory> const& rpFactory, OUString const& rURI,
589 uno::Sequence<beans::NamedValue> const& rFlags,
590 ::ucbhelper::InternetProxyDecider const& rProxyDecider)
591 : DAVSession(rpFactory)
592 , m_xContext(std::move(xContext))
593 , m_Flags(rFlags)
594 , m_URI(rURI)
595 , m_Proxy(rProxyDecider.getProxy(m_URI.GetScheme(), m_URI.GetHost(), m_URI.GetPort()))
597 assert(m_URI.GetScheme() == "http" || m_URI.GetScheme() == "https");
598 m_pCurlMulti.reset(curl_multi_init());
599 if (!m_pCurlMulti)
601 SAL_WARN("ucb.ucp.webdav.curl", "curl_multi_init failed");
602 throw DAVException(DAVException::DAV_SESSION_CREATE,
603 ConnectionEndPointString(m_URI.GetHost(), m_URI.GetPort()));
605 m_pCurl.reset(curl_easy_init());
606 if (!m_pCurl)
608 SAL_WARN("ucb.ucp.webdav.curl", "curl_easy_init failed");
609 throw DAVException(DAVException::DAV_SESSION_CREATE,
610 ConnectionEndPointString(m_URI.GetHost(), m_URI.GetPort()));
612 m_ErrorBuffer[0] = '\0';
613 // this supposedly gives the highest quality error reporting
614 auto rc = curl_easy_setopt(m_pCurl.get(), CURLOPT_ERRORBUFFER, m_ErrorBuffer);
615 assert(rc == CURLE_OK);
616 #if 1
617 // just for debugging...
618 rc = curl_easy_setopt(m_pCurl.get(), CURLOPT_DEBUGFUNCTION, debug_callback);
619 assert(rc == CURLE_OK);
620 #endif
621 rc = curl_easy_setopt(m_pCurl.get(), CURLOPT_VERBOSE, 1L);
622 assert(rc == CURLE_OK);
623 // accept any encoding supported by libcurl
624 rc = curl_easy_setopt(m_pCurl.get(), CURLOPT_ACCEPT_ENCODING, "");
625 if (rc != CURLE_OK)
627 SAL_WARN("ucb.ucp.webdav.curl", "CURLOPT_ACCEPT_ENCODING failed: " << GetErrorString(rc));
628 throw DAVException(DAVException::DAV_SESSION_CREATE,
629 ConnectionEndPointString(m_URI.GetHost(), m_URI.GetPort()));
631 auto const connectTimeout(officecfg::Inet::Settings::ConnectTimeout::get());
632 // default is 300s
633 rc = curl_easy_setopt(m_pCurl.get(), CURLOPT_CONNECTTIMEOUT,
634 ::std::max<long>(2L, ::std::min<long>(connectTimeout, 180L)));
635 if (rc != CURLE_OK)
637 SAL_WARN("ucb.ucp.webdav.curl", "CURLOPT_CONNECTTIMEOUT failed: " << GetErrorString(rc));
638 throw DAVException(DAVException::DAV_SESSION_CREATE,
639 ConnectionEndPointString(m_URI.GetHost(), m_URI.GetPort()));
641 auto const readTimeout(officecfg::Inet::Settings::ReadTimeout::get());
642 m_nReadTimeout = ::std::max<int>(20, ::std::min<long>(readTimeout, 180)) * 1000;
643 // default is infinite
644 rc = curl_easy_setopt(m_pCurl.get(), CURLOPT_TIMEOUT, 300L);
645 if (rc != CURLE_OK)
647 SAL_WARN("ucb.ucp.webdav.curl", "CURLOPT_TIMEOUT failed: " << GetErrorString(rc));
648 throw DAVException(DAVException::DAV_SESSION_CREATE,
649 ConnectionEndPointString(m_URI.GetHost(), m_URI.GetPort()));
651 rc = curl_easy_setopt(m_pCurl.get(), CURLOPT_WRITEFUNCTION, &write_callback);
652 assert(rc == CURLE_OK);
653 rc = curl_easy_setopt(m_pCurl.get(), CURLOPT_READFUNCTION, &read_callback);
654 assert(rc == CURLE_OK);
655 rc = curl_easy_setopt(m_pCurl.get(), CURLOPT_HEADERFUNCTION, &header_callback);
656 assert(rc == CURLE_OK);
657 ::InitCurl_easy(m_pCurl.get());
658 if (officecfg::Office::Security::Net::AllowInsecureProtocols::get())
660 // tdf#149921 by default, with schannel (WNT) connection fails if revocation
661 // lists cannot be checked; try to limit the checking to when revocation
662 // lists can actually be retrieved (usually not the case for self-signed CA)
663 #if CURL_AT_LEAST_VERSION(7, 70, 0)
664 rc = curl_easy_setopt(m_pCurl.get(), CURLOPT_SSL_OPTIONS, CURLSSLOPT_REVOKE_BEST_EFFORT);
665 assert(rc == CURLE_OK);
666 rc = curl_easy_setopt(m_pCurl.get(), CURLOPT_PROXY_SSL_OPTIONS,
667 CURLSSLOPT_REVOKE_BEST_EFFORT);
668 assert(rc == CURLE_OK);
669 #endif
671 // set this initially, may be overwritten during authentication
672 rc = curl_easy_setopt(m_pCurl.get(), CURLOPT_HTTPAUTH, CURLAUTH_ANY);
673 assert(rc == CURLE_OK); // ANY is always available
674 // always set CURLOPT_PROXY to suppress proxy detection in libcurl
675 OString const utf8Proxy(OUStringToOString(m_Proxy, RTL_TEXTENCODING_UTF8));
676 rc = curl_easy_setopt(m_pCurl.get(), CURLOPT_PROXY, utf8Proxy.getStr());
677 if (rc != CURLE_OK)
679 SAL_WARN("ucb.ucp.webdav.curl", "CURLOPT_PROXY failed: " << GetErrorString(rc));
680 throw DAVException(DAVException::DAV_SESSION_CREATE, m_Proxy);
682 if (!m_Proxy.isEmpty())
684 // set this initially, may be overwritten during authentication
685 rc = curl_easy_setopt(m_pCurl.get(), CURLOPT_PROXYAUTH, CURLAUTH_ANY);
686 assert(rc == CURLE_OK); // ANY is always available
688 auto const it(::std::find_if(m_Flags.begin(), m_Flags.end(),
689 [](auto const& rFlag) { return rFlag.Name == "KeepAlive"; }));
690 if (it != m_Flags.end() && it->Value.get<bool>())
692 // neon would close the connection from ne_end_request(), this seems
693 // to be the equivalent and not CURLOPT_TCP_KEEPALIVE
694 rc = curl_easy_setopt(m_pCurl.get(), CURLOPT_FORBID_REUSE, 1L);
695 assert(rc == CURLE_OK);
699 CurlSession::~CurlSession() {}
701 auto CurlSession::CanUse(OUString const& rURI, uno::Sequence<beans::NamedValue> const& rFlags)
702 -> bool
706 CurlUri const uri(rURI);
708 return m_URI.GetScheme() == uri.GetScheme() && m_URI.GetHost() == uri.GetHost()
709 && m_URI.GetPort() == uri.GetPort() && m_Flags == rFlags;
711 catch (DAVException const&)
713 return false;
717 auto CurlSession::UsesProxy() -> bool
719 assert(m_URI.GetScheme() == "http" || m_URI.GetScheme() == "https");
720 return !m_Proxy.isEmpty();
723 auto CurlSession::abort() -> void
725 // note: abort() was a no-op since OOo 3.2 and before that it crashed.
726 bool expected(false);
727 // it would be pointless to lock m_Mutex here as the other thread holds it
728 if (m_AbortFlag.compare_exchange_strong(expected, true))
730 // This function looks safe to call without m_Mutex as long as the
731 // m_pCurlMulti handle is not destroyed, and the caller must own a ref
732 // to this object which keeps it alive; it should cause poll to return.
733 curl_multi_wakeup(m_pCurlMulti.get());
737 /// this is just a bunch of static member functions called from CurlSession
738 struct CurlProcessor
740 static auto URIReferenceToURI(CurlSession& rSession, std::u16string_view rURIReference)
741 -> CurlUri;
743 static auto ProcessRequestImpl(
744 CurlSession& rSession, CurlUri const& rURI, OUString const& rMethod,
745 curl_slist* pRequestHeaderList, uno::Reference<io::XOutputStream> const* pxOutStream,
746 uno::Sequence<sal_Int8> const* pInData,
747 ::std::pair<::std::vector<OUString> const&, DAVResource&> const* pRequestedHeaders,
748 ResponseHeaders& rHeaders) -> void;
750 static auto ProcessRequest(
751 CurlSession& rSession, CurlUri const& rURI, OUString const& rMethod,
752 ::std::vector<CurlOption> const& rOptions, DAVRequestEnvironment const* pEnv,
753 ::std::unique_ptr<curl_slist, deleter_from_fn<curl_slist, curl_slist_free_all>>
754 pRequestHeaderList,
755 uno::Reference<io::XOutputStream> const* pxOutStream,
756 uno::Reference<io::XInputStream> const* pxInStream,
757 ::std::pair<::std::vector<OUString> const&, DAVResource&> const* pRequestedHeaders) -> void;
759 static auto
760 PropFind(CurlSession& rSession, CurlUri const& rURI, Depth depth,
761 ::std::tuple<::std::vector<OUString> const&, ::std::vector<DAVResource>* const,
762 ::std::vector<ucb::Lock>* const> const* o_pRequestedProperties,
763 ::std::vector<DAVResourceInfo>* const o_pResourceInfos,
764 DAVRequestEnvironment const& rEnv) -> void;
766 static auto MoveOrCopy(CurlSession& rSession, std::u16string_view rSourceURIReference,
767 ::std::u16string_view rDestinationURI, DAVRequestEnvironment const& rEnv,
768 bool isOverwrite, char const* pMethod) -> void;
770 static auto Lock(CurlSession& rSession, CurlUri const& rURI, DAVRequestEnvironment const* pEnv,
771 ::std::unique_ptr<curl_slist, deleter_from_fn<curl_slist, curl_slist_free_all>>
772 pRequestHeaderList,
773 uno::Reference<io::XInputStream> const* pxInStream)
774 -> ::std::vector<::std::pair<ucb::Lock, sal_Int32>>;
776 static auto Unlock(CurlSession& rSession, CurlUri const& rURI,
777 DAVRequestEnvironment const* pEnv) -> void;
780 auto CurlProcessor::URIReferenceToURI(CurlSession& rSession, std::u16string_view rURIReference)
781 -> CurlUri
783 // No need to acquire rSession.m_Mutex because accessed members are const.
784 if (rSession.UsesProxy())
785 // very odd, but see DAVResourceAccess::getRequestURI() :-/
787 assert(o3tl::starts_with(rURIReference, u"http://")
788 || o3tl::starts_with(rURIReference, u"https://"));
789 return CurlUri(rURIReference);
791 else
793 assert(o3tl::starts_with(rURIReference, u"/"));
794 return rSession.m_URI.CloneWithRelativeRefPathAbsolute(rURIReference);
798 /// main function to initiate libcurl requests
799 auto CurlProcessor::ProcessRequestImpl(
800 CurlSession& rSession, CurlUri const& rURI, OUString const& rMethod,
801 curl_slist* const pRequestHeaderList,
802 uno::Reference<io::XOutputStream> const* const pxOutStream,
803 uno::Sequence<sal_Int8> const* const pInData,
804 ::std::pair<::std::vector<OUString> const&, DAVResource&> const* const pRequestedHeaders,
805 ResponseHeaders& rHeaders) -> void
807 ::comphelper::ScopeGuard const g([&]() {
808 auto rc = curl_easy_setopt(rSession.m_pCurl.get(), CURLOPT_HEADERDATA, nullptr);
809 assert(rc == CURLE_OK);
810 (void)rc;
811 if (pxOutStream)
813 rc = curl_easy_setopt(rSession.m_pCurl.get(), CURLOPT_WRITEDATA, nullptr);
814 assert(rc == CURLE_OK);
816 if (pInData)
818 rc = curl_easy_setopt(rSession.m_pCurl.get(), CURLOPT_READDATA, nullptr);
819 assert(rc == CURLE_OK);
820 rc = curl_easy_setopt(rSession.m_pCurl.get(), CURLOPT_UPLOAD, 0L);
821 assert(rc == CURLE_OK);
823 if (pRequestHeaderList)
825 rc = curl_easy_setopt(rSession.m_pCurl.get(), CURLOPT_HTTPHEADER, nullptr);
826 assert(rc == CURLE_OK);
830 if (pRequestHeaderList)
832 auto rc = curl_easy_setopt(rSession.m_pCurl.get(), CURLOPT_HTTPHEADER, pRequestHeaderList);
833 assert(rc == CURLE_OK);
834 (void)rc;
837 auto rc = curl_easy_setopt(rSession.m_pCurl.get(), CURLOPT_CURLU, rURI.GetCURLU());
838 assert(rc == CURLE_OK); // can't fail since 7.63.0
840 rc = curl_easy_setopt(rSession.m_pCurl.get(), CURLOPT_HEADERDATA, &rHeaders);
841 assert(rc == CURLE_OK);
842 ::std::optional<DownloadTarget> oDownloadTarget;
843 if (pxOutStream)
845 oDownloadTarget.emplace(*pxOutStream, rHeaders);
846 rc = curl_easy_setopt(rSession.m_pCurl.get(), CURLOPT_WRITEDATA, &*oDownloadTarget);
847 assert(rc == CURLE_OK);
849 ::std::optional<UploadSource> oUploadSource;
850 if (pInData)
852 oUploadSource.emplace(*pInData);
853 rc = curl_easy_setopt(rSession.m_pCurl.get(), CURLOPT_READDATA, &*oUploadSource);
854 assert(rc == CURLE_OK);
856 rSession.m_ErrorBuffer[0] = '\0';
858 // note: easy handle must be added for *every* transfer!
859 // otherwise it gets stuck in MSTATE_MSGSENT forever after 1st transfer
860 auto mc = curl_multi_add_handle(rSession.m_pCurlMulti.get(), rSession.m_pCurl.get());
861 if (mc != CURLM_OK)
863 SAL_WARN("ucb.ucp.webdav.curl",
864 "curl_multi_add_handle failed: " << GetErrorStringMulti(mc));
865 throw DAVException(
866 DAVException::DAV_SESSION_CREATE,
867 ConnectionEndPointString(rSession.m_URI.GetHost(), rSession.m_URI.GetPort()));
869 ::comphelper::ScopeGuard const gg([&]() {
870 mc = curl_multi_remove_handle(rSession.m_pCurlMulti.get(), rSession.m_pCurl.get());
871 if (mc != CURLM_OK)
873 SAL_WARN("ucb.ucp.webdav.curl",
874 "curl_multi_remove_handle failed: " << GetErrorStringMulti(mc));
878 // this is where libcurl actually does something
879 rc = CURL_LAST; // clear current value
880 int nRunning;
883 mc = curl_multi_perform(rSession.m_pCurlMulti.get(), &nRunning);
884 if (mc != CURLM_OK)
886 SAL_WARN("ucb.ucp.webdav.curl",
887 "curl_multi_perform failed: " << GetErrorStringMulti(mc));
888 throw DAVException(
889 DAVException::DAV_HTTP_CONNECT,
890 ConnectionEndPointString(rSession.m_URI.GetHost(), rSession.m_URI.GetPort()));
892 if (nRunning == 0)
893 { // short request like HEAD on loopback could be done in first call
894 break;
896 int nFDs;
897 mc = curl_multi_poll(rSession.m_pCurlMulti.get(), nullptr, 0, rSession.m_nReadTimeout,
898 &nFDs);
899 if (mc != CURLM_OK)
901 SAL_WARN("ucb.ucp.webdav.curl", "curl_multi_poll failed: " << GetErrorStringMulti(mc));
902 throw DAVException(
903 DAVException::DAV_HTTP_CONNECT,
904 ConnectionEndPointString(rSession.m_URI.GetHost(), rSession.m_URI.GetPort()));
906 if (rSession.m_AbortFlag.load())
907 { // flag was set by abort() -> not sure what exception to throw?
908 throw DAVException(DAVException::DAV_HTTP_ERROR, "abort() was called", 0);
910 } while (nRunning != 0);
911 // there should be exactly 1 CURLMsg now, but the interface is
912 // extensible so future libcurl versions could yield additional things
915 CURLMsg const* const pMsg = curl_multi_info_read(rSession.m_pCurlMulti.get(), &nRunning);
916 if (pMsg && pMsg->msg == CURLMSG_DONE)
918 assert(pMsg->easy_handle == rSession.m_pCurl.get());
919 rc = pMsg->data.result;
921 else
923 SAL_WARN("ucb.ucp.webdav.curl", "curl_multi_info_read unexpected result");
925 } while (nRunning != 0);
927 // error handling part 1: libcurl errors
928 if (rc != CURLE_OK)
930 // TODO: is there any value in extracting CURLINFO_OS_ERRNO
931 SAL_WARN("ucb.ucp.webdav.curl",
932 "curl_easy_perform failed: " << GetErrorString(rc, rSession.m_ErrorBuffer));
933 switch (rc)
935 case CURLE_UNSUPPORTED_PROTOCOL:
936 throw DAVException(DAVException::DAV_UNSUPPORTED);
937 case CURLE_COULDNT_RESOLVE_PROXY:
938 throw DAVException(DAVException::DAV_HTTP_LOOKUP, rSession.m_Proxy);
939 case CURLE_COULDNT_RESOLVE_HOST:
940 throw DAVException(
941 DAVException::DAV_HTTP_LOOKUP,
942 ConnectionEndPointString(rSession.m_URI.GetHost(), rSession.m_URI.GetPort()));
943 case CURLE_COULDNT_CONNECT:
944 case CURLE_SSL_CONNECT_ERROR:
945 case CURLE_SSL_CERTPROBLEM:
946 case CURLE_SSL_CIPHER:
947 case CURLE_PEER_FAILED_VERIFICATION:
948 case CURLE_SSL_ISSUER_ERROR:
949 case CURLE_SSL_PINNEDPUBKEYNOTMATCH:
950 case CURLE_SSL_INVALIDCERTSTATUS:
951 case CURLE_FAILED_INIT:
952 #if CURL_AT_LEAST_VERSION(7, 69, 0)
953 case CURLE_QUIC_CONNECT_ERROR:
954 #endif
955 throw DAVException(
956 DAVException::DAV_HTTP_CONNECT,
957 ConnectionEndPointString(rSession.m_URI.GetHost(), rSession.m_URI.GetPort()));
958 case CURLE_REMOTE_ACCESS_DENIED:
959 case CURLE_LOGIN_DENIED:
960 case CURLE_AUTH_ERROR:
961 throw DAVException(
962 DAVException::DAV_HTTP_AUTH, // probably?
963 ConnectionEndPointString(rSession.m_URI.GetHost(), rSession.m_URI.GetPort()));
964 case CURLE_WRITE_ERROR:
965 case CURLE_READ_ERROR: // error returned from our callbacks
966 case CURLE_OUT_OF_MEMORY:
967 case CURLE_ABORTED_BY_CALLBACK:
968 case CURLE_BAD_FUNCTION_ARGUMENT:
969 case CURLE_SEND_ERROR:
970 case CURLE_RECV_ERROR:
971 case CURLE_SSL_CACERT_BADFILE:
972 case CURLE_SSL_CRL_BADFILE:
973 case CURLE_RECURSIVE_API_CALL:
974 throw DAVException(
975 DAVException::DAV_HTTP_FAILED,
976 ConnectionEndPointString(rSession.m_URI.GetHost(), rSession.m_URI.GetPort()));
977 case CURLE_OPERATION_TIMEDOUT:
978 throw DAVException(
979 DAVException::DAV_HTTP_TIMEOUT,
980 ConnectionEndPointString(rSession.m_URI.GetHost(), rSession.m_URI.GetPort()));
981 default: // lots of generic errors
982 throw DAVException(DAVException::DAV_HTTP_ERROR, "", 0);
985 // error handling part 2: HTTP status codes
986 long statusCode(SC_NONE);
987 rc = curl_easy_getinfo(rSession.m_pCurl.get(), CURLINFO_RESPONSE_CODE, &statusCode);
988 assert(rc == CURLE_OK);
989 assert(statusCode != SC_NONE); // ??? should be error returned from perform?
990 SAL_INFO("ucb.ucp.webdav.curl", "HTTP status code: " << statusCode);
991 if (statusCode < 300)
993 // neon did this regardless of status or even error, which seems odd
994 ExtractRequestedHeaders(rHeaders, pRequestedHeaders);
996 else
998 // create message containing the HTTP method and response status line
999 OUString statusLine("\n" + rMethod + "\n=>\n");
1000 if (!rHeaders.HeaderFields.empty() && !rHeaders.HeaderFields.back().first.empty()
1001 && rHeaders.HeaderFields.back().first.front().startsWith("HTTP"))
1003 statusLine += ::rtl::OStringToOUString(
1004 ::o3tl::trim(rHeaders.HeaderFields.back().first.front()),
1005 RTL_TEXTENCODING_ASCII_US);
1007 switch (statusCode)
1009 case SC_REQUEST_TIMEOUT:
1011 throw DAVException(
1012 DAVException::DAV_HTTP_TIMEOUT,
1013 ConnectionEndPointString(rSession.m_URI.GetHost(), rSession.m_URI.GetPort()));
1014 break;
1016 case SC_MOVED_PERMANENTLY:
1017 case SC_MOVED_TEMPORARILY:
1018 case SC_SEE_OTHER:
1019 case SC_TEMPORARY_REDIRECT:
1021 // could also use CURLOPT_FOLLOWLOCATION but apparently the
1022 // upper layer wants to know about redirects?
1023 char* pRedirectURL(nullptr);
1024 rc = curl_easy_getinfo(rSession.m_pCurl.get(), CURLINFO_REDIRECT_URL,
1025 &pRedirectURL);
1026 assert(rc == CURLE_OK);
1027 if (pRedirectURL)
1029 // Sharepoint 2016 workaround: contains unencoded U+0020
1030 OUString const redirectURL(::rtl::Uri::encode(
1031 pRedirectURL
1032 ? OUString(pRedirectURL, strlen(pRedirectURL), RTL_TEXTENCODING_UTF8)
1033 : OUString(),
1034 rtl_UriCharClassUric, rtl_UriEncodeKeepEscapes, RTL_TEXTENCODING_UTF8));
1036 throw DAVException(DAVException::DAV_HTTP_REDIRECT, redirectURL);
1038 [[fallthrough]];
1040 default:
1041 throw DAVException(DAVException::DAV_HTTP_ERROR, statusLine, statusCode);
1045 if (pxOutStream)
1047 (*pxOutStream)->closeOutput(); // signal EOF
1051 static auto TryRemoveExpiredLockToken(CurlSession& rSession, CurlUri const& rURI,
1052 DAVRequestEnvironment const* const pEnv) -> bool
1054 if (!pEnv)
1056 // caller was a NonInteractive_*LOCK function anyway, its caller is LockStore
1057 return false;
1059 OUString const* const pToken(g_Init.LockStore.getLockTokenForURI(rURI.GetURI(), nullptr));
1060 if (!pToken)
1062 return false;
1066 // determine validity of existing lock via lockdiscovery request
1067 ::std::vector<OUString> const propertyNames{ DAVProperties::LOCKDISCOVERY };
1068 ::std::vector<ucb::Lock> locks;
1069 ::std::tuple<::std::vector<OUString> const&, ::std::vector<DAVResource>* const,
1070 ::std::vector<ucb::Lock>* const> const args(propertyNames, nullptr, &locks);
1072 CurlProcessor::PropFind(rSession, rURI, DAVZERO, &args, nullptr, *pEnv);
1074 // https://datatracker.ietf.org/doc/html/rfc4918#section-15.8
1075 // The response MAY not contain tokens, but hopefully it
1076 // will if client is properly authenticated.
1077 if (::std::any_of(locks.begin(), locks.end(), [pToken](ucb::Lock const& rLock) {
1078 return ::std::any_of(
1079 rLock.LockTokens.begin(), rLock.LockTokens.end(),
1080 [pToken](OUString const& rToken) { return *pToken == rToken; });
1083 return false; // still have the lock
1086 SAL_INFO("ucb.ucp.webdav.curl",
1087 "lock token expired, removing: " << rURI.GetURI() << " " << *pToken);
1088 g_Init.LockStore.removeLock(rURI.GetURI());
1089 return true;
1091 catch (DAVException const&)
1093 return false; // ignore, the caller already has a better exception
1097 auto CurlProcessor::ProcessRequest(
1098 CurlSession& rSession, CurlUri const& rURI, OUString const& rMethod,
1099 ::std::vector<CurlOption> const& rOptions, DAVRequestEnvironment const* const pEnv,
1100 ::std::unique_ptr<curl_slist, deleter_from_fn<curl_slist, curl_slist_free_all>>
1101 pRequestHeaderList,
1102 uno::Reference<io::XOutputStream> const* const pxOutStream,
1103 uno::Reference<io::XInputStream> const* const pxInStream,
1104 ::std::pair<::std::vector<OUString> const&, DAVResource&> const* const pRequestedHeaders)
1105 -> void
1107 if (HostFilter::isForbidden(rURI.GetHost()))
1109 SAL_WARN("ucb.ucp.webdav.curl", "Access denied to host: " << rURI.GetHost());
1110 throw uno::RuntimeException("access to host denied");
1113 if (pEnv)
1114 { // add custom request headers passed by caller
1115 for (auto const& rHeader : pEnv->m_aRequestHeaders)
1117 OString const utf8Header(
1118 OUStringToOString(rHeader.first, RTL_TEXTENCODING_ASCII_US) + ": "
1119 + OUStringToOString(rHeader.second, RTL_TEXTENCODING_ASCII_US));
1120 pRequestHeaderList.reset(
1121 curl_slist_append(pRequestHeaderList.release(), utf8Header.getStr()));
1122 if (!pRequestHeaderList)
1124 throw uno::RuntimeException("curl_slist_append failed");
1129 uno::Sequence<sal_Int8> data;
1130 if (pxInStream)
1132 uno::Reference<io::XSeekable> const xSeekable(*pxInStream, uno::UNO_QUERY);
1133 if (xSeekable.is())
1135 auto const len(xSeekable->getLength() - xSeekable->getPosition());
1136 if ((**pxInStream).readBytes(data, len) != len)
1138 throw uno::RuntimeException("short readBytes");
1141 else
1143 ::std::vector<uno::Sequence<sal_Int8>> bufs;
1144 bool isDone(false);
1147 bufs.emplace_back();
1148 isDone = (**pxInStream).readSomeBytes(bufs.back(), 65536) == 0;
1149 } while (!isDone);
1150 sal_Int32 nSize(0);
1151 for (auto const& rBuf : bufs)
1153 if (o3tl::checked_add(nSize, rBuf.getLength(), nSize))
1155 throw std::bad_alloc(); // too large for Sequence
1158 data.realloc(nSize);
1159 size_t nCopied(0);
1160 for (auto const& rBuf : bufs)
1162 ::std::memcpy(data.getArray() + nCopied, rBuf.getConstArray(), rBuf.getLength());
1163 nCopied += rBuf.getLength(); // can't overflow
1168 // Clear flag before transfer starts; only a transfer started before
1169 // calling abort() will be aborted, not one started later.
1170 rSession.m_AbortFlag.store(false);
1172 Guard guard(rSession.m_Mutex, rOptions, rURI, rSession.m_pCurl.get());
1174 // authentication data may be in the URI, or requested via XInteractionHandler
1175 struct Auth
1177 OUString UserName;
1178 OUString PassWord;
1179 decltype(CURLAUTH_ANY) AuthMask; ///< allowed auth methods
1180 Auth(OUString aUserName, OUString aPassword, decltype(CURLAUTH_ANY) const & rAuthMask)
1181 : UserName(std::move(aUserName))
1182 , PassWord(std::move(aPassword))
1183 , AuthMask(rAuthMask)
1187 ::std::optional<Auth> oAuth;
1188 ::std::optional<Auth> oAuthProxy;
1189 if (pEnv && !rSession.m_isAuthenticatedProxy && !rSession.m_Proxy.isEmpty())
1193 // the hope is that this must be a URI
1194 CurlUri const uri(rSession.m_Proxy);
1195 if (!uri.GetUser().isEmpty() || !uri.GetPassword().isEmpty())
1197 oAuthProxy.emplace(uri.GetUser(), uri.GetPassword(), CURLAUTH_ANY);
1200 catch (DAVException&)
1202 // ignore any parsing failure here
1205 decltype(CURLAUTH_ANY) const authSystem(CURLAUTH_NEGOTIATE | CURLAUTH_NTLM | CURLAUTH_NTLM_WB);
1206 if (pRequestedHeaders || (pEnv && !rSession.m_isAuthenticated))
1208 // m_aRequestURI *may* be a path or *may* be URI - wtf
1209 // TODO: why is there this m_aRequestURI and also rURIReference argument?
1210 // ... only caller is DAVResourceAccess - always identical except MOVE/COPY
1211 // which doesn't work if it's just a URI reference so let's just use
1212 // rURIReference via rURI instead
1213 #if 0
1214 CurlUri const uri(pEnv->m_aRequestURI);
1215 #endif
1216 // note: due to parsing bug pwd didn't work in previous webdav ucps
1217 if (pEnv && !rSession.m_isAuthenticated
1218 && (!rURI.GetUser().isEmpty() || !rURI.GetPassword().isEmpty()))
1220 oAuth.emplace(rURI.GetUser(), rURI.GetPassword(), CURLAUTH_ANY);
1222 if (pRequestedHeaders)
1224 // note: Previously this would be the rURIReference directly but
1225 // that ends up in CurlUri anyway and curl is unhappy.
1226 // But it looks like all consumers of this .uri are interested
1227 // only in the path, so it shouldn't make a difference to give
1228 // the entire URI when the caller extracts the path anyway.
1229 pRequestedHeaders->second.uri = rURI.GetURI();
1230 pRequestedHeaders->second.properties.clear();
1233 bool isRetry(false);
1234 bool isFallbackHTTP10(false);
1235 int nAuthRequests(0);
1236 int nAuthRequestsProxy(0);
1238 // libcurl does not have an authentication callback so handle auth
1239 // related status codes and requesting credentials via this loop
1242 isRetry = false;
1244 // re-check m_isAuthenticated flags every time, could have been set
1245 // by re-entrant call
1246 if (oAuth && !rSession.m_isAuthenticated)
1248 OString const utf8UserName(OUStringToOString(oAuth->UserName, RTL_TEXTENCODING_UTF8));
1249 auto rc
1250 = curl_easy_setopt(rSession.m_pCurl.get(), CURLOPT_USERNAME, utf8UserName.getStr());
1251 if (rc != CURLE_OK)
1253 SAL_WARN("ucb.ucp.webdav.curl", "CURLOPT_USERNAME failed: " << GetErrorString(rc));
1254 throw DAVException(DAVException::DAV_INVALID_ARG);
1256 OString const utf8PassWord(OUStringToOString(oAuth->PassWord, RTL_TEXTENCODING_UTF8));
1257 rc = curl_easy_setopt(rSession.m_pCurl.get(), CURLOPT_PASSWORD, utf8PassWord.getStr());
1258 if (rc != CURLE_OK)
1260 SAL_WARN("ucb.ucp.webdav.curl", "CURLOPT_PASSWORD failed: " << GetErrorString(rc));
1261 throw DAVException(DAVException::DAV_INVALID_ARG);
1263 rc = curl_easy_setopt(rSession.m_pCurl.get(), CURLOPT_HTTPAUTH, oAuth->AuthMask);
1264 assert(
1266 == CURLE_OK); // it shouldn't be possible to reduce auth to 0 via the authSystem masks
1269 if (oAuthProxy && !rSession.m_isAuthenticatedProxy)
1271 OString const utf8UserName(
1272 OUStringToOString(oAuthProxy->UserName, RTL_TEXTENCODING_UTF8));
1273 auto rc = curl_easy_setopt(rSession.m_pCurl.get(), CURLOPT_PROXYUSERNAME,
1274 utf8UserName.getStr());
1275 if (rc != CURLE_OK)
1277 SAL_WARN("ucb.ucp.webdav.curl",
1278 "CURLOPT_PROXYUSERNAME failed: " << GetErrorString(rc));
1279 throw DAVException(DAVException::DAV_INVALID_ARG);
1281 OString const utf8PassWord(
1282 OUStringToOString(oAuthProxy->PassWord, RTL_TEXTENCODING_UTF8));
1283 rc = curl_easy_setopt(rSession.m_pCurl.get(), CURLOPT_PROXYPASSWORD,
1284 utf8PassWord.getStr());
1285 if (rc != CURLE_OK)
1287 SAL_WARN("ucb.ucp.webdav.curl",
1288 "CURLOPT_PROXYPASSWORD failed: " << GetErrorString(rc));
1289 throw DAVException(DAVException::DAV_INVALID_ARG);
1291 rc = curl_easy_setopt(rSession.m_pCurl.get(), CURLOPT_PROXYAUTH, oAuthProxy->AuthMask);
1292 assert(
1294 == CURLE_OK); // it shouldn't be possible to reduce auth to 0 via the authSystem masks
1297 ResponseHeaders headers(rSession.m_pCurl.get());
1298 // always pass a stream for debug logging, buffer the result body
1299 uno::Reference<io::XSequenceOutputStream> const xSeqOutStream(
1300 io::SequenceOutputStream::create(rSession.m_xContext));
1301 uno::Reference<io::XOutputStream> const xTempOutStream(xSeqOutStream);
1302 assert(xTempOutStream.is());
1306 ProcessRequestImpl(rSession, rURI, rMethod, pRequestHeaderList.get(), &xTempOutStream,
1307 pxInStream ? &data : nullptr, pRequestedHeaders, headers);
1308 if (pxOutStream)
1309 { // only copy to result stream if transfer was successful
1310 (*pxOutStream)->writeBytes(xSeqOutStream->getWrittenBytes());
1311 (*pxOutStream)->closeOutput(); // signal EOF
1314 catch (DAVException const& rException)
1316 // log start of request body if there was any
1317 auto const bytes(xSeqOutStream->getWrittenBytes());
1318 auto const len(::std::min<sal_Int32>(bytes.getLength(), 10000));
1319 SAL_INFO("ucb.ucp.webdav.curl",
1320 "DAVException; (first) " << len << " bytes of data received:");
1321 if (0 < len)
1323 OStringBuffer buf(len);
1324 for (sal_Int32 i = 0; i < len; ++i)
1326 if (bytes[i] < 0x20) // also if negative
1328 static char const hexDigit[16] = { '0', '1', '2', '3', '4', '5', '6', '7',
1329 '8', '9', 'A', 'B', 'C', 'D', 'E', 'F' };
1330 buf.append(OString::Concat("\\x")
1331 + OStringChar(hexDigit[static_cast<sal_uInt8>(bytes[i]) >> 4])
1332 + OStringChar(hexDigit[bytes[i] & 0x0F]));
1334 else
1336 buf.append(static_cast<char>(bytes[i]));
1339 SAL_INFO("ucb.ucp.webdav.curl", buf.makeStringAndClear());
1342 // error handling part 3: special HTTP status codes
1343 // that require unlocking m_Mutex to handle
1344 if (rException.getError() == DAVException::DAV_HTTP_ERROR)
1346 auto const statusCode(rException.getStatus());
1347 switch (statusCode)
1349 case SC_LOCKED:
1351 guard.Release(); // release m_Mutex before accessing LockStore
1352 if (g_Init.LockStore.getLockTokenForURI(rURI.GetURI(), nullptr))
1354 throw DAVException(DAVException::DAV_LOCKED_SELF);
1356 else // locked by third party
1358 throw DAVException(DAVException::DAV_LOCKED);
1360 break;
1362 case SC_PRECONDITION_FAILED:
1363 case SC_BAD_REQUEST:
1365 guard.Release(); // release m_Mutex before accessing LockStore
1366 // Not obvious but apparently these codes may indicate
1367 // the expiration of a lock.
1368 // Initiate a new request *outside* ProcessRequestImpl
1369 // *after* rGuard.unlock() to avoid messing up m_pCurl state.
1370 if (TryRemoveExpiredLockToken(rSession, rURI, pEnv))
1372 throw DAVException(DAVException::DAV_LOCK_EXPIRED);
1374 break;
1376 case SC_UNAUTHORIZED:
1377 case SC_PROXY_AUTHENTICATION_REQUIRED:
1379 (statusCode != SC_PROXY_AUTHENTICATION_REQUIRED
1380 ? rSession.m_isAuthenticated
1381 : rSession.m_isAuthenticatedProxy)
1382 = false; // any auth data in m_pCurl is invalid
1383 auto& rnAuthRequests(statusCode == SC_UNAUTHORIZED ? nAuthRequests
1384 : nAuthRequestsProxy);
1385 if (rnAuthRequests == 10)
1387 SAL_INFO("ucb.ucp.webdav.curl", "aborting authentication after "
1388 << rnAuthRequests << " attempts");
1390 else if (pEnv && pEnv->m_xAuthListener)
1392 ::std::optional<OUString> const oRealm(ExtractRealm(
1393 headers, statusCode == SC_UNAUTHORIZED ? "WWW-Authenticate"
1394 : "Proxy-Authenticate"));
1396 ::std::optional<Auth>& roAuth(
1397 statusCode == SC_UNAUTHORIZED ? oAuth : oAuthProxy);
1398 OUString userName(roAuth ? roAuth->UserName : OUString());
1399 OUString passWord(roAuth ? roAuth->PassWord : OUString());
1400 long authAvail(0);
1401 auto const rc = curl_easy_getinfo(rSession.m_pCurl.get(),
1402 statusCode == SC_UNAUTHORIZED
1403 ? CURLINFO_HTTPAUTH_AVAIL
1404 : CURLINFO_PROXYAUTH_AVAIL,
1405 &authAvail);
1406 assert(rc == CURLE_OK);
1407 (void)rc;
1408 // only allow SystemCredentials once - the
1409 // PasswordContainer may have stored it in the
1410 // Config (TrySystemCredentialsFirst or
1411 // AuthenticateUsingSystemCredentials) and then it
1412 // will always force its use no matter how hopeless
1413 bool const isSystemCredSupported((authAvail & authSystem) != 0
1414 && rnAuthRequests == 0);
1415 ++rnAuthRequests;
1417 // Ask user via XInteractionHandler.
1418 // Warning: This likely runs an event loop which may
1419 // end up calling back into this instance, so all
1420 // changes to m_pCurl must be undone now and
1421 // restored after return.
1422 guard.Release();
1424 auto const ret = pEnv->m_xAuthListener->authenticate(
1425 oRealm ? *oRealm : "",
1426 statusCode == SC_UNAUTHORIZED ? rSession.m_URI.GetHost()
1427 : rSession.m_Proxy,
1428 userName, passWord, isSystemCredSupported);
1430 if (ret == 0)
1432 // NTLM may either use a password requested
1433 // from the user, or from the system via SSPI
1434 // so i guess it should not be disabled here
1435 // regardless of the state of the system auth
1436 // checkbox, particularly since SSPI is only
1437 // available on WNT.
1438 // Additionally, "Negotiate" has a "legacy"
1439 // mode that is actually just NTLM according to
1440 // https://curl.se/rfc/ntlm.html#ntlmHttpAuthentication
1441 // so there's nothing in authSystem that can be
1442 // disabled here.
1443 roAuth.emplace(userName, passWord,
1444 ((userName.isEmpty() && passWord.isEmpty())
1445 ? (authAvail & authSystem)
1446 : authAvail));
1447 isRetry = true;
1448 // Acquire is only necessary in case of success.
1449 guard.Acquire();
1450 break; // break out of switch
1452 // else: throw
1454 SAL_INFO("ucb.ucp.webdav.curl", "no auth credentials provided");
1455 throw DAVException(DAVException::DAV_HTTP_NOAUTH,
1456 ConnectionEndPointString(rSession.m_URI.GetHost(),
1457 rSession.m_URI.GetPort()));
1458 break;
1462 else if (rException.getError() == DAVException::DAV_UNSUPPORTED)
1464 // tdf#152493 libcurl can't handle "Transfer-Encoding: chunked"
1465 // in HTTP/1.1 100 Continue response.
1466 // workaround: if HTTP/1.1 didn't work, try HTTP/1.0
1467 // (but fallback only once - to prevent infinite loop)
1468 if (isFallbackHTTP10)
1470 throw DAVException(DAVException::DAV_HTTP_ERROR);
1472 isFallbackHTTP10 = true;
1473 // note: this is not reset - future requests to this URI use it!
1474 auto rc = curl_easy_setopt(rSession.m_pCurl.get(), CURLOPT_HTTP_VERSION,
1475 CURL_HTTP_VERSION_1_0);
1476 if (rc != CURLE_OK)
1478 throw DAVException(DAVException::DAV_HTTP_ERROR);
1480 SAL_INFO("ucb.ucp.webdav.curl", "attempting fallback to HTTP/1.0");
1481 isRetry = true;
1483 if (!isRetry)
1485 throw; // everything else: re-throw
1488 } while (isRetry);
1490 if (oAuth)
1492 // assume this worked, leave auth data as stored in m_pCurl
1493 rSession.m_isAuthenticated = true;
1495 if (oAuthProxy)
1497 // assume this worked, leave auth data as stored in m_pCurl
1498 rSession.m_isAuthenticatedProxy = true;
1502 auto CurlSession::OPTIONS(OUString const& rURIReference,
1504 DAVOptions& rOptions, DAVRequestEnvironment const& rEnv) -> void
1506 SAL_INFO("ucb.ucp.webdav.curl", "OPTIONS: " << rURIReference);
1508 rOptions.init();
1510 CurlUri const uri(CurlProcessor::URIReferenceToURI(*this, rURIReference));
1512 ::std::vector<OUString> const headerNames{ "allow", "dav" };
1513 DAVResource result;
1514 ::std::pair<::std::vector<OUString> const&, DAVResource&> const headers(headerNames, result);
1516 ::std::vector<CurlOption> const options{ { CURLOPT_CUSTOMREQUEST, "OPTIONS",
1517 "CURLOPT_CUSTOMREQUEST" } };
1519 CurlProcessor::ProcessRequest(*this, uri, "OPTIONS", options, &rEnv, nullptr, nullptr, nullptr,
1520 &headers);
1522 for (auto const& it : result.properties)
1524 OUString value;
1525 it.Value >>= value;
1526 SAL_INFO("ucb.ucp.webdav.curl", "OPTIONS: header: " << it.Name << ": " << value);
1527 if (it.Name.equalsIgnoreAsciiCase("allow"))
1529 rOptions.setAllowedMethods(value);
1531 else if (it.Name.equalsIgnoreAsciiCase("dav"))
1533 // see <http://tools.ietf.org/html/rfc4918#section-10.1>,
1534 // <http://tools.ietf.org/html/rfc4918#section-18>,
1535 // and <http://tools.ietf.org/html/rfc7230#section-3.2>
1536 // we detect the class (1, 2 and 3), other elements (token, URL)
1537 // are not used for now
1538 auto const list(::comphelper::string::convertCommaSeparated(value));
1539 for (OUString const& v : list)
1541 if (v == "1")
1543 rOptions.setClass1();
1545 else if (v == "2")
1547 rOptions.setClass2();
1549 else if (v == "3")
1551 rOptions.setClass3();
1556 if (rOptions.isClass2() || rOptions.isClass3())
1558 if (g_Init.LockStore.getLockTokenForURI(uri.GetURI(), nullptr))
1560 rOptions.setLocked();
1565 auto CurlProcessor::PropFind(
1566 CurlSession& rSession, CurlUri const& rURI, Depth const nDepth,
1567 ::std::tuple<::std::vector<OUString> const&, ::std::vector<DAVResource>* const,
1568 ::std::vector<ucb::Lock>* const> const* const o_pRequestedProperties,
1569 ::std::vector<DAVResourceInfo>* const o_pResourceInfos, DAVRequestEnvironment const& rEnv)
1570 -> void
1572 assert((o_pRequestedProperties != nullptr) != (o_pResourceInfos != nullptr));
1573 assert((o_pRequestedProperties == nullptr)
1574 || (::std::get<1>(*o_pRequestedProperties) != nullptr)
1575 != (::std::get<2>(*o_pRequestedProperties) != nullptr));
1577 ::std::unique_ptr<curl_slist, deleter_from_fn<curl_slist, curl_slist_free_all>> pList;
1578 pList.reset(curl_slist_append(pList.release(), "Content-Type: application/xml"));
1579 if (!pList)
1581 throw uno::RuntimeException("curl_slist_append failed");
1583 OString depth;
1584 switch (nDepth)
1586 case DAVZERO:
1587 depth = "Depth: 0"_ostr;
1588 break;
1589 case DAVONE:
1590 depth = "Depth: 1"_ostr;
1591 break;
1592 case DAVINFINITY:
1593 depth = "Depth: infinity"_ostr;
1594 break;
1595 default:
1596 assert(false);
1598 pList.reset(curl_slist_append(pList.release(), depth.getStr()));
1599 if (!pList)
1601 throw uno::RuntimeException("curl_slist_append failed");
1604 uno::Reference<io::XSequenceOutputStream> const xSeqOutStream(
1605 io::SequenceOutputStream::create(rSession.m_xContext));
1606 uno::Reference<io::XOutputStream> const xRequestOutStream(xSeqOutStream);
1607 assert(xRequestOutStream.is());
1609 uno::Reference<xml::sax::XWriter> const xWriter(xml::sax::Writer::create(rSession.m_xContext));
1610 xWriter->setOutputStream(xRequestOutStream);
1611 xWriter->startDocument();
1612 rtl::Reference<::comphelper::AttributeList> const pAttrList(new ::comphelper::AttributeList);
1613 pAttrList->AddAttribute("xmlns", "DAV:");
1614 xWriter->startElement("propfind", pAttrList);
1615 if (o_pResourceInfos)
1617 xWriter->startElement("propname", nullptr);
1618 xWriter->endElement("propname");
1620 else
1622 if (::std::get<0>(*o_pRequestedProperties).empty())
1624 xWriter->startElement("allprop", nullptr);
1625 xWriter->endElement("allprop");
1627 else
1629 xWriter->startElement("prop", nullptr);
1630 for (OUString const& rName : ::std::get<0>(*o_pRequestedProperties))
1632 SerfPropName name;
1633 DAVProperties::createSerfPropName(rName, name);
1634 pAttrList->Clear();
1635 pAttrList->AddAttribute("xmlns", OUString::createFromAscii(name.nspace));
1636 xWriter->startElement(OUString::createFromAscii(name.name), pAttrList);
1637 xWriter->endElement(OUString::createFromAscii(name.name));
1639 xWriter->endElement("prop");
1642 xWriter->endElement("propfind");
1643 xWriter->endDocument();
1645 uno::Reference<io::XInputStream> const xRequestInStream(
1646 io::SequenceInputStream::createStreamFromSequence(rSession.m_xContext,
1647 xSeqOutStream->getWrittenBytes()));
1648 assert(xRequestInStream.is());
1650 curl_off_t const len(xSeqOutStream->getWrittenBytes().getLength());
1652 ::std::vector<CurlOption> const options{
1653 { CURLOPT_UPLOAD, 1L, nullptr },
1654 { CURLOPT_CUSTOMREQUEST, "PROPFIND", "CURLOPT_CUSTOMREQUEST" },
1655 // note: Sharepoint cannot handle "Transfer-Encoding: chunked"
1656 { CURLOPT_INFILESIZE_LARGE, len, nullptr, CurlOption::Type::CurlOffT }
1659 // stream for response
1660 uno::Reference<io::XInputStream> const xResponseInStream(io::Pipe::create(rSession.m_xContext));
1661 uno::Reference<io::XOutputStream> const xResponseOutStream(xResponseInStream, uno::UNO_QUERY);
1662 assert(xResponseInStream.is());
1663 assert(xResponseOutStream.is());
1665 CurlProcessor::ProcessRequest(rSession, rURI, "PROPFIND", options, &rEnv, ::std::move(pList),
1666 &xResponseOutStream, &xRequestInStream, nullptr);
1668 if (o_pResourceInfos)
1670 *o_pResourceInfos = parseWebDAVPropNameResponse(xResponseInStream);
1672 else
1674 if (::std::get<1>(*o_pRequestedProperties) != nullptr)
1676 *::std::get<1>(*o_pRequestedProperties)
1677 = parseWebDAVPropFindResponse(xResponseInStream);
1678 for (DAVResource& it : *::std::get<1>(*o_pRequestedProperties))
1680 // caller will give these uris to CurlUri so can't be relative
1681 if (it.uri.startsWith("/"))
1685 it.uri = rSession.m_URI.CloneWithRelativeRefPathAbsolute(it.uri).GetURI();
1687 catch (DAVException const&)
1689 SAL_INFO("ucb.ucp.webdav.curl",
1690 "PROPFIND: exception parsing uri " << it.uri);
1695 else
1697 *::std::get<2>(*o_pRequestedProperties) = parseWebDAVLockResponse(xResponseInStream);
1702 // DAV methods
1703 auto CurlSession::PROPFIND(OUString const& rURIReference, Depth const depth,
1704 ::std::vector<OUString> const& rPropertyNames,
1705 ::std::vector<DAVResource>& o_rResources,
1706 DAVRequestEnvironment const& rEnv) -> void
1708 SAL_INFO("ucb.ucp.webdav.curl", "PROPFIND: " << rURIReference << " " << depth);
1710 CurlUri const uri(CurlProcessor::URIReferenceToURI(*this, rURIReference));
1712 ::std::tuple<::std::vector<OUString> const&, ::std::vector<DAVResource>* const,
1713 ::std::vector<ucb::Lock>* const> const args(rPropertyNames, &o_rResources,
1714 nullptr);
1715 return CurlProcessor::PropFind(*this, uri, depth, &args, nullptr, rEnv);
1718 auto CurlSession::PROPFIND(OUString const& rURIReference, Depth const depth,
1719 ::std::vector<DAVResourceInfo>& o_rResourceInfos,
1720 DAVRequestEnvironment const& rEnv) -> void
1722 SAL_INFO("ucb.ucp.webdav.curl", "PROPFIND: " << rURIReference << " " << depth);
1724 CurlUri const uri(CurlProcessor::URIReferenceToURI(*this, rURIReference));
1726 return CurlProcessor::PropFind(*this, uri, depth, nullptr, &o_rResourceInfos, rEnv);
1729 auto CurlSession::PROPPATCH(OUString const& rURIReference,
1730 ::std::vector<ProppatchValue> const& rValues,
1731 DAVRequestEnvironment const& rEnv) -> void
1733 SAL_INFO("ucb.ucp.webdav.curl", "PROPPATCH: " << rURIReference);
1735 CurlUri const uri(CurlProcessor::URIReferenceToURI(*this, rURIReference));
1737 // TODO: either set CURLOPT_INFILESIZE_LARGE or chunked?
1738 ::std::unique_ptr<curl_slist, deleter_from_fn<curl_slist, curl_slist_free_all>> pList;
1739 pList.reset(curl_slist_append(pList.release(), "Content-Type: application/xml"));
1740 if (!pList)
1742 throw uno::RuntimeException("curl_slist_append failed");
1745 // generate XML document for PROPPATCH
1746 uno::Reference<io::XSequenceOutputStream> const xSeqOutStream(
1747 io::SequenceOutputStream::create(m_xContext));
1748 uno::Reference<io::XOutputStream> const xRequestOutStream(xSeqOutStream);
1749 assert(xRequestOutStream.is());
1750 uno::Reference<xml::sax::XWriter> const xWriter(xml::sax::Writer::create(m_xContext));
1751 xWriter->setOutputStream(xRequestOutStream);
1752 xWriter->startDocument();
1753 rtl::Reference<::comphelper::AttributeList> const pAttrList(new ::comphelper::AttributeList);
1754 pAttrList->AddAttribute("xmlns", "DAV:");
1755 xWriter->startElement("propertyupdate", pAttrList);
1756 for (ProppatchValue const& rPropValue : rValues)
1758 assert(rPropValue.operation == PROPSET || rPropValue.operation == PROPREMOVE);
1759 OUString const operation((rPropValue.operation == PROPSET) ? OUString("set")
1760 : OUString("remove"));
1761 xWriter->startElement(operation, nullptr);
1762 xWriter->startElement("prop", nullptr);
1763 SerfPropName name;
1764 DAVProperties::createSerfPropName(rPropValue.name, name);
1765 pAttrList->Clear();
1766 pAttrList->AddAttribute("xmlns", OUString::createFromAscii(name.nspace));
1767 xWriter->startElement(OUString::createFromAscii(name.name), pAttrList);
1768 if (rPropValue.operation == PROPSET)
1770 if (DAVProperties::isUCBDeadProperty(name))
1772 ::std::optional<::std::pair<OUString, OUString>> const oProp(
1773 UCBDeadPropertyValue::toXML(rPropValue.value));
1774 if (oProp)
1776 xWriter->startElement("ucbprop", nullptr);
1777 xWriter->startElement("type", nullptr);
1778 xWriter->characters(oProp->first);
1779 xWriter->endElement("type");
1780 xWriter->startElement("value", nullptr);
1781 xWriter->characters(oProp->second);
1782 xWriter->endElement("value");
1783 xWriter->endElement("ucbprop");
1786 else
1788 OUString value;
1789 rPropValue.value >>= value;
1790 xWriter->characters(value);
1793 xWriter->endElement(OUString::createFromAscii(name.name));
1794 xWriter->endElement("prop");
1795 xWriter->endElement(operation);
1797 xWriter->endElement("propertyupdate");
1798 xWriter->endDocument();
1800 uno::Reference<io::XInputStream> const xRequestInStream(
1801 io::SequenceInputStream::createStreamFromSequence(m_xContext,
1802 xSeqOutStream->getWrittenBytes()));
1803 assert(xRequestInStream.is());
1805 curl_off_t const len(xSeqOutStream->getWrittenBytes().getLength());
1807 ::std::vector<CurlOption> const options{
1808 { CURLOPT_UPLOAD, 1L, nullptr },
1809 { CURLOPT_CUSTOMREQUEST, "PROPPATCH", "CURLOPT_CUSTOMREQUEST" },
1810 // note: Sharepoint cannot handle "Transfer-Encoding: chunked"
1811 { CURLOPT_INFILESIZE_LARGE, len, nullptr, CurlOption::Type::CurlOffT }
1814 CurlProcessor::ProcessRequest(*this, uri, "PROPPATCH", options, &rEnv, ::std::move(pList),
1815 nullptr, &xRequestInStream, nullptr);
1818 auto CurlSession::HEAD(OUString const& rURIReference, ::std::vector<OUString> const& rHeaderNames,
1819 DAVResource& io_rResource, DAVRequestEnvironment const& rEnv) -> void
1821 SAL_INFO("ucb.ucp.webdav.curl", "HEAD: " << rURIReference);
1823 CurlUri const uri(CurlProcessor::URIReferenceToURI(*this, rURIReference));
1825 ::std::vector<CurlOption> const options{
1826 // NOBODY will prevent logging the response body in ProcessRequest()
1827 // exception, but omitting it here results in a long timeout until the
1828 // server closes the connection, which is worse
1829 { CURLOPT_NOBODY, 1L, nullptr },
1830 { CURLOPT_CUSTOMREQUEST, "HEAD", "CURLOPT_CUSTOMREQUEST" }
1833 ::std::pair<::std::vector<OUString> const&, DAVResource&> const headers(rHeaderNames,
1834 io_rResource);
1836 CurlProcessor::ProcessRequest(*this, uri, "HEAD", options, &rEnv, nullptr, nullptr, nullptr,
1837 &headers);
1840 auto CurlSession::GET(OUString const& rURIReference, DAVRequestEnvironment const& rEnv)
1841 -> uno::Reference<io::XInputStream>
1843 SAL_INFO("ucb.ucp.webdav.curl", "GET: " << rURIReference);
1845 CurlUri const uri(CurlProcessor::URIReferenceToURI(*this, rURIReference));
1847 // could use either com.sun.star.io.Pipe or com.sun.star.io.SequenceInputStream?
1848 // Pipe can just write into its XOuputStream, which is simpler.
1849 // Both resize exponentially, so performance should be fine.
1850 // However, Pipe doesn't implement XSeekable, which is required by filters.
1852 uno::Reference<io::XSequenceOutputStream> const xSeqOutStream(
1853 io::SequenceOutputStream::create(m_xContext));
1854 uno::Reference<io::XOutputStream> const xResponseOutStream(xSeqOutStream);
1855 assert(xResponseOutStream.is());
1857 ::std::vector<CurlOption> const options{ { CURLOPT_HTTPGET, 1L, nullptr } };
1859 CurlProcessor::ProcessRequest(*this, uri, "GET", options, &rEnv, nullptr, &xResponseOutStream,
1860 nullptr, nullptr);
1862 uno::Reference<io::XInputStream> const xResponseInStream(
1863 io::SequenceInputStream::createStreamFromSequence(m_xContext,
1864 xSeqOutStream->getWrittenBytes()));
1865 assert(xResponseInStream.is());
1867 return xResponseInStream;
1870 auto CurlSession::GET(OUString const& rURIReference, uno::Reference<io::XOutputStream>& rxOutStream,
1871 DAVRequestEnvironment const& rEnv) -> void
1873 SAL_INFO("ucb.ucp.webdav.curl", "GET: " << rURIReference);
1875 CurlUri const uri(CurlProcessor::URIReferenceToURI(*this, rURIReference));
1877 ::std::vector<CurlOption> const options{ { CURLOPT_HTTPGET, 1L, nullptr } };
1879 CurlProcessor::ProcessRequest(*this, uri, "GET", options, &rEnv, nullptr, &rxOutStream, nullptr,
1880 nullptr);
1883 auto CurlSession::GET(OUString const& rURIReference, ::std::vector<OUString> const& rHeaderNames,
1884 DAVResource& io_rResource, DAVRequestEnvironment const& rEnv)
1885 -> uno::Reference<io::XInputStream>
1887 SAL_INFO("ucb.ucp.webdav.curl", "GET: " << rURIReference);
1889 CurlUri const uri(CurlProcessor::URIReferenceToURI(*this, rURIReference));
1891 ::std::vector<CurlOption> const options{ { CURLOPT_HTTPGET, 1L, nullptr } };
1893 uno::Reference<io::XSequenceOutputStream> const xSeqOutStream(
1894 io::SequenceOutputStream::create(m_xContext));
1895 uno::Reference<io::XOutputStream> const xResponseOutStream(xSeqOutStream);
1896 assert(xResponseOutStream.is());
1898 ::std::pair<::std::vector<OUString> const&, DAVResource&> const headers(rHeaderNames,
1899 io_rResource);
1901 CurlProcessor::ProcessRequest(*this, uri, "GET", options, &rEnv, nullptr, &xResponseOutStream,
1902 nullptr, &headers);
1904 uno::Reference<io::XInputStream> const xResponseInStream(
1905 io::SequenceInputStream::createStreamFromSequence(m_xContext,
1906 xSeqOutStream->getWrittenBytes()));
1907 assert(xResponseInStream.is());
1909 return xResponseInStream;
1912 auto CurlSession::GET(OUString const& rURIReference, uno::Reference<io::XOutputStream>& rxOutStream,
1913 ::std::vector<OUString> const& rHeaderNames, DAVResource& io_rResource,
1914 DAVRequestEnvironment const& rEnv) -> void
1916 SAL_INFO("ucb.ucp.webdav.curl", "GET: " << rURIReference);
1918 CurlUri const uri(CurlProcessor::URIReferenceToURI(*this, rURIReference));
1920 ::std::vector<CurlOption> const options{ { CURLOPT_HTTPGET, 1L, nullptr } };
1922 ::std::pair<::std::vector<OUString> const&, DAVResource&> const headers(rHeaderNames,
1923 io_rResource);
1925 CurlProcessor::ProcessRequest(*this, uri, "GET", options, &rEnv, nullptr, &rxOutStream, nullptr,
1926 &headers);
1929 auto CurlSession::PUT(OUString const& rURIReference,
1930 uno::Reference<io::XInputStream> const& rxInStream,
1931 DAVRequestEnvironment const& rEnv) -> void
1933 SAL_INFO("ucb.ucp.webdav.curl", "PUT: " << rURIReference);
1935 CurlUri const uri(CurlProcessor::URIReferenceToURI(*this, rURIReference));
1937 // NextCloud silently fails with chunked encoding
1938 uno::Reference<io::XSeekable> const xSeekable(rxInStream, uno::UNO_QUERY);
1939 if (!xSeekable.is())
1941 throw uno::RuntimeException("TODO: not seekable");
1943 curl_off_t const len(xSeekable->getLength() - xSeekable->getPosition());
1945 ::std::unique_ptr<curl_slist, deleter_from_fn<curl_slist, curl_slist_free_all>> pList;
1946 OUString const* const pToken(g_Init.LockStore.getLockTokenForURI(uri.GetURI(), nullptr));
1947 if (pToken)
1949 OString const utf8If("If: "
1950 // disabled as Sharepoint 2013 workaround, it accepts only
1951 // "No-Tag-List", see fed2984281a85a5a2f308841ec810f218c75f2ab
1952 #if 0
1953 "<" + OUStringToOString(rURIReference, RTL_TEXTENCODING_ASCII_US)
1954 + "> "
1955 #endif
1956 "(<"
1957 + OUStringToOString(*pToken, RTL_TEXTENCODING_ASCII_US) + ">)");
1958 pList.reset(curl_slist_append(pList.release(), utf8If.getStr()));
1959 if (!pList)
1961 throw uno::RuntimeException("curl_slist_append failed");
1965 // lock m_Mutex after accessing global LockStore to avoid deadlock
1967 // note: Nextcloud 20 cannot handle "Transfer-Encoding: chunked"
1968 ::std::vector<CurlOption> const options{
1969 { CURLOPT_UPLOAD, 1L, nullptr }, // libcurl won't upload without setting this
1970 { CURLOPT_INFILESIZE_LARGE, len, nullptr, CurlOption::Type::CurlOffT }
1973 CurlProcessor::ProcessRequest(*this, uri, "PUT", options, &rEnv, ::std::move(pList), nullptr,
1974 &rxInStream, nullptr);
1977 auto CurlSession::POST(OUString const& rURIReference, OUString const& rContentType,
1978 OUString const& rReferer, uno::Reference<io::XInputStream> const& rxInStream,
1979 DAVRequestEnvironment const& rEnv) -> uno::Reference<io::XInputStream>
1981 SAL_INFO("ucb.ucp.webdav.curl", "POST: " << rURIReference);
1983 CurlUri const uri(CurlProcessor::URIReferenceToURI(*this, rURIReference));
1985 // TODO: either set CURLOPT_POSTFIELDSIZE_LARGE or chunked?
1986 ::std::unique_ptr<curl_slist, deleter_from_fn<curl_slist, curl_slist_free_all>> pList(
1987 curl_slist_append(nullptr, "Transfer-Encoding: chunked"));
1988 if (!pList)
1990 throw uno::RuntimeException("curl_slist_append failed");
1992 OString const utf8ContentType("Content-Type: "
1993 + OUStringToOString(rContentType, RTL_TEXTENCODING_ASCII_US));
1994 pList.reset(curl_slist_append(pList.release(), utf8ContentType.getStr()));
1995 if (!pList)
1997 throw uno::RuntimeException("curl_slist_append failed");
1999 OString const utf8Referer("Referer: " + OUStringToOString(rReferer, RTL_TEXTENCODING_ASCII_US));
2000 pList.reset(curl_slist_append(pList.release(), utf8Referer.getStr()));
2001 if (!pList)
2003 throw uno::RuntimeException("curl_slist_append failed");
2006 ::std::vector<CurlOption> const options{ { CURLOPT_POST, 1L, nullptr } };
2008 uno::Reference<io::XSequenceOutputStream> const xSeqOutStream(
2009 io::SequenceOutputStream::create(m_xContext));
2010 uno::Reference<io::XOutputStream> const xResponseOutStream(xSeqOutStream);
2011 assert(xResponseOutStream.is());
2013 CurlProcessor::ProcessRequest(*this, uri, "POST", options, &rEnv, ::std::move(pList),
2014 &xResponseOutStream, &rxInStream, nullptr);
2016 uno::Reference<io::XInputStream> const xResponseInStream(
2017 io::SequenceInputStream::createStreamFromSequence(m_xContext,
2018 xSeqOutStream->getWrittenBytes()));
2019 assert(xResponseInStream.is());
2021 return xResponseInStream;
2024 auto CurlSession::POST(OUString const& rURIReference, OUString const& rContentType,
2025 OUString const& rReferer, uno::Reference<io::XInputStream> const& rxInStream,
2026 uno::Reference<io::XOutputStream>& rxOutStream,
2027 DAVRequestEnvironment const& rEnv) -> void
2029 SAL_INFO("ucb.ucp.webdav.curl", "POST: " << rURIReference);
2031 CurlUri const uri(CurlProcessor::URIReferenceToURI(*this, rURIReference));
2033 // TODO: either set CURLOPT_POSTFIELDSIZE_LARGE or chunked?
2034 ::std::unique_ptr<curl_slist, deleter_from_fn<curl_slist, curl_slist_free_all>> pList(
2035 curl_slist_append(nullptr, "Transfer-Encoding: chunked"));
2036 if (!pList)
2038 throw uno::RuntimeException("curl_slist_append failed");
2040 OString const utf8ContentType("Content-Type: "
2041 + OUStringToOString(rContentType, RTL_TEXTENCODING_ASCII_US));
2042 pList.reset(curl_slist_append(pList.release(), utf8ContentType.getStr()));
2043 if (!pList)
2045 throw uno::RuntimeException("curl_slist_append failed");
2047 OString const utf8Referer("Referer: " + OUStringToOString(rReferer, RTL_TEXTENCODING_ASCII_US));
2048 pList.reset(curl_slist_append(pList.release(), utf8Referer.getStr()));
2049 if (!pList)
2051 throw uno::RuntimeException("curl_slist_append failed");
2054 ::std::vector<CurlOption> const options{ { CURLOPT_POST, 1L, nullptr } };
2056 CurlProcessor::ProcessRequest(*this, uri, "POST", options, &rEnv, ::std::move(pList),
2057 &rxOutStream, &rxInStream, nullptr);
2060 auto CurlSession::MKCOL(OUString const& rURIReference, DAVRequestEnvironment const& rEnv) -> void
2062 SAL_INFO("ucb.ucp.webdav.curl", "MKCOL: " << rURIReference);
2064 CurlUri const uri(CurlProcessor::URIReferenceToURI(*this, rURIReference));
2066 ::std::vector<CurlOption> const options{ { CURLOPT_CUSTOMREQUEST, "MKCOL",
2067 "CURLOPT_CUSTOMREQUEST" } };
2069 CurlProcessor::ProcessRequest(*this, uri, "MKCOL", options, &rEnv, nullptr, nullptr, nullptr,
2070 nullptr);
2073 auto CurlProcessor::MoveOrCopy(CurlSession& rSession, std::u16string_view rSourceURIReference,
2074 ::std::u16string_view const rDestinationURI,
2075 DAVRequestEnvironment const& rEnv, bool const isOverwrite,
2076 char const* const pMethod) -> void
2078 CurlUri const uriSource(CurlProcessor::URIReferenceToURI(rSession, rSourceURIReference));
2080 OString const utf8Destination("Destination: "
2081 + OUStringToOString(rDestinationURI, RTL_TEXTENCODING_ASCII_US));
2082 ::std::unique_ptr<curl_slist, deleter_from_fn<curl_slist, curl_slist_free_all>> pList(
2083 curl_slist_append(nullptr, utf8Destination.getStr()));
2084 if (!pList)
2086 throw uno::RuntimeException("curl_slist_append failed");
2088 OString const utf8Overwrite(OString::Concat("Overwrite: ") + (isOverwrite ? "T" : "F"));
2089 pList.reset(curl_slist_append(pList.release(), utf8Overwrite.getStr()));
2090 if (!pList)
2092 throw uno::RuntimeException("curl_slist_append failed");
2095 ::std::vector<CurlOption> const options{ { CURLOPT_CUSTOMREQUEST, pMethod,
2096 "CURLOPT_CUSTOMREQUEST" } };
2098 CurlProcessor::ProcessRequest(rSession, uriSource, OUString::createFromAscii(pMethod), options,
2099 &rEnv, ::std::move(pList), nullptr, nullptr, nullptr);
2102 auto CurlSession::COPY(OUString const& rSourceURIReference, OUString const& rDestinationURI,
2103 DAVRequestEnvironment const& rEnv, bool const isOverwrite) -> void
2105 SAL_INFO("ucb.ucp.webdav.curl", "COPY: " << rSourceURIReference);
2107 return CurlProcessor::MoveOrCopy(*this, rSourceURIReference, rDestinationURI, rEnv, isOverwrite,
2108 "COPY");
2111 auto CurlSession::MOVE(OUString const& rSourceURIReference, OUString const& rDestinationURI,
2112 DAVRequestEnvironment const& rEnv, bool const isOverwrite) -> void
2114 SAL_INFO("ucb.ucp.webdav.curl", "MOVE: " << rSourceURIReference);
2116 return CurlProcessor::MoveOrCopy(*this, rSourceURIReference, rDestinationURI, rEnv, isOverwrite,
2117 "MOVE");
2120 auto CurlSession::DESTROY(OUString const& rURIReference, DAVRequestEnvironment const& rEnv) -> void
2122 SAL_INFO("ucb.ucp.webdav.curl", "DESTROY: " << rURIReference);
2124 CurlUri const uri(CurlProcessor::URIReferenceToURI(*this, rURIReference));
2126 ::std::vector<CurlOption> const options{ { CURLOPT_CUSTOMREQUEST, "DELETE",
2127 "CURLOPT_CUSTOMREQUEST" } };
2129 CurlProcessor::ProcessRequest(*this, uri, "DESTROY", options, &rEnv, nullptr, nullptr, nullptr,
2130 nullptr);
2133 auto CurlProcessor::Lock(
2134 CurlSession& rSession, CurlUri const& rURI, DAVRequestEnvironment const* const pEnv,
2135 ::std::unique_ptr<curl_slist, deleter_from_fn<curl_slist, curl_slist_free_all>>
2136 pRequestHeaderList,
2137 uno::Reference<io::XInputStream> const* const pxRequestInStream)
2138 -> ::std::vector<::std::pair<ucb::Lock, sal_Int32>>
2140 curl_off_t len(0);
2141 if (pxRequestInStream)
2143 uno::Reference<io::XSeekable> const xSeekable(*pxRequestInStream, uno::UNO_QUERY);
2144 assert(xSeekable.is());
2145 len = xSeekable->getLength();
2148 ::std::vector<CurlOption> const options{
2149 { CURLOPT_UPLOAD, 1L, nullptr },
2150 { CURLOPT_CUSTOMREQUEST, "LOCK", "CURLOPT_CUSTOMREQUEST" },
2151 // note: Sharepoint cannot handle "Transfer-Encoding: chunked"
2152 { CURLOPT_INFILESIZE_LARGE, len, nullptr, CurlOption::Type::CurlOffT }
2155 // stream for response
2156 uno::Reference<io::XInputStream> const xResponseInStream(io::Pipe::create(rSession.m_xContext));
2157 uno::Reference<io::XOutputStream> const xResponseOutStream(xResponseInStream, uno::UNO_QUERY);
2158 assert(xResponseInStream.is());
2159 assert(xResponseOutStream.is());
2161 TimeValue startTime;
2162 osl_getSystemTime(&startTime);
2164 CurlProcessor::ProcessRequest(rSession, rURI, "LOCK", options, pEnv,
2165 ::std::move(pRequestHeaderList), &xResponseOutStream,
2166 pxRequestInStream, nullptr);
2168 ::std::vector<ucb::Lock> const acquiredLocks(parseWebDAVLockResponse(xResponseInStream));
2169 SAL_WARN_IF(acquiredLocks.empty(), "ucb.ucp.webdav.curl",
2170 "could not get LOCK for " << rURI.GetURI());
2172 TimeValue endTime;
2173 osl_getSystemTime(&endTime);
2174 auto const elapsedSeconds(endTime.Seconds - startTime.Seconds);
2176 // determine expiration time (seconds from endTime) for each acquired lock
2177 ::std::vector<::std::pair<ucb::Lock, sal_Int32>> ret;
2178 ret.reserve(acquiredLocks.size());
2179 for (auto const& rLock : acquiredLocks)
2181 sal_Int32 lockExpirationTimeSeconds;
2182 if (rLock.Timeout == -1)
2184 lockExpirationTimeSeconds = -1;
2186 else if (rLock.Timeout <= elapsedSeconds)
2188 SAL_WARN("ucb.ucp.webdav.curl",
2189 "LOCK timeout already expired when receiving LOCK response for "
2190 << rURI.GetURI());
2191 lockExpirationTimeSeconds = 0;
2193 else
2195 lockExpirationTimeSeconds = startTime.Seconds + rLock.Timeout;
2197 ret.emplace_back(rLock, lockExpirationTimeSeconds);
2200 return ret;
2203 auto CurlSession::LOCK(OUString const& rURIReference, ucb::Lock /*const*/& rLock,
2204 DAVRequestEnvironment const& rEnv) -> void
2206 SAL_INFO("ucb.ucp.webdav.curl", "LOCK: " << rURIReference);
2208 CurlUri const uri(CurlProcessor::URIReferenceToURI(*this, rURIReference));
2210 if (g_Init.LockStore.getLockTokenForURI(uri.GetURI(), &rLock))
2212 // already have a lock that covers the requirement
2213 // TODO: maybe use DAV:lockdiscovery to ensure it's valid
2214 return;
2217 // note: no m_Mutex lock needed here, only in CurlProcessor::Lock()
2219 // generate XML document for acquiring new LOCK
2220 uno::Reference<io::XSequenceOutputStream> const xSeqOutStream(
2221 io::SequenceOutputStream::create(m_xContext));
2222 uno::Reference<io::XOutputStream> const xRequestOutStream(xSeqOutStream);
2223 assert(xRequestOutStream.is());
2224 uno::Reference<xml::sax::XWriter> const xWriter(xml::sax::Writer::create(m_xContext));
2225 xWriter->setOutputStream(xRequestOutStream);
2226 xWriter->startDocument();
2227 rtl::Reference<::comphelper::AttributeList> const pAttrList(new ::comphelper::AttributeList);
2228 pAttrList->AddAttribute("xmlns", "DAV:");
2229 xWriter->startElement("lockinfo", pAttrList);
2230 xWriter->startElement("lockscope", nullptr);
2231 switch (rLock.Scope)
2233 case ucb::LockScope_EXCLUSIVE:
2234 xWriter->startElement("exclusive", nullptr);
2235 xWriter->endElement("exclusive");
2236 break;
2237 case ucb::LockScope_SHARED:
2238 xWriter->startElement("shared", nullptr);
2239 xWriter->endElement("shared");
2240 break;
2241 default:
2242 assert(false);
2244 xWriter->endElement("lockscope");
2245 xWriter->startElement("locktype", nullptr);
2246 xWriter->startElement("write", nullptr);
2247 xWriter->endElement("write");
2248 xWriter->endElement("locktype");
2249 OUString owner;
2250 if ((rLock.Owner >>= owner) && !owner.isEmpty())
2252 xWriter->startElement("owner", nullptr);
2253 xWriter->characters(owner);
2254 xWriter->endElement("owner");
2256 xWriter->endElement("lockinfo");
2257 xWriter->endDocument();
2259 uno::Reference<io::XInputStream> const xRequestInStream(
2260 io::SequenceInputStream::createStreamFromSequence(m_xContext,
2261 xSeqOutStream->getWrittenBytes()));
2262 assert(xRequestInStream.is());
2264 ::std::unique_ptr<curl_slist, deleter_from_fn<curl_slist, curl_slist_free_all>> pList;
2265 pList.reset(curl_slist_append(pList.release(), "Content-Type: application/xml"));
2266 if (!pList)
2268 throw uno::RuntimeException("curl_slist_append failed");
2270 OString depth;
2271 switch (rLock.Depth)
2273 case ucb::LockDepth_ZERO:
2274 depth = "Depth: 0"_ostr;
2275 break;
2276 case ucb::LockDepth_ONE:
2277 depth = "Depth: 1"_ostr;
2278 break;
2279 case ucb::LockDepth_INFINITY:
2280 depth = "Depth: infinity"_ostr;
2281 break;
2282 default:
2283 assert(false);
2285 pList.reset(curl_slist_append(pList.release(), depth.getStr()));
2286 if (!pList)
2288 throw uno::RuntimeException("curl_slist_append failed");
2290 OString timeout;
2291 switch (rLock.Timeout)
2293 case -1:
2294 timeout = "Timeout: Infinite"_ostr;
2295 break;
2296 case 0:
2297 timeout = "Timeout: Second-180"_ostr;
2298 break;
2299 default:
2300 timeout = "Timeout: Second-" + OString::number(rLock.Timeout);
2301 assert(0 < rLock.Timeout);
2302 break;
2304 pList.reset(curl_slist_append(pList.release(), timeout.getStr()));
2305 if (!pList)
2307 throw uno::RuntimeException("curl_slist_append failed");
2310 auto const acquiredLocks
2311 = CurlProcessor::Lock(*this, uri, &rEnv, ::std::move(pList), &xRequestInStream);
2313 for (auto const& rAcquiredLock : acquiredLocks)
2315 g_Init.LockStore.addLock(uri.GetURI(), rAcquiredLock.first,
2316 rAcquiredLock.first.LockTokens[0], this, rAcquiredLock.second);
2317 SAL_INFO("ucb.ucp.webdav.curl", "created LOCK for " << rURIReference);
2321 auto CurlProcessor::Unlock(CurlSession& rSession, CurlUri const& rURI,
2322 DAVRequestEnvironment const* const pEnv) -> void
2324 OUString const* const pToken(g_Init.LockStore.getLockTokenForURI(rURI.GetURI(), nullptr));
2325 if (!pToken)
2327 SAL_WARN("ucb.ucp.webdav.curl", "attempt to unlock but not locked");
2328 throw DAVException(DAVException::DAV_NOT_LOCKED);
2330 OString const utf8LockToken("Lock-Token: <"
2331 + OUStringToOString(*pToken, RTL_TEXTENCODING_ASCII_US) + ">");
2332 ::std::unique_ptr<curl_slist, deleter_from_fn<curl_slist, curl_slist_free_all>> pList(
2333 curl_slist_append(nullptr, utf8LockToken.getStr()));
2334 if (!pList)
2336 throw uno::RuntimeException("curl_slist_append failed");
2339 ::std::vector<CurlOption> const options{ { CURLOPT_CUSTOMREQUEST, "UNLOCK",
2340 "CURLOPT_CUSTOMREQUEST" } };
2342 CurlProcessor::ProcessRequest(rSession, rURI, "UNLOCK", options, pEnv, ::std::move(pList),
2343 nullptr, nullptr, nullptr);
2346 auto CurlSession::UNLOCK(OUString const& rURIReference, DAVRequestEnvironment const& rEnv) -> void
2348 SAL_INFO("ucb.ucp.webdav.curl", "UNLOCK: " << rURIReference);
2350 // note: no m_Mutex lock needed here, only in CurlProcessor::Unlock()
2352 CurlUri const uri(CurlProcessor::URIReferenceToURI(*this, rURIReference));
2354 CurlProcessor::Unlock(*this, uri, &rEnv);
2356 g_Init.LockStore.removeLock(uri.GetURI());
2359 auto CurlSession::NonInteractive_LOCK(OUString const& rURI, ::std::u16string_view const rLockToken,
2360 sal_Int32& o_rLastChanceToSendRefreshRequest,
2361 bool& o_rIsAuthFailed) -> bool
2363 SAL_INFO("ucb.ucp.webdav.curl", "NonInteractive_LOCK: " << rURI);
2365 // note: no m_Mutex lock needed here, only in CurlProcessor::Lock()
2369 CurlUri const uri(rURI);
2370 ::std::unique_ptr<curl_slist, deleter_from_fn<curl_slist, curl_slist_free_all>> pList(
2371 curl_slist_append(nullptr, "Timeout: Second-180"));
2373 assert(!rLockToken.empty()); // LockStore is the caller
2374 OString const utf8If("If: (<" + OUStringToOString(rLockToken, RTL_TEXTENCODING_ASCII_US)
2375 + ">)");
2376 pList.reset(curl_slist_append(pList.release(), utf8If.getStr()));
2377 if (!pList)
2379 throw uno::RuntimeException("curl_slist_append failed");
2382 auto const acquiredLocks
2383 = CurlProcessor::Lock(*this, uri, nullptr, ::std::move(pList), nullptr);
2385 SAL_WARN_IF(1 < acquiredLocks.size(), "ucb.ucp.webdav.curl",
2386 "multiple locks acquired on refresh for " << rURI);
2387 if (!acquiredLocks.empty())
2389 o_rLastChanceToSendRefreshRequest = acquiredLocks.begin()->second;
2391 SAL_INFO("ucb.ucp.webdav.curl", "NonInteractive_LOCK succeeded on " << rURI);
2392 return true;
2394 catch (DAVException const& rException)
2396 SAL_INFO("ucb.ucp.webdav.curl", "NonInteractive_LOCK failed on " << rURI);
2397 switch (rException.getError())
2399 case DAVException::DAV_HTTP_AUTH:
2400 case DAVException::DAV_HTTP_NOAUTH:
2401 o_rIsAuthFailed = true;
2402 break;
2403 default:
2404 break;
2406 return false;
2408 catch (...)
2410 SAL_INFO("ucb.ucp.webdav.curl", "NonInteractive_LOCK failed on " << rURI);
2411 return false;
2415 auto CurlSession::NonInteractive_UNLOCK(OUString const& rURI) -> void
2417 SAL_INFO("ucb.ucp.webdav.curl", "NonInteractive_UNLOCK: " << rURI);
2419 // note: no m_Mutex lock needed here, only in CurlProcessor::Unlock()
2423 CurlUri const uri(rURI);
2425 CurlProcessor::Unlock(*this, uri, nullptr);
2427 // the only caller is the dtor of the LockStore, don't call remove!
2428 SAL_INFO("ucb.ucp.webdav.curl", "NonInteractive_UNLOCK succeeded on " << rURI);
2430 catch (...)
2432 SAL_INFO("ucb.ucp.webdav.curl", "NonInteractive_UNLOCK failed on " << rURI);
2436 } // namespace http_dav_ucp
2438 namespace
2440 /// Manage lifecycle of global DAV worker threads
2441 class WebDAVManager : public cppu::WeakImplHelper<css::lang::XServiceInfo>,
2442 public comphelper::LibreOfficeKit::ThreadJoinable
2444 public:
2445 WebDAVManager() {}
2447 // XServiceInfo
2448 virtual OUString SAL_CALL getImplementationName() override
2450 return "com.sun.star.comp.WebDAVManager";
2452 virtual sal_Bool SAL_CALL supportsService(const OUString& ServiceName) override
2454 return cppu::supportsService(this, ServiceName);
2456 virtual css::uno::Sequence<OUString> SAL_CALL getSupportedServiceNames() override
2458 return { "com.sun.star.ucb.WebDAVManager" };
2461 // comphelper::LibreOfficeKit::ThreadJoinable
2462 virtual bool joinThreads() override { return g_Init.LockStore.joinThreads(); }
2464 virtual void startThreads() override { g_Init.LockStore.startThreads(); }
2467 } // anonymous namespace
2469 extern "C" SAL_DLLPUBLIC_EXPORT css::uno::XInterface*
2470 ucb_webdav_manager_get_implementation(css::uno::XComponentContext*,
2471 css::uno::Sequence<css::uno::Any> const&)
2473 return cppu::acquire(new WebDAVManager());
2476 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */