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