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 <cppuhelper/implbase.hxx>
18 #include <comphelper/processfactory.hxx>
19 #include <comphelper/attributelist.hxx>
20 #include <comphelper/scopeguard.hxx>
21 #include <comphelper/string.hxx>
22 #include <cppuhelper/queryinterface.hxx>
23 #include <cppuhelper/supportsservice.hxx>
25 #include <o3tl/safeint.hxx>
26 #include <o3tl/string_view.hxx>
28 #include <officecfg/Inet.hxx>
29 #include <officecfg/Office/Security.hxx>
31 #include <com/sun/star/beans/NamedValue.hpp>
32 #include <com/sun/star/io/Pipe.hpp>
33 #include <com/sun/star/lang/XServiceInfo.hpp>
34 #include <com/sun/star/io/SequenceInputStream.hpp>
35 #include <com/sun/star/io/SequenceOutputStream.hpp>
36 #include <com/sun/star/xml/sax/Writer.hpp>
39 #include <sal/log.hxx>
40 #include <rtl/uri.hxx>
41 #include <rtl/strbuf.hxx>
42 #include <rtl/ustrbuf.hxx>
43 #include <systools/curlinit.hxx>
44 #include <tools/hostfilter.hxx>
45 #include <config_version.h>
52 using namespace ::com::sun::star
;
59 /// note: LockStore has its own mutex and calls CurlSession from its thread
60 /// so don't call LockStore with m_Mutex held to prevent deadlock.
61 ::http_dav_ucp::SerfLockStore LockStore
;
65 if (curl_global_init(CURL_GLOBAL_ALL
) != CURLE_OK
)
67 assert(!"curl_global_init failed");
70 // do not call curl_global_cleanup() - this is not the only client of curl
74 struct ResponseHeaders
76 ::std::vector
<::std::pair
<::std::vector
<OString
>, ::std::optional
<long>>> HeaderFields
;
78 ResponseHeaders(CURL
* const i_pCurl
)
84 auto GetResponseCode(ResponseHeaders
const& rHeaders
) -> ::std::optional
<long>
86 return (rHeaders
.HeaderFields
.empty()) ? ::std::optional
<long>{}
87 : rHeaders
.HeaderFields
.back().second
;
92 uno::Reference
<io::XOutputStream
> xOutStream
;
93 ResponseHeaders
const& rHeaders
;
94 DownloadTarget(uno::Reference
<io::XOutputStream
> i_xOutStream
,
95 ResponseHeaders
const& i_rHeaders
)
96 : xOutStream(std::move(i_xOutStream
))
97 , rHeaders(i_rHeaders
)
104 uno::Sequence
<sal_Int8
> const& rInData
;
106 UploadSource(uno::Sequence
<sal_Int8
> const& i_rInData
)
113 auto GetErrorString(CURLcode
const rc
, char const* const pErrorBuffer
= nullptr) -> OString
115 char const* const pMessage( // static fallback
116 (pErrorBuffer
&& pErrorBuffer
[0] != '\0') ? pErrorBuffer
: curl_easy_strerror(rc
));
117 return OString::Concat("(") + OString::number(sal_Int32(rc
)) + ") " + pMessage
;
120 auto GetErrorStringMulti(CURLMcode
const mc
) -> OString
122 return OString::Concat("(") + OString::number(sal_Int32(mc
)) + ") " + curl_multi_strerror(mc
);
125 /// represent an option to be passed to curl_easy_setopt()
128 CURLoption
const Option
;
137 void const* const pValue
;
138 long /*const*/ lValue
;
139 curl_off_t
/*const*/ cValue
;
141 char const* const pExceptionString
;
143 CurlOption(CURLoption
const i_Option
, void const* const i_Value
,
144 char const* const i_pExceptionString
)
148 , pExceptionString(i_pExceptionString
)
151 // Depending on platform, curl_off_t may be "long" or a larger type
152 // so cannot use overloading to distinguish these cases.
153 CurlOption(CURLoption
const i_Option
, curl_off_t
const i_Value
,
154 char const* const i_pExceptionString
, Type
const type
= Type::Long
)
157 , pExceptionString(i_pExceptionString
)
159 static_assert(sizeof(long) <= sizeof(curl_off_t
));
174 /// combined guard class to ensure things are released in correct order,
175 /// particularly in ProcessRequest() error handling
179 /// mutex *first* because m_oGuard requires it
180 ::std::unique_lock
<::std::mutex
> m_Lock
;
181 ::std::vector
<CurlOption
> const m_Options
;
182 ::http_dav_ucp::CurlUri
const& m_rURI
;
186 explicit Guard(::std::mutex
& rMutex
, ::std::vector
<CurlOption
> aOptions
,
187 ::http_dav_ucp::CurlUri
const& rURI
, CURL
* const pCurl
)
188 : m_Lock(rMutex
, ::std::defer_lock
)
189 , m_Options(std::move(aOptions
))
197 if (m_Lock
.owns_lock())
205 assert(!m_Lock
.owns_lock());
207 for (auto const& it
: m_Options
)
209 CURLcode
rc(CURL_LAST
); // warning C4701
210 if (it
.Tag
== CurlOption::Type::Pointer
)
212 rc
= curl_easy_setopt(m_pCurl
, it
.Option
, it
.pValue
);
214 else if (it
.Tag
== CurlOption::Type::Long
)
216 rc
= curl_easy_setopt(m_pCurl
, it
.Option
, it
.lValue
);
218 else if (it
.Tag
== CurlOption::Type::CurlOffT
)
220 rc
= curl_easy_setopt(m_pCurl
, it
.Option
, it
.cValue
);
226 if (it
.pExceptionString
!= nullptr)
230 SAL_WARN("ucb.ucp.webdav.curl",
231 "set " << it
.pExceptionString
<< " failed: " << GetErrorString(rc
));
232 throw ::http_dav_ucp::DAVException(
233 ::http_dav_ucp::DAVException::DAV_SESSION_CREATE
,
234 ::http_dav_ucp::ConnectionEndPointString(m_rURI
.GetHost(),
238 else // many of the options cannot fail
240 assert(rc
== CURLE_OK
);
246 assert(m_Lock
.owns_lock());
247 for (auto const& it
: m_Options
)
249 CURLcode
rc(CURL_LAST
); // warning C4701
250 if (it
.Tag
== CurlOption::Type::Pointer
)
252 rc
= curl_easy_setopt(m_pCurl
, it
.Option
, nullptr);
254 else if (it
.Tag
== CurlOption::Type::Long
)
256 rc
= curl_easy_setopt(m_pCurl
, it
.Option
, 0L);
258 else if (it
.Tag
== CurlOption::Type::CurlOffT
)
260 rc
= curl_easy_setopt(m_pCurl
, it
.Option
, curl_off_t(-1));
266 assert(rc
== CURLE_OK
);
275 namespace http_dav_ucp
277 // libcurl callbacks:
279 static int debug_callback(CURL
* handle
, curl_infotype type
, char* data
, size_t size
,
282 char const* pType(nullptr);
286 SAL_INFO("ucb.ucp.webdav.curl", "debug log: " << handle
<< ": " << data
);
288 case CURLINFO_HEADER_IN
:
289 SAL_INFO("ucb.ucp.webdav.curl",
290 "CURLINFO_HEADER_IN: " << handle
<< ": " << OString(data
, size
));
292 case CURLINFO_HEADER_OUT
:
294 // unlike IN, this is all headers in one call
295 OString
tmp(data
, size
);
296 sal_Int32
const start(tmp
.indexOf("Authorization: "));
299 sal_Int32
const end(tmp
.indexOf("\r\n", start
));
301 sal_Int32
const len(SAL_N_ELEMENTS("Authorization: ") - 1);
303 start
+ len
, end
- start
- len
,
304 Concat2View(OString::number(end
- start
- len
) + " bytes redacted"));
306 SAL_INFO("ucb.ucp.webdav.curl", "CURLINFO_HEADER_OUT: " << handle
<< ": " << tmp
);
309 case CURLINFO_DATA_IN
:
310 pType
= "CURLINFO_DATA_IN";
312 case CURLINFO_DATA_OUT
:
313 pType
= "CURLINFO_DATA_OUT";
315 case CURLINFO_SSL_DATA_IN
:
316 pType
= "CURLINFO_SSL_DATA_IN";
318 case CURLINFO_SSL_DATA_OUT
:
319 pType
= "CURLINFO_SSL_DATA_OUT";
322 SAL_WARN("ucb.ucp.webdav.curl", "unexpected debug log type");
325 SAL_INFO("ucb.ucp.webdav.curl", "debug log: " << handle
<< ": " << pType
<< " " << size
);
329 static size_t write_callback(char* const ptr
, size_t const size
, size_t const nmemb
,
330 void* const userdata
)
332 auto* const pTarget(static_cast<DownloadTarget
*>(userdata
));
333 if (!pTarget
) // looks like ~every request may have a response body
337 assert(size
== 1); // says the man page
339 assert(pTarget
->xOutStream
.is());
340 auto const oResponseCode(GetResponseCode(pTarget
->rHeaders
));
343 return 0; // that is an error
345 // always write, for exception handler in ProcessRequest()
346 // if (200 <= *oResponseCode && *oResponseCode < 300)
350 uno::Sequence
<sal_Int8
> const data(reinterpret_cast<sal_Int8
*>(ptr
), nmemb
);
351 pTarget
->xOutStream
->writeBytes(data
);
355 SAL_WARN("ucb.ucp.webdav.curl", "exception in write_callback");
359 // else: ignore the body? CurlSession will check the status eventually
363 static size_t read_callback(char* const buffer
, size_t const size
, size_t const nitems
,
364 void* const userdata
)
366 auto* const pSource(static_cast<UploadSource
*>(userdata
));
368 size_t const nBytes(size
* nitems
);
372 assert(pSource
->nPosition
<= o3tl::make_unsigned(pSource
->rInData
.getLength()));
373 nRet
= ::std::min
<size_t>(pSource
->rInData
.getLength() - pSource
->nPosition
, nBytes
);
374 ::std::memcpy(buffer
, pSource
->rInData
.getConstArray() + pSource
->nPosition
, nRet
);
375 pSource
->nPosition
+= nRet
;
379 SAL_WARN("ucb.ucp.webdav.curl", "exception in read_callback");
380 return CURL_READFUNC_ABORT
; // error
385 static size_t header_callback(char* const buffer
, size_t const size
, size_t const nitems
,
386 void* const userdata
)
388 auto* const pHeaders(static_cast<ResponseHeaders
*>(userdata
));
391 if (!pHeaders
) // TODO maybe not needed in every request? not sure
396 assert(size
== 1); // says the man page
402 // end of header, body follows...
403 if (pHeaders
->HeaderFields
.empty())
405 SAL_WARN("ucb.ucp.webdav.curl", "header_callback: empty header?");
408 // unfortunately there's no separate callback when the body begins,
409 // so have to manually retrieve the status code here
410 long statusCode(SC_NONE
);
411 auto rc
= curl_easy_getinfo(pHeaders
->pCurl
, CURLINFO_RESPONSE_CODE
, &statusCode
);
412 assert(rc
== CURLE_OK
);
414 // always put the current response code here - wasn't necessarily in this header
415 pHeaders
->HeaderFields
.back().second
.emplace(statusCode
);
417 else if (buffer
[0] == ' ' || buffer
[0] == '\t') // folded header field?
423 } while (i
== ' ' || i
== '\t');
424 if (pHeaders
->HeaderFields
.empty() || pHeaders
->HeaderFields
.back().second
425 || pHeaders
->HeaderFields
.back().first
.empty())
427 SAL_WARN("ucb.ucp.webdav.curl",
428 "header_callback: folded header field without start");
431 pHeaders
->HeaderFields
.back().first
.back()
432 += OString::Concat(" ") + ::std::string_view(&buffer
[i
], nitems
- i
);
436 if (pHeaders
->HeaderFields
.empty() || pHeaders
->HeaderFields
.back().second
)
438 pHeaders
->HeaderFields
.emplace_back();
440 pHeaders
->HeaderFields
.back().first
.emplace_back(OString(buffer
, nitems
));
445 SAL_WARN("ucb.ucp.webdav.curl", "exception in header_callback");
451 static auto ProcessHeaders(::std::vector
<OString
> const& rHeaders
) -> ::std::map
<OUString
, OUString
>
453 ::std::map
<OUString
, OUString
> ret
;
454 for (OString
const& rLine
: rHeaders
)
457 if (!rLine
.endsWith("\r\n", &line
))
459 SAL_WARN("ucb.ucp.webdav.curl", "invalid header field (no CRLF)");
462 if (line
.startsWith("HTTP/") // first line
463 || line
.isEmpty()) // last line
467 auto const nColon(line
.indexOf(':'));
471 SAL_WARN("ucb.ucp.webdav.curl", "invalid header field (no :)");
477 SAL_WARN("ucb.ucp.webdav.curl", "invalid header field (empty name)");
480 // case insensitive; must be ASCII
481 auto const name(::rtl::OStringToOUString(line
.copy(0, nColon
).toAsciiLowerCase(),
482 RTL_TEXTENCODING_ASCII_US
));
483 sal_Int32
nStart(nColon
+ 1);
484 while (nStart
< line
.getLength() && (line
[nStart
] == ' ' || line
[nStart
] == '\t'))
488 sal_Int32
nEnd(line
.getLength());
489 while (nStart
< nEnd
&& (line
[nEnd
- 1] == ' ' || line
[nEnd
- 1] == '\t'))
493 // RFC 7230 says that only ASCII works reliably anyway (neon also did this)
494 auto const value(::rtl::OStringToOUString(line
.subView(nStart
, nEnd
- nStart
),
495 RTL_TEXTENCODING_ASCII_US
));
496 auto const it(ret
.find(name
));
499 it
->second
= it
->second
+ "," + value
;
509 static auto ExtractRequestedHeaders(
510 ResponseHeaders
const& rHeaders
,
511 ::std::pair
<::std::vector
<OUString
> const&, DAVResource
&> const* const pRequestedHeaders
)
514 ::std::map
<OUString
, OUString
> const headerMap(
515 ProcessHeaders(rHeaders
.HeaderFields
.back().first
));
516 if (pRequestedHeaders
)
518 for (OUString
const& rHeader
: pRequestedHeaders
->first
)
520 auto const it(headerMap
.find(rHeader
.toAsciiLowerCase()));
521 if (it
!= headerMap
.end())
523 DAVPropertyValue value
;
524 value
.IsCaseSensitive
= false;
525 value
.Name
= it
->first
;
526 value
.Value
<<= it
->second
;
527 pRequestedHeaders
->second
.properties
.push_back(value
);
533 // this appears to be the only way to get the "realm" from libcurl
534 static auto ExtractRealm(ResponseHeaders
const& rHeaders
, char const* const pAuthHeaderName
)
535 -> ::std::optional
<OUString
>
537 ::std::map
<OUString
, OUString
> const headerMap(
538 ProcessHeaders(rHeaders
.HeaderFields
.back().first
));
539 auto const it(headerMap
.find(OUString::createFromAscii(pAuthHeaderName
).toAsciiLowerCase()));
540 if (it
== headerMap
.end())
542 SAL_WARN("ucb.ucp.webdav.curl", "cannot find auth header");
545 // It may be possible that the header contains multiple methods each with
546 // a different realm - extract only the first one bc the downstream API
547 // only supports one anyway.
549 auto i(it
->second
.toAsciiLowerCase().indexOf("realm="));
553 SAL_INFO("ucb.ucp.webdav.curl", "auth header has no realm");
556 // no whitespace allowed before or after =
557 i
+= ::std::strlen("realm=");
558 if (it
->second
.getLength() < i
+ 2 || it
->second
[i
] != '\"')
560 SAL_WARN("ucb.ucp.webdav.curl", "no realm value");
565 while (i
< it
->second
.getLength() && it
->second
[i
] != '\"')
567 if (it
->second
[i
] == '\\') // quoted-pair escape
570 if (it
->second
.getLength() <= i
)
572 SAL_WARN("ucb.ucp.webdav.curl", "unterminated quoted-pair");
576 buf
.append(it
->second
[i
]);
579 if (it
->second
.getLength() <= i
)
581 SAL_WARN("ucb.ucp.webdav.curl", "unterminated realm");
584 return buf
.makeStringAndClear();
587 CurlSession::CurlSession(uno::Reference
<uno::XComponentContext
> xContext
,
588 ::rtl::Reference
<DAVSessionFactory
> const& rpFactory
, OUString
const& rURI
,
589 uno::Sequence
<beans::NamedValue
> const& rFlags
,
590 ::ucbhelper::InternetProxyDecider
const& rProxyDecider
)
591 : DAVSession(rpFactory
)
592 , m_xContext(std::move(xContext
))
595 , m_Proxy(rProxyDecider
.getProxy(m_URI
.GetScheme(), m_URI
.GetHost(), m_URI
.GetPort()))
597 assert(m_URI
.GetScheme() == "http" || m_URI
.GetScheme() == "https");
598 m_pCurlMulti
.reset(curl_multi_init());
601 SAL_WARN("ucb.ucp.webdav.curl", "curl_multi_init failed");
602 throw DAVException(DAVException::DAV_SESSION_CREATE
,
603 ConnectionEndPointString(m_URI
.GetHost(), m_URI
.GetPort()));
605 m_pCurl
.reset(curl_easy_init());
608 SAL_WARN("ucb.ucp.webdav.curl", "curl_easy_init failed");
609 throw DAVException(DAVException::DAV_SESSION_CREATE
,
610 ConnectionEndPointString(m_URI
.GetHost(), m_URI
.GetPort()));
612 m_ErrorBuffer
[0] = '\0';
613 // this supposedly gives the highest quality error reporting
614 auto rc
= curl_easy_setopt(m_pCurl
.get(), CURLOPT_ERRORBUFFER
, m_ErrorBuffer
);
615 assert(rc
== CURLE_OK
);
617 // just for debugging...
618 rc
= curl_easy_setopt(m_pCurl
.get(), CURLOPT_DEBUGFUNCTION
, debug_callback
);
619 assert(rc
== CURLE_OK
);
621 rc
= curl_easy_setopt(m_pCurl
.get(), CURLOPT_VERBOSE
, 1L);
622 assert(rc
== CURLE_OK
);
623 // accept any encoding supported by libcurl
624 rc
= curl_easy_setopt(m_pCurl
.get(), CURLOPT_ACCEPT_ENCODING
, "");
627 SAL_WARN("ucb.ucp.webdav.curl", "CURLOPT_ACCEPT_ENCODING failed: " << GetErrorString(rc
));
628 throw DAVException(DAVException::DAV_SESSION_CREATE
,
629 ConnectionEndPointString(m_URI
.GetHost(), m_URI
.GetPort()));
631 auto const connectTimeout(officecfg::Inet::Settings::ConnectTimeout::get());
633 rc
= curl_easy_setopt(m_pCurl
.get(), CURLOPT_CONNECTTIMEOUT
,
634 ::std::max
<long>(2L, ::std::min
<long>(connectTimeout
, 180L)));
637 SAL_WARN("ucb.ucp.webdav.curl", "CURLOPT_CONNECTTIMEOUT failed: " << GetErrorString(rc
));
638 throw DAVException(DAVException::DAV_SESSION_CREATE
,
639 ConnectionEndPointString(m_URI
.GetHost(), m_URI
.GetPort()));
641 auto const readTimeout(officecfg::Inet::Settings::ReadTimeout::get());
642 m_nReadTimeout
= ::std::max
<int>(20, ::std::min
<long>(readTimeout
, 180)) * 1000;
643 // default is infinite
644 rc
= curl_easy_setopt(m_pCurl
.get(), CURLOPT_TIMEOUT
, 300L);
647 SAL_WARN("ucb.ucp.webdav.curl", "CURLOPT_TIMEOUT failed: " << GetErrorString(rc
));
648 throw DAVException(DAVException::DAV_SESSION_CREATE
,
649 ConnectionEndPointString(m_URI
.GetHost(), m_URI
.GetPort()));
651 rc
= curl_easy_setopt(m_pCurl
.get(), CURLOPT_WRITEFUNCTION
, &write_callback
);
652 assert(rc
== CURLE_OK
);
653 rc
= curl_easy_setopt(m_pCurl
.get(), CURLOPT_READFUNCTION
, &read_callback
);
654 assert(rc
== CURLE_OK
);
655 rc
= curl_easy_setopt(m_pCurl
.get(), CURLOPT_HEADERFUNCTION
, &header_callback
);
656 assert(rc
== CURLE_OK
);
657 ::InitCurl_easy(m_pCurl
.get());
658 if (officecfg::Office::Security::Net::AllowInsecureProtocols::get())
660 // tdf#149921 by default, with schannel (WNT) connection fails if revocation
661 // lists cannot be checked; try to limit the checking to when revocation
662 // lists can actually be retrieved (usually not the case for self-signed CA)
663 #if CURL_AT_LEAST_VERSION(7, 70, 0)
664 rc
= curl_easy_setopt(m_pCurl
.get(), CURLOPT_SSL_OPTIONS
, CURLSSLOPT_REVOKE_BEST_EFFORT
);
665 assert(rc
== CURLE_OK
);
666 rc
= curl_easy_setopt(m_pCurl
.get(), CURLOPT_PROXY_SSL_OPTIONS
,
667 CURLSSLOPT_REVOKE_BEST_EFFORT
);
668 assert(rc
== CURLE_OK
);
671 // set this initially, may be overwritten during authentication
672 rc
= curl_easy_setopt(m_pCurl
.get(), CURLOPT_HTTPAUTH
, CURLAUTH_ANY
);
673 assert(rc
== CURLE_OK
); // ANY is always available
674 // always set CURLOPT_PROXY to suppress proxy detection in libcurl
675 OString
const utf8Proxy(OUStringToOString(m_Proxy
, RTL_TEXTENCODING_UTF8
));
676 rc
= curl_easy_setopt(m_pCurl
.get(), CURLOPT_PROXY
, utf8Proxy
.getStr());
679 SAL_WARN("ucb.ucp.webdav.curl", "CURLOPT_PROXY failed: " << GetErrorString(rc
));
680 throw DAVException(DAVException::DAV_SESSION_CREATE
, m_Proxy
);
682 if (!m_Proxy
.isEmpty())
684 // set this initially, may be overwritten during authentication
685 rc
= curl_easy_setopt(m_pCurl
.get(), CURLOPT_PROXYAUTH
, CURLAUTH_ANY
);
686 assert(rc
== CURLE_OK
); // ANY is always available
688 auto const it(::std::find_if(m_Flags
.begin(), m_Flags
.end(),
689 [](auto const& rFlag
) { return rFlag
.Name
== "KeepAlive"; }));
690 if (it
!= m_Flags
.end() && it
->Value
.get
<bool>())
692 // neon would close the connection from ne_end_request(), this seems
693 // to be the equivalent and not CURLOPT_TCP_KEEPALIVE
694 rc
= curl_easy_setopt(m_pCurl
.get(), CURLOPT_FORBID_REUSE
, 1L);
695 assert(rc
== CURLE_OK
);
699 CurlSession::~CurlSession() {}
701 auto CurlSession::CanUse(OUString
const& rURI
, uno::Sequence
<beans::NamedValue
> const& rFlags
)
706 CurlUri
const uri(rURI
);
708 return m_URI
.GetScheme() == uri
.GetScheme() && m_URI
.GetHost() == uri
.GetHost()
709 && m_URI
.GetPort() == uri
.GetPort() && m_Flags
== rFlags
;
711 catch (DAVException
const&)
717 auto CurlSession::UsesProxy() -> bool
719 assert(m_URI
.GetScheme() == "http" || m_URI
.GetScheme() == "https");
720 return !m_Proxy
.isEmpty();
723 auto CurlSession::abort() -> void
725 // note: abort() was a no-op since OOo 3.2 and before that it crashed.
726 bool expected(false);
727 // it would be pointless to lock m_Mutex here as the other thread holds it
728 if (m_AbortFlag
.compare_exchange_strong(expected
, true))
730 // This function looks safe to call without m_Mutex as long as the
731 // m_pCurlMulti handle is not destroyed, and the caller must own a ref
732 // to this object which keeps it alive; it should cause poll to return.
733 curl_multi_wakeup(m_pCurlMulti
.get());
737 /// this is just a bunch of static member functions called from CurlSession
740 static auto URIReferenceToURI(CurlSession
& rSession
, std::u16string_view rURIReference
)
743 static auto ProcessRequestImpl(
744 CurlSession
& rSession
, CurlUri
const& rURI
, OUString
const& rMethod
,
745 curl_slist
* pRequestHeaderList
, uno::Reference
<io::XOutputStream
> const* pxOutStream
,
746 uno::Sequence
<sal_Int8
> const* pInData
,
747 ::std::pair
<::std::vector
<OUString
> const&, DAVResource
&> const* pRequestedHeaders
,
748 ResponseHeaders
& rHeaders
) -> void;
750 static auto ProcessRequest(
751 CurlSession
& rSession
, CurlUri
const& rURI
, OUString
const& rMethod
,
752 ::std::vector
<CurlOption
> const& rOptions
, DAVRequestEnvironment
const* pEnv
,
753 ::std::unique_ptr
<curl_slist
, deleter_from_fn
<curl_slist
, curl_slist_free_all
>>
755 uno::Reference
<io::XOutputStream
> const* pxOutStream
,
756 uno::Reference
<io::XInputStream
> const* pxInStream
,
757 ::std::pair
<::std::vector
<OUString
> const&, DAVResource
&> const* pRequestedHeaders
) -> void;
760 PropFind(CurlSession
& rSession
, CurlUri
const& rURI
, Depth depth
,
761 ::std::tuple
<::std::vector
<OUString
> const&, ::std::vector
<DAVResource
>* const,
762 ::std::vector
<ucb::Lock
>* const> const* o_pRequestedProperties
,
763 ::std::vector
<DAVResourceInfo
>* const o_pResourceInfos
,
764 DAVRequestEnvironment
const& rEnv
) -> void;
766 static auto MoveOrCopy(CurlSession
& rSession
, std::u16string_view rSourceURIReference
,
767 ::std::u16string_view rDestinationURI
, DAVRequestEnvironment
const& rEnv
,
768 bool isOverwrite
, char const* pMethod
) -> void;
770 static auto Lock(CurlSession
& rSession
, CurlUri
const& rURI
, DAVRequestEnvironment
const* pEnv
,
771 ::std::unique_ptr
<curl_slist
, deleter_from_fn
<curl_slist
, curl_slist_free_all
>>
773 uno::Reference
<io::XInputStream
> const* pxInStream
)
774 -> ::std::vector
<::std::pair
<ucb::Lock
, sal_Int32
>>;
776 static auto Unlock(CurlSession
& rSession
, CurlUri
const& rURI
,
777 DAVRequestEnvironment
const* pEnv
) -> void;
780 auto CurlProcessor::URIReferenceToURI(CurlSession
& rSession
, std::u16string_view rURIReference
)
783 // No need to acquire rSession.m_Mutex because accessed members are const.
784 if (rSession
.UsesProxy())
785 // very odd, but see DAVResourceAccess::getRequestURI() :-/
787 assert(o3tl::starts_with(rURIReference
, u
"http://")
788 || o3tl::starts_with(rURIReference
, u
"https://"));
789 return CurlUri(rURIReference
);
793 assert(o3tl::starts_with(rURIReference
, u
"/"));
794 return rSession
.m_URI
.CloneWithRelativeRefPathAbsolute(rURIReference
);
798 /// main function to initiate libcurl requests
799 auto CurlProcessor::ProcessRequestImpl(
800 CurlSession
& rSession
, CurlUri
const& rURI
, OUString
const& rMethod
,
801 curl_slist
* const pRequestHeaderList
,
802 uno::Reference
<io::XOutputStream
> const* const pxOutStream
,
803 uno::Sequence
<sal_Int8
> const* const pInData
,
804 ::std::pair
<::std::vector
<OUString
> const&, DAVResource
&> const* const pRequestedHeaders
,
805 ResponseHeaders
& rHeaders
) -> void
807 ::comphelper::ScopeGuard
const g([&]() {
808 auto rc
= curl_easy_setopt(rSession
.m_pCurl
.get(), CURLOPT_HEADERDATA
, nullptr);
809 assert(rc
== CURLE_OK
);
813 rc
= curl_easy_setopt(rSession
.m_pCurl
.get(), CURLOPT_WRITEDATA
, nullptr);
814 assert(rc
== CURLE_OK
);
818 rc
= curl_easy_setopt(rSession
.m_pCurl
.get(), CURLOPT_READDATA
, nullptr);
819 assert(rc
== CURLE_OK
);
820 rc
= curl_easy_setopt(rSession
.m_pCurl
.get(), CURLOPT_UPLOAD
, 0L);
821 assert(rc
== CURLE_OK
);
823 if (pRequestHeaderList
)
825 rc
= curl_easy_setopt(rSession
.m_pCurl
.get(), CURLOPT_HTTPHEADER
, nullptr);
826 assert(rc
== CURLE_OK
);
830 if (pRequestHeaderList
)
832 auto rc
= curl_easy_setopt(rSession
.m_pCurl
.get(), CURLOPT_HTTPHEADER
, pRequestHeaderList
);
833 assert(rc
== CURLE_OK
);
837 auto rc
= curl_easy_setopt(rSession
.m_pCurl
.get(), CURLOPT_CURLU
, rURI
.GetCURLU());
838 assert(rc
== CURLE_OK
); // can't fail since 7.63.0
840 rc
= curl_easy_setopt(rSession
.m_pCurl
.get(), CURLOPT_HEADERDATA
, &rHeaders
);
841 assert(rc
== CURLE_OK
);
842 ::std::optional
<DownloadTarget
> oDownloadTarget
;
845 oDownloadTarget
.emplace(*pxOutStream
, rHeaders
);
846 rc
= curl_easy_setopt(rSession
.m_pCurl
.get(), CURLOPT_WRITEDATA
, &*oDownloadTarget
);
847 assert(rc
== CURLE_OK
);
849 ::std::optional
<UploadSource
> oUploadSource
;
852 oUploadSource
.emplace(*pInData
);
853 rc
= curl_easy_setopt(rSession
.m_pCurl
.get(), CURLOPT_READDATA
, &*oUploadSource
);
854 assert(rc
== CURLE_OK
);
856 rSession
.m_ErrorBuffer
[0] = '\0';
858 // note: easy handle must be added for *every* transfer!
859 // otherwise it gets stuck in MSTATE_MSGSENT forever after 1st transfer
860 auto mc
= curl_multi_add_handle(rSession
.m_pCurlMulti
.get(), rSession
.m_pCurl
.get());
863 SAL_WARN("ucb.ucp.webdav.curl",
864 "curl_multi_add_handle failed: " << GetErrorStringMulti(mc
));
866 DAVException::DAV_SESSION_CREATE
,
867 ConnectionEndPointString(rSession
.m_URI
.GetHost(), rSession
.m_URI
.GetPort()));
869 ::comphelper::ScopeGuard
const gg([&]() {
870 mc
= curl_multi_remove_handle(rSession
.m_pCurlMulti
.get(), rSession
.m_pCurl
.get());
873 SAL_WARN("ucb.ucp.webdav.curl",
874 "curl_multi_remove_handle failed: " << GetErrorStringMulti(mc
));
878 // this is where libcurl actually does something
879 rc
= CURL_LAST
; // clear current value
883 mc
= curl_multi_perform(rSession
.m_pCurlMulti
.get(), &nRunning
);
886 SAL_WARN("ucb.ucp.webdav.curl",
887 "curl_multi_perform failed: " << GetErrorStringMulti(mc
));
889 DAVException::DAV_HTTP_CONNECT
,
890 ConnectionEndPointString(rSession
.m_URI
.GetHost(), rSession
.m_URI
.GetPort()));
893 { // short request like HEAD on loopback could be done in first call
897 mc
= curl_multi_poll(rSession
.m_pCurlMulti
.get(), nullptr, 0, rSession
.m_nReadTimeout
,
901 SAL_WARN("ucb.ucp.webdav.curl", "curl_multi_poll failed: " << GetErrorStringMulti(mc
));
903 DAVException::DAV_HTTP_CONNECT
,
904 ConnectionEndPointString(rSession
.m_URI
.GetHost(), rSession
.m_URI
.GetPort()));
906 if (rSession
.m_AbortFlag
.load())
907 { // flag was set by abort() -> not sure what exception to throw?
908 throw DAVException(DAVException::DAV_HTTP_ERROR
, "abort() was called", 0);
910 } while (nRunning
!= 0);
911 // there should be exactly 1 CURLMsg now, but the interface is
912 // extensible so future libcurl versions could yield additional things
915 CURLMsg
const* const pMsg
= curl_multi_info_read(rSession
.m_pCurlMulti
.get(), &nRunning
);
916 if (pMsg
&& pMsg
->msg
== CURLMSG_DONE
)
918 assert(pMsg
->easy_handle
== rSession
.m_pCurl
.get());
919 rc
= pMsg
->data
.result
;
923 SAL_WARN("ucb.ucp.webdav.curl", "curl_multi_info_read unexpected result");
925 } while (nRunning
!= 0);
927 // error handling part 1: libcurl errors
930 // TODO: is there any value in extracting CURLINFO_OS_ERRNO
931 SAL_WARN("ucb.ucp.webdav.curl",
932 "curl_easy_perform failed: " << GetErrorString(rc
, rSession
.m_ErrorBuffer
));
935 case CURLE_UNSUPPORTED_PROTOCOL
:
936 throw DAVException(DAVException::DAV_UNSUPPORTED
);
937 case CURLE_COULDNT_RESOLVE_PROXY
:
938 throw DAVException(DAVException::DAV_HTTP_LOOKUP
, rSession
.m_Proxy
);
939 case CURLE_COULDNT_RESOLVE_HOST
:
941 DAVException::DAV_HTTP_LOOKUP
,
942 ConnectionEndPointString(rSession
.m_URI
.GetHost(), rSession
.m_URI
.GetPort()));
943 case CURLE_COULDNT_CONNECT
:
944 case CURLE_SSL_CONNECT_ERROR
:
945 case CURLE_SSL_CERTPROBLEM
:
946 case CURLE_SSL_CIPHER
:
947 case CURLE_PEER_FAILED_VERIFICATION
:
948 case CURLE_SSL_ISSUER_ERROR
:
949 case CURLE_SSL_PINNEDPUBKEYNOTMATCH
:
950 case CURLE_SSL_INVALIDCERTSTATUS
:
951 case CURLE_FAILED_INIT
:
952 #if CURL_AT_LEAST_VERSION(7, 69, 0)
953 case CURLE_QUIC_CONNECT_ERROR
:
956 DAVException::DAV_HTTP_CONNECT
,
957 ConnectionEndPointString(rSession
.m_URI
.GetHost(), rSession
.m_URI
.GetPort()));
958 case CURLE_REMOTE_ACCESS_DENIED
:
959 case CURLE_LOGIN_DENIED
:
960 case CURLE_AUTH_ERROR
:
962 DAVException::DAV_HTTP_AUTH
, // probably?
963 ConnectionEndPointString(rSession
.m_URI
.GetHost(), rSession
.m_URI
.GetPort()));
964 case CURLE_WRITE_ERROR
:
965 case CURLE_READ_ERROR
: // error returned from our callbacks
966 case CURLE_OUT_OF_MEMORY
:
967 case CURLE_ABORTED_BY_CALLBACK
:
968 case CURLE_BAD_FUNCTION_ARGUMENT
:
969 case CURLE_SEND_ERROR
:
970 case CURLE_RECV_ERROR
:
971 case CURLE_SSL_CACERT_BADFILE
:
972 case CURLE_SSL_CRL_BADFILE
:
973 case CURLE_RECURSIVE_API_CALL
:
975 DAVException::DAV_HTTP_FAILED
,
976 ConnectionEndPointString(rSession
.m_URI
.GetHost(), rSession
.m_URI
.GetPort()));
977 case CURLE_OPERATION_TIMEDOUT
:
979 DAVException::DAV_HTTP_TIMEOUT
,
980 ConnectionEndPointString(rSession
.m_URI
.GetHost(), rSession
.m_URI
.GetPort()));
981 default: // lots of generic errors
982 throw DAVException(DAVException::DAV_HTTP_ERROR
, "", 0);
985 // error handling part 2: HTTP status codes
986 long statusCode(SC_NONE
);
987 rc
= curl_easy_getinfo(rSession
.m_pCurl
.get(), CURLINFO_RESPONSE_CODE
, &statusCode
);
988 assert(rc
== CURLE_OK
);
989 assert(statusCode
!= SC_NONE
); // ??? should be error returned from perform?
990 SAL_INFO("ucb.ucp.webdav.curl", "HTTP status code: " << statusCode
);
991 if (statusCode
< 300)
993 // neon did this regardless of status or even error, which seems odd
994 ExtractRequestedHeaders(rHeaders
, pRequestedHeaders
);
998 // create message containing the HTTP method and response status line
999 OUString
statusLine("\n" + rMethod
+ "\n=>\n");
1000 if (!rHeaders
.HeaderFields
.empty() && !rHeaders
.HeaderFields
.back().first
.empty()
1001 && rHeaders
.HeaderFields
.back().first
.front().startsWith("HTTP"))
1003 statusLine
+= ::rtl::OStringToOUString(
1004 ::o3tl::trim(rHeaders
.HeaderFields
.back().first
.front()),
1005 RTL_TEXTENCODING_ASCII_US
);
1009 case SC_REQUEST_TIMEOUT
:
1012 DAVException::DAV_HTTP_TIMEOUT
,
1013 ConnectionEndPointString(rSession
.m_URI
.GetHost(), rSession
.m_URI
.GetPort()));
1016 case SC_MOVED_PERMANENTLY
:
1017 case SC_MOVED_TEMPORARILY
:
1019 case SC_TEMPORARY_REDIRECT
:
1021 // could also use CURLOPT_FOLLOWLOCATION but apparently the
1022 // upper layer wants to know about redirects?
1023 char* pRedirectURL(nullptr);
1024 rc
= curl_easy_getinfo(rSession
.m_pCurl
.get(), CURLINFO_REDIRECT_URL
,
1026 assert(rc
== CURLE_OK
);
1029 // Sharepoint 2016 workaround: contains unencoded U+0020
1030 OUString
const redirectURL(::rtl::Uri::encode(
1032 ? OUString(pRedirectURL
, strlen(pRedirectURL
), RTL_TEXTENCODING_UTF8
)
1034 rtl_UriCharClassUric
, rtl_UriEncodeKeepEscapes
, RTL_TEXTENCODING_UTF8
));
1036 throw DAVException(DAVException::DAV_HTTP_REDIRECT
, redirectURL
);
1041 throw DAVException(DAVException::DAV_HTTP_ERROR
, statusLine
, statusCode
);
1047 (*pxOutStream
)->closeOutput(); // signal EOF
1051 static auto TryRemoveExpiredLockToken(CurlSession
& rSession
, CurlUri
const& rURI
,
1052 DAVRequestEnvironment
const* const pEnv
) -> bool
1056 // caller was a NonInteractive_*LOCK function anyway, its caller is LockStore
1059 OUString
const* const pToken(g_Init
.LockStore
.getLockTokenForURI(rURI
.GetURI(), nullptr));
1066 // determine validity of existing lock via lockdiscovery request
1067 ::std::vector
<OUString
> const propertyNames
{ DAVProperties::LOCKDISCOVERY
};
1068 ::std::vector
<ucb::Lock
> locks
;
1069 ::std::tuple
<::std::vector
<OUString
> const&, ::std::vector
<DAVResource
>* const,
1070 ::std::vector
<ucb::Lock
>* const> const args(propertyNames
, nullptr, &locks
);
1072 CurlProcessor::PropFind(rSession
, rURI
, DAVZERO
, &args
, nullptr, *pEnv
);
1074 // https://datatracker.ietf.org/doc/html/rfc4918#section-15.8
1075 // The response MAY not contain tokens, but hopefully it
1076 // will if client is properly authenticated.
1077 if (::std::any_of(locks
.begin(), locks
.end(), [pToken
](ucb::Lock
const& rLock
) {
1078 return ::std::any_of(
1079 rLock
.LockTokens
.begin(), rLock
.LockTokens
.end(),
1080 [pToken
](OUString
const& rToken
) { return *pToken
== rToken
; });
1083 return false; // still have the lock
1086 SAL_INFO("ucb.ucp.webdav.curl",
1087 "lock token expired, removing: " << rURI
.GetURI() << " " << *pToken
);
1088 g_Init
.LockStore
.removeLock(rURI
.GetURI());
1091 catch (DAVException
const&)
1093 return false; // ignore, the caller already has a better exception
1097 auto CurlProcessor::ProcessRequest(
1098 CurlSession
& rSession
, CurlUri
const& rURI
, OUString
const& rMethod
,
1099 ::std::vector
<CurlOption
> const& rOptions
, DAVRequestEnvironment
const* const pEnv
,
1100 ::std::unique_ptr
<curl_slist
, deleter_from_fn
<curl_slist
, curl_slist_free_all
>>
1102 uno::Reference
<io::XOutputStream
> const* const pxOutStream
,
1103 uno::Reference
<io::XInputStream
> const* const pxInStream
,
1104 ::std::pair
<::std::vector
<OUString
> const&, DAVResource
&> const* const pRequestedHeaders
)
1107 if (HostFilter::isForbidden(rURI
.GetHost()))
1109 SAL_WARN("ucb.ucp.webdav.curl", "Access denied to host: " << rURI
.GetHost());
1110 throw uno::RuntimeException("access to host denied");
1114 { // add custom request headers passed by caller
1115 for (auto const& rHeader
: pEnv
->m_aRequestHeaders
)
1117 OString
const utf8Header(
1118 OUStringToOString(rHeader
.first
, RTL_TEXTENCODING_ASCII_US
) + ": "
1119 + OUStringToOString(rHeader
.second
, RTL_TEXTENCODING_ASCII_US
));
1120 pRequestHeaderList
.reset(
1121 curl_slist_append(pRequestHeaderList
.release(), utf8Header
.getStr()));
1122 if (!pRequestHeaderList
)
1124 throw uno::RuntimeException("curl_slist_append failed");
1129 uno::Sequence
<sal_Int8
> data
;
1132 uno::Reference
<io::XSeekable
> const xSeekable(*pxInStream
, uno::UNO_QUERY
);
1135 auto const len(xSeekable
->getLength() - xSeekable
->getPosition());
1136 if ((**pxInStream
).readBytes(data
, len
) != len
)
1138 throw uno::RuntimeException("short readBytes");
1143 ::std::vector
<uno::Sequence
<sal_Int8
>> bufs
;
1147 bufs
.emplace_back();
1148 isDone
= (**pxInStream
).readSomeBytes(bufs
.back(), 65536) == 0;
1151 for (auto const& rBuf
: bufs
)
1153 if (o3tl::checked_add(nSize
, rBuf
.getLength(), nSize
))
1155 throw std::bad_alloc(); // too large for Sequence
1158 data
.realloc(nSize
);
1160 for (auto const& rBuf
: bufs
)
1162 ::std::memcpy(data
.getArray() + nCopied
, rBuf
.getConstArray(), rBuf
.getLength());
1163 nCopied
+= rBuf
.getLength(); // can't overflow
1168 // Clear flag before transfer starts; only a transfer started before
1169 // calling abort() will be aborted, not one started later.
1170 rSession
.m_AbortFlag
.store(false);
1172 Guard
guard(rSession
.m_Mutex
, rOptions
, rURI
, rSession
.m_pCurl
.get());
1174 // authentication data may be in the URI, or requested via XInteractionHandler
1179 decltype(CURLAUTH_ANY
) AuthMask
; ///< allowed auth methods
1180 Auth(OUString aUserName
, OUString aPassword
, decltype(CURLAUTH_ANY
) const & rAuthMask
)
1181 : UserName(std::move(aUserName
))
1182 , PassWord(std::move(aPassword
))
1183 , AuthMask(rAuthMask
)
1187 ::std::optional
<Auth
> oAuth
;
1188 ::std::optional
<Auth
> oAuthProxy
;
1189 if (pEnv
&& !rSession
.m_isAuthenticatedProxy
&& !rSession
.m_Proxy
.isEmpty())
1193 // the hope is that this must be a URI
1194 CurlUri
const uri(rSession
.m_Proxy
);
1195 if (!uri
.GetUser().isEmpty() || !uri
.GetPassword().isEmpty())
1197 oAuthProxy
.emplace(uri
.GetUser(), uri
.GetPassword(), CURLAUTH_ANY
);
1200 catch (DAVException
&)
1202 // ignore any parsing failure here
1205 decltype(CURLAUTH_ANY
) const authSystem(CURLAUTH_NEGOTIATE
| CURLAUTH_NTLM
| CURLAUTH_NTLM_WB
);
1206 if (pRequestedHeaders
|| (pEnv
&& !rSession
.m_isAuthenticated
))
1208 // m_aRequestURI *may* be a path or *may* be URI - wtf
1209 // TODO: why is there this m_aRequestURI and also rURIReference argument?
1210 // ... only caller is DAVResourceAccess - always identical except MOVE/COPY
1211 // which doesn't work if it's just a URI reference so let's just use
1212 // rURIReference via rURI instead
1214 CurlUri
const uri(pEnv
->m_aRequestURI
);
1216 // note: due to parsing bug pwd didn't work in previous webdav ucps
1217 if (pEnv
&& !rSession
.m_isAuthenticated
1218 && (!rURI
.GetUser().isEmpty() || !rURI
.GetPassword().isEmpty()))
1220 oAuth
.emplace(rURI
.GetUser(), rURI
.GetPassword(), CURLAUTH_ANY
);
1222 if (pRequestedHeaders
)
1224 // note: Previously this would be the rURIReference directly but
1225 // that ends up in CurlUri anyway and curl is unhappy.
1226 // But it looks like all consumers of this .uri are interested
1227 // only in the path, so it shouldn't make a difference to give
1228 // the entire URI when the caller extracts the path anyway.
1229 pRequestedHeaders
->second
.uri
= rURI
.GetURI();
1230 pRequestedHeaders
->second
.properties
.clear();
1233 bool isRetry(false);
1234 bool isFallbackHTTP10(false);
1235 int nAuthRequests(0);
1236 int nAuthRequestsProxy(0);
1238 // libcurl does not have an authentication callback so handle auth
1239 // related status codes and requesting credentials via this loop
1244 // re-check m_isAuthenticated flags every time, could have been set
1245 // by re-entrant call
1246 if (oAuth
&& !rSession
.m_isAuthenticated
)
1248 OString
const utf8UserName(OUStringToOString(oAuth
->UserName
, RTL_TEXTENCODING_UTF8
));
1250 = curl_easy_setopt(rSession
.m_pCurl
.get(), CURLOPT_USERNAME
, utf8UserName
.getStr());
1253 SAL_WARN("ucb.ucp.webdav.curl", "CURLOPT_USERNAME failed: " << GetErrorString(rc
));
1254 throw DAVException(DAVException::DAV_INVALID_ARG
);
1256 OString
const utf8PassWord(OUStringToOString(oAuth
->PassWord
, RTL_TEXTENCODING_UTF8
));
1257 rc
= curl_easy_setopt(rSession
.m_pCurl
.get(), CURLOPT_PASSWORD
, utf8PassWord
.getStr());
1260 SAL_WARN("ucb.ucp.webdav.curl", "CURLOPT_PASSWORD failed: " << GetErrorString(rc
));
1261 throw DAVException(DAVException::DAV_INVALID_ARG
);
1263 rc
= curl_easy_setopt(rSession
.m_pCurl
.get(), CURLOPT_HTTPAUTH
, oAuth
->AuthMask
);
1266 == CURLE_OK
); // it shouldn't be possible to reduce auth to 0 via the authSystem masks
1269 if (oAuthProxy
&& !rSession
.m_isAuthenticatedProxy
)
1271 OString
const utf8UserName(
1272 OUStringToOString(oAuthProxy
->UserName
, RTL_TEXTENCODING_UTF8
));
1273 auto rc
= curl_easy_setopt(rSession
.m_pCurl
.get(), CURLOPT_PROXYUSERNAME
,
1274 utf8UserName
.getStr());
1277 SAL_WARN("ucb.ucp.webdav.curl",
1278 "CURLOPT_PROXYUSERNAME failed: " << GetErrorString(rc
));
1279 throw DAVException(DAVException::DAV_INVALID_ARG
);
1281 OString
const utf8PassWord(
1282 OUStringToOString(oAuthProxy
->PassWord
, RTL_TEXTENCODING_UTF8
));
1283 rc
= curl_easy_setopt(rSession
.m_pCurl
.get(), CURLOPT_PROXYPASSWORD
,
1284 utf8PassWord
.getStr());
1287 SAL_WARN("ucb.ucp.webdav.curl",
1288 "CURLOPT_PROXYPASSWORD failed: " << GetErrorString(rc
));
1289 throw DAVException(DAVException::DAV_INVALID_ARG
);
1291 rc
= curl_easy_setopt(rSession
.m_pCurl
.get(), CURLOPT_PROXYAUTH
, oAuthProxy
->AuthMask
);
1294 == CURLE_OK
); // it shouldn't be possible to reduce auth to 0 via the authSystem masks
1297 ResponseHeaders
headers(rSession
.m_pCurl
.get());
1298 // always pass a stream for debug logging, buffer the result body
1299 uno::Reference
<io::XSequenceOutputStream
> const xSeqOutStream(
1300 io::SequenceOutputStream::create(rSession
.m_xContext
));
1301 uno::Reference
<io::XOutputStream
> const xTempOutStream(xSeqOutStream
);
1302 assert(xTempOutStream
.is());
1306 ProcessRequestImpl(rSession
, rURI
, rMethod
, pRequestHeaderList
.get(), &xTempOutStream
,
1307 pxInStream
? &data
: nullptr, pRequestedHeaders
, headers
);
1309 { // only copy to result stream if transfer was successful
1310 (*pxOutStream
)->writeBytes(xSeqOutStream
->getWrittenBytes());
1311 (*pxOutStream
)->closeOutput(); // signal EOF
1314 catch (DAVException
const& rException
)
1316 // log start of request body if there was any
1317 auto const bytes(xSeqOutStream
->getWrittenBytes());
1318 auto const len(::std::min
<sal_Int32
>(bytes
.getLength(), 10000));
1319 SAL_INFO("ucb.ucp.webdav.curl",
1320 "DAVException; (first) " << len
<< " bytes of data received:");
1323 OStringBuffer
buf(len
);
1324 for (sal_Int32 i
= 0; i
< len
; ++i
)
1326 if (bytes
[i
] < 0x20) // also if negative
1328 static char const hexDigit
[16] = { '0', '1', '2', '3', '4', '5', '6', '7',
1329 '8', '9', 'A', 'B', 'C', 'D', 'E', 'F' };
1330 buf
.append(OString::Concat("\\x")
1331 + OStringChar(hexDigit
[static_cast<sal_uInt8
>(bytes
[i
]) >> 4])
1332 + OStringChar(hexDigit
[bytes
[i
] & 0x0F]));
1336 buf
.append(static_cast<char>(bytes
[i
]));
1339 SAL_INFO("ucb.ucp.webdav.curl", buf
.makeStringAndClear());
1342 // error handling part 3: special HTTP status codes
1343 // that require unlocking m_Mutex to handle
1344 if (rException
.getError() == DAVException::DAV_HTTP_ERROR
)
1346 auto const statusCode(rException
.getStatus());
1351 guard
.Release(); // release m_Mutex before accessing LockStore
1352 if (g_Init
.LockStore
.getLockTokenForURI(rURI
.GetURI(), nullptr))
1354 throw DAVException(DAVException::DAV_LOCKED_SELF
);
1356 else // locked by third party
1358 throw DAVException(DAVException::DAV_LOCKED
);
1362 case SC_PRECONDITION_FAILED
:
1363 case SC_BAD_REQUEST
:
1365 guard
.Release(); // release m_Mutex before accessing LockStore
1366 // Not obvious but apparently these codes may indicate
1367 // the expiration of a lock.
1368 // Initiate a new request *outside* ProcessRequestImpl
1369 // *after* rGuard.unlock() to avoid messing up m_pCurl state.
1370 if (TryRemoveExpiredLockToken(rSession
, rURI
, pEnv
))
1372 throw DAVException(DAVException::DAV_LOCK_EXPIRED
);
1376 case SC_UNAUTHORIZED
:
1377 case SC_PROXY_AUTHENTICATION_REQUIRED
:
1379 (statusCode
!= SC_PROXY_AUTHENTICATION_REQUIRED
1380 ? rSession
.m_isAuthenticated
1381 : rSession
.m_isAuthenticatedProxy
)
1382 = false; // any auth data in m_pCurl is invalid
1383 auto& rnAuthRequests(statusCode
== SC_UNAUTHORIZED
? nAuthRequests
1384 : nAuthRequestsProxy
);
1385 if (rnAuthRequests
== 10)
1387 SAL_INFO("ucb.ucp.webdav.curl", "aborting authentication after "
1388 << rnAuthRequests
<< " attempts");
1390 else if (pEnv
&& pEnv
->m_xAuthListener
)
1392 ::std::optional
<OUString
> const oRealm(ExtractRealm(
1393 headers
, statusCode
== SC_UNAUTHORIZED
? "WWW-Authenticate"
1394 : "Proxy-Authenticate"));
1396 ::std::optional
<Auth
>& roAuth(
1397 statusCode
== SC_UNAUTHORIZED
? oAuth
: oAuthProxy
);
1398 OUString
userName(roAuth
? roAuth
->UserName
: OUString());
1399 OUString
passWord(roAuth
? roAuth
->PassWord
: OUString());
1401 auto const rc
= curl_easy_getinfo(rSession
.m_pCurl
.get(),
1402 statusCode
== SC_UNAUTHORIZED
1403 ? CURLINFO_HTTPAUTH_AVAIL
1404 : CURLINFO_PROXYAUTH_AVAIL
,
1406 assert(rc
== CURLE_OK
);
1408 // only allow SystemCredentials once - the
1409 // PasswordContainer may have stored it in the
1410 // Config (TrySystemCredentialsFirst or
1411 // AuthenticateUsingSystemCredentials) and then it
1412 // will always force its use no matter how hopeless
1413 bool const isSystemCredSupported((authAvail
& authSystem
) != 0
1414 && rnAuthRequests
== 0);
1417 // Ask user via XInteractionHandler.
1418 // Warning: This likely runs an event loop which may
1419 // end up calling back into this instance, so all
1420 // changes to m_pCurl must be undone now and
1421 // restored after return.
1424 auto const ret
= pEnv
->m_xAuthListener
->authenticate(
1425 oRealm
? *oRealm
: "",
1426 statusCode
== SC_UNAUTHORIZED
? rSession
.m_URI
.GetHost()
1428 userName
, passWord
, isSystemCredSupported
);
1432 // NTLM may either use a password requested
1433 // from the user, or from the system via SSPI
1434 // so i guess it should not be disabled here
1435 // regardless of the state of the system auth
1436 // checkbox, particularly since SSPI is only
1437 // available on WNT.
1438 // Additionally, "Negotiate" has a "legacy"
1439 // mode that is actually just NTLM according to
1440 // https://curl.se/rfc/ntlm.html#ntlmHttpAuthentication
1441 // so there's nothing in authSystem that can be
1443 roAuth
.emplace(userName
, passWord
,
1444 ((userName
.isEmpty() && passWord
.isEmpty())
1445 ? (authAvail
& authSystem
)
1448 // Acquire is only necessary in case of success.
1450 break; // break out of switch
1454 SAL_INFO("ucb.ucp.webdav.curl", "no auth credentials provided");
1455 throw DAVException(DAVException::DAV_HTTP_NOAUTH
,
1456 ConnectionEndPointString(rSession
.m_URI
.GetHost(),
1457 rSession
.m_URI
.GetPort()));
1462 else if (rException
.getError() == DAVException::DAV_UNSUPPORTED
)
1464 // tdf#152493 libcurl can't handle "Transfer-Encoding: chunked"
1465 // in HTTP/1.1 100 Continue response.
1466 // workaround: if HTTP/1.1 didn't work, try HTTP/1.0
1467 // (but fallback only once - to prevent infinite loop)
1468 if (isFallbackHTTP10
)
1470 throw DAVException(DAVException::DAV_HTTP_ERROR
);
1472 isFallbackHTTP10
= true;
1473 // note: this is not reset - future requests to this URI use it!
1474 auto rc
= curl_easy_setopt(rSession
.m_pCurl
.get(), CURLOPT_HTTP_VERSION
,
1475 CURL_HTTP_VERSION_1_0
);
1478 throw DAVException(DAVException::DAV_HTTP_ERROR
);
1480 SAL_INFO("ucb.ucp.webdav.curl", "attempting fallback to HTTP/1.0");
1485 throw; // everything else: re-throw
1492 // assume this worked, leave auth data as stored in m_pCurl
1493 rSession
.m_isAuthenticated
= true;
1497 // assume this worked, leave auth data as stored in m_pCurl
1498 rSession
.m_isAuthenticatedProxy
= true;
1502 auto CurlSession::OPTIONS(OUString
const& rURIReference
,
1504 DAVOptions
& rOptions
, DAVRequestEnvironment
const& rEnv
) -> void
1506 SAL_INFO("ucb.ucp.webdav.curl", "OPTIONS: " << rURIReference
);
1510 CurlUri
const uri(CurlProcessor::URIReferenceToURI(*this, rURIReference
));
1512 ::std::vector
<OUString
> const headerNames
{ "allow", "dav" };
1514 ::std::pair
<::std::vector
<OUString
> const&, DAVResource
&> const headers(headerNames
, result
);
1516 ::std::vector
<CurlOption
> const options
{ { CURLOPT_CUSTOMREQUEST
, "OPTIONS",
1517 "CURLOPT_CUSTOMREQUEST" } };
1519 CurlProcessor::ProcessRequest(*this, uri
, "OPTIONS", options
, &rEnv
, nullptr, nullptr, nullptr,
1522 for (auto const& it
: result
.properties
)
1526 SAL_INFO("ucb.ucp.webdav.curl", "OPTIONS: header: " << it
.Name
<< ": " << value
);
1527 if (it
.Name
.equalsIgnoreAsciiCase("allow"))
1529 rOptions
.setAllowedMethods(value
);
1531 else if (it
.Name
.equalsIgnoreAsciiCase("dav"))
1533 // see <http://tools.ietf.org/html/rfc4918#section-10.1>,
1534 // <http://tools.ietf.org/html/rfc4918#section-18>,
1535 // and <http://tools.ietf.org/html/rfc7230#section-3.2>
1536 // we detect the class (1, 2 and 3), other elements (token, URL)
1537 // are not used for now
1538 auto const list(::comphelper::string::convertCommaSeparated(value
));
1539 for (OUString
const& v
: list
)
1543 rOptions
.setClass1();
1547 rOptions
.setClass2();
1551 rOptions
.setClass3();
1556 if (rOptions
.isClass2() || rOptions
.isClass3())
1558 if (g_Init
.LockStore
.getLockTokenForURI(uri
.GetURI(), nullptr))
1560 rOptions
.setLocked();
1565 auto CurlProcessor::PropFind(
1566 CurlSession
& rSession
, CurlUri
const& rURI
, Depth
const nDepth
,
1567 ::std::tuple
<::std::vector
<OUString
> const&, ::std::vector
<DAVResource
>* const,
1568 ::std::vector
<ucb::Lock
>* const> const* const o_pRequestedProperties
,
1569 ::std::vector
<DAVResourceInfo
>* const o_pResourceInfos
, DAVRequestEnvironment
const& rEnv
)
1572 assert((o_pRequestedProperties
!= nullptr) != (o_pResourceInfos
!= nullptr));
1573 assert((o_pRequestedProperties
== nullptr)
1574 || (::std::get
<1>(*o_pRequestedProperties
) != nullptr)
1575 != (::std::get
<2>(*o_pRequestedProperties
) != nullptr));
1577 ::std::unique_ptr
<curl_slist
, deleter_from_fn
<curl_slist
, curl_slist_free_all
>> pList
;
1578 pList
.reset(curl_slist_append(pList
.release(), "Content-Type: application/xml"));
1581 throw uno::RuntimeException("curl_slist_append failed");
1587 depth
= "Depth: 0"_ostr
;
1590 depth
= "Depth: 1"_ostr
;
1593 depth
= "Depth: infinity"_ostr
;
1598 pList
.reset(curl_slist_append(pList
.release(), depth
.getStr()));
1601 throw uno::RuntimeException("curl_slist_append failed");
1604 uno::Reference
<io::XSequenceOutputStream
> const xSeqOutStream(
1605 io::SequenceOutputStream::create(rSession
.m_xContext
));
1606 uno::Reference
<io::XOutputStream
> const xRequestOutStream(xSeqOutStream
);
1607 assert(xRequestOutStream
.is());
1609 uno::Reference
<xml::sax::XWriter
> const xWriter(xml::sax::Writer::create(rSession
.m_xContext
));
1610 xWriter
->setOutputStream(xRequestOutStream
);
1611 xWriter
->startDocument();
1612 rtl::Reference
<::comphelper::AttributeList
> const pAttrList(new ::comphelper::AttributeList
);
1613 pAttrList
->AddAttribute("xmlns", "DAV:");
1614 xWriter
->startElement("propfind", pAttrList
);
1615 if (o_pResourceInfos
)
1617 xWriter
->startElement("propname", nullptr);
1618 xWriter
->endElement("propname");
1622 if (::std::get
<0>(*o_pRequestedProperties
).empty())
1624 xWriter
->startElement("allprop", nullptr);
1625 xWriter
->endElement("allprop");
1629 xWriter
->startElement("prop", nullptr);
1630 for (OUString
const& rName
: ::std::get
<0>(*o_pRequestedProperties
))
1633 DAVProperties::createSerfPropName(rName
, name
);
1635 pAttrList
->AddAttribute("xmlns", OUString::createFromAscii(name
.nspace
));
1636 xWriter
->startElement(OUString::createFromAscii(name
.name
), pAttrList
);
1637 xWriter
->endElement(OUString::createFromAscii(name
.name
));
1639 xWriter
->endElement("prop");
1642 xWriter
->endElement("propfind");
1643 xWriter
->endDocument();
1645 uno::Reference
<io::XInputStream
> const xRequestInStream(
1646 io::SequenceInputStream::createStreamFromSequence(rSession
.m_xContext
,
1647 xSeqOutStream
->getWrittenBytes()));
1648 assert(xRequestInStream
.is());
1650 curl_off_t
const len(xSeqOutStream
->getWrittenBytes().getLength());
1652 ::std::vector
<CurlOption
> const options
{
1653 { CURLOPT_UPLOAD
, 1L, nullptr },
1654 { CURLOPT_CUSTOMREQUEST
, "PROPFIND", "CURLOPT_CUSTOMREQUEST" },
1655 // note: Sharepoint cannot handle "Transfer-Encoding: chunked"
1656 { CURLOPT_INFILESIZE_LARGE
, len
, nullptr, CurlOption::Type::CurlOffT
}
1659 // stream for response
1660 uno::Reference
<io::XInputStream
> const xResponseInStream(io::Pipe::create(rSession
.m_xContext
));
1661 uno::Reference
<io::XOutputStream
> const xResponseOutStream(xResponseInStream
, uno::UNO_QUERY
);
1662 assert(xResponseInStream
.is());
1663 assert(xResponseOutStream
.is());
1665 CurlProcessor::ProcessRequest(rSession
, rURI
, "PROPFIND", options
, &rEnv
, ::std::move(pList
),
1666 &xResponseOutStream
, &xRequestInStream
, nullptr);
1668 if (o_pResourceInfos
)
1670 *o_pResourceInfos
= parseWebDAVPropNameResponse(xResponseInStream
);
1674 if (::std::get
<1>(*o_pRequestedProperties
) != nullptr)
1676 *::std::get
<1>(*o_pRequestedProperties
)
1677 = parseWebDAVPropFindResponse(xResponseInStream
);
1678 for (DAVResource
& it
: *::std::get
<1>(*o_pRequestedProperties
))
1680 // caller will give these uris to CurlUri so can't be relative
1681 if (it
.uri
.startsWith("/"))
1685 it
.uri
= rSession
.m_URI
.CloneWithRelativeRefPathAbsolute(it
.uri
).GetURI();
1687 catch (DAVException
const&)
1689 SAL_INFO("ucb.ucp.webdav.curl",
1690 "PROPFIND: exception parsing uri " << it
.uri
);
1697 *::std::get
<2>(*o_pRequestedProperties
) = parseWebDAVLockResponse(xResponseInStream
);
1703 auto CurlSession::PROPFIND(OUString
const& rURIReference
, Depth
const depth
,
1704 ::std::vector
<OUString
> const& rPropertyNames
,
1705 ::std::vector
<DAVResource
>& o_rResources
,
1706 DAVRequestEnvironment
const& rEnv
) -> void
1708 SAL_INFO("ucb.ucp.webdav.curl", "PROPFIND: " << rURIReference
<< " " << depth
);
1710 CurlUri
const uri(CurlProcessor::URIReferenceToURI(*this, rURIReference
));
1712 ::std::tuple
<::std::vector
<OUString
> const&, ::std::vector
<DAVResource
>* const,
1713 ::std::vector
<ucb::Lock
>* const> const args(rPropertyNames
, &o_rResources
,
1715 return CurlProcessor::PropFind(*this, uri
, depth
, &args
, nullptr, rEnv
);
1718 auto CurlSession::PROPFIND(OUString
const& rURIReference
, Depth
const depth
,
1719 ::std::vector
<DAVResourceInfo
>& o_rResourceInfos
,
1720 DAVRequestEnvironment
const& rEnv
) -> void
1722 SAL_INFO("ucb.ucp.webdav.curl", "PROPFIND: " << rURIReference
<< " " << depth
);
1724 CurlUri
const uri(CurlProcessor::URIReferenceToURI(*this, rURIReference
));
1726 return CurlProcessor::PropFind(*this, uri
, depth
, nullptr, &o_rResourceInfos
, rEnv
);
1729 auto CurlSession::PROPPATCH(OUString
const& rURIReference
,
1730 ::std::vector
<ProppatchValue
> const& rValues
,
1731 DAVRequestEnvironment
const& rEnv
) -> void
1733 SAL_INFO("ucb.ucp.webdav.curl", "PROPPATCH: " << rURIReference
);
1735 CurlUri
const uri(CurlProcessor::URIReferenceToURI(*this, rURIReference
));
1737 // TODO: either set CURLOPT_INFILESIZE_LARGE or chunked?
1738 ::std::unique_ptr
<curl_slist
, deleter_from_fn
<curl_slist
, curl_slist_free_all
>> pList
;
1739 pList
.reset(curl_slist_append(pList
.release(), "Content-Type: application/xml"));
1742 throw uno::RuntimeException("curl_slist_append failed");
1745 // generate XML document for PROPPATCH
1746 uno::Reference
<io::XSequenceOutputStream
> const xSeqOutStream(
1747 io::SequenceOutputStream::create(m_xContext
));
1748 uno::Reference
<io::XOutputStream
> const xRequestOutStream(xSeqOutStream
);
1749 assert(xRequestOutStream
.is());
1750 uno::Reference
<xml::sax::XWriter
> const xWriter(xml::sax::Writer::create(m_xContext
));
1751 xWriter
->setOutputStream(xRequestOutStream
);
1752 xWriter
->startDocument();
1753 rtl::Reference
<::comphelper::AttributeList
> const pAttrList(new ::comphelper::AttributeList
);
1754 pAttrList
->AddAttribute("xmlns", "DAV:");
1755 xWriter
->startElement("propertyupdate", pAttrList
);
1756 for (ProppatchValue
const& rPropValue
: rValues
)
1758 assert(rPropValue
.operation
== PROPSET
|| rPropValue
.operation
== PROPREMOVE
);
1759 OUString
const operation((rPropValue
.operation
== PROPSET
) ? OUString("set")
1760 : OUString("remove"));
1761 xWriter
->startElement(operation
, nullptr);
1762 xWriter
->startElement("prop", nullptr);
1764 DAVProperties::createSerfPropName(rPropValue
.name
, name
);
1766 pAttrList
->AddAttribute("xmlns", OUString::createFromAscii(name
.nspace
));
1767 xWriter
->startElement(OUString::createFromAscii(name
.name
), pAttrList
);
1768 if (rPropValue
.operation
== PROPSET
)
1770 if (DAVProperties::isUCBDeadProperty(name
))
1772 ::std::optional
<::std::pair
<OUString
, OUString
>> const oProp(
1773 UCBDeadPropertyValue::toXML(rPropValue
.value
));
1776 xWriter
->startElement("ucbprop", nullptr);
1777 xWriter
->startElement("type", nullptr);
1778 xWriter
->characters(oProp
->first
);
1779 xWriter
->endElement("type");
1780 xWriter
->startElement("value", nullptr);
1781 xWriter
->characters(oProp
->second
);
1782 xWriter
->endElement("value");
1783 xWriter
->endElement("ucbprop");
1789 rPropValue
.value
>>= value
;
1790 xWriter
->characters(value
);
1793 xWriter
->endElement(OUString::createFromAscii(name
.name
));
1794 xWriter
->endElement("prop");
1795 xWriter
->endElement(operation
);
1797 xWriter
->endElement("propertyupdate");
1798 xWriter
->endDocument();
1800 uno::Reference
<io::XInputStream
> const xRequestInStream(
1801 io::SequenceInputStream::createStreamFromSequence(m_xContext
,
1802 xSeqOutStream
->getWrittenBytes()));
1803 assert(xRequestInStream
.is());
1805 curl_off_t
const len(xSeqOutStream
->getWrittenBytes().getLength());
1807 ::std::vector
<CurlOption
> const options
{
1808 { CURLOPT_UPLOAD
, 1L, nullptr },
1809 { CURLOPT_CUSTOMREQUEST
, "PROPPATCH", "CURLOPT_CUSTOMREQUEST" },
1810 // note: Sharepoint cannot handle "Transfer-Encoding: chunked"
1811 { CURLOPT_INFILESIZE_LARGE
, len
, nullptr, CurlOption::Type::CurlOffT
}
1814 CurlProcessor::ProcessRequest(*this, uri
, "PROPPATCH", options
, &rEnv
, ::std::move(pList
),
1815 nullptr, &xRequestInStream
, nullptr);
1818 auto CurlSession::HEAD(OUString
const& rURIReference
, ::std::vector
<OUString
> const& rHeaderNames
,
1819 DAVResource
& io_rResource
, DAVRequestEnvironment
const& rEnv
) -> void
1821 SAL_INFO("ucb.ucp.webdav.curl", "HEAD: " << rURIReference
);
1823 CurlUri
const uri(CurlProcessor::URIReferenceToURI(*this, rURIReference
));
1825 ::std::vector
<CurlOption
> const options
{
1826 // NOBODY will prevent logging the response body in ProcessRequest()
1827 // exception, but omitting it here results in a long timeout until the
1828 // server closes the connection, which is worse
1829 { CURLOPT_NOBODY
, 1L, nullptr },
1830 { CURLOPT_CUSTOMREQUEST
, "HEAD", "CURLOPT_CUSTOMREQUEST" }
1833 ::std::pair
<::std::vector
<OUString
> const&, DAVResource
&> const headers(rHeaderNames
,
1836 CurlProcessor::ProcessRequest(*this, uri
, "HEAD", options
, &rEnv
, nullptr, nullptr, nullptr,
1840 auto CurlSession::GET(OUString
const& rURIReference
, DAVRequestEnvironment
const& rEnv
)
1841 -> uno::Reference
<io::XInputStream
>
1843 SAL_INFO("ucb.ucp.webdav.curl", "GET: " << rURIReference
);
1845 CurlUri
const uri(CurlProcessor::URIReferenceToURI(*this, rURIReference
));
1847 // could use either com.sun.star.io.Pipe or com.sun.star.io.SequenceInputStream?
1848 // Pipe can just write into its XOuputStream, which is simpler.
1849 // Both resize exponentially, so performance should be fine.
1850 // However, Pipe doesn't implement XSeekable, which is required by filters.
1852 uno::Reference
<io::XSequenceOutputStream
> const xSeqOutStream(
1853 io::SequenceOutputStream::create(m_xContext
));
1854 uno::Reference
<io::XOutputStream
> const xResponseOutStream(xSeqOutStream
);
1855 assert(xResponseOutStream
.is());
1857 ::std::vector
<CurlOption
> const options
{ { CURLOPT_HTTPGET
, 1L, nullptr } };
1859 CurlProcessor::ProcessRequest(*this, uri
, "GET", options
, &rEnv
, nullptr, &xResponseOutStream
,
1862 uno::Reference
<io::XInputStream
> const xResponseInStream(
1863 io::SequenceInputStream::createStreamFromSequence(m_xContext
,
1864 xSeqOutStream
->getWrittenBytes()));
1865 assert(xResponseInStream
.is());
1867 return xResponseInStream
;
1870 auto CurlSession::GET(OUString
const& rURIReference
, uno::Reference
<io::XOutputStream
>& rxOutStream
,
1871 DAVRequestEnvironment
const& rEnv
) -> void
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 CurlProcessor::ProcessRequest(*this, uri
, "GET", options
, &rEnv
, nullptr, &rxOutStream
, nullptr,
1883 auto CurlSession::GET(OUString
const& rURIReference
, ::std::vector
<OUString
> const& rHeaderNames
,
1884 DAVResource
& io_rResource
, DAVRequestEnvironment
const& rEnv
)
1885 -> uno::Reference
<io::XInputStream
>
1887 SAL_INFO("ucb.ucp.webdav.curl", "GET: " << rURIReference
);
1889 CurlUri
const uri(CurlProcessor::URIReferenceToURI(*this, rURIReference
));
1891 ::std::vector
<CurlOption
> const options
{ { CURLOPT_HTTPGET
, 1L, nullptr } };
1893 uno::Reference
<io::XSequenceOutputStream
> const xSeqOutStream(
1894 io::SequenceOutputStream::create(m_xContext
));
1895 uno::Reference
<io::XOutputStream
> const xResponseOutStream(xSeqOutStream
);
1896 assert(xResponseOutStream
.is());
1898 ::std::pair
<::std::vector
<OUString
> const&, DAVResource
&> const headers(rHeaderNames
,
1901 CurlProcessor::ProcessRequest(*this, uri
, "GET", options
, &rEnv
, nullptr, &xResponseOutStream
,
1904 uno::Reference
<io::XInputStream
> const xResponseInStream(
1905 io::SequenceInputStream::createStreamFromSequence(m_xContext
,
1906 xSeqOutStream
->getWrittenBytes()));
1907 assert(xResponseInStream
.is());
1909 return xResponseInStream
;
1912 auto CurlSession::GET(OUString
const& rURIReference
, uno::Reference
<io::XOutputStream
>& rxOutStream
,
1913 ::std::vector
<OUString
> const& rHeaderNames
, DAVResource
& io_rResource
,
1914 DAVRequestEnvironment
const& rEnv
) -> void
1916 SAL_INFO("ucb.ucp.webdav.curl", "GET: " << rURIReference
);
1918 CurlUri
const uri(CurlProcessor::URIReferenceToURI(*this, rURIReference
));
1920 ::std::vector
<CurlOption
> const options
{ { CURLOPT_HTTPGET
, 1L, nullptr } };
1922 ::std::pair
<::std::vector
<OUString
> const&, DAVResource
&> const headers(rHeaderNames
,
1925 CurlProcessor::ProcessRequest(*this, uri
, "GET", options
, &rEnv
, nullptr, &rxOutStream
, nullptr,
1929 auto CurlSession::PUT(OUString
const& rURIReference
,
1930 uno::Reference
<io::XInputStream
> const& rxInStream
,
1931 DAVRequestEnvironment
const& rEnv
) -> void
1933 SAL_INFO("ucb.ucp.webdav.curl", "PUT: " << rURIReference
);
1935 CurlUri
const uri(CurlProcessor::URIReferenceToURI(*this, rURIReference
));
1937 // NextCloud silently fails with chunked encoding
1938 uno::Reference
<io::XSeekable
> const xSeekable(rxInStream
, uno::UNO_QUERY
);
1939 if (!xSeekable
.is())
1941 throw uno::RuntimeException("TODO: not seekable");
1943 curl_off_t
const len(xSeekable
->getLength() - xSeekable
->getPosition());
1945 ::std::unique_ptr
<curl_slist
, deleter_from_fn
<curl_slist
, curl_slist_free_all
>> pList
;
1946 OUString
const* const pToken(g_Init
.LockStore
.getLockTokenForURI(uri
.GetURI(), nullptr));
1949 OString
const utf8If("If: "
1950 // disabled as Sharepoint 2013 workaround, it accepts only
1951 // "No-Tag-List", see fed2984281a85a5a2f308841ec810f218c75f2ab
1953 "<" + OUStringToOString(rURIReference
, RTL_TEXTENCODING_ASCII_US
)
1957 + OUStringToOString(*pToken
, RTL_TEXTENCODING_ASCII_US
) + ">)");
1958 pList
.reset(curl_slist_append(pList
.release(), utf8If
.getStr()));
1961 throw uno::RuntimeException("curl_slist_append failed");
1965 // lock m_Mutex after accessing global LockStore to avoid deadlock
1967 // note: Nextcloud 20 cannot handle "Transfer-Encoding: chunked"
1968 ::std::vector
<CurlOption
> const options
{
1969 { CURLOPT_UPLOAD
, 1L, nullptr }, // libcurl won't upload without setting this
1970 { CURLOPT_INFILESIZE_LARGE
, len
, nullptr, CurlOption::Type::CurlOffT
}
1973 CurlProcessor::ProcessRequest(*this, uri
, "PUT", options
, &rEnv
, ::std::move(pList
), nullptr,
1974 &rxInStream
, nullptr);
1977 auto CurlSession::POST(OUString
const& rURIReference
, OUString
const& rContentType
,
1978 OUString
const& rReferer
, uno::Reference
<io::XInputStream
> const& rxInStream
,
1979 DAVRequestEnvironment
const& rEnv
) -> uno::Reference
<io::XInputStream
>
1981 SAL_INFO("ucb.ucp.webdav.curl", "POST: " << rURIReference
);
1983 CurlUri
const uri(CurlProcessor::URIReferenceToURI(*this, rURIReference
));
1985 // TODO: either set CURLOPT_POSTFIELDSIZE_LARGE or chunked?
1986 ::std::unique_ptr
<curl_slist
, deleter_from_fn
<curl_slist
, curl_slist_free_all
>> pList(
1987 curl_slist_append(nullptr, "Transfer-Encoding: chunked"));
1990 throw uno::RuntimeException("curl_slist_append failed");
1992 OString
const utf8ContentType("Content-Type: "
1993 + OUStringToOString(rContentType
, RTL_TEXTENCODING_ASCII_US
));
1994 pList
.reset(curl_slist_append(pList
.release(), utf8ContentType
.getStr()));
1997 throw uno::RuntimeException("curl_slist_append failed");
1999 OString
const utf8Referer("Referer: " + OUStringToOString(rReferer
, RTL_TEXTENCODING_ASCII_US
));
2000 pList
.reset(curl_slist_append(pList
.release(), utf8Referer
.getStr()));
2003 throw uno::RuntimeException("curl_slist_append failed");
2006 ::std::vector
<CurlOption
> const options
{ { CURLOPT_POST
, 1L, nullptr } };
2008 uno::Reference
<io::XSequenceOutputStream
> const xSeqOutStream(
2009 io::SequenceOutputStream::create(m_xContext
));
2010 uno::Reference
<io::XOutputStream
> const xResponseOutStream(xSeqOutStream
);
2011 assert(xResponseOutStream
.is());
2013 CurlProcessor::ProcessRequest(*this, uri
, "POST", options
, &rEnv
, ::std::move(pList
),
2014 &xResponseOutStream
, &rxInStream
, nullptr);
2016 uno::Reference
<io::XInputStream
> const xResponseInStream(
2017 io::SequenceInputStream::createStreamFromSequence(m_xContext
,
2018 xSeqOutStream
->getWrittenBytes()));
2019 assert(xResponseInStream
.is());
2021 return xResponseInStream
;
2024 auto CurlSession::POST(OUString
const& rURIReference
, OUString
const& rContentType
,
2025 OUString
const& rReferer
, uno::Reference
<io::XInputStream
> const& rxInStream
,
2026 uno::Reference
<io::XOutputStream
>& rxOutStream
,
2027 DAVRequestEnvironment
const& rEnv
) -> void
2029 SAL_INFO("ucb.ucp.webdav.curl", "POST: " << rURIReference
);
2031 CurlUri
const uri(CurlProcessor::URIReferenceToURI(*this, rURIReference
));
2033 // TODO: either set CURLOPT_POSTFIELDSIZE_LARGE or chunked?
2034 ::std::unique_ptr
<curl_slist
, deleter_from_fn
<curl_slist
, curl_slist_free_all
>> pList(
2035 curl_slist_append(nullptr, "Transfer-Encoding: chunked"));
2038 throw uno::RuntimeException("curl_slist_append failed");
2040 OString
const utf8ContentType("Content-Type: "
2041 + OUStringToOString(rContentType
, RTL_TEXTENCODING_ASCII_US
));
2042 pList
.reset(curl_slist_append(pList
.release(), utf8ContentType
.getStr()));
2045 throw uno::RuntimeException("curl_slist_append failed");
2047 OString
const utf8Referer("Referer: " + OUStringToOString(rReferer
, RTL_TEXTENCODING_ASCII_US
));
2048 pList
.reset(curl_slist_append(pList
.release(), utf8Referer
.getStr()));
2051 throw uno::RuntimeException("curl_slist_append failed");
2054 ::std::vector
<CurlOption
> const options
{ { CURLOPT_POST
, 1L, nullptr } };
2056 CurlProcessor::ProcessRequest(*this, uri
, "POST", options
, &rEnv
, ::std::move(pList
),
2057 &rxOutStream
, &rxInStream
, nullptr);
2060 auto CurlSession::MKCOL(OUString
const& rURIReference
, DAVRequestEnvironment
const& rEnv
) -> void
2062 SAL_INFO("ucb.ucp.webdav.curl", "MKCOL: " << rURIReference
);
2064 CurlUri
const uri(CurlProcessor::URIReferenceToURI(*this, rURIReference
));
2066 ::std::vector
<CurlOption
> const options
{ { CURLOPT_CUSTOMREQUEST
, "MKCOL",
2067 "CURLOPT_CUSTOMREQUEST" } };
2069 CurlProcessor::ProcessRequest(*this, uri
, "MKCOL", options
, &rEnv
, nullptr, nullptr, nullptr,
2073 auto CurlProcessor::MoveOrCopy(CurlSession
& rSession
, std::u16string_view rSourceURIReference
,
2074 ::std::u16string_view
const rDestinationURI
,
2075 DAVRequestEnvironment
const& rEnv
, bool const isOverwrite
,
2076 char const* const pMethod
) -> void
2078 CurlUri
const uriSource(CurlProcessor::URIReferenceToURI(rSession
, rSourceURIReference
));
2080 OString
const utf8Destination("Destination: "
2081 + OUStringToOString(rDestinationURI
, RTL_TEXTENCODING_ASCII_US
));
2082 ::std::unique_ptr
<curl_slist
, deleter_from_fn
<curl_slist
, curl_slist_free_all
>> pList(
2083 curl_slist_append(nullptr, utf8Destination
.getStr()));
2086 throw uno::RuntimeException("curl_slist_append failed");
2088 OString
const utf8Overwrite(OString::Concat("Overwrite: ") + (isOverwrite
? "T" : "F"));
2089 pList
.reset(curl_slist_append(pList
.release(), utf8Overwrite
.getStr()));
2092 throw uno::RuntimeException("curl_slist_append failed");
2095 ::std::vector
<CurlOption
> const options
{ { CURLOPT_CUSTOMREQUEST
, pMethod
,
2096 "CURLOPT_CUSTOMREQUEST" } };
2098 CurlProcessor::ProcessRequest(rSession
, uriSource
, OUString::createFromAscii(pMethod
), options
,
2099 &rEnv
, ::std::move(pList
), nullptr, nullptr, nullptr);
2102 auto CurlSession::COPY(OUString
const& rSourceURIReference
, OUString
const& rDestinationURI
,
2103 DAVRequestEnvironment
const& rEnv
, bool const isOverwrite
) -> void
2105 SAL_INFO("ucb.ucp.webdav.curl", "COPY: " << rSourceURIReference
);
2107 return CurlProcessor::MoveOrCopy(*this, rSourceURIReference
, rDestinationURI
, rEnv
, isOverwrite
,
2111 auto CurlSession::MOVE(OUString
const& rSourceURIReference
, OUString
const& rDestinationURI
,
2112 DAVRequestEnvironment
const& rEnv
, bool const isOverwrite
) -> void
2114 SAL_INFO("ucb.ucp.webdav.curl", "MOVE: " << rSourceURIReference
);
2116 return CurlProcessor::MoveOrCopy(*this, rSourceURIReference
, rDestinationURI
, rEnv
, isOverwrite
,
2120 auto CurlSession::DESTROY(OUString
const& rURIReference
, DAVRequestEnvironment
const& rEnv
) -> void
2122 SAL_INFO("ucb.ucp.webdav.curl", "DESTROY: " << rURIReference
);
2124 CurlUri
const uri(CurlProcessor::URIReferenceToURI(*this, rURIReference
));
2126 ::std::vector
<CurlOption
> const options
{ { CURLOPT_CUSTOMREQUEST
, "DELETE",
2127 "CURLOPT_CUSTOMREQUEST" } };
2129 CurlProcessor::ProcessRequest(*this, uri
, "DESTROY", options
, &rEnv
, nullptr, nullptr, nullptr,
2133 auto CurlProcessor::Lock(
2134 CurlSession
& rSession
, CurlUri
const& rURI
, DAVRequestEnvironment
const* const pEnv
,
2135 ::std::unique_ptr
<curl_slist
, deleter_from_fn
<curl_slist
, curl_slist_free_all
>>
2137 uno::Reference
<io::XInputStream
> const* const pxRequestInStream
)
2138 -> ::std::vector
<::std::pair
<ucb::Lock
, sal_Int32
>>
2141 if (pxRequestInStream
)
2143 uno::Reference
<io::XSeekable
> const xSeekable(*pxRequestInStream
, uno::UNO_QUERY
);
2144 assert(xSeekable
.is());
2145 len
= xSeekable
->getLength();
2148 ::std::vector
<CurlOption
> const options
{
2149 { CURLOPT_UPLOAD
, 1L, nullptr },
2150 { CURLOPT_CUSTOMREQUEST
, "LOCK", "CURLOPT_CUSTOMREQUEST" },
2151 // note: Sharepoint cannot handle "Transfer-Encoding: chunked"
2152 { CURLOPT_INFILESIZE_LARGE
, len
, nullptr, CurlOption::Type::CurlOffT
}
2155 // stream for response
2156 uno::Reference
<io::XInputStream
> const xResponseInStream(io::Pipe::create(rSession
.m_xContext
));
2157 uno::Reference
<io::XOutputStream
> const xResponseOutStream(xResponseInStream
, uno::UNO_QUERY
);
2158 assert(xResponseInStream
.is());
2159 assert(xResponseOutStream
.is());
2161 TimeValue startTime
;
2162 osl_getSystemTime(&startTime
);
2164 CurlProcessor::ProcessRequest(rSession
, rURI
, "LOCK", options
, pEnv
,
2165 ::std::move(pRequestHeaderList
), &xResponseOutStream
,
2166 pxRequestInStream
, nullptr);
2168 ::std::vector
<ucb::Lock
> const acquiredLocks(parseWebDAVLockResponse(xResponseInStream
));
2169 SAL_WARN_IF(acquiredLocks
.empty(), "ucb.ucp.webdav.curl",
2170 "could not get LOCK for " << rURI
.GetURI());
2173 osl_getSystemTime(&endTime
);
2174 auto const elapsedSeconds(endTime
.Seconds
- startTime
.Seconds
);
2176 // determine expiration time (seconds from endTime) for each acquired lock
2177 ::std::vector
<::std::pair
<ucb::Lock
, sal_Int32
>> ret
;
2178 ret
.reserve(acquiredLocks
.size());
2179 for (auto const& rLock
: acquiredLocks
)
2181 sal_Int32 lockExpirationTimeSeconds
;
2182 if (rLock
.Timeout
== -1)
2184 lockExpirationTimeSeconds
= -1;
2186 else if (rLock
.Timeout
<= elapsedSeconds
)
2188 SAL_WARN("ucb.ucp.webdav.curl",
2189 "LOCK timeout already expired when receiving LOCK response for "
2191 lockExpirationTimeSeconds
= 0;
2195 lockExpirationTimeSeconds
= startTime
.Seconds
+ rLock
.Timeout
;
2197 ret
.emplace_back(rLock
, lockExpirationTimeSeconds
);
2203 auto CurlSession::LOCK(OUString
const& rURIReference
, ucb::Lock
/*const*/& rLock
,
2204 DAVRequestEnvironment
const& rEnv
) -> void
2206 SAL_INFO("ucb.ucp.webdav.curl", "LOCK: " << rURIReference
);
2208 CurlUri
const uri(CurlProcessor::URIReferenceToURI(*this, rURIReference
));
2210 if (g_Init
.LockStore
.getLockTokenForURI(uri
.GetURI(), &rLock
))
2212 // already have a lock that covers the requirement
2213 // TODO: maybe use DAV:lockdiscovery to ensure it's valid
2217 // note: no m_Mutex lock needed here, only in CurlProcessor::Lock()
2219 // generate XML document for acquiring new LOCK
2220 uno::Reference
<io::XSequenceOutputStream
> const xSeqOutStream(
2221 io::SequenceOutputStream::create(m_xContext
));
2222 uno::Reference
<io::XOutputStream
> const xRequestOutStream(xSeqOutStream
);
2223 assert(xRequestOutStream
.is());
2224 uno::Reference
<xml::sax::XWriter
> const xWriter(xml::sax::Writer::create(m_xContext
));
2225 xWriter
->setOutputStream(xRequestOutStream
);
2226 xWriter
->startDocument();
2227 rtl::Reference
<::comphelper::AttributeList
> const pAttrList(new ::comphelper::AttributeList
);
2228 pAttrList
->AddAttribute("xmlns", "DAV:");
2229 xWriter
->startElement("lockinfo", pAttrList
);
2230 xWriter
->startElement("lockscope", nullptr);
2231 switch (rLock
.Scope
)
2233 case ucb::LockScope_EXCLUSIVE
:
2234 xWriter
->startElement("exclusive", nullptr);
2235 xWriter
->endElement("exclusive");
2237 case ucb::LockScope_SHARED
:
2238 xWriter
->startElement("shared", nullptr);
2239 xWriter
->endElement("shared");
2244 xWriter
->endElement("lockscope");
2245 xWriter
->startElement("locktype", nullptr);
2246 xWriter
->startElement("write", nullptr);
2247 xWriter
->endElement("write");
2248 xWriter
->endElement("locktype");
2250 if ((rLock
.Owner
>>= owner
) && !owner
.isEmpty())
2252 xWriter
->startElement("owner", nullptr);
2253 xWriter
->characters(owner
);
2254 xWriter
->endElement("owner");
2256 xWriter
->endElement("lockinfo");
2257 xWriter
->endDocument();
2259 uno::Reference
<io::XInputStream
> const xRequestInStream(
2260 io::SequenceInputStream::createStreamFromSequence(m_xContext
,
2261 xSeqOutStream
->getWrittenBytes()));
2262 assert(xRequestInStream
.is());
2264 ::std::unique_ptr
<curl_slist
, deleter_from_fn
<curl_slist
, curl_slist_free_all
>> pList
;
2265 pList
.reset(curl_slist_append(pList
.release(), "Content-Type: application/xml"));
2268 throw uno::RuntimeException("curl_slist_append failed");
2271 switch (rLock
.Depth
)
2273 case ucb::LockDepth_ZERO
:
2274 depth
= "Depth: 0"_ostr
;
2276 case ucb::LockDepth_ONE
:
2277 depth
= "Depth: 1"_ostr
;
2279 case ucb::LockDepth_INFINITY
:
2280 depth
= "Depth: infinity"_ostr
;
2285 pList
.reset(curl_slist_append(pList
.release(), depth
.getStr()));
2288 throw uno::RuntimeException("curl_slist_append failed");
2291 switch (rLock
.Timeout
)
2294 timeout
= "Timeout: Infinite"_ostr
;
2297 timeout
= "Timeout: Second-180"_ostr
;
2300 timeout
= "Timeout: Second-" + OString::number(rLock
.Timeout
);
2301 assert(0 < rLock
.Timeout
);
2304 pList
.reset(curl_slist_append(pList
.release(), timeout
.getStr()));
2307 throw uno::RuntimeException("curl_slist_append failed");
2310 auto const acquiredLocks
2311 = CurlProcessor::Lock(*this, uri
, &rEnv
, ::std::move(pList
), &xRequestInStream
);
2313 for (auto const& rAcquiredLock
: acquiredLocks
)
2315 g_Init
.LockStore
.addLock(uri
.GetURI(), rAcquiredLock
.first
,
2316 rAcquiredLock
.first
.LockTokens
[0], this, rAcquiredLock
.second
);
2317 SAL_INFO("ucb.ucp.webdav.curl", "created LOCK for " << rURIReference
);
2321 auto CurlProcessor::Unlock(CurlSession
& rSession
, CurlUri
const& rURI
,
2322 DAVRequestEnvironment
const* const pEnv
) -> void
2324 OUString
const* const pToken(g_Init
.LockStore
.getLockTokenForURI(rURI
.GetURI(), nullptr));
2327 SAL_WARN("ucb.ucp.webdav.curl", "attempt to unlock but not locked");
2328 throw DAVException(DAVException::DAV_NOT_LOCKED
);
2330 OString
const utf8LockToken("Lock-Token: <"
2331 + OUStringToOString(*pToken
, RTL_TEXTENCODING_ASCII_US
) + ">");
2332 ::std::unique_ptr
<curl_slist
, deleter_from_fn
<curl_slist
, curl_slist_free_all
>> pList(
2333 curl_slist_append(nullptr, utf8LockToken
.getStr()));
2336 throw uno::RuntimeException("curl_slist_append failed");
2339 ::std::vector
<CurlOption
> const options
{ { CURLOPT_CUSTOMREQUEST
, "UNLOCK",
2340 "CURLOPT_CUSTOMREQUEST" } };
2342 CurlProcessor::ProcessRequest(rSession
, rURI
, "UNLOCK", options
, pEnv
, ::std::move(pList
),
2343 nullptr, nullptr, nullptr);
2346 auto CurlSession::UNLOCK(OUString
const& rURIReference
, DAVRequestEnvironment
const& rEnv
) -> void
2348 SAL_INFO("ucb.ucp.webdav.curl", "UNLOCK: " << rURIReference
);
2350 // note: no m_Mutex lock needed here, only in CurlProcessor::Unlock()
2352 CurlUri
const uri(CurlProcessor::URIReferenceToURI(*this, rURIReference
));
2354 CurlProcessor::Unlock(*this, uri
, &rEnv
);
2356 g_Init
.LockStore
.removeLock(uri
.GetURI());
2359 auto CurlSession::NonInteractive_LOCK(OUString
const& rURI
, ::std::u16string_view
const rLockToken
,
2360 sal_Int32
& o_rLastChanceToSendRefreshRequest
,
2361 bool& o_rIsAuthFailed
) -> bool
2363 SAL_INFO("ucb.ucp.webdav.curl", "NonInteractive_LOCK: " << rURI
);
2365 // note: no m_Mutex lock needed here, only in CurlProcessor::Lock()
2369 CurlUri
const uri(rURI
);
2370 ::std::unique_ptr
<curl_slist
, deleter_from_fn
<curl_slist
, curl_slist_free_all
>> pList(
2371 curl_slist_append(nullptr, "Timeout: Second-180"));
2373 assert(!rLockToken
.empty()); // LockStore is the caller
2374 OString
const utf8If("If: (<" + OUStringToOString(rLockToken
, RTL_TEXTENCODING_ASCII_US
)
2376 pList
.reset(curl_slist_append(pList
.release(), utf8If
.getStr()));
2379 throw uno::RuntimeException("curl_slist_append failed");
2382 auto const acquiredLocks
2383 = CurlProcessor::Lock(*this, uri
, nullptr, ::std::move(pList
), nullptr);
2385 SAL_WARN_IF(1 < acquiredLocks
.size(), "ucb.ucp.webdav.curl",
2386 "multiple locks acquired on refresh for " << rURI
);
2387 if (!acquiredLocks
.empty())
2389 o_rLastChanceToSendRefreshRequest
= acquiredLocks
.begin()->second
;
2391 SAL_INFO("ucb.ucp.webdav.curl", "NonInteractive_LOCK succeeded on " << rURI
);
2394 catch (DAVException
const& rException
)
2396 SAL_INFO("ucb.ucp.webdav.curl", "NonInteractive_LOCK failed on " << rURI
);
2397 switch (rException
.getError())
2399 case DAVException::DAV_HTTP_AUTH
:
2400 case DAVException::DAV_HTTP_NOAUTH
:
2401 o_rIsAuthFailed
= true;
2410 SAL_INFO("ucb.ucp.webdav.curl", "NonInteractive_LOCK failed on " << rURI
);
2415 auto CurlSession::NonInteractive_UNLOCK(OUString
const& rURI
) -> void
2417 SAL_INFO("ucb.ucp.webdav.curl", "NonInteractive_UNLOCK: " << rURI
);
2419 // note: no m_Mutex lock needed here, only in CurlProcessor::Unlock()
2423 CurlUri
const uri(rURI
);
2425 CurlProcessor::Unlock(*this, uri
, nullptr);
2427 // the only caller is the dtor of the LockStore, don't call remove!
2428 SAL_INFO("ucb.ucp.webdav.curl", "NonInteractive_UNLOCK succeeded on " << rURI
);
2432 SAL_INFO("ucb.ucp.webdav.curl", "NonInteractive_UNLOCK failed on " << rURI
);
2436 } // namespace http_dav_ucp
2440 /// Manage lifecycle of global DAV worker threads
2441 class WebDAVManager
: public cppu::WeakImplHelper
<css::lang::XServiceInfo
>,
2442 public comphelper::LibreOfficeKit::ThreadJoinable
2448 virtual OUString SAL_CALL
getImplementationName() override
2450 return "com.sun.star.comp.WebDAVManager";
2452 virtual sal_Bool SAL_CALL
supportsService(const OUString
& ServiceName
) override
2454 return cppu::supportsService(this, ServiceName
);
2456 virtual css::uno::Sequence
<OUString
> SAL_CALL
getSupportedServiceNames() override
2458 return { "com.sun.star.ucb.WebDAVManager" };
2461 // comphelper::LibreOfficeKit::ThreadJoinable
2462 virtual bool joinThreads() override
{ return g_Init
.LockStore
.joinThreads(); }
2464 virtual void startThreads() override
{ g_Init
.LockStore
.startThreads(); }
2467 } // anonymous namespace
2469 extern "C" SAL_DLLPUBLIC_EXPORT
css::uno::XInterface
*
2470 ucb_webdav_manager_get_implementation(css::uno::XComponentContext
*,
2471 css::uno::Sequence
<css::uno::Any
> const&)
2473 return cppu::acquire(new WebDAVManager());
2476 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */