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