Fix curl proxy access for non-authenticated proxy
[LibreOffice.git] / ucb / source / ucp / webdav-curl / CurlSession.cxx
blobca6f93744c7b7d92523d2b2917106b9a3185e3a8
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 <comphelper/attributelist.hxx>
18 #include <comphelper/scopeguard.hxx>
19 #include <comphelper/string.hxx>
21 #include <o3tl/safeint.hxx>
22 #include <o3tl/string_view.hxx>
24 #include <officecfg/Inet.hxx>
26 #include <com/sun/star/beans/NamedValue.hpp>
27 #include <com/sun/star/io/Pipe.hpp>
28 #include <com/sun/star/io/SequenceInputStream.hpp>
29 #include <com/sun/star/io/SequenceOutputStream.hpp>
30 #include <com/sun/star/xml/sax/Writer.hpp>
32 #include <osl/time.h>
33 #include <sal/log.hxx>
34 #include <rtl/strbuf.hxx>
35 #include <rtl/ustrbuf.hxx>
36 #include <config_version.h>
38 #include <map>
39 #include <optional>
40 #include <tuple>
41 #include <vector>
43 using namespace ::com::sun::star;
45 namespace
47 /// globals container
48 struct Init
50 /// note: LockStore has its own mutex and calls CurlSession from its thread
51 /// so don't call LockStore with m_Mutex held to prevent deadlock.
52 ::http_dav_ucp::SerfLockStore LockStore;
54 Init()
56 if (curl_global_init(CURL_GLOBAL_ALL) != CURLE_OK)
58 assert(!"curl_global_init failed");
61 // do not call curl_global_cleanup() - this is not the only client of curl
63 Init g_Init;
65 struct ResponseHeaders
67 ::std::vector<::std::pair<::std::vector<OString>, ::std::optional<long>>> HeaderFields;
68 CURL* pCurl;
69 ResponseHeaders(CURL* const i_pCurl)
70 : pCurl(i_pCurl)
75 auto GetResponseCode(ResponseHeaders const& rHeaders) -> ::std::optional<long>
77 return (rHeaders.HeaderFields.empty()) ? ::std::optional<long>{}
78 : rHeaders.HeaderFields.back().second;
81 struct DownloadTarget
83 uno::Reference<io::XOutputStream> xOutStream;
84 ResponseHeaders const& rHeaders;
85 DownloadTarget(uno::Reference<io::XOutputStream> const& i_xOutStream,
86 ResponseHeaders const& i_rHeaders)
87 : xOutStream(i_xOutStream)
88 , rHeaders(i_rHeaders)
93 struct UploadSource
95 uno::Sequence<sal_Int8> const& rInData;
96 ResponseHeaders const& rHeaders;
97 size_t nPosition;
98 UploadSource(uno::Sequence<sal_Int8> const& i_rInData, ResponseHeaders const& i_rHeaders)
99 : rInData(i_rInData)
100 , rHeaders(i_rHeaders)
101 , nPosition(0)
106 auto GetErrorString(CURLcode const rc, char const* const pErrorBuffer = nullptr) -> OString
108 char const* const pMessage( // static fallback
109 (pErrorBuffer && pErrorBuffer[0] != '\0') ? pErrorBuffer : curl_easy_strerror(rc));
110 return OString::Concat("(") + OString::number(sal_Int32(rc)) + ") " + pMessage;
113 auto GetErrorStringMulti(CURLMcode const mc) -> OString
115 return OString::Concat("(") + OString::number(sal_Int32(mc)) + ") " + curl_multi_strerror(mc);
118 /// represent an option to be passed to curl_easy_setopt()
119 struct CurlOption
121 CURLoption const Option;
122 enum class Type
124 Pointer,
125 Long,
126 CurlOffT
128 Type const Tag;
129 union {
130 void const* const pValue;
131 long /*const*/ lValue;
132 curl_off_t /*const*/ cValue;
134 char const* const pExceptionString;
136 CurlOption(CURLoption const i_Option, void const* const i_Value,
137 char const* const i_pExceptionString)
138 : Option(i_Option)
139 , Tag(Type::Pointer)
140 , pValue(i_Value)
141 , pExceptionString(i_pExceptionString)
144 // Depending on platform, curl_off_t may be "long" or a larger type
145 // so cannot use overloading to distinguish these cases.
146 CurlOption(CURLoption const i_Option, curl_off_t const i_Value,
147 char const* const i_pExceptionString, Type const type = Type::Long)
148 : Option(i_Option)
149 , Tag(type)
150 , pExceptionString(i_pExceptionString)
152 static_assert(sizeof(long) <= sizeof(curl_off_t));
153 switch (type)
155 case Type::Long:
156 lValue = i_Value;
157 break;
158 case Type::CurlOffT:
159 cValue = i_Value;
160 break;
161 default:
162 assert(false);
167 // NOBODY will prevent logging the response body in ProcessRequest() exception
168 // handler, so only use it if logging is disabled
169 const CurlOption g_NoBody{ CURLOPT_NOBODY,
170 sal_detail_log_report(SAL_DETAIL_LOG_LEVEL_INFO, "ucb.ucp.webdav.curl")
171 == SAL_DETAIL_LOG_ACTION_IGNORE
172 ? 1L
173 : 0L,
174 nullptr };
176 /// combined guard class to ensure things are released in correct order,
177 /// particularly in ProcessRequest() error handling
178 class Guard
180 private:
181 /// mutex *first* because m_oGuard requires it
182 ::std::unique_lock<::std::mutex> m_Lock;
183 ::std::vector<CurlOption> const m_Options;
184 ::http_dav_ucp::CurlUri const& m_rURI;
185 CURL* const m_pCurl;
187 public:
188 explicit Guard(::std::mutex& rMutex, ::std::vector<CurlOption> const& rOptions,
189 ::http_dav_ucp::CurlUri const& rURI, CURL* const pCurl)
190 : m_Lock(rMutex, ::std::defer_lock)
191 , m_Options(rOptions)
192 , m_rURI(rURI)
193 , m_pCurl(pCurl)
195 Acquire();
197 ~Guard()
199 if (m_Lock.owns_lock())
201 Release();
205 void Acquire()
207 assert(!m_Lock.owns_lock());
208 m_Lock.lock();
209 for (auto const& it : m_Options)
211 CURLcode rc(CURL_LAST); // warning C4701
212 if (it.Tag == CurlOption::Type::Pointer)
214 rc = curl_easy_setopt(m_pCurl, it.Option, it.pValue);
216 else if (it.Tag == CurlOption::Type::Long)
218 rc = curl_easy_setopt(m_pCurl, it.Option, it.lValue);
220 else if (it.Tag == CurlOption::Type::CurlOffT)
222 rc = curl_easy_setopt(m_pCurl, it.Option, it.cValue);
224 else
226 assert(false);
228 if (it.pExceptionString != nullptr)
230 if (rc != CURLE_OK)
232 SAL_WARN("ucb.ucp.webdav.curl",
233 "set " << it.pExceptionString << " failed: " << GetErrorString(rc));
234 throw ::http_dav_ucp::DAVException(
235 ::http_dav_ucp::DAVException::DAV_SESSION_CREATE,
236 ::http_dav_ucp::ConnectionEndPointString(m_rURI.GetHost(),
237 m_rURI.GetPort()));
240 else // many of the options cannot fail
242 assert(rc == CURLE_OK);
246 void Release()
248 assert(m_Lock.owns_lock());
249 for (auto const& it : m_Options)
251 CURLcode rc(CURL_LAST); // warning C4701
252 if (it.Tag == CurlOption::Type::Pointer)
254 rc = curl_easy_setopt(m_pCurl, it.Option, nullptr);
256 else if (it.Tag == CurlOption::Type::Long)
258 rc = curl_easy_setopt(m_pCurl, it.Option, 0L);
260 else if (it.Tag == CurlOption::Type::CurlOffT)
262 rc = curl_easy_setopt(m_pCurl, it.Option, curl_off_t(-1));
264 else
266 assert(false);
268 assert(rc == CURLE_OK);
269 (void)rc;
271 m_Lock.unlock();
275 } // namespace
277 namespace http_dav_ucp
279 // libcurl callbacks:
281 static int debug_callback(CURL* handle, curl_infotype type, char* data, size_t size,
282 void* /*userdata*/)
284 char const* pType(nullptr);
285 switch (type)
287 case CURLINFO_TEXT:
288 SAL_INFO("ucb.ucp.webdav.curl", "debug log: " << handle << ": " << data);
289 return 0;
290 case CURLINFO_HEADER_IN:
291 SAL_INFO("ucb.ucp.webdav.curl",
292 "CURLINFO_HEADER_IN: " << handle << ": " << OString(data, size));
293 return 0;
294 case CURLINFO_HEADER_OUT:
296 // unlike IN, this is all headers in one call
297 OString tmp(data, size);
298 sal_Int32 const start(tmp.indexOf("Authorization: "));
299 if (start != -1)
301 sal_Int32 const end(tmp.indexOf("\r\n", start));
302 assert(end != -1);
303 sal_Int32 const len(SAL_N_ELEMENTS("Authorization: ") - 1);
304 tmp = tmp.replaceAt(
305 start + len, end - start - len,
306 OStringConcatenation(OString::number(end - start - len) + " bytes redacted"));
308 SAL_INFO("ucb.ucp.webdav.curl", "CURLINFO_HEADER_OUT: " << handle << ": " << tmp);
309 return 0;
311 case CURLINFO_DATA_IN:
312 pType = "CURLINFO_DATA_IN";
313 break;
314 case CURLINFO_DATA_OUT:
315 pType = "CURLINFO_DATA_OUT";
316 break;
317 case CURLINFO_SSL_DATA_IN:
318 pType = "CURLINFO_SSL_DATA_IN";
319 break;
320 case CURLINFO_SSL_DATA_OUT:
321 pType = "CURLINFO_SSL_DATA_OUT";
322 break;
323 default:
324 SAL_WARN("ucb.ucp.webdav.curl", "unexpected debug log type");
325 return 0;
327 SAL_INFO("ucb.ucp.webdav.curl", "debug log: " << handle << ": " << pType << " " << size);
328 return 0;
331 static size_t write_callback(char* const ptr, size_t const size, size_t const nmemb,
332 void* const userdata)
334 auto* const pTarget(static_cast<DownloadTarget*>(userdata));
335 if (!pTarget) // looks like ~every request may have a response body
337 return nmemb;
339 assert(size == 1); // says the man page
340 (void)size;
341 assert(pTarget->xOutStream.is());
342 auto const oResponseCode(GetResponseCode(pTarget->rHeaders));
343 if (!oResponseCode)
345 return 0; // that is an error
347 // always write, for exception handler in ProcessRequest()
348 // if (200 <= *oResponseCode && *oResponseCode < 300)
352 uno::Sequence<sal_Int8> const data(reinterpret_cast<sal_Int8*>(ptr), nmemb);
353 pTarget->xOutStream->writeBytes(data);
355 catch (...)
357 SAL_WARN("ucb.ucp.webdav.curl", "exception in write_callback");
358 return 0; // error
361 // else: ignore the body? CurlSession will check the status eventually
362 return nmemb;
365 static size_t read_callback(char* const buffer, size_t const size, size_t const nitems,
366 void* const userdata)
368 auto* const pSource(static_cast<UploadSource*>(userdata));
369 assert(pSource);
370 size_t const nBytes(size * nitems);
371 size_t nRet(0);
374 assert(pSource->nPosition <= o3tl::make_unsigned(pSource->rInData.getLength()));
375 nRet = ::std::min<size_t>(pSource->rInData.getLength() - pSource->nPosition, nBytes);
376 ::std::memcpy(buffer, pSource->rInData.getConstArray() + pSource->nPosition, nRet);
377 pSource->nPosition += nRet;
379 catch (...)
381 SAL_WARN("ucb.ucp.webdav.curl", "exception in read_callback");
382 return CURL_READFUNC_ABORT; // error
384 return nRet;
387 static size_t header_callback(char* const buffer, size_t const size, size_t const nitems,
388 void* const userdata)
390 auto* const pHeaders(static_cast<ResponseHeaders*>(userdata));
391 assert(pHeaders);
392 #if 0
393 if (!pHeaders) // TODO maybe not needed in every request? not sure
395 return nitems;
397 #endif
398 assert(size == 1); // says the man page
399 (void)size;
402 if (nitems <= 2)
404 // end of header, body follows...
405 if (pHeaders->HeaderFields.empty())
407 SAL_WARN("ucb.ucp.webdav.curl", "header_callback: empty header?");
408 return 0; // error
410 // unfortunately there's no separate callback when the body begins,
411 // so have to manually retrieve the status code here
412 long statusCode(SC_NONE);
413 auto rc = curl_easy_getinfo(pHeaders->pCurl, CURLINFO_RESPONSE_CODE, &statusCode);
414 assert(rc == CURLE_OK);
415 (void)rc;
416 // always put the current response code here - wasn't necessarily in this header
417 pHeaders->HeaderFields.back().second.emplace(statusCode);
419 else if (buffer[0] == ' ' || buffer[0] == '\t') // folded header field?
421 size_t i(0);
424 ++i;
425 } while (i == ' ' || i == '\t');
426 if (pHeaders->HeaderFields.empty() || pHeaders->HeaderFields.back().second
427 || pHeaders->HeaderFields.back().first.empty())
429 SAL_WARN("ucb.ucp.webdav.curl",
430 "header_callback: folded header field without start");
431 return 0; // error
433 pHeaders->HeaderFields.back().first.back()
434 += OString::Concat(" ") + ::std::string_view(&buffer[i], nitems - i);
436 else
438 if (pHeaders->HeaderFields.empty() || pHeaders->HeaderFields.back().second)
440 pHeaders->HeaderFields.emplace_back();
442 pHeaders->HeaderFields.back().first.emplace_back(OString(buffer, nitems));
445 catch (...)
447 SAL_WARN("ucb.ucp.webdav.curl", "exception in header_callback");
448 return 0; // error
450 return nitems;
453 static auto ProcessHeaders(::std::vector<OString> const& rHeaders) -> ::std::map<OUString, OUString>
455 ::std::map<OUString, OUString> ret;
456 for (OString const& rLine : rHeaders)
458 OString line;
459 if (!rLine.endsWith("\r\n", &line))
461 SAL_WARN("ucb.ucp.webdav.curl", "invalid header field (no CRLF)");
462 continue;
464 if (line.startsWith("HTTP/") // first line
465 || line.isEmpty()) // last line
467 continue;
469 auto const nColon(line.indexOf(':'));
470 if (nColon == -1)
473 SAL_WARN("ucb.ucp.webdav.curl", "invalid header field (no :)");
475 continue;
477 if (nColon == 0)
479 SAL_WARN("ucb.ucp.webdav.curl", "invalid header field (empty name)");
480 continue;
482 // case insensitive; must be ASCII
483 auto const name(::rtl::OStringToOUString(line.copy(0, nColon).toAsciiLowerCase(),
484 RTL_TEXTENCODING_ASCII_US));
485 sal_Int32 nStart(nColon + 1);
486 while (nStart < line.getLength() && (line[nStart] == ' ' || line[nStart] == '\t'))
488 ++nStart;
490 sal_Int32 nEnd(line.getLength());
491 while (nStart < nEnd && (line[nEnd - 1] == ' ' || line[nEnd - 1] == '\t'))
493 --nEnd;
495 // RFC 7230 says that only ASCII works reliably anyway (neon also did this)
496 auto const value(::rtl::OStringToOUString(line.subView(nStart, nEnd - nStart),
497 RTL_TEXTENCODING_ASCII_US));
498 auto const it(ret.find(name));
499 if (it != ret.end())
501 it->second = it->second + "," + value;
503 else
505 ret[name] = value;
508 return ret;
511 static auto ExtractRequestedHeaders(
512 ResponseHeaders const& rHeaders,
513 ::std::pair<::std::vector<OUString> const&, DAVResource&> const* const pRequestedHeaders)
514 -> void
516 ::std::map<OUString, OUString> const headerMap(
517 ProcessHeaders(rHeaders.HeaderFields.back().first));
518 if (pRequestedHeaders)
520 for (OUString const& rHeader : pRequestedHeaders->first)
522 auto const it(headerMap.find(rHeader.toAsciiLowerCase()));
523 if (it != headerMap.end())
525 DAVPropertyValue value;
526 value.IsCaseSensitive = false;
527 value.Name = it->first;
528 value.Value <<= it->second;
529 pRequestedHeaders->second.properties.push_back(value);
535 // this appears to be the only way to get the "realm" from libcurl
536 static auto ExtractRealm(ResponseHeaders const& rHeaders, char const* const pAuthHeaderName)
537 -> ::std::optional<OUString>
539 ::std::map<OUString, OUString> const headerMap(
540 ProcessHeaders(rHeaders.HeaderFields.back().first));
541 auto const it(headerMap.find(OUString::createFromAscii(pAuthHeaderName).toAsciiLowerCase()));
542 if (it == headerMap.end())
544 SAL_WARN("ucb.ucp.webdav.curl", "cannot find auth header");
545 return {};
547 // It may be possible that the header contains multiple methods each with
548 // a different realm - extract only the first one bc the downstream API
549 // only supports one anyway.
550 // case insensitive!
551 auto i(it->second.toAsciiLowerCase().indexOf("realm="));
552 // is optional
553 if (i == -1)
555 SAL_INFO("ucb.ucp.webdav.curl", "auth header has no realm");
556 return {};
558 // no whitespace allowed before or after =
559 i += ::std::strlen("realm=");
560 if (it->second.getLength() < i + 2 || it->second[i] != '\"')
562 SAL_WARN("ucb.ucp.webdav.curl", "no realm value");
563 return {};
565 ++i;
566 OUStringBuffer buf;
567 while (i < it->second.getLength() && it->second[i] != '\"')
569 if (it->second[i] == '\\') // quoted-pair escape
571 ++i;
572 if (it->second.getLength() <= i)
574 SAL_WARN("ucb.ucp.webdav.curl", "unterminated quoted-pair");
575 return {};
578 buf.append(it->second[i]);
579 ++i;
581 if (it->second.getLength() <= i)
583 SAL_WARN("ucb.ucp.webdav.curl", "unterminated realm");
584 return {};
586 return buf.makeStringAndClear();
589 CurlSession::CurlSession(uno::Reference<uno::XComponentContext> const& xContext,
590 ::rtl::Reference<DAVSessionFactory> const& rpFactory, OUString const& rURI,
591 uno::Sequence<beans::NamedValue> const& rFlags,
592 ::ucbhelper::InternetProxyDecider const& rProxyDecider)
593 : DAVSession(rpFactory)
594 , m_xContext(xContext)
595 , m_Flags(rFlags)
596 , m_URI(rURI)
597 , m_Proxy(rProxyDecider.getProxy(m_URI.GetScheme(), m_URI.GetHost(), m_URI.GetPort()))
599 assert(m_URI.GetScheme() == "http" || m_URI.GetScheme() == "https");
600 m_pCurlMulti.reset(curl_multi_init());
601 if (!m_pCurlMulti)
603 SAL_WARN("ucb.ucp.webdav.curl", "curl_multi_init failed");
604 throw DAVException(DAVException::DAV_SESSION_CREATE,
605 ConnectionEndPointString(m_URI.GetHost(), m_URI.GetPort()));
607 m_pCurl.reset(curl_easy_init());
608 if (!m_pCurl)
610 SAL_WARN("ucb.ucp.webdav.curl", "curl_easy_init failed");
611 throw DAVException(DAVException::DAV_SESSION_CREATE,
612 ConnectionEndPointString(m_URI.GetHost(), m_URI.GetPort()));
614 curl_version_info_data const* const pVersion(curl_version_info(CURLVERSION_NOW));
615 assert(pVersion);
616 SAL_INFO("ucb.ucp.webdav.curl",
617 "curl version: " << pVersion->version << " " << pVersion->host
618 << " features: " << ::std::hex << pVersion->features << " ssl: "
619 << pVersion->ssl_version << " libz: " << pVersion->libz_version);
620 // Make sure a User-Agent header is always included, as at least
621 // en.wikipedia.org:80 forces back 403 "Scripts should use an informative
622 // User-Agent string with contact information, or they may be IP-blocked
623 // without notice" otherwise:
624 OString const useragent(
625 OString::Concat("LibreOffice " LIBO_VERSION_DOTTED " denylistedbackend/")
626 + ::std::string_view(pVersion->version, strlen(pVersion->version)) + " "
627 + pVersion->ssl_version);
628 // looks like an explicit "User-Agent" header in CURLOPT_HTTPHEADER
629 // will override CURLOPT_USERAGENT, see Curl_http_useragent(), so no need
630 // to check anything here
631 auto rc = curl_easy_setopt(m_pCurl.get(), CURLOPT_USERAGENT, useragent.getStr());
632 if (rc != CURLE_OK)
634 SAL_WARN("ucb.ucp.webdav.curl", "CURLOPT_USERAGENT failed: " << GetErrorString(rc));
635 throw DAVException(DAVException::DAV_SESSION_CREATE,
636 ConnectionEndPointString(m_URI.GetHost(), m_URI.GetPort()));
638 m_ErrorBuffer[0] = '\0';
639 // this supposedly gives the highest quality error reporting
640 rc = curl_easy_setopt(m_pCurl.get(), CURLOPT_ERRORBUFFER, m_ErrorBuffer);
641 assert(rc == CURLE_OK);
642 #if 1
643 // just for debugging...
644 rc = curl_easy_setopt(m_pCurl.get(), CURLOPT_DEBUGFUNCTION, debug_callback);
645 assert(rc == CURLE_OK);
646 #endif
647 rc = curl_easy_setopt(m_pCurl.get(), CURLOPT_VERBOSE, 1L);
648 assert(rc == CURLE_OK);
649 // accept any encoding supported by libcurl
650 rc = curl_easy_setopt(m_pCurl.get(), CURLOPT_ACCEPT_ENCODING, "");
651 if (rc != CURLE_OK)
653 SAL_WARN("ucb.ucp.webdav.curl", "CURLOPT_ACCEPT_ENCODING failed: " << GetErrorString(rc));
654 throw DAVException(DAVException::DAV_SESSION_CREATE,
655 ConnectionEndPointString(m_URI.GetHost(), m_URI.GetPort()));
657 auto const connectTimeout(officecfg::Inet::Settings::ConnectTimeout::get(m_xContext));
658 // default is 300s
659 rc = curl_easy_setopt(m_pCurl.get(), CURLOPT_CONNECTTIMEOUT,
660 ::std::max<long>(2L, ::std::min<long>(connectTimeout, 180L)));
661 if (rc != CURLE_OK)
663 SAL_WARN("ucb.ucp.webdav.curl", "CURLOPT_CONNECTTIMEOUT failed: " << GetErrorString(rc));
664 throw DAVException(DAVException::DAV_SESSION_CREATE,
665 ConnectionEndPointString(m_URI.GetHost(), m_URI.GetPort()));
667 auto const readTimeout(officecfg::Inet::Settings::ReadTimeout::get(m_xContext));
668 m_nReadTimeout = ::std::max<int>(20, ::std::min<long>(readTimeout, 180)) * 1000;
669 // default is infinite
670 rc = curl_easy_setopt(m_pCurl.get(), CURLOPT_TIMEOUT, 300L);
671 if (rc != CURLE_OK)
673 SAL_WARN("ucb.ucp.webdav.curl", "CURLOPT_TIMEOUT failed: " << GetErrorString(rc));
674 throw DAVException(DAVException::DAV_SESSION_CREATE,
675 ConnectionEndPointString(m_URI.GetHost(), m_URI.GetPort()));
677 rc = curl_easy_setopt(m_pCurl.get(), CURLOPT_WRITEFUNCTION, &write_callback);
678 assert(rc == CURLE_OK);
679 rc = curl_easy_setopt(m_pCurl.get(), CURLOPT_READFUNCTION, &read_callback);
680 assert(rc == CURLE_OK);
681 rc = curl_easy_setopt(m_pCurl.get(), CURLOPT_HEADERFUNCTION, &header_callback);
682 assert(rc == CURLE_OK);
683 // tdf#149921 by default, with schannel (WNT) connection fails if revocation
684 // lists cannot be checked; try to limit the checking to when revocation
685 // lists can actually be retrieved (usually not the case for self-signed CA)
686 #if CURL_AT_LEAST_VERSION(7, 70, 0)
687 rc = curl_easy_setopt(m_pCurl.get(), CURLOPT_SSL_OPTIONS, CURLSSLOPT_REVOKE_BEST_EFFORT);
688 assert(rc == CURLE_OK);
689 rc = curl_easy_setopt(m_pCurl.get(), CURLOPT_PROXY_SSL_OPTIONS, CURLSSLOPT_REVOKE_BEST_EFFORT);
690 assert(rc == CURLE_OK);
691 #endif
692 // set this initially, may be overwritten during authentication
693 rc = curl_easy_setopt(m_pCurl.get(), CURLOPT_HTTPAUTH, CURLAUTH_ANY);
694 assert(rc == CURLE_OK); // ANY is always available
695 // always set CURLOPT_PROXY to suppress proxy detection in libcurl
696 OString const utf8Proxy(OUStringToOString(m_Proxy.aName, RTL_TEXTENCODING_UTF8));
697 rc = curl_easy_setopt(m_pCurl.get(), CURLOPT_PROXY, utf8Proxy.getStr());
698 if (rc != CURLE_OK)
700 SAL_WARN("ucb.ucp.webdav.curl", "CURLOPT_PROXY failed: " << GetErrorString(rc));
701 throw DAVException(DAVException::DAV_SESSION_CREATE,
702 ConnectionEndPointString(m_Proxy.aName, m_Proxy.nPort));
704 if (!m_Proxy.aName.isEmpty())
706 rc = curl_easy_setopt(m_pCurl.get(), CURLOPT_PROXYPORT, static_cast<long>(m_Proxy.nPort));
707 assert(rc == CURLE_OK);
708 // set this initially, may be overwritten during authentication
709 rc = curl_easy_setopt(m_pCurl.get(), CURLOPT_PROXYAUTH, CURLAUTH_ANY);
710 assert(rc == CURLE_OK); // ANY is always available
712 auto const it(::std::find_if(m_Flags.begin(), m_Flags.end(),
713 [](auto const& rFlag) { return rFlag.Name == "KeepAlive"; }));
714 if (it != m_Flags.end() && it->Value.get<bool>())
716 // neon would close the connection from ne_end_request(), this seems
717 // to be the equivalent and not CURLOPT_TCP_KEEPALIVE
718 rc = curl_easy_setopt(m_pCurl.get(), CURLOPT_FORBID_REUSE, 1L);
719 assert(rc == CURLE_OK);
723 CurlSession::~CurlSession() {}
725 auto CurlSession::CanUse(OUString const& rURI, uno::Sequence<beans::NamedValue> const& rFlags)
726 -> bool
730 CurlUri const uri(rURI);
732 return m_URI.GetScheme() == uri.GetScheme() && m_URI.GetHost() == uri.GetHost()
733 && m_URI.GetPort() == uri.GetPort() && m_Flags == rFlags;
735 catch (DAVException const&)
737 return false;
741 auto CurlSession::UsesProxy() -> bool
743 assert(m_URI.GetScheme() == "http" || m_URI.GetScheme() == "https");
744 return !m_Proxy.aName.isEmpty();
747 auto CurlSession::abort() -> void
749 // note: abort() was a no-op since OOo 3.2 and before that it crashed.
750 bool expected(false);
751 // it would be pointless to lock m_Mutex here as the other thread holds it
752 if (m_AbortFlag.compare_exchange_strong(expected, true))
754 // This function looks safe to call without m_Mutex as long as the
755 // m_pCurlMulti handle is not destroyed, and the caller must own a ref
756 // to this object which keeps it alive; it should cause poll to return.
757 curl_multi_wakeup(m_pCurlMulti.get());
761 /// this is just a bunch of static member functions called from CurlSession
762 struct CurlProcessor
764 static auto URIReferenceToURI(CurlSession& rSession, OUString const& rURIReference) -> CurlUri;
766 static auto ProcessRequestImpl(
767 CurlSession& rSession, CurlUri const& rURI, OUString const& rMethod,
768 curl_slist* pRequestHeaderList, uno::Reference<io::XOutputStream> const* pxOutStream,
769 uno::Sequence<sal_Int8> const* pInData,
770 ::std::pair<::std::vector<OUString> const&, DAVResource&> const* pRequestedHeaders,
771 ResponseHeaders& rHeaders) -> void;
773 static auto ProcessRequest(
774 CurlSession& rSession, CurlUri const& rURI, OUString const& rMethod,
775 ::std::vector<CurlOption> const& rOptions, DAVRequestEnvironment const* pEnv,
776 ::std::unique_ptr<curl_slist, deleter_from_fn<curl_slist, curl_slist_free_all>>
777 pRequestHeaderList,
778 uno::Reference<io::XOutputStream> const* pxOutStream,
779 uno::Reference<io::XInputStream> const* pxInStream,
780 ::std::pair<::std::vector<OUString> const&, DAVResource&> const* pRequestedHeaders) -> void;
782 static auto
783 PropFind(CurlSession& rSession, CurlUri const& rURI, Depth depth,
784 ::std::tuple<::std::vector<OUString> const&, ::std::vector<DAVResource>* const,
785 ::std::vector<ucb::Lock>* const> const* o_pRequestedProperties,
786 ::std::vector<DAVResourceInfo>* const o_pResourceInfos,
787 DAVRequestEnvironment const& rEnv) -> void;
789 static auto MoveOrCopy(CurlSession& rSession, OUString const& rSourceURIReference,
790 ::std::u16string_view rDestinationURI, DAVRequestEnvironment const& rEnv,
791 bool isOverwrite, char const* pMethod) -> void;
793 static auto Lock(CurlSession& rSession, CurlUri const& rURI, DAVRequestEnvironment const* pEnv,
794 ::std::unique_ptr<curl_slist, deleter_from_fn<curl_slist, curl_slist_free_all>>
795 pRequestHeaderList,
796 uno::Reference<io::XInputStream> const* pxInStream)
797 -> ::std::vector<::std::pair<ucb::Lock, sal_Int32>>;
799 static auto Unlock(CurlSession& rSession, CurlUri const& rURI,
800 DAVRequestEnvironment const* pEnv) -> void;
803 auto CurlProcessor::URIReferenceToURI(CurlSession& rSession, OUString const& rURIReference)
804 -> CurlUri
806 // No need to acquire rSession.m_Mutex because accessed members are const.
807 if (rSession.UsesProxy())
808 // very odd, but see DAVResourceAccess::getRequestURI() :-/
810 assert(rURIReference.startsWith("http://") || rURIReference.startsWith("https://"));
811 return CurlUri(rURIReference);
813 else
815 assert(rURIReference.startsWith("/"));
816 return rSession.m_URI.CloneWithRelativeRefPathAbsolute(rURIReference);
820 /// main function to initiate libcurl requests
821 auto CurlProcessor::ProcessRequestImpl(
822 CurlSession& rSession, CurlUri const& rURI, OUString const& rMethod,
823 curl_slist* const pRequestHeaderList,
824 uno::Reference<io::XOutputStream> const* const pxOutStream,
825 uno::Sequence<sal_Int8> const* const pInData,
826 ::std::pair<::std::vector<OUString> const&, DAVResource&> const* const pRequestedHeaders,
827 ResponseHeaders& rHeaders) -> void
829 ::comphelper::ScopeGuard const g([&]() {
830 auto rc = curl_easy_setopt(rSession.m_pCurl.get(), CURLOPT_HEADERDATA, nullptr);
831 assert(rc == CURLE_OK);
832 (void)rc;
833 if (pxOutStream)
835 rc = curl_easy_setopt(rSession.m_pCurl.get(), CURLOPT_WRITEDATA, nullptr);
836 assert(rc == CURLE_OK);
838 if (pInData)
840 rc = curl_easy_setopt(rSession.m_pCurl.get(), CURLOPT_READDATA, nullptr);
841 assert(rc == CURLE_OK);
842 rc = curl_easy_setopt(rSession.m_pCurl.get(), CURLOPT_UPLOAD, 0L);
843 assert(rc == CURLE_OK);
845 if (pRequestHeaderList)
847 rc = curl_easy_setopt(rSession.m_pCurl.get(), CURLOPT_HTTPHEADER, nullptr);
848 assert(rc == CURLE_OK);
852 if (pRequestHeaderList)
854 auto rc = curl_easy_setopt(rSession.m_pCurl.get(), CURLOPT_HTTPHEADER, pRequestHeaderList);
855 assert(rc == CURLE_OK);
856 (void)rc;
859 auto rc = curl_easy_setopt(rSession.m_pCurl.get(), CURLOPT_CURLU, rURI.GetCURLU());
860 assert(rc == CURLE_OK); // can't fail since 7.63.0
862 rc = curl_easy_setopt(rSession.m_pCurl.get(), CURLOPT_HEADERDATA, &rHeaders);
863 assert(rc == CURLE_OK);
864 ::std::optional<DownloadTarget> oDownloadTarget;
865 if (pxOutStream)
867 oDownloadTarget.emplace(*pxOutStream, rHeaders);
868 rc = curl_easy_setopt(rSession.m_pCurl.get(), CURLOPT_WRITEDATA, &*oDownloadTarget);
869 assert(rc == CURLE_OK);
871 ::std::optional<UploadSource> oUploadSource;
872 if (pInData)
874 oUploadSource.emplace(*pInData, rHeaders);
875 rc = curl_easy_setopt(rSession.m_pCurl.get(), CURLOPT_READDATA, &*oUploadSource);
876 assert(rc == CURLE_OK);
877 // libcurl won't upload without setting this
878 rc = curl_easy_setopt(rSession.m_pCurl.get(), CURLOPT_UPLOAD, 1L);
879 assert(rc == CURLE_OK);
881 rSession.m_ErrorBuffer[0] = '\0';
883 // note: easy handle must be added for *every* transfer!
884 // otherwise it gets stuck in MSTATE_MSGSENT forever after 1st transfer
885 auto mc = curl_multi_add_handle(rSession.m_pCurlMulti.get(), rSession.m_pCurl.get());
886 if (mc != CURLM_OK)
888 SAL_WARN("ucb.ucp.webdav.curl",
889 "curl_multi_add_handle failed: " << GetErrorStringMulti(mc));
890 throw DAVException(
891 DAVException::DAV_SESSION_CREATE,
892 ConnectionEndPointString(rSession.m_URI.GetHost(), rSession.m_URI.GetPort()));
894 ::comphelper::ScopeGuard const gg([&]() {
895 mc = curl_multi_remove_handle(rSession.m_pCurlMulti.get(), rSession.m_pCurl.get());
896 if (mc != CURLM_OK)
898 SAL_WARN("ucb.ucp.webdav.curl",
899 "curl_multi_remove_handle failed: " << GetErrorStringMulti(mc));
903 // this is where libcurl actually does something
904 rc = CURL_LAST; // clear current value
905 int nRunning;
908 mc = curl_multi_perform(rSession.m_pCurlMulti.get(), &nRunning);
909 if (mc != CURLM_OK)
911 SAL_WARN("ucb.ucp.webdav.curl",
912 "curl_multi_perform failed: " << GetErrorStringMulti(mc));
913 throw DAVException(
914 DAVException::DAV_HTTP_CONNECT,
915 ConnectionEndPointString(rSession.m_URI.GetHost(), rSession.m_URI.GetPort()));
917 if (nRunning == 0)
918 { // short request like HEAD on loopback could be done in first call
919 break;
921 int nFDs;
922 mc = curl_multi_poll(rSession.m_pCurlMulti.get(), nullptr, 0, rSession.m_nReadTimeout,
923 &nFDs);
924 if (mc != CURLM_OK)
926 SAL_WARN("ucb.ucp.webdav.curl", "curl_multi_poll failed: " << GetErrorStringMulti(mc));
927 throw DAVException(
928 DAVException::DAV_HTTP_CONNECT,
929 ConnectionEndPointString(rSession.m_URI.GetHost(), rSession.m_URI.GetPort()));
931 if (rSession.m_AbortFlag.load())
932 { // flag was set by abort() -> not sure what exception to throw?
933 throw DAVException(DAVException::DAV_HTTP_ERROR, "abort() was called", 0);
935 } while (nRunning != 0);
936 // there should be exactly 1 CURLMsg now, but the interface is
937 // extensible so future libcurl versions could yield additional things
940 CURLMsg const* const pMsg = curl_multi_info_read(rSession.m_pCurlMulti.get(), &nRunning);
941 if (pMsg && pMsg->msg == CURLMSG_DONE)
943 assert(pMsg->easy_handle == rSession.m_pCurl.get());
944 rc = pMsg->data.result;
946 else
948 SAL_WARN("ucb.ucp.webdav.curl", "curl_multi_info_read unexpected result");
950 } while (nRunning != 0);
952 // error handling part 1: libcurl errors
953 if (rc != CURLE_OK)
955 // TODO: is there any value in extracting CURLINFO_OS_ERRNO
956 SAL_WARN("ucb.ucp.webdav.curl",
957 "curl_easy_perform failed: " << GetErrorString(rc, rSession.m_ErrorBuffer));
958 switch (rc)
960 case CURLE_COULDNT_RESOLVE_PROXY:
961 throw DAVException(
962 DAVException::DAV_HTTP_LOOKUP,
963 ConnectionEndPointString(rSession.m_Proxy.aName, rSession.m_Proxy.nPort));
964 case CURLE_COULDNT_RESOLVE_HOST:
965 throw DAVException(
966 DAVException::DAV_HTTP_LOOKUP,
967 ConnectionEndPointString(rSession.m_URI.GetHost(), rSession.m_URI.GetPort()));
968 case CURLE_COULDNT_CONNECT:
969 case CURLE_SSL_CONNECT_ERROR:
970 case CURLE_SSL_CERTPROBLEM:
971 case CURLE_SSL_CIPHER:
972 case CURLE_PEER_FAILED_VERIFICATION:
973 case CURLE_SSL_ISSUER_ERROR:
974 case CURLE_SSL_PINNEDPUBKEYNOTMATCH:
975 case CURLE_SSL_INVALIDCERTSTATUS:
976 case CURLE_FAILED_INIT:
977 #if CURL_AT_LEAST_VERSION(7, 69, 0)
978 case CURLE_QUIC_CONNECT_ERROR:
979 #endif
980 throw DAVException(
981 DAVException::DAV_HTTP_CONNECT,
982 ConnectionEndPointString(rSession.m_URI.GetHost(), rSession.m_URI.GetPort()));
983 case CURLE_REMOTE_ACCESS_DENIED:
984 case CURLE_LOGIN_DENIED:
985 case CURLE_AUTH_ERROR:
986 throw DAVException(
987 DAVException::DAV_HTTP_AUTH, // probably?
988 ConnectionEndPointString(rSession.m_URI.GetHost(), rSession.m_URI.GetPort()));
989 case CURLE_WRITE_ERROR:
990 case CURLE_READ_ERROR: // error returned from our callbacks
991 case CURLE_OUT_OF_MEMORY:
992 case CURLE_ABORTED_BY_CALLBACK:
993 case CURLE_BAD_FUNCTION_ARGUMENT:
994 case CURLE_SEND_ERROR:
995 case CURLE_RECV_ERROR:
996 case CURLE_SSL_CACERT_BADFILE:
997 case CURLE_SSL_CRL_BADFILE:
998 case CURLE_RECURSIVE_API_CALL:
999 throw DAVException(
1000 DAVException::DAV_HTTP_FAILED,
1001 ConnectionEndPointString(rSession.m_URI.GetHost(), rSession.m_URI.GetPort()));
1002 case CURLE_OPERATION_TIMEDOUT:
1003 throw DAVException(
1004 DAVException::DAV_HTTP_TIMEOUT,
1005 ConnectionEndPointString(rSession.m_URI.GetHost(), rSession.m_URI.GetPort()));
1006 default: // lots of generic errors
1007 throw DAVException(DAVException::DAV_HTTP_ERROR, "", 0);
1010 // error handling part 2: HTTP status codes
1011 long statusCode(SC_NONE);
1012 rc = curl_easy_getinfo(rSession.m_pCurl.get(), CURLINFO_RESPONSE_CODE, &statusCode);
1013 assert(rc == CURLE_OK);
1014 assert(statusCode != SC_NONE); // ??? should be error returned from perform?
1015 SAL_INFO("ucb.ucp.webdav.curl", "HTTP status code: " << statusCode);
1016 if (statusCode < 300)
1018 // neon did this regardless of status or even error, which seems odd
1019 ExtractRequestedHeaders(rHeaders, pRequestedHeaders);
1021 else
1023 // create message containing the HTTP method and response status line
1024 OUString statusLine("\n" + rMethod + "\n=>\n");
1025 if (!rHeaders.HeaderFields.empty() && !rHeaders.HeaderFields.back().first.empty()
1026 && rHeaders.HeaderFields.back().first.front().startsWith("HTTP"))
1028 statusLine += ::rtl::OStringToOUString(
1029 rHeaders.HeaderFields.back().first.front().trim(), RTL_TEXTENCODING_ASCII_US);
1031 switch (statusCode)
1033 case SC_REQUEST_TIMEOUT:
1035 throw DAVException(
1036 DAVException::DAV_HTTP_TIMEOUT,
1037 ConnectionEndPointString(rSession.m_URI.GetHost(), rSession.m_URI.GetPort()));
1038 break;
1040 case SC_MOVED_PERMANENTLY:
1041 case SC_MOVED_TEMPORARILY:
1042 case SC_SEE_OTHER:
1043 case SC_TEMPORARY_REDIRECT:
1045 // could also use CURLOPT_FOLLOWLOCATION but apparently the
1046 // upper layer wants to know about redirects?
1047 char* pRedirectURL(nullptr);
1048 rc = curl_easy_getinfo(rSession.m_pCurl.get(), CURLINFO_REDIRECT_URL,
1049 &pRedirectURL);
1050 assert(rc == CURLE_OK);
1051 if (pRedirectURL)
1053 // Sharepoint 2016 workaround: contains unencoded U+0020
1054 OUString const redirectURL(::rtl::Uri::encode(
1055 pRedirectURL
1056 ? OUString(pRedirectURL, strlen(pRedirectURL), RTL_TEXTENCODING_UTF8)
1057 : OUString(),
1058 rtl_UriCharClassUric, rtl_UriEncodeKeepEscapes, RTL_TEXTENCODING_UTF8));
1060 throw DAVException(DAVException::DAV_HTTP_REDIRECT, redirectURL);
1062 [[fallthrough]];
1064 default:
1065 throw DAVException(DAVException::DAV_HTTP_ERROR, statusLine, statusCode);
1069 if (pxOutStream)
1071 (*pxOutStream)->closeOutput(); // signal EOF
1075 static auto TryRemoveExpiredLockToken(CurlSession& rSession, CurlUri const& rURI,
1076 DAVRequestEnvironment const* const pEnv) -> bool
1078 if (!pEnv)
1080 // caller was a NonInteractive_*LOCK function anyway, its caller is LockStore
1081 return false;
1083 OUString const* const pToken(g_Init.LockStore.getLockTokenForURI(rURI.GetURI(), nullptr));
1084 if (!pToken)
1086 return false;
1090 // determine validity of existing lock via lockdiscovery request
1091 ::std::vector<OUString> const propertyNames{ DAVProperties::LOCKDISCOVERY };
1092 ::std::vector<ucb::Lock> locks;
1093 ::std::tuple<::std::vector<OUString> const&, ::std::vector<DAVResource>* const,
1094 ::std::vector<ucb::Lock>* const> const args(propertyNames, nullptr, &locks);
1096 CurlProcessor::PropFind(rSession, rURI, DAVZERO, &args, nullptr, *pEnv);
1098 // https://datatracker.ietf.org/doc/html/rfc4918#section-15.8
1099 // The response MAY not contain tokens, but hopefully it
1100 // will if client is properly authenticated.
1101 if (::std::any_of(locks.begin(), locks.end(), [pToken](ucb::Lock const& rLock) {
1102 return ::std::any_of(
1103 rLock.LockTokens.begin(), rLock.LockTokens.end(),
1104 [pToken](OUString const& rToken) { return *pToken == rToken; });
1107 return false; // still have the lock
1110 SAL_INFO("ucb.ucp.webdav.curl",
1111 "lock token expired, removing: " << rURI.GetURI() << " " << *pToken);
1112 g_Init.LockStore.removeLock(rURI.GetURI());
1113 return true;
1115 catch (DAVException const&)
1117 return false; // ignore, the caller already has a better exception
1121 auto CurlProcessor::ProcessRequest(
1122 CurlSession& rSession, CurlUri const& rURI, OUString const& rMethod,
1123 ::std::vector<CurlOption> const& rOptions, DAVRequestEnvironment const* const pEnv,
1124 ::std::unique_ptr<curl_slist, deleter_from_fn<curl_slist, curl_slist_free_all>>
1125 pRequestHeaderList,
1126 uno::Reference<io::XOutputStream> const* const pxOutStream,
1127 uno::Reference<io::XInputStream> const* const pxInStream,
1128 ::std::pair<::std::vector<OUString> const&, DAVResource&> const* const pRequestedHeaders)
1129 -> void
1131 if (pEnv)
1132 { // add custom request headers passed by caller
1133 for (auto const& rHeader : pEnv->m_aRequestHeaders)
1135 OString const utf8Header(
1136 OUStringToOString(rHeader.first, RTL_TEXTENCODING_ASCII_US) + ": "
1137 + OUStringToOString(rHeader.second, RTL_TEXTENCODING_ASCII_US));
1138 pRequestHeaderList.reset(
1139 curl_slist_append(pRequestHeaderList.release(), utf8Header.getStr()));
1140 if (!pRequestHeaderList)
1142 throw uno::RuntimeException("curl_slist_append failed");
1147 uno::Sequence<sal_Int8> data;
1148 if (pxInStream)
1150 uno::Reference<io::XSeekable> const xSeekable(*pxInStream, uno::UNO_QUERY);
1151 if (xSeekable.is())
1153 auto const len(xSeekable->getLength() - xSeekable->getPosition());
1154 if ((**pxInStream).readBytes(data, len) != len)
1156 throw uno::RuntimeException("short readBytes");
1159 else
1161 ::std::vector<uno::Sequence<sal_Int8>> bufs;
1162 bool isDone(false);
1165 bufs.emplace_back();
1166 isDone = (**pxInStream).readSomeBytes(bufs.back(), 65536) == 0;
1167 } while (!isDone);
1168 sal_Int32 nSize(0);
1169 for (auto const& rBuf : bufs)
1171 if (o3tl::checked_add(nSize, rBuf.getLength(), nSize))
1173 throw std::bad_alloc(); // too large for Sequence
1176 data.realloc(nSize);
1177 size_t nCopied(0);
1178 for (auto const& rBuf : bufs)
1180 ::std::memcpy(data.getArray() + nCopied, rBuf.getConstArray(), rBuf.getLength());
1181 nCopied += rBuf.getLength(); // can't overflow
1186 // Clear flag before transfer starts; only a transfer started before
1187 // calling abort() will be aborted, not one started later.
1188 rSession.m_AbortFlag.store(false);
1190 Guard guard(rSession.m_Mutex, rOptions, rURI, rSession.m_pCurl.get());
1192 // authentication data may be in the URI, or requested via XInteractionHandler
1193 struct Auth
1195 OUString UserName;
1196 OUString PassWord;
1197 decltype(CURLAUTH_ANY) AuthMask; ///< allowed auth methods
1198 Auth(OUString const& rUserName, OUString const& rPassword,
1199 decltype(CURLAUTH_ANY) const & rAuthMask)
1200 : UserName(rUserName)
1201 , PassWord(rPassword)
1202 , AuthMask(rAuthMask)
1206 ::std::optional<Auth> oAuth;
1207 ::std::optional<Auth> oAuthProxy;
1208 if (pEnv && !rSession.m_isAuthenticatedProxy && !rSession.m_Proxy.aName.isEmpty())
1212 // the hope is that this must be a URI
1213 CurlUri const uri(rSession.m_Proxy.aName);
1214 if (!uri.GetUser().isEmpty() || !uri.GetPassword().isEmpty())
1216 oAuthProxy.emplace(uri.GetUser(), uri.GetPassword(), CURLAUTH_ANY);
1219 catch (DAVException&)
1221 // ignore any parsing failure here
1224 decltype(CURLAUTH_ANY) const authSystem(CURLAUTH_NEGOTIATE | CURLAUTH_NTLM | CURLAUTH_NTLM_WB);
1225 if (pRequestedHeaders || (pEnv && !rSession.m_isAuthenticated))
1227 // m_aRequestURI *may* be a path or *may* be URI - wtf
1228 // TODO: why is there this m_aRequestURI and also rURIReference argument?
1229 // ... only caller is DAVResourceAccess - always identical except MOVE/COPY
1230 // which doesn't work if it's just a URI reference so let's just use
1231 // rURIReference via rURI instead
1232 #if 0
1233 CurlUri const uri(pEnv->m_aRequestURI);
1234 #endif
1235 // note: due to parsing bug pwd didn't work in previous webdav ucps
1236 if (pEnv && !rSession.m_isAuthenticated
1237 && (!rURI.GetUser().isEmpty() || !rURI.GetPassword().isEmpty()))
1239 oAuth.emplace(rURI.GetUser(), rURI.GetPassword(), CURLAUTH_ANY);
1241 if (pRequestedHeaders)
1243 // note: Previously this would be the rURIReference directly but
1244 // that ends up in CurlUri anyway and curl is unhappy.
1245 // But it looks like all consumers of this .uri are interested
1246 // only in the path, so it shouldn't make a difference to give
1247 // the entire URI when the caller extracts the path anyway.
1248 pRequestedHeaders->second.uri = rURI.GetURI();
1249 pRequestedHeaders->second.properties.clear();
1252 bool isRetry(false);
1253 int nAuthRequests(0);
1254 int nAuthRequestsProxy(0);
1256 // libcurl does not have an authentication callback so handle auth
1257 // related status codes and requesting credentials via this loop
1260 isRetry = false;
1262 // re-check m_isAuthenticated flags every time, could have been set
1263 // by re-entrant call
1264 if (oAuth && !rSession.m_isAuthenticated)
1266 OString const utf8UserName(OUStringToOString(oAuth->UserName, RTL_TEXTENCODING_UTF8));
1267 auto rc
1268 = curl_easy_setopt(rSession.m_pCurl.get(), CURLOPT_USERNAME, utf8UserName.getStr());
1269 if (rc != CURLE_OK)
1271 SAL_WARN("ucb.ucp.webdav.curl", "CURLOPT_USERNAME failed: " << GetErrorString(rc));
1272 throw DAVException(DAVException::DAV_INVALID_ARG);
1274 OString const utf8PassWord(OUStringToOString(oAuth->PassWord, RTL_TEXTENCODING_UTF8));
1275 rc = curl_easy_setopt(rSession.m_pCurl.get(), CURLOPT_PASSWORD, utf8PassWord.getStr());
1276 if (rc != CURLE_OK)
1278 SAL_WARN("ucb.ucp.webdav.curl", "CURLOPT_PASSWORD failed: " << GetErrorString(rc));
1279 throw DAVException(DAVException::DAV_INVALID_ARG);
1281 rc = curl_easy_setopt(rSession.m_pCurl.get(), CURLOPT_HTTPAUTH, oAuth->AuthMask);
1282 assert(
1284 == CURLE_OK); // it shouldn't be possible to reduce auth to 0 via the authSystem masks
1287 if (oAuthProxy && !rSession.m_isAuthenticatedProxy)
1289 OString const utf8UserName(
1290 OUStringToOString(oAuthProxy->UserName, RTL_TEXTENCODING_UTF8));
1291 auto rc = curl_easy_setopt(rSession.m_pCurl.get(), CURLOPT_PROXYUSERNAME,
1292 utf8UserName.getStr());
1293 if (rc != CURLE_OK)
1295 SAL_WARN("ucb.ucp.webdav.curl",
1296 "CURLOPT_PROXYUSERNAME failed: " << GetErrorString(rc));
1297 throw DAVException(DAVException::DAV_INVALID_ARG);
1299 OString const utf8PassWord(
1300 OUStringToOString(oAuthProxy->PassWord, RTL_TEXTENCODING_UTF8));
1301 rc = curl_easy_setopt(rSession.m_pCurl.get(), CURLOPT_PROXYPASSWORD,
1302 utf8PassWord.getStr());
1303 if (rc != CURLE_OK)
1305 SAL_WARN("ucb.ucp.webdav.curl",
1306 "CURLOPT_PROXYPASSWORD failed: " << GetErrorString(rc));
1307 throw DAVException(DAVException::DAV_INVALID_ARG);
1309 rc = curl_easy_setopt(rSession.m_pCurl.get(), CURLOPT_PROXYAUTH, oAuthProxy->AuthMask);
1310 assert(
1312 == CURLE_OK); // it shouldn't be possible to reduce auth to 0 via the authSystem masks
1315 ResponseHeaders headers(rSession.m_pCurl.get());
1316 // always pass a stream for debug logging, buffer the result body
1317 uno::Reference<io::XSequenceOutputStream> const xSeqOutStream(
1318 io::SequenceOutputStream::create(rSession.m_xContext));
1319 uno::Reference<io::XOutputStream> const xTempOutStream(xSeqOutStream);
1320 assert(xTempOutStream.is());
1324 ProcessRequestImpl(rSession, rURI, rMethod, pRequestHeaderList.get(), &xTempOutStream,
1325 pxInStream ? &data : nullptr, pRequestedHeaders, headers);
1326 if (pxOutStream)
1327 { // only copy to result stream if transfer was successful
1328 (*pxOutStream)->writeBytes(xSeqOutStream->getWrittenBytes());
1329 (*pxOutStream)->closeOutput(); // signal EOF
1332 catch (DAVException const& rException)
1334 // log start of request body if there was any
1335 auto const bytes(xSeqOutStream->getWrittenBytes());
1336 auto const len(::std::min<sal_Int32>(bytes.getLength(), 10000));
1337 SAL_INFO("ucb.ucp.webdav.curl",
1338 "DAVException; (first) " << len << " bytes of data received:");
1339 if (0 < len)
1341 OStringBuffer buf(len);
1342 for (sal_Int32 i = 0; i < len; ++i)
1344 if (bytes[i] < 0x20) // also if negative
1346 static char const hexDigit[16] = { '0', '1', '2', '3', '4', '5', '6', '7',
1347 '8', '9', 'A', 'B', 'C', 'D', 'E', 'F' };
1348 buf.append("\\x");
1349 buf.append(hexDigit[static_cast<sal_uInt8>(bytes[i]) >> 4]);
1350 buf.append(hexDigit[bytes[i] & 0x0F]);
1352 else
1354 buf.append(static_cast<char>(bytes[i]));
1357 SAL_INFO("ucb.ucp.webdav.curl", buf.makeStringAndClear());
1360 // error handling part 3: special HTTP status codes
1361 // that require unlocking m_Mutex to handle
1362 if (rException.getError() == DAVException::DAV_HTTP_ERROR)
1364 auto const statusCode(rException.getStatus());
1365 switch (statusCode)
1367 case SC_LOCKED:
1369 guard.Release(); // release m_Mutex before accessing LockStore
1370 if (g_Init.LockStore.getLockTokenForURI(rURI.GetURI(), nullptr))
1372 throw DAVException(DAVException::DAV_LOCKED_SELF);
1374 else // locked by third party
1376 throw DAVException(DAVException::DAV_LOCKED);
1378 break;
1380 case SC_PRECONDITION_FAILED:
1381 case SC_BAD_REQUEST:
1383 guard.Release(); // release m_Mutex before accessing LockStore
1384 // Not obvious but apparently these codes may indicate
1385 // the expiration of a lock.
1386 // Initiate a new request *outside* ProcessRequestImpl
1387 // *after* rGuard.unlock() to avoid messing up m_pCurl state.
1388 if (TryRemoveExpiredLockToken(rSession, rURI, pEnv))
1390 throw DAVException(DAVException::DAV_LOCK_EXPIRED);
1392 break;
1394 case SC_UNAUTHORIZED:
1395 case SC_PROXY_AUTHENTICATION_REQUIRED:
1397 auto& rnAuthRequests(statusCode == SC_UNAUTHORIZED ? nAuthRequests
1398 : nAuthRequestsProxy);
1399 if (rnAuthRequests == 10)
1401 SAL_INFO("ucb.ucp.webdav.curl", "aborting authentication after "
1402 << rnAuthRequests << " attempts");
1404 else if (pEnv && pEnv->m_xAuthListener)
1406 ::std::optional<OUString> const oRealm(ExtractRealm(
1407 headers, statusCode == SC_UNAUTHORIZED ? "WWW-Authenticate"
1408 : "Proxy-Authenticate"));
1410 ::std::optional<Auth>& roAuth(
1411 statusCode == SC_UNAUTHORIZED ? oAuth : oAuthProxy);
1412 OUString userName(roAuth ? roAuth->UserName : OUString());
1413 OUString passWord(roAuth ? roAuth->PassWord : OUString());
1414 long authAvail(0);
1415 auto const rc = curl_easy_getinfo(rSession.m_pCurl.get(),
1416 statusCode == SC_UNAUTHORIZED
1417 ? CURLINFO_HTTPAUTH_AVAIL
1418 : CURLINFO_PROXYAUTH_AVAIL,
1419 &authAvail);
1420 assert(rc == CURLE_OK);
1421 (void)rc;
1422 // only allow SystemCredentials once - the
1423 // PasswordContainer may have stored it in the
1424 // Config (TrySystemCredentialsFirst or
1425 // AuthenticateUsingSystemCredentials) and then it
1426 // will always force its use no matter how hopeless
1427 bool const isSystemCredSupported((authAvail & authSystem) != 0
1428 && rnAuthRequests == 0);
1429 ++rnAuthRequests;
1431 // Ask user via XInteractionHandler.
1432 // Warning: This likely runs an event loop which may
1433 // end up calling back into this instance, so all
1434 // changes to m_pCurl must be undone now and
1435 // restored after return.
1436 guard.Release();
1438 auto const ret = pEnv->m_xAuthListener->authenticate(
1439 oRealm ? *oRealm : "",
1440 statusCode == SC_UNAUTHORIZED ? rSession.m_URI.GetHost()
1441 : rSession.m_Proxy.aName,
1442 userName, passWord, isSystemCredSupported);
1444 if (ret == 0)
1446 // NTLM may either use a password requested
1447 // from the user, or from the system via SSPI
1448 // so i guess it should not be disabled here
1449 // regardless of the state of the system auth
1450 // checkbox, particularly since SSPI is only
1451 // available on WNT.
1452 // Additionally, "Negotiate" has a "legacy"
1453 // mode that is actually just NTLM according to
1454 // https://curl.se/rfc/ntlm.html#ntlmHttpAuthentication
1455 // so there's nothing in authSystem that can be
1456 // disabled here.
1457 roAuth.emplace(userName, passWord,
1458 ((userName.isEmpty() && passWord.isEmpty())
1459 ? (authAvail & authSystem)
1460 : authAvail));
1461 isRetry = true;
1462 // Acquire is only necessary in case of success.
1463 guard.Acquire();
1464 break; // break out of switch
1466 // else: throw
1468 SAL_INFO("ucb.ucp.webdav.curl", "no auth credentials provided");
1469 throw DAVException(DAVException::DAV_HTTP_NOAUTH,
1470 ConnectionEndPointString(rSession.m_URI.GetHost(),
1471 rSession.m_URI.GetPort()));
1472 break;
1476 if (!isRetry)
1478 throw; // everything else: re-throw
1481 } while (isRetry);
1483 if (oAuth)
1485 // assume this worked, leave auth data as stored in m_pCurl
1486 rSession.m_isAuthenticated = true;
1488 if (oAuthProxy)
1490 // assume this worked, leave auth data as stored in m_pCurl
1491 rSession.m_isAuthenticatedProxy = true;
1495 auto CurlSession::OPTIONS(OUString const& rURIReference,
1497 DAVOptions& rOptions, DAVRequestEnvironment const& rEnv) -> void
1499 SAL_INFO("ucb.ucp.webdav.curl", "OPTIONS: " << rURIReference);
1501 rOptions.init();
1503 CurlUri const uri(CurlProcessor::URIReferenceToURI(*this, rURIReference));
1505 ::std::vector<OUString> const headerNames{ "allow", "dav" };
1506 DAVResource result;
1507 ::std::pair<::std::vector<OUString> const&, DAVResource&> const headers(headerNames, result);
1509 ::std::vector<CurlOption> const options{
1510 g_NoBody, { CURLOPT_CUSTOMREQUEST, "OPTIONS", "CURLOPT_CUSTOMREQUEST" }
1513 CurlProcessor::ProcessRequest(*this, uri, "OPTIONS", options, &rEnv, nullptr, nullptr, nullptr,
1514 &headers);
1516 for (auto const& it : result.properties)
1518 OUString value;
1519 it.Value >>= value;
1520 SAL_INFO("ucb.ucp.webdav.curl", "OPTIONS: header: " << it.Name << ": " << value);
1521 if (it.Name.equalsIgnoreAsciiCase("allow"))
1523 rOptions.setAllowedMethods(value);
1525 else if (it.Name.equalsIgnoreAsciiCase("dav"))
1527 // see <http://tools.ietf.org/html/rfc4918#section-10.1>,
1528 // <http://tools.ietf.org/html/rfc4918#section-18>,
1529 // and <http://tools.ietf.org/html/rfc7230#section-3.2>
1530 // we detect the class (1, 2 and 3), other elements (token, URL)
1531 // are not used for now
1532 auto const list(::comphelper::string::convertCommaSeparated(value));
1533 for (OUString const& v : list)
1535 if (v == "1")
1537 rOptions.setClass1();
1539 else if (v == "2")
1541 rOptions.setClass2();
1543 else if (v == "3")
1545 rOptions.setClass3();
1550 if (rOptions.isClass2() || rOptions.isClass3())
1552 if (g_Init.LockStore.getLockTokenForURI(uri.GetURI(), nullptr))
1554 rOptions.setLocked();
1559 auto CurlProcessor::PropFind(
1560 CurlSession& rSession, CurlUri const& rURI, Depth const nDepth,
1561 ::std::tuple<::std::vector<OUString> const&, ::std::vector<DAVResource>* const,
1562 ::std::vector<ucb::Lock>* const> const* const o_pRequestedProperties,
1563 ::std::vector<DAVResourceInfo>* const o_pResourceInfos, DAVRequestEnvironment const& rEnv)
1564 -> void
1566 assert((o_pRequestedProperties != nullptr) != (o_pResourceInfos != nullptr));
1567 assert((o_pRequestedProperties == nullptr)
1568 || (::std::get<1>(*o_pRequestedProperties) != nullptr)
1569 != (::std::get<2>(*o_pRequestedProperties) != nullptr));
1571 ::std::unique_ptr<curl_slist, deleter_from_fn<curl_slist, curl_slist_free_all>> pList;
1572 pList.reset(curl_slist_append(pList.release(), "Content-Type: application/xml"));
1573 if (!pList)
1575 throw uno::RuntimeException("curl_slist_append failed");
1577 OString depth;
1578 switch (nDepth)
1580 case DAVZERO:
1581 depth = "Depth: 0";
1582 break;
1583 case DAVONE:
1584 depth = "Depth: 1";
1585 break;
1586 case DAVINFINITY:
1587 depth = "Depth: infinity";
1588 break;
1589 default:
1590 assert(false);
1592 pList.reset(curl_slist_append(pList.release(), depth.getStr()));
1593 if (!pList)
1595 throw uno::RuntimeException("curl_slist_append failed");
1598 uno::Reference<io::XSequenceOutputStream> const xSeqOutStream(
1599 io::SequenceOutputStream::create(rSession.m_xContext));
1600 uno::Reference<io::XOutputStream> const xRequestOutStream(xSeqOutStream);
1601 assert(xRequestOutStream.is());
1603 uno::Reference<xml::sax::XWriter> const xWriter(xml::sax::Writer::create(rSession.m_xContext));
1604 xWriter->setOutputStream(xRequestOutStream);
1605 xWriter->startDocument();
1606 rtl::Reference<::comphelper::AttributeList> const pAttrList(new ::comphelper::AttributeList);
1607 pAttrList->AddAttribute("xmlns", "CDATA", "DAV:");
1608 xWriter->startElement("propfind", pAttrList);
1609 if (o_pResourceInfos)
1611 xWriter->startElement("propname", nullptr);
1612 xWriter->endElement("propname");
1614 else
1616 if (::std::get<0>(*o_pRequestedProperties).empty())
1618 xWriter->startElement("allprop", nullptr);
1619 xWriter->endElement("allprop");
1621 else
1623 xWriter->startElement("prop", nullptr);
1624 for (OUString const& rName : ::std::get<0>(*o_pRequestedProperties))
1626 SerfPropName name;
1627 DAVProperties::createSerfPropName(rName, name);
1628 pAttrList->Clear();
1629 pAttrList->AddAttribute("xmlns", "CDATA", OUString::createFromAscii(name.nspace));
1630 xWriter->startElement(OUString::createFromAscii(name.name), pAttrList);
1631 xWriter->endElement(OUString::createFromAscii(name.name));
1633 xWriter->endElement("prop");
1636 xWriter->endElement("propfind");
1637 xWriter->endDocument();
1639 uno::Reference<io::XInputStream> const xRequestInStream(
1640 io::SequenceInputStream::createStreamFromSequence(rSession.m_xContext,
1641 xSeqOutStream->getWrittenBytes()));
1642 assert(xRequestInStream.is());
1644 curl_off_t const len(xSeqOutStream->getWrittenBytes().getLength());
1646 ::std::vector<CurlOption> const options{
1647 { CURLOPT_CUSTOMREQUEST, "PROPFIND", "CURLOPT_CUSTOMREQUEST" },
1648 // note: Sharepoint cannot handle "Transfer-Encoding: chunked"
1649 { CURLOPT_INFILESIZE_LARGE, len, nullptr, CurlOption::Type::CurlOffT }
1652 // stream for response
1653 uno::Reference<io::XInputStream> const xResponseInStream(io::Pipe::create(rSession.m_xContext));
1654 uno::Reference<io::XOutputStream> const xResponseOutStream(xResponseInStream, uno::UNO_QUERY);
1655 assert(xResponseInStream.is());
1656 assert(xResponseOutStream.is());
1658 CurlProcessor::ProcessRequest(rSession, rURI, "PROPFIND", options, &rEnv, ::std::move(pList),
1659 &xResponseOutStream, &xRequestInStream, nullptr);
1661 if (o_pResourceInfos)
1663 *o_pResourceInfos = parseWebDAVPropNameResponse(xResponseInStream);
1665 else
1667 if (::std::get<1>(*o_pRequestedProperties) != nullptr)
1669 *::std::get<1>(*o_pRequestedProperties)
1670 = parseWebDAVPropFindResponse(xResponseInStream);
1671 for (DAVResource& it : *::std::get<1>(*o_pRequestedProperties))
1673 // caller will give these uris to CurlUri so can't be relative
1674 if (it.uri.startsWith("/"))
1678 it.uri = rSession.m_URI.CloneWithRelativeRefPathAbsolute(it.uri).GetURI();
1680 catch (DAVException const&)
1682 SAL_INFO("ucb.ucp.webdav.curl",
1683 "PROPFIND: exception parsing uri " << it.uri);
1688 else
1690 *::std::get<2>(*o_pRequestedProperties) = parseWebDAVLockResponse(xResponseInStream);
1695 // DAV methods
1696 auto CurlSession::PROPFIND(OUString const& rURIReference, Depth const depth,
1697 ::std::vector<OUString> const& rPropertyNames,
1698 ::std::vector<DAVResource>& o_rResources,
1699 DAVRequestEnvironment const& rEnv) -> void
1701 SAL_INFO("ucb.ucp.webdav.curl", "PROPFIND: " << rURIReference << " " << depth);
1703 CurlUri const uri(CurlProcessor::URIReferenceToURI(*this, rURIReference));
1705 ::std::tuple<::std::vector<OUString> const&, ::std::vector<DAVResource>* const,
1706 ::std::vector<ucb::Lock>* const> const args(rPropertyNames, &o_rResources,
1707 nullptr);
1708 return CurlProcessor::PropFind(*this, uri, depth, &args, nullptr, rEnv);
1711 auto CurlSession::PROPFIND(OUString const& rURIReference, Depth const depth,
1712 ::std::vector<DAVResourceInfo>& o_rResourceInfos,
1713 DAVRequestEnvironment const& rEnv) -> void
1715 SAL_INFO("ucb.ucp.webdav.curl", "PROPFIND: " << rURIReference << " " << depth);
1717 CurlUri const uri(CurlProcessor::URIReferenceToURI(*this, rURIReference));
1719 return CurlProcessor::PropFind(*this, uri, depth, nullptr, &o_rResourceInfos, rEnv);
1722 auto CurlSession::PROPPATCH(OUString const& rURIReference,
1723 ::std::vector<ProppatchValue> const& rValues,
1724 DAVRequestEnvironment const& rEnv) -> void
1726 SAL_INFO("ucb.ucp.webdav.curl", "PROPPATCH: " << rURIReference);
1728 CurlUri const uri(CurlProcessor::URIReferenceToURI(*this, rURIReference));
1730 // TODO: either set CURLOPT_INFILESIZE_LARGE or chunked?
1731 ::std::unique_ptr<curl_slist, deleter_from_fn<curl_slist, curl_slist_free_all>> pList;
1732 pList.reset(curl_slist_append(pList.release(), "Content-Type: application/xml"));
1733 if (!pList)
1735 throw uno::RuntimeException("curl_slist_append failed");
1738 // generate XML document for PROPPATCH
1739 uno::Reference<io::XSequenceOutputStream> const xSeqOutStream(
1740 io::SequenceOutputStream::create(m_xContext));
1741 uno::Reference<io::XOutputStream> const xRequestOutStream(xSeqOutStream);
1742 assert(xRequestOutStream.is());
1743 uno::Reference<xml::sax::XWriter> const xWriter(xml::sax::Writer::create(m_xContext));
1744 xWriter->setOutputStream(xRequestOutStream);
1745 xWriter->startDocument();
1746 rtl::Reference<::comphelper::AttributeList> const pAttrList(new ::comphelper::AttributeList);
1747 pAttrList->AddAttribute("xmlns", "CDATA", "DAV:");
1748 xWriter->startElement("propertyupdate", pAttrList);
1749 for (ProppatchValue const& rPropValue : rValues)
1751 assert(rPropValue.operation == PROPSET || rPropValue.operation == PROPREMOVE);
1752 OUString const operation((rPropValue.operation == PROPSET) ? OUString("set")
1753 : OUString("remove"));
1754 xWriter->startElement(operation, nullptr);
1755 xWriter->startElement("prop", nullptr);
1756 SerfPropName name;
1757 DAVProperties::createSerfPropName(rPropValue.name, name);
1758 pAttrList->Clear();
1759 pAttrList->AddAttribute("xmlns", "CDATA", OUString::createFromAscii(name.nspace));
1760 xWriter->startElement(OUString::createFromAscii(name.name), pAttrList);
1761 if (rPropValue.operation == PROPSET)
1763 if (DAVProperties::isUCBDeadProperty(name))
1765 ::std::optional<::std::pair<OUString, OUString>> const oProp(
1766 UCBDeadPropertyValue::toXML(rPropValue.value));
1767 if (oProp)
1769 xWriter->startElement("ucbprop", nullptr);
1770 xWriter->startElement("type", nullptr);
1771 xWriter->characters(oProp->first);
1772 xWriter->endElement("type");
1773 xWriter->startElement("value", nullptr);
1774 xWriter->characters(oProp->second);
1775 xWriter->endElement("value");
1776 xWriter->endElement("ucbprop");
1779 else
1781 OUString value;
1782 rPropValue.value >>= value;
1783 xWriter->characters(value);
1786 xWriter->endElement(OUString::createFromAscii(name.name));
1787 xWriter->endElement("prop");
1788 xWriter->endElement(operation);
1790 xWriter->endElement("propertyupdate");
1791 xWriter->endDocument();
1793 uno::Reference<io::XInputStream> const xRequestInStream(
1794 io::SequenceInputStream::createStreamFromSequence(m_xContext,
1795 xSeqOutStream->getWrittenBytes()));
1796 assert(xRequestInStream.is());
1798 curl_off_t const len(xSeqOutStream->getWrittenBytes().getLength());
1800 ::std::vector<CurlOption> const options{
1801 { CURLOPT_CUSTOMREQUEST, "PROPPATCH", "CURLOPT_CUSTOMREQUEST" },
1802 // note: Sharepoint cannot handle "Transfer-Encoding: chunked"
1803 { CURLOPT_INFILESIZE_LARGE, len, nullptr, CurlOption::Type::CurlOffT }
1806 CurlProcessor::ProcessRequest(*this, uri, "PROPPATCH", options, &rEnv, ::std::move(pList),
1807 nullptr, &xRequestInStream, nullptr);
1810 auto CurlSession::HEAD(OUString const& rURIReference, ::std::vector<OUString> const& rHeaderNames,
1811 DAVResource& io_rResource, DAVRequestEnvironment const& rEnv) -> void
1813 SAL_INFO("ucb.ucp.webdav.curl", "HEAD: " << rURIReference);
1815 CurlUri const uri(CurlProcessor::URIReferenceToURI(*this, rURIReference));
1817 ::std::vector<CurlOption> const options{ g_NoBody };
1819 ::std::pair<::std::vector<OUString> const&, DAVResource&> const headers(rHeaderNames,
1820 io_rResource);
1822 CurlProcessor::ProcessRequest(*this, uri, "HEAD", options, &rEnv, nullptr, nullptr, nullptr,
1823 &headers);
1826 auto CurlSession::GET(OUString const& rURIReference, DAVRequestEnvironment const& rEnv)
1827 -> uno::Reference<io::XInputStream>
1829 SAL_INFO("ucb.ucp.webdav.curl", "GET: " << rURIReference);
1831 CurlUri const uri(CurlProcessor::URIReferenceToURI(*this, rURIReference));
1833 // could use either com.sun.star.io.Pipe or com.sun.star.io.SequenceInputStream?
1834 // Pipe can just write into its XOuputStream, which is simpler.
1835 // Both resize exponentially, so performance should be fine.
1836 // However, Pipe doesn't implement XSeekable, which is required by filters.
1838 uno::Reference<io::XSequenceOutputStream> const xSeqOutStream(
1839 io::SequenceOutputStream::create(m_xContext));
1840 uno::Reference<io::XOutputStream> const xResponseOutStream(xSeqOutStream);
1841 assert(xResponseOutStream.is());
1843 ::std::vector<CurlOption> const options{ { CURLOPT_HTTPGET, 1L, nullptr } };
1845 CurlProcessor::ProcessRequest(*this, uri, "GET", options, &rEnv, nullptr, &xResponseOutStream,
1846 nullptr, nullptr);
1848 uno::Reference<io::XInputStream> const xResponseInStream(
1849 io::SequenceInputStream::createStreamFromSequence(m_xContext,
1850 xSeqOutStream->getWrittenBytes()));
1851 assert(xResponseInStream.is());
1853 return xResponseInStream;
1856 auto CurlSession::GET(OUString const& rURIReference, uno::Reference<io::XOutputStream>& rxOutStream,
1857 DAVRequestEnvironment const& rEnv) -> void
1859 SAL_INFO("ucb.ucp.webdav.curl", "GET: " << rURIReference);
1861 CurlUri const uri(CurlProcessor::URIReferenceToURI(*this, rURIReference));
1863 ::std::vector<CurlOption> const options{ { CURLOPT_HTTPGET, 1L, nullptr } };
1865 CurlProcessor::ProcessRequest(*this, uri, "GET", options, &rEnv, nullptr, &rxOutStream, nullptr,
1866 nullptr);
1869 auto CurlSession::GET(OUString const& rURIReference, ::std::vector<OUString> const& rHeaderNames,
1870 DAVResource& io_rResource, DAVRequestEnvironment const& rEnv)
1871 -> uno::Reference<io::XInputStream>
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 uno::Reference<io::XSequenceOutputStream> const xSeqOutStream(
1880 io::SequenceOutputStream::create(m_xContext));
1881 uno::Reference<io::XOutputStream> const xResponseOutStream(xSeqOutStream);
1882 assert(xResponseOutStream.is());
1884 ::std::pair<::std::vector<OUString> const&, DAVResource&> const headers(rHeaderNames,
1885 io_rResource);
1887 CurlProcessor::ProcessRequest(*this, uri, "GET", options, &rEnv, nullptr, &xResponseOutStream,
1888 nullptr, &headers);
1890 uno::Reference<io::XInputStream> const xResponseInStream(
1891 io::SequenceInputStream::createStreamFromSequence(m_xContext,
1892 xSeqOutStream->getWrittenBytes()));
1893 assert(xResponseInStream.is());
1895 return xResponseInStream;
1898 auto CurlSession::GET(OUString const& rURIReference, uno::Reference<io::XOutputStream>& rxOutStream,
1899 ::std::vector<OUString> const& rHeaderNames, DAVResource& io_rResource,
1900 DAVRequestEnvironment const& rEnv) -> void
1902 SAL_INFO("ucb.ucp.webdav.curl", "GET: " << rURIReference);
1904 CurlUri const uri(CurlProcessor::URIReferenceToURI(*this, rURIReference));
1906 ::std::vector<CurlOption> const options{ { CURLOPT_HTTPGET, 1L, nullptr } };
1908 ::std::pair<::std::vector<OUString> const&, DAVResource&> const headers(rHeaderNames,
1909 io_rResource);
1911 CurlProcessor::ProcessRequest(*this, uri, "GET", options, &rEnv, nullptr, &rxOutStream, nullptr,
1912 &headers);
1915 auto CurlSession::PUT(OUString const& rURIReference,
1916 uno::Reference<io::XInputStream> const& rxInStream,
1917 DAVRequestEnvironment const& rEnv) -> void
1919 SAL_INFO("ucb.ucp.webdav.curl", "PUT: " << rURIReference);
1921 CurlUri const uri(CurlProcessor::URIReferenceToURI(*this, rURIReference));
1923 // NextCloud silently fails with chunked encoding
1924 uno::Reference<io::XSeekable> const xSeekable(rxInStream, uno::UNO_QUERY);
1925 if (!xSeekable.is())
1927 throw uno::RuntimeException("TODO: not seekable");
1929 curl_off_t const len(xSeekable->getLength() - xSeekable->getPosition());
1931 ::std::unique_ptr<curl_slist, deleter_from_fn<curl_slist, curl_slist_free_all>> pList;
1932 OUString const* const pToken(g_Init.LockStore.getLockTokenForURI(uri.GetURI(), nullptr));
1933 if (pToken)
1935 OString const utf8If("If: "
1936 // disabled as Sharepoint 2013 workaround, it accepts only
1937 // "No-Tag-List", see fed2984281a85a5a2f308841ec810f218c75f2ab
1938 #if 0
1939 "<" + OUStringToOString(rURIReference, RTL_TEXTENCODING_ASCII_US)
1940 + "> "
1941 #endif
1942 "(<"
1943 + OUStringToOString(*pToken, RTL_TEXTENCODING_ASCII_US) + ">)");
1944 pList.reset(curl_slist_append(pList.release(), utf8If.getStr()));
1945 if (!pList)
1947 throw uno::RuntimeException("curl_slist_append failed");
1951 // lock m_Mutex after accessing global LockStore to avoid deadlock
1953 // note: Nextcloud 20 cannot handle "Transfer-Encoding: chunked"
1954 ::std::vector<CurlOption> const options{ { CURLOPT_INFILESIZE_LARGE, len, nullptr,
1955 CurlOption::Type::CurlOffT } };
1957 CurlProcessor::ProcessRequest(*this, uri, "PUT", options, &rEnv, ::std::move(pList), nullptr,
1958 &rxInStream, nullptr);
1961 auto CurlSession::POST(OUString const& rURIReference, OUString const& rContentType,
1962 OUString const& rReferer, uno::Reference<io::XInputStream> const& rxInStream,
1963 DAVRequestEnvironment const& rEnv) -> uno::Reference<io::XInputStream>
1965 SAL_INFO("ucb.ucp.webdav.curl", "POST: " << rURIReference);
1967 CurlUri const uri(CurlProcessor::URIReferenceToURI(*this, rURIReference));
1969 // TODO: either set CURLOPT_POSTFIELDSIZE_LARGE or chunked?
1970 ::std::unique_ptr<curl_slist, deleter_from_fn<curl_slist, curl_slist_free_all>> pList(
1971 curl_slist_append(nullptr, "Transfer-Encoding: chunked"));
1972 if (!pList)
1974 throw uno::RuntimeException("curl_slist_append failed");
1976 OString const utf8ContentType("Content-Type: "
1977 + OUStringToOString(rContentType, RTL_TEXTENCODING_ASCII_US));
1978 pList.reset(curl_slist_append(pList.release(), utf8ContentType.getStr()));
1979 if (!pList)
1981 throw uno::RuntimeException("curl_slist_append failed");
1983 OString const utf8Referer("Referer: " + OUStringToOString(rReferer, RTL_TEXTENCODING_ASCII_US));
1984 pList.reset(curl_slist_append(pList.release(), utf8Referer.getStr()));
1985 if (!pList)
1987 throw uno::RuntimeException("curl_slist_append failed");
1990 ::std::vector<CurlOption> const options{ { CURLOPT_POST, 1L, nullptr } };
1992 uno::Reference<io::XSequenceOutputStream> const xSeqOutStream(
1993 io::SequenceOutputStream::create(m_xContext));
1994 uno::Reference<io::XOutputStream> const xResponseOutStream(xSeqOutStream);
1995 assert(xResponseOutStream.is());
1997 CurlProcessor::ProcessRequest(*this, uri, "POST", options, &rEnv, ::std::move(pList),
1998 &xResponseOutStream, &rxInStream, nullptr);
2000 uno::Reference<io::XInputStream> const xResponseInStream(
2001 io::SequenceInputStream::createStreamFromSequence(m_xContext,
2002 xSeqOutStream->getWrittenBytes()));
2003 assert(xResponseInStream.is());
2005 return xResponseInStream;
2008 auto CurlSession::POST(OUString const& rURIReference, OUString const& rContentType,
2009 OUString const& rReferer, uno::Reference<io::XInputStream> const& rxInStream,
2010 uno::Reference<io::XOutputStream>& rxOutStream,
2011 DAVRequestEnvironment const& rEnv) -> void
2013 SAL_INFO("ucb.ucp.webdav.curl", "POST: " << rURIReference);
2015 CurlUri const uri(CurlProcessor::URIReferenceToURI(*this, rURIReference));
2017 // TODO: either set CURLOPT_POSTFIELDSIZE_LARGE or chunked?
2018 ::std::unique_ptr<curl_slist, deleter_from_fn<curl_slist, curl_slist_free_all>> pList(
2019 curl_slist_append(nullptr, "Transfer-Encoding: chunked"));
2020 if (!pList)
2022 throw uno::RuntimeException("curl_slist_append failed");
2024 OString const utf8ContentType("Content-Type: "
2025 + OUStringToOString(rContentType, RTL_TEXTENCODING_ASCII_US));
2026 pList.reset(curl_slist_append(pList.release(), utf8ContentType.getStr()));
2027 if (!pList)
2029 throw uno::RuntimeException("curl_slist_append failed");
2031 OString const utf8Referer("Referer: " + OUStringToOString(rReferer, RTL_TEXTENCODING_ASCII_US));
2032 pList.reset(curl_slist_append(pList.release(), utf8Referer.getStr()));
2033 if (!pList)
2035 throw uno::RuntimeException("curl_slist_append failed");
2038 ::std::vector<CurlOption> const options{ { CURLOPT_POST, 1L, nullptr } };
2040 CurlProcessor::ProcessRequest(*this, uri, "POST", options, &rEnv, ::std::move(pList),
2041 &rxOutStream, &rxInStream, nullptr);
2044 auto CurlSession::MKCOL(OUString const& rURIReference, DAVRequestEnvironment const& rEnv) -> void
2046 SAL_INFO("ucb.ucp.webdav.curl", "MKCOL: " << rURIReference);
2048 CurlUri const uri(CurlProcessor::URIReferenceToURI(*this, rURIReference));
2050 ::std::vector<CurlOption> const options{
2051 g_NoBody, { CURLOPT_CUSTOMREQUEST, "MKCOL", "CURLOPT_CUSTOMREQUEST" }
2054 CurlProcessor::ProcessRequest(*this, uri, "MKCOL", options, &rEnv, nullptr, nullptr, nullptr,
2055 nullptr);
2058 auto CurlProcessor::MoveOrCopy(CurlSession& rSession, OUString const& rSourceURIReference,
2059 ::std::u16string_view const rDestinationURI,
2060 DAVRequestEnvironment const& rEnv, bool const isOverwrite,
2061 char const* const pMethod) -> void
2063 CurlUri const uriSource(CurlProcessor::URIReferenceToURI(rSession, rSourceURIReference));
2065 OString const utf8Destination("Destination: "
2066 + OUStringToOString(rDestinationURI, RTL_TEXTENCODING_ASCII_US));
2067 ::std::unique_ptr<curl_slist, deleter_from_fn<curl_slist, curl_slist_free_all>> pList(
2068 curl_slist_append(nullptr, utf8Destination.getStr()));
2069 if (!pList)
2071 throw uno::RuntimeException("curl_slist_append failed");
2073 OString const utf8Overwrite(OString::Concat("Overwrite: ") + (isOverwrite ? "T" : "F"));
2074 pList.reset(curl_slist_append(pList.release(), utf8Overwrite.getStr()));
2075 if (!pList)
2077 throw uno::RuntimeException("curl_slist_append failed");
2080 ::std::vector<CurlOption> const options{
2081 g_NoBody, { CURLOPT_CUSTOMREQUEST, pMethod, "CURLOPT_CUSTOMREQUEST" }
2084 CurlProcessor::ProcessRequest(rSession, uriSource, OUString::createFromAscii(pMethod), options,
2085 &rEnv, ::std::move(pList), nullptr, nullptr, nullptr);
2088 auto CurlSession::COPY(OUString const& rSourceURIReference, OUString const& rDestinationURI,
2089 DAVRequestEnvironment const& rEnv, bool const isOverwrite) -> void
2091 SAL_INFO("ucb.ucp.webdav.curl", "COPY: " << rSourceURIReference);
2093 return CurlProcessor::MoveOrCopy(*this, rSourceURIReference, rDestinationURI, rEnv, isOverwrite,
2094 "COPY");
2097 auto CurlSession::MOVE(OUString const& rSourceURIReference, OUString const& rDestinationURI,
2098 DAVRequestEnvironment const& rEnv, bool const isOverwrite) -> void
2100 SAL_INFO("ucb.ucp.webdav.curl", "MOVE: " << rSourceURIReference);
2102 return CurlProcessor::MoveOrCopy(*this, rSourceURIReference, rDestinationURI, rEnv, isOverwrite,
2103 "MOVE");
2106 auto CurlSession::DESTROY(OUString const& rURIReference, DAVRequestEnvironment const& rEnv) -> void
2108 SAL_INFO("ucb.ucp.webdav.curl", "DESTROY: " << rURIReference);
2110 CurlUri const uri(CurlProcessor::URIReferenceToURI(*this, rURIReference));
2112 ::std::vector<CurlOption> const options{
2113 g_NoBody, { CURLOPT_CUSTOMREQUEST, "DELETE", "CURLOPT_CUSTOMREQUEST" }
2116 CurlProcessor::ProcessRequest(*this, uri, "DESTROY", options, &rEnv, nullptr, nullptr, nullptr,
2117 nullptr);
2120 auto CurlProcessor::Lock(
2121 CurlSession& rSession, CurlUri const& rURI, DAVRequestEnvironment const* const pEnv,
2122 ::std::unique_ptr<curl_slist, deleter_from_fn<curl_slist, curl_slist_free_all>>
2123 pRequestHeaderList,
2124 uno::Reference<io::XInputStream> const* const pxRequestInStream)
2125 -> ::std::vector<::std::pair<ucb::Lock, sal_Int32>>
2127 curl_off_t len(0);
2128 if (pxRequestInStream)
2130 uno::Reference<io::XSeekable> const xSeekable(*pxRequestInStream, uno::UNO_QUERY);
2131 assert(xSeekable.is());
2132 len = xSeekable->getLength();
2135 ::std::vector<CurlOption> const options{
2136 { CURLOPT_CUSTOMREQUEST, "LOCK", "CURLOPT_CUSTOMREQUEST" },
2137 // note: Sharepoint cannot handle "Transfer-Encoding: chunked"
2138 { CURLOPT_INFILESIZE_LARGE, len, nullptr, CurlOption::Type::CurlOffT }
2141 // stream for response
2142 uno::Reference<io::XInputStream> const xResponseInStream(io::Pipe::create(rSession.m_xContext));
2143 uno::Reference<io::XOutputStream> const xResponseOutStream(xResponseInStream, uno::UNO_QUERY);
2144 assert(xResponseInStream.is());
2145 assert(xResponseOutStream.is());
2147 TimeValue startTime;
2148 osl_getSystemTime(&startTime);
2150 CurlProcessor::ProcessRequest(rSession, rURI, "LOCK", options, pEnv,
2151 ::std::move(pRequestHeaderList), &xResponseOutStream,
2152 pxRequestInStream, nullptr);
2154 ::std::vector<ucb::Lock> const acquiredLocks(parseWebDAVLockResponse(xResponseInStream));
2155 SAL_WARN_IF(acquiredLocks.empty(), "ucb.ucp.webdav.curl",
2156 "could not get LOCK for " << rURI.GetURI());
2158 TimeValue endTime;
2159 osl_getSystemTime(&endTime);
2160 auto const elapsedSeconds(endTime.Seconds - startTime.Seconds);
2162 // determine expiration time (seconds from endTime) for each acquired lock
2163 ::std::vector<::std::pair<ucb::Lock, sal_Int32>> ret;
2164 ret.reserve(acquiredLocks.size());
2165 for (auto const& rLock : acquiredLocks)
2167 sal_Int32 lockExpirationTimeSeconds;
2168 if (rLock.Timeout == -1)
2170 lockExpirationTimeSeconds = -1;
2172 else if (rLock.Timeout <= elapsedSeconds)
2174 SAL_WARN("ucb.ucp.webdav.curl",
2175 "LOCK timeout already expired when receiving LOCK response for "
2176 << rURI.GetURI());
2177 lockExpirationTimeSeconds = 0;
2179 else
2181 lockExpirationTimeSeconds = startTime.Seconds + rLock.Timeout;
2183 ret.emplace_back(rLock, lockExpirationTimeSeconds);
2186 return ret;
2189 auto CurlSession::LOCK(OUString const& rURIReference, ucb::Lock /*const*/& rLock,
2190 DAVRequestEnvironment const& rEnv) -> void
2192 SAL_INFO("ucb.ucp.webdav.curl", "LOCK: " << rURIReference);
2194 CurlUri const uri(CurlProcessor::URIReferenceToURI(*this, rURIReference));
2196 if (g_Init.LockStore.getLockTokenForURI(uri.GetURI(), &rLock))
2198 // already have a lock that covers the requirement
2199 // TODO: maybe use DAV:lockdiscovery to ensure it's valid
2200 return;
2203 // note: no m_Mutex lock needed here, only in CurlProcessor::Lock()
2205 // generate XML document for acquiring new LOCK
2206 uno::Reference<io::XSequenceOutputStream> const xSeqOutStream(
2207 io::SequenceOutputStream::create(m_xContext));
2208 uno::Reference<io::XOutputStream> const xRequestOutStream(xSeqOutStream);
2209 assert(xRequestOutStream.is());
2210 uno::Reference<xml::sax::XWriter> const xWriter(xml::sax::Writer::create(m_xContext));
2211 xWriter->setOutputStream(xRequestOutStream);
2212 xWriter->startDocument();
2213 rtl::Reference<::comphelper::AttributeList> const pAttrList(new ::comphelper::AttributeList);
2214 pAttrList->AddAttribute("xmlns", "CDATA", "DAV:");
2215 xWriter->startElement("lockinfo", pAttrList);
2216 xWriter->startElement("lockscope", nullptr);
2217 switch (rLock.Scope)
2219 case ucb::LockScope_EXCLUSIVE:
2220 xWriter->startElement("exclusive", nullptr);
2221 xWriter->endElement("exclusive");
2222 break;
2223 case ucb::LockScope_SHARED:
2224 xWriter->startElement("shared", nullptr);
2225 xWriter->endElement("shared");
2226 break;
2227 default:
2228 assert(false);
2230 xWriter->endElement("lockscope");
2231 xWriter->startElement("locktype", nullptr);
2232 xWriter->startElement("write", nullptr);
2233 xWriter->endElement("write");
2234 xWriter->endElement("locktype");
2235 OUString owner;
2236 if ((rLock.Owner >>= owner) && !owner.isEmpty())
2238 xWriter->startElement("owner", nullptr);
2239 xWriter->characters(owner);
2240 xWriter->endElement("owner");
2242 xWriter->endElement("lockinfo");
2243 xWriter->endDocument();
2245 uno::Reference<io::XInputStream> const xRequestInStream(
2246 io::SequenceInputStream::createStreamFromSequence(m_xContext,
2247 xSeqOutStream->getWrittenBytes()));
2248 assert(xRequestInStream.is());
2250 ::std::unique_ptr<curl_slist, deleter_from_fn<curl_slist, curl_slist_free_all>> pList;
2251 pList.reset(curl_slist_append(pList.release(), "Content-Type: application/xml"));
2252 if (!pList)
2254 throw uno::RuntimeException("curl_slist_append failed");
2256 OString depth;
2257 switch (rLock.Depth)
2259 case ucb::LockDepth_ZERO:
2260 depth = "Depth: 0";
2261 break;
2262 case ucb::LockDepth_ONE:
2263 depth = "Depth: 1";
2264 break;
2265 case ucb::LockDepth_INFINITY:
2266 depth = "Depth: infinity";
2267 break;
2268 default:
2269 assert(false);
2271 pList.reset(curl_slist_append(pList.release(), depth.getStr()));
2272 if (!pList)
2274 throw uno::RuntimeException("curl_slist_append failed");
2276 OString timeout;
2277 switch (rLock.Timeout)
2279 case -1:
2280 timeout = "Timeout: Infinite";
2281 break;
2282 case 0:
2283 timeout = "Timeout: Second-180";
2284 break;
2285 default:
2286 timeout = "Timeout: Second-" + OString::number(rLock.Timeout);
2287 assert(0 < rLock.Timeout);
2288 break;
2290 pList.reset(curl_slist_append(pList.release(), timeout.getStr()));
2291 if (!pList)
2293 throw uno::RuntimeException("curl_slist_append failed");
2296 auto const acquiredLocks
2297 = CurlProcessor::Lock(*this, uri, &rEnv, ::std::move(pList), &xRequestInStream);
2299 for (auto const& rAcquiredLock : acquiredLocks)
2301 g_Init.LockStore.addLock(uri.GetURI(), rAcquiredLock.first,
2302 rAcquiredLock.first.LockTokens[0], this, rAcquiredLock.second);
2303 SAL_INFO("ucb.ucp.webdav.curl", "created LOCK for " << rURIReference);
2307 auto CurlProcessor::Unlock(CurlSession& rSession, CurlUri const& rURI,
2308 DAVRequestEnvironment const* const pEnv) -> void
2310 OUString const* const pToken(g_Init.LockStore.getLockTokenForURI(rURI.GetURI(), nullptr));
2311 if (!pToken)
2313 SAL_WARN("ucb.ucp.webdav.curl", "attempt to unlock but not locked");
2314 throw DAVException(DAVException::DAV_NOT_LOCKED);
2316 OString const utf8LockToken("Lock-Token: <"
2317 + OUStringToOString(*pToken, RTL_TEXTENCODING_ASCII_US) + ">");
2318 ::std::unique_ptr<curl_slist, deleter_from_fn<curl_slist, curl_slist_free_all>> pList(
2319 curl_slist_append(nullptr, utf8LockToken.getStr()));
2320 if (!pList)
2322 throw uno::RuntimeException("curl_slist_append failed");
2325 ::std::vector<CurlOption> const options{ { CURLOPT_CUSTOMREQUEST, "UNLOCK",
2326 "CURLOPT_CUSTOMREQUEST" } };
2328 CurlProcessor::ProcessRequest(rSession, rURI, "UNLOCK", options, pEnv, ::std::move(pList),
2329 nullptr, nullptr, nullptr);
2332 auto CurlSession::UNLOCK(OUString const& rURIReference, DAVRequestEnvironment const& rEnv) -> void
2334 SAL_INFO("ucb.ucp.webdav.curl", "UNLOCK: " << rURIReference);
2336 // note: no m_Mutex lock needed here, only in CurlProcessor::Unlock()
2338 CurlUri const uri(CurlProcessor::URIReferenceToURI(*this, rURIReference));
2340 CurlProcessor::Unlock(*this, uri, &rEnv);
2342 g_Init.LockStore.removeLock(uri.GetURI());
2345 auto CurlSession::NonInteractive_LOCK(OUString const& rURI, ::std::u16string_view const rLockToken,
2346 sal_Int32& o_rLastChanceToSendRefreshRequest,
2347 bool& o_rIsAuthFailed) -> bool
2349 SAL_INFO("ucb.ucp.webdav.curl", "NonInteractive_LOCK: " << rURI);
2351 // note: no m_Mutex lock needed here, only in CurlProcessor::Lock()
2355 CurlUri const uri(rURI);
2356 ::std::unique_ptr<curl_slist, deleter_from_fn<curl_slist, curl_slist_free_all>> pList(
2357 curl_slist_append(nullptr, "Timeout: Second-180"));
2359 assert(!rLockToken.empty()); // LockStore is the caller
2360 OString const utf8If("If: (<" + OUStringToOString(rLockToken, RTL_TEXTENCODING_ASCII_US)
2361 + ">)");
2362 pList.reset(curl_slist_append(pList.release(), utf8If.getStr()));
2363 if (!pList)
2365 throw uno::RuntimeException("curl_slist_append failed");
2368 auto const acquiredLocks
2369 = CurlProcessor::Lock(*this, uri, nullptr, ::std::move(pList), nullptr);
2371 SAL_WARN_IF(1 < acquiredLocks.size(), "ucb.ucp.webdav.curl",
2372 "multiple locks acquired on refresh for " << rURI);
2373 if (!acquiredLocks.empty())
2375 o_rLastChanceToSendRefreshRequest = acquiredLocks.begin()->second;
2377 SAL_INFO("ucb.ucp.webdav.curl", "NonInteractive_LOCK succeeded on " << rURI);
2378 return true;
2380 catch (DAVException const& rException)
2382 SAL_INFO("ucb.ucp.webdav.curl", "NonInteractive_LOCK failed on " << rURI);
2383 switch (rException.getError())
2385 case DAVException::DAV_HTTP_AUTH:
2386 case DAVException::DAV_HTTP_NOAUTH:
2387 o_rIsAuthFailed = true;
2388 break;
2389 default:
2390 break;
2392 return false;
2394 catch (...)
2396 SAL_INFO("ucb.ucp.webdav.curl", "NonInteractive_LOCK failed on " << rURI);
2397 return false;
2401 auto CurlSession::NonInteractive_UNLOCK(OUString const& rURI) -> void
2403 SAL_INFO("ucb.ucp.webdav.curl", "NonInteractive_UNLOCK: " << rURI);
2405 // note: no m_Mutex lock needed here, only in CurlProcessor::Unlock()
2409 CurlUri const uri(rURI);
2411 CurlProcessor::Unlock(*this, uri, nullptr);
2413 // the only caller is the dtor of the LockStore, don't call remove!
2414 SAL_INFO("ucb.ucp.webdav.curl", "NonInteractive_UNLOCK succeeded on " << rURI);
2416 catch (...)
2418 SAL_INFO("ucb.ucp.webdav.curl", "NonInteractive_UNLOCK failed on " << rURI);
2422 } // namespace http_dav_ucp
2424 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */