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