Version 7.6.3.2-android, tag libreoffice-7.6.3.2-android
[LibreOffice.git] / ucb / source / ucp / webdav-curl / CurlSession.cxx
blobf046395a668e254fa089e5526c752ee983e98ce9
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/lok.hxx>
19 #include <comphelper/scopeguard.hxx>
20 #include <comphelper/string.hxx>
22 #include <o3tl/safeint.hxx>
23 #include <o3tl/string_view.hxx>
25 #include <officecfg/Inet.hxx>
27 #include <com/sun/star/beans/NamedValue.hpp>
28 #include <com/sun/star/io/Pipe.hpp>
29 #include <com/sun/star/io/SequenceInputStream.hpp>
30 #include <com/sun/star/io/SequenceOutputStream.hpp>
31 #include <com/sun/star/xml/sax/Writer.hpp>
33 #include <osl/time.h>
34 #include <sal/log.hxx>
35 #include <rtl/uri.hxx>
36 #include <rtl/strbuf.hxx>
37 #include <rtl/ustrbuf.hxx>
38 #include <curlinit.hxx>
39 #include <config_version.h>
41 #include <map>
42 #include <optional>
43 #include <tuple>
44 #include <utility>
46 using namespace ::com::sun::star;
48 namespace
50 /// globals container
51 struct Init
53 /// note: LockStore has its own mutex and calls CurlSession from its thread
54 /// so don't call LockStore with m_Mutex held to prevent deadlock.
55 ::http_dav_ucp::SerfLockStore LockStore;
57 Init()
59 if (curl_global_init(CURL_GLOBAL_ALL) != CURLE_OK)
61 assert(!"curl_global_init failed");
64 // do not call curl_global_cleanup() - this is not the only client of curl
66 Init g_Init;
68 struct ResponseHeaders
70 ::std::vector<::std::pair<::std::vector<OString>, ::std::optional<long>>> HeaderFields;
71 CURL* pCurl;
72 ResponseHeaders(CURL* const i_pCurl)
73 : pCurl(i_pCurl)
78 auto GetResponseCode(ResponseHeaders const& rHeaders) -> ::std::optional<long>
80 return (rHeaders.HeaderFields.empty()) ? ::std::optional<long>{}
81 : rHeaders.HeaderFields.back().second;
84 struct DownloadTarget
86 uno::Reference<io::XOutputStream> xOutStream;
87 ResponseHeaders const& rHeaders;
88 DownloadTarget(uno::Reference<io::XOutputStream> i_xOutStream,
89 ResponseHeaders const& i_rHeaders)
90 : xOutStream(std::move(i_xOutStream))
91 , rHeaders(i_rHeaders)
96 struct UploadSource
98 uno::Sequence<sal_Int8> const& rInData;
99 size_t nPosition;
100 UploadSource(uno::Sequence<sal_Int8> const& i_rInData)
101 : rInData(i_rInData)
102 , nPosition(0)
107 auto GetErrorString(CURLcode const rc, char const* const pErrorBuffer = nullptr) -> OString
109 char const* const pMessage( // static fallback
110 (pErrorBuffer && pErrorBuffer[0] != '\0') ? pErrorBuffer : curl_easy_strerror(rc));
111 return OString::Concat("(") + OString::number(sal_Int32(rc)) + ") " + pMessage;
114 auto GetErrorStringMulti(CURLMcode const mc) -> OString
116 return OString::Concat("(") + OString::number(sal_Int32(mc)) + ") " + curl_multi_strerror(mc);
119 /// represent an option to be passed to curl_easy_setopt()
120 struct CurlOption
122 CURLoption const Option;
123 enum class Type
125 Pointer,
126 Long,
127 CurlOffT
129 Type const Tag;
130 union {
131 void const* const pValue;
132 long /*const*/ lValue;
133 curl_off_t /*const*/ cValue;
135 char const* const pExceptionString;
137 CurlOption(CURLoption const i_Option, void const* const i_Value,
138 char const* const i_pExceptionString)
139 : Option(i_Option)
140 , Tag(Type::Pointer)
141 , pValue(i_Value)
142 , pExceptionString(i_pExceptionString)
145 // Depending on platform, curl_off_t may be "long" or a larger type
146 // so cannot use overloading to distinguish these cases.
147 CurlOption(CURLoption const i_Option, curl_off_t const i_Value,
148 char const* const i_pExceptionString, Type const type = Type::Long)
149 : Option(i_Option)
150 , Tag(type)
151 , pExceptionString(i_pExceptionString)
153 static_assert(sizeof(long) <= sizeof(curl_off_t));
154 switch (type)
156 case Type::Long:
157 lValue = i_Value;
158 break;
159 case Type::CurlOffT:
160 cValue = i_Value;
161 break;
162 default:
163 assert(false);
168 // NOBODY will prevent logging the response body in ProcessRequest() exception
169 // handler, so only use it if logging is disabled
170 const CurlOption g_NoBody{ CURLOPT_NOBODY,
171 sal_detail_log_report(SAL_DETAIL_LOG_LEVEL_INFO, "ucb.ucp.webdav.curl")
172 == SAL_DETAIL_LOG_ACTION_IGNORE
173 ? 1L
174 : 0L,
175 nullptr };
177 /// combined guard class to ensure things are released in correct order,
178 /// particularly in ProcessRequest() error handling
179 class Guard
181 private:
182 /// mutex *first* because m_oGuard requires it
183 ::std::unique_lock<::std::mutex> m_Lock;
184 ::std::vector<CurlOption> const m_Options;
185 ::http_dav_ucp::CurlUri const& m_rURI;
186 CURL* const m_pCurl;
188 public:
189 explicit Guard(::std::mutex& rMutex, ::std::vector<CurlOption> aOptions,
190 ::http_dav_ucp::CurlUri const& rURI, CURL* const pCurl)
191 : m_Lock(rMutex, ::std::defer_lock)
192 , m_Options(std::move(aOptions))
193 , m_rURI(rURI)
194 , m_pCurl(pCurl)
196 Acquire();
198 ~Guard()
200 if (m_Lock.owns_lock())
202 Release();
206 void Acquire()
208 assert(!m_Lock.owns_lock());
209 m_Lock.lock();
210 for (auto const& it : m_Options)
212 CURLcode rc(CURL_LAST); // warning C4701
213 if (it.Tag == CurlOption::Type::Pointer)
215 rc = curl_easy_setopt(m_pCurl, it.Option, it.pValue);
217 else if (it.Tag == CurlOption::Type::Long)
219 rc = curl_easy_setopt(m_pCurl, it.Option, it.lValue);
221 else if (it.Tag == CurlOption::Type::CurlOffT)
223 rc = curl_easy_setopt(m_pCurl, it.Option, it.cValue);
225 else
227 assert(false);
229 if (it.pExceptionString != nullptr)
231 if (rc != CURLE_OK)
233 SAL_WARN("ucb.ucp.webdav.curl",
234 "set " << it.pExceptionString << " failed: " << GetErrorString(rc));
235 throw ::http_dav_ucp::DAVException(
236 ::http_dav_ucp::DAVException::DAV_SESSION_CREATE,
237 ::http_dav_ucp::ConnectionEndPointString(m_rURI.GetHost(),
238 m_rURI.GetPort()));
241 else // many of the options cannot fail
243 assert(rc == CURLE_OK);
247 void Release()
249 assert(m_Lock.owns_lock());
250 for (auto const& it : m_Options)
252 CURLcode rc(CURL_LAST); // warning C4701
253 if (it.Tag == CurlOption::Type::Pointer)
255 rc = curl_easy_setopt(m_pCurl, it.Option, nullptr);
257 else if (it.Tag == CurlOption::Type::Long)
259 rc = curl_easy_setopt(m_pCurl, it.Option, 0L);
261 else if (it.Tag == CurlOption::Type::CurlOffT)
263 rc = curl_easy_setopt(m_pCurl, it.Option, curl_off_t(-1));
265 else
267 assert(false);
269 assert(rc == CURLE_OK);
270 (void)rc;
272 m_Lock.unlock();
276 } // namespace
278 namespace http_dav_ucp
280 // libcurl callbacks:
282 static int debug_callback(CURL* handle, curl_infotype type, char* data, size_t size,
283 void* /*userdata*/)
285 char const* pType(nullptr);
286 switch (type)
288 case CURLINFO_TEXT:
289 SAL_INFO("ucb.ucp.webdav.curl", "debug log: " << handle << ": " << data);
290 return 0;
291 case CURLINFO_HEADER_IN:
292 SAL_INFO("ucb.ucp.webdav.curl",
293 "CURLINFO_HEADER_IN: " << handle << ": " << OString(data, size));
294 return 0;
295 case CURLINFO_HEADER_OUT:
297 // unlike IN, this is all headers in one call
298 OString tmp(data, size);
299 sal_Int32 const start(tmp.indexOf("Authorization: "));
300 if (start != -1)
302 sal_Int32 const end(tmp.indexOf("\r\n", start));
303 assert(end != -1);
304 sal_Int32 const len(SAL_N_ELEMENTS("Authorization: ") - 1);
305 tmp = tmp.replaceAt(
306 start + len, end - start - len,
307 Concat2View(OString::number(end - start - len) + " bytes redacted"));
309 SAL_INFO("ucb.ucp.webdav.curl", "CURLINFO_HEADER_OUT: " << handle << ": " << tmp);
310 return 0;
312 case CURLINFO_DATA_IN:
313 pType = "CURLINFO_DATA_IN";
314 break;
315 case CURLINFO_DATA_OUT:
316 pType = "CURLINFO_DATA_OUT";
317 break;
318 case CURLINFO_SSL_DATA_IN:
319 pType = "CURLINFO_SSL_DATA_IN";
320 break;
321 case CURLINFO_SSL_DATA_OUT:
322 pType = "CURLINFO_SSL_DATA_OUT";
323 break;
324 default:
325 SAL_WARN("ucb.ucp.webdav.curl", "unexpected debug log type");
326 return 0;
328 SAL_INFO("ucb.ucp.webdav.curl", "debug log: " << handle << ": " << pType << " " << size);
329 return 0;
332 static size_t write_callback(char* const ptr, size_t const size, size_t const nmemb,
333 void* const userdata)
335 auto* const pTarget(static_cast<DownloadTarget*>(userdata));
336 if (!pTarget) // looks like ~every request may have a response body
338 return nmemb;
340 assert(size == 1); // says the man page
341 (void)size;
342 assert(pTarget->xOutStream.is());
343 auto const oResponseCode(GetResponseCode(pTarget->rHeaders));
344 if (!oResponseCode)
346 return 0; // that is an error
348 // always write, for exception handler in ProcessRequest()
349 // if (200 <= *oResponseCode && *oResponseCode < 300)
353 uno::Sequence<sal_Int8> const data(reinterpret_cast<sal_Int8*>(ptr), nmemb);
354 pTarget->xOutStream->writeBytes(data);
356 catch (...)
358 SAL_WARN("ucb.ucp.webdav.curl", "exception in write_callback");
359 return 0; // error
362 // else: ignore the body? CurlSession will check the status eventually
363 return nmemb;
366 static size_t read_callback(char* const buffer, size_t const size, size_t const nitems,
367 void* const userdata)
369 auto* const pSource(static_cast<UploadSource*>(userdata));
370 assert(pSource);
371 size_t const nBytes(size * nitems);
372 size_t nRet(0);
375 assert(pSource->nPosition <= o3tl::make_unsigned(pSource->rInData.getLength()));
376 nRet = ::std::min<size_t>(pSource->rInData.getLength() - pSource->nPosition, nBytes);
377 ::std::memcpy(buffer, pSource->rInData.getConstArray() + pSource->nPosition, nRet);
378 pSource->nPosition += nRet;
380 catch (...)
382 SAL_WARN("ucb.ucp.webdav.curl", "exception in read_callback");
383 return CURL_READFUNC_ABORT; // error
385 return nRet;
388 static size_t header_callback(char* const buffer, size_t const size, size_t const nitems,
389 void* const userdata)
391 auto* const pHeaders(static_cast<ResponseHeaders*>(userdata));
392 assert(pHeaders);
393 #if 0
394 if (!pHeaders) // TODO maybe not needed in every request? not sure
396 return nitems;
398 #endif
399 assert(size == 1); // says the man page
400 (void)size;
403 if (nitems <= 2)
405 // end of header, body follows...
406 if (pHeaders->HeaderFields.empty())
408 SAL_WARN("ucb.ucp.webdav.curl", "header_callback: empty header?");
409 return 0; // error
411 // unfortunately there's no separate callback when the body begins,
412 // so have to manually retrieve the status code here
413 long statusCode(SC_NONE);
414 auto rc = curl_easy_getinfo(pHeaders->pCurl, CURLINFO_RESPONSE_CODE, &statusCode);
415 assert(rc == CURLE_OK);
416 (void)rc;
417 // always put the current response code here - wasn't necessarily in this header
418 pHeaders->HeaderFields.back().second.emplace(statusCode);
420 else if (buffer[0] == ' ' || buffer[0] == '\t') // folded header field?
422 size_t i(0);
425 ++i;
426 } while (i == ' ' || i == '\t');
427 if (pHeaders->HeaderFields.empty() || pHeaders->HeaderFields.back().second
428 || pHeaders->HeaderFields.back().first.empty())
430 SAL_WARN("ucb.ucp.webdav.curl",
431 "header_callback: folded header field without start");
432 return 0; // error
434 pHeaders->HeaderFields.back().first.back()
435 += OString::Concat(" ") + ::std::string_view(&buffer[i], nitems - i);
437 else
439 if (pHeaders->HeaderFields.empty() || pHeaders->HeaderFields.back().second)
441 pHeaders->HeaderFields.emplace_back();
443 pHeaders->HeaderFields.back().first.emplace_back(OString(buffer, nitems));
446 catch (...)
448 SAL_WARN("ucb.ucp.webdav.curl", "exception in header_callback");
449 return 0; // error
451 return nitems;
454 static auto ProcessHeaders(::std::vector<OString> const& rHeaders) -> ::std::map<OUString, OUString>
456 ::std::map<OUString, OUString> ret;
457 for (OString const& rLine : rHeaders)
459 OString line;
460 if (!rLine.endsWith("\r\n", &line))
462 SAL_WARN("ucb.ucp.webdav.curl", "invalid header field (no CRLF)");
463 continue;
465 if (line.startsWith("HTTP/") // first line
466 || line.isEmpty()) // last line
468 continue;
470 auto const nColon(line.indexOf(':'));
471 if (nColon == -1)
474 SAL_WARN("ucb.ucp.webdav.curl", "invalid header field (no :)");
476 continue;
478 if (nColon == 0)
480 SAL_WARN("ucb.ucp.webdav.curl", "invalid header field (empty name)");
481 continue;
483 // case insensitive; must be ASCII
484 auto const name(::rtl::OStringToOUString(line.copy(0, nColon).toAsciiLowerCase(),
485 RTL_TEXTENCODING_ASCII_US));
486 sal_Int32 nStart(nColon + 1);
487 while (nStart < line.getLength() && (line[nStart] == ' ' || line[nStart] == '\t'))
489 ++nStart;
491 sal_Int32 nEnd(line.getLength());
492 while (nStart < nEnd && (line[nEnd - 1] == ' ' || line[nEnd - 1] == '\t'))
494 --nEnd;
496 // RFC 7230 says that only ASCII works reliably anyway (neon also did this)
497 auto const value(::rtl::OStringToOUString(line.subView(nStart, nEnd - nStart),
498 RTL_TEXTENCODING_ASCII_US));
499 auto const it(ret.find(name));
500 if (it != ret.end())
502 it->second = it->second + "," + value;
504 else
506 ret[name] = value;
509 return ret;
512 static auto ExtractRequestedHeaders(
513 ResponseHeaders const& rHeaders,
514 ::std::pair<::std::vector<OUString> const&, DAVResource&> const* const pRequestedHeaders)
515 -> void
517 ::std::map<OUString, OUString> const headerMap(
518 ProcessHeaders(rHeaders.HeaderFields.back().first));
519 if (pRequestedHeaders)
521 for (OUString const& rHeader : pRequestedHeaders->first)
523 auto const it(headerMap.find(rHeader.toAsciiLowerCase()));
524 if (it != headerMap.end())
526 DAVPropertyValue value;
527 value.IsCaseSensitive = false;
528 value.Name = it->first;
529 value.Value <<= it->second;
530 pRequestedHeaders->second.properties.push_back(value);
536 // this appears to be the only way to get the "realm" from libcurl
537 static auto ExtractRealm(ResponseHeaders const& rHeaders, char const* const pAuthHeaderName)
538 -> ::std::optional<OUString>
540 ::std::map<OUString, OUString> const headerMap(
541 ProcessHeaders(rHeaders.HeaderFields.back().first));
542 auto const it(headerMap.find(OUString::createFromAscii(pAuthHeaderName).toAsciiLowerCase()));
543 if (it == headerMap.end())
545 SAL_WARN("ucb.ucp.webdav.curl", "cannot find auth header");
546 return {};
548 // It may be possible that the header contains multiple methods each with
549 // a different realm - extract only the first one bc the downstream API
550 // only supports one anyway.
551 // case insensitive!
552 auto i(it->second.toAsciiLowerCase().indexOf("realm="));
553 // is optional
554 if (i == -1)
556 SAL_INFO("ucb.ucp.webdav.curl", "auth header has no realm");
557 return {};
559 // no whitespace allowed before or after =
560 i += ::std::strlen("realm=");
561 if (it->second.getLength() < i + 2 || it->second[i] != '\"')
563 SAL_WARN("ucb.ucp.webdav.curl", "no realm value");
564 return {};
566 ++i;
567 OUStringBuffer buf;
568 while (i < it->second.getLength() && it->second[i] != '\"')
570 if (it->second[i] == '\\') // quoted-pair escape
572 ++i;
573 if (it->second.getLength() <= i)
575 SAL_WARN("ucb.ucp.webdav.curl", "unterminated quoted-pair");
576 return {};
579 buf.append(it->second[i]);
580 ++i;
582 if (it->second.getLength() <= i)
584 SAL_WARN("ucb.ucp.webdav.curl", "unterminated realm");
585 return {};
587 return buf.makeStringAndClear();
590 CurlSession::CurlSession(uno::Reference<uno::XComponentContext> xContext,
591 ::rtl::Reference<DAVSessionFactory> const& rpFactory, OUString const& rURI,
592 uno::Sequence<beans::NamedValue> const& rFlags,
593 ::ucbhelper::InternetProxyDecider const& rProxyDecider)
594 : DAVSession(rpFactory)
595 , m_xContext(std::move(xContext))
596 , m_Flags(rFlags)
597 , m_URI(rURI)
598 , m_Proxy(rProxyDecider.getProxy(m_URI.GetScheme(), m_URI.GetHost(), m_URI.GetPort()))
600 assert(m_URI.GetScheme() == "http" || m_URI.GetScheme() == "https");
601 m_pCurlMulti.reset(curl_multi_init());
602 if (!m_pCurlMulti)
604 SAL_WARN("ucb.ucp.webdav.curl", "curl_multi_init failed");
605 throw DAVException(DAVException::DAV_SESSION_CREATE,
606 ConnectionEndPointString(m_URI.GetHost(), m_URI.GetPort()));
608 m_pCurl.reset(curl_easy_init());
609 if (!m_pCurl)
611 SAL_WARN("ucb.ucp.webdav.curl", "curl_easy_init failed");
612 throw DAVException(DAVException::DAV_SESSION_CREATE,
613 ConnectionEndPointString(m_URI.GetHost(), m_URI.GetPort()));
615 curl_version_info_data const* const pVersion(curl_version_info(CURLVERSION_NOW));
616 assert(pVersion);
617 SAL_INFO("ucb.ucp.webdav.curl",
618 "curl version: " << pVersion->version << " " << pVersion->host
619 << " features: " << ::std::hex << pVersion->features << " ssl: "
620 << pVersion->ssl_version << " libz: " << pVersion->libz_version);
621 // Make sure a User-Agent header is always included, as at least
622 // en.wikipedia.org:80 forces back 403 "Scripts should use an informative
623 // User-Agent string with contact information, or they may be IP-blocked
624 // without notice" otherwise:
625 OString const useragent(
626 OString::Concat("LibreOffice " LIBO_VERSION_DOTTED " denylistedbackend/")
627 + ::std::string_view(pVersion->version, strlen(pVersion->version)) + " "
628 + pVersion->ssl_version);
629 // looks like an explicit "User-Agent" header in CURLOPT_HTTPHEADER
630 // will override CURLOPT_USERAGENT, see Curl_http_useragent(), so no need
631 // to check anything here
632 auto rc = curl_easy_setopt(m_pCurl.get(), CURLOPT_USERAGENT, useragent.getStr());
633 if (rc != CURLE_OK)
635 SAL_WARN("ucb.ucp.webdav.curl", "CURLOPT_USERAGENT failed: " << GetErrorString(rc));
636 throw DAVException(DAVException::DAV_SESSION_CREATE,
637 ConnectionEndPointString(m_URI.GetHost(), m_URI.GetPort()));
639 m_ErrorBuffer[0] = '\0';
640 // this supposedly gives the highest quality error reporting
641 rc = curl_easy_setopt(m_pCurl.get(), CURLOPT_ERRORBUFFER, m_ErrorBuffer);
642 assert(rc == CURLE_OK);
643 #if 1
644 // just for debugging...
645 rc = curl_easy_setopt(m_pCurl.get(), CURLOPT_DEBUGFUNCTION, debug_callback);
646 assert(rc == CURLE_OK);
647 #endif
648 rc = curl_easy_setopt(m_pCurl.get(), CURLOPT_VERBOSE, 1L);
649 assert(rc == CURLE_OK);
650 // accept any encoding supported by libcurl
651 rc = curl_easy_setopt(m_pCurl.get(), CURLOPT_ACCEPT_ENCODING, "");
652 if (rc != CURLE_OK)
654 SAL_WARN("ucb.ucp.webdav.curl", "CURLOPT_ACCEPT_ENCODING failed: " << GetErrorString(rc));
655 throw DAVException(DAVException::DAV_SESSION_CREATE,
656 ConnectionEndPointString(m_URI.GetHost(), m_URI.GetPort()));
658 auto const connectTimeout(officecfg::Inet::Settings::ConnectTimeout::get());
659 // default is 300s
660 rc = curl_easy_setopt(m_pCurl.get(), CURLOPT_CONNECTTIMEOUT,
661 ::std::max<long>(2L, ::std::min<long>(connectTimeout, 180L)));
662 if (rc != CURLE_OK)
664 SAL_WARN("ucb.ucp.webdav.curl", "CURLOPT_CONNECTTIMEOUT failed: " << GetErrorString(rc));
665 throw DAVException(DAVException::DAV_SESSION_CREATE,
666 ConnectionEndPointString(m_URI.GetHost(), m_URI.GetPort()));
668 auto const readTimeout(officecfg::Inet::Settings::ReadTimeout::get());
669 m_nReadTimeout = ::std::max<int>(20, ::std::min<long>(readTimeout, 180)) * 1000;
670 // default is infinite
671 rc = curl_easy_setopt(m_pCurl.get(), CURLOPT_TIMEOUT, 300L);
672 if (rc != CURLE_OK)
674 SAL_WARN("ucb.ucp.webdav.curl", "CURLOPT_TIMEOUT failed: " << GetErrorString(rc));
675 throw DAVException(DAVException::DAV_SESSION_CREATE,
676 ConnectionEndPointString(m_URI.GetHost(), m_URI.GetPort()));
678 rc = curl_easy_setopt(m_pCurl.get(), CURLOPT_WRITEFUNCTION, &write_callback);
679 assert(rc == CURLE_OK);
680 rc = curl_easy_setopt(m_pCurl.get(), CURLOPT_READFUNCTION, &read_callback);
681 assert(rc == CURLE_OK);
682 rc = curl_easy_setopt(m_pCurl.get(), CURLOPT_HEADERFUNCTION, &header_callback);
683 assert(rc == CURLE_OK);
684 ::InitCurl_easy(m_pCurl.get());
685 // tdf#149921 by default, with schannel (WNT) connection fails if revocation
686 // lists cannot be checked; try to limit the checking to when revocation
687 // lists can actually be retrieved (usually not the case for self-signed CA)
688 #if CURL_AT_LEAST_VERSION(7, 70, 0)
689 rc = curl_easy_setopt(m_pCurl.get(), CURLOPT_SSL_OPTIONS, CURLSSLOPT_REVOKE_BEST_EFFORT);
690 assert(rc == CURLE_OK);
691 rc = curl_easy_setopt(m_pCurl.get(), CURLOPT_PROXY_SSL_OPTIONS, CURLSSLOPT_REVOKE_BEST_EFFORT);
692 assert(rc == CURLE_OK);
693 #endif
694 // set this initially, may be overwritten during authentication
695 rc = curl_easy_setopt(m_pCurl.get(), CURLOPT_HTTPAUTH, CURLAUTH_ANY);
696 assert(rc == CURLE_OK); // ANY is always available
697 // always set CURLOPT_PROXY to suppress proxy detection in libcurl
698 OString const utf8Proxy(OUStringToOString(m_Proxy.aName, RTL_TEXTENCODING_UTF8));
699 rc = curl_easy_setopt(m_pCurl.get(), CURLOPT_PROXY, utf8Proxy.getStr());
700 if (rc != CURLE_OK)
702 SAL_WARN("ucb.ucp.webdav.curl", "CURLOPT_PROXY failed: " << GetErrorString(rc));
703 throw DAVException(DAVException::DAV_SESSION_CREATE,
704 ConnectionEndPointString(m_Proxy.aName, m_Proxy.nPort));
706 if (!m_Proxy.aName.isEmpty())
708 rc = curl_easy_setopt(m_pCurl.get(), CURLOPT_PROXYPORT, static_cast<long>(m_Proxy.nPort));
709 assert(rc == CURLE_OK);
710 // set this initially, may be overwritten during authentication
711 rc = curl_easy_setopt(m_pCurl.get(), CURLOPT_PROXYAUTH, CURLAUTH_ANY);
712 assert(rc == CURLE_OK); // ANY is always available
714 auto const it(::std::find_if(m_Flags.begin(), m_Flags.end(),
715 [](auto const& rFlag) { return rFlag.Name == "KeepAlive"; }));
716 if (it != m_Flags.end() && it->Value.get<bool>())
718 // neon would close the connection from ne_end_request(), this seems
719 // to be the equivalent and not CURLOPT_TCP_KEEPALIVE
720 rc = curl_easy_setopt(m_pCurl.get(), CURLOPT_FORBID_REUSE, 1L);
721 assert(rc == CURLE_OK);
723 // If WOPI-like host has self-signed certificate, it's not possible to insert images
724 // to the document, so here is a compromise. The user has already accepted the self
725 // signed certificate in the browser, when we get here.
726 if (comphelper::LibreOfficeKit::isActive())
728 rc = curl_easy_setopt(m_pCurl.get(), CURLOPT_SSL_VERIFYPEER, 0L);
729 assert(rc == CURLE_OK);
733 CurlSession::~CurlSession() {}
735 auto CurlSession::CanUse(OUString const& rURI, uno::Sequence<beans::NamedValue> const& rFlags)
736 -> bool
740 CurlUri const uri(rURI);
742 return m_URI.GetScheme() == uri.GetScheme() && m_URI.GetHost() == uri.GetHost()
743 && m_URI.GetPort() == uri.GetPort() && m_Flags == rFlags;
745 catch (DAVException const&)
747 return false;
751 auto CurlSession::UsesProxy() -> bool
753 assert(m_URI.GetScheme() == "http" || m_URI.GetScheme() == "https");
754 return !m_Proxy.aName.isEmpty();
757 auto CurlSession::abort() -> void
759 // note: abort() was a no-op since OOo 3.2 and before that it crashed.
760 bool expected(false);
761 // it would be pointless to lock m_Mutex here as the other thread holds it
762 if (m_AbortFlag.compare_exchange_strong(expected, true))
764 // This function looks safe to call without m_Mutex as long as the
765 // m_pCurlMulti handle is not destroyed, and the caller must own a ref
766 // to this object which keeps it alive; it should cause poll to return.
767 curl_multi_wakeup(m_pCurlMulti.get());
771 /// this is just a bunch of static member functions called from CurlSession
772 struct CurlProcessor
774 static auto URIReferenceToURI(CurlSession& rSession, std::u16string_view rURIReference)
775 -> CurlUri;
777 static auto ProcessRequestImpl(
778 CurlSession& rSession, CurlUri const& rURI, OUString const& rMethod,
779 curl_slist* pRequestHeaderList, uno::Reference<io::XOutputStream> const* pxOutStream,
780 uno::Sequence<sal_Int8> const* pInData,
781 ::std::pair<::std::vector<OUString> const&, DAVResource&> const* pRequestedHeaders,
782 ResponseHeaders& rHeaders) -> void;
784 static auto ProcessRequest(
785 CurlSession& rSession, CurlUri const& rURI, OUString const& rMethod,
786 ::std::vector<CurlOption> const& rOptions, DAVRequestEnvironment const* pEnv,
787 ::std::unique_ptr<curl_slist, deleter_from_fn<curl_slist, curl_slist_free_all>>
788 pRequestHeaderList,
789 uno::Reference<io::XOutputStream> const* pxOutStream,
790 uno::Reference<io::XInputStream> const* pxInStream,
791 ::std::pair<::std::vector<OUString> const&, DAVResource&> const* pRequestedHeaders) -> void;
793 static auto
794 PropFind(CurlSession& rSession, CurlUri const& rURI, Depth depth,
795 ::std::tuple<::std::vector<OUString> const&, ::std::vector<DAVResource>* const,
796 ::std::vector<ucb::Lock>* const> const* o_pRequestedProperties,
797 ::std::vector<DAVResourceInfo>* const o_pResourceInfos,
798 DAVRequestEnvironment const& rEnv) -> void;
800 static auto MoveOrCopy(CurlSession& rSession, std::u16string_view rSourceURIReference,
801 ::std::u16string_view rDestinationURI, DAVRequestEnvironment const& rEnv,
802 bool isOverwrite, char const* pMethod) -> void;
804 static auto Lock(CurlSession& rSession, CurlUri const& rURI, DAVRequestEnvironment const* pEnv,
805 ::std::unique_ptr<curl_slist, deleter_from_fn<curl_slist, curl_slist_free_all>>
806 pRequestHeaderList,
807 uno::Reference<io::XInputStream> const* pxInStream)
808 -> ::std::vector<::std::pair<ucb::Lock, sal_Int32>>;
810 static auto Unlock(CurlSession& rSession, CurlUri const& rURI,
811 DAVRequestEnvironment const* pEnv) -> void;
814 auto CurlProcessor::URIReferenceToURI(CurlSession& rSession, std::u16string_view rURIReference)
815 -> CurlUri
817 // No need to acquire rSession.m_Mutex because accessed members are const.
818 if (rSession.UsesProxy())
819 // very odd, but see DAVResourceAccess::getRequestURI() :-/
821 assert(o3tl::starts_with(rURIReference, u"http://")
822 || o3tl::starts_with(rURIReference, u"https://"));
823 return CurlUri(rURIReference);
825 else
827 assert(o3tl::starts_with(rURIReference, u"/"));
828 return rSession.m_URI.CloneWithRelativeRefPathAbsolute(rURIReference);
832 /// main function to initiate libcurl requests
833 auto CurlProcessor::ProcessRequestImpl(
834 CurlSession& rSession, CurlUri const& rURI, OUString const& rMethod,
835 curl_slist* const pRequestHeaderList,
836 uno::Reference<io::XOutputStream> const* const pxOutStream,
837 uno::Sequence<sal_Int8> const* const pInData,
838 ::std::pair<::std::vector<OUString> const&, DAVResource&> const* const pRequestedHeaders,
839 ResponseHeaders& rHeaders) -> void
841 ::comphelper::ScopeGuard const g([&]() {
842 auto rc = curl_easy_setopt(rSession.m_pCurl.get(), CURLOPT_HEADERDATA, nullptr);
843 assert(rc == CURLE_OK);
844 (void)rc;
845 if (pxOutStream)
847 rc = curl_easy_setopt(rSession.m_pCurl.get(), CURLOPT_WRITEDATA, nullptr);
848 assert(rc == CURLE_OK);
850 if (pInData)
852 rc = curl_easy_setopt(rSession.m_pCurl.get(), CURLOPT_READDATA, nullptr);
853 assert(rc == CURLE_OK);
854 rc = curl_easy_setopt(rSession.m_pCurl.get(), CURLOPT_UPLOAD, 0L);
855 assert(rc == CURLE_OK);
857 if (pRequestHeaderList)
859 rc = curl_easy_setopt(rSession.m_pCurl.get(), CURLOPT_HTTPHEADER, nullptr);
860 assert(rc == CURLE_OK);
864 if (pRequestHeaderList)
866 auto rc = curl_easy_setopt(rSession.m_pCurl.get(), CURLOPT_HTTPHEADER, pRequestHeaderList);
867 assert(rc == CURLE_OK);
868 (void)rc;
871 auto rc = curl_easy_setopt(rSession.m_pCurl.get(), CURLOPT_CURLU, rURI.GetCURLU());
872 assert(rc == CURLE_OK); // can't fail since 7.63.0
874 rc = curl_easy_setopt(rSession.m_pCurl.get(), CURLOPT_HEADERDATA, &rHeaders);
875 assert(rc == CURLE_OK);
876 ::std::optional<DownloadTarget> oDownloadTarget;
877 if (pxOutStream)
879 oDownloadTarget.emplace(*pxOutStream, rHeaders);
880 rc = curl_easy_setopt(rSession.m_pCurl.get(), CURLOPT_WRITEDATA, &*oDownloadTarget);
881 assert(rc == CURLE_OK);
883 ::std::optional<UploadSource> oUploadSource;
884 if (pInData)
886 oUploadSource.emplace(*pInData);
887 rc = curl_easy_setopt(rSession.m_pCurl.get(), CURLOPT_READDATA, &*oUploadSource);
888 assert(rc == CURLE_OK);
890 rSession.m_ErrorBuffer[0] = '\0';
892 // note: easy handle must be added for *every* transfer!
893 // otherwise it gets stuck in MSTATE_MSGSENT forever after 1st transfer
894 auto mc = curl_multi_add_handle(rSession.m_pCurlMulti.get(), rSession.m_pCurl.get());
895 if (mc != CURLM_OK)
897 SAL_WARN("ucb.ucp.webdav.curl",
898 "curl_multi_add_handle failed: " << GetErrorStringMulti(mc));
899 throw DAVException(
900 DAVException::DAV_SESSION_CREATE,
901 ConnectionEndPointString(rSession.m_URI.GetHost(), rSession.m_URI.GetPort()));
903 ::comphelper::ScopeGuard const gg([&]() {
904 mc = curl_multi_remove_handle(rSession.m_pCurlMulti.get(), rSession.m_pCurl.get());
905 if (mc != CURLM_OK)
907 SAL_WARN("ucb.ucp.webdav.curl",
908 "curl_multi_remove_handle failed: " << GetErrorStringMulti(mc));
912 // this is where libcurl actually does something
913 rc = CURL_LAST; // clear current value
914 int nRunning;
917 mc = curl_multi_perform(rSession.m_pCurlMulti.get(), &nRunning);
918 if (mc != CURLM_OK)
920 SAL_WARN("ucb.ucp.webdav.curl",
921 "curl_multi_perform failed: " << GetErrorStringMulti(mc));
922 throw DAVException(
923 DAVException::DAV_HTTP_CONNECT,
924 ConnectionEndPointString(rSession.m_URI.GetHost(), rSession.m_URI.GetPort()));
926 if (nRunning == 0)
927 { // short request like HEAD on loopback could be done in first call
928 break;
930 int nFDs;
931 mc = curl_multi_poll(rSession.m_pCurlMulti.get(), nullptr, 0, rSession.m_nReadTimeout,
932 &nFDs);
933 if (mc != CURLM_OK)
935 SAL_WARN("ucb.ucp.webdav.curl", "curl_multi_poll failed: " << GetErrorStringMulti(mc));
936 throw DAVException(
937 DAVException::DAV_HTTP_CONNECT,
938 ConnectionEndPointString(rSession.m_URI.GetHost(), rSession.m_URI.GetPort()));
940 if (rSession.m_AbortFlag.load())
941 { // flag was set by abort() -> not sure what exception to throw?
942 throw DAVException(DAVException::DAV_HTTP_ERROR, "abort() was called", 0);
944 } while (nRunning != 0);
945 // there should be exactly 1 CURLMsg now, but the interface is
946 // extensible so future libcurl versions could yield additional things
949 CURLMsg const* const pMsg = curl_multi_info_read(rSession.m_pCurlMulti.get(), &nRunning);
950 if (pMsg && pMsg->msg == CURLMSG_DONE)
952 assert(pMsg->easy_handle == rSession.m_pCurl.get());
953 rc = pMsg->data.result;
955 else
957 SAL_WARN("ucb.ucp.webdav.curl", "curl_multi_info_read unexpected result");
959 } while (nRunning != 0);
961 // error handling part 1: libcurl errors
962 if (rc != CURLE_OK)
964 // TODO: is there any value in extracting CURLINFO_OS_ERRNO
965 SAL_WARN("ucb.ucp.webdav.curl",
966 "curl_easy_perform failed: " << GetErrorString(rc, rSession.m_ErrorBuffer));
967 switch (rc)
969 case CURLE_UNSUPPORTED_PROTOCOL:
970 throw DAVException(DAVException::DAV_UNSUPPORTED);
971 case CURLE_COULDNT_RESOLVE_PROXY:
972 throw DAVException(
973 DAVException::DAV_HTTP_LOOKUP,
974 ConnectionEndPointString(rSession.m_Proxy.aName, rSession.m_Proxy.nPort));
975 case CURLE_COULDNT_RESOLVE_HOST:
976 throw DAVException(
977 DAVException::DAV_HTTP_LOOKUP,
978 ConnectionEndPointString(rSession.m_URI.GetHost(), rSession.m_URI.GetPort()));
979 case CURLE_COULDNT_CONNECT:
980 case CURLE_SSL_CONNECT_ERROR:
981 case CURLE_SSL_CERTPROBLEM:
982 case CURLE_SSL_CIPHER:
983 case CURLE_PEER_FAILED_VERIFICATION:
984 case CURLE_SSL_ISSUER_ERROR:
985 case CURLE_SSL_PINNEDPUBKEYNOTMATCH:
986 case CURLE_SSL_INVALIDCERTSTATUS:
987 case CURLE_FAILED_INIT:
988 #if CURL_AT_LEAST_VERSION(7, 69, 0)
989 case CURLE_QUIC_CONNECT_ERROR:
990 #endif
991 throw DAVException(
992 DAVException::DAV_HTTP_CONNECT,
993 ConnectionEndPointString(rSession.m_URI.GetHost(), rSession.m_URI.GetPort()));
994 case CURLE_REMOTE_ACCESS_DENIED:
995 case CURLE_LOGIN_DENIED:
996 case CURLE_AUTH_ERROR:
997 throw DAVException(
998 DAVException::DAV_HTTP_AUTH, // probably?
999 ConnectionEndPointString(rSession.m_URI.GetHost(), rSession.m_URI.GetPort()));
1000 case CURLE_WRITE_ERROR:
1001 case CURLE_READ_ERROR: // error returned from our callbacks
1002 case CURLE_OUT_OF_MEMORY:
1003 case CURLE_ABORTED_BY_CALLBACK:
1004 case CURLE_BAD_FUNCTION_ARGUMENT:
1005 case CURLE_SEND_ERROR:
1006 case CURLE_RECV_ERROR:
1007 case CURLE_SSL_CACERT_BADFILE:
1008 case CURLE_SSL_CRL_BADFILE:
1009 case CURLE_RECURSIVE_API_CALL:
1010 throw DAVException(
1011 DAVException::DAV_HTTP_FAILED,
1012 ConnectionEndPointString(rSession.m_URI.GetHost(), rSession.m_URI.GetPort()));
1013 case CURLE_OPERATION_TIMEDOUT:
1014 throw DAVException(
1015 DAVException::DAV_HTTP_TIMEOUT,
1016 ConnectionEndPointString(rSession.m_URI.GetHost(), rSession.m_URI.GetPort()));
1017 default: // lots of generic errors
1018 throw DAVException(DAVException::DAV_HTTP_ERROR, "", 0);
1021 // error handling part 2: HTTP status codes
1022 long statusCode(SC_NONE);
1023 rc = curl_easy_getinfo(rSession.m_pCurl.get(), CURLINFO_RESPONSE_CODE, &statusCode);
1024 assert(rc == CURLE_OK);
1025 assert(statusCode != SC_NONE); // ??? should be error returned from perform?
1026 SAL_INFO("ucb.ucp.webdav.curl", "HTTP status code: " << statusCode);
1027 if (statusCode < 300)
1029 // neon did this regardless of status or even error, which seems odd
1030 ExtractRequestedHeaders(rHeaders, pRequestedHeaders);
1032 else
1034 // create message containing the HTTP method and response status line
1035 OUString statusLine("\n" + rMethod + "\n=>\n");
1036 if (!rHeaders.HeaderFields.empty() && !rHeaders.HeaderFields.back().first.empty()
1037 && rHeaders.HeaderFields.back().first.front().startsWith("HTTP"))
1039 statusLine += ::rtl::OStringToOUString(
1040 ::o3tl::trim(rHeaders.HeaderFields.back().first.front()),
1041 RTL_TEXTENCODING_ASCII_US);
1043 switch (statusCode)
1045 case SC_REQUEST_TIMEOUT:
1047 throw DAVException(
1048 DAVException::DAV_HTTP_TIMEOUT,
1049 ConnectionEndPointString(rSession.m_URI.GetHost(), rSession.m_URI.GetPort()));
1050 break;
1052 case SC_MOVED_PERMANENTLY:
1053 case SC_MOVED_TEMPORARILY:
1054 case SC_SEE_OTHER:
1055 case SC_TEMPORARY_REDIRECT:
1057 // could also use CURLOPT_FOLLOWLOCATION but apparently the
1058 // upper layer wants to know about redirects?
1059 char* pRedirectURL(nullptr);
1060 rc = curl_easy_getinfo(rSession.m_pCurl.get(), CURLINFO_REDIRECT_URL,
1061 &pRedirectURL);
1062 assert(rc == CURLE_OK);
1063 if (pRedirectURL)
1065 // Sharepoint 2016 workaround: contains unencoded U+0020
1066 OUString const redirectURL(::rtl::Uri::encode(
1067 pRedirectURL
1068 ? OUString(pRedirectURL, strlen(pRedirectURL), RTL_TEXTENCODING_UTF8)
1069 : OUString(),
1070 rtl_UriCharClassUric, rtl_UriEncodeKeepEscapes, RTL_TEXTENCODING_UTF8));
1072 throw DAVException(DAVException::DAV_HTTP_REDIRECT, redirectURL);
1074 [[fallthrough]];
1076 default:
1077 throw DAVException(DAVException::DAV_HTTP_ERROR, statusLine, statusCode);
1081 if (pxOutStream)
1083 (*pxOutStream)->closeOutput(); // signal EOF
1087 static auto TryRemoveExpiredLockToken(CurlSession& rSession, CurlUri const& rURI,
1088 DAVRequestEnvironment const* const pEnv) -> bool
1090 if (!pEnv)
1092 // caller was a NonInteractive_*LOCK function anyway, its caller is LockStore
1093 return false;
1095 OUString const* const pToken(g_Init.LockStore.getLockTokenForURI(rURI.GetURI(), nullptr));
1096 if (!pToken)
1098 return false;
1102 // determine validity of existing lock via lockdiscovery request
1103 ::std::vector<OUString> const propertyNames{ DAVProperties::LOCKDISCOVERY };
1104 ::std::vector<ucb::Lock> locks;
1105 ::std::tuple<::std::vector<OUString> const&, ::std::vector<DAVResource>* const,
1106 ::std::vector<ucb::Lock>* const> const args(propertyNames, nullptr, &locks);
1108 CurlProcessor::PropFind(rSession, rURI, DAVZERO, &args, nullptr, *pEnv);
1110 // https://datatracker.ietf.org/doc/html/rfc4918#section-15.8
1111 // The response MAY not contain tokens, but hopefully it
1112 // will if client is properly authenticated.
1113 if (::std::any_of(locks.begin(), locks.end(), [pToken](ucb::Lock const& rLock) {
1114 return ::std::any_of(
1115 rLock.LockTokens.begin(), rLock.LockTokens.end(),
1116 [pToken](OUString const& rToken) { return *pToken == rToken; });
1119 return false; // still have the lock
1122 SAL_INFO("ucb.ucp.webdav.curl",
1123 "lock token expired, removing: " << rURI.GetURI() << " " << *pToken);
1124 g_Init.LockStore.removeLock(rURI.GetURI());
1125 return true;
1127 catch (DAVException const&)
1129 return false; // ignore, the caller already has a better exception
1133 auto CurlProcessor::ProcessRequest(
1134 CurlSession& rSession, CurlUri const& rURI, OUString const& rMethod,
1135 ::std::vector<CurlOption> const& rOptions, DAVRequestEnvironment const* const pEnv,
1136 ::std::unique_ptr<curl_slist, deleter_from_fn<curl_slist, curl_slist_free_all>>
1137 pRequestHeaderList,
1138 uno::Reference<io::XOutputStream> const* const pxOutStream,
1139 uno::Reference<io::XInputStream> const* const pxInStream,
1140 ::std::pair<::std::vector<OUString> const&, DAVResource&> const* const pRequestedHeaders)
1141 -> void
1143 if (pEnv)
1144 { // add custom request headers passed by caller
1145 for (auto const& rHeader : pEnv->m_aRequestHeaders)
1147 OString const utf8Header(
1148 OUStringToOString(rHeader.first, RTL_TEXTENCODING_ASCII_US) + ": "
1149 + OUStringToOString(rHeader.second, RTL_TEXTENCODING_ASCII_US));
1150 pRequestHeaderList.reset(
1151 curl_slist_append(pRequestHeaderList.release(), utf8Header.getStr()));
1152 if (!pRequestHeaderList)
1154 throw uno::RuntimeException("curl_slist_append failed");
1159 uno::Sequence<sal_Int8> data;
1160 if (pxInStream)
1162 uno::Reference<io::XSeekable> const xSeekable(*pxInStream, uno::UNO_QUERY);
1163 if (xSeekable.is())
1165 auto const len(xSeekable->getLength() - xSeekable->getPosition());
1166 if ((**pxInStream).readBytes(data, len) != len)
1168 throw uno::RuntimeException("short readBytes");
1171 else
1173 ::std::vector<uno::Sequence<sal_Int8>> bufs;
1174 bool isDone(false);
1177 bufs.emplace_back();
1178 isDone = (**pxInStream).readSomeBytes(bufs.back(), 65536) == 0;
1179 } while (!isDone);
1180 sal_Int32 nSize(0);
1181 for (auto const& rBuf : bufs)
1183 if (o3tl::checked_add(nSize, rBuf.getLength(), nSize))
1185 throw std::bad_alloc(); // too large for Sequence
1188 data.realloc(nSize);
1189 size_t nCopied(0);
1190 for (auto const& rBuf : bufs)
1192 ::std::memcpy(data.getArray() + nCopied, rBuf.getConstArray(), rBuf.getLength());
1193 nCopied += rBuf.getLength(); // can't overflow
1198 // Clear flag before transfer starts; only a transfer started before
1199 // calling abort() will be aborted, not one started later.
1200 rSession.m_AbortFlag.store(false);
1202 Guard guard(rSession.m_Mutex, rOptions, rURI, rSession.m_pCurl.get());
1204 // authentication data may be in the URI, or requested via XInteractionHandler
1205 struct Auth
1207 OUString UserName;
1208 OUString PassWord;
1209 decltype(CURLAUTH_ANY) AuthMask; ///< allowed auth methods
1210 Auth(OUString aUserName, OUString aPassword, decltype(CURLAUTH_ANY) const & rAuthMask)
1211 : UserName(std::move(aUserName))
1212 , PassWord(std::move(aPassword))
1213 , AuthMask(rAuthMask)
1217 ::std::optional<Auth> oAuth;
1218 ::std::optional<Auth> oAuthProxy;
1219 if (pEnv && !rSession.m_isAuthenticatedProxy && !rSession.m_Proxy.aName.isEmpty())
1223 // the hope is that this must be a URI
1224 CurlUri const uri(rSession.m_Proxy.aName);
1225 if (!uri.GetUser().isEmpty() || !uri.GetPassword().isEmpty())
1227 oAuthProxy.emplace(uri.GetUser(), uri.GetPassword(), CURLAUTH_ANY);
1230 catch (DAVException&)
1232 // ignore any parsing failure here
1235 decltype(CURLAUTH_ANY) const authSystem(CURLAUTH_NEGOTIATE | CURLAUTH_NTLM | CURLAUTH_NTLM_WB);
1236 if (pRequestedHeaders || (pEnv && !rSession.m_isAuthenticated))
1238 // m_aRequestURI *may* be a path or *may* be URI - wtf
1239 // TODO: why is there this m_aRequestURI and also rURIReference argument?
1240 // ... only caller is DAVResourceAccess - always identical except MOVE/COPY
1241 // which doesn't work if it's just a URI reference so let's just use
1242 // rURIReference via rURI instead
1243 #if 0
1244 CurlUri const uri(pEnv->m_aRequestURI);
1245 #endif
1246 // note: due to parsing bug pwd didn't work in previous webdav ucps
1247 if (pEnv && !rSession.m_isAuthenticated
1248 && (!rURI.GetUser().isEmpty() || !rURI.GetPassword().isEmpty()))
1250 oAuth.emplace(rURI.GetUser(), rURI.GetPassword(), CURLAUTH_ANY);
1252 if (pRequestedHeaders)
1254 // note: Previously this would be the rURIReference directly but
1255 // that ends up in CurlUri anyway and curl is unhappy.
1256 // But it looks like all consumers of this .uri are interested
1257 // only in the path, so it shouldn't make a difference to give
1258 // the entire URI when the caller extracts the path anyway.
1259 pRequestedHeaders->second.uri = rURI.GetURI();
1260 pRequestedHeaders->second.properties.clear();
1263 bool isRetry(false);
1264 bool isFallbackHTTP10(false);
1265 int nAuthRequests(0);
1266 int nAuthRequestsProxy(0);
1268 // libcurl does not have an authentication callback so handle auth
1269 // related status codes and requesting credentials via this loop
1272 isRetry = false;
1274 // re-check m_isAuthenticated flags every time, could have been set
1275 // by re-entrant call
1276 if (oAuth && !rSession.m_isAuthenticated)
1278 OString const utf8UserName(OUStringToOString(oAuth->UserName, RTL_TEXTENCODING_UTF8));
1279 auto rc
1280 = curl_easy_setopt(rSession.m_pCurl.get(), CURLOPT_USERNAME, utf8UserName.getStr());
1281 if (rc != CURLE_OK)
1283 SAL_WARN("ucb.ucp.webdav.curl", "CURLOPT_USERNAME failed: " << GetErrorString(rc));
1284 throw DAVException(DAVException::DAV_INVALID_ARG);
1286 OString const utf8PassWord(OUStringToOString(oAuth->PassWord, RTL_TEXTENCODING_UTF8));
1287 rc = curl_easy_setopt(rSession.m_pCurl.get(), CURLOPT_PASSWORD, utf8PassWord.getStr());
1288 if (rc != CURLE_OK)
1290 SAL_WARN("ucb.ucp.webdav.curl", "CURLOPT_PASSWORD failed: " << GetErrorString(rc));
1291 throw DAVException(DAVException::DAV_INVALID_ARG);
1293 rc = curl_easy_setopt(rSession.m_pCurl.get(), CURLOPT_HTTPAUTH, oAuth->AuthMask);
1294 assert(
1296 == CURLE_OK); // it shouldn't be possible to reduce auth to 0 via the authSystem masks
1299 if (oAuthProxy && !rSession.m_isAuthenticatedProxy)
1301 OString const utf8UserName(
1302 OUStringToOString(oAuthProxy->UserName, RTL_TEXTENCODING_UTF8));
1303 auto rc = curl_easy_setopt(rSession.m_pCurl.get(), CURLOPT_PROXYUSERNAME,
1304 utf8UserName.getStr());
1305 if (rc != CURLE_OK)
1307 SAL_WARN("ucb.ucp.webdav.curl",
1308 "CURLOPT_PROXYUSERNAME failed: " << GetErrorString(rc));
1309 throw DAVException(DAVException::DAV_INVALID_ARG);
1311 OString const utf8PassWord(
1312 OUStringToOString(oAuthProxy->PassWord, RTL_TEXTENCODING_UTF8));
1313 rc = curl_easy_setopt(rSession.m_pCurl.get(), CURLOPT_PROXYPASSWORD,
1314 utf8PassWord.getStr());
1315 if (rc != CURLE_OK)
1317 SAL_WARN("ucb.ucp.webdav.curl",
1318 "CURLOPT_PROXYPASSWORD failed: " << GetErrorString(rc));
1319 throw DAVException(DAVException::DAV_INVALID_ARG);
1321 rc = curl_easy_setopt(rSession.m_pCurl.get(), CURLOPT_PROXYAUTH, oAuthProxy->AuthMask);
1322 assert(
1324 == CURLE_OK); // it shouldn't be possible to reduce auth to 0 via the authSystem masks
1327 ResponseHeaders headers(rSession.m_pCurl.get());
1328 // always pass a stream for debug logging, buffer the result body
1329 uno::Reference<io::XSequenceOutputStream> const xSeqOutStream(
1330 io::SequenceOutputStream::create(rSession.m_xContext));
1331 uno::Reference<io::XOutputStream> const xTempOutStream(xSeqOutStream);
1332 assert(xTempOutStream.is());
1336 ProcessRequestImpl(rSession, rURI, rMethod, pRequestHeaderList.get(), &xTempOutStream,
1337 pxInStream ? &data : nullptr, pRequestedHeaders, headers);
1338 if (pxOutStream)
1339 { // only copy to result stream if transfer was successful
1340 (*pxOutStream)->writeBytes(xSeqOutStream->getWrittenBytes());
1341 (*pxOutStream)->closeOutput(); // signal EOF
1344 catch (DAVException const& rException)
1346 // log start of request body if there was any
1347 auto const bytes(xSeqOutStream->getWrittenBytes());
1348 auto const len(::std::min<sal_Int32>(bytes.getLength(), 10000));
1349 SAL_INFO("ucb.ucp.webdav.curl",
1350 "DAVException; (first) " << len << " bytes of data received:");
1351 if (0 < len)
1353 OStringBuffer buf(len);
1354 for (sal_Int32 i = 0; i < len; ++i)
1356 if (bytes[i] < 0x20) // also if negative
1358 static char const hexDigit[16] = { '0', '1', '2', '3', '4', '5', '6', '7',
1359 '8', '9', 'A', 'B', 'C', 'D', 'E', 'F' };
1360 buf.append(OString::Concat("\\x")
1361 + OStringChar(hexDigit[static_cast<sal_uInt8>(bytes[i]) >> 4])
1362 + OStringChar(hexDigit[bytes[i] & 0x0F]));
1364 else
1366 buf.append(static_cast<char>(bytes[i]));
1369 SAL_INFO("ucb.ucp.webdav.curl", buf.makeStringAndClear());
1372 // error handling part 3: special HTTP status codes
1373 // that require unlocking m_Mutex to handle
1374 if (rException.getError() == DAVException::DAV_HTTP_ERROR)
1376 auto const statusCode(rException.getStatus());
1377 switch (statusCode)
1379 case SC_LOCKED:
1381 guard.Release(); // release m_Mutex before accessing LockStore
1382 if (g_Init.LockStore.getLockTokenForURI(rURI.GetURI(), nullptr))
1384 throw DAVException(DAVException::DAV_LOCKED_SELF);
1386 else // locked by third party
1388 throw DAVException(DAVException::DAV_LOCKED);
1390 break;
1392 case SC_PRECONDITION_FAILED:
1393 case SC_BAD_REQUEST:
1395 guard.Release(); // release m_Mutex before accessing LockStore
1396 // Not obvious but apparently these codes may indicate
1397 // the expiration of a lock.
1398 // Initiate a new request *outside* ProcessRequestImpl
1399 // *after* rGuard.unlock() to avoid messing up m_pCurl state.
1400 if (TryRemoveExpiredLockToken(rSession, rURI, pEnv))
1402 throw DAVException(DAVException::DAV_LOCK_EXPIRED);
1404 break;
1406 case SC_UNAUTHORIZED:
1407 case SC_PROXY_AUTHENTICATION_REQUIRED:
1409 (statusCode != SC_PROXY_AUTHENTICATION_REQUIRED
1410 ? rSession.m_isAuthenticated
1411 : rSession.m_isAuthenticatedProxy)
1412 = false; // any auth data in m_pCurl is invalid
1413 auto& rnAuthRequests(statusCode == SC_UNAUTHORIZED ? nAuthRequests
1414 : nAuthRequestsProxy);
1415 if (rnAuthRequests == 10)
1417 SAL_INFO("ucb.ucp.webdav.curl", "aborting authentication after "
1418 << rnAuthRequests << " attempts");
1420 else if (pEnv && pEnv->m_xAuthListener)
1422 ::std::optional<OUString> const oRealm(ExtractRealm(
1423 headers, statusCode == SC_UNAUTHORIZED ? "WWW-Authenticate"
1424 : "Proxy-Authenticate"));
1426 ::std::optional<Auth>& roAuth(
1427 statusCode == SC_UNAUTHORIZED ? oAuth : oAuthProxy);
1428 OUString userName(roAuth ? roAuth->UserName : OUString());
1429 OUString passWord(roAuth ? roAuth->PassWord : OUString());
1430 long authAvail(0);
1431 auto const rc = curl_easy_getinfo(rSession.m_pCurl.get(),
1432 statusCode == SC_UNAUTHORIZED
1433 ? CURLINFO_HTTPAUTH_AVAIL
1434 : CURLINFO_PROXYAUTH_AVAIL,
1435 &authAvail);
1436 assert(rc == CURLE_OK);
1437 (void)rc;
1438 // only allow SystemCredentials once - the
1439 // PasswordContainer may have stored it in the
1440 // Config (TrySystemCredentialsFirst or
1441 // AuthenticateUsingSystemCredentials) and then it
1442 // will always force its use no matter how hopeless
1443 bool const isSystemCredSupported((authAvail & authSystem) != 0
1444 && rnAuthRequests == 0);
1445 ++rnAuthRequests;
1447 // Ask user via XInteractionHandler.
1448 // Warning: This likely runs an event loop which may
1449 // end up calling back into this instance, so all
1450 // changes to m_pCurl must be undone now and
1451 // restored after return.
1452 guard.Release();
1454 auto const ret = pEnv->m_xAuthListener->authenticate(
1455 oRealm ? *oRealm : "",
1456 statusCode == SC_UNAUTHORIZED ? rSession.m_URI.GetHost()
1457 : rSession.m_Proxy.aName,
1458 userName, passWord, isSystemCredSupported);
1460 if (ret == 0)
1462 // NTLM may either use a password requested
1463 // from the user, or from the system via SSPI
1464 // so i guess it should not be disabled here
1465 // regardless of the state of the system auth
1466 // checkbox, particularly since SSPI is only
1467 // available on WNT.
1468 // Additionally, "Negotiate" has a "legacy"
1469 // mode that is actually just NTLM according to
1470 // https://curl.se/rfc/ntlm.html#ntlmHttpAuthentication
1471 // so there's nothing in authSystem that can be
1472 // disabled here.
1473 roAuth.emplace(userName, passWord,
1474 ((userName.isEmpty() && passWord.isEmpty())
1475 ? (authAvail & authSystem)
1476 : authAvail));
1477 isRetry = true;
1478 // Acquire is only necessary in case of success.
1479 guard.Acquire();
1480 break; // break out of switch
1482 // else: throw
1484 SAL_INFO("ucb.ucp.webdav.curl", "no auth credentials provided");
1485 throw DAVException(DAVException::DAV_HTTP_NOAUTH,
1486 ConnectionEndPointString(rSession.m_URI.GetHost(),
1487 rSession.m_URI.GetPort()));
1488 break;
1492 else if (rException.getError() == DAVException::DAV_UNSUPPORTED)
1494 // tdf#152493 libcurl can't handle "Transfer-Encoding: chunked"
1495 // in HTTP/1.1 100 Continue response.
1496 // workaround: if HTTP/1.1 didn't work, try HTTP/1.0
1497 // (but fallback only once - to prevent infinite loop)
1498 if (isFallbackHTTP10)
1500 throw DAVException(DAVException::DAV_HTTP_ERROR);
1502 isFallbackHTTP10 = true;
1503 // note: this is not reset - future requests to this URI use it!
1504 auto rc = curl_easy_setopt(rSession.m_pCurl.get(), CURLOPT_HTTP_VERSION,
1505 CURL_HTTP_VERSION_1_0);
1506 if (rc != CURLE_OK)
1508 throw DAVException(DAVException::DAV_HTTP_ERROR);
1510 SAL_INFO("ucb.ucp.webdav.curl", "attempting fallback to HTTP/1.0");
1511 isRetry = true;
1513 if (!isRetry)
1515 throw; // everything else: re-throw
1518 } while (isRetry);
1520 if (oAuth)
1522 // assume this worked, leave auth data as stored in m_pCurl
1523 rSession.m_isAuthenticated = true;
1525 if (oAuthProxy)
1527 // assume this worked, leave auth data as stored in m_pCurl
1528 rSession.m_isAuthenticatedProxy = true;
1532 auto CurlSession::OPTIONS(OUString const& rURIReference,
1534 DAVOptions& rOptions, DAVRequestEnvironment const& rEnv) -> void
1536 SAL_INFO("ucb.ucp.webdav.curl", "OPTIONS: " << rURIReference);
1538 rOptions.init();
1540 CurlUri const uri(CurlProcessor::URIReferenceToURI(*this, rURIReference));
1542 ::std::vector<OUString> const headerNames{ "allow", "dav" };
1543 DAVResource result;
1544 ::std::pair<::std::vector<OUString> const&, DAVResource&> const headers(headerNames, result);
1546 ::std::vector<CurlOption> const options{
1547 g_NoBody, { CURLOPT_CUSTOMREQUEST, "OPTIONS", "CURLOPT_CUSTOMREQUEST" }
1550 CurlProcessor::ProcessRequest(*this, uri, "OPTIONS", options, &rEnv, nullptr, nullptr, nullptr,
1551 &headers);
1553 for (auto const& it : result.properties)
1555 OUString value;
1556 it.Value >>= value;
1557 SAL_INFO("ucb.ucp.webdav.curl", "OPTIONS: header: " << it.Name << ": " << value);
1558 if (it.Name.equalsIgnoreAsciiCase("allow"))
1560 rOptions.setAllowedMethods(value);
1562 else if (it.Name.equalsIgnoreAsciiCase("dav"))
1564 // see <http://tools.ietf.org/html/rfc4918#section-10.1>,
1565 // <http://tools.ietf.org/html/rfc4918#section-18>,
1566 // and <http://tools.ietf.org/html/rfc7230#section-3.2>
1567 // we detect the class (1, 2 and 3), other elements (token, URL)
1568 // are not used for now
1569 auto const list(::comphelper::string::convertCommaSeparated(value));
1570 for (OUString const& v : list)
1572 if (v == "1")
1574 rOptions.setClass1();
1576 else if (v == "2")
1578 rOptions.setClass2();
1580 else if (v == "3")
1582 rOptions.setClass3();
1587 if (rOptions.isClass2() || rOptions.isClass3())
1589 if (g_Init.LockStore.getLockTokenForURI(uri.GetURI(), nullptr))
1591 rOptions.setLocked();
1596 auto CurlProcessor::PropFind(
1597 CurlSession& rSession, CurlUri const& rURI, Depth const nDepth,
1598 ::std::tuple<::std::vector<OUString> const&, ::std::vector<DAVResource>* const,
1599 ::std::vector<ucb::Lock>* const> const* const o_pRequestedProperties,
1600 ::std::vector<DAVResourceInfo>* const o_pResourceInfos, DAVRequestEnvironment const& rEnv)
1601 -> void
1603 assert((o_pRequestedProperties != nullptr) != (o_pResourceInfos != nullptr));
1604 assert((o_pRequestedProperties == nullptr)
1605 || (::std::get<1>(*o_pRequestedProperties) != nullptr)
1606 != (::std::get<2>(*o_pRequestedProperties) != nullptr));
1608 ::std::unique_ptr<curl_slist, deleter_from_fn<curl_slist, curl_slist_free_all>> pList;
1609 pList.reset(curl_slist_append(pList.release(), "Content-Type: application/xml"));
1610 if (!pList)
1612 throw uno::RuntimeException("curl_slist_append failed");
1614 OString depth;
1615 switch (nDepth)
1617 case DAVZERO:
1618 depth = "Depth: 0";
1619 break;
1620 case DAVONE:
1621 depth = "Depth: 1";
1622 break;
1623 case DAVINFINITY:
1624 depth = "Depth: infinity";
1625 break;
1626 default:
1627 assert(false);
1629 pList.reset(curl_slist_append(pList.release(), depth.getStr()));
1630 if (!pList)
1632 throw uno::RuntimeException("curl_slist_append failed");
1635 uno::Reference<io::XSequenceOutputStream> const xSeqOutStream(
1636 io::SequenceOutputStream::create(rSession.m_xContext));
1637 uno::Reference<io::XOutputStream> const xRequestOutStream(xSeqOutStream);
1638 assert(xRequestOutStream.is());
1640 uno::Reference<xml::sax::XWriter> const xWriter(xml::sax::Writer::create(rSession.m_xContext));
1641 xWriter->setOutputStream(xRequestOutStream);
1642 xWriter->startDocument();
1643 rtl::Reference<::comphelper::AttributeList> const pAttrList(new ::comphelper::AttributeList);
1644 pAttrList->AddAttribute("xmlns", "DAV:");
1645 xWriter->startElement("propfind", pAttrList);
1646 if (o_pResourceInfos)
1648 xWriter->startElement("propname", nullptr);
1649 xWriter->endElement("propname");
1651 else
1653 if (::std::get<0>(*o_pRequestedProperties).empty())
1655 xWriter->startElement("allprop", nullptr);
1656 xWriter->endElement("allprop");
1658 else
1660 xWriter->startElement("prop", nullptr);
1661 for (OUString const& rName : ::std::get<0>(*o_pRequestedProperties))
1663 SerfPropName name;
1664 DAVProperties::createSerfPropName(rName, name);
1665 pAttrList->Clear();
1666 pAttrList->AddAttribute("xmlns", OUString::createFromAscii(name.nspace));
1667 xWriter->startElement(OUString::createFromAscii(name.name), pAttrList);
1668 xWriter->endElement(OUString::createFromAscii(name.name));
1670 xWriter->endElement("prop");
1673 xWriter->endElement("propfind");
1674 xWriter->endDocument();
1676 uno::Reference<io::XInputStream> const xRequestInStream(
1677 io::SequenceInputStream::createStreamFromSequence(rSession.m_xContext,
1678 xSeqOutStream->getWrittenBytes()));
1679 assert(xRequestInStream.is());
1681 curl_off_t const len(xSeqOutStream->getWrittenBytes().getLength());
1683 ::std::vector<CurlOption> const options{
1684 { CURLOPT_UPLOAD, 1L, nullptr },
1685 { CURLOPT_CUSTOMREQUEST, "PROPFIND", "CURLOPT_CUSTOMREQUEST" },
1686 // note: Sharepoint cannot handle "Transfer-Encoding: chunked"
1687 { CURLOPT_INFILESIZE_LARGE, len, nullptr, CurlOption::Type::CurlOffT }
1690 // stream for response
1691 uno::Reference<io::XInputStream> const xResponseInStream(io::Pipe::create(rSession.m_xContext));
1692 uno::Reference<io::XOutputStream> const xResponseOutStream(xResponseInStream, uno::UNO_QUERY);
1693 assert(xResponseInStream.is());
1694 assert(xResponseOutStream.is());
1696 CurlProcessor::ProcessRequest(rSession, rURI, "PROPFIND", options, &rEnv, ::std::move(pList),
1697 &xResponseOutStream, &xRequestInStream, nullptr);
1699 if (o_pResourceInfos)
1701 *o_pResourceInfos = parseWebDAVPropNameResponse(xResponseInStream);
1703 else
1705 if (::std::get<1>(*o_pRequestedProperties) != nullptr)
1707 *::std::get<1>(*o_pRequestedProperties)
1708 = parseWebDAVPropFindResponse(xResponseInStream);
1709 for (DAVResource& it : *::std::get<1>(*o_pRequestedProperties))
1711 // caller will give these uris to CurlUri so can't be relative
1712 if (it.uri.startsWith("/"))
1716 it.uri = rSession.m_URI.CloneWithRelativeRefPathAbsolute(it.uri).GetURI();
1718 catch (DAVException const&)
1720 SAL_INFO("ucb.ucp.webdav.curl",
1721 "PROPFIND: exception parsing uri " << it.uri);
1726 else
1728 *::std::get<2>(*o_pRequestedProperties) = parseWebDAVLockResponse(xResponseInStream);
1733 // DAV methods
1734 auto CurlSession::PROPFIND(OUString const& rURIReference, Depth const depth,
1735 ::std::vector<OUString> const& rPropertyNames,
1736 ::std::vector<DAVResource>& o_rResources,
1737 DAVRequestEnvironment const& rEnv) -> void
1739 SAL_INFO("ucb.ucp.webdav.curl", "PROPFIND: " << rURIReference << " " << depth);
1741 CurlUri const uri(CurlProcessor::URIReferenceToURI(*this, rURIReference));
1743 ::std::tuple<::std::vector<OUString> const&, ::std::vector<DAVResource>* const,
1744 ::std::vector<ucb::Lock>* const> const args(rPropertyNames, &o_rResources,
1745 nullptr);
1746 return CurlProcessor::PropFind(*this, uri, depth, &args, nullptr, rEnv);
1749 auto CurlSession::PROPFIND(OUString const& rURIReference, Depth const depth,
1750 ::std::vector<DAVResourceInfo>& o_rResourceInfos,
1751 DAVRequestEnvironment const& rEnv) -> void
1753 SAL_INFO("ucb.ucp.webdav.curl", "PROPFIND: " << rURIReference << " " << depth);
1755 CurlUri const uri(CurlProcessor::URIReferenceToURI(*this, rURIReference));
1757 return CurlProcessor::PropFind(*this, uri, depth, nullptr, &o_rResourceInfos, rEnv);
1760 auto CurlSession::PROPPATCH(OUString const& rURIReference,
1761 ::std::vector<ProppatchValue> const& rValues,
1762 DAVRequestEnvironment const& rEnv) -> void
1764 SAL_INFO("ucb.ucp.webdav.curl", "PROPPATCH: " << rURIReference);
1766 CurlUri const uri(CurlProcessor::URIReferenceToURI(*this, rURIReference));
1768 // TODO: either set CURLOPT_INFILESIZE_LARGE or chunked?
1769 ::std::unique_ptr<curl_slist, deleter_from_fn<curl_slist, curl_slist_free_all>> pList;
1770 pList.reset(curl_slist_append(pList.release(), "Content-Type: application/xml"));
1771 if (!pList)
1773 throw uno::RuntimeException("curl_slist_append failed");
1776 // generate XML document for PROPPATCH
1777 uno::Reference<io::XSequenceOutputStream> const xSeqOutStream(
1778 io::SequenceOutputStream::create(m_xContext));
1779 uno::Reference<io::XOutputStream> const xRequestOutStream(xSeqOutStream);
1780 assert(xRequestOutStream.is());
1781 uno::Reference<xml::sax::XWriter> const xWriter(xml::sax::Writer::create(m_xContext));
1782 xWriter->setOutputStream(xRequestOutStream);
1783 xWriter->startDocument();
1784 rtl::Reference<::comphelper::AttributeList> const pAttrList(new ::comphelper::AttributeList);
1785 pAttrList->AddAttribute("xmlns", "DAV:");
1786 xWriter->startElement("propertyupdate", pAttrList);
1787 for (ProppatchValue const& rPropValue : rValues)
1789 assert(rPropValue.operation == PROPSET || rPropValue.operation == PROPREMOVE);
1790 OUString const operation((rPropValue.operation == PROPSET) ? OUString("set")
1791 : OUString("remove"));
1792 xWriter->startElement(operation, nullptr);
1793 xWriter->startElement("prop", nullptr);
1794 SerfPropName name;
1795 DAVProperties::createSerfPropName(rPropValue.name, name);
1796 pAttrList->Clear();
1797 pAttrList->AddAttribute("xmlns", OUString::createFromAscii(name.nspace));
1798 xWriter->startElement(OUString::createFromAscii(name.name), pAttrList);
1799 if (rPropValue.operation == PROPSET)
1801 if (DAVProperties::isUCBDeadProperty(name))
1803 ::std::optional<::std::pair<OUString, OUString>> const oProp(
1804 UCBDeadPropertyValue::toXML(rPropValue.value));
1805 if (oProp)
1807 xWriter->startElement("ucbprop", nullptr);
1808 xWriter->startElement("type", nullptr);
1809 xWriter->characters(oProp->first);
1810 xWriter->endElement("type");
1811 xWriter->startElement("value", nullptr);
1812 xWriter->characters(oProp->second);
1813 xWriter->endElement("value");
1814 xWriter->endElement("ucbprop");
1817 else
1819 OUString value;
1820 rPropValue.value >>= value;
1821 xWriter->characters(value);
1824 xWriter->endElement(OUString::createFromAscii(name.name));
1825 xWriter->endElement("prop");
1826 xWriter->endElement(operation);
1828 xWriter->endElement("propertyupdate");
1829 xWriter->endDocument();
1831 uno::Reference<io::XInputStream> const xRequestInStream(
1832 io::SequenceInputStream::createStreamFromSequence(m_xContext,
1833 xSeqOutStream->getWrittenBytes()));
1834 assert(xRequestInStream.is());
1836 curl_off_t const len(xSeqOutStream->getWrittenBytes().getLength());
1838 ::std::vector<CurlOption> const options{
1839 { CURLOPT_UPLOAD, 1L, nullptr },
1840 { CURLOPT_CUSTOMREQUEST, "PROPPATCH", "CURLOPT_CUSTOMREQUEST" },
1841 // note: Sharepoint cannot handle "Transfer-Encoding: chunked"
1842 { CURLOPT_INFILESIZE_LARGE, len, nullptr, CurlOption::Type::CurlOffT }
1845 CurlProcessor::ProcessRequest(*this, uri, "PROPPATCH", options, &rEnv, ::std::move(pList),
1846 nullptr, &xRequestInStream, nullptr);
1849 auto CurlSession::HEAD(OUString const& rURIReference, ::std::vector<OUString> const& rHeaderNames,
1850 DAVResource& io_rResource, DAVRequestEnvironment const& rEnv) -> void
1852 SAL_INFO("ucb.ucp.webdav.curl", "HEAD: " << rURIReference);
1854 CurlUri const uri(CurlProcessor::URIReferenceToURI(*this, rURIReference));
1856 ::std::vector<CurlOption> const options{ g_NoBody };
1858 ::std::pair<::std::vector<OUString> const&, DAVResource&> const headers(rHeaderNames,
1859 io_rResource);
1861 CurlProcessor::ProcessRequest(*this, uri, "HEAD", options, &rEnv, nullptr, nullptr, nullptr,
1862 &headers);
1865 auto CurlSession::GET(OUString const& rURIReference, DAVRequestEnvironment const& rEnv)
1866 -> uno::Reference<io::XInputStream>
1868 SAL_INFO("ucb.ucp.webdav.curl", "GET: " << rURIReference);
1870 CurlUri const uri(CurlProcessor::URIReferenceToURI(*this, rURIReference));
1872 // could use either com.sun.star.io.Pipe or com.sun.star.io.SequenceInputStream?
1873 // Pipe can just write into its XOuputStream, which is simpler.
1874 // Both resize exponentially, so performance should be fine.
1875 // However, Pipe doesn't implement XSeekable, which is required by filters.
1877 uno::Reference<io::XSequenceOutputStream> const xSeqOutStream(
1878 io::SequenceOutputStream::create(m_xContext));
1879 uno::Reference<io::XOutputStream> const xResponseOutStream(xSeqOutStream);
1880 assert(xResponseOutStream.is());
1882 ::std::vector<CurlOption> const options{ { CURLOPT_HTTPGET, 1L, nullptr } };
1884 CurlProcessor::ProcessRequest(*this, uri, "GET", options, &rEnv, nullptr, &xResponseOutStream,
1885 nullptr, nullptr);
1887 uno::Reference<io::XInputStream> const xResponseInStream(
1888 io::SequenceInputStream::createStreamFromSequence(m_xContext,
1889 xSeqOutStream->getWrittenBytes()));
1890 assert(xResponseInStream.is());
1892 return xResponseInStream;
1895 auto CurlSession::GET(OUString const& rURIReference, uno::Reference<io::XOutputStream>& rxOutStream,
1896 DAVRequestEnvironment const& rEnv) -> void
1898 SAL_INFO("ucb.ucp.webdav.curl", "GET: " << rURIReference);
1900 CurlUri const uri(CurlProcessor::URIReferenceToURI(*this, rURIReference));
1902 ::std::vector<CurlOption> const options{ { CURLOPT_HTTPGET, 1L, nullptr } };
1904 CurlProcessor::ProcessRequest(*this, uri, "GET", options, &rEnv, nullptr, &rxOutStream, nullptr,
1905 nullptr);
1908 auto CurlSession::GET(OUString const& rURIReference, ::std::vector<OUString> const& rHeaderNames,
1909 DAVResource& io_rResource, DAVRequestEnvironment const& rEnv)
1910 -> uno::Reference<io::XInputStream>
1912 SAL_INFO("ucb.ucp.webdav.curl", "GET: " << rURIReference);
1914 CurlUri const uri(CurlProcessor::URIReferenceToURI(*this, rURIReference));
1916 ::std::vector<CurlOption> const options{ { CURLOPT_HTTPGET, 1L, nullptr } };
1918 uno::Reference<io::XSequenceOutputStream> const xSeqOutStream(
1919 io::SequenceOutputStream::create(m_xContext));
1920 uno::Reference<io::XOutputStream> const xResponseOutStream(xSeqOutStream);
1921 assert(xResponseOutStream.is());
1923 ::std::pair<::std::vector<OUString> const&, DAVResource&> const headers(rHeaderNames,
1924 io_rResource);
1926 CurlProcessor::ProcessRequest(*this, uri, "GET", options, &rEnv, nullptr, &xResponseOutStream,
1927 nullptr, &headers);
1929 uno::Reference<io::XInputStream> const xResponseInStream(
1930 io::SequenceInputStream::createStreamFromSequence(m_xContext,
1931 xSeqOutStream->getWrittenBytes()));
1932 assert(xResponseInStream.is());
1934 return xResponseInStream;
1937 auto CurlSession::GET(OUString const& rURIReference, uno::Reference<io::XOutputStream>& rxOutStream,
1938 ::std::vector<OUString> const& rHeaderNames, DAVResource& io_rResource,
1939 DAVRequestEnvironment const& rEnv) -> void
1941 SAL_INFO("ucb.ucp.webdav.curl", "GET: " << rURIReference);
1943 CurlUri const uri(CurlProcessor::URIReferenceToURI(*this, rURIReference));
1945 ::std::vector<CurlOption> const options{ { CURLOPT_HTTPGET, 1L, nullptr } };
1947 ::std::pair<::std::vector<OUString> const&, DAVResource&> const headers(rHeaderNames,
1948 io_rResource);
1950 CurlProcessor::ProcessRequest(*this, uri, "GET", options, &rEnv, nullptr, &rxOutStream, nullptr,
1951 &headers);
1954 auto CurlSession::PUT(OUString const& rURIReference,
1955 uno::Reference<io::XInputStream> const& rxInStream,
1956 DAVRequestEnvironment const& rEnv) -> void
1958 SAL_INFO("ucb.ucp.webdav.curl", "PUT: " << rURIReference);
1960 CurlUri const uri(CurlProcessor::URIReferenceToURI(*this, rURIReference));
1962 // NextCloud silently fails with chunked encoding
1963 uno::Reference<io::XSeekable> const xSeekable(rxInStream, uno::UNO_QUERY);
1964 if (!xSeekable.is())
1966 throw uno::RuntimeException("TODO: not seekable");
1968 curl_off_t const len(xSeekable->getLength() - xSeekable->getPosition());
1970 ::std::unique_ptr<curl_slist, deleter_from_fn<curl_slist, curl_slist_free_all>> pList;
1971 OUString const* const pToken(g_Init.LockStore.getLockTokenForURI(uri.GetURI(), nullptr));
1972 if (pToken)
1974 OString const utf8If("If: "
1975 // disabled as Sharepoint 2013 workaround, it accepts only
1976 // "No-Tag-List", see fed2984281a85a5a2f308841ec810f218c75f2ab
1977 #if 0
1978 "<" + OUStringToOString(rURIReference, RTL_TEXTENCODING_ASCII_US)
1979 + "> "
1980 #endif
1981 "(<"
1982 + OUStringToOString(*pToken, RTL_TEXTENCODING_ASCII_US) + ">)");
1983 pList.reset(curl_slist_append(pList.release(), utf8If.getStr()));
1984 if (!pList)
1986 throw uno::RuntimeException("curl_slist_append failed");
1990 // lock m_Mutex after accessing global LockStore to avoid deadlock
1992 // note: Nextcloud 20 cannot handle "Transfer-Encoding: chunked"
1993 ::std::vector<CurlOption> const options{
1994 { CURLOPT_UPLOAD, 1L, nullptr }, // libcurl won't upload without setting this
1995 { CURLOPT_INFILESIZE_LARGE, len, nullptr, CurlOption::Type::CurlOffT }
1998 CurlProcessor::ProcessRequest(*this, uri, "PUT", options, &rEnv, ::std::move(pList), nullptr,
1999 &rxInStream, nullptr);
2002 auto CurlSession::POST(OUString const& rURIReference, OUString const& rContentType,
2003 OUString const& rReferer, uno::Reference<io::XInputStream> const& rxInStream,
2004 DAVRequestEnvironment const& rEnv) -> uno::Reference<io::XInputStream>
2006 SAL_INFO("ucb.ucp.webdav.curl", "POST: " << rURIReference);
2008 CurlUri const uri(CurlProcessor::URIReferenceToURI(*this, rURIReference));
2010 // TODO: either set CURLOPT_POSTFIELDSIZE_LARGE or chunked?
2011 ::std::unique_ptr<curl_slist, deleter_from_fn<curl_slist, curl_slist_free_all>> pList(
2012 curl_slist_append(nullptr, "Transfer-Encoding: chunked"));
2013 if (!pList)
2015 throw uno::RuntimeException("curl_slist_append failed");
2017 OString const utf8ContentType("Content-Type: "
2018 + OUStringToOString(rContentType, RTL_TEXTENCODING_ASCII_US));
2019 pList.reset(curl_slist_append(pList.release(), utf8ContentType.getStr()));
2020 if (!pList)
2022 throw uno::RuntimeException("curl_slist_append failed");
2024 OString const utf8Referer("Referer: " + OUStringToOString(rReferer, RTL_TEXTENCODING_ASCII_US));
2025 pList.reset(curl_slist_append(pList.release(), utf8Referer.getStr()));
2026 if (!pList)
2028 throw uno::RuntimeException("curl_slist_append failed");
2031 ::std::vector<CurlOption> const options{ { CURLOPT_POST, 1L, nullptr } };
2033 uno::Reference<io::XSequenceOutputStream> const xSeqOutStream(
2034 io::SequenceOutputStream::create(m_xContext));
2035 uno::Reference<io::XOutputStream> const xResponseOutStream(xSeqOutStream);
2036 assert(xResponseOutStream.is());
2038 CurlProcessor::ProcessRequest(*this, uri, "POST", options, &rEnv, ::std::move(pList),
2039 &xResponseOutStream, &rxInStream, nullptr);
2041 uno::Reference<io::XInputStream> const xResponseInStream(
2042 io::SequenceInputStream::createStreamFromSequence(m_xContext,
2043 xSeqOutStream->getWrittenBytes()));
2044 assert(xResponseInStream.is());
2046 return xResponseInStream;
2049 auto CurlSession::POST(OUString const& rURIReference, OUString const& rContentType,
2050 OUString const& rReferer, uno::Reference<io::XInputStream> const& rxInStream,
2051 uno::Reference<io::XOutputStream>& rxOutStream,
2052 DAVRequestEnvironment const& rEnv) -> void
2054 SAL_INFO("ucb.ucp.webdav.curl", "POST: " << rURIReference);
2056 CurlUri const uri(CurlProcessor::URIReferenceToURI(*this, rURIReference));
2058 // TODO: either set CURLOPT_POSTFIELDSIZE_LARGE or chunked?
2059 ::std::unique_ptr<curl_slist, deleter_from_fn<curl_slist, curl_slist_free_all>> pList(
2060 curl_slist_append(nullptr, "Transfer-Encoding: chunked"));
2061 if (!pList)
2063 throw uno::RuntimeException("curl_slist_append failed");
2065 OString const utf8ContentType("Content-Type: "
2066 + OUStringToOString(rContentType, RTL_TEXTENCODING_ASCII_US));
2067 pList.reset(curl_slist_append(pList.release(), utf8ContentType.getStr()));
2068 if (!pList)
2070 throw uno::RuntimeException("curl_slist_append failed");
2072 OString const utf8Referer("Referer: " + OUStringToOString(rReferer, RTL_TEXTENCODING_ASCII_US));
2073 pList.reset(curl_slist_append(pList.release(), utf8Referer.getStr()));
2074 if (!pList)
2076 throw uno::RuntimeException("curl_slist_append failed");
2079 ::std::vector<CurlOption> const options{ { CURLOPT_POST, 1L, nullptr } };
2081 CurlProcessor::ProcessRequest(*this, uri, "POST", options, &rEnv, ::std::move(pList),
2082 &rxOutStream, &rxInStream, nullptr);
2085 auto CurlSession::MKCOL(OUString const& rURIReference, DAVRequestEnvironment const& rEnv) -> void
2087 SAL_INFO("ucb.ucp.webdav.curl", "MKCOL: " << rURIReference);
2089 CurlUri const uri(CurlProcessor::URIReferenceToURI(*this, rURIReference));
2091 ::std::vector<CurlOption> const options{
2092 g_NoBody, { CURLOPT_CUSTOMREQUEST, "MKCOL", "CURLOPT_CUSTOMREQUEST" }
2095 CurlProcessor::ProcessRequest(*this, uri, "MKCOL", options, &rEnv, nullptr, nullptr, nullptr,
2096 nullptr);
2099 auto CurlProcessor::MoveOrCopy(CurlSession& rSession, std::u16string_view rSourceURIReference,
2100 ::std::u16string_view const rDestinationURI,
2101 DAVRequestEnvironment const& rEnv, bool const isOverwrite,
2102 char const* const pMethod) -> void
2104 CurlUri const uriSource(CurlProcessor::URIReferenceToURI(rSession, rSourceURIReference));
2106 OString const utf8Destination("Destination: "
2107 + OUStringToOString(rDestinationURI, RTL_TEXTENCODING_ASCII_US));
2108 ::std::unique_ptr<curl_slist, deleter_from_fn<curl_slist, curl_slist_free_all>> pList(
2109 curl_slist_append(nullptr, utf8Destination.getStr()));
2110 if (!pList)
2112 throw uno::RuntimeException("curl_slist_append failed");
2114 OString const utf8Overwrite(OString::Concat("Overwrite: ") + (isOverwrite ? "T" : "F"));
2115 pList.reset(curl_slist_append(pList.release(), utf8Overwrite.getStr()));
2116 if (!pList)
2118 throw uno::RuntimeException("curl_slist_append failed");
2121 ::std::vector<CurlOption> const options{
2122 g_NoBody, { CURLOPT_CUSTOMREQUEST, pMethod, "CURLOPT_CUSTOMREQUEST" }
2125 CurlProcessor::ProcessRequest(rSession, uriSource, OUString::createFromAscii(pMethod), options,
2126 &rEnv, ::std::move(pList), nullptr, nullptr, nullptr);
2129 auto CurlSession::COPY(OUString const& rSourceURIReference, OUString const& rDestinationURI,
2130 DAVRequestEnvironment const& rEnv, bool const isOverwrite) -> void
2132 SAL_INFO("ucb.ucp.webdav.curl", "COPY: " << rSourceURIReference);
2134 return CurlProcessor::MoveOrCopy(*this, rSourceURIReference, rDestinationURI, rEnv, isOverwrite,
2135 "COPY");
2138 auto CurlSession::MOVE(OUString const& rSourceURIReference, OUString const& rDestinationURI,
2139 DAVRequestEnvironment const& rEnv, bool const isOverwrite) -> void
2141 SAL_INFO("ucb.ucp.webdav.curl", "MOVE: " << rSourceURIReference);
2143 return CurlProcessor::MoveOrCopy(*this, rSourceURIReference, rDestinationURI, rEnv, isOverwrite,
2144 "MOVE");
2147 auto CurlSession::DESTROY(OUString const& rURIReference, DAVRequestEnvironment const& rEnv) -> void
2149 SAL_INFO("ucb.ucp.webdav.curl", "DESTROY: " << rURIReference);
2151 CurlUri const uri(CurlProcessor::URIReferenceToURI(*this, rURIReference));
2153 ::std::vector<CurlOption> const options{
2154 g_NoBody, { CURLOPT_CUSTOMREQUEST, "DELETE", "CURLOPT_CUSTOMREQUEST" }
2157 CurlProcessor::ProcessRequest(*this, uri, "DESTROY", options, &rEnv, nullptr, nullptr, nullptr,
2158 nullptr);
2161 auto CurlProcessor::Lock(
2162 CurlSession& rSession, CurlUri const& rURI, DAVRequestEnvironment const* const pEnv,
2163 ::std::unique_ptr<curl_slist, deleter_from_fn<curl_slist, curl_slist_free_all>>
2164 pRequestHeaderList,
2165 uno::Reference<io::XInputStream> const* const pxRequestInStream)
2166 -> ::std::vector<::std::pair<ucb::Lock, sal_Int32>>
2168 curl_off_t len(0);
2169 if (pxRequestInStream)
2171 uno::Reference<io::XSeekable> const xSeekable(*pxRequestInStream, uno::UNO_QUERY);
2172 assert(xSeekable.is());
2173 len = xSeekable->getLength();
2176 ::std::vector<CurlOption> const options{
2177 { CURLOPT_UPLOAD, 1L, nullptr },
2178 { CURLOPT_CUSTOMREQUEST, "LOCK", "CURLOPT_CUSTOMREQUEST" },
2179 // note: Sharepoint cannot handle "Transfer-Encoding: chunked"
2180 { CURLOPT_INFILESIZE_LARGE, len, nullptr, CurlOption::Type::CurlOffT }
2183 // stream for response
2184 uno::Reference<io::XInputStream> const xResponseInStream(io::Pipe::create(rSession.m_xContext));
2185 uno::Reference<io::XOutputStream> const xResponseOutStream(xResponseInStream, uno::UNO_QUERY);
2186 assert(xResponseInStream.is());
2187 assert(xResponseOutStream.is());
2189 TimeValue startTime;
2190 osl_getSystemTime(&startTime);
2192 CurlProcessor::ProcessRequest(rSession, rURI, "LOCK", options, pEnv,
2193 ::std::move(pRequestHeaderList), &xResponseOutStream,
2194 pxRequestInStream, nullptr);
2196 ::std::vector<ucb::Lock> const acquiredLocks(parseWebDAVLockResponse(xResponseInStream));
2197 SAL_WARN_IF(acquiredLocks.empty(), "ucb.ucp.webdav.curl",
2198 "could not get LOCK for " << rURI.GetURI());
2200 TimeValue endTime;
2201 osl_getSystemTime(&endTime);
2202 auto const elapsedSeconds(endTime.Seconds - startTime.Seconds);
2204 // determine expiration time (seconds from endTime) for each acquired lock
2205 ::std::vector<::std::pair<ucb::Lock, sal_Int32>> ret;
2206 ret.reserve(acquiredLocks.size());
2207 for (auto const& rLock : acquiredLocks)
2209 sal_Int32 lockExpirationTimeSeconds;
2210 if (rLock.Timeout == -1)
2212 lockExpirationTimeSeconds = -1;
2214 else if (rLock.Timeout <= elapsedSeconds)
2216 SAL_WARN("ucb.ucp.webdav.curl",
2217 "LOCK timeout already expired when receiving LOCK response for "
2218 << rURI.GetURI());
2219 lockExpirationTimeSeconds = 0;
2221 else
2223 lockExpirationTimeSeconds = startTime.Seconds + rLock.Timeout;
2225 ret.emplace_back(rLock, lockExpirationTimeSeconds);
2228 return ret;
2231 auto CurlSession::LOCK(OUString const& rURIReference, ucb::Lock /*const*/& rLock,
2232 DAVRequestEnvironment const& rEnv) -> void
2234 SAL_INFO("ucb.ucp.webdav.curl", "LOCK: " << rURIReference);
2236 CurlUri const uri(CurlProcessor::URIReferenceToURI(*this, rURIReference));
2238 if (g_Init.LockStore.getLockTokenForURI(uri.GetURI(), &rLock))
2240 // already have a lock that covers the requirement
2241 // TODO: maybe use DAV:lockdiscovery to ensure it's valid
2242 return;
2245 // note: no m_Mutex lock needed here, only in CurlProcessor::Lock()
2247 // generate XML document for acquiring new LOCK
2248 uno::Reference<io::XSequenceOutputStream> const xSeqOutStream(
2249 io::SequenceOutputStream::create(m_xContext));
2250 uno::Reference<io::XOutputStream> const xRequestOutStream(xSeqOutStream);
2251 assert(xRequestOutStream.is());
2252 uno::Reference<xml::sax::XWriter> const xWriter(xml::sax::Writer::create(m_xContext));
2253 xWriter->setOutputStream(xRequestOutStream);
2254 xWriter->startDocument();
2255 rtl::Reference<::comphelper::AttributeList> const pAttrList(new ::comphelper::AttributeList);
2256 pAttrList->AddAttribute("xmlns", "DAV:");
2257 xWriter->startElement("lockinfo", pAttrList);
2258 xWriter->startElement("lockscope", nullptr);
2259 switch (rLock.Scope)
2261 case ucb::LockScope_EXCLUSIVE:
2262 xWriter->startElement("exclusive", nullptr);
2263 xWriter->endElement("exclusive");
2264 break;
2265 case ucb::LockScope_SHARED:
2266 xWriter->startElement("shared", nullptr);
2267 xWriter->endElement("shared");
2268 break;
2269 default:
2270 assert(false);
2272 xWriter->endElement("lockscope");
2273 xWriter->startElement("locktype", nullptr);
2274 xWriter->startElement("write", nullptr);
2275 xWriter->endElement("write");
2276 xWriter->endElement("locktype");
2277 OUString owner;
2278 if ((rLock.Owner >>= owner) && !owner.isEmpty())
2280 xWriter->startElement("owner", nullptr);
2281 xWriter->characters(owner);
2282 xWriter->endElement("owner");
2284 xWriter->endElement("lockinfo");
2285 xWriter->endDocument();
2287 uno::Reference<io::XInputStream> const xRequestInStream(
2288 io::SequenceInputStream::createStreamFromSequence(m_xContext,
2289 xSeqOutStream->getWrittenBytes()));
2290 assert(xRequestInStream.is());
2292 ::std::unique_ptr<curl_slist, deleter_from_fn<curl_slist, curl_slist_free_all>> pList;
2293 pList.reset(curl_slist_append(pList.release(), "Content-Type: application/xml"));
2294 if (!pList)
2296 throw uno::RuntimeException("curl_slist_append failed");
2298 OString depth;
2299 switch (rLock.Depth)
2301 case ucb::LockDepth_ZERO:
2302 depth = "Depth: 0";
2303 break;
2304 case ucb::LockDepth_ONE:
2305 depth = "Depth: 1";
2306 break;
2307 case ucb::LockDepth_INFINITY:
2308 depth = "Depth: infinity";
2309 break;
2310 default:
2311 assert(false);
2313 pList.reset(curl_slist_append(pList.release(), depth.getStr()));
2314 if (!pList)
2316 throw uno::RuntimeException("curl_slist_append failed");
2318 OString timeout;
2319 switch (rLock.Timeout)
2321 case -1:
2322 timeout = "Timeout: Infinite";
2323 break;
2324 case 0:
2325 timeout = "Timeout: Second-180";
2326 break;
2327 default:
2328 timeout = "Timeout: Second-" + OString::number(rLock.Timeout);
2329 assert(0 < rLock.Timeout);
2330 break;
2332 pList.reset(curl_slist_append(pList.release(), timeout.getStr()));
2333 if (!pList)
2335 throw uno::RuntimeException("curl_slist_append failed");
2338 auto const acquiredLocks
2339 = CurlProcessor::Lock(*this, uri, &rEnv, ::std::move(pList), &xRequestInStream);
2341 for (auto const& rAcquiredLock : acquiredLocks)
2343 g_Init.LockStore.addLock(uri.GetURI(), rAcquiredLock.first,
2344 rAcquiredLock.first.LockTokens[0], this, rAcquiredLock.second);
2345 SAL_INFO("ucb.ucp.webdav.curl", "created LOCK for " << rURIReference);
2349 auto CurlProcessor::Unlock(CurlSession& rSession, CurlUri const& rURI,
2350 DAVRequestEnvironment const* const pEnv) -> void
2352 OUString const* const pToken(g_Init.LockStore.getLockTokenForURI(rURI.GetURI(), nullptr));
2353 if (!pToken)
2355 SAL_WARN("ucb.ucp.webdav.curl", "attempt to unlock but not locked");
2356 throw DAVException(DAVException::DAV_NOT_LOCKED);
2358 OString const utf8LockToken("Lock-Token: <"
2359 + OUStringToOString(*pToken, RTL_TEXTENCODING_ASCII_US) + ">");
2360 ::std::unique_ptr<curl_slist, deleter_from_fn<curl_slist, curl_slist_free_all>> pList(
2361 curl_slist_append(nullptr, utf8LockToken.getStr()));
2362 if (!pList)
2364 throw uno::RuntimeException("curl_slist_append failed");
2367 ::std::vector<CurlOption> const options{ { CURLOPT_CUSTOMREQUEST, "UNLOCK",
2368 "CURLOPT_CUSTOMREQUEST" } };
2370 CurlProcessor::ProcessRequest(rSession, rURI, "UNLOCK", options, pEnv, ::std::move(pList),
2371 nullptr, nullptr, nullptr);
2374 auto CurlSession::UNLOCK(OUString const& rURIReference, DAVRequestEnvironment const& rEnv) -> void
2376 SAL_INFO("ucb.ucp.webdav.curl", "UNLOCK: " << rURIReference);
2378 // note: no m_Mutex lock needed here, only in CurlProcessor::Unlock()
2380 CurlUri const uri(CurlProcessor::URIReferenceToURI(*this, rURIReference));
2382 CurlProcessor::Unlock(*this, uri, &rEnv);
2384 g_Init.LockStore.removeLock(uri.GetURI());
2387 auto CurlSession::NonInteractive_LOCK(OUString const& rURI, ::std::u16string_view const rLockToken,
2388 sal_Int32& o_rLastChanceToSendRefreshRequest,
2389 bool& o_rIsAuthFailed) -> bool
2391 SAL_INFO("ucb.ucp.webdav.curl", "NonInteractive_LOCK: " << rURI);
2393 // note: no m_Mutex lock needed here, only in CurlProcessor::Lock()
2397 CurlUri const uri(rURI);
2398 ::std::unique_ptr<curl_slist, deleter_from_fn<curl_slist, curl_slist_free_all>> pList(
2399 curl_slist_append(nullptr, "Timeout: Second-180"));
2401 assert(!rLockToken.empty()); // LockStore is the caller
2402 OString const utf8If("If: (<" + OUStringToOString(rLockToken, RTL_TEXTENCODING_ASCII_US)
2403 + ">)");
2404 pList.reset(curl_slist_append(pList.release(), utf8If.getStr()));
2405 if (!pList)
2407 throw uno::RuntimeException("curl_slist_append failed");
2410 auto const acquiredLocks
2411 = CurlProcessor::Lock(*this, uri, nullptr, ::std::move(pList), nullptr);
2413 SAL_WARN_IF(1 < acquiredLocks.size(), "ucb.ucp.webdav.curl",
2414 "multiple locks acquired on refresh for " << rURI);
2415 if (!acquiredLocks.empty())
2417 o_rLastChanceToSendRefreshRequest = acquiredLocks.begin()->second;
2419 SAL_INFO("ucb.ucp.webdav.curl", "NonInteractive_LOCK succeeded on " << rURI);
2420 return true;
2422 catch (DAVException const& rException)
2424 SAL_INFO("ucb.ucp.webdav.curl", "NonInteractive_LOCK failed on " << rURI);
2425 switch (rException.getError())
2427 case DAVException::DAV_HTTP_AUTH:
2428 case DAVException::DAV_HTTP_NOAUTH:
2429 o_rIsAuthFailed = true;
2430 break;
2431 default:
2432 break;
2434 return false;
2436 catch (...)
2438 SAL_INFO("ucb.ucp.webdav.curl", "NonInteractive_LOCK failed on " << rURI);
2439 return false;
2443 auto CurlSession::NonInteractive_UNLOCK(OUString const& rURI) -> void
2445 SAL_INFO("ucb.ucp.webdav.curl", "NonInteractive_UNLOCK: " << rURI);
2447 // note: no m_Mutex lock needed here, only in CurlProcessor::Unlock()
2451 CurlUri const uri(rURI);
2453 CurlProcessor::Unlock(*this, uri, nullptr);
2455 // the only caller is the dtor of the LockStore, don't call remove!
2456 SAL_INFO("ucb.ucp.webdav.curl", "NonInteractive_UNLOCK succeeded on " << rURI);
2458 catch (...)
2460 SAL_INFO("ucb.ucp.webdav.curl", "NonInteractive_UNLOCK failed on " << rURI);
2464 } // namespace http_dav_ucp
2466 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */