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(OString::Concat("\\x")
1352 + OStringChar(hexDigit[static_cast<sal_uInt8>(bytes[i]) >> 4])
1353 + OStringChar(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 {
1401 ? rSession.m_isAuthenticated
1402 : rSession.m_isAuthenticatedProxy)
1403 = false; // any auth data in m_pCurl is invalid
1404 auto& rnAuthRequests(statusCode == SC_UNAUTHORIZED ? nAuthRequests
1405 : nAuthRequestsProxy);
1406 if (rnAuthRequests == 10)
1407 {
1408 SAL_INFO("ucb.ucp.webdav.curl", "aborting authentication after "
1409 << rnAuthRequests << " attempts");
1410 }
1411 else if (pEnv && pEnv->m_xAuthListener)
1412 {
1413 ::std::optional<OUString> const oRealm(ExtractRealm(
1414 headers, statusCode == SC_UNAUTHORIZED ? "WWW-Authenticate"
1415 : "Proxy-Authenticate"));
1416
1417 ::std::optional<Auth>& roAuth(
1418 statusCode == SC_UNAUTHORIZED ? oAuth : oAuthProxy);
1419 OUString userName(roAuth ? roAuth->UserName : OUString());
1420 OUString passWord(roAuth ? roAuth->PassWord : OUString());
1421 long authAvail(0);
1422 auto const rc = curl_easy_getinfo(rSession.m_pCurl.get(),
1423 statusCode == SC_UNAUTHORIZED
1424 ? CURLINFO_HTTPAUTH_AVAIL
1425 : CURLINFO_PROXYAUTH_AVAIL,
1426 &authAvail);
1427 assert(rc == CURLE_OK);
1428 (void)rc;
1429 // only allow SystemCredentials once - the
1430 // PasswordContainer may have stored it in the
1431 // Config (TrySystemCredentialsFirst or
1432 // AuthenticateUsingSystemCredentials) and then it
1433 // will always force its use no matter how hopeless
1434 bool const isSystemCredSupported((authAvail & authSystem) != 0
1435 && rnAuthRequests == 0);
1436 ++rnAuthRequests;
1437
1438 // Ask user via XInteractionHandler.
1439 // Warning: This likely runs an event loop which may
1440 // end up calling back into this instance, so all
1441 // changes to m_pCurl must be undone now and
1442 // restored after return.
1443 guard.Release();
1444
1445 auto const ret = pEnv->m_xAuthListener->authenticate(
1446 oRealm ? *oRealm : "",
1447 statusCode == SC_UNAUTHORIZED ? rSession.m_URI.GetHost()
1448 : rSession.m_Proxy.aName,
1449 userName, passWord, isSystemCredSupported);
1450
1451 if (ret == 0)
1452 {
1453 // NTLM may either use a password requested
1454 // from the user, or from the system via SSPI
1455 // so i guess it should not be disabled here
1456 // regardless of the state of the system auth
1457 // checkbox, particularly since SSPI is only
1458 // available on WNT.
1459 // Additionally, "Negotiate" has a "legacy"
1460 // mode that is actually just NTLM according to
1461 // https://curl.se/rfc/ntlm.html#ntlmHttpAuthentication
1462 // so there's nothing in authSystem that can be
1463 // disabled here.
1464 roAuth.emplace(userName, passWord,
1465 ((userName.isEmpty() && passWord.isEmpty())
1466 ? (authAvail & authSystem)
1467 : authAvail));
1468 isRetry = true;
1469 // Acquire is only necessary in case of success.
1470 guard.Acquire();
1471 break; // break out of switch
1472 }
1473 // else: throw
1474 }
1475 SAL_INFO("ucb.ucp.webdav.curl", "no auth credentials provided");
1476 throw DAVException(DAVException::DAV_HTTP_NOAUTH,
1477 ConnectionEndPointString(rSession.m_URI.GetHost(),
1478 rSession.m_URI.GetPort()));
1479 break;
1480 }
1481 }
1482 }
1483 else if (rException.getError() == DAVException::DAV_UNSUPPORTED)
1484 {
1485 // tdf#152493 libcurl can't handle "Transfer-Encoding: chunked"
1486 // in HTTP/1.1 100 Continue response.
1487 // workaround: if HTTP/1.1 didn't work, try HTTP/1.0
1488 // (but fallback only once - to prevent infinite loop)
1489 if (isFallbackHTTP10)
1490 {
1491 throw DAVException(DAVException::DAV_HTTP_ERROR);
1492 }
1493 isFallbackHTTP10 = true;
1494 // note: this is not reset - future requests to this URI use it!
1495 auto rc = curl_easy_setopt(rSession.m_pCurl.get(), CURLOPT_HTTP_VERSION,
1496 CURL_HTTP_VERSION_1_0);
1497 if (rc != CURLE_OK)
1498 {
1499 throw DAVException(DAVException::DAV_HTTP_ERROR);
1500 }
1501 SAL_INFO("ucb.ucp.webdav.curl", "attempting fallback to HTTP/1.0");
1502 isRetry = true;
1503 }
1504 if (!isRetry)
1505 {
1506 throw; // everything else: re-throw
1507 }
1508 }
1509 } while (isRetry);
1510
1511 if (oAuth)
1512 {
1513 // assume this worked, leave auth data as stored in m_pCurl
1514 rSession.m_isAuthenticated = true;
1515 }
1516 if (oAuthProxy)
1517 {
1518 // assume this worked, leave auth data as stored in m_pCurl
1519 rSession.m_isAuthenticatedProxy = true;
1520 }
1521}
1522
1523auto CurlSession::OPTIONS(OUString const& rURIReference,
1524
1525 DAVOptions& rOptions, DAVRequestEnvironment const& rEnv) -> void
1526{
1527 SAL_INFO("ucb.ucp.webdav.curl", "OPTIONS: " << rURIReference);
1528
1529 rOptions.init();
1530
1531 CurlUri const uri(CurlProcessor::URIReferenceToURI(*this, rURIReference));
1532
1533 ::std::vector<OUString> const headerNames{ "allow", "dav" };
1535 ::std::pair<::std::vector<OUString> const&, DAVResource&> const headers(headerNames, result);
1536
1537 ::std::vector<CurlOption> const options{
1538 g_NoBody, { CURLOPT_CUSTOMREQUEST, "OPTIONS", "CURLOPT_CUSTOMREQUEST" }
1539 };
1540
1541 CurlProcessor::ProcessRequest(*this, uri, "OPTIONS", options, &rEnv, nullptr, nullptr, nullptr,
1542 &headers);
1543
1544 for (auto const& it : result.properties)
1545 {
1546 OUString value;
1547 it.Value >>= value;
1548 SAL_INFO("ucb.ucp.webdav.curl", "OPTIONS: header: " << it.Name << ": " << value);
1549 if (it.Name.equalsIgnoreAsciiCase("allow"))
1550 {
1551 rOptions.setAllowedMethods(value);
1552 }
1553 else if (it.Name.equalsIgnoreAsciiCase("dav"))
1554 {
1555 // see <http://tools.ietf.org/html/rfc4918#section-10.1>,
1556 // <http://tools.ietf.org/html/rfc4918#section-18>,
1557 // and <http://tools.ietf.org/html/rfc7230#section-3.2>
1558 // we detect the class (1, 2 and 3), other elements (token, URL)
1559 // are not used for now
1560 auto const list(::comphelper::string::convertCommaSeparated(value));
1561 for (OUString const& v : list)
1562 {
1563 if (v == "1")
1564 {
1565 rOptions.setClass1();
1566 }
1567 else if (v == "2")
1568 {
1569 rOptions.setClass2();
1570 }
1571 else if (v == "3")
1572 {
1573 rOptions.setClass3();
1574 }
1575 }
1576 }
1577 }
1578 if (rOptions.isClass2() || rOptions.isClass3())
1579 {
1580 if (g_Init.LockStore.getLockTokenForURI(uri.GetURI(), nullptr))
1581 {
1582 rOptions.setLocked();
1583 }
1584 }
1585}
1586
1587auto CurlProcessor::PropFind(
1588 CurlSession& rSession, CurlUri const& rURI, Depth const nDepth,
1589 ::std::tuple<::std::vector<OUString> const&, ::std::vector<DAVResource>* const,
1590 ::std::vector<ucb::Lock>* const> const* const o_pRequestedProperties,
1591 ::std::vector<DAVResourceInfo>* const o_pResourceInfos, DAVRequestEnvironment const& rEnv)
1592 -> void
1593{
1594 assert((o_pRequestedProperties != nullptr) != (o_pResourceInfos != nullptr));
1595 assert((o_pRequestedProperties == nullptr)
1596 || (::std::get<1>(*o_pRequestedProperties) != nullptr)
1597 != (::std::get<2>(*o_pRequestedProperties) != nullptr));
1598
1599 ::std::unique_ptr<curl_slist, deleter_from_fn<curl_slist, curl_slist_free_all>> pList;
1600 pList.reset(curl_slist_append(pList.release(), "Content-Type: application/xml"));
1601 if (!pList)
1602 {
1603 throw uno::RuntimeException("curl_slist_append failed");
1604 }
1605 OString depth;
1606 switch (nDepth)
1607 {
1608 case DAVZERO:
1609 depth = "Depth: 0";
1610 break;
1611 case DAVONE:
1612 depth = "Depth: 1";
1613 break;
1614 case DAVINFINITY:
1615 depth = "Depth: infinity";
1616 break;
1617 default:
1618 assert(false);
1619 }
1620 pList.reset(curl_slist_append(pList.release(), depth.getStr()));
1621 if (!pList)
1622 {
1623 throw uno::RuntimeException("curl_slist_append failed");
1624 }
1625
1626 uno::Reference<io::XSequenceOutputStream> const xSeqOutStream(
1627 io::SequenceOutputStream::create(rSession.m_xContext));
1628 uno::Reference<io::XOutputStream> const xRequestOutStream(xSeqOutStream);
1629 assert(xRequestOutStream.is());
1630
1631 uno::Reference<xml::sax::XWriter> const xWriter(xml::sax::Writer::create(rSession.m_xContext));
1632 xWriter->setOutputStream(xRequestOutStream);
1633 xWriter->startDocument();
1634 rtl::Reference<::comphelper::AttributeList> const pAttrList(new ::comphelper::AttributeList);
1635 pAttrList->AddAttribute("xmlns", "DAV:");
1636 xWriter->startElement("propfind", pAttrList);
1637 if (o_pResourceInfos)
1638 {
1639 xWriter->startElement("propname", nullptr);
1640 xWriter->endElement("propname");
1641 }
1642 else
1643 {
1644 if (::std::get<0>(*o_pRequestedProperties).empty())
1645 {
1646 xWriter->startElement("allprop", nullptr);
1647 xWriter->endElement("allprop");
1648 }
1649 else
1650 {
1651 xWriter->startElement("prop", nullptr);
1652 for (OUString const& rName : ::std::get<0>(*o_pRequestedProperties))
1653 {
1655 DAVProperties::createSerfPropName(rName, name);
1656 pAttrList->Clear();
1657 pAttrList->AddAttribute("xmlns", OUString::createFromAscii(name.nspace));
1658 xWriter->startElement(OUString::createFromAscii(name.name), pAttrList);
1659 xWriter->endElement(OUString::createFromAscii(name.name));
1660 }
1661 xWriter->endElement("prop");
1662 }
1663 }
1664 xWriter->endElement("propfind");
1665 xWriter->endDocument();
1666
1667 uno::Reference<io::XInputStream> const xRequestInStream(
1668 io::SequenceInputStream::createStreamFromSequence(rSession.m_xContext,
1669 xSeqOutStream->getWrittenBytes()));
1670 assert(xRequestInStream.is());
1671
1672 curl_off_t const len(xSeqOutStream->getWrittenBytes().getLength());
1673
1674 ::std::vector<CurlOption> const options{
1675 { CURLOPT_UPLOAD, 1L, nullptr },
1676 { CURLOPT_CUSTOMREQUEST, "PROPFIND", "CURLOPT_CUSTOMREQUEST" },
1677 // note: Sharepoint cannot handle "Transfer-Encoding: chunked"
1678 { CURLOPT_INFILESIZE_LARGE, len, nullptr, CurlOption::Type::CurlOffT }
1679 };
1680
1681 // stream for response
1682 uno::Reference<io::XInputStream> const xResponseInStream(io::Pipe::create(rSession.m_xContext));
1683 uno::Reference<io::XOutputStream> const xResponseOutStream(xResponseInStream, uno::UNO_QUERY);
1684 assert(xResponseInStream.is());
1685 assert(xResponseOutStream.is());
1686
1687 CurlProcessor::ProcessRequest(rSession, rURI, "PROPFIND", options, &rEnv, ::std::move(pList),
1688 &xResponseOutStream, &xRequestInStream, nullptr);
1689
1690 if (o_pResourceInfos)
1691 {
1692 *o_pResourceInfos = parseWebDAVPropNameResponse(xResponseInStream);
1693 }
1694 else
1695 {
1696 if (::std::get<1>(*o_pRequestedProperties) != nullptr)
1697 {
1698 *::std::get<1>(*o_pRequestedProperties)
1699 = parseWebDAVPropFindResponse(xResponseInStream);
1700 for (DAVResource& it : *::std::get<1>(*o_pRequestedProperties))
1701 {
1702 // caller will give these uris to CurlUri so can't be relative
1703 if (it.uri.startsWith("/"))
1704 {
1705 try
1706 {
1707 it.uri = rSession.m_URI.CloneWithRelativeRefPathAbsolute(it.uri).GetURI();
1708 }
1709 catch (DAVException const&)
1710 {
1711 SAL_INFO("ucb.ucp.webdav.curl",
1712 "PROPFIND: exception parsing uri " << it.uri);
1713 }
1714 }
1715 }
1716 }
1717 else
1718 {
1719 *::std::get<2>(*o_pRequestedProperties) = parseWebDAVLockResponse(xResponseInStream);
1720 }
1721 }
1722}
1723
1724// DAV methods
1725auto CurlSession::PROPFIND(OUString const& rURIReference, Depth const depth,
1726 ::std::vector<OUString> const& rPropertyNames,
1727 ::std::vector<DAVResource>& o_rResources,
1728 DAVRequestEnvironment const& rEnv) -> void
1729{
1730 SAL_INFO("ucb.ucp.webdav.curl", "PROPFIND: " << rURIReference << " " << depth);
1731
1732 CurlUri const uri(CurlProcessor::URIReferenceToURI(*this, rURIReference));
1733
1734 ::std::tuple<::std::vector<OUString> const&, ::std::vector<DAVResource>* const,
1735 ::std::vector<ucb::Lock>* const> const args(rPropertyNames, &o_rResources,
1736 nullptr);
1737 return CurlProcessor::PropFind(*this, uri, depth, &args, nullptr, rEnv);
1738}
1739
1740auto CurlSession::PROPFIND(OUString const& rURIReference, Depth const depth,
1741 ::std::vector<DAVResourceInfo>& o_rResourceInfos,
1742 DAVRequestEnvironment const& rEnv) -> void
1743{
1744 SAL_INFO("ucb.ucp.webdav.curl", "PROPFIND: " << rURIReference << " " << depth);
1745
1746 CurlUri const uri(CurlProcessor::URIReferenceToURI(*this, rURIReference));
1747
1748 return CurlProcessor::PropFind(*this, uri, depth, nullptr, &o_rResourceInfos, rEnv);
1749}
1750
1751auto CurlSession::PROPPATCH(OUString const& rURIReference,
1752 ::std::vector<ProppatchValue> const& rValues,
1753 DAVRequestEnvironment const& rEnv) -> void
1754{
1755 SAL_INFO("ucb.ucp.webdav.curl", "PROPPATCH: " << rURIReference);
1756
1757 CurlUri const uri(CurlProcessor::URIReferenceToURI(*this, rURIReference));
1758
1759 // TODO: either set CURLOPT_INFILESIZE_LARGE or chunked?
1760 ::std::unique_ptr<curl_slist, deleter_from_fn<curl_slist, curl_slist_free_all>> pList;
1761 pList.reset(curl_slist_append(pList.release(), "Content-Type: application/xml"));
1762 if (!pList)
1763 {
1764 throw uno::RuntimeException("curl_slist_append failed");
1765 }
1766
1767 // generate XML document for PROPPATCH
1768 uno::Reference<io::XSequenceOutputStream> const xSeqOutStream(
1769 io::SequenceOutputStream::create(m_xContext));
1770 uno::Reference<io::XOutputStream> const xRequestOutStream(xSeqOutStream);
1771 assert(xRequestOutStream.is());
1772 uno::Reference<xml::sax::XWriter> const xWriter(xml::sax::Writer::create(m_xContext));
1773 xWriter->setOutputStream(xRequestOutStream);
1774 xWriter->startDocument();
1775 rtl::Reference<::comphelper::AttributeList> const pAttrList(new ::comphelper::AttributeList);
1776 pAttrList->AddAttribute("xmlns", "DAV:");
1777 xWriter->startElement("propertyupdate", pAttrList);
1778 for (ProppatchValue const& rPropValue : rValues)
1779 {
1780 assert(rPropValue.operation == PROPSET || rPropValue.operation == PROPREMOVE);
1781 OUString const operation((rPropValue.operation == PROPSET) ? OUString("set")
1782 : OUString("remove"));
1783 xWriter->startElement(operation, nullptr);
1784 xWriter->startElement("prop", nullptr);
1786 DAVProperties::createSerfPropName(rPropValue.name, name);
1787 pAttrList->Clear();
1788 pAttrList->AddAttribute("xmlns", OUString::createFromAscii(name.nspace));
1789 xWriter->startElement(OUString::createFromAscii(name.name), pAttrList);
1790 if (rPropValue.operation == PROPSET)
1791 {
1792 if (DAVProperties::isUCBDeadProperty(name))
1793 {
1794 ::std::optional<::std::pair<OUString, OUString>> const oProp(
1795 UCBDeadPropertyValue::toXML(rPropValue.value));
1796 if (oProp)
1797 {
1798 xWriter->startElement("ucbprop", nullptr);
1799 xWriter->startElement("type", nullptr);
1800 xWriter->characters(oProp->first);
1801 xWriter->endElement("type");
1802 xWriter->startElement("value", nullptr);
1803 xWriter->characters(oProp->second);
1804 xWriter->endElement("value");
1805 xWriter->endElement("ucbprop");
1806 }
1807 }
1808 else
1809 {
1810 OUString value;
1811 rPropValue.value >>= value;
1812 xWriter->characters(value);
1813 }
1814 }
1815 xWriter->endElement(OUString::createFromAscii(name.name));
1816 xWriter->endElement("prop");
1817 xWriter->endElement(operation);
1818 }
1819 xWriter->endElement("propertyupdate");
1820 xWriter->endDocument();
1821
1822 uno::Reference<io::XInputStream> const xRequestInStream(
1823 io::SequenceInputStream::createStreamFromSequence(m_xContext,
1824 xSeqOutStream->getWrittenBytes()));
1825 assert(xRequestInStream.is());
1826
1827 curl_off_t const len(xSeqOutStream->getWrittenBytes().getLength());
1828
1829 ::std::vector<CurlOption> const options{
1830 { CURLOPT_UPLOAD, 1L, nullptr },
1831 { CURLOPT_CUSTOMREQUEST, "PROPPATCH", "CURLOPT_CUSTOMREQUEST" },
1832 // note: Sharepoint cannot handle "Transfer-Encoding: chunked"
1833 { CURLOPT_INFILESIZE_LARGE, len, nullptr, CurlOption::Type::CurlOffT }
1834 };
1835
1836 CurlProcessor::ProcessRequest(*this, uri, "PROPPATCH", options, &rEnv, ::std::move(pList),
1837 nullptr, &xRequestInStream, nullptr);
1838}
1839
1840auto CurlSession::HEAD(OUString const& rURIReference, ::std::vector<OUString> const& rHeaderNames,
1841 DAVResource& io_rResource, DAVRequestEnvironment const& rEnv) -> void
1842{
1843 SAL_INFO("ucb.ucp.webdav.curl", "HEAD: " << rURIReference);
1844
1845 CurlUri const uri(CurlProcessor::URIReferenceToURI(*this, rURIReference));
1846
1847 ::std::vector<CurlOption> const options{ g_NoBody };
1848
1849 ::std::pair<::std::vector<OUString> const&, DAVResource&> const headers(rHeaderNames,
1850 io_rResource);
1851
1852 CurlProcessor::ProcessRequest(*this, uri, "HEAD", options, &rEnv, nullptr, nullptr, nullptr,
1853 &headers);
1854}
1855
1856auto CurlSession::GET(OUString const& rURIReference, DAVRequestEnvironment const& rEnv)
1857 -> uno::Reference<io::XInputStream>
1858{
1859 SAL_INFO("ucb.ucp.webdav.curl", "GET: " << rURIReference);
1860
1861 CurlUri const uri(CurlProcessor::URIReferenceToURI(*this, rURIReference));
1862
1863 // could use either com.sun.star.io.Pipe or com.sun.star.io.SequenceInputStream?
1864 // Pipe can just write into its XOuputStream, which is simpler.
1865 // Both resize exponentially, so performance should be fine.
1866 // However, Pipe doesn't implement XSeekable, which is required by filters.
1867
1868 uno::Reference<io::XSequenceOutputStream> const xSeqOutStream(
1869 io::SequenceOutputStream::create(m_xContext));
1870 uno::Reference<io::XOutputStream> const xResponseOutStream(xSeqOutStream);
1871 assert(xResponseOutStream.is());
1872
1873 ::std::vector<CurlOption> const options{ { CURLOPT_HTTPGET, 1L, nullptr } };
1874
1875 CurlProcessor::ProcessRequest(*this, uri, "GET", options, &rEnv, nullptr, &xResponseOutStream,
1876 nullptr, nullptr);
1877
1878 uno::Reference<io::XInputStream> const xResponseInStream(
1879 io::SequenceInputStream::createStreamFromSequence(m_xContext,
1880 xSeqOutStream->getWrittenBytes()));
1881 assert(xResponseInStream.is());
1882
1883 return xResponseInStream;
1884}
1885
1886auto CurlSession::GET(OUString const& rURIReference, uno::Reference<io::XOutputStream>& rxOutStream,
1887 DAVRequestEnvironment const& rEnv) -> void
1888{
1889 SAL_INFO("ucb.ucp.webdav.curl", "GET: " << rURIReference);
1890
1891 CurlUri const uri(CurlProcessor::URIReferenceToURI(*this, rURIReference));
1892
1893 ::std::vector<CurlOption> const options{ { CURLOPT_HTTPGET, 1L, nullptr } };
1894
1895 CurlProcessor::ProcessRequest(*this, uri, "GET", options, &rEnv, nullptr, &rxOutStream, nullptr,
1896 nullptr);
1897}
1898
1899auto CurlSession::GET(OUString const& rURIReference, ::std::vector<OUString> const& rHeaderNames,
1900 DAVResource& io_rResource, DAVRequestEnvironment const& rEnv)
1901 -> uno::Reference<io::XInputStream>
1902{
1903 SAL_INFO("ucb.ucp.webdav.curl", "GET: " << rURIReference);
1904
1905 CurlUri const uri(CurlProcessor::URIReferenceToURI(*this, rURIReference));
1906
1907 ::std::vector<CurlOption> const options{ { CURLOPT_HTTPGET, 1L, nullptr } };
1908
1909 uno::Reference<io::XSequenceOutputStream> const xSeqOutStream(
1910 io::SequenceOutputStream::create(m_xContext));
1911 uno::Reference<io::XOutputStream> const xResponseOutStream(xSeqOutStream);
1912 assert(xResponseOutStream.is());
1913
1914 ::std::pair<::std::vector<OUString> const&, DAVResource&> const headers(rHeaderNames,
1915 io_rResource);
1916
1917 CurlProcessor::ProcessRequest(*this, uri, "GET", options, &rEnv, nullptr, &xResponseOutStream,
1918 nullptr, &headers);
1919
1920 uno::Reference<io::XInputStream> const xResponseInStream(
1921 io::SequenceInputStream::createStreamFromSequence(m_xContext,
1922 xSeqOutStream->getWrittenBytes()));
1923 assert(xResponseInStream.is());
1924
1925 return xResponseInStream;
1926}
1927
1928auto CurlSession::GET(OUString const& rURIReference, uno::Reference<io::XOutputStream>& rxOutStream,
1929 ::std::vector<OUString> const& rHeaderNames, DAVResource& io_rResource,
1930 DAVRequestEnvironment const& rEnv) -> void
1931{
1932 SAL_INFO("ucb.ucp.webdav.curl", "GET: " << rURIReference);
1933
1934 CurlUri const uri(CurlProcessor::URIReferenceToURI(*this, rURIReference));
1935
1936 ::std::vector<CurlOption> const options{ { CURLOPT_HTTPGET, 1L, nullptr } };
1937
1938 ::std::pair<::std::vector<OUString> const&, DAVResource&> const headers(rHeaderNames,
1939 io_rResource);
1940
1941 CurlProcessor::ProcessRequest(*this, uri, "GET", options, &rEnv, nullptr, &rxOutStream, nullptr,
1942 &headers);
1943}
1944
1945auto CurlSession::PUT(OUString const& rURIReference,
1946 uno::Reference<io::XInputStream> const& rxInStream,
1947 DAVRequestEnvironment const& rEnv) -> void
1948{
1949 SAL_INFO("ucb.ucp.webdav.curl", "PUT: " << rURIReference);
1950
1951 CurlUri const uri(CurlProcessor::URIReferenceToURI(*this, rURIReference));
1952
1953 // NextCloud silently fails with chunked encoding
1954 uno::Reference<io::XSeekable> const xSeekable(rxInStream, uno::UNO_QUERY);
1955 if (!xSeekable.is())
1956 {
1957 throw uno::RuntimeException("TODO: not seekable");
1958 }
1959 curl_off_t const len(xSeekable->getLength() - xSeekable->getPosition());
1960
1961 ::std::unique_ptr<curl_slist, deleter_from_fn<curl_slist, curl_slist_free_all>> pList;
1962 OUString const* const pToken(g_Init.LockStore.getLockTokenForURI(uri.GetURI(), nullptr));
1963 if (pToken)
1964 {
1965 OString const utf8If("If: "
1966 // disabled as Sharepoint 2013 workaround, it accepts only
1967 // "No-Tag-List", see fed2984281a85a5a2f308841ec810f218c75f2ab
1968#if 0
1969 "<" + OUStringToOString(rURIReference, RTL_TEXTENCODING_ASCII_US)
1970 + "> "
1971#endif
1972 "(<"
1973 + OUStringToOString(*pToken, RTL_TEXTENCODING_ASCII_US) + ">)");
1974 pList.reset(curl_slist_append(pList.release(), utf8If.getStr()));
1975 if (!pList)
1976 {
1977 throw uno::RuntimeException("curl_slist_append failed");
1978 }
1979 }
1980
1981 // lock m_Mutex after accessing global LockStore to avoid deadlock
1982
1983 // note: Nextcloud 20 cannot handle "Transfer-Encoding: chunked"
1984 ::std::vector<CurlOption> const options{
1985 { CURLOPT_UPLOAD, 1L, nullptr }, // libcurl won't upload without setting this
1986 { CURLOPT_INFILESIZE_LARGE, len, nullptr, CurlOption::Type::CurlOffT }
1987 };
1988
1989 CurlProcessor::ProcessRequest(*this, uri, "PUT", options, &rEnv, ::std::move(pList), nullptr,
1990 &rxInStream, nullptr);
1991}
1992
1993auto CurlSession::POST(OUString const& rURIReference, OUString const& rContentType,
1994 OUString const& rReferer, uno::Reference<io::XInputStream> const& rxInStream,
1995 DAVRequestEnvironment const& rEnv) -> uno::Reference<io::XInputStream>
1996{
1997 SAL_INFO("ucb.ucp.webdav.curl", "POST: " << rURIReference);
1998
1999 CurlUri const uri(CurlProcessor::URIReferenceToURI(*this, rURIReference));
2000
2001 // TODO: either set CURLOPT_POSTFIELDSIZE_LARGE or chunked?
2002 ::std::unique_ptr<curl_slist, deleter_from_fn<curl_slist, curl_slist_free_all>> pList(
2003 curl_slist_append(nullptr, "Transfer-Encoding: chunked"));
2004 if (!pList)
2005 {
2006 throw uno::RuntimeException("curl_slist_append failed");
2007 }
2008 OString const utf8ContentType("Content-Type: "
2009 + OUStringToOString(rContentType, RTL_TEXTENCODING_ASCII_US));
2010 pList.reset(curl_slist_append(pList.release(), utf8ContentType.getStr()));
2011 if (!pList)
2012 {
2013 throw uno::RuntimeException("curl_slist_append failed");
2014 }
2015 OString const utf8Referer("Referer: " + OUStringToOString(rReferer, RTL_TEXTENCODING_ASCII_US));
2016 pList.reset(curl_slist_append(pList.release(), utf8Referer.getStr()));
2017 if (!pList)
2018 {
2019 throw uno::RuntimeException("curl_slist_append failed");
2020 }
2021
2022 ::std::vector<CurlOption> const options{ { CURLOPT_POST, 1L, nullptr } };
2023
2024 uno::Reference<io::XSequenceOutputStream> const xSeqOutStream(
2025 io::SequenceOutputStream::create(m_xContext));
2026 uno::Reference<io::XOutputStream> const xResponseOutStream(xSeqOutStream);
2027 assert(xResponseOutStream.is());
2028
2029 CurlProcessor::ProcessRequest(*this, uri, "POST", options, &rEnv, ::std::move(pList),
2030 &xResponseOutStream, &rxInStream, nullptr);
2031
2032 uno::Reference<io::XInputStream> const xResponseInStream(
2033 io::SequenceInputStream::createStreamFromSequence(m_xContext,
2034 xSeqOutStream->getWrittenBytes()));
2035 assert(xResponseInStream.is());
2036
2037 return xResponseInStream;
2038}
2039
2040auto CurlSession::POST(OUString const& rURIReference, OUString const& rContentType,
2041 OUString const& rReferer, uno::Reference<io::XInputStream> const& rxInStream,
2042 uno::Reference<io::XOutputStream>& rxOutStream,
2043 DAVRequestEnvironment const& rEnv) -> void
2044{
2045 SAL_INFO("ucb.ucp.webdav.curl", "POST: " << rURIReference);
2046
2047 CurlUri const uri(CurlProcessor::URIReferenceToURI(*this, rURIReference));
2048
2049 // TODO: either set CURLOPT_POSTFIELDSIZE_LARGE or chunked?
2050 ::std::unique_ptr<curl_slist, deleter_from_fn<curl_slist, curl_slist_free_all>> pList(
2051 curl_slist_append(nullptr, "Transfer-Encoding: chunked"));
2052 if (!pList)
2053 {
2054 throw uno::RuntimeException("curl_slist_append failed");
2055 }
2056 OString const utf8ContentType("Content-Type: "
2057 + OUStringToOString(rContentType, RTL_TEXTENCODING_ASCII_US));
2058 pList.reset(curl_slist_append(pList.release(), utf8ContentType.getStr()));
2059 if (!pList)
2060 {
2061 throw uno::RuntimeException("curl_slist_append failed");
2062 }
2063 OString const utf8Referer("Referer: " + OUStringToOString(rReferer, RTL_TEXTENCODING_ASCII_US));
2064 pList.reset(curl_slist_append(pList.release(), utf8Referer.getStr()));
2065 if (!pList)
2066 {
2067 throw uno::RuntimeException("curl_slist_append failed");
2068 }
2069
2070 ::std::vector<CurlOption> const options{ { CURLOPT_POST, 1L, nullptr } };
2071
2072 CurlProcessor::ProcessRequest(*this, uri, "POST", options, &rEnv, ::std::move(pList),
2073 &rxOutStream, &rxInStream, nullptr);
2074}
2075
2076auto CurlSession::MKCOL(OUString const& rURIReference, DAVRequestEnvironment const& rEnv) -> void
2077{
2078 SAL_INFO("ucb.ucp.webdav.curl", "MKCOL: " << rURIReference);
2079
2080 CurlUri const uri(CurlProcessor::URIReferenceToURI(*this, rURIReference));
2081
2082 ::std::vector<CurlOption> const options{
2083 g_NoBody, { CURLOPT_CUSTOMREQUEST, "MKCOL", "CURLOPT_CUSTOMREQUEST" }
2084 };
2085
2086 CurlProcessor::ProcessRequest(*this, uri, "MKCOL", options, &rEnv, nullptr, nullptr, nullptr,
2087 nullptr);
2088}
2089
2090auto CurlProcessor::MoveOrCopy(CurlSession& rSession, std::u16string_view rSourceURIReference,
2091 ::std::u16string_view const rDestinationURI,
2092 DAVRequestEnvironment const& rEnv, bool const isOverwrite,
2093 char const* const pMethod) -> void
2094{
2095 CurlUri const uriSource(CurlProcessor::URIReferenceToURI(rSession, rSourceURIReference));
2096
2097 OString const utf8Destination("Destination: "
2098 + OUStringToOString(rDestinationURI, RTL_TEXTENCODING_ASCII_US));
2099 ::std::unique_ptr<curl_slist, deleter_from_fn<curl_slist, curl_slist_free_all>> pList(
2100 curl_slist_append(nullptr, utf8Destination.getStr()));
2101 if (!pList)
2102 {
2103 throw uno::RuntimeException("curl_slist_append failed");
2104 }
2105 OString const utf8Overwrite(OString::Concat("Overwrite: ") + (isOverwrite ? "T" : "F"));
2106 pList.reset(curl_slist_append(pList.release(), utf8Overwrite.getStr()));
2107 if (!pList)
2108 {
2109 throw uno::RuntimeException("curl_slist_append failed");
2110 }
2111
2112 ::std::vector<CurlOption> const options{
2113 g_NoBody, { CURLOPT_CUSTOMREQUEST, pMethod, "CURLOPT_CUSTOMREQUEST" }
2114 };
2115
2116 CurlProcessor::ProcessRequest(rSession, uriSource, OUString::createFromAscii(pMethod), options,
2117 &rEnv, ::std::move(pList), nullptr, nullptr, nullptr);
2118}
2119
2120auto CurlSession::COPY(OUString const& rSourceURIReference, OUString const& rDestinationURI,
2121 DAVRequestEnvironment const& rEnv, bool const isOverwrite) -> void
2122{
2123 SAL_INFO("ucb.ucp.webdav.curl", "COPY: " << rSourceURIReference);
2124
2125 return CurlProcessor::MoveOrCopy(*this, rSourceURIReference, rDestinationURI, rEnv, isOverwrite,
2126 "COPY");
2127}
2128
2129auto CurlSession::MOVE(OUString const& rSourceURIReference, OUString const& rDestinationURI,
2130 DAVRequestEnvironment const& rEnv, bool const isOverwrite) -> void
2131{
2132 SAL_INFO("ucb.ucp.webdav.curl", "MOVE: " << rSourceURIReference);
2133
2134 return CurlProcessor::MoveOrCopy(*this, rSourceURIReference, rDestinationURI, rEnv, isOverwrite,
2135 "MOVE");
2136}
2137
2138auto CurlSession::DESTROY(OUString const& rURIReference, DAVRequestEnvironment const& rEnv) -> void
2139{
2140 SAL_INFO("ucb.ucp.webdav.curl", "DESTROY: " << rURIReference);
2141
2142 CurlUri const uri(CurlProcessor::URIReferenceToURI(*this, rURIReference));
2143
2144 ::std::vector<CurlOption> const options{
2145 g_NoBody, { CURLOPT_CUSTOMREQUEST, "DELETE", "CURLOPT_CUSTOMREQUEST" }
2146 };
2147
2148 CurlProcessor::ProcessRequest(*this, uri, "DESTROY", options, &rEnv, nullptr, nullptr, nullptr,
2149 nullptr);
2150}
2151
2152auto CurlProcessor::Lock(
2153 CurlSession& rSession, CurlUri const& rURI, DAVRequestEnvironment const* const pEnv,
2154 ::std::unique_ptr<curl_slist, deleter_from_fn<curl_slist, curl_slist_free_all>>
2155 pRequestHeaderList,
2156 uno::Reference<io::XInputStream> const* const pxRequestInStream)
2157 -> ::std::vector<::std::pair<ucb::Lock, sal_Int32>>
2158{
2159 curl_off_t len(0);
2160 if (pxRequestInStream)
2161 {
2162 uno::Reference<io::XSeekable> const xSeekable(*pxRequestInStream, uno::UNO_QUERY);
2163 assert(xSeekable.is());
2164 len = xSeekable->getLength();
2165 }
2166
2167 ::std::vector<CurlOption> const options{
2168 { CURLOPT_UPLOAD, 1L, nullptr },
2169 { CURLOPT_CUSTOMREQUEST, "LOCK", "CURLOPT_CUSTOMREQUEST" },
2170 // note: Sharepoint cannot handle "Transfer-Encoding: chunked"
2171 { CURLOPT_INFILESIZE_LARGE, len, nullptr, CurlOption::Type::CurlOffT }
2172 };
2173
2174 // stream for response
2175 uno::Reference<io::XInputStream> const xResponseInStream(io::Pipe::create(rSession.m_xContext));
2176 uno::Reference<io::XOutputStream> const xResponseOutStream(xResponseInStream, uno::UNO_QUERY);
2177 assert(xResponseInStream.is());
2178 assert(xResponseOutStream.is());
2179
2180 TimeValue startTime;
2181 osl_getSystemTime(&startTime);
2182
2183 CurlProcessor::ProcessRequest(rSession, rURI, "LOCK", options, pEnv,
2184 ::std::move(pRequestHeaderList), &xResponseOutStream,
2185 pxRequestInStream, nullptr);
2186
2187 ::std::vector<ucb::Lock> const acquiredLocks(parseWebDAVLockResponse(xResponseInStream));
2188 SAL_WARN_IF(acquiredLocks.empty(), "ucb.ucp.webdav.curl",
2189 "could not get LOCK for " << rURI.GetURI());
2190
2191 TimeValue endTime;
2192 osl_getSystemTime(&endTime);
2193 auto const elapsedSeconds(endTime.Seconds - startTime.Seconds);
2194
2195 // determine expiration time (seconds from endTime) for each acquired lock
2196 ::std::vector<::std::pair<ucb::Lock, sal_Int32>> ret;
2197 ret.reserve(acquiredLocks.size());
2198 for (auto const& rLock : acquiredLocks)
2199 {
2200 sal_Int32 lockExpirationTimeSeconds;
2201 if (rLock.Timeout == -1)
2202 {
2203 lockExpirationTimeSeconds = -1;
2204 }
2205 else if (rLock.Timeout <= elapsedSeconds)
2206 {
2207 SAL_WARN("ucb.ucp.webdav.curl",
2208 "LOCK timeout already expired when receiving LOCK response for "
2209 << rURI.GetURI());
2210 lockExpirationTimeSeconds = 0;
2211 }
2212 else
2213 {
2214 lockExpirationTimeSeconds = startTime.Seconds + rLock.Timeout;
2215 }
2216 ret.emplace_back(rLock, lockExpirationTimeSeconds);
2217 }
2218
2219 return ret;
2220}
2221
2222auto CurlSession::LOCK(OUString const& rURIReference, ucb::Lock /*const*/& rLock,
2223 DAVRequestEnvironment const& rEnv) -> void
2224{
2225 SAL_INFO("ucb.ucp.webdav.curl", "LOCK: " << rURIReference);
2226
2227 CurlUri const uri(CurlProcessor::URIReferenceToURI(*this, rURIReference));
2228
2229 if (g_Init.LockStore.getLockTokenForURI(uri.GetURI(), &rLock))
2230 {
2231 // already have a lock that covers the requirement
2232 // TODO: maybe use DAV:lockdiscovery to ensure it's valid
2233 return;
2234 }
2235
2236 // note: no m_Mutex lock needed here, only in CurlProcessor::Lock()
2237
2238 // generate XML document for acquiring new LOCK
2239 uno::Reference<io::XSequenceOutputStream> const xSeqOutStream(
2240 io::SequenceOutputStream::create(m_xContext));
2241 uno::Reference<io::XOutputStream> const xRequestOutStream(xSeqOutStream);
2242 assert(xRequestOutStream.is());
2243 uno::Reference<xml::sax::XWriter> const xWriter(xml::sax::Writer::create(m_xContext));
2244 xWriter->setOutputStream(xRequestOutStream);
2245 xWriter->startDocument();
2246 rtl::Reference<::comphelper::AttributeList> const pAttrList(new ::comphelper::AttributeList);
2247 pAttrList->AddAttribute("xmlns", "DAV:");
2248 xWriter->startElement("lockinfo", pAttrList);
2249 xWriter->startElement("lockscope", nullptr);
2250 switch (rLock.Scope)
2251 {
2252 case ucb::LockScope_EXCLUSIVE:
2253 xWriter->startElement("exclusive", nullptr);
2254 xWriter->endElement("exclusive");
2255 break;
2256 case ucb::LockScope_SHARED:
2257 xWriter->startElement("shared", nullptr);
2258 xWriter->endElement("shared");
2259 break;
2260 default:
2261 assert(false);
2262 }
2263 xWriter->endElement("lockscope");
2264 xWriter->startElement("locktype", nullptr);
2265 xWriter->startElement("write", nullptr);
2266 xWriter->endElement("write");
2267 xWriter->endElement("locktype");
2268 OUString owner;
2269 if ((rLock.Owner >>= owner) && !owner.isEmpty())
2270 {
2271 xWriter->startElement("owner", nullptr);
2272 xWriter->characters(owner);
2273 xWriter->endElement("owner");
2274 }
2275 xWriter->endElement("lockinfo");
2276 xWriter->endDocument();
2277
2278 uno::Reference<io::XInputStream> const xRequestInStream(
2279 io::SequenceInputStream::createStreamFromSequence(m_xContext,
2280 xSeqOutStream->getWrittenBytes()));
2281 assert(xRequestInStream.is());
2282
2283 ::std::unique_ptr<curl_slist, deleter_from_fn<curl_slist, curl_slist_free_all>> pList;
2284 pList.reset(curl_slist_append(pList.release(), "Content-Type: application/xml"));
2285 if (!pList)
2286 {
2287 throw uno::RuntimeException("curl_slist_append failed");
2288 }
2289 OString depth;
2290 switch (rLock.Depth)
2291 {
2292 case ucb::LockDepth_ZERO:
2293 depth = "Depth: 0";
2294 break;
2295 case ucb::LockDepth_ONE:
2296 depth = "Depth: 1";
2297 break;
2298 case ucb::LockDepth_INFINITY:
2299 depth = "Depth: infinity";
2300 break;
2301 default:
2302 assert(false);
2303 }
2304 pList.reset(curl_slist_append(pList.release(), depth.getStr()));
2305 if (!pList)
2306 {
2307 throw uno::RuntimeException("curl_slist_append failed");
2308 }
2309 OString timeout;
2310 switch (rLock.Timeout)
2311 {
2312 case -1:
2313 timeout = "Timeout: Infinite";
2314 break;
2315 case 0:
2316 timeout = "Timeout: Second-180";
2317 break;
2318 default:
2319 timeout = "Timeout: Second-" + OString::number(rLock.Timeout);
2320 assert(0 < rLock.Timeout);
2321 break;
2322 }
2323 pList.reset(curl_slist_append(pList.release(), timeout.getStr()));
2324 if (!pList)
2325 {
2326 throw uno::RuntimeException("curl_slist_append failed");
2327 }
2328
2329 auto const acquiredLocks
2330 = CurlProcessor::Lock(*this, uri, &rEnv, ::std::move(pList), &xRequestInStream);
2331
2332 for (auto const& rAcquiredLock : acquiredLocks)
2333 {
2334 g_Init.LockStore.addLock(uri.GetURI(), rAcquiredLock.first,
2335 rAcquiredLock.first.LockTokens[0], this, rAcquiredLock.second);
2336 SAL_INFO("ucb.ucp.webdav.curl", "created LOCK for " << rURIReference);
2337 }
2338}
2339
2340auto CurlProcessor::Unlock(CurlSession& rSession, CurlUri const& rURI,
2341 DAVRequestEnvironment const* const pEnv) -> void
2342{
2343 OUString const* const pToken(g_Init.LockStore.getLockTokenForURI(rURI.GetURI(), nullptr));
2344 if (!pToken)
2345 {
2346 SAL_WARN("ucb.ucp.webdav.curl", "attempt to unlock but not locked");
2347 throw DAVException(DAVException::DAV_NOT_LOCKED);
2348 }
2349 OString const utf8LockToken("Lock-Token: <"
2350 + OUStringToOString(*pToken, RTL_TEXTENCODING_ASCII_US) + ">");
2351 ::std::unique_ptr<curl_slist, deleter_from_fn<curl_slist, curl_slist_free_all>> pList(
2352 curl_slist_append(nullptr, utf8LockToken.getStr()));
2353 if (!pList)
2354 {
2355 throw uno::RuntimeException("curl_slist_append failed");
2356 }
2357
2358 ::std::vector<CurlOption> const options{ { CURLOPT_CUSTOMREQUEST, "UNLOCK",
2359 "CURLOPT_CUSTOMREQUEST" } };
2360
2361 CurlProcessor::ProcessRequest(rSession, rURI, "UNLOCK", options, pEnv, ::std::move(pList),
2362 nullptr, nullptr, nullptr);
2363}
2364
2365auto CurlSession::UNLOCK(OUString const& rURIReference, DAVRequestEnvironment const& rEnv) -> void
2366{
2367 SAL_INFO("ucb.ucp.webdav.curl", "UNLOCK: " << rURIReference);
2368
2369 // note: no m_Mutex lock needed here, only in CurlProcessor::Unlock()
2370
2371 CurlUri const uri(CurlProcessor::URIReferenceToURI(*this, rURIReference));
2372
2373 CurlProcessor::Unlock(*this, uri, &rEnv);
2374
2375 g_Init.LockStore.removeLock(uri.GetURI());
2376}
2377
2378auto CurlSession::NonInteractive_LOCK(OUString const& rURI, ::std::u16string_view const rLockToken,
2379 sal_Int32& o_rLastChanceToSendRefreshRequest,
2380 bool& o_rIsAuthFailed) -> bool
2381{
2382 SAL_INFO("ucb.ucp.webdav.curl", "NonInteractive_LOCK: " << rURI);
2383
2384 // note: no m_Mutex lock needed here, only in CurlProcessor::Lock()
2385
2386 try
2387 {
2388 CurlUri const uri(rURI);
2389 ::std::unique_ptr<curl_slist, deleter_from_fn<curl_slist, curl_slist_free_all>> pList(
2390 curl_slist_append(nullptr, "Timeout: Second-180"));
2391
2392 assert(!rLockToken.empty()); // LockStore is the caller
2393 OString const utf8If("If: (<" + OUStringToOString(rLockToken, RTL_TEXTENCODING_ASCII_US)
2394 + ">)");
2395 pList.reset(curl_slist_append(pList.release(), utf8If.getStr()));
2396 if (!pList)
2397 {
2398 throw uno::RuntimeException("curl_slist_append failed");
2399 }
2400
2401 auto const acquiredLocks
2402 = CurlProcessor::Lock(*this, uri, nullptr, ::std::move(pList), nullptr);
2403
2404 SAL_WARN_IF(1 < acquiredLocks.size(), "ucb.ucp.webdav.curl",
2405 "multiple locks acquired on refresh for " << rURI);
2406 if (!acquiredLocks.empty())
2407 {
2408 o_rLastChanceToSendRefreshRequest = acquiredLocks.begin()->second;
2409 }
2410 SAL_INFO("ucb.ucp.webdav.curl", "NonInteractive_LOCK succeeded on " << rURI);
2411 return true;
2412 }
2413 catch (DAVException const& rException)
2414 {
2415 SAL_INFO("ucb.ucp.webdav.curl", "NonInteractive_LOCK failed on " << rURI);
2416 switch (rException.getError())
2417 {
2418 case DAVException::DAV_HTTP_AUTH:
2419 case DAVException::DAV_HTTP_NOAUTH:
2420 o_rIsAuthFailed = true;
2421 break;
2422 default:
2423 break;
2424 }
2425 return false;
2426 }
2427 catch (...)
2428 {
2429 SAL_INFO("ucb.ucp.webdav.curl", "NonInteractive_LOCK failed on " << rURI);
2430 return false;
2431 }
2432}
2433
2434auto CurlSession::NonInteractive_UNLOCK(OUString const& rURI) -> void
2435{
2436 SAL_INFO("ucb.ucp.webdav.curl", "NonInteractive_UNLOCK: " << rURI);
2437
2438 // note: no m_Mutex lock needed here, only in CurlProcessor::Unlock()
2439
2440 try
2441 {
2442 CurlUri const uri(rURI);
2443
2444 CurlProcessor::Unlock(*this, uri, nullptr);
2445
2446 // the only caller is the dtor of the LockStore, don't call remove!
2447 SAL_INFO("ucb.ucp.webdav.curl", "NonInteractive_UNLOCK succeeded on " << rURI);
2448 }
2449 catch (...)
2450 {
2451 SAL_INFO("ucb.ucp.webdav.curl", "NonInteractive_UNLOCK failed on " << rURI);
2452 }
2453}
2454
2455} // namespace http_dav_ucp
2456
2457/* 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:307
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