LibreOffice Module ucb (master) 1
CurlSession.cxx
Go to the documentation of this file.
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.
4 *
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 */
9
10#include "CurlSession.hxx"
11
12#include "SerfLockStore.hxx"
13#include "DAVProperties.hxx"
16
18#include <comphelper/lok.hxx>
20#include <comphelper/string.hxx>
21
22#include <o3tl/safeint.hxx>
23#include <o3tl/string_view.hxx>
24
25#include <officecfg/Inet.hxx>
26
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>
32
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 <config_version.h>
39
40#include <map>
41#include <optional>
42#include <tuple>
43#include <utility>
44
45using namespace ::com::sun::star;
46
47namespace
48{
50struct Init
51{
55
56 Init()
57 {
58 if (curl_global_init(CURL_GLOBAL_ALL) != CURLE_OK)
59 {
60 assert(!"curl_global_init failed");
61 }
62 }
63 // do not call curl_global_cleanup() - this is not the only client of curl
64};
65Init g_Init;
66
67struct ResponseHeaders
68{
69 ::std::vector<::std::pair<::std::vector<OString>, ::std::optional<long>>> HeaderFields;
70 CURL* pCurl;
71 ResponseHeaders(CURL* const i_pCurl)
72 : pCurl(i_pCurl)
73 {
74 }
75};
76
77auto GetResponseCode(ResponseHeaders const& rHeaders) -> ::std::optional<long>
78{
79 return (rHeaders.HeaderFields.empty()) ? ::std::optional<long>{}
80 : rHeaders.HeaderFields.back().second;
81}
82
83struct DownloadTarget
84{
85 uno::Reference<io::XOutputStream> xOutStream;
86 ResponseHeaders const& rHeaders;
87 DownloadTarget(uno::Reference<io::XOutputStream> i_xOutStream,
88 ResponseHeaders const& i_rHeaders)
89 : xOutStream(std::move(i_xOutStream))
90 , rHeaders(i_rHeaders)
91 {
92 }
93};
94
95struct UploadSource
96{
97 uno::Sequence<sal_Int8> const& rInData;
98 size_t nPosition;
99 UploadSource(uno::Sequence<sal_Int8> const& i_rInData)
100 : rInData(i_rInData)
101 , nPosition(0)
102 {
103 }
104};
105
106auto GetErrorString(CURLcode const rc, char const* const pErrorBuffer = nullptr) -> OString
107{
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;
111}
112
113auto GetErrorStringMulti(CURLMcode const mc) -> OString
114{
115 return OString::Concat("(") + OString::number(sal_Int32(mc)) + ") " + curl_multi_strerror(mc);
116}
117
119struct CurlOption
120{
121 CURLoption const Option;
122 enum class Type
123 {
124 Pointer,
125 Long,
126 CurlOffT
127 };
128 Type const Tag;
129 union {
130 void const* const pValue;
131 long /*const*/ lValue;
132 curl_off_t /*const*/ cValue;
133 };
134 char const* const pExceptionString;
135
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)
142 {
143 }
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)
151 {
152 static_assert(sizeof(long) <= sizeof(curl_off_t));
153 switch (type)
154 {
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);
163 }
164 }
165};
166
167// NOBODY will prevent logging the response body in ProcessRequest() exception
168// handler, so only use it if logging is disabled
169const 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 };
175
178class Guard
179{
180private:
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;
186
187public:
188 explicit Guard(::std::mutex& rMutex, ::std::vector<CurlOption> aOptions,
189 ::http_dav_ucp::CurlUri const& rURI, CURL* const pCurl)
190 : m_Lock(rMutex, ::std::defer_lock)
191 , m_Options(std::move(aOptions))
192 , m_rURI(rURI)
193 , m_pCurl(pCurl)
194 {
195 Acquire();
196 }
197 ~Guard()
198 {
199 if (m_Lock.owns_lock())
200 {
201 Release();
202 }
203 }
204
205 void Acquire()
206 {
207 assert(!m_Lock.owns_lock());
208 m_Lock.lock();
209 for (auto const& it : m_Options)
210 {
211 CURLcode rc(CURL_LAST); // warning C4701
212 if (it.Tag == CurlOption::Type::Pointer)
213 {
214 rc = curl_easy_setopt(m_pCurl, it.Option, it.pValue);
215 }
216 else if (it.Tag == CurlOption::Type::Long)
217 {
218 rc = curl_easy_setopt(m_pCurl, it.Option, it.lValue);
219 }
220 else if (it.Tag == CurlOption::Type::CurlOffT)
221 {
222 rc = curl_easy_setopt(m_pCurl, it.Option, it.cValue);
223 }
224 else
225 {
226 assert(false);
227 }
228 if (it.pExceptionString != nullptr)
229 {
230 if (rc != CURLE_OK)
231 {
232 SAL_WARN("ucb.ucp.webdav.curl",
233 "set " << it.pExceptionString << " failed: " << GetErrorString(rc));
234 throw ::http_dav_ucp::DAVException(
237 m_rURI.GetPort()));
238 }
239 }
240 else // many of the options cannot fail
241 {
242 assert(rc == CURLE_OK);
243 }
244 }
245 }
246 void Release()
247 {
248 assert(m_Lock.owns_lock());
249 for (auto const& it : m_Options)
250 {
251 CURLcode rc(CURL_LAST); // warning C4701
252 if (it.Tag == CurlOption::Type::Pointer)
253 {
254 rc = curl_easy_setopt(m_pCurl, it.Option, nullptr);
255 }
256 else if (it.Tag == CurlOption::Type::Long)
257 {
258 rc = curl_easy_setopt(m_pCurl, it.Option, 0L);
259 }
260 else if (it.Tag == CurlOption::Type::CurlOffT)
261 {
262 rc = curl_easy_setopt(m_pCurl, it.Option, curl_off_t(-1));
263 }
264 else
265 {
266 assert(false);
267 }
268 assert(rc == CURLE_OK);
269 (void)rc;
270 }
271 m_Lock.unlock();
272 }
273};
274
275} // namespace
276
277namespace http_dav_ucp
278{
279// libcurl callbacks:
280
281static int debug_callback(CURL* handle, curl_infotype type, char* data, size_t size,
282 void* /*userdata*/)
283{
284 char const* pType(nullptr);
285 switch (type)
286 {
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:
295 {
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)
300 {
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 Concat2View(OString::number(end - start - len) + " bytes redacted"));
307 }
308 SAL_INFO("ucb.ucp.webdav.curl", "CURLINFO_HEADER_OUT: " << handle << ": " << tmp);
309 return 0;
310 }
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;
326 }
327 SAL_INFO("ucb.ucp.webdav.curl", "debug log: " << handle << ": " << pType << " " << size);
328 return 0;
329}
330
331static size_t write_callback(char* const ptr, size_t const size, size_t const nmemb,
332 void* const userdata)
333{
334 auto* const pTarget(static_cast<DownloadTarget*>(userdata));
335 if (!pTarget) // looks like ~every request may have a response body
336 {
337 return nmemb;
338 }
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)
344 {
345 return 0; // that is an error
346 }
347 // always write, for exception handler in ProcessRequest()
348 // if (200 <= *oResponseCode && *oResponseCode < 300)
349 {
350 try
351 {
352 uno::Sequence<sal_Int8> const data(reinterpret_cast<sal_Int8*>(ptr), nmemb);
353 pTarget->xOutStream->writeBytes(data);
354 }
355 catch (...)
356 {
357 SAL_WARN("ucb.ucp.webdav.curl", "exception in write_callback");
358 return 0; // error
359 }
360 }
361 // else: ignore the body? CurlSession will check the status eventually
362 return nmemb;
363}
364
365static size_t read_callback(char* const buffer, size_t const size, size_t const nitems,
366 void* const userdata)
367{
368 auto* const pSource(static_cast<UploadSource*>(userdata));
369 assert(pSource);
370 size_t const nBytes(size * nitems);
371 size_t nRet(0);
372 try
373 {
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;
378 }
379 catch (...)
380 {
381 SAL_WARN("ucb.ucp.webdav.curl", "exception in read_callback");
382 return CURL_READFUNC_ABORT; // error
383 }
384 return nRet;
385}
386
387static size_t header_callback(char* const buffer, size_t const size, size_t const nitems,
388 void* const userdata)
389{
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
394 {
395 return nitems;
396 }
397#endif
398 assert(size == 1); // says the man page
399 (void)size;
400 try
401 {
402 if (nitems <= 2)
403 {
404 // end of header, body follows...
405 if (pHeaders->HeaderFields.empty())
406 {
407 SAL_WARN("ucb.ucp.webdav.curl", "header_callback: empty header?");
408 return 0; // error
409 }
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);
418 }
419 else if (buffer[0] == ' ' || buffer[0] == '\t') // folded header field?
420 {
421 size_t i(0);
422 do
423 {
424 ++i;
425 } while (i == ' ' || i == '\t');
426 if (pHeaders->HeaderFields.empty() || pHeaders->HeaderFields.back().second
427 || pHeaders->HeaderFields.back().first.empty())
428 {
429 SAL_WARN("ucb.ucp.webdav.curl",
430 "header_callback: folded header field without start");
431 return 0; // error
432 }
433 pHeaders->HeaderFields.back().first.back()
434 += OString::Concat(" ") + ::std::string_view(&buffer[i], nitems - i);
435 }
436 else
437 {
438 if (pHeaders->HeaderFields.empty() || pHeaders->HeaderFields.back().second)
439 {
440 pHeaders->HeaderFields.emplace_back();
441 }
442 pHeaders->HeaderFields.back().first.emplace_back(OString(buffer, nitems));
443 }
444 }
445 catch (...)
446 {
447 SAL_WARN("ucb.ucp.webdav.curl", "exception in header_callback");
448 return 0; // error
449 }
450 return nitems;
451}
452
453static auto ProcessHeaders(::std::vector<OString> const& rHeaders) -> ::std::map<OUString, OUString>
454{
455 ::std::map<OUString, OUString> ret;
456 for (OString const& rLine : rHeaders)
457 {
458 OString line;
459 if (!rLine.endsWith("\r\n", &line))
460 {
461 SAL_WARN("ucb.ucp.webdav.curl", "invalid header field (no CRLF)");
462 continue;
463 }
464 if (line.startsWith("HTTP/") // first line
465 || line.isEmpty()) // last line
466 {
467 continue;
468 }
469 auto const nColon(line.indexOf(':'));
470 if (nColon == -1)
471 {
472 {
473 SAL_WARN("ucb.ucp.webdav.curl", "invalid header field (no :)");
474 }
475 continue;
476 }
477 if (nColon == 0)
478 {
479 SAL_WARN("ucb.ucp.webdav.curl", "invalid header field (empty name)");
480 continue;
481 }
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'))
487 {
488 ++nStart;
489 }
490 sal_Int32 nEnd(line.getLength());
491 while (nStart < nEnd && (line[nEnd - 1] == ' ' || line[nEnd - 1] == '\t'))
492 {
493 --nEnd;
494 }
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())
500 {
501 it->second = it->second + "," + value;
502 }
503 else
504 {
505 ret[name] = value;
506 }
507 }
508 return ret;
509}
510
512 ResponseHeaders const& rHeaders,
513 ::std::pair<::std::vector<OUString> const&, DAVResource&> const* const pRequestedHeaders)
514 -> void
515{
516 ::std::map<OUString, OUString> const headerMap(
517 ProcessHeaders(rHeaders.HeaderFields.back().first));
518 if (pRequestedHeaders)
519 {
520 for (OUString const& rHeader : pRequestedHeaders->first)
521 {
522 auto const it(headerMap.find(rHeader.toAsciiLowerCase()));
523 if (it != headerMap.end())
524 {
526 value.IsCaseSensitive = false;
527 value.Name = it->first;
528 value.Value <<= it->second;
529 pRequestedHeaders->second.properties.push_back(value);
530 }
531 }
532 }
533}
534
535// this appears to be the only way to get the "realm" from libcurl
536static auto ExtractRealm(ResponseHeaders const& rHeaders, char const* const pAuthHeaderName)
537 -> ::std::optional<OUString>
538{
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())
543 {
544 SAL_WARN("ucb.ucp.webdav.curl", "cannot find auth header");
545 return {};
546 }
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)
554 {
555 SAL_INFO("ucb.ucp.webdav.curl", "auth header has no realm");
556 return {};
557 }
558 // no whitespace allowed before or after =
559 i += ::std::strlen("realm=");
560 if (it->second.getLength() < i + 2 || it->second[i] != '\"')
561 {
562 SAL_WARN("ucb.ucp.webdav.curl", "no realm value");
563 return {};
564 }
565 ++i;
566 OUStringBuffer buf;
567 while (i < it->second.getLength() && it->second[i] != '\"')
568 {
569 if (it->second[i] == '\\') // quoted-pair escape
570 {
571 ++i;
572 if (it->second.getLength() <= i)
573 {
574 SAL_WARN("ucb.ucp.webdav.curl", "unterminated quoted-pair");
575 return {};
576 }
577 }
578 buf.append(it->second[i]);
579 ++i;
580 }
581 if (it->second.getLength() <= i)
582 {
583 SAL_WARN("ucb.ucp.webdav.curl", "unterminated realm");
584 return {};
585 }
586 return buf.makeStringAndClear();
587}
588
589CurlSession::CurlSession(uno::Reference<uno::XComponentContext> 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(std::move(xContext))
595 , m_Flags(rFlags)
596 , m_URI(rURI)
597 , m_Proxy(rProxyDecider.getProxy(m_URI.GetScheme(), m_URI.GetHost(), m_URI.GetPort()))
598{
599 assert(m_URI.GetScheme() == "http" || m_URI.GetScheme() == "https");
600 m_pCurlMulti.reset(curl_multi_init());
601 if (!m_pCurlMulti)
602 {
603 SAL_WARN("ucb.ucp.webdav.curl", "curl_multi_init failed");
606 }
607 m_pCurl.reset(curl_easy_init());
608 if (!m_pCurl)
609 {
610 SAL_WARN("ucb.ucp.webdav.curl", "curl_easy_init failed");
613 }
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)
633 {
634 SAL_WARN("ucb.ucp.webdav.curl", "CURLOPT_USERAGENT failed: " << GetErrorString(rc));
637 }
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)
652 {
653 SAL_WARN("ucb.ucp.webdav.curl", "CURLOPT_ACCEPT_ENCODING failed: " << GetErrorString(rc));
656 }
657 auto const connectTimeout(officecfg::Inet::Settings::ConnectTimeout::get());
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)
662 {
663 SAL_WARN("ucb.ucp.webdav.curl", "CURLOPT_CONNECTTIMEOUT failed: " << GetErrorString(rc));
666 }
667 auto const readTimeout(officecfg::Inet::Settings::ReadTimeout::get());
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)
672 {
673 SAL_WARN("ucb.ucp.webdav.curl", "CURLOPT_TIMEOUT failed: " << GetErrorString(rc));
676 }
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)
699 {
700 SAL_WARN("ucb.ucp.webdav.curl", "CURLOPT_PROXY failed: " << GetErrorString(rc));
703 }
704 if (!m_Proxy.aName.isEmpty())
705 {
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
711 }
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>())
715 {
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);
720 }
721 // If WOPI-like host has self-signed certificate, it's not possible to insert images
722 // to the document, so here is a compromise. The user has already accepted the self
723 // signed certificate in the browser, when we get here.
725 {
726 rc = curl_easy_setopt(m_pCurl.get(), CURLOPT_SSL_VERIFYPEER, 0L);
727 assert(rc == CURLE_OK);
728 }
729}
730
732
733auto CurlSession::CanUse(OUString const& rURI, uno::Sequence<beans::NamedValue> const& rFlags)
734 -> bool
735{
736 try
737 {
738 CurlUri const uri(rURI);
739
740 return m_URI.GetScheme() == uri.GetScheme() && m_URI.GetHost() == uri.GetHost()
741 && m_URI.GetPort() == uri.GetPort() && m_Flags == rFlags;
742 }
743 catch (DAVException const&)
744 {
745 return false;
746 }
747}
748
750{
751 assert(m_URI.GetScheme() == "http" || m_URI.GetScheme() == "https");
752 return !m_Proxy.aName.isEmpty();
753}
754
755auto CurlSession::abort() -> void
756{
757 // note: abort() was a no-op since OOo 3.2 and before that it crashed.
758 bool expected(false);
759 // it would be pointless to lock m_Mutex here as the other thread holds it
760 if (m_AbortFlag.compare_exchange_strong(expected, true))
761 {
762 // This function looks safe to call without m_Mutex as long as the
763 // m_pCurlMulti handle is not destroyed, and the caller must own a ref
764 // to this object which keeps it alive; it should cause poll to return.
765 curl_multi_wakeup(m_pCurlMulti.get());
766 }
767}
768
771{
772 static auto URIReferenceToURI(CurlSession& rSession, std::u16string_view rURIReference)
773 -> CurlUri;
774
775 static auto ProcessRequestImpl(
776 CurlSession& rSession, CurlUri const& rURI, OUString const& rMethod,
777 curl_slist* pRequestHeaderList, uno::Reference<io::XOutputStream> const* pxOutStream,
778 uno::Sequence<sal_Int8> const* pInData,
779 ::std::pair<::std::vector<OUString> const&, DAVResource&> const* pRequestedHeaders,
780 ResponseHeaders& rHeaders) -> void;
781
782 static auto ProcessRequest(
783 CurlSession& rSession, CurlUri const& rURI, OUString const& rMethod,
784 ::std::vector<CurlOption> const& rOptions, DAVRequestEnvironment const* pEnv,
785 ::std::unique_ptr<curl_slist, deleter_from_fn<curl_slist, curl_slist_free_all>>
786 pRequestHeaderList,
787 uno::Reference<io::XOutputStream> const* pxOutStream,
788 uno::Reference<io::XInputStream> const* pxInStream,
789 ::std::pair<::std::vector<OUString> const&, DAVResource&> const* pRequestedHeaders) -> void;
790
791 static auto
792 PropFind(CurlSession& rSession, CurlUri const& rURI, Depth depth,
793 ::std::tuple<::std::vector<OUString> const&, ::std::vector<DAVResource>* const,
794 ::std::vector<ucb::Lock>* const> const* o_pRequestedProperties,
795 ::std::vector<DAVResourceInfo>* const o_pResourceInfos,
796 DAVRequestEnvironment const& rEnv) -> void;
797
798 static auto MoveOrCopy(CurlSession& rSession, std::u16string_view rSourceURIReference,
799 ::std::u16string_view rDestinationURI, DAVRequestEnvironment const& rEnv,
800 bool isOverwrite, char const* pMethod) -> void;
801
802 static auto Lock(CurlSession& rSession, CurlUri const& rURI, DAVRequestEnvironment const* pEnv,
803 ::std::unique_ptr<curl_slist, deleter_from_fn<curl_slist, curl_slist_free_all>>
804 pRequestHeaderList,
805 uno::Reference<io::XInputStream> const* pxInStream)
806 -> ::std::vector<::std::pair<ucb::Lock, sal_Int32>>;
807
808 static auto Unlock(CurlSession& rSession, CurlUri const& rURI,
809 DAVRequestEnvironment const* pEnv) -> void;
810};
811
812auto CurlProcessor::URIReferenceToURI(CurlSession& rSession, std::u16string_view rURIReference)
813 -> CurlUri
814{
815 // No need to acquire rSession.m_Mutex because accessed members are const.
816 if (rSession.UsesProxy())
817 // very odd, but see DAVResourceAccess::getRequestURI() :-/
818 {
819 assert(o3tl::starts_with(rURIReference, u"http://")
820 || o3tl::starts_with(rURIReference, u"https://"));
821 return CurlUri(rURIReference);
822 }
823 else
824 {
825 assert(o3tl::starts_with(rURIReference, u"/"));
826 return rSession.m_URI.CloneWithRelativeRefPathAbsolute(rURIReference);
827 }
828}
829
832 CurlSession& rSession, CurlUri const& rURI, OUString const& rMethod,
833 curl_slist* const pRequestHeaderList,
834 uno::Reference<io::XOutputStream> const* const pxOutStream,
835 uno::Sequence<sal_Int8> const* const pInData,
836 ::std::pair<::std::vector<OUString> const&, DAVResource&> const* const pRequestedHeaders,
837 ResponseHeaders& rHeaders) -> void
838{
839 ::comphelper::ScopeGuard const g([&]() {
840 auto rc = curl_easy_setopt(rSession.m_pCurl.get(), CURLOPT_HEADERDATA, nullptr);
841 assert(rc == CURLE_OK);
842 (void)rc;
843 if (pxOutStream)
844 {
845 rc = curl_easy_setopt(rSession.m_pCurl.get(), CURLOPT_WRITEDATA, nullptr);
846 assert(rc == CURLE_OK);
847 }
848 if (pInData)
849 {
850 rc = curl_easy_setopt(rSession.m_pCurl.get(), CURLOPT_READDATA, nullptr);
851 assert(rc == CURLE_OK);
852 rc = curl_easy_setopt(rSession.m_pCurl.get(), CURLOPT_UPLOAD, 0L);
853 assert(rc == CURLE_OK);
854 }
855 if (pRequestHeaderList)
856 {
857 rc = curl_easy_setopt(rSession.m_pCurl.get(), CURLOPT_HTTPHEADER, nullptr);
858 assert(rc == CURLE_OK);
859 }
860 });
861
862 if (pRequestHeaderList)
863 {
864 auto rc = curl_easy_setopt(rSession.m_pCurl.get(), CURLOPT_HTTPHEADER, pRequestHeaderList);
865 assert(rc == CURLE_OK);
866 (void)rc;
867 }
868
869 auto rc = curl_easy_setopt(rSession.m_pCurl.get(), CURLOPT_CURLU, rURI.GetCURLU());
870 assert(rc == CURLE_OK); // can't fail since 7.63.0
871
872 rc = curl_easy_setopt(rSession.m_pCurl.get(), CURLOPT_HEADERDATA, &rHeaders);
873 assert(rc == CURLE_OK);
874 ::std::optional<DownloadTarget> oDownloadTarget;
875 if (pxOutStream)
876 {
877 oDownloadTarget.emplace(*pxOutStream, rHeaders);
878 rc = curl_easy_setopt(rSession.m_pCurl.get(), CURLOPT_WRITEDATA, &*oDownloadTarget);
879 assert(rc == CURLE_OK);
880 }
881 ::std::optional<UploadSource> oUploadSource;
882 if (pInData)
883 {
884 oUploadSource.emplace(*pInData);
885 rc = curl_easy_setopt(rSession.m_pCurl.get(), CURLOPT_READDATA, &*oUploadSource);
886 assert(rc == CURLE_OK);
887 }
888 rSession.m_ErrorBuffer[0] = '\0';
889
890 // note: easy handle must be added for *every* transfer!
891 // otherwise it gets stuck in MSTATE_MSGSENT forever after 1st transfer
892 auto mc = curl_multi_add_handle(rSession.m_pCurlMulti.get(), rSession.m_pCurl.get());
893 if (mc != CURLM_OK)
894 {
895 SAL_WARN("ucb.ucp.webdav.curl",
896 "curl_multi_add_handle failed: " << GetErrorStringMulti(mc));
897 throw DAVException(
899 ConnectionEndPointString(rSession.m_URI.GetHost(), rSession.m_URI.GetPort()));
900 }
901 ::comphelper::ScopeGuard const gg([&]() {
902 mc = curl_multi_remove_handle(rSession.m_pCurlMulti.get(), rSession.m_pCurl.get());
903 if (mc != CURLM_OK)
904 {
905 SAL_WARN("ucb.ucp.webdav.curl",
906 "curl_multi_remove_handle failed: " << GetErrorStringMulti(mc));
907 }
908 });
909
910 // this is where libcurl actually does something
911 rc = CURL_LAST; // clear current value
912 int nRunning;
913 do
914 {
915 mc = curl_multi_perform(rSession.m_pCurlMulti.get(), &nRunning);
916 if (mc != CURLM_OK)
917 {
918 SAL_WARN("ucb.ucp.webdav.curl",
919 "curl_multi_perform failed: " << GetErrorStringMulti(mc));
920 throw DAVException(
922 ConnectionEndPointString(rSession.m_URI.GetHost(), rSession.m_URI.GetPort()));
923 }
924 if (nRunning == 0)
925 { // short request like HEAD on loopback could be done in first call
926 break;
927 }
928 int nFDs;
929 mc = curl_multi_poll(rSession.m_pCurlMulti.get(), nullptr, 0, rSession.m_nReadTimeout,
930 &nFDs);
931 if (mc != CURLM_OK)
932 {
933 SAL_WARN("ucb.ucp.webdav.curl", "curl_multi_poll failed: " << GetErrorStringMulti(mc));
934 throw DAVException(
936 ConnectionEndPointString(rSession.m_URI.GetHost(), rSession.m_URI.GetPort()));
937 }
938 if (rSession.m_AbortFlag.load())
939 { // flag was set by abort() -> not sure what exception to throw?
940 throw DAVException(DAVException::DAV_HTTP_ERROR, "abort() was called", 0);
941 }
942 } while (nRunning != 0);
943 // there should be exactly 1 CURLMsg now, but the interface is
944 // extensible so future libcurl versions could yield additional things
945 do
946 {
947 CURLMsg const* const pMsg = curl_multi_info_read(rSession.m_pCurlMulti.get(), &nRunning);
948 if (pMsg && pMsg->msg == CURLMSG_DONE)
949 {
950 assert(pMsg->easy_handle == rSession.m_pCurl.get());
951 rc = pMsg->data.result;
952 }
953 else
954 {
955 SAL_WARN("ucb.ucp.webdav.curl", "curl_multi_info_read unexpected result");
956 }
957 } while (nRunning != 0);
958
959 // error handling part 1: libcurl errors
960 if (rc != CURLE_OK)
961 {
962 // TODO: is there any value in extracting CURLINFO_OS_ERRNO
963 SAL_WARN("ucb.ucp.webdav.curl",
964 "curl_easy_perform failed: " << GetErrorString(rc, rSession.m_ErrorBuffer));
965 switch (rc)
966 {
967 case CURLE_UNSUPPORTED_PROTOCOL:
969 case CURLE_COULDNT_RESOLVE_PROXY:
970 throw DAVException(
972 ConnectionEndPointString(rSession.m_Proxy.aName, rSession.m_Proxy.nPort));
973 case CURLE_COULDNT_RESOLVE_HOST:
974 throw DAVException(
976 ConnectionEndPointString(rSession.m_URI.GetHost(), rSession.m_URI.GetPort()));
977 case CURLE_COULDNT_CONNECT:
978 case CURLE_SSL_CONNECT_ERROR:
979 case CURLE_SSL_CERTPROBLEM:
980 case CURLE_SSL_CIPHER:
981 case CURLE_PEER_FAILED_VERIFICATION:
982 case CURLE_SSL_ISSUER_ERROR:
983 case CURLE_SSL_PINNEDPUBKEYNOTMATCH:
984 case CURLE_SSL_INVALIDCERTSTATUS:
985 case CURLE_FAILED_INIT:
986#if CURL_AT_LEAST_VERSION(7, 69, 0)
987 case CURLE_QUIC_CONNECT_ERROR:
988#endif
989 throw DAVException(
991 ConnectionEndPointString(rSession.m_URI.GetHost(), rSession.m_URI.GetPort()));
992 case CURLE_REMOTE_ACCESS_DENIED:
993 case CURLE_LOGIN_DENIED:
994 case CURLE_AUTH_ERROR:
995 throw DAVException(
996 DAVException::DAV_HTTP_AUTH, // probably?
997 ConnectionEndPointString(rSession.m_URI.GetHost(), rSession.m_URI.GetPort()));
998 case CURLE_WRITE_ERROR:
999 case CURLE_READ_ERROR: // error returned from our callbacks
1000 case CURLE_OUT_OF_MEMORY:
1001 case CURLE_ABORTED_BY_CALLBACK:
1002 case CURLE_BAD_FUNCTION_ARGUMENT:
1003 case CURLE_SEND_ERROR:
1004 case CURLE_RECV_ERROR:
1005 case CURLE_SSL_CACERT_BADFILE:
1006 case CURLE_SSL_CRL_BADFILE:
1007 case CURLE_RECURSIVE_API_CALL:
1008 throw DAVException(
1010 ConnectionEndPointString(rSession.m_URI.GetHost(), rSession.m_URI.GetPort()));
1011 case CURLE_OPERATION_TIMEDOUT:
1012 throw DAVException(
1014 ConnectionEndPointString(rSession.m_URI.GetHost(), rSession.m_URI.GetPort()));
1015 default: // lots of generic errors
1017 }
1018 }
1019 // error handling part 2: HTTP status codes
1020 long statusCode(SC_NONE);
1021 rc = curl_easy_getinfo(rSession.m_pCurl.get(), CURLINFO_RESPONSE_CODE, &statusCode);
1022 assert(rc == CURLE_OK);
1023 assert(statusCode != SC_NONE); // ??? should be error returned from perform?
1024 SAL_INFO("ucb.ucp.webdav.curl", "HTTP status code: " << statusCode);
1025 if (statusCode < 300)
1026 {
1027 // neon did this regardless of status or even error, which seems odd
1028 ExtractRequestedHeaders(rHeaders, pRequestedHeaders);
1029 }
1030 else
1031 {
1032 // create message containing the HTTP method and response status line
1033 OUString statusLine("\n" + rMethod + "\n=>\n");
1034 if (!rHeaders.HeaderFields.empty() && !rHeaders.HeaderFields.back().first.empty()
1035 && rHeaders.HeaderFields.back().first.front().startsWith("HTTP"))
1036 {
1037 statusLine += ::rtl::OStringToOUString(
1038 ::o3tl::trim(rHeaders.HeaderFields.back().first.front()),
1039 RTL_TEXTENCODING_ASCII_US);
1040 }
1041 switch (statusCode)
1042 {
1043 case SC_REQUEST_TIMEOUT:
1044 {
1045 throw DAVException(
1047 ConnectionEndPointString(rSession.m_URI.GetHost(), rSession.m_URI.GetPort()));
1048 break;
1049 }
1052 case SC_SEE_OTHER:
1054 {
1055 // could also use CURLOPT_FOLLOWLOCATION but apparently the
1056 // upper layer wants to know about redirects?
1057 char* pRedirectURL(nullptr);
1058 rc = curl_easy_getinfo(rSession.m_pCurl.get(), CURLINFO_REDIRECT_URL,
1059 &pRedirectURL);
1060 assert(rc == CURLE_OK);
1061 if (pRedirectURL)
1062 {
1063 // Sharepoint 2016 workaround: contains unencoded U+0020
1064 OUString const redirectURL(::rtl::Uri::encode(
1065 pRedirectURL
1066 ? OUString(pRedirectURL, strlen(pRedirectURL), RTL_TEXTENCODING_UTF8)
1067 : OUString(),
1068 rtl_UriCharClassUric, rtl_UriEncodeKeepEscapes, RTL_TEXTENCODING_UTF8));
1069
1071 }
1072 [[fallthrough]];
1073 }
1074 default:
1075 throw DAVException(DAVException::DAV_HTTP_ERROR, statusLine, statusCode);
1076 }
1077 }
1078
1079 if (pxOutStream)
1080 {
1081 (*pxOutStream)->closeOutput(); // signal EOF
1082 }
1083}
1084
1085static auto TryRemoveExpiredLockToken(CurlSession& rSession, CurlUri const& rURI,
1086 DAVRequestEnvironment const* const pEnv) -> bool
1087{
1088 if (!pEnv)
1089 {
1090 // caller was a NonInteractive_*LOCK function anyway, its caller is LockStore
1091 return false;
1092 }
1093 OUString const* const pToken(g_Init.LockStore.getLockTokenForURI(rURI.GetURI(), nullptr));
1094 if (!pToken)
1095 {
1096 return false;
1097 }
1098 try
1099 {
1100 // determine validity of existing lock via lockdiscovery request
1101 ::std::vector<OUString> const propertyNames{ DAVProperties::LOCKDISCOVERY };
1102 ::std::vector<ucb::Lock> locks;
1103 ::std::tuple<::std::vector<OUString> const&, ::std::vector<DAVResource>* const,
1104 ::std::vector<ucb::Lock>* const> const args(propertyNames, nullptr, &locks);
1105
1106 CurlProcessor::PropFind(rSession, rURI, DAVZERO, &args, nullptr, *pEnv);
1107
1108 // https://datatracker.ietf.org/doc/html/rfc4918#section-15.8
1109 // The response MAY not contain tokens, but hopefully it
1110 // will if client is properly authenticated.
1111 if (::std::any_of(locks.begin(), locks.end(), [pToken](ucb::Lock const& rLock) {
1112 return ::std::any_of(
1113 rLock.LockTokens.begin(), rLock.LockTokens.end(),
1114 [pToken](OUString const& rToken) { return *pToken == rToken; });
1115 }))
1116 {
1117 return false; // still have the lock
1118 }
1119
1120 SAL_INFO("ucb.ucp.webdav.curl",
1121 "lock token expired, removing: " << rURI.GetURI() << " " << *pToken);
1122 g_Init.LockStore.removeLock(rURI.GetURI());
1123 return true;
1124 }
1125 catch (DAVException const&)
1126 {
1127 return false; // ignore, the caller already has a better exception
1128 }
1129}
1130
1131auto CurlProcessor::ProcessRequest(
1132 CurlSession& rSession, CurlUri const& rURI, OUString const& rMethod,
1133 ::std::vector<CurlOption> const& rOptions, DAVRequestEnvironment const* const pEnv,
1134 ::std::unique_ptr<curl_slist, deleter_from_fn<curl_slist, curl_slist_free_all>>
1135 pRequestHeaderList,
1136 uno::Reference<io::XOutputStream> const* const pxOutStream,
1137 uno::Reference<io::XInputStream> const* const pxInStream,
1138 ::std::pair<::std::vector<OUString> const&, DAVResource&> const* const pRequestedHeaders)
1139 -> void
1140{
1141 if (pEnv)
1142 { // add custom request headers passed by caller
1143 for (auto const& rHeader : pEnv->m_aRequestHeaders)
1144 {
1145 OString const utf8Header(
1146 OUStringToOString(rHeader.first, RTL_TEXTENCODING_ASCII_US) + ": "
1147 + OUStringToOString(rHeader.second, RTL_TEXTENCODING_ASCII_US));
1148 pRequestHeaderList.reset(
1149 curl_slist_append(pRequestHeaderList.release(), utf8Header.getStr()));
1150 if (!pRequestHeaderList)
1151 {
1152 throw uno::RuntimeException("curl_slist_append failed");
1153 }
1154 }
1155 }
1156
1157 uno::Sequence<sal_Int8> data;
1158 if (pxInStream)
1159 {
1160 uno::Reference<io::XSeekable> const xSeekable(*pxInStream, uno::UNO_QUERY);
1161 if (xSeekable.is())
1162 {
1163 auto const len(xSeekable->getLength() - xSeekable->getPosition());
1164 if ((**pxInStream).readBytes(data, len) != len)
1165 {
1166 throw uno::RuntimeException("short readBytes");
1167 }
1168 }
1169 else
1170 {
1171 ::std::vector<uno::Sequence<sal_Int8>> bufs;
1172 bool isDone(false);
1173 do
1174 {
1175 bufs.emplace_back();
1176 isDone = (**pxInStream).readSomeBytes(bufs.back(), 65536) == 0;
1177 } while (!isDone);
1178 sal_Int32 nSize(0);
1179 for (auto const& rBuf : bufs)
1180 {
1181 if (o3tl::checked_add(nSize, rBuf.getLength(), nSize))
1182 {
1183 throw std::bad_alloc(); // too large for Sequence
1184 }
1185 }
1186 data.realloc(nSize);
1187 size_t nCopied(0);
1188 for (auto const& rBuf : bufs)
1189 {
1190 ::std::memcpy(data.getArray() + nCopied, rBuf.getConstArray(), rBuf.getLength());
1191 nCopied += rBuf.getLength(); // can't overflow
1192 }
1193 }
1194 }
1195
1196 // Clear flag before transfer starts; only a transfer started before
1197 // calling abort() will be aborted, not one started later.
1198 rSession.m_AbortFlag.store(false);
1199
1200 Guard guard(rSession.m_Mutex, rOptions, rURI, rSession.m_pCurl.get());
1201
1202 // authentication data may be in the URI, or requested via XInteractionHandler
1203 struct Auth
1204 {
1205 OUString UserName;
1206 OUString PassWord;
1207 decltype(CURLAUTH_ANY) AuthMask;
1208 Auth(OUString aUserName, OUString aPassword, decltype(CURLAUTH_ANY) const & rAuthMask)
1209 : UserName(std::move(aUserName))
1210 , PassWord(std::move(aPassword))
1211 , AuthMask(rAuthMask)
1212 {
1213 }
1214 };
1215 ::std::optional<Auth> oAuth;
1216 ::std::optional<Auth> oAuthProxy;
1217 if (pEnv && !rSession.m_isAuthenticatedProxy && !rSession.m_Proxy.aName.isEmpty())
1218 {
1219 // the hope is that this must be a URI
1220 CurlUri const uri(rSession.m_Proxy.aName);
1221 if (!uri.GetUser().isEmpty() || !uri.GetPassword().isEmpty())
1222 {
1223 oAuthProxy.emplace(uri.GetUser(), uri.GetPassword(), CURLAUTH_ANY);
1224 }
1225 }
1226 decltype(CURLAUTH_ANY) const authSystem(CURLAUTH_NEGOTIATE | CURLAUTH_NTLM | CURLAUTH_NTLM_WB);
1227 if (pRequestedHeaders || (pEnv && !rSession.m_isAuthenticated))
1228 {
1229 // m_aRequestURI *may* be a path or *may* be URI - wtf
1230 // TODO: why is there this m_aRequestURI and also rURIReference argument?
1231 // ... only caller is DAVResourceAccess - always identical except MOVE/COPY
1232 // which doesn't work if it's just a URI reference so let's just use
1233 // rURIReference via rURI instead
1234#if 0
1235 CurlUri const uri(pEnv->m_aRequestURI);
1236#endif
1237 // note: due to parsing bug pwd didn't work in previous webdav ucps
1238 if (pEnv && !rSession.m_isAuthenticated
1239 && (!rURI.GetUser().isEmpty() || !rURI.GetPassword().isEmpty()))
1240 {
1241 oAuth.emplace(rURI.GetUser(), rURI.GetPassword(), CURLAUTH_ANY);
1242 }
1243 if (pRequestedHeaders)
1244 {
1245 // note: Previously this would be the rURIReference directly but
1246 // that ends up in CurlUri anyway and curl is unhappy.
1247 // But it looks like all consumers of this .uri are interested
1248 // only in the path, so it shouldn't make a difference to give
1249 // the entire URI when the caller extracts the path anyway.
1250 pRequestedHeaders->second.uri = rURI.GetURI();
1251 pRequestedHeaders->second.properties.clear();
1252 }
1253 }
1254 bool isRetry(false);
1255 bool isFallbackHTTP10(false);
1256 int nAuthRequests(0);
1257 int nAuthRequestsProxy(0);
1258
1259 // libcurl does not have an authentication callback so handle auth
1260 // related status codes and requesting credentials via this loop
1261 do
1262 {
1263 isRetry = false;
1264
1265 // re-check m_isAuthenticated flags every time, could have been set
1266 // by re-entrant call
1267 if (oAuth && !rSession.m_isAuthenticated)
1268 {
1269 OString const utf8UserName(OUStringToOString(oAuth->UserName, RTL_TEXTENCODING_UTF8));
1270 auto rc
1271 = curl_easy_setopt(rSession.m_pCurl.get(), CURLOPT_USERNAME, utf8UserName.getStr());
1272 if (rc != CURLE_OK)
1273 {
1274 SAL_WARN("ucb.ucp.webdav.curl", "CURLOPT_USERNAME failed: " << GetErrorString(rc));
1275 throw DAVException(DAVException::DAV_INVALID_ARG);
1276 }
1277 OString const utf8PassWord(OUStringToOString(oAuth->PassWord, RTL_TEXTENCODING_UTF8));
1278 rc = curl_easy_setopt(rSession.m_pCurl.get(), CURLOPT_PASSWORD, utf8PassWord.getStr());
1279 if (rc != CURLE_OK)
1280 {
1281 SAL_WARN("ucb.ucp.webdav.curl", "CURLOPT_PASSWORD failed: " << GetErrorString(rc));
1282 throw DAVException(DAVException::DAV_INVALID_ARG);
1283 }
1284 rc = curl_easy_setopt(rSession.m_pCurl.get(), CURLOPT_HTTPAUTH, oAuth->AuthMask);
1285 assert(
1286 rc
1287 == CURLE_OK); // it shouldn't be possible to reduce auth to 0 via the authSystem masks
1288 }
1289
1290 if (oAuthProxy && !rSession.m_isAuthenticatedProxy)
1291 {
1292 OString const utf8UserName(
1293 OUStringToOString(oAuthProxy->UserName, RTL_TEXTENCODING_UTF8));
1294 auto rc = curl_easy_setopt(rSession.m_pCurl.get(), CURLOPT_PROXYUSERNAME,
1295 utf8UserName.getStr());
1296 if (rc != CURLE_OK)
1297 {
1298 SAL_WARN("ucb.ucp.webdav.curl",
1299 "CURLOPT_PROXYUSERNAME failed: " << GetErrorString(rc));
1300 throw DAVException(DAVException::DAV_INVALID_ARG);
1301 }
1302 OString const utf8PassWord(
1303 OUStringToOString(oAuthProxy->PassWord, RTL_TEXTENCODING_UTF8));
1304 rc = curl_easy_setopt(rSession.m_pCurl.get(), CURLOPT_PROXYPASSWORD,
1305 utf8PassWord.getStr());
1306 if (rc != CURLE_OK)
1307 {
1308 SAL_WARN("ucb.ucp.webdav.curl",
1309 "CURLOPT_PROXYPASSWORD failed: " << GetErrorString(rc));
1310 throw DAVException(DAVException::DAV_INVALID_ARG);
1311 }
1312 rc = curl_easy_setopt(rSession.m_pCurl.get(), CURLOPT_PROXYAUTH, oAuthProxy->AuthMask);
1313 assert(
1314 rc
1315 == CURLE_OK); // it shouldn't be possible to reduce auth to 0 via the authSystem masks
1316 }
1317
1318 ResponseHeaders headers(rSession.m_pCurl.get());
1319 // always pass a stream for debug logging, buffer the result body
1320 uno::Reference<io::XSequenceOutputStream> const xSeqOutStream(
1321 io::SequenceOutputStream::create(rSession.m_xContext));
1322 uno::Reference<io::XOutputStream> const xTempOutStream(xSeqOutStream);
1323 assert(xTempOutStream.is());
1324
1325 try
1326 {
1327 ProcessRequestImpl(rSession, rURI, rMethod, pRequestHeaderList.get(), &xTempOutStream,
1328 pxInStream ? &data : nullptr, pRequestedHeaders, headers);
1329 if (pxOutStream)
1330 { // only copy to result stream if transfer was successful
1331 (*pxOutStream)->writeBytes(xSeqOutStream->getWrittenBytes());
1332 (*pxOutStream)->closeOutput(); // signal EOF
1333 }
1334 }
1335 catch (DAVException const& rException)
1336 {
1337 // log start of request body if there was any
1338 auto const bytes(xSeqOutStream->getWrittenBytes());
1339 auto const len(::std::min<sal_Int32>(bytes.getLength(), 10000));
1340 SAL_INFO("ucb.ucp.webdav.curl",
1341 "DAVException; (first) " << len << " bytes of data received:");
1342 if (0 < len)
1343 {
1344 OStringBuffer buf(len);
1345 for (sal_Int32 i = 0; i < len; ++i)
1346 {
1347 if (bytes[i] < 0x20) // also if negative
1348 {
1349 static char const hexDigit[16] = { '0', '1', '2', '3', '4', '5', '6', '7',
1350 '8', '9', 'A', 'B', 'C', 'D', 'E', 'F' };
1351 buf.append("\\x");
1352 buf.append(hexDigit[static_cast<sal_uInt8>(bytes[i]) >> 4]);
1353 buf.append(hexDigit[bytes[i] & 0x0F]);
1354 }
1355 else
1356 {
1357 buf.append(static_cast<char>(bytes[i]));
1358 }
1359 }
1360 SAL_INFO("ucb.ucp.webdav.curl", buf.makeStringAndClear());
1361 }
1362
1363 // error handling part 3: special HTTP status codes
1364 // that require unlocking m_Mutex to handle
1365 if (rException.getError() == DAVException::DAV_HTTP_ERROR)
1366 {
1367 auto const statusCode(rException.getStatus());
1368 switch (statusCode)
1369 {
1370 case SC_LOCKED:
1371 {
1372 guard.Release(); // release m_Mutex before accessing LockStore
1373 if (g_Init.LockStore.getLockTokenForURI(rURI.GetURI(), nullptr))
1374 {
1375 throw DAVException(DAVException::DAV_LOCKED_SELF);
1376 }
1377 else // locked by third party
1378 {
1379 throw DAVException(DAVException::DAV_LOCKED);
1380 }
1381 break;
1382 }
1384 case SC_BAD_REQUEST:
1385 {
1386 guard.Release(); // release m_Mutex before accessing LockStore
1387 // Not obvious but apparently these codes may indicate
1388 // the expiration of a lock.
1389 // Initiate a new request *outside* ProcessRequestImpl
1390 // *after* rGuard.unlock() to avoid messing up m_pCurl state.
1391 if (TryRemoveExpiredLockToken(rSession, rURI, pEnv))
1392 {
1393 throw DAVException(DAVException::DAV_LOCK_EXPIRED);
1394 }
1395 break;
1396 }
1397 case SC_UNAUTHORIZED:
1399 {
1400 auto& rnAuthRequests(statusCode == SC_UNAUTHORIZED ? nAuthRequests
1401 : nAuthRequestsProxy);
1402 if (rnAuthRequests == 10)
1403 {
1404 SAL_INFO("ucb.ucp.webdav.curl", "aborting authentication after "
1405 << rnAuthRequests << " attempts");
1406 }
1407 else if (pEnv && pEnv->m_xAuthListener)
1408 {
1409 ::std::optional<OUString> const oRealm(ExtractRealm(
1410 headers, statusCode == SC_UNAUTHORIZED ? "WWW-Authenticate"
1411 : "Proxy-Authenticate"));
1412
1413 ::std::optional<Auth>& roAuth(
1414 statusCode == SC_UNAUTHORIZED ? oAuth : oAuthProxy);
1415 OUString userName(roAuth ? roAuth->UserName : OUString());
1416 OUString passWord(roAuth ? roAuth->PassWord : OUString());
1417 long authAvail(0);
1418 auto const rc = curl_easy_getinfo(rSession.m_pCurl.get(),
1419 statusCode == SC_UNAUTHORIZED
1420 ? CURLINFO_HTTPAUTH_AVAIL
1421 : CURLINFO_PROXYAUTH_AVAIL,
1422 &authAvail);
1423 assert(rc == CURLE_OK);
1424 (void)rc;
1425 // only allow SystemCredentials once - the
1426 // PasswordContainer may have stored it in the
1427 // Config (TrySystemCredentialsFirst or
1428 // AuthenticateUsingSystemCredentials) and then it
1429 // will always force its use no matter how hopeless
1430 bool const isSystemCredSupported((authAvail & authSystem) != 0
1431 && rnAuthRequests == 0);
1432 ++rnAuthRequests;
1433
1434 // Ask user via XInteractionHandler.
1435 // Warning: This likely runs an event loop which may
1436 // end up calling back into this instance, so all
1437 // changes to m_pCurl must be undone now and
1438 // restored after return.
1439 guard.Release();
1440
1441 auto const ret = pEnv->m_xAuthListener->authenticate(
1442 oRealm ? *oRealm : "",
1443 statusCode == SC_UNAUTHORIZED ? rSession.m_URI.GetHost()
1444 : rSession.m_Proxy.aName,
1445 userName, passWord, isSystemCredSupported);
1446
1447 if (ret == 0)
1448 {
1449 // NTLM may either use a password requested
1450 // from the user, or from the system via SSPI
1451 // so i guess it should not be disabled here
1452 // regardless of the state of the system auth
1453 // checkbox, particularly since SSPI is only
1454 // available on WNT.
1455 // Additionally, "Negotiate" has a "legacy"
1456 // mode that is actually just NTLM according to
1457 // https://curl.se/rfc/ntlm.html#ntlmHttpAuthentication
1458 // so there's nothing in authSystem that can be
1459 // disabled here.
1460 roAuth.emplace(userName, passWord,
1461 ((userName.isEmpty() && passWord.isEmpty())
1462 ? (authAvail & authSystem)
1463 : authAvail));
1464 isRetry = true;
1465 // Acquire is only necessary in case of success.
1466 guard.Acquire();
1467 break; // break out of switch
1468 }
1469 // else: throw
1470 }
1471 SAL_INFO("ucb.ucp.webdav.curl", "no auth credentials provided");
1472 throw DAVException(DAVException::DAV_HTTP_NOAUTH,
1473 ConnectionEndPointString(rSession.m_URI.GetHost(),
1474 rSession.m_URI.GetPort()));
1475 break;
1476 }
1477 }
1478 }
1479 else if (rException.getError() == DAVException::DAV_UNSUPPORTED)
1480 {
1481 // tdf#152493 libcurl can't handle "Transfer-Encoding: chunked"
1482 // in HTTP/1.1 100 Continue response.
1483 // workaround: if HTTP/1.1 didn't work, try HTTP/1.0
1484 // (but fallback only once - to prevent infinite loop)
1485 if (isFallbackHTTP10)
1486 {
1487 throw DAVException(DAVException::DAV_HTTP_ERROR);
1488 }
1489 isFallbackHTTP10 = true;
1490 // note: this is not reset - future requests to this URI use it!
1491 auto rc = curl_easy_setopt(rSession.m_pCurl.get(), CURLOPT_HTTP_VERSION,
1492 CURL_HTTP_VERSION_1_0);
1493 if (rc != CURLE_OK)
1494 {
1495 throw DAVException(DAVException::DAV_HTTP_ERROR);
1496 }
1497 SAL_INFO("ucb.ucp.webdav.curl", "attempting fallback to HTTP/1.0");
1498 isRetry = true;
1499 }
1500 if (!isRetry)
1501 {
1502 throw; // everything else: re-throw
1503 }
1504 }
1505 } while (isRetry);
1506
1507 if (oAuth)
1508 {
1509 // assume this worked, leave auth data as stored in m_pCurl
1510 rSession.m_isAuthenticated = true;
1511 }
1512 if (oAuthProxy)
1513 {
1514 // assume this worked, leave auth data as stored in m_pCurl
1515 rSession.m_isAuthenticatedProxy = true;
1516 }
1517}
1518
1519auto CurlSession::OPTIONS(OUString const& rURIReference,
1520
1521 DAVOptions& rOptions, DAVRequestEnvironment const& rEnv) -> void
1522{
1523 SAL_INFO("ucb.ucp.webdav.curl", "OPTIONS: " << rURIReference);
1524
1525 rOptions.init();
1526
1527 CurlUri const uri(CurlProcessor::URIReferenceToURI(*this, rURIReference));
1528
1529 ::std::vector<OUString> const headerNames{ "allow", "dav" };
1531 ::std::pair<::std::vector<OUString> const&, DAVResource&> const headers(headerNames, result);
1532
1533 ::std::vector<CurlOption> const options{
1534 g_NoBody, { CURLOPT_CUSTOMREQUEST, "OPTIONS", "CURLOPT_CUSTOMREQUEST" }
1535 };
1536
1537 CurlProcessor::ProcessRequest(*this, uri, "OPTIONS", options, &rEnv, nullptr, nullptr, nullptr,
1538 &headers);
1539
1540 for (auto const& it : result.properties)
1541 {
1542 OUString value;
1543 it.Value >>= value;
1544 SAL_INFO("ucb.ucp.webdav.curl", "OPTIONS: header: " << it.Name << ": " << value);
1545 if (it.Name.equalsIgnoreAsciiCase("allow"))
1546 {
1547 rOptions.setAllowedMethods(value);
1548 }
1549 else if (it.Name.equalsIgnoreAsciiCase("dav"))
1550 {
1551 // see <http://tools.ietf.org/html/rfc4918#section-10.1>,
1552 // <http://tools.ietf.org/html/rfc4918#section-18>,
1553 // and <http://tools.ietf.org/html/rfc7230#section-3.2>
1554 // we detect the class (1, 2 and 3), other elements (token, URL)
1555 // are not used for now
1556 auto const list(::comphelper::string::convertCommaSeparated(value));
1557 for (OUString const& v : list)
1558 {
1559 if (v == "1")
1560 {
1561 rOptions.setClass1();
1562 }
1563 else if (v == "2")
1564 {
1565 rOptions.setClass2();
1566 }
1567 else if (v == "3")
1568 {
1569 rOptions.setClass3();
1570 }
1571 }
1572 }
1573 }
1574 if (rOptions.isClass2() || rOptions.isClass3())
1575 {
1576 if (g_Init.LockStore.getLockTokenForURI(uri.GetURI(), nullptr))
1577 {
1578 rOptions.setLocked();
1579 }
1580 }
1581}
1582
1583auto CurlProcessor::PropFind(
1584 CurlSession& rSession, CurlUri const& rURI, Depth const nDepth,
1585 ::std::tuple<::std::vector<OUString> const&, ::std::vector<DAVResource>* const,
1586 ::std::vector<ucb::Lock>* const> const* const o_pRequestedProperties,
1587 ::std::vector<DAVResourceInfo>* const o_pResourceInfos, DAVRequestEnvironment const& rEnv)
1588 -> void
1589{
1590 assert((o_pRequestedProperties != nullptr) != (o_pResourceInfos != nullptr));
1591 assert((o_pRequestedProperties == nullptr)
1592 || (::std::get<1>(*o_pRequestedProperties) != nullptr)
1593 != (::std::get<2>(*o_pRequestedProperties) != nullptr));
1594
1595 ::std::unique_ptr<curl_slist, deleter_from_fn<curl_slist, curl_slist_free_all>> pList;
1596 pList.reset(curl_slist_append(pList.release(), "Content-Type: application/xml"));
1597 if (!pList)
1598 {
1599 throw uno::RuntimeException("curl_slist_append failed");
1600 }
1601 OString depth;
1602 switch (nDepth)
1603 {
1604 case DAVZERO:
1605 depth = "Depth: 0";
1606 break;
1607 case DAVONE:
1608 depth = "Depth: 1";
1609 break;
1610 case DAVINFINITY:
1611 depth = "Depth: infinity";
1612 break;
1613 default:
1614 assert(false);
1615 }
1616 pList.reset(curl_slist_append(pList.release(), depth.getStr()));
1617 if (!pList)
1618 {
1619 throw uno::RuntimeException("curl_slist_append failed");
1620 }
1621
1622 uno::Reference<io::XSequenceOutputStream> const xSeqOutStream(
1623 io::SequenceOutputStream::create(rSession.m_xContext));
1624 uno::Reference<io::XOutputStream> const xRequestOutStream(xSeqOutStream);
1625 assert(xRequestOutStream.is());
1626
1627 uno::Reference<xml::sax::XWriter> const xWriter(xml::sax::Writer::create(rSession.m_xContext));
1628 xWriter->setOutputStream(xRequestOutStream);
1629 xWriter->startDocument();
1630 rtl::Reference<::comphelper::AttributeList> const pAttrList(new ::comphelper::AttributeList);
1631 pAttrList->AddAttribute("xmlns", "DAV:");
1632 xWriter->startElement("propfind", pAttrList);
1633 if (o_pResourceInfos)
1634 {
1635 xWriter->startElement("propname", nullptr);
1636 xWriter->endElement("propname");
1637 }
1638 else
1639 {
1640 if (::std::get<0>(*o_pRequestedProperties).empty())
1641 {
1642 xWriter->startElement("allprop", nullptr);
1643 xWriter->endElement("allprop");
1644 }
1645 else
1646 {
1647 xWriter->startElement("prop", nullptr);
1648 for (OUString const& rName : ::std::get<0>(*o_pRequestedProperties))
1649 {
1651 DAVProperties::createSerfPropName(rName, name);
1652 pAttrList->Clear();
1653 pAttrList->AddAttribute("xmlns", OUString::createFromAscii(name.nspace));
1654 xWriter->startElement(OUString::createFromAscii(name.name), pAttrList);
1655 xWriter->endElement(OUString::createFromAscii(name.name));
1656 }
1657 xWriter->endElement("prop");
1658 }
1659 }
1660 xWriter->endElement("propfind");
1661 xWriter->endDocument();
1662
1663 uno::Reference<io::XInputStream> const xRequestInStream(
1664 io::SequenceInputStream::createStreamFromSequence(rSession.m_xContext,
1665 xSeqOutStream->getWrittenBytes()));
1666 assert(xRequestInStream.is());
1667
1668 curl_off_t const len(xSeqOutStream->getWrittenBytes().getLength());
1669
1670 ::std::vector<CurlOption> const options{
1671 { CURLOPT_UPLOAD, 1L, nullptr },
1672 { CURLOPT_CUSTOMREQUEST, "PROPFIND", "CURLOPT_CUSTOMREQUEST" },
1673 // note: Sharepoint cannot handle "Transfer-Encoding: chunked"
1674 { CURLOPT_INFILESIZE_LARGE, len, nullptr, CurlOption::Type::CurlOffT }
1675 };
1676
1677 // stream for response
1678 uno::Reference<io::XInputStream> const xResponseInStream(io::Pipe::create(rSession.m_xContext));
1679 uno::Reference<io::XOutputStream> const xResponseOutStream(xResponseInStream, uno::UNO_QUERY);
1680 assert(xResponseInStream.is());
1681 assert(xResponseOutStream.is());
1682
1683 CurlProcessor::ProcessRequest(rSession, rURI, "PROPFIND", options, &rEnv, ::std::move(pList),
1684 &xResponseOutStream, &xRequestInStream, nullptr);
1685
1686 if (o_pResourceInfos)
1687 {
1688 *o_pResourceInfos = parseWebDAVPropNameResponse(xResponseInStream);
1689 }
1690 else
1691 {
1692 if (::std::get<1>(*o_pRequestedProperties) != nullptr)
1693 {
1694 *::std::get<1>(*o_pRequestedProperties)
1695 = parseWebDAVPropFindResponse(xResponseInStream);
1696 for (DAVResource& it : *::std::get<1>(*o_pRequestedProperties))
1697 {
1698 // caller will give these uris to CurlUri so can't be relative
1699 if (it.uri.startsWith("/"))
1700 {
1701 try
1702 {
1703 it.uri = rSession.m_URI.CloneWithRelativeRefPathAbsolute(it.uri).GetURI();
1704 }
1705 catch (DAVException const&)
1706 {
1707 SAL_INFO("ucb.ucp.webdav.curl",
1708 "PROPFIND: exception parsing uri " << it.uri);
1709 }
1710 }
1711 }
1712 }
1713 else
1714 {
1715 *::std::get<2>(*o_pRequestedProperties) = parseWebDAVLockResponse(xResponseInStream);
1716 }
1717 }
1718}
1719
1720// DAV methods
1721auto CurlSession::PROPFIND(OUString const& rURIReference, Depth const depth,
1722 ::std::vector<OUString> const& rPropertyNames,
1723 ::std::vector<DAVResource>& o_rResources,
1724 DAVRequestEnvironment const& rEnv) -> void
1725{
1726 SAL_INFO("ucb.ucp.webdav.curl", "PROPFIND: " << rURIReference << " " << depth);
1727
1728 CurlUri const uri(CurlProcessor::URIReferenceToURI(*this, rURIReference));
1729
1730 ::std::tuple<::std::vector<OUString> const&, ::std::vector<DAVResource>* const,
1731 ::std::vector<ucb::Lock>* const> const args(rPropertyNames, &o_rResources,
1732 nullptr);
1733 return CurlProcessor::PropFind(*this, uri, depth, &args, nullptr, rEnv);
1734}
1735
1736auto CurlSession::PROPFIND(OUString const& rURIReference, Depth const depth,
1737 ::std::vector<DAVResourceInfo>& o_rResourceInfos,
1738 DAVRequestEnvironment const& rEnv) -> void
1739{
1740 SAL_INFO("ucb.ucp.webdav.curl", "PROPFIND: " << rURIReference << " " << depth);
1741
1742 CurlUri const uri(CurlProcessor::URIReferenceToURI(*this, rURIReference));
1743
1744 return CurlProcessor::PropFind(*this, uri, depth, nullptr, &o_rResourceInfos, rEnv);
1745}
1746
1747auto CurlSession::PROPPATCH(OUString const& rURIReference,
1748 ::std::vector<ProppatchValue> const& rValues,
1749 DAVRequestEnvironment const& rEnv) -> void
1750{
1751 SAL_INFO("ucb.ucp.webdav.curl", "PROPPATCH: " << rURIReference);
1752
1753 CurlUri const uri(CurlProcessor::URIReferenceToURI(*this, rURIReference));
1754
1755 // TODO: either set CURLOPT_INFILESIZE_LARGE or chunked?
1756 ::std::unique_ptr<curl_slist, deleter_from_fn<curl_slist, curl_slist_free_all>> pList;
1757 pList.reset(curl_slist_append(pList.release(), "Content-Type: application/xml"));
1758 if (!pList)
1759 {
1760 throw uno::RuntimeException("curl_slist_append failed");
1761 }
1762
1763 // generate XML document for PROPPATCH
1764 uno::Reference<io::XSequenceOutputStream> const xSeqOutStream(
1765 io::SequenceOutputStream::create(m_xContext));
1766 uno::Reference<io::XOutputStream> const xRequestOutStream(xSeqOutStream);
1767 assert(xRequestOutStream.is());
1768 uno::Reference<xml::sax::XWriter> const xWriter(xml::sax::Writer::create(m_xContext));
1769 xWriter->setOutputStream(xRequestOutStream);
1770 xWriter->startDocument();
1771 rtl::Reference<::comphelper::AttributeList> const pAttrList(new ::comphelper::AttributeList);
1772 pAttrList->AddAttribute("xmlns", "DAV:");
1773 xWriter->startElement("propertyupdate", pAttrList);
1774 for (ProppatchValue const& rPropValue : rValues)
1775 {
1776 assert(rPropValue.operation == PROPSET || rPropValue.operation == PROPREMOVE);
1777 OUString const operation((rPropValue.operation == PROPSET) ? OUString("set")
1778 : OUString("remove"));
1779 xWriter->startElement(operation, nullptr);
1780 xWriter->startElement("prop", nullptr);
1782 DAVProperties::createSerfPropName(rPropValue.name, name);
1783 pAttrList->Clear();
1784 pAttrList->AddAttribute("xmlns", OUString::createFromAscii(name.nspace));
1785 xWriter->startElement(OUString::createFromAscii(name.name), pAttrList);
1786 if (rPropValue.operation == PROPSET)
1787 {
1788 if (DAVProperties::isUCBDeadProperty(name))
1789 {
1790 ::std::optional<::std::pair<OUString, OUString>> const oProp(
1791 UCBDeadPropertyValue::toXML(rPropValue.value));
1792 if (oProp)
1793 {
1794 xWriter->startElement("ucbprop", nullptr);
1795 xWriter->startElement("type", nullptr);
1796 xWriter->characters(oProp->first);
1797 xWriter->endElement("type");
1798 xWriter->startElement("value", nullptr);
1799 xWriter->characters(oProp->second);
1800 xWriter->endElement("value");
1801 xWriter->endElement("ucbprop");
1802 }
1803 }
1804 else
1805 {
1806 OUString value;
1807 rPropValue.value >>= value;
1808 xWriter->characters(value);
1809 }
1810 }
1811 xWriter->endElement(OUString::createFromAscii(name.name));
1812 xWriter->endElement("prop");
1813 xWriter->endElement(operation);
1814 }
1815 xWriter->endElement("propertyupdate");
1816 xWriter->endDocument();
1817
1818 uno::Reference<io::XInputStream> const xRequestInStream(
1819 io::SequenceInputStream::createStreamFromSequence(m_xContext,
1820 xSeqOutStream->getWrittenBytes()));
1821 assert(xRequestInStream.is());
1822
1823 curl_off_t const len(xSeqOutStream->getWrittenBytes().getLength());
1824
1825 ::std::vector<CurlOption> const options{
1826 { CURLOPT_UPLOAD, 1L, nullptr },
1827 { CURLOPT_CUSTOMREQUEST, "PROPPATCH", "CURLOPT_CUSTOMREQUEST" },
1828 // note: Sharepoint cannot handle "Transfer-Encoding: chunked"
1829 { CURLOPT_INFILESIZE_LARGE, len, nullptr, CurlOption::Type::CurlOffT }
1830 };
1831
1832 CurlProcessor::ProcessRequest(*this, uri, "PROPPATCH", options, &rEnv, ::std::move(pList),
1833 nullptr, &xRequestInStream, nullptr);
1834}
1835
1836auto CurlSession::HEAD(OUString const& rURIReference, ::std::vector<OUString> const& rHeaderNames,
1837 DAVResource& io_rResource, DAVRequestEnvironment const& rEnv) -> void
1838{
1839 SAL_INFO("ucb.ucp.webdav.curl", "HEAD: " << rURIReference);
1840
1841 CurlUri const uri(CurlProcessor::URIReferenceToURI(*this, rURIReference));
1842
1843 ::std::vector<CurlOption> const options{ g_NoBody };
1844
1845 ::std::pair<::std::vector<OUString> const&, DAVResource&> const headers(rHeaderNames,
1846 io_rResource);
1847
1848 CurlProcessor::ProcessRequest(*this, uri, "HEAD", options, &rEnv, nullptr, nullptr, nullptr,
1849 &headers);
1850}
1851
1852auto CurlSession::GET(OUString const& rURIReference, DAVRequestEnvironment const& rEnv)
1853 -> uno::Reference<io::XInputStream>
1854{
1855 SAL_INFO("ucb.ucp.webdav.curl", "GET: " << rURIReference);
1856
1857 CurlUri const uri(CurlProcessor::URIReferenceToURI(*this, rURIReference));
1858
1859 // could use either com.sun.star.io.Pipe or com.sun.star.io.SequenceInputStream?
1860 // Pipe can just write into its XOuputStream, which is simpler.
1861 // Both resize exponentially, so performance should be fine.
1862 // However, Pipe doesn't implement XSeekable, which is required by filters.
1863
1864 uno::Reference<io::XSequenceOutputStream> const xSeqOutStream(
1865 io::SequenceOutputStream::create(m_xContext));
1866 uno::Reference<io::XOutputStream> const xResponseOutStream(xSeqOutStream);
1867 assert(xResponseOutStream.is());
1868
1869 ::std::vector<CurlOption> const options{ { CURLOPT_HTTPGET, 1L, nullptr } };
1870
1871 CurlProcessor::ProcessRequest(*this, uri, "GET", options, &rEnv, nullptr, &xResponseOutStream,
1872 nullptr, nullptr);
1873
1874 uno::Reference<io::XInputStream> const xResponseInStream(
1875 io::SequenceInputStream::createStreamFromSequence(m_xContext,
1876 xSeqOutStream->getWrittenBytes()));
1877 assert(xResponseInStream.is());
1878
1879 return xResponseInStream;
1880}
1881
1882auto CurlSession::GET(OUString const& rURIReference, uno::Reference<io::XOutputStream>& rxOutStream,
1883 DAVRequestEnvironment const& rEnv) -> void
1884{
1885 SAL_INFO("ucb.ucp.webdav.curl", "GET: " << rURIReference);
1886
1887 CurlUri const uri(CurlProcessor::URIReferenceToURI(*this, rURIReference));
1888
1889 ::std::vector<CurlOption> const options{ { CURLOPT_HTTPGET, 1L, nullptr } };
1890
1891 CurlProcessor::ProcessRequest(*this, uri, "GET", options, &rEnv, nullptr, &rxOutStream, nullptr,
1892 nullptr);
1893}
1894
1895auto CurlSession::GET(OUString const& rURIReference, ::std::vector<OUString> const& rHeaderNames,
1896 DAVResource& io_rResource, DAVRequestEnvironment const& rEnv)
1897 -> uno::Reference<io::XInputStream>
1898{
1899 SAL_INFO("ucb.ucp.webdav.curl", "GET: " << rURIReference);
1900
1901 CurlUri const uri(CurlProcessor::URIReferenceToURI(*this, rURIReference));
1902
1903 ::std::vector<CurlOption> const options{ { CURLOPT_HTTPGET, 1L, nullptr } };
1904
1905 uno::Reference<io::XSequenceOutputStream> const xSeqOutStream(
1906 io::SequenceOutputStream::create(m_xContext));
1907 uno::Reference<io::XOutputStream> const xResponseOutStream(xSeqOutStream);
1908 assert(xResponseOutStream.is());
1909
1910 ::std::pair<::std::vector<OUString> const&, DAVResource&> const headers(rHeaderNames,
1911 io_rResource);
1912
1913 CurlProcessor::ProcessRequest(*this, uri, "GET", options, &rEnv, nullptr, &xResponseOutStream,
1914 nullptr, &headers);
1915
1916 uno::Reference<io::XInputStream> const xResponseInStream(
1917 io::SequenceInputStream::createStreamFromSequence(m_xContext,
1918 xSeqOutStream->getWrittenBytes()));
1919 assert(xResponseInStream.is());
1920
1921 return xResponseInStream;
1922}
1923
1924auto CurlSession::GET(OUString const& rURIReference, uno::Reference<io::XOutputStream>& rxOutStream,
1925 ::std::vector<OUString> const& rHeaderNames, DAVResource& io_rResource,
1926 DAVRequestEnvironment const& rEnv) -> void
1927{
1928 SAL_INFO("ucb.ucp.webdav.curl", "GET: " << rURIReference);
1929
1930 CurlUri const uri(CurlProcessor::URIReferenceToURI(*this, rURIReference));
1931
1932 ::std::vector<CurlOption> const options{ { CURLOPT_HTTPGET, 1L, nullptr } };
1933
1934 ::std::pair<::std::vector<OUString> const&, DAVResource&> const headers(rHeaderNames,
1935 io_rResource);
1936
1937 CurlProcessor::ProcessRequest(*this, uri, "GET", options, &rEnv, nullptr, &rxOutStream, nullptr,
1938 &headers);
1939}
1940
1941auto CurlSession::PUT(OUString const& rURIReference,
1942 uno::Reference<io::XInputStream> const& rxInStream,
1943 DAVRequestEnvironment const& rEnv) -> void
1944{
1945 SAL_INFO("ucb.ucp.webdav.curl", "PUT: " << rURIReference);
1946
1947 CurlUri const uri(CurlProcessor::URIReferenceToURI(*this, rURIReference));
1948
1949 // NextCloud silently fails with chunked encoding
1950 uno::Reference<io::XSeekable> const xSeekable(rxInStream, uno::UNO_QUERY);
1951 if (!xSeekable.is())
1952 {
1953 throw uno::RuntimeException("TODO: not seekable");
1954 }
1955 curl_off_t const len(xSeekable->getLength() - xSeekable->getPosition());
1956
1957 ::std::unique_ptr<curl_slist, deleter_from_fn<curl_slist, curl_slist_free_all>> pList;
1958 OUString const* const pToken(g_Init.LockStore.getLockTokenForURI(uri.GetURI(), nullptr));
1959 if (pToken)
1960 {
1961 OString const utf8If("If: "
1962 // disabled as Sharepoint 2013 workaround, it accepts only
1963 // "No-Tag-List", see fed2984281a85a5a2f308841ec810f218c75f2ab
1964#if 0
1965 "<" + OUStringToOString(rURIReference, RTL_TEXTENCODING_ASCII_US)
1966 + "> "
1967#endif
1968 "(<"
1969 + OUStringToOString(*pToken, RTL_TEXTENCODING_ASCII_US) + ">)");
1970 pList.reset(curl_slist_append(pList.release(), utf8If.getStr()));
1971 if (!pList)
1972 {
1973 throw uno::RuntimeException("curl_slist_append failed");
1974 }
1975 }
1976
1977 // lock m_Mutex after accessing global LockStore to avoid deadlock
1978
1979 // note: Nextcloud 20 cannot handle "Transfer-Encoding: chunked"
1980 ::std::vector<CurlOption> const options{
1981 { CURLOPT_UPLOAD, 1L, nullptr }, // libcurl won't upload without setting this
1982 { CURLOPT_INFILESIZE_LARGE, len, nullptr, CurlOption::Type::CurlOffT }
1983 };
1984
1985 CurlProcessor::ProcessRequest(*this, uri, "PUT", options, &rEnv, ::std::move(pList), nullptr,
1986 &rxInStream, nullptr);
1987}
1988
1989auto CurlSession::POST(OUString const& rURIReference, OUString const& rContentType,
1990 OUString const& rReferer, uno::Reference<io::XInputStream> const& rxInStream,
1991 DAVRequestEnvironment const& rEnv) -> uno::Reference<io::XInputStream>
1992{
1993 SAL_INFO("ucb.ucp.webdav.curl", "POST: " << rURIReference);
1994
1995 CurlUri const uri(CurlProcessor::URIReferenceToURI(*this, rURIReference));
1996
1997 // TODO: either set CURLOPT_POSTFIELDSIZE_LARGE or chunked?
1998 ::std::unique_ptr<curl_slist, deleter_from_fn<curl_slist, curl_slist_free_all>> pList(
1999 curl_slist_append(nullptr, "Transfer-Encoding: chunked"));
2000 if (!pList)
2001 {
2002 throw uno::RuntimeException("curl_slist_append failed");
2003 }
2004 OString const utf8ContentType("Content-Type: "
2005 + OUStringToOString(rContentType, RTL_TEXTENCODING_ASCII_US));
2006 pList.reset(curl_slist_append(pList.release(), utf8ContentType.getStr()));
2007 if (!pList)
2008 {
2009 throw uno::RuntimeException("curl_slist_append failed");
2010 }
2011 OString const utf8Referer("Referer: " + OUStringToOString(rReferer, RTL_TEXTENCODING_ASCII_US));
2012 pList.reset(curl_slist_append(pList.release(), utf8Referer.getStr()));
2013 if (!pList)
2014 {
2015 throw uno::RuntimeException("curl_slist_append failed");
2016 }
2017
2018 ::std::vector<CurlOption> const options{ { CURLOPT_POST, 1L, nullptr } };
2019
2020 uno::Reference<io::XSequenceOutputStream> const xSeqOutStream(
2021 io::SequenceOutputStream::create(m_xContext));
2022 uno::Reference<io::XOutputStream> const xResponseOutStream(xSeqOutStream);
2023 assert(xResponseOutStream.is());
2024
2025 CurlProcessor::ProcessRequest(*this, uri, "POST", options, &rEnv, ::std::move(pList),
2026 &xResponseOutStream, &rxInStream, nullptr);
2027
2028 uno::Reference<io::XInputStream> const xResponseInStream(
2029 io::SequenceInputStream::createStreamFromSequence(m_xContext,
2030 xSeqOutStream->getWrittenBytes()));
2031 assert(xResponseInStream.is());
2032
2033 return xResponseInStream;
2034}
2035
2036auto CurlSession::POST(OUString const& rURIReference, OUString const& rContentType,
2037 OUString const& rReferer, uno::Reference<io::XInputStream> const& rxInStream,
2038 uno::Reference<io::XOutputStream>& rxOutStream,
2039 DAVRequestEnvironment const& rEnv) -> void
2040{
2041 SAL_INFO("ucb.ucp.webdav.curl", "POST: " << rURIReference);
2042
2043 CurlUri const uri(CurlProcessor::URIReferenceToURI(*this, rURIReference));
2044
2045 // TODO: either set CURLOPT_POSTFIELDSIZE_LARGE or chunked?
2046 ::std::unique_ptr<curl_slist, deleter_from_fn<curl_slist, curl_slist_free_all>> pList(
2047 curl_slist_append(nullptr, "Transfer-Encoding: chunked"));
2048 if (!pList)
2049 {
2050 throw uno::RuntimeException("curl_slist_append failed");
2051 }
2052 OString const utf8ContentType("Content-Type: "
2053 + OUStringToOString(rContentType, RTL_TEXTENCODING_ASCII_US));
2054 pList.reset(curl_slist_append(pList.release(), utf8ContentType.getStr()));
2055 if (!pList)
2056 {
2057 throw uno::RuntimeException("curl_slist_append failed");
2058 }
2059 OString const utf8Referer("Referer: " + OUStringToOString(rReferer, RTL_TEXTENCODING_ASCII_US));
2060 pList.reset(curl_slist_append(pList.release(), utf8Referer.getStr()));
2061 if (!pList)
2062 {
2063 throw uno::RuntimeException("curl_slist_append failed");
2064 }
2065
2066 ::std::vector<CurlOption> const options{ { CURLOPT_POST, 1L, nullptr } };
2067
2068 CurlProcessor::ProcessRequest(*this, uri, "POST", options, &rEnv, ::std::move(pList),
2069 &rxOutStream, &rxInStream, nullptr);
2070}
2071
2072auto CurlSession::MKCOL(OUString const& rURIReference, DAVRequestEnvironment const& rEnv) -> void
2073{
2074 SAL_INFO("ucb.ucp.webdav.curl", "MKCOL: " << rURIReference);
2075
2076 CurlUri const uri(CurlProcessor::URIReferenceToURI(*this, rURIReference));
2077
2078 ::std::vector<CurlOption> const options{
2079 g_NoBody, { CURLOPT_CUSTOMREQUEST, "MKCOL", "CURLOPT_CUSTOMREQUEST" }
2080 };
2081
2082 CurlProcessor::ProcessRequest(*this, uri, "MKCOL", options, &rEnv, nullptr, nullptr, nullptr,
2083 nullptr);
2084}
2085
2086auto CurlProcessor::MoveOrCopy(CurlSession& rSession, std::u16string_view rSourceURIReference,
2087 ::std::u16string_view const rDestinationURI,
2088 DAVRequestEnvironment const& rEnv, bool const isOverwrite,
2089 char const* const pMethod) -> void
2090{
2091 CurlUri const uriSource(CurlProcessor::URIReferenceToURI(rSession, rSourceURIReference));
2092
2093 OString const utf8Destination("Destination: "
2094 + OUStringToOString(rDestinationURI, RTL_TEXTENCODING_ASCII_US));
2095 ::std::unique_ptr<curl_slist, deleter_from_fn<curl_slist, curl_slist_free_all>> pList(
2096 curl_slist_append(nullptr, utf8Destination.getStr()));
2097 if (!pList)
2098 {
2099 throw uno::RuntimeException("curl_slist_append failed");
2100 }
2101 OString const utf8Overwrite(OString::Concat("Overwrite: ") + (isOverwrite ? "T" : "F"));
2102 pList.reset(curl_slist_append(pList.release(), utf8Overwrite.getStr()));
2103 if (!pList)
2104 {
2105 throw uno::RuntimeException("curl_slist_append failed");
2106 }
2107
2108 ::std::vector<CurlOption> const options{
2109 g_NoBody, { CURLOPT_CUSTOMREQUEST, pMethod, "CURLOPT_CUSTOMREQUEST" }
2110 };
2111
2112 CurlProcessor::ProcessRequest(rSession, uriSource, OUString::createFromAscii(pMethod), options,
2113 &rEnv, ::std::move(pList), nullptr, nullptr, nullptr);
2114}
2115
2116auto CurlSession::COPY(OUString const& rSourceURIReference, OUString const& rDestinationURI,
2117 DAVRequestEnvironment const& rEnv, bool const isOverwrite) -> void
2118{
2119 SAL_INFO("ucb.ucp.webdav.curl", "COPY: " << rSourceURIReference);
2120
2121 return CurlProcessor::MoveOrCopy(*this, rSourceURIReference, rDestinationURI, rEnv, isOverwrite,
2122 "COPY");
2123}
2124
2125auto CurlSession::MOVE(OUString const& rSourceURIReference, OUString const& rDestinationURI,
2126 DAVRequestEnvironment const& rEnv, bool const isOverwrite) -> void
2127{
2128 SAL_INFO("ucb.ucp.webdav.curl", "MOVE: " << rSourceURIReference);
2129
2130 return CurlProcessor::MoveOrCopy(*this, rSourceURIReference, rDestinationURI, rEnv, isOverwrite,
2131 "MOVE");
2132}
2133
2134auto CurlSession::DESTROY(OUString const& rURIReference, DAVRequestEnvironment const& rEnv) -> void
2135{
2136 SAL_INFO("ucb.ucp.webdav.curl", "DESTROY: " << rURIReference);
2137
2138 CurlUri const uri(CurlProcessor::URIReferenceToURI(*this, rURIReference));
2139
2140 ::std::vector<CurlOption> const options{
2141 g_NoBody, { CURLOPT_CUSTOMREQUEST, "DELETE", "CURLOPT_CUSTOMREQUEST" }
2142 };
2143
2144 CurlProcessor::ProcessRequest(*this, uri, "DESTROY", options, &rEnv, nullptr, nullptr, nullptr,
2145 nullptr);
2146}
2147
2148auto CurlProcessor::Lock(
2149 CurlSession& rSession, CurlUri const& rURI, DAVRequestEnvironment const* const pEnv,
2150 ::std::unique_ptr<curl_slist, deleter_from_fn<curl_slist, curl_slist_free_all>>
2151 pRequestHeaderList,
2152 uno::Reference<io::XInputStream> const* const pxRequestInStream)
2153 -> ::std::vector<::std::pair<ucb::Lock, sal_Int32>>
2154{
2155 curl_off_t len(0);
2156 if (pxRequestInStream)
2157 {
2158 uno::Reference<io::XSeekable> const xSeekable(*pxRequestInStream, uno::UNO_QUERY);
2159 assert(xSeekable.is());
2160 len = xSeekable->getLength();
2161 }
2162
2163 ::std::vector<CurlOption> const options{
2164 { CURLOPT_UPLOAD, 1L, nullptr },
2165 { CURLOPT_CUSTOMREQUEST, "LOCK", "CURLOPT_CUSTOMREQUEST" },
2166 // note: Sharepoint cannot handle "Transfer-Encoding: chunked"
2167 { CURLOPT_INFILESIZE_LARGE, len, nullptr, CurlOption::Type::CurlOffT }
2168 };
2169
2170 // stream for response
2171 uno::Reference<io::XInputStream> const xResponseInStream(io::Pipe::create(rSession.m_xContext));
2172 uno::Reference<io::XOutputStream> const xResponseOutStream(xResponseInStream, uno::UNO_QUERY);
2173 assert(xResponseInStream.is());
2174 assert(xResponseOutStream.is());
2175
2176 TimeValue startTime;
2177 osl_getSystemTime(&startTime);
2178
2179 CurlProcessor::ProcessRequest(rSession, rURI, "LOCK", options, pEnv,
2180 ::std::move(pRequestHeaderList), &xResponseOutStream,
2181 pxRequestInStream, nullptr);
2182
2183 ::std::vector<ucb::Lock> const acquiredLocks(parseWebDAVLockResponse(xResponseInStream));
2184 SAL_WARN_IF(acquiredLocks.empty(), "ucb.ucp.webdav.curl",
2185 "could not get LOCK for " << rURI.GetURI());
2186
2187 TimeValue endTime;
2188 osl_getSystemTime(&endTime);
2189 auto const elapsedSeconds(endTime.Seconds - startTime.Seconds);
2190
2191 // determine expiration time (seconds from endTime) for each acquired lock
2192 ::std::vector<::std::pair<ucb::Lock, sal_Int32>> ret;
2193 ret.reserve(acquiredLocks.size());
2194 for (auto const& rLock : acquiredLocks)
2195 {
2196 sal_Int32 lockExpirationTimeSeconds;
2197 if (rLock.Timeout == -1)
2198 {
2199 lockExpirationTimeSeconds = -1;
2200 }
2201 else if (rLock.Timeout <= elapsedSeconds)
2202 {
2203 SAL_WARN("ucb.ucp.webdav.curl",
2204 "LOCK timeout already expired when receiving LOCK response for "
2205 << rURI.GetURI());
2206 lockExpirationTimeSeconds = 0;
2207 }
2208 else
2209 {
2210 lockExpirationTimeSeconds = startTime.Seconds + rLock.Timeout;
2211 }
2212 ret.emplace_back(rLock, lockExpirationTimeSeconds);
2213 }
2214
2215 return ret;
2216}
2217
2218auto CurlSession::LOCK(OUString const& rURIReference, ucb::Lock /*const*/& rLock,
2219 DAVRequestEnvironment const& rEnv) -> void
2220{
2221 SAL_INFO("ucb.ucp.webdav.curl", "LOCK: " << rURIReference);
2222
2223 CurlUri const uri(CurlProcessor::URIReferenceToURI(*this, rURIReference));
2224
2225 if (g_Init.LockStore.getLockTokenForURI(uri.GetURI(), &rLock))
2226 {
2227 // already have a lock that covers the requirement
2228 // TODO: maybe use DAV:lockdiscovery to ensure it's valid
2229 return;
2230 }
2231
2232 // note: no m_Mutex lock needed here, only in CurlProcessor::Lock()
2233
2234 // generate XML document for acquiring new LOCK
2235 uno::Reference<io::XSequenceOutputStream> const xSeqOutStream(
2236 io::SequenceOutputStream::create(m_xContext));
2237 uno::Reference<io::XOutputStream> const xRequestOutStream(xSeqOutStream);
2238 assert(xRequestOutStream.is());
2239 uno::Reference<xml::sax::XWriter> const xWriter(xml::sax::Writer::create(m_xContext));
2240 xWriter->setOutputStream(xRequestOutStream);
2241 xWriter->startDocument();
2242 rtl::Reference<::comphelper::AttributeList> const pAttrList(new ::comphelper::AttributeList);
2243 pAttrList->AddAttribute("xmlns", "DAV:");
2244 xWriter->startElement("lockinfo", pAttrList);
2245 xWriter->startElement("lockscope", nullptr);
2246 switch (rLock.Scope)
2247 {
2248 case ucb::LockScope_EXCLUSIVE:
2249 xWriter->startElement("exclusive", nullptr);
2250 xWriter->endElement("exclusive");
2251 break;
2252 case ucb::LockScope_SHARED:
2253 xWriter->startElement("shared", nullptr);
2254 xWriter->endElement("shared");
2255 break;
2256 default:
2257 assert(false);
2258 }
2259 xWriter->endElement("lockscope");
2260 xWriter->startElement("locktype", nullptr);
2261 xWriter->startElement("write", nullptr);
2262 xWriter->endElement("write");
2263 xWriter->endElement("locktype");
2264 OUString owner;
2265 if ((rLock.Owner >>= owner) && !owner.isEmpty())
2266 {
2267 xWriter->startElement("owner", nullptr);
2268 xWriter->characters(owner);
2269 xWriter->endElement("owner");
2270 }
2271 xWriter->endElement("lockinfo");
2272 xWriter->endDocument();
2273
2274 uno::Reference<io::XInputStream> const xRequestInStream(
2275 io::SequenceInputStream::createStreamFromSequence(m_xContext,
2276 xSeqOutStream->getWrittenBytes()));
2277 assert(xRequestInStream.is());
2278
2279 ::std::unique_ptr<curl_slist, deleter_from_fn<curl_slist, curl_slist_free_all>> pList;
2280 pList.reset(curl_slist_append(pList.release(), "Content-Type: application/xml"));
2281 if (!pList)
2282 {
2283 throw uno::RuntimeException("curl_slist_append failed");
2284 }
2285 OString depth;
2286 switch (rLock.Depth)
2287 {
2288 case ucb::LockDepth_ZERO:
2289 depth = "Depth: 0";
2290 break;
2291 case ucb::LockDepth_ONE:
2292 depth = "Depth: 1";
2293 break;
2294 case ucb::LockDepth_INFINITY:
2295 depth = "Depth: infinity";
2296 break;
2297 default:
2298 assert(false);
2299 }
2300 pList.reset(curl_slist_append(pList.release(), depth.getStr()));
2301 if (!pList)
2302 {
2303 throw uno::RuntimeException("curl_slist_append failed");
2304 }
2305 OString timeout;
2306 switch (rLock.Timeout)
2307 {
2308 case -1:
2309 timeout = "Timeout: Infinite";
2310 break;
2311 case 0:
2312 timeout = "Timeout: Second-180";
2313 break;
2314 default:
2315 timeout = "Timeout: Second-" + OString::number(rLock.Timeout);
2316 assert(0 < rLock.Timeout);
2317 break;
2318 }
2319 pList.reset(curl_slist_append(pList.release(), timeout.getStr()));
2320 if (!pList)
2321 {
2322 throw uno::RuntimeException("curl_slist_append failed");
2323 }
2324
2325 auto const acquiredLocks
2326 = CurlProcessor::Lock(*this, uri, &rEnv, ::std::move(pList), &xRequestInStream);
2327
2328 for (auto const& rAcquiredLock : acquiredLocks)
2329 {
2330 g_Init.LockStore.addLock(uri.GetURI(), rAcquiredLock.first,
2331 rAcquiredLock.first.LockTokens[0], this, rAcquiredLock.second);
2332 SAL_INFO("ucb.ucp.webdav.curl", "created LOCK for " << rURIReference);
2333 }
2334}
2335
2336auto CurlProcessor::Unlock(CurlSession& rSession, CurlUri const& rURI,
2337 DAVRequestEnvironment const* const pEnv) -> void
2338{
2339 OUString const* const pToken(g_Init.LockStore.getLockTokenForURI(rURI.GetURI(), nullptr));
2340 if (!pToken)
2341 {
2342 SAL_WARN("ucb.ucp.webdav.curl", "attempt to unlock but not locked");
2343 throw DAVException(DAVException::DAV_NOT_LOCKED);
2344 }
2345 OString const utf8LockToken("Lock-Token: <"
2346 + OUStringToOString(*pToken, RTL_TEXTENCODING_ASCII_US) + ">");
2347 ::std::unique_ptr<curl_slist, deleter_from_fn<curl_slist, curl_slist_free_all>> pList(
2348 curl_slist_append(nullptr, utf8LockToken.getStr()));
2349 if (!pList)
2350 {
2351 throw uno::RuntimeException("curl_slist_append failed");
2352 }
2353
2354 ::std::vector<CurlOption> const options{ { CURLOPT_CUSTOMREQUEST, "UNLOCK",
2355 "CURLOPT_CUSTOMREQUEST" } };
2356
2357 CurlProcessor::ProcessRequest(rSession, rURI, "UNLOCK", options, pEnv, ::std::move(pList),
2358 nullptr, nullptr, nullptr);
2359}
2360
2361auto CurlSession::UNLOCK(OUString const& rURIReference, DAVRequestEnvironment const& rEnv) -> void
2362{
2363 SAL_INFO("ucb.ucp.webdav.curl", "UNLOCK: " << rURIReference);
2364
2365 // note: no m_Mutex lock needed here, only in CurlProcessor::Unlock()
2366
2367 CurlUri const uri(CurlProcessor::URIReferenceToURI(*this, rURIReference));
2368
2369 CurlProcessor::Unlock(*this, uri, &rEnv);
2370
2371 g_Init.LockStore.removeLock(uri.GetURI());
2372}
2373
2374auto CurlSession::NonInteractive_LOCK(OUString const& rURI, ::std::u16string_view const rLockToken,
2375 sal_Int32& o_rLastChanceToSendRefreshRequest,
2376 bool& o_rIsAuthFailed) -> bool
2377{
2378 SAL_INFO("ucb.ucp.webdav.curl", "NonInteractive_LOCK: " << rURI);
2379
2380 // note: no m_Mutex lock needed here, only in CurlProcessor::Lock()
2381
2382 try
2383 {
2384 CurlUri const uri(rURI);
2385 ::std::unique_ptr<curl_slist, deleter_from_fn<curl_slist, curl_slist_free_all>> pList(
2386 curl_slist_append(nullptr, "Timeout: Second-180"));
2387
2388 assert(!rLockToken.empty()); // LockStore is the caller
2389 OString const utf8If("If: (<" + OUStringToOString(rLockToken, RTL_TEXTENCODING_ASCII_US)
2390 + ">)");
2391 pList.reset(curl_slist_append(pList.release(), utf8If.getStr()));
2392 if (!pList)
2393 {
2394 throw uno::RuntimeException("curl_slist_append failed");
2395 }
2396
2397 auto const acquiredLocks
2398 = CurlProcessor::Lock(*this, uri, nullptr, ::std::move(pList), nullptr);
2399
2400 SAL_WARN_IF(1 < acquiredLocks.size(), "ucb.ucp.webdav.curl",
2401 "multiple locks acquired on refresh for " << rURI);
2402 if (!acquiredLocks.empty())
2403 {
2404 o_rLastChanceToSendRefreshRequest = acquiredLocks.begin()->second;
2405 }
2406 SAL_INFO("ucb.ucp.webdav.curl", "NonInteractive_LOCK succeeded on " << rURI);
2407 return true;
2408 }
2409 catch (DAVException const& rException)
2410 {
2411 SAL_INFO("ucb.ucp.webdav.curl", "NonInteractive_LOCK failed on " << rURI);
2412 switch (rException.getError())
2413 {
2414 case DAVException::DAV_HTTP_AUTH:
2415 case DAVException::DAV_HTTP_NOAUTH:
2416 o_rIsAuthFailed = true;
2417 break;
2418 default:
2419 break;
2420 }
2421 return false;
2422 }
2423 catch (...)
2424 {
2425 SAL_INFO("ucb.ucp.webdav.curl", "NonInteractive_LOCK failed on " << rURI);
2426 return false;
2427 }
2428}
2429
2430auto CurlSession::NonInteractive_UNLOCK(OUString const& rURI) -> void
2431{
2432 SAL_INFO("ucb.ucp.webdav.curl", "NonInteractive_UNLOCK: " << rURI);
2433
2434 // note: no m_Mutex lock needed here, only in CurlProcessor::Unlock()
2435
2436 try
2437 {
2438 CurlUri const uri(rURI);
2439
2440 CurlProcessor::Unlock(*this, uri, nullptr);
2441
2442 // the only caller is the dtor of the LockStore, don't call remove!
2443 SAL_INFO("ucb.ucp.webdav.curl", "NonInteractive_UNLOCK succeeded on " << rURI);
2444 }
2445 catch (...)
2446 {
2447 SAL_INFO("ucb.ucp.webdav.curl", "NonInteractive_UNLOCK failed on " << rURI);
2448 }
2449}
2450
2451} // namespace http_dav_ucp
2452
2453/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
Tag
Reference< XComponentContext > m_xContext
implementation of libcurl HTTP/DAV back-end
Definition: CurlSession.hxx:24
int m_nReadTimeout
read timeout in milliseconds (connection timeout is stored in m_pCurl)
Definition: CurlSession.hxx:40
virtual auto abort() -> void override
::std::unique_ptr< CURLM, deleter_from_fn< CURLM, curl_multi_cleanup > > m_pCurlMulti
libcurl multi handle
Definition: CurlSession.hxx:45
virtual ~CurlSession() override
css::uno::Sequence< css::beans::NamedValue > const m_Flags
flags may be passed to constructor, e.g. "KeepAlive"
Definition: CurlSession.hxx:30
char m_ErrorBuffer[CURL_ERROR_SIZE]
buffer for libcurl detailed error messages
Definition: CurlSession.hxx:33
virtual auto UsesProxy() -> bool override
CurlSession(css::uno::Reference< css::uno::XComponentContext > xContext, ::rtl::Reference< DAVSessionFactory > const &rpFactory, OUString const &rURI, css::uno::Sequence< css::beans::NamedValue > const &rFlags, ::ucbhelper::InternetProxyDecider const &rProxyDecider)
::std::unique_ptr< CURL, deleter_from_fn< CURL, curl_easy_cleanup > > m_pCurl
libcurl easy handle
Definition: CurlSession.hxx:47
virtual auto CanUse(OUString const &rURI, css::uno::Sequence< css::beans::NamedValue > const &rFlags) -> bool override
::ucbhelper::InternetProxyServer const m_Proxy
proxy is used if aName is non-empty
Definition: CurlSession.hxx:35
sal_uInt16 GetPort() const
Definition: CurlUri.hxx:71
OUString const & GetHost() const
Definition: CurlUri.hxx:70
OUString const & GetPassword() const
Definition: CurlUri.hxx:69
OUString const & GetUser() const
Definition: CurlUri.hxx:68
OUString const & GetURI() const
Definition: CurlUri.hxx:66
OUString const & GetScheme() const
Definition: CurlUri.hxx:67
const ExceptionCode & getError() const
sal_uInt16 getStatus() const
void Init()
Any value
float v
float u
const char * name
#define SAL_WARN_IF(condition, area, stream)
#define SAL_WARN(area, stream)
#define SAL_INFO(area, stream)
#define SAL_N_ELEMENTS(arr)
size
Type
int i
line
static auto ExtractRequestedHeaders(ResponseHeaders const &rHeaders, ::std::pair<::std::vector< OUString > const &, DAVResource & > const *const pRequestedHeaders) -> void
const sal_uInt16 SC_TEMPORARY_REDIRECT
const sal_uInt16 SC_SEE_OTHER
const sal_uInt16 SC_LOCKED
static size_t write_callback(char *const ptr, size_t const size, size_t const nmemb, void *const userdata)
static auto TryRemoveExpiredLockToken(CurlSession &rSession, CurlUri const &rURI, DAVRequestEnvironment const *const pEnv) -> bool
const sal_uInt16 SC_MOVED_TEMPORARILY
std::vector< ucb::Lock > parseWebDAVLockResponse(const uno::Reference< io::XInputStream > &xInputStream)
std::vector< DAVResourceInfo > parseWebDAVPropNameResponse(const uno::Reference< io::XInputStream > &xInputStream)
static size_t read_callback(char *const buffer, size_t const size, size_t const nitems, void *const userdata)
const sal_uInt16 SC_PRECONDITION_FAILED
static auto ProcessHeaders(::std::vector< OString > const &rHeaders) -> ::std::map< OUString, OUString >
const sal_uInt16 SC_NONE
const sal_uInt16 SC_PROXY_AUTHENTICATION_REQUIRED
const sal_uInt16 SC_MOVED_PERMANENTLY
std::vector< DAVResource > parseWebDAVPropFindResponse(const uno::Reference< io::XInputStream > &xInputStream)
OUString ConnectionEndPointString(std::u16string_view rHostName, sal_uInt16 const nPort)
Definition: CurlUri.cxx:303
const sal_uInt16 SC_UNAUTHORIZED
const sal_uInt16 SC_BAD_REQUEST
static int debug_callback(CURL *handle, curl_infotype type, char *data, size_t size, void *)
static size_t header_callback(char *const buffer, size_t const size, size_t const nitems, void *const userdata)
static auto ExtractRealm(ResponseHeaders const &rHeaders, char const *const pAuthHeaderName) -> ::std::optional< OUString >
const sal_uInt16 SC_REQUEST_TIMEOUT
std::enable_if< std::is_signed< T >::value, bool >::type checked_add(T a, T b, T &result)
constexpr std::enable_if_t< std::is_signed_v< T >, std::make_unsigned_t< T > > make_unsigned(T value)
constexpr bool starts_with(std::basic_string_view< charT, traits > sv, std::basic_string_view< charT, traits > x) noexcept
end
args
OString OUStringToOString(std::u16string_view str, ConnectionSettings const *settings)
long Long
std::vector< sal_uInt8 > bytes
this is just a bunch of static member functions called from CurlSession
static auto URIReferenceToURI(CurlSession &rSession, std::u16string_view rURIReference) -> CurlUri
static auto Lock(CurlSession &rSession, CurlUri const &rURI, DAVRequestEnvironment const *pEnv, ::std::unique_ptr< curl_slist, deleter_from_fn< curl_slist, curl_slist_free_all > > pRequestHeaderList, uno::Reference< io::XInputStream > const *pxInStream) -> ::std::vector<::std::pair< ucb::Lock, sal_Int32 > >
static auto ProcessRequestImpl(CurlSession &rSession, CurlUri const &rURI, OUString const &rMethod, curl_slist *pRequestHeaderList, uno::Reference< io::XOutputStream > const *pxOutStream, uno::Sequence< sal_Int8 > const *pInData, ::std::pair<::std::vector< OUString > const &, DAVResource & > const *pRequestedHeaders, ResponseHeaders &rHeaders) -> void
main function to initiate libcurl requests
static auto Unlock(CurlSession &rSession, CurlUri const &rURI, DAVRequestEnvironment const *pEnv) -> void
static auto PropFind(CurlSession &rSession, CurlUri const &rURI, Depth depth, ::std::tuple<::std::vector< OUString > const &, ::std::vector< DAVResource > *const, ::std::vector< ucb::Lock > *const > const *o_pRequestedProperties, ::std::vector< DAVResourceInfo > *const o_pResourceInfos, DAVRequestEnvironment const &rEnv) -> void
static auto MoveOrCopy(CurlSession &rSession, std::u16string_view rSourceURIReference, ::std::u16string_view rDestinationURI, DAVRequestEnvironment const &rEnv, bool isOverwrite, char const *pMethod) -> void
static auto ProcessRequest(CurlSession &rSession, CurlUri const &rURI, OUString const &rMethod, ::std::vector< CurlOption > const &rOptions, DAVRequestEnvironment const *pEnv, ::std::unique_ptr< curl_slist, deleter_from_fn< curl_slist, curl_slist_free_all > > pRequestHeaderList, uno::Reference< io::XOutputStream > const *pxOutStream, uno::Reference< io::XInputStream > const *pxInStream, ::std::pair<::std::vector< OUString > const &, DAVResource & > const *pRequestedHeaders) -> void
static constexpr OUStringLiteral LOCKDISCOVERY
unsigned char sal_uInt8
signed char sal_Int8
Any result
ResultType type