RepositoryExternal.mk | 2 comphelper/source/streaming/seqoutputstreamserv.cxx | 13 configure.ac | 36 external/curl/ExternalPackage_curl.mk | 2 external/curl/ExternalProject_curl.mk | 1 external/curl/UnpackedTarball_curl.mk | 2 external/curl/curl-debug.patch.1 | 11 external/curl/curl-msvc-zlib.patch.1 | 16 include/sal/log-areas.dox | 1 postprocess/Rdb_services.mk | 3 solenv/clang-format/blacklist | 35 ucb/CppunitTest_ucb_webdav_core.mk | 26 ucb/CppunitTest_ucb_webdav_neon_opts.mk | 56 ucb/CppunitTest_ucb_webdav_propfindcache.mk | 56 ucb/CppunitTest_ucb_webdav_res_access.mk | 56 ucb/Module_ucb.mk | 7 ucb/qa/cppunit/webdav/webdav_local_neon.cxx | 69 ucb/qa/cppunit/webdav/webdav_options.cxx | 6 ucb/qa/cppunit/webdav/webdav_propfindcache.cxx | 8 ucb/qa/cppunit/webdav/webdav_resource_access.cxx | 4 ucb/source/ucp/webdav-curl/ContentProperties.cxx | 568 ++ ucb/source/ucp/webdav-curl/ContentProperties.hxx | 173 ucb/source/ucp/webdav-curl/CurlSession.cxx | 2376 ++++++++++ ucb/source/ucp/webdav-curl/CurlSession.hxx | 145 ucb/source/ucp/webdav-curl/CurlUri.cxx | 327 + ucb/source/ucp/webdav-curl/CurlUri.hxx | 94 ucb/source/ucp/webdav-curl/DAVAuthListener.hxx | 43 ucb/source/ucp/webdav-curl/DAVAuthListenerImpl.hxx | 61 ucb/source/ucp/webdav-curl/DAVException.hxx | 174 ucb/source/ucp/webdav-curl/DAVProperties.cxx | 199 ucb/source/ucp/webdav-curl/DAVProperties.hxx | 57 ucb/source/ucp/webdav-curl/DAVRequestEnvironment.hxx | 54 ucb/source/ucp/webdav-curl/DAVResource.hxx | 61 ucb/source/ucp/webdav-curl/DAVResourceAccess.cxx | 1192 +++++ ucb/source/ucp/webdav-curl/DAVResourceAccess.hxx | 220 ucb/source/ucp/webdav-curl/DAVSession.hxx | 196 ucb/source/ucp/webdav-curl/DAVSessionFactory.cxx | 87 ucb/source/ucp/webdav-curl/DAVSessionFactory.hxx | 74 ucb/source/ucp/webdav-curl/DAVTypes.cxx | 199 ucb/source/ucp/webdav-curl/DAVTypes.hxx | 202 ucb/source/ucp/webdav-curl/DateTimeHelper.cxx | 258 + ucb/source/ucp/webdav-curl/DateTimeHelper.hxx | 55 ucb/source/ucp/webdav-curl/PropertyMap.hxx | 55 ucb/source/ucp/webdav-curl/PropfindCache.cxx | 91 ucb/source/ucp/webdav-curl/PropfindCache.hxx | 81 ucb/source/ucp/webdav-curl/SerfLockStore.cxx | 275 + ucb/source/ucp/webdav-curl/SerfLockStore.hxx | 96 ucb/source/ucp/webdav-curl/UCBDeadPropertyValue.cxx | 220 ucb/source/ucp/webdav-curl/UCBDeadPropertyValue.hxx | 46 ucb/source/ucp/webdav-curl/ucpdav1.component | 26 ucb/source/ucp/webdav-curl/webdavcontent.cxx | 4302 +++++++++++++++++++ ucb/source/ucp/webdav-curl/webdavcontent.hxx | 305 + ucb/source/ucp/webdav-curl/webdavcontentcaps.cxx | 636 ++ ucb/source/ucp/webdav-curl/webdavdatasupplier.cxx | 486 ++ ucb/source/ucp/webdav-curl/webdavdatasupplier.hxx | 73 ucb/source/ucp/webdav-curl/webdavprovider.cxx | 174 ucb/source/ucp/webdav-curl/webdavprovider.hxx | 100 ucb/source/ucp/webdav-curl/webdavresponseparser.cxx | 975 ++++ ucb/source/ucp/webdav-curl/webdavresponseparser.hxx | 38 ucb/source/ucp/webdav-curl/webdavresultset.cxx | 76 ucb/source/ucp/webdav-curl/webdavresultset.hxx | 48 61 files changed, 15086 insertions(+), 242 deletions(-)
New commits: commit 06e4c6fd51f60f1f7d474ede321fe031cde0b3ca Author: Michael Stahl <michael.st...@allotropia.de> AuthorDate: Wed Mar 9 16:21:07 2022 +0100 Commit: Michael Stahl <michael.st...@allotropia.de> CommitDate: Wed Mar 9 19:00:31 2022 +0100 TODO: does it need own component file? Change-Id: I01e169e9445b2711d95c83a07f3c26507b5c1ab1 diff --git a/postprocess/Rdb_services.mk b/postprocess/Rdb_services.mk index f8c5047de79f..35955d90ac3e 100644 --- a/postprocess/Rdb_services.mk +++ b/postprocess/Rdb_services.mk @@ -168,6 +168,9 @@ $(eval $(call gb_Rdb_add_components,services,\ $(if $(WITH_WEBDAV), \ ucb/source/ucp/webdav-neon/ucpdav1 \ ) \ + $(if $(filter curl,$(WITH_WEBDAV)), \ + ucb/source/ucp/webdav-curl/ucpdav1 \ + ) \ $(call gb_Helper_optional,SCRIPTING, \ basctl/util/basctl \ basic/util/sb \ commit d84595ec6da2555937874556a22677f41eb39984 Author: Michael Stahl <michael.st...@allotropia.de> AuthorDate: Fri Nov 26 16:29:08 2021 +0100 Commit: Michael Stahl <michael.st...@allotropia.de> CommitDate: Wed Mar 9 19:00:31 2022 +0100 ucb: webdav-curl: fix CurlUri::CloneWithRelativeRefPathAbsolute() Change-Id: Idf1d75817009286cd79a00c0ba154cea70e2ffda Reviewed-on: https://gerrit.libreoffice.org/c/core/+/125908 Tested-by: Jenkins Reviewed-by: Michael Stahl <michael.st...@allotropia.de> (cherry picked from commit b03e070420606d407df2ec5e9dfa7043ecc46177) Reviewed-on: https://gerrit.libreoffice.org/c/core/+/125895 Reviewed-by: Thorsten Behrens <thorsten.behr...@allotropia.de> (cherry picked from commit db8edd163cf453bbb9b85c2a17c78da49df27cdf) diff --git a/ucb/qa/cppunit/webdav/webdav_local_neon.cxx b/ucb/qa/cppunit/webdav/webdav_local_neon.cxx index 08a384dee36b..bde7652b9ffa 100644 --- a/ucb/qa/cppunit/webdav/webdav_local_neon.cxx +++ b/ucb/qa/cppunit/webdav/webdav_local_neon.cxx @@ -43,6 +43,22 @@ namespace CPPUNIT_ASSERT_EQUAL( OUString( "user%40anothername" ), aURI.GetUser() ); CPPUNIT_ASSERT_EQUAL( sal_uInt16( 8040 ), aURI.GetPort() ); CPPUNIT_ASSERT_EQUAL( OUString( "/aService/asegment/nextsegment/check.this?test=true&link=http://anotherserver.com/%3Fcheck=theapplication%26os=linuxintel%26lang=en-US%26version=5.2.0" ), aURI.GetRelativeReference() ); + + CurlUri uri2(aURI.CloneWithRelativeRefPathAbsolute(u"/foo/bar")); + CPPUNIT_ASSERT_EQUAL( OUString("http"), uri2.GetScheme() ); + CPPUNIT_ASSERT_EQUAL( OUString("server.biz"), uri2.GetHost() ); + CPPUNIT_ASSERT_EQUAL( OUString("user%40anothername"), uri2.GetUser() ); + CPPUNIT_ASSERT_EQUAL( sal_uInt16(8040), uri2.GetPort() ); + CPPUNIT_ASSERT_EQUAL( OUString("/foo/bar"), uri2.GetRelativeReference() ); + CPPUNIT_ASSERT_EQUAL( OUString("http://user%40anothern...@server.biz:8040/foo/bar"), uri2.GetURI() ); + + CurlUri uri3(aURI.CloneWithRelativeRefPathAbsolute(u"?query#fragment")); + CPPUNIT_ASSERT_EQUAL( OUString("http"), uri3.GetScheme() ); + CPPUNIT_ASSERT_EQUAL( OUString("server.biz"), uri3.GetHost() ); + CPPUNIT_ASSERT_EQUAL( OUString("user%40anothername"), uri3.GetUser() ); + CPPUNIT_ASSERT_EQUAL( sal_uInt16(8040), uri3.GetPort() ); + CPPUNIT_ASSERT_EQUAL( OUString("?query#fragment"), uri3.GetRelativeReference() ); + CPPUNIT_ASSERT_EQUAL( OUString("http://user%40anothern...@server.biz:8040/?query#fragment"), uri3.GetURI() ); } void webdav_local_test::WebdavUriTest2() @@ -54,6 +70,24 @@ namespace CPPUNIT_ASSERT_EQUAL( OUString("bar"), aURI.GetPassword() ); CPPUNIT_ASSERT_EQUAL( sal_uInt16( 8040 ), aURI.GetPort() ); CPPUNIT_ASSERT_EQUAL( OUString( "/aService#aaa" ), aURI.GetRelativeReference() ); + + CurlUri uri2(aURI.CloneWithRelativeRefPathAbsolute(u"/foo/bar")); + CPPUNIT_ASSERT_EQUAL( OUString("https"), uri2.GetScheme() ); + CPPUNIT_ASSERT_EQUAL( OUString("server.biz"), uri2.GetHost() ); + CPPUNIT_ASSERT_EQUAL( OUString("foo"), uri2.GetUser() ); + CPPUNIT_ASSERT_EQUAL( OUString("bar"), uri2.GetPassword() ); + CPPUNIT_ASSERT_EQUAL( sal_uInt16( 8040 ), uri2.GetPort() ); + CPPUNIT_ASSERT_EQUAL( OUString("/foo/bar"), uri2.GetRelativeReference() ); + CPPUNIT_ASSERT_EQUAL( OUString("https://foo:b...@server.biz:8040/foo/bar"), uri2.GetURI() ); + + CurlUri uri3(aURI.CloneWithRelativeRefPathAbsolute(u"?query")); + CPPUNIT_ASSERT_EQUAL( OUString("https"), uri3.GetScheme() ); + CPPUNIT_ASSERT_EQUAL( OUString("server.biz"), uri3.GetHost() ); + CPPUNIT_ASSERT_EQUAL( OUString("foo"), uri3.GetUser() ); + CPPUNIT_ASSERT_EQUAL( OUString("bar"), uri3.GetPassword() ); + CPPUNIT_ASSERT_EQUAL( sal_uInt16(8040), uri3.GetPort() ); + CPPUNIT_ASSERT_EQUAL( OUString("?query"), uri3.GetRelativeReference() ); + CPPUNIT_ASSERT_EQUAL( OUString("https://foo:b...@server.biz:8040/?query"), uri3.GetURI() ); } CPPUNIT_TEST_SUITE_REGISTRATION( webdav_local_test ); commit f0e6781ff272dc08795a2ab2fe2498ad8d97241d Author: Michael Stahl <michael.st...@allotropia.de> AuthorDate: Tue Nov 2 16:53:29 2021 +0100 Commit: Michael Stahl <michael.st...@allotropia.de> CommitDate: Wed Mar 9 19:00:31 2022 +0100 ucb: webdav-curl: consolidate unit tests 1 CppuniTest makefile should be sufficient, particularly since all 4 of them used gb_CppunitTest_use_library_objects so this should reduce wasted space. Change-Id: I485f0af0aee2b265b56c505ced44257834784e98 Reviewed-on: https://gerrit.libreoffice.org/c/core/+/124608 Tested-by: Jenkins Reviewed-by: Michael Stahl <michael.st...@allotropia.de> (cherry picked from commit 895918b07da8fbe5603b3a96b3c58a8dc2d066e5) diff --git a/ucb/CppunitTest_ucb_webdav_res_access.mk b/ucb/CppunitTest_ucb_webdav_core.mk similarity index 55% rename from ucb/CppunitTest_ucb_webdav_res_access.mk rename to ucb/CppunitTest_ucb_webdav_core.mk index dd335a6ff065..fbd3f59f55f6 100644 --- a/ucb/CppunitTest_ucb_webdav_res_access.mk +++ b/ucb/CppunitTest_ucb_webdav_core.mk @@ -9,14 +9,11 @@ # #************************************************************************* -$(eval $(call gb_CppunitTest_CppunitTest,ucb_webdav_res_access)) +$(eval $(call gb_CppunitTest_CppunitTest,ucb_webdav_core)) -$(eval $(call gb_CppunitTest_use_api,ucb_webdav_res_access, \ - offapi \ - udkapi \ -)) +$(eval $(call gb_CppunitTest_use_sdk_api,ucb_webdav_core)) -$(eval $(call gb_CppunitTest_use_libraries,ucb_webdav_res_access, \ +$(eval $(call gb_CppunitTest_use_libraries,ucb_webdav_core, \ comphelper \ cppu \ cppuhelper \ @@ -27,29 +24,30 @@ $(eval $(call gb_CppunitTest_use_libraries,ucb_webdav_res_access, \ tl \ )) -$(eval $(call gb_CppunitTest_use_library_objects,ucb_webdav_res_access, \ +$(eval $(call gb_CppunitTest_use_library_objects,ucb_webdav_core, \ ucpdav1 \ )) -$(eval $(call gb_CppunitTest_use_externals,ucb_webdav_res_access,\ +$(eval $(call gb_CppunitTest_use_externals,ucb_webdav_core,\ boost_headers \ libxml2 \ curl \ )) -$(eval $(call gb_CppunitTest_use_custom_headers,ucb_webdav_res_access,\ +$(eval $(call gb_CppunitTest_use_custom_headers,ucb_webdav_core,\ officecfg/registry \ )) -$(eval $(call gb_CppunitTest_add_exception_objects,ucb_webdav_res_access, \ +$(eval $(call gb_CppunitTest_add_exception_objects,ucb_webdav_core, \ ucb/qa/cppunit/webdav/webdav_resource_access \ + ucb/qa/cppunit/webdav/webdav_propfindcache \ + ucb/qa/cppunit/webdav/webdav_options \ + ucb/qa/cppunit/webdav/webdav_local_neon \ )) -$(eval $(call gb_CppunitTest_set_include,ucb_webdav_res_access,\ +$(eval $(call gb_CppunitTest_set_include,ucb_webdav_core,\ $$(INCLUDE) \ -I$(SRCDIR)/ucb/source/ucp/webdav-curl \ )) -$(eval $(call gb_CppunitTest_use_static_libraries,ucb_webdav_res_access)) - # vim: set noet sw=4 ts=4: diff --git a/ucb/CppunitTest_ucb_webdav_local_neon.mk b/ucb/CppunitTest_ucb_webdav_local_neon.mk deleted file mode 100644 index b9b6d5d448ef..000000000000 --- a/ucb/CppunitTest_ucb_webdav_local_neon.mk +++ /dev/null @@ -1,52 +0,0 @@ -# -*- Mode: makefile-gmake; tab-width: 4; indent-tabs-mode: t -*- -#************************************************************************* -# -# This file is part of the LibreOffice project. -# -# This Source Code Form is subject to the terms of the Mozilla Public -# License, v. 2.0. If a copy of the MPL was not distributed with this -# file, You can obtain one at http://mozilla.org/MPL/2.0/. -# -#************************************************************************* - -$(eval $(call gb_CppunitTest_CppunitTest,ucb_webdav_local_neon)) - -$(eval $(call gb_CppunitTest_use_sdk_api,ucb_webdav_local_neon)) - -$(eval $(call gb_CppunitTest_use_libraries,ucb_webdav_local_neon, \ - comphelper \ - cppu \ - cppuhelper \ - sal \ - salhelper \ - test \ - ucbhelper \ - tl \ -)) - -$(eval $(call gb_CppunitTest_use_library_objects,ucb_webdav_local_neon, \ - ucpdav1 \ -)) - -$(eval $(call gb_CppunitTest_use_externals,ucb_webdav_local_neon,\ - boost_headers \ - libxml2 \ - curl \ -)) - -$(eval $(call gb_CppunitTest_use_custom_headers,ucb_webdav_local_neon,\ - officecfg/registry \ -)) - -$(eval $(call gb_CppunitTest_add_exception_objects,ucb_webdav_local_neon, \ - ucb/qa/cppunit/webdav/webdav_local_neon \ -)) - -$(eval $(call gb_CppunitTest_set_include,ucb_webdav_local_neon,\ - $$(INCLUDE) \ - -I$(SRCDIR)/ucb/source/ucp/webdav-curl \ -)) - -$(eval $(call gb_CppunitTest_use_static_libraries,ucb_webdav_local_neon)) - -# vim: set noet sw=4 ts=4: diff --git a/ucb/CppunitTest_ucb_webdav_neon_opts.mk b/ucb/CppunitTest_ucb_webdav_neon_opts.mk deleted file mode 100644 index ba77a4c59a42..000000000000 --- a/ucb/CppunitTest_ucb_webdav_neon_opts.mk +++ /dev/null @@ -1,55 +0,0 @@ -# -*- Mode: makefile-gmake; tab-width: 4; indent-tabs-mode: t -*- -#************************************************************************* -# -# This file is part of the LibreOffice project. -# -# This Source Code Form is subject to the terms of the Mozilla Public -# License, v. 2.0. If a copy of the MPL was not distributed with this -# file, You can obtain one at http://mozilla.org/MPL/2.0/. -# -#************************************************************************* - -$(eval $(call gb_CppunitTest_CppunitTest,ucb_webdav_neon_opts)) - -$(eval $(call gb_CppunitTest_use_api,ucb_webdav_neon_opts, \ - offapi \ - udkapi \ -)) - -$(eval $(call gb_CppunitTest_use_libraries,ucb_webdav_neon_opts, \ - comphelper \ - cppu \ - cppuhelper \ - sal \ - salhelper \ - test \ - ucbhelper \ - tl \ -)) - -$(eval $(call gb_CppunitTest_use_library_objects,ucb_webdav_neon_opts, \ - ucpdav1 \ -)) - -$(eval $(call gb_CppunitTest_use_externals,ucb_webdav_neon_opts,\ - boost_headers \ - libxml2 \ - curl \ -)) - -$(eval $(call gb_CppunitTest_use_custom_headers,ucb_webdav_neon_opts,\ - officecfg/registry \ -)) - -$(eval $(call gb_CppunitTest_add_exception_objects,ucb_webdav_neon_opts, \ - ucb/qa/cppunit/webdav/webdav_options \ -)) - -$(eval $(call gb_CppunitTest_set_include,ucb_webdav_neon_opts,\ - $$(INCLUDE) \ - -I$(SRCDIR)/ucb/source/ucp/webdav-curl \ -)) - -$(eval $(call gb_CppunitTest_use_static_libraries,ucb_webdav_neon_opts)) - -# vim: set noet sw=4 ts=4: diff --git a/ucb/CppunitTest_ucb_webdav_propfindcache.mk b/ucb/CppunitTest_ucb_webdav_propfindcache.mk deleted file mode 100644 index 4e9ad0668829..000000000000 --- a/ucb/CppunitTest_ucb_webdav_propfindcache.mk +++ /dev/null @@ -1,55 +0,0 @@ -# -*- Mode: makefile-gmake; tab-width: 4; indent-tabs-mode: t -*- -#************************************************************************* -# -# This file is part of the LibreOffice project. -# -# This Source Code Form is subject to the terms of the Mozilla Public -# License, v. 2.0. If a copy of the MPL was not distributed with this -# file, You can obtain one at http://mozilla.org/MPL/2.0/. -# -#************************************************************************* - -$(eval $(call gb_CppunitTest_CppunitTest,ucb_webdav_propfindcache)) - -$(eval $(call gb_CppunitTest_use_api,ucb_webdav_propfindcache, \ - offapi \ - udkapi \ -)) - -$(eval $(call gb_CppunitTest_use_libraries,ucb_webdav_propfindcache, \ - comphelper \ - cppu \ - cppuhelper \ - sal \ - salhelper \ - test \ - ucbhelper \ - tl \ -)) - -$(eval $(call gb_CppunitTest_use_library_objects,ucb_webdav_propfindcache, \ - ucpdav1 \ -)) - -$(eval $(call gb_CppunitTest_use_externals,ucb_webdav_propfindcache,\ - boost_headers \ - libxml2 \ - curl \ -)) - -$(eval $(call gb_CppunitTest_use_custom_headers,ucb_webdav_propfindcache,\ - officecfg/registry \ -)) - -$(eval $(call gb_CppunitTest_add_exception_objects,ucb_webdav_propfindcache, \ - ucb/qa/cppunit/webdav/webdav_propfindcache \ -)) - -$(eval $(call gb_CppunitTest_set_include,ucb_webdav_propfindcache,\ - $$(INCLUDE) \ - -I$(SRCDIR)/ucb/source/ucp/webdav-curl \ -)) - -$(eval $(call gb_CppunitTest_use_static_libraries,ucb_webdav_propfindcache)) - -# vim: set noet sw=4 ts=4: diff --git a/ucb/Module_ucb.mk b/ucb/Module_ucb.mk index 6e630e9c6342..19c1aec96591 100644 --- a/ucb/Module_ucb.mk +++ b/ucb/Module_ucb.mk @@ -34,10 +34,7 @@ endif ifeq ($(WITH_WEBDAV),curl) $(eval $(call gb_Module_add_check_targets,ucb,\ - CppunitTest_ucb_webdav_local_neon \ - CppunitTest_ucb_webdav_neon_opts \ - CppunitTest_ucb_webdav_propfindcache \ - CppunitTest_ucb_webdav_res_access \ + CppunitTest_ucb_webdav_core \ )) endif diff --git a/ucb/qa/cppunit/webdav/webdav_local_neon.cxx b/ucb/qa/cppunit/webdav/webdav_local_neon.cxx index a2208ffaccad..08a384dee36b 100644 --- a/ucb/qa/cppunit/webdav/webdav_local_neon.cxx +++ b/ucb/qa/cppunit/webdav/webdav_local_neon.cxx @@ -57,8 +57,6 @@ namespace } CPPUNIT_TEST_SUITE_REGISTRATION( webdav_local_test ); -} // namespace rtl_random - -CPPUNIT_PLUGIN_IMPLEMENT(); +} /* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/ucb/qa/cppunit/webdav/webdav_options.cxx b/ucb/qa/cppunit/webdav/webdav_options.cxx index de939ada889e..ebf59da920f2 100644 --- a/ucb/qa/cppunit/webdav/webdav_options.cxx +++ b/ucb/qa/cppunit/webdav/webdav_options.cxx @@ -364,8 +364,6 @@ namespace } CPPUNIT_TEST_SUITE_REGISTRATION( webdav_opts_test ); -} // namespace rtl_random - -CPPUNIT_PLUGIN_IMPLEMENT(); +} /* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/ucb/qa/cppunit/webdav/webdav_propfindcache.cxx b/ucb/qa/cppunit/webdav/webdav_propfindcache.cxx index 6158faea28b9..e684c135649b 100644 --- a/ucb/qa/cppunit/webdav/webdav_propfindcache.cxx +++ b/ucb/qa/cppunit/webdav/webdav_propfindcache.cxx @@ -128,8 +128,6 @@ namespace } CPPUNIT_TEST_SUITE_REGISTRATION( webdav_propcache_test ); -} // namespace rtl_random - -CPPUNIT_PLUGIN_IMPLEMENT(); +} /* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/ucb/qa/cppunit/webdav/webdav_resource_access.cxx b/ucb/qa/cppunit/webdav/webdav_resource_access.cxx index 85d3e1c236a6..2379139c04a3 100644 --- a/ucb/qa/cppunit/webdav/webdav_resource_access.cxx +++ b/ucb/qa/cppunit/webdav/webdav_resource_access.cxx @@ -96,7 +96,7 @@ namespace } CPPUNIT_TEST_SUITE_REGISTRATION( webdav_resource_access_test ); -} // namespace rtl_random +} CPPUNIT_PLUGIN_IMPLEMENT(); commit 4a064222cbd99a6e7ebaa21ea0fa114e5eef77b2 Author: Michael Stahl <michael.st...@allotropia.de> AuthorDate: Tue Nov 2 16:08:53 2021 +0100 Commit: Michael Stahl <michael.st...@allotropia.de> CommitDate: Wed Mar 9 19:00:31 2022 +0100 ucb: webdav-curl: convert webdav unit tests to curl There's no point in building the tests for neon any more, just build them only if curl is used. This finds a bug in CurlUri::Init() where query and fragment separators need to be added manually. Change-Id: I3dcd1512450522df2c2a7d223b2e29b6c0e794fb Reviewed-on: https://gerrit.libreoffice.org/c/core/+/124607 Tested-by: Jenkins Reviewed-by: Michael Stahl <michael.st...@allotropia.de> (cherry picked from commit 5a44e3772adb281d16757cdac3120d56565a96e5) diff --git a/ucb/CppunitTest_ucb_webdav_local_neon.mk b/ucb/CppunitTest_ucb_webdav_local_neon.mk index 2e920f4ca03f..b9b6d5d448ef 100644 --- a/ucb/CppunitTest_ucb_webdav_local_neon.mk +++ b/ucb/CppunitTest_ucb_webdav_local_neon.mk @@ -31,8 +31,7 @@ $(eval $(call gb_CppunitTest_use_library_objects,ucb_webdav_local_neon, \ $(eval $(call gb_CppunitTest_use_externals,ucb_webdav_local_neon,\ boost_headers \ libxml2 \ - neon \ - openssl \ + curl \ )) $(eval $(call gb_CppunitTest_use_custom_headers,ucb_webdav_local_neon,\ @@ -45,7 +44,7 @@ $(eval $(call gb_CppunitTest_add_exception_objects,ucb_webdav_local_neon, \ $(eval $(call gb_CppunitTest_set_include,ucb_webdav_local_neon,\ $$(INCLUDE) \ - -I$(SRCDIR)/ucb/source/ucp/webdav-neon \ + -I$(SRCDIR)/ucb/source/ucp/webdav-curl \ )) $(eval $(call gb_CppunitTest_use_static_libraries,ucb_webdav_local_neon)) diff --git a/ucb/CppunitTest_ucb_webdav_neon_opts.mk b/ucb/CppunitTest_ucb_webdav_neon_opts.mk index df670ed7afdd..ba77a4c59a42 100644 --- a/ucb/CppunitTest_ucb_webdav_neon_opts.mk +++ b/ucb/CppunitTest_ucb_webdav_neon_opts.mk @@ -34,8 +34,7 @@ $(eval $(call gb_CppunitTest_use_library_objects,ucb_webdav_neon_opts, \ $(eval $(call gb_CppunitTest_use_externals,ucb_webdav_neon_opts,\ boost_headers \ libxml2 \ - neon \ - openssl \ + curl \ )) $(eval $(call gb_CppunitTest_use_custom_headers,ucb_webdav_neon_opts,\ @@ -48,7 +47,7 @@ $(eval $(call gb_CppunitTest_add_exception_objects,ucb_webdav_neon_opts, \ $(eval $(call gb_CppunitTest_set_include,ucb_webdav_neon_opts,\ $$(INCLUDE) \ - -I$(SRCDIR)/ucb/source/ucp/webdav-neon \ + -I$(SRCDIR)/ucb/source/ucp/webdav-curl \ )) $(eval $(call gb_CppunitTest_use_static_libraries,ucb_webdav_neon_opts)) diff --git a/ucb/CppunitTest_ucb_webdav_propfindcache.mk b/ucb/CppunitTest_ucb_webdav_propfindcache.mk index 8d4a12d32ad4..4e9ad0668829 100644 --- a/ucb/CppunitTest_ucb_webdav_propfindcache.mk +++ b/ucb/CppunitTest_ucb_webdav_propfindcache.mk @@ -34,8 +34,7 @@ $(eval $(call gb_CppunitTest_use_library_objects,ucb_webdav_propfindcache, \ $(eval $(call gb_CppunitTest_use_externals,ucb_webdav_propfindcache,\ boost_headers \ libxml2 \ - neon \ - openssl \ + curl \ )) $(eval $(call gb_CppunitTest_use_custom_headers,ucb_webdav_propfindcache,\ @@ -48,7 +47,7 @@ $(eval $(call gb_CppunitTest_add_exception_objects,ucb_webdav_propfindcache, \ $(eval $(call gb_CppunitTest_set_include,ucb_webdav_propfindcache,\ $$(INCLUDE) \ - -I$(SRCDIR)/ucb/source/ucp/webdav-neon \ + -I$(SRCDIR)/ucb/source/ucp/webdav-curl \ )) $(eval $(call gb_CppunitTest_use_static_libraries,ucb_webdav_propfindcache)) diff --git a/ucb/CppunitTest_ucb_webdav_res_access.mk b/ucb/CppunitTest_ucb_webdav_res_access.mk index fa0669987bd2..dd335a6ff065 100644 --- a/ucb/CppunitTest_ucb_webdav_res_access.mk +++ b/ucb/CppunitTest_ucb_webdav_res_access.mk @@ -34,8 +34,7 @@ $(eval $(call gb_CppunitTest_use_library_objects,ucb_webdav_res_access, \ $(eval $(call gb_CppunitTest_use_externals,ucb_webdav_res_access,\ boost_headers \ libxml2 \ - neon \ - openssl \ + curl \ )) $(eval $(call gb_CppunitTest_use_custom_headers,ucb_webdav_res_access,\ @@ -48,7 +47,7 @@ $(eval $(call gb_CppunitTest_add_exception_objects,ucb_webdav_res_access, \ $(eval $(call gb_CppunitTest_set_include,ucb_webdav_res_access,\ $$(INCLUDE) \ - -I$(SRCDIR)/ucb/source/ucp/webdav-neon \ + -I$(SRCDIR)/ucb/source/ucp/webdav-curl \ )) $(eval $(call gb_CppunitTest_use_static_libraries,ucb_webdav_res_access)) diff --git a/ucb/Module_ucb.mk b/ucb/Module_ucb.mk index dc1405e72992..6e630e9c6342 100644 --- a/ucb/Module_ucb.mk +++ b/ucb/Module_ucb.mk @@ -31,7 +31,7 @@ $(eval $(call gb_Module_add_targets,ucb,\ )) endif -ifeq ($(WITH_WEBDAV),neon) +ifeq ($(WITH_WEBDAV),curl) $(eval $(call gb_Module_add_check_targets,ucb,\ CppunitTest_ucb_webdav_local_neon \ diff --git a/ucb/qa/cppunit/webdav/webdav_local_neon.cxx b/ucb/qa/cppunit/webdav/webdav_local_neon.cxx index a88266020787..a2208ffaccad 100644 --- a/ucb/qa/cppunit/webdav/webdav_local_neon.cxx +++ b/ucb/qa/cppunit/webdav/webdav_local_neon.cxx @@ -10,9 +10,9 @@ #include <cppunit/TestFixture.h> #include <cppunit/extensions/HelperMacros.h> #include <cppunit/plugin/TestPlugIn.h> -#include <NeonUri.hxx> +#include <CurlUri.hxx> -using namespace webdav_ucp; +using namespace http_dav_ucp; namespace { @@ -21,26 +21,39 @@ namespace { public: - void NeonUriTest(); + void WebdavUriTest(); + void WebdavUriTest2(); // Change the following lines only, if you add, remove or rename // member functions of the current class, // because these macros are need by auto register mechanism. CPPUNIT_TEST_SUITE( webdav_local_test ); - CPPUNIT_TEST( NeonUriTest ); + CPPUNIT_TEST( WebdavUriTest ); + CPPUNIT_TEST( WebdavUriTest2 ); CPPUNIT_TEST_SUITE_END(); }; // class webdav_local_test - void webdav_local_test::NeonUriTest() + void webdav_local_test::WebdavUriTest() { //try URL decomposition - NeonUri aURI( "http://user%40anothern...@server.biz:8040/aService/asegment/nextsegment/check.this?test=true&link=http://anotherserver.com/%3Fcheck=theapplication%26os=linuxintel%26lang=en-US%26version=5.2.0" ); + CurlUri aURI(u"http://user%40anothern...@server.biz:8040/aService/asegment/nextsegment/check.this?test=true&link=http://anotherserver.com/%3Fcheck=theapplication%26os=linuxintel%26lang=en-US%26version=5.2.0" ); CPPUNIT_ASSERT_EQUAL( OUString( "http" ), aURI.GetScheme() ); CPPUNIT_ASSERT_EQUAL( OUString( "server.biz" ), aURI.GetHost() ); - CPPUNIT_ASSERT_EQUAL( OUString( "user%40anothername" ), aURI.GetUserInfo() ); - CPPUNIT_ASSERT_EQUAL( sal_Int32( 8040 ), aURI.GetPort() ); - CPPUNIT_ASSERT_EQUAL( OUString( "/aService/asegment/nextsegment/check.this?test=true&link=http://anotherserver.com/%3Fcheck=theapplication%26os=linuxintel%26lang=en-US%26version=5.2.0" ), aURI.GetPath( ) ); + CPPUNIT_ASSERT_EQUAL( OUString( "user%40anothername" ), aURI.GetUser() ); + CPPUNIT_ASSERT_EQUAL( sal_uInt16( 8040 ), aURI.GetPort() ); + CPPUNIT_ASSERT_EQUAL( OUString( "/aService/asegment/nextsegment/check.this?test=true&link=http://anotherserver.com/%3Fcheck=theapplication%26os=linuxintel%26lang=en-US%26version=5.2.0" ), aURI.GetRelativeReference() ); + } + + void webdav_local_test::WebdavUriTest2() + { + CurlUri aURI(u"https://foo:b...@server.biz:8040/aService#aaa" ); + CPPUNIT_ASSERT_EQUAL( OUString("https"), aURI.GetScheme() ); + CPPUNIT_ASSERT_EQUAL( OUString("server.biz"), aURI.GetHost() ); + CPPUNIT_ASSERT_EQUAL( OUString("foo"), aURI.GetUser() ); + CPPUNIT_ASSERT_EQUAL( OUString("bar"), aURI.GetPassword() ); + CPPUNIT_ASSERT_EQUAL( sal_uInt16( 8040 ), aURI.GetPort() ); + CPPUNIT_ASSERT_EQUAL( OUString( "/aService#aaa" ), aURI.GetRelativeReference() ); } CPPUNIT_TEST_SUITE_REGISTRATION( webdav_local_test ); diff --git a/ucb/qa/cppunit/webdav/webdav_options.cxx b/ucb/qa/cppunit/webdav/webdav_options.cxx index ef0cc1b969fc..de939ada889e 100644 --- a/ucb/qa/cppunit/webdav/webdav_options.cxx +++ b/ucb/qa/cppunit/webdav/webdav_options.cxx @@ -11,7 +11,7 @@ #include <cppunit/plugin/TestPlugIn.h> #include <DAVTypes.hxx> -using namespace webdav_ucp; +using namespace http_dav_ucp; namespace { diff --git a/ucb/qa/cppunit/webdav/webdav_propfindcache.cxx b/ucb/qa/cppunit/webdav/webdav_propfindcache.cxx index 29ccf8a462be..6158faea28b9 100644 --- a/ucb/qa/cppunit/webdav/webdav_propfindcache.cxx +++ b/ucb/qa/cppunit/webdav/webdav_propfindcache.cxx @@ -11,7 +11,7 @@ #include <cppunit/plugin/TestPlugIn.h> #include <PropfindCache.hxx> -using namespace webdav_ucp; +using namespace http_dav_ucp; namespace { @@ -113,7 +113,7 @@ namespace // add the cache an element aPropsNames.setPropertiesNames( aProps ); - PropCache.addCachePropertyNames( aPropsNames ); + PropCache.addCachePropertyNames( aPropsNames, 10 ); PropertyNames aRetPropsNames; //test existence diff --git a/ucb/qa/cppunit/webdav/webdav_resource_access.cxx b/ucb/qa/cppunit/webdav/webdav_resource_access.cxx index d1a73be2d5f3..85d3e1c236a6 100644 --- a/ucb/qa/cppunit/webdav/webdav_resource_access.cxx +++ b/ucb/qa/cppunit/webdav/webdav_resource_access.cxx @@ -12,7 +12,7 @@ #include <DAVResourceAccess.hxx> #include <DAVException.hxx> -using namespace webdav_ucp; +using namespace http_dav_ucp; namespace { commit 1558ef4416673f04cfdb3c1f1cb02d123802d06c Author: Michael Stahl <michael.st...@allotropia.de> AuthorDate: Fri Sep 10 14:37:41 2021 +0200 Commit: Michael Stahl <michael.st...@allotropia.de> CommitDate: Wed Mar 9 19:00:31 2022 +0100 configure: add --with-webdav=curl Change-Id: Id5cbeb9924b8e80b3b88f77fee2efd8457b5c302 Reviewed-on: https://gerrit.libreoffice.org/c/core/+/122043 Tested-by: Jenkins Reviewed-by: Michael Stahl <michael.st...@allotropia.de> (cherry picked from commit b0e0fa1d2aee653586e1803bc30447b066e6cc66) diff --git a/configure.ac b/configure.ac index 271f4e4240de..1a0305742346 100644 --- a/configure.ac +++ b/configure.ac @@ -1951,7 +1951,7 @@ AC_ARG_WITH(system-liblangtag, AC_ARG_WITH(webdav, AS_HELP_STRING([--with-webdav], [Specify which library to use for webdav implementation. - Possible values: "neon", "serf", "no". The default value is "neon". + Possible values: "neon", "serf", "curl", "no". The default value is "neon". Example: --with-webdav="serf"]), WITH_WEBDAV=$withval, WITH_WEBDAV="neon") @@ -9817,6 +9817,13 @@ if test $_os = iOS -o $_os = Android; then fi AC_MSG_CHECKING([for webdav library]) case "$WITH_WEBDAV" in +curl) + AC_MSG_RESULT([curl]) + # curl is already mandatory (almost) and checked elsewhere + if test "$enable_curl" = "no"; then + AC_MSG_ERROR(["--with-webdav=curl conflicts with --disable-curl"]) + fi + ;; serf) AC_MSG_RESULT([serf]) # Check for system apr-util commit f9d82db9f73b6e9e20d79fc9392baee28011113b Author: Michael Stahl <michael.st...@allotropia.de> AuthorDate: Thu Jan 20 12:48:50 2022 +0100 Commit: Michael Stahl <michael.st...@allotropia.de> CommitDate: Wed Mar 9 19:00:30 2022 +0100 comphelper: fix UAF in SequenceOutputStreamService dtor Change-Id: I91f77ee9ab4d509ebee3d04f94a3c63986de0ef1 Reviewed-on: https://gerrit.libreoffice.org/c/core/+/128657 Tested-by: Jenkins Reviewed-by: Michael Stahl <michael.st...@allotropia.de> (cherry picked from commit 03e8e1a408eef3c8acc5545416eda9d0938e21f7) Reviewed-on: https://gerrit.libreoffice.org/c/core/+/128669 Reviewed-by: Thorsten Behrens <thorsten.behr...@allotropia.de> (cherry picked from commit 512e8c5ebb8cc398ae3ca316de70f008b9646011) diff --git a/comphelper/source/streaming/seqoutputstreamserv.cxx b/comphelper/source/streaming/seqoutputstreamserv.cxx index 757df72c3f58..5c7de4d87080 100644 --- a/comphelper/source/streaming/seqoutputstreamserv.cxx +++ b/comphelper/source/streaming/seqoutputstreamserv.cxx @@ -62,8 +62,9 @@ private: ::osl::Mutex m_aMutex; - uno::Reference< io::XOutputStream > m_xOutputStream; + // WARNING: dtor of m_xOutputStream writes into m_aSequence so that must live longer! uno::Sequence< ::sal_Int8 > m_aSequence; + uno::Reference< io::XOutputStream > m_xOutputStream; }; SequenceOutputStreamService::SequenceOutputStreamService() { commit 5baaae8f89b7d61bc5a9349cb6fd42a5a3423af3 Author: Caolán McNamara <caol...@redhat.com> AuthorDate: Thu Nov 25 08:45:41 2021 +0000 Commit: Michael Stahl <michael.st...@allotropia.de> CommitDate: Wed Mar 9 19:00:30 2022 +0100 required curl_multi_wakeup is only in >= curl 7.68.0 Change-Id: I53878de49549098ff90ef1f68f88880a6eb5bf55 Reviewed-on: https://gerrit.libreoffice.org/c/core/+/125797 Reviewed-by: Michael Stahl <michael.st...@allotropia.de> Reviewed-by: Caolán McNamara <caol...@redhat.com> Tested-by: Caolán McNamara <caol...@redhat.com> (cherry picked from commit 70dd00f23ee53ea5ae7f3c292906335fb6000ab4) diff --git a/configure.ac b/configure.ac index 0aab88e1d64a..271f4e4240de 100644 --- a/configure.ac +++ b/configure.ac @@ -9279,32 +9279,7 @@ if test "$with_system_curl" = "yes"; then AC_MSG_RESULT([external]) SYSTEM_CURL=TRUE - # First try PKGCONFIG and then fall back - PKG_CHECK_MODULES(CURL, libcurl >= 7.19.4,, [:]) - - if test -n "$CURL_PKG_ERRORS"; then - AC_PATH_PROG(CURLCONFIG, curl-config) - if test -z "$CURLCONFIG"; then - AC_MSG_ERROR([curl development files not found]) - fi - CURL_LIBS=`$CURLCONFIG --libs` - FilterLibs "${CURL_LIBS}" - CURL_LIBS="${filteredlibs}" - CURL_CFLAGS=$("$CURLCONFIG" --cflags | sed -e "s/-I/${ISYSTEM?}/g") - curl_version=`$CURLCONFIG --version | $SED -e 's/^libcurl //'` - - AC_MSG_CHECKING([whether libcurl is >= 7.19.4]) - case $curl_version in - dnl brackets doubled below because Autoconf uses them as m4 quote characters, - dnl so they need to be doubled to end up in the configure script - 7.19.4|7.19.[[5-9]]|7.[[2-9]]?.*|7.???.*|[[8-9]].*|[[1-9]][[0-9]].*) - AC_MSG_RESULT([yes]) - ;; - *) - AC_MSG_ERROR([no, you have $curl_version]) - ;; - esac - fi + PKG_CHECK_MODULES(CURL, libcurl >= 7.68.0) ENABLE_CURL=TRUE elif test $_os = iOS; then commit aa2887120c95316fa9d9a0c9a1bf453360868e6d Author: Michael Stahl <michael.st...@allotropia.de> AuthorDate: Wed Oct 27 21:07:48 2021 +0200 Commit: Michael Stahl <michael.st...@allotropia.de> CommitDate: Wed Mar 9 19:00:30 2022 +0100 curl: build with zlib on WNT Change-Id: I53eb6ed41fb8a17a79f72807df15822e9c1c6e88 Reviewed-on: https://gerrit.libreoffice.org/c/core/+/124290 Tested-by: Jenkins Reviewed-by: Michael Stahl <michael.st...@allotropia.de> (cherry picked from commit 4b7f8f7cf3146317ae2770a766efe48f7a295f72) diff --git a/RepositoryExternal.mk b/RepositoryExternal.mk index f202f3d25ca0..c3f29650b6bd 100644 --- a/RepositoryExternal.mk +++ b/RepositoryExternal.mk @@ -2725,7 +2725,7 @@ $(call gb_LinkTarget_set_include,$(1),\ ifeq ($(COM),MSC) $(call gb_LinkTarget_add_libs,$(1),\ - $(call gb_UnpackedTarball_get_dir,curl)/builds/libcurl-vc12-$(if $(filter X86_64,$(CPUNAME)),x64,x86)-$(if $(MSVC_USE_DEBUG_RUNTIME),debug,release)-dll-ipv6-sspi-schannel/lib/libcurl$(if $(MSVC_USE_DEBUG_RUNTIME),_debug).lib \ + $(call gb_UnpackedTarball_get_dir,curl)/builds/libcurl-vc12-$(if $(filter X86_64,$(CPUNAME)),x64,x86)-$(if $(MSVC_USE_DEBUG_RUNTIME),debug,release)-dll-zlib-static-ipv6-sspi-schannel/lib/libcurl$(if $(MSVC_USE_DEBUG_RUNTIME),_debug).lib \ ) else $(call gb_LinkTarget_add_libs,$(1),\ diff --git a/external/curl/ExternalPackage_curl.mk b/external/curl/ExternalPackage_curl.mk index 1fb360c85ca9..943a05147c56 100644 --- a/external/curl/ExternalPackage_curl.mk +++ b/external/curl/ExternalPackage_curl.mk @@ -14,7 +14,7 @@ $(eval $(call gb_ExternalPackage_use_external_project,curl,curl)) ifneq ($(DISABLE_DYNLOADING),TRUE) ifeq ($(COM),MSC) -$(eval $(call gb_ExternalPackage_add_file,curl,$(LIBO_LIB_FOLDER)/libcurl$(if $(MSVC_USE_DEBUG_RUNTIME),_debug).dll,builds/libcurl-vc12-$(if $(filter X86_64,$(CPUNAME)),x64,x86)-$(if $(MSVC_USE_DEBUG_RUNTIME),debug,release)-dll-ipv6-sspi-schannel/bin/libcurl$(if $(MSVC_USE_DEBUG_RUNTIME),_debug).dll)) +$(eval $(call gb_ExternalPackage_add_file,curl,$(LIBO_LIB_FOLDER)/libcurl$(if $(MSVC_USE_DEBUG_RUNTIME),_debug).dll,builds/libcurl-vc12-$(if $(filter X86_64,$(CPUNAME)),x64,x86)-$(if $(MSVC_USE_DEBUG_RUNTIME),debug,release)-dll-zlib-static-ipv6-sspi-schannel/bin/libcurl$(if $(MSVC_USE_DEBUG_RUNTIME),_debug).dll)) else ifeq ($(OS),MACOSX) $(eval $(call gb_ExternalPackage_add_file,curl,$(LIBO_LIB_FOLDER)/libcurl.4.dylib,lib/.libs/libcurl.4.dylib)) else ifeq ($(OS),AIX) diff --git a/external/curl/ExternalProject_curl.mk b/external/curl/ExternalProject_curl.mk index 06cc96c2e8de..233e79156d96 100644 --- a/external/curl/ExternalProject_curl.mk +++ b/external/curl/ExternalProject_curl.mk @@ -85,6 +85,7 @@ $(call gb_ExternalProject_get_state_target,curl,build): ENABLE_IPV6=yes \ ENABLE_SSPI=yes \ ENABLE_WINSSL=yes \ + WITH_ZLIB=static \ ,winbuild) endif diff --git a/external/curl/UnpackedTarball_curl.mk b/external/curl/UnpackedTarball_curl.mk index 46848ae79ae0..e6e07abe89b3 100644 --- a/external/curl/UnpackedTarball_curl.mk +++ b/external/curl/UnpackedTarball_curl.mk @@ -19,6 +19,7 @@ $(eval $(call gb_UnpackedTarball_fix_end_of_line,curl,\ $(eval $(call gb_UnpackedTarball_add_patches,curl,\ external/curl/curl-msvc.patch.1 \ + external/curl/curl-msvc-zlib.patch.1 \ external/curl/curl-msvc-disable-protocols.patch.1 \ external/curl/curl-7.26.0_win-proxy.patch \ external/curl/zlib.patch.0 \ diff --git a/external/curl/curl-msvc-zlib.patch.1 b/external/curl/curl-msvc-zlib.patch.1 new file mode 100644 index 000000000000..654303c21874 --- /dev/null +++ b/external/curl/curl-msvc-zlib.patch.1 @@ -0,0 +1,16 @@ +find internal zlib in nmake buildsystem + +--- curl/winbuild/MakefileBuild.vc.orig2 2021-10-27 20:44:48.685237000 +0200 ++++ curl/winbuild/MakefileBuild.vc 2021-10-27 20:47:23.792407400 +0200 +@@ -222,8 +222,9 @@ + ZLIB_LIB_DIR = $(ZLIB_PATH)\lib + ZLIB_LFLAGS = $(ZLIB_LFLAGS) "/LIBPATH:$(ZLIB_LIB_DIR)" + !ELSE +-ZLIB_INC_DIR = $(DEVEL_INCLUDE) +-ZLIB_LIB_DIR = $(DEVEL_LIB) ++ZLIB_INC_DIR = $(WORKDIR)/UnpackedTarball/zlib ++ZLIB_LIB_DIR = $(WORKDIR)/LinkTarget/StaticLibrary ++ZLIB_LFLAGS = $(ZLIB_LFLAGS) "/LIBPATH:$(ZLIB_LIB_DIR)" + !ENDIF + + # Depending on how zlib is built the libraries have different names, we commit 7399b1eb25a1a856a1e203bcfb6267a14a1ed70e Author: Michael Stahl <michael.st...@allotropia.de> AuthorDate: Mon Oct 4 17:16:59 2021 +0200 Commit: Michael Stahl <michael.st...@allotropia.de> CommitDate: Wed Mar 9 19:00:30 2022 +0100 comphelper: fix SequenceOutputStreamService::getWrittenBytes() With com.sun.star.io.Pipe, you must call closeOutput() or reading from the other end will block forever. If you use com.sun.star.io.SequenceOutputStream, you can't read the output after calling closeOutput() on it because for no good reason it throws a NotConnectedException. Let's just fix it so that a sequence of closeOutput() then getWrittenBytes() returns the finished buffer. Change-Id: Ia6ce28ec05e00e5f1c703dea8669e0271c0b9c20 Reviewed-on: https://gerrit.libreoffice.org/c/core/+/123057 Tested-by: Jenkins Reviewed-by: Michael Stahl <michael.st...@allotropia.de> (cherry picked from commit 1a3b3eda6ca1c8e063ba6db9e9f34f999ec92c49) diff --git a/comphelper/source/streaming/seqoutputstreamserv.cxx b/comphelper/source/streaming/seqoutputstreamserv.cxx index cadc691c8f03..757df72c3f58 100644 --- a/comphelper/source/streaming/seqoutputstreamserv.cxx +++ b/comphelper/source/streaming/seqoutputstreamserv.cxx @@ -112,6 +112,7 @@ void SAL_CALL SequenceOutputStreamService::closeOutput() if ( !m_xOutputStream.is() ) throw io::NotConnectedException(); + m_xOutputStream->flush(); m_xOutputStream->closeOutput(); m_xOutputStream.clear(); } @@ -120,10 +121,13 @@ void SAL_CALL SequenceOutputStreamService::closeOutput() uno::Sequence< ::sal_Int8 > SAL_CALL SequenceOutputStreamService::getWrittenBytes() { ::osl::MutexGuard aGuard( m_aMutex ); - if ( !m_xOutputStream.is() ) - throw io::NotConnectedException(); - m_xOutputStream->flush(); + if (m_xOutputStream.is()) + { + m_xOutputStream->flush(); + } + // else: no exception, just return the finished sequence + return m_aSequence; } commit 59f4780a113bd7bf8cbec08f3ec30c7f620e0b88 Author: Michael Stahl <michael.st...@allotropia.de> AuthorDate: Fri Oct 1 17:42:09 2021 +0200 Commit: Michael Stahl <michael.st...@allotropia.de> CommitDate: Wed Mar 9 19:00:30 2022 +0100 curl: patch invalid format string in debug log This causes: soffice.bin: sendf.c:243: Curl_infof: Assertion `!strchr(fmt, '\n')' failed. Change-Id: I5a78b2225f6769cc49025e1e73ce72cd3d6bec16 Reviewed-on: https://gerrit.libreoffice.org/c/core/+/122963 Tested-by: Jenkins Reviewed-by: Michael Stahl <michael.st...@allotropia.de> (cherry picked from commit 68a3df2f1282f40fa4accaed21a69a84d76be37d) diff --git a/external/curl/UnpackedTarball_curl.mk b/external/curl/UnpackedTarball_curl.mk index 1cdb64c0bca2..46848ae79ae0 100644 --- a/external/curl/UnpackedTarball_curl.mk +++ b/external/curl/UnpackedTarball_curl.mk @@ -22,6 +22,7 @@ $(eval $(call gb_UnpackedTarball_add_patches,curl,\ external/curl/curl-msvc-disable-protocols.patch.1 \ external/curl/curl-7.26.0_win-proxy.patch \ external/curl/zlib.patch.0 \ + external/curl/curl-debug.patch.1 \ )) ifeq ($(SYSTEM_NSS),) diff --git a/external/curl/curl-debug.patch.1 b/external/curl/curl-debug.patch.1 new file mode 100644 index 000000000000..a000913ed335 --- /dev/null +++ b/external/curl/curl-debug.patch.1 @@ -0,0 +1,11 @@ +--- curl/lib/vtls/nss.c.orig 2021-10-01 17:34:59.302663021 +0200 ++++ curl/lib/vtls/nss.c 2021-10-01 17:35:55.376666010 +0200 +@@ -955,7 +955,7 @@ + subject = CERT_NameToAscii(&cert->subject); + issuer = CERT_NameToAscii(&cert->issuer); + common_name = CERT_GetCommonName(&cert->subject); +- infof(data, "subject: %s\n", subject); ++ infof(data, "subject: %s", subject); + + CERT_GetCertTimes(cert, ¬Before, ¬After); + PR_ExplodeTime(notBefore, PR_GMTParameters, &printableTime); commit 0a36fd14f66b0f31ee3257217ab08de1475bec61 Author: Michael Stahl <michael.st...@allotropia.de> AuthorDate: Wed Mar 9 15:51:53 2022 +0100 Commit: Michael Stahl <michael.st...@allotropia.de> CommitDate: Wed Mar 9 19:00:09 2022 +0100 ucb: add webdav-curl from libreoffice-7-3 branch TODO: ucb/Library_ucpdav1.mk changes Change-Id: I528bd0b4ccbd3c8cfe98b74a94269ef99292f82e diff --git a/include/sal/log-areas.dox b/include/sal/log-areas.dox index 4711bd8a6a0f..31483cb3b473 100644 --- a/include/sal/log-areas.dox +++ b/include/sal/log-areas.dox @@ -432,6 +432,7 @@ certain functionality. @li @c ucb.ucp.ftp @li @c ucb.ucp.gio @li @c ucb.ucp.webdav +@li @c ucb.ucp.webdav.curl @section unotools diff --git a/solenv/clang-format/blacklist b/solenv/clang-format/blacklist index 753d4c963ca3..61fbcaeca801 100644 --- a/solenv/clang-format/blacklist +++ b/solenv/clang-format/blacklist @@ -16720,6 +16720,41 @@ ucb/source/ucp/tdoc/tdoc_storage.cxx ucb/source/ucp/tdoc/tdoc_storage.hxx ucb/source/ucp/tdoc/tdoc_uri.cxx ucb/source/ucp/tdoc/tdoc_uri.hxx +ucb/source/ucp/webdav-curl/ContentProperties.cxx +ucb/source/ucp/webdav-curl/ContentProperties.hxx +ucb/source/ucp/webdav-curl/DAVAuthListener.hxx +ucb/source/ucp/webdav-curl/DAVAuthListenerImpl.hxx +ucb/source/ucp/webdav-curl/DAVException.hxx +ucb/source/ucp/webdav-curl/DAVProperties.cxx +ucb/source/ucp/webdav-curl/DAVProperties.hxx +ucb/source/ucp/webdav-curl/DAVRequestEnvironment.hxx +ucb/source/ucp/webdav-curl/DAVResource.hxx +ucb/source/ucp/webdav-curl/DAVResourceAccess.cxx +ucb/source/ucp/webdav-curl/DAVResourceAccess.hxx +ucb/source/ucp/webdav-curl/DAVSession.hxx +ucb/source/ucp/webdav-curl/DAVSessionFactory.cxx +ucb/source/ucp/webdav-curl/DAVTypes.cxx +ucb/source/ucp/webdav-curl/DAVTypes.hxx +ucb/source/ucp/webdav-curl/DateTimeHelper.cxx +ucb/source/ucp/webdav-curl/DateTimeHelper.hxx +ucb/source/ucp/webdav-curl/PropertyMap.hxx +ucb/source/ucp/webdav-curl/PropfindCache.cxx +ucb/source/ucp/webdav-curl/PropfindCache.hxx +ucb/source/ucp/webdav-curl/SerfLockStore.cxx +ucb/source/ucp/webdav-curl/SerfLockStore.hxx +ucb/source/ucp/webdav-curl/UCBDeadPropertyValue.cxx +ucb/source/ucp/webdav-curl/UCBDeadPropertyValue.hxx +ucb/source/ucp/webdav-curl/webdavcontent.cxx +ucb/source/ucp/webdav-curl/webdavcontent.hxx +ucb/source/ucp/webdav-curl/webdavcontentcaps.cxx +ucb/source/ucp/webdav-curl/webdavdatasupplier.cxx +ucb/source/ucp/webdav-curl/webdavdatasupplier.hxx +ucb/source/ucp/webdav-curl/webdavprovider.cxx +ucb/source/ucp/webdav-curl/webdavprovider.hxx +ucb/source/ucp/webdav-curl/webdavresponseparser.cxx +ucb/source/ucp/webdav-curl/webdavresultset.cxx +ucb/source/ucp/webdav-curl/webdavresultset.hxx +ucb/source/ucp/webdav-curl/webdavservices.cxx ucb/source/ucp/webdav-neon/ContentProperties.cxx ucb/source/ucp/webdav-neon/ContentProperties.hxx ucb/source/ucp/webdav-neon/DAVAuthListener.hxx diff --git a/ucb/source/ucp/webdav-curl/ContentProperties.cxx b/ucb/source/ucp/webdav-curl/ContentProperties.cxx new file mode 100644 index 000000000000..59773db1cbdc --- /dev/null +++ b/ucb/source/ucp/webdav-curl/ContentProperties.cxx @@ -0,0 +1,568 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include <memory> +#include <com/sun/star/util/DateTime.hpp> +#include "CurlUri.hxx" +#include "DAVResource.hxx" +#include "DAVProperties.hxx" +#include "DateTimeHelper.hxx" +#include "webdavprovider.hxx" +#include "ContentProperties.hxx" + +#include <sal/log.hxx> + +using namespace com::sun::star; +using namespace http_dav_ucp; + +/* +============================================================================= + + Property Mapping + +============================================================================= +HTTP (entity header) WebDAV (property) UCB (property) +============================================================================= + +Allow +Content-Encoding +Content-Language getcontentlanguage +Content-Length getcontentlength Size +Content-Location +Content-MD5 +Content-Range +Content-Type getcontenttype MediaType +Expires +Last-Modified getlastmodified DateModified + creationdate DateCreated + resourcetype IsFolder,IsDocument,ContentType + displayname +ETag (actually getetag +a response header ) + lockdiscovery + supportedlock + source + Title (always taken from URI) + +============================================================================= + +Important: HTTP headers will not be mapped to DAV properties; only to UCB + properties. (Content-Length,Content-Type,Last-Modified) +*/ + + +// ContentProperties Implementation. + + +// static member! +uno::Any ContentProperties::m_aEmptyAny; + +ContentProperties::ContentProperties( const DAVResource& rResource ) +: m_xProps( new PropertyValueMap ), + m_bTrailingSlash( false ) +{ + assert(!rResource.uri.isEmpty() && + "ContentProperties ctor - Empty resource URI!"); + + // Title + try + { + CurlUri const aURI( rResource.uri ); + m_aEscapedTitle = aURI.GetPathBaseName(); + + (*m_xProps)[ OUString( "Title" ) ] + = PropertyValue( + uno::makeAny( aURI.GetPathBaseNameUnescaped() ), true ); + } + catch ( DAVException const & ) + { + (*m_xProps)[ OUString( "Title" ) ] + = PropertyValue( + uno::makeAny( + OUString( "*** unknown ***" ) ), + true ); + } + + for ( const auto& rProp : rResource.properties ) + { + addProperty( rProp ); + } + + if ( rResource.uri.endsWith("/") ) + m_bTrailingSlash = true; +} + + +ContentProperties::ContentProperties( + const OUString & rTitle, bool bFolder ) +: m_xProps( new PropertyValueMap ), + m_bTrailingSlash( false ) +{ + (*m_xProps)[ OUString( "Title" ) ] + = PropertyValue( uno::makeAny( rTitle ), true ); + (*m_xProps)[ OUString( "IsFolder" ) ] + = PropertyValue( uno::makeAny( bFolder ), true ); + (*m_xProps)[ OUString( "IsDocument" ) ] + = PropertyValue( uno::makeAny( bool( !bFolder ) ), true ); +} + + +ContentProperties::ContentProperties( const OUString & rTitle ) +: m_xProps( new PropertyValueMap ), + m_bTrailingSlash( false ) +{ + (*m_xProps)[ OUString( "Title" ) ] + = PropertyValue( uno::makeAny( rTitle ), true ); +} + + +ContentProperties::ContentProperties() +: m_xProps( new PropertyValueMap ), + m_bTrailingSlash( false ) +{ +} + + +ContentProperties::ContentProperties( const ContentProperties & rOther ) +: m_aEscapedTitle( rOther.m_aEscapedTitle ), + m_xProps( rOther.m_xProps + ? new PropertyValueMap( *rOther.m_xProps ) + : new PropertyValueMap ), + m_bTrailingSlash( rOther.m_bTrailingSlash ) +{ +} + + +bool ContentProperties::contains( const OUString & rName ) const +{ + if ( get( rName ) ) + return true; + else + return false; +} + + +const uno::Any & ContentProperties::getValue( + const OUString & rName ) const +{ + const PropertyValue * pProp = get( rName ); + if ( pProp ) + return pProp->value(); + else + return m_aEmptyAny; +} + + +const PropertyValue * ContentProperties::get( + const OUString & rName ) const +{ + PropertyValueMap::const_iterator it = m_xProps->find( rName ); + const PropertyValueMap::const_iterator end = m_xProps->end(); + + if ( it == end ) + { + it = std::find_if(m_xProps->cbegin(), end, + [&rName](const PropertyValueMap::value_type& rEntry) { + return rEntry.first.equalsIgnoreAsciiCase( rName ); + }); + if ( it != end ) + return &(*it).second; + + return nullptr; + } + else + return &(*it).second; +} + + +// static +void ContentProperties::UCBNamesToDAVNames( + const uno::Sequence< beans::Property > & rProps, + std::vector< OUString > & propertyNames ) +{ + + // Assemble list of DAV properties to obtain from server. + // Append DAV properties needed to obtain requested UCB props. + + + // DAV UCB + // creationdate <- DateCreated + // getlastmodified <- DateModified + // getcontenttype <- MediaType + // getcontentlength <- Size + // resourcetype <- IsFolder, IsDocument, ContentType + // (taken from URI) <- Title + + bool bCreationDate = false; + bool bLastModified = false; + bool bContentType = false; + bool bContentLength = false; + bool bResourceType = false; + + sal_Int32 nCount = rProps.getLength(); + for ( sal_Int32 n = 0; n < nCount; ++n ) + { + const beans::Property & rProp = rProps[ n ]; + + if ( rProp.Name == "Title" ) + { + // Title is always obtained from resource's URI. + continue; + } + else if ( rProp.Name == "DateCreated" || + ( rProp.Name == DAVProperties::CREATIONDATE ) ) + { + if ( !bCreationDate ) + { + propertyNames.push_back( DAVProperties::CREATIONDATE ); + bCreationDate = true; + } + } + else if ( rProp.Name == "DateModified" || + ( rProp.Name == DAVProperties::GETLASTMODIFIED ) ) + { + if ( !bLastModified ) + { + propertyNames.push_back( + DAVProperties::GETLASTMODIFIED ); + bLastModified = true; + } + } + else if ( rProp.Name == "MediaType" || + ( rProp.Name == DAVProperties::GETCONTENTTYPE ) ) + { + if ( !bContentType ) + { + propertyNames.push_back( + DAVProperties::GETCONTENTTYPE ); + bContentType = true; + } + } + else if ( rProp.Name == "Size" || + ( rProp.Name == DAVProperties::GETCONTENTLENGTH ) ) + { + if ( !bContentLength ) + { + propertyNames.push_back( + DAVProperties::GETCONTENTLENGTH ); + bContentLength = true; + } + } + else if ( rProp.Name == "ContentType" || + rProp.Name == "IsDocument" || + rProp.Name == "IsFolder" || + ( rProp.Name == DAVProperties::RESOURCETYPE ) ) + { + if ( !bResourceType ) + { + propertyNames.push_back( DAVProperties::RESOURCETYPE ); + bResourceType = true; + } + } + else + { + propertyNames.push_back( rProp.Name ); + } + } +} + + +// static +void ContentProperties::UCBNamesToHTTPNames( + const uno::Sequence< beans::Property > & rProps, + std::vector< OUString > & propertyNames ) +{ + + // Assemble list of HTTP header names to obtain from server. + // Append HTTP headers needed to obtain requested UCB props. + + + // HTTP UCB + // Last-Modified <- DateModified + // Content-Type <- MediaType + // Content-Length <- Size + + sal_Int32 nCount = rProps.getLength(); + for ( sal_Int32 n = 0; n < nCount; ++n ) + { + const beans::Property & rProp = rProps[ n ]; + + if ( rProp.Name == "DateModified" ) + { + propertyNames.push_back( OUString( "Last-Modified" ) ); + } + else if ( rProp.Name == "MediaType" ) + { + propertyNames.push_back( OUString( "Content-Type" ) ); + } + else if ( rProp.Name == "Size" ) + { + propertyNames.push_back( OUString( "Content-Length" ) ); + } + else + { + propertyNames.push_back( rProp.Name ); + } + } +} + + +bool ContentProperties::containsAllNames( + const uno::Sequence< beans::Property >& rProps, + std::vector< OUString > & rNamesNotContained ) const +{ + rNamesNotContained.clear(); + + sal_Int32 nCount = rProps.getLength(); + for ( sal_Int32 n = 0; n < nCount; ++n ) + { + const OUString & rName = rProps[ n ].Name; + if ( !contains( rName ) ) + { + // Not found. + rNamesNotContained.push_back( rName ); + } + } + + return ( rNamesNotContained.size() == 0 ); +} + + +void ContentProperties::addProperties( + const std::vector< OUString > & rProps, + const ContentProperties & rContentProps ) +{ + for ( const OUString & rName : rProps ) + { + if ( !contains( rName ) ) // ignore duplicates + { + const PropertyValue * pProp = rContentProps.get( rName ); + if ( pProp ) + { + // Add it. + addProperty( rName, pProp->value(), pProp->isCaseSensitive() ); + } + else + { + addProperty( rName, uno::Any(), false ); + } + } + } +} + +void ContentProperties::addProperty( const DAVPropertyValue & rProp ) +{ + addProperty( rProp.Name, rProp.Value, rProp.IsCaseSensitive ); +} + + +void ContentProperties::addProperty( const OUString & rName, + const css::uno::Any & rValue, + bool bIsCaseSensitive ) +{ + if ( rName == DAVProperties::CREATIONDATE ) + { + // Map DAV:creationdate to UCP:DateCreated + OUString aValue; + rValue >>= aValue; + util::DateTime aDate; + DateTimeHelper::convert( aValue, aDate ); + + (*m_xProps)[ OUString( "DateCreated" ) ] + = PropertyValue( uno::makeAny( aDate ), true ); + } + // else if ( rName.equals( DAVProperties::DISPLAYNAME ) ) + // { + // } + // else if ( rName.equals( DAVProperties::GETCONTENTLANGUAGE ) ) + // { + // } + else if ( rName == DAVProperties::GETCONTENTLENGTH ) + { + // Map DAV:getcontentlength to UCP:Size + OUString aValue; + rValue >>= aValue; + + (*m_xProps)[ OUString( "Size" ) ] + = PropertyValue( uno::makeAny( aValue.toInt64() ), true ); + } + else if ( rName.equalsIgnoreAsciiCase( "Content-Length" ) ) + { + // Do NOT map Content-Length entity header to DAV:getcontentlength! + // Only DAV resources have this property. + + // Map Content-Length entity header to UCP:Size + OUString aValue; + rValue >>= aValue; + + (*m_xProps)[ OUString( "Size" ) ] + = PropertyValue( uno::makeAny( aValue.toInt64() ), true ); + } + else if ( rName == DAVProperties::GETCONTENTTYPE ) + { + // Map DAV:getcontenttype to UCP:MediaType (1:1) + (*m_xProps)[ OUString( "MediaType" ) ] + = PropertyValue( rValue, true ); + } + else if ( rName.equalsIgnoreAsciiCase( "Content-Type" ) ) + { + // Do NOT map Content-Type entity header to DAV:getcontenttype! + // Only DAV resources have this property. + + // Map DAV:getcontenttype to UCP:MediaType (1:1) + (*m_xProps)[ OUString( "MediaType" ) ] + = PropertyValue( rValue, true ); + } + // else if ( rName.equals( DAVProperties::GETETAG ) ) + // { + // } + else if ( rName == DAVProperties::GETLASTMODIFIED ) + { + // Map the DAV:getlastmodified entity header to UCP:DateModified + OUString aValue; + rValue >>= aValue; + util::DateTime aDate; + DateTimeHelper::convert( aValue, aDate ); + + (*m_xProps)[ OUString( "DateModified" ) ] + = PropertyValue( uno::makeAny( aDate ), true ); + } + else if ( rName.equalsIgnoreAsciiCase( "Last-Modified" ) ) + { + // Do not map Last-Modified entity header to DAV:getlastmodified! + // Only DAV resources have this property. + + // Map the Last-Modified entity header to UCP:DateModified + OUString aValue; + rValue >>= aValue; + util::DateTime aDate; + DateTimeHelper::convert( aValue, aDate ); + + (*m_xProps)[ OUString( "DateModified" ) ] + = PropertyValue( uno::makeAny( aDate ), true ); + } + // else if ( rName.equals( DAVProperties::LOCKDISCOVERY ) ) + // { + // } + else if ( rName == DAVProperties::RESOURCETYPE ) + { + OUString aValue; + rValue >>= aValue; + + // Map DAV:resourcetype to UCP:IsFolder, UCP:IsDocument, UCP:ContentType + bool bFolder = + aValue.equalsIgnoreAsciiCase( "collection" ); + + (*m_xProps)[ OUString( "IsFolder" ) ] + = PropertyValue( uno::makeAny( bFolder ), true ); + (*m_xProps)[ OUString( "IsDocument" ) ] + = PropertyValue( uno::makeAny( bool( !bFolder ) ), true ); + (*m_xProps)[ OUString( "ContentType" ) ] + = PropertyValue( uno::makeAny( bFolder + ? OUString( WEBDAV_COLLECTION_TYPE ) + : OUString( WEBDAV_CONTENT_TYPE ) ), true ); + } + // else if ( rName.equals( DAVProperties::SUPPORTEDLOCK ) ) + // { + // } + + // Save property. + (*m_xProps)[ rName ] = PropertyValue( rValue, bIsCaseSensitive ); +} + + +// CachableContentProperties Implementation. + + +namespace +{ + bool isCachable( OUString const & rName, + bool isCaseSensitive ) + { + const OUString aNonCachableProps [] = + { + DAVProperties::LOCKDISCOVERY, + + DAVProperties::GETETAG, + OUString( "ETag" ), + + OUString( "DateModified" ), + OUString( "Last-Modified" ), + DAVProperties::GETLASTMODIFIED, + + OUString( "Size" ), + OUString( "Content-Length" ), + DAVProperties::GETCONTENTLENGTH, + + OUString( "Date" ) + }; + + for ( sal_uInt32 n = 0; + n < ( sizeof( aNonCachableProps ) + / sizeof( aNonCachableProps[ 0 ] ) ); + ++n ) + { + if ( isCaseSensitive ) + { + if ( rName.equals( aNonCachableProps[ n ] ) ) + return false; + } + else + if ( rName.equalsIgnoreAsciiCase( aNonCachableProps[ n ] ) ) + return false; + } + return true; + } + +} // namespace + + +CachableContentProperties::CachableContentProperties( + const ContentProperties & rProps ) +{ + addProperties( rProps ); +} + + +void CachableContentProperties::addProperties( + const ContentProperties & rProps ) +{ + const std::unique_ptr< PropertyValueMap > & props = rProps.getProperties(); + + for ( const auto& rProp : *props ) + { + if ( isCachable( rProp.first, rProp.second.isCaseSensitive() ) ) + m_aProps.addProperty( rProp.first, + rProp.second.value(), + rProp.second.isCaseSensitive() ); + } +} + + +void CachableContentProperties::addProperties( + const std::vector< DAVPropertyValue > & rProps ) +{ + for ( const auto& rProp : rProps ) + { + if ( isCachable( rProp.Name, rProp.IsCaseSensitive ) ) + m_aProps.addProperty( rProp ); + } +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/ucb/source/ucp/webdav-curl/ContentProperties.hxx b/ucb/source/ucp/webdav-curl/ContentProperties.hxx new file mode 100644 index 000000000000..0256450c2f90 --- /dev/null +++ b/ucb/source/ucp/webdav-curl/ContentProperties.hxx @@ -0,0 +1,173 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + + +#pragma once + +#include <memory> +#include <unordered_map> +#include <vector> +#include <rtl/ustring.hxx> +#include <com/sun/star/uno/Any.hxx> +#include <com/sun/star/uno/Sequence.hxx> +#include "DAVResource.hxx" + +namespace com::sun::star::beans { + struct Property; +} + +namespace http_dav_ucp +{ + +struct DAVResource; + +// PropertyValueMap. +class PropertyValue +{ +private: + css::uno::Any m_aValue; + bool m_bIsCaseSensitive; + +public: + PropertyValue() + : m_bIsCaseSensitive( true ) {} + + explicit PropertyValue( const css::uno::Any & rValue, + bool bIsCaseSensitive ) + : m_aValue( rValue), + m_bIsCaseSensitive( bIsCaseSensitive ) {} + + bool isCaseSensitive() const { return m_bIsCaseSensitive; } + const css::uno::Any & value() const { return m_aValue; } + +}; + +typedef std::unordered_map< OUString, PropertyValue > PropertyValueMap; + +class ContentProperties +{ +public: + ContentProperties(); + + explicit ContentProperties( const DAVResource& rResource ); + + // Mini props for transient contents. + ContentProperties( const OUString & rTitle, bool bFolder ); + + // Micro props for non-existing contents. + explicit ContentProperties( const OUString & rTitle ); + + ContentProperties( const ContentProperties & rOther ); + + bool contains( const OUString & rName ) const; + + const css::uno::Any& getValue( const OUString & rName ) const; + + // Maps the UCB property names contained in rProps with their DAV property + // counterparts, if possible. All unmappable properties will be included + // unchanged in resulting vector. + // The vector filled by this method can directly be handed over to + // DAVResourceAccess::PROPFIND. The result from PROPFIND + // (vector< DAVResource >) can be used to create a ContentProperties + // instance which can map DAV properties back to UCB properties. + static void UCBNamesToDAVNames( const css::uno::Sequence< css::beans::Property > & rProps, + std::vector< OUString > & resources ); + + // Maps the UCB property names contained in rProps with their HTTP header + // counterparts, if possible. All unmappable properties will be included + // unchanged in resulting vector. + // The vector filled by this method can directly be handed over to + // DAVResourceAccess::HEAD. The result from HEAD (vector< DAVResource >) + // can be used to create a ContentProperties instance which can map header + // names back to UCB properties. + static void UCBNamesToHTTPNames( const css::uno::Sequence< css::beans::Property > & rProps, + std::vector< OUString > & resources ); + + // return true, if all properties contained in rProps are contained in + // this ContentProperties instance. Otherwise, false will be returned. + // rNamesNotContained contain the missing names. + bool containsAllNames( + const css::uno::Sequence< css::beans::Property >& rProps, + std::vector< OUString > & rNamesNotContained ) const; + + // adds all properties described by rProps that are actually contained in + // rContentProps to this instance. In case of duplicates the value + // already contained in this will left unchanged. + void addProperties( const std::vector< OUString > & rProps, + const ContentProperties & rContentProps ); + + // overwrites probably existing entry. + void addProperty( const OUString & rName, + const css::uno::Any & rValue, + bool bIsCaseSensitive ); + + // overwrites probably existing entry. + void addProperty( const DAVPropertyValue & rProp ); + + bool isTrailingSlash() const { return m_bTrailingSlash; } + + const OUString & getEscapedTitle() const { return m_aEscapedTitle; } + + // Not good to expose implementation details, but this is actually an + // internal class. + const std::unique_ptr< PropertyValueMap > & getProperties() const + { return m_xProps; } + +private: + OUString m_aEscapedTitle; + std::unique_ptr< PropertyValueMap > m_xProps; + bool m_bTrailingSlash; + + static css::uno::Any m_aEmptyAny; + + ContentProperties & operator=( const ContentProperties & ); // n.i. + + const PropertyValue * get( const OUString & rName ) const; +}; + +class CachableContentProperties +{ +private: + ContentProperties m_aProps; + + CachableContentProperties & operator=( const CachableContentProperties & ); // n.i. + CachableContentProperties( const CachableContentProperties & ); // n.i. + +public: + explicit CachableContentProperties( const ContentProperties & rProps ); + + void addProperties( const ContentProperties & rProps ); + + void addProperties( const std::vector< DAVPropertyValue > & rProps ); + + bool containsAllNames( + const css::uno::Sequence< css::beans::Property >& rProps, + std::vector< OUString > & rNamesNotContained ) const + { return m_aProps.containsAllNames( rProps, rNamesNotContained ); } + + const css::uno::Any & + getValue( const OUString & rName ) const + { return m_aProps.getValue( rName ); } + + operator const ContentProperties & () const { return m_aProps; } +}; + +} // namespace http_dav_ucp + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/ucb/source/ucp/webdav-curl/CurlSession.cxx b/ucb/source/ucp/webdav-curl/CurlSession.cxx new file mode 100644 index 000000000000..70f9f828467d --- /dev/null +++ b/ucb/source/ucp/webdav-curl/CurlSession.cxx @@ -0,0 +1,2376 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4; fill-column: 100 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ + +#include "CurlSession.hxx" + +#include "SerfLockStore.hxx" +#include "DAVProperties.hxx" +#include "UCBDeadPropertyValue.hxx" +#include "webdavresponseparser.hxx" + +#include <comphelper/attributelist.hxx> +#include <comphelper/scopeguard.hxx> +#include <comphelper/string.hxx> + +#include <o3tl/safeint.hxx> + +#include <officecfg/Inet.hxx> + +#include <com/sun/star/beans/NamedValue.hpp> +#include <com/sun/star/io/Pipe.hpp> +#include <com/sun/star/io/SequenceInputStream.hpp> +#include <com/sun/star/io/SequenceOutputStream.hpp> +#include <com/sun/star/xml/sax/Writer.hpp> + +#include <osl/time.h> +#include <sal/log.hxx> +#include <rtl/strbuf.hxx> +#include <rtl/ustrbuf.hxx> +#include <config_version.h> + +#include <map> +#include <optional> +#include <tuple> +#include <vector> + +using namespace ::com::sun::star; + +namespace +{ +/// globals container +struct Init +{ + /// note: LockStore has its own mutex and calls CurlSession from its thread + /// so don't call LockStore with m_Mutex held to prevent deadlock. + ::http_dav_ucp::SerfLockStore LockStore; + + Init() + { + if (curl_global_init(CURL_GLOBAL_ALL) != CURLE_OK) + { + assert(!"curl_global_init failed"); + } + } + // do not call curl_global_cleanup() - this is not the only client of curl +}; +Init g_Init; + +struct ResponseHeaders +{ + ::std::vector<::std::pair<::std::vector<OString>, ::std::optional<long>>> HeaderFields; + CURL* pCurl; + ResponseHeaders(CURL* const i_pCurl) + : pCurl(i_pCurl) + { + } +}; + +auto GetResponseCode(ResponseHeaders const& rHeaders) -> ::std::optional<long> +{ + return (rHeaders.HeaderFields.empty()) ? ::std::optional<long>{} + : rHeaders.HeaderFields.back().second; +} + +struct DownloadTarget +{ + uno::Reference<io::XOutputStream> xOutStream; + ResponseHeaders const& rHeaders; + DownloadTarget(uno::Reference<io::XOutputStream> const& i_xOutStream, + ResponseHeaders const& i_rHeaders) + : xOutStream(i_xOutStream) + , rHeaders(i_rHeaders) + { + } +}; + +struct UploadSource +{ + uno::Sequence<sal_Int8> const& rInData; + ResponseHeaders const& rHeaders; + size_t nPosition; + UploadSource(uno::Sequence<sal_Int8> const& i_rInData, ResponseHeaders const& i_rHeaders) + : rInData(i_rInData) + , rHeaders(i_rHeaders) + , nPosition(0) + { + } +}; + +auto GetErrorString(CURLcode const rc, char const* const pErrorBuffer = nullptr) -> OString +{ + char const* const pMessage( // static fallback + (pErrorBuffer && pErrorBuffer[0] != '\0') ? pErrorBuffer : curl_easy_strerror(rc)); + return OString::Concat("(") + OString::number(sal_Int32(rc)) + ") " + pMessage; +} + +auto GetErrorStringMulti(CURLMcode const mc) -> OString +{ + return OString::Concat("(") + OString::number(sal_Int32(mc)) + ") " + curl_multi_strerror(mc); +} + +/// represent an option to be passed to curl_easy_setopt() +struct CurlOption +{ + CURLoption const Option; + enum class Type + { + Pointer, + Long, + CurlOffT + }; + Type const Tag; + union { + void const* const pValue; + long /*const*/ lValue; + curl_off_t /*const*/ cValue; + }; + char const* const pExceptionString; + + CurlOption(CURLoption const i_Option, void const* const i_Value, + char const* const i_pExceptionString) + : Option(i_Option) + , Tag(Type::Pointer) + , pValue(i_Value) + , pExceptionString(i_pExceptionString) + { + } + // Depending on platform, curl_off_t may be "long" or a larger type + // so cannot use overloading to distinguish these cases. + CurlOption(CURLoption const i_Option, curl_off_t const i_Value, + char const* const i_pExceptionString, Type const type = Type::Long) + : Option(i_Option) + , Tag(type) + , pExceptionString(i_pExceptionString) + { + static_assert(sizeof(long) <= sizeof(curl_off_t)); + switch (type) + { + case Type::Long: + lValue = i_Value; + break; + case Type::CurlOffT: + cValue = i_Value; + break; + default: + assert(false); + } + } +}; + +// NOBODY will prevent logging the response body in ProcessRequest() exception +// handler, so only use it if logging is disabled +const CurlOption g_NoBody{ CURLOPT_NOBODY, + sal_detail_log_report(SAL_DETAIL_LOG_LEVEL_INFO, "ucb.ucp.webdav.curl") + == SAL_DETAIL_LOG_ACTION_IGNORE + ? 1L + : 0L, + nullptr }; + +/// combined guard class to ensure things are released in correct order, +/// particularly in ProcessRequest() error handling +class Guard +{ +private: + /// mutex *first* because m_oGuard requires it + ::std::unique_lock<::std::mutex> m_Lock; + ::std::vector<CurlOption> const m_Options; + ::http_dav_ucp::CurlUri const& m_rURI; + CURL* const m_pCurl; + +public: + explicit Guard(::std::mutex& rMutex, ::std::vector<CurlOption> const& rOptions, + ::http_dav_ucp::CurlUri const& rURI, CURL* const pCurl) + : m_Lock(rMutex, ::std::defer_lock) + , m_Options(rOptions) + , m_rURI(rURI) + , m_pCurl(pCurl) + { + Acquire(); + } + ~Guard() + { + if (m_Lock.owns_lock()) + { + Release(); + } + } + + void Acquire() + { + assert(!m_Lock.owns_lock()); + m_Lock.lock(); + for (auto const& it : m_Options) + { + CURLcode rc(CURL_LAST); // warning C4701 + if (it.Tag == CurlOption::Type::Pointer) + { + rc = curl_easy_setopt(m_pCurl, it.Option, it.pValue); + } + else if (it.Tag == CurlOption::Type::Long) + { + rc = curl_easy_setopt(m_pCurl, it.Option, it.lValue); + } + else if (it.Tag == CurlOption::Type::CurlOffT) + { + rc = curl_easy_setopt(m_pCurl, it.Option, it.cValue); + } + else + { + assert(false); + } + if (it.pExceptionString != nullptr) + { + if (rc != CURLE_OK) + { + SAL_WARN("ucb.ucp.webdav.curl", + "set " << it.pExceptionString << " failed: " << GetErrorString(rc)); + throw ::http_dav_ucp::DAVException( + ::http_dav_ucp::DAVException::DAV_SESSION_CREATE, + ::http_dav_ucp::ConnectionEndPointString(m_rURI.GetHost(), + m_rURI.GetPort())); + } + } + else // many of the options cannot fail + { + assert(rc == CURLE_OK); + } + } + } + void Release() + { + assert(m_Lock.owns_lock()); + for (auto const& it : m_Options) + { + CURLcode rc(CURL_LAST); // warning C4701 + if (it.Tag == CurlOption::Type::Pointer) + { + rc = curl_easy_setopt(m_pCurl, it.Option, nullptr); + } + else if (it.Tag == CurlOption::Type::Long) + { + rc = curl_easy_setopt(m_pCurl, it.Option, 0L); + } + else if (it.Tag == CurlOption::Type::CurlOffT) + { + rc = curl_easy_setopt(m_pCurl, it.Option, curl_off_t(-1)); + } + else + { + assert(false); + } + assert(rc == CURLE_OK); + (void)rc; + } + m_Lock.unlock(); + } +}; + +} // namespace + +namespace http_dav_ucp +{ +// libcurl callbacks: + +static int debug_callback(CURL* handle, curl_infotype type, char* data, size_t size, + void* /*userdata*/) +{ + char const* pType(nullptr); + switch (type) + { + case CURLINFO_TEXT: + SAL_INFO("ucb.ucp.webdav.curl", "debug log: " << handle << ": " << data); + return 0; + case CURLINFO_HEADER_IN: + SAL_INFO("ucb.ucp.webdav.curl", + "CURLINFO_HEADER_IN: " << handle << ": " << OString(data, size)); + return 0; + case CURLINFO_HEADER_OUT: + { + // unlike IN, this is all headers in one call + OString tmp(data, size); + sal_Int32 const start(tmp.indexOf("Authorization: ")); + if (start != -1) + { + sal_Int32 const end(tmp.indexOf("\r\n", start)); + assert(end != -1); + sal_Int32 const len(SAL_N_ELEMENTS("Authorization: ") - 1); + tmp = tmp.replaceAt( + start + len, end - start - len, + OStringConcatenation(OString::number(end - start - len) + " bytes redacted")); + } + SAL_INFO("ucb.ucp.webdav.curl", "CURLINFO_HEADER_OUT: " << handle << ": " << tmp); + return 0; + } + case CURLINFO_DATA_IN: + pType = "CURLINFO_DATA_IN"; + break; + case CURLINFO_DATA_OUT: + pType = "CURLINFO_DATA_OUT"; + break; + case CURLINFO_SSL_DATA_IN: + pType = "CURLINFO_SSL_DATA_IN"; + break; + case CURLINFO_SSL_DATA_OUT: + pType = "CURLINFO_SSL_DATA_OUT"; + break; + default: + SAL_WARN("ucb.ucp.webdav.curl", "unexpected debug log type"); + return 0; + } + SAL_INFO("ucb.ucp.webdav.curl", "debug log: " << handle << ": " << pType << " " << size); + return 0; +} + +static size_t write_callback(char* const ptr, size_t const size, size_t const nmemb, + void* const userdata) +{ + auto* const pTarget(static_cast<DownloadTarget*>(userdata)); + if (!pTarget) // looks like ~every request may have a response body + { + return nmemb; + } + assert(size == 1); // says the man page + (void)size; + assert(pTarget->xOutStream.is()); + auto const oResponseCode(GetResponseCode(pTarget->rHeaders)); + if (!oResponseCode) + { + return 0; // that is an error + } + // always write, for exception handler in ProcessRequest() + // if (200 <= *oResponseCode && *oResponseCode < 300) + { + try + { + uno::Sequence<sal_Int8> const data(reinterpret_cast<sal_Int8*>(ptr), nmemb); + pTarget->xOutStream->writeBytes(data); + } + catch (...) + { + SAL_WARN("ucb.ucp.webdav.curl", "exception in write_callback"); + return 0; // error + } + } + // else: ignore the body? CurlSession will check the status eventually + return nmemb; +} + +static size_t read_callback(char* const buffer, size_t const size, size_t const nitems, + void* const userdata) +{ + auto* const pSource(static_cast<UploadSource*>(userdata)); + assert(pSource); + size_t const nBytes(size * nitems); + size_t nRet(0); + try + { + assert(pSource->nPosition <= o3tl::make_unsigned(pSource->rInData.getLength())); + nRet = ::std::min<size_t>(pSource->rInData.getLength() - pSource->nPosition, nBytes); + ::std::memcpy(buffer, pSource->rInData.getConstArray() + pSource->nPosition, nRet); + pSource->nPosition += nRet; + } + catch (...) + { + SAL_WARN("ucb.ucp.webdav.curl", "exception in read_callback"); + return CURL_READFUNC_ABORT; // error + } + return nRet; +} + +static size_t header_callback(char* const buffer, size_t const size, size_t const nitems, + void* const userdata) +{ + auto* const pHeaders(static_cast<ResponseHeaders*>(userdata)); + assert(pHeaders); +#if 0 + if (!pHeaders) // TODO maybe not needed in every request? not sure + { + return nitems; + } +#endif + assert(size == 1); // says the man page + (void)size; + try + { + if (nitems <= 2) + { + // end of header, body follows... + if (pHeaders->HeaderFields.empty()) + { + SAL_WARN("ucb.ucp.webdav.curl", "header_callback: empty header?"); + return 0; // error + } + // unfortunately there's no separate callback when the body begins, + // so have to manually retrieve the status code here + long statusCode(SC_NONE); + auto rc = curl_easy_getinfo(pHeaders->pCurl, CURLINFO_RESPONSE_CODE, &statusCode); + assert(rc == CURLE_OK); + (void)rc; + // always put the current response code here - wasn't necessarily in this header + pHeaders->HeaderFields.back().second.emplace(statusCode); + } + else if (buffer[0] == ' ' || buffer[0] == '\t') // folded header field? + { + size_t i(0); + do + { + ++i; + } while (i == ' ' || i == '\t'); + if (pHeaders->HeaderFields.empty() || pHeaders->HeaderFields.back().second + || pHeaders->HeaderFields.back().first.empty()) + { + SAL_WARN("ucb.ucp.webdav.curl", + "header_callback: folded header field without start"); + return 0; // error + } + pHeaders->HeaderFields.back().first.back() + += OString::Concat(" ") + ::std::string_view(&buffer[i], nitems - i); + } + else + { + if (pHeaders->HeaderFields.empty() || pHeaders->HeaderFields.back().second) + { + pHeaders->HeaderFields.emplace_back(); + } + pHeaders->HeaderFields.back().first.emplace_back(OString(buffer, nitems)); + } + } + catch (...) + { + SAL_WARN("ucb.ucp.webdav.curl", "exception in header_callback"); + return 0; // error + } + return nitems; +} + +static auto ProcessHeaders(::std::vector<OString> const& rHeaders) -> ::std::map<OUString, OUString> +{ + ::std::map<OUString, OUString> ret; + for (OString const& rLine : rHeaders) + { + OString line; + if (!rLine.endsWith("\r\n", &line)) + { + SAL_WARN("ucb.ucp.webdav.curl", "invalid header field (no CRLF)"); + continue; + } + if (line.startsWith("HTTP/") // first line + || line.isEmpty()) // last line + { + continue; + } + auto const nColon(line.indexOf(':')); + if (nColon == -1) + { + { + SAL_WARN("ucb.ucp.webdav.curl", "invalid header field (no :)"); + } + continue; + } + if (nColon == 0) + { + SAL_WARN("ucb.ucp.webdav.curl", "invalid header field (empty name)"); + continue; + } + // case insensitive; must be ASCII + auto const name(::rtl::OStringToOUString(line.copy(0, nColon).toAsciiLowerCase(), + RTL_TEXTENCODING_ASCII_US)); + sal_Int32 nStart(nColon + 1); + while (nStart < line.getLength() && (line[nStart] == ' ' || line[nStart] == '\t')) + { + ++nStart; + } + sal_Int32 nEnd(line.getLength()); + while (nStart < nEnd && (line[nEnd - 1] == ' ' || line[nEnd - 1] == '\t')) + { + --nEnd; + } + // RFC 7230 says that only ASCII works reliably anyway (neon also did this) + auto const value(::rtl::OStringToOUString(line.subView(nStart, nEnd - nStart), + RTL_TEXTENCODING_ASCII_US)); + auto const it(ret.find(name)); + if (it != ret.end()) + { + it->second = it->second + "," + value; + } + else + { + ret[name] = value; + } + } + return ret; +} + +static auto ExtractRequestedHeaders( + ResponseHeaders const& rHeaders, + ::std::pair<::std::vector<OUString> const&, DAVResource&> const* const pRequestedHeaders) + -> void +{ + ::std::map<OUString, OUString> const headerMap( + ProcessHeaders(rHeaders.HeaderFields.back().first)); + if (pRequestedHeaders) + { + for (OUString const& rHeader : pRequestedHeaders->first) + { + auto const it(headerMap.find(rHeader.toAsciiLowerCase())); + if (it != headerMap.end()) + { + DAVPropertyValue value; + value.IsCaseSensitive = false; + value.Name = it->first; + value.Value <<= it->second; + pRequestedHeaders->second.properties.push_back(value); + } + } + } +} + +// this appears to be the only way to get the "realm" from libcurl +static auto ExtractRealm(ResponseHeaders const& rHeaders, char const* const pAuthHeaderName) + -> ::std::optional<OUString> +{ + ::std::map<OUString, OUString> const headerMap( + ProcessHeaders(rHeaders.HeaderFields.back().first)); + auto const it(headerMap.find(OUString::createFromAscii(pAuthHeaderName).toAsciiLowerCase())); + if (it == headerMap.end()) + { + SAL_WARN("ucb.ucp.webdav.curl", "cannot find auth header"); + return {}; + } + // It may be possible that the header contains multiple methods each with + // a different realm - extract only the first one bc the downstream API + // only supports one anyway. + // case insensitive! + auto i(it->second.toAsciiLowerCase().indexOf("realm=")); + // is optional + if (i == -1) + { + SAL_INFO("ucb.ucp.webdav.curl", "auth header has no realm"); + return {}; + } + // no whitespace allowed before or after = + i += ::std::strlen("realm="); + if (it->second.getLength() < i + 2 || it->second[i] != '\"') + { + SAL_WARN("ucb.ucp.webdav.curl", "no realm value"); + return {}; + } + ++i; + OUStringBuffer buf; + while (i < it->second.getLength() && it->second[i] != '\"') + { + if (it->second[i] == '\\') // quoted-pair escape + { + ++i; + if (it->second.getLength() <= i) + { + SAL_WARN("ucb.ucp.webdav.curl", "unterminated quoted-pair"); + return {}; + } + } + buf.append(it->second[i]); + ++i; + } + if (it->second.getLength() <= i) + { + SAL_WARN("ucb.ucp.webdav.curl", "unterminated realm"); + return {}; + } + return buf.makeStringAndClear(); +} + +CurlSession::CurlSession(uno::Reference<uno::XComponentContext> const& xContext, + ::rtl::Reference<DAVSessionFactory> const& rpFactory, OUString const& rURI, + uno::Sequence<beans::NamedValue> const& rFlags, + ::ucbhelper::InternetProxyDecider const& rProxyDecider) + : DAVSession(rpFactory) + , m_xContext(xContext) + , m_Flags(rFlags) + , m_URI(rURI) + , m_Proxy(rProxyDecider.getProxy(m_URI.GetScheme(), m_URI.GetHost(), m_URI.GetPort())) +{ + assert(m_URI.GetScheme() == "http" || m_URI.GetScheme() == "https"); + m_pCurlMulti.reset(curl_multi_init()); + if (!m_pCurlMulti) + { + SAL_WARN("ucb.ucp.webdav.curl", "curl_multi_init failed"); + throw DAVException(DAVException::DAV_SESSION_CREATE, + ConnectionEndPointString(m_URI.GetHost(), m_URI.GetPort())); + } + m_pCurl.reset(curl_easy_init()); + if (!m_pCurl) + { + SAL_WARN("ucb.ucp.webdav.curl", "curl_easy_init failed"); + throw DAVException(DAVException::DAV_SESSION_CREATE, + ConnectionEndPointString(m_URI.GetHost(), m_URI.GetPort())); + } + curl_version_info_data const* const pVersion(curl_version_info(CURLVERSION_NOW)); + assert(pVersion); + SAL_INFO("ucb.ucp.webdav.curl", + "curl version: " << pVersion->version << " " << pVersion->host + << " features: " << ::std::hex << pVersion->features << " ssl: " + << pVersion->ssl_version << " libz: " << pVersion->libz_version); + // Make sure a User-Agent header is always included, as at least + // en.wikipedia.org:80 forces back 403 "Scripts should use an informative + // User-Agent string with contact information, or they may be IP-blocked + // without notice" otherwise: + OString const useragent(OString::Concat("LibreOffice " LIBO_VERSION_DOTTED " curl/") + + ::std::string_view(pVersion->version, strlen(pVersion->version)) + " " + + pVersion->ssl_version); + // looks like an explicit "User-Agent" header in CURLOPT_HTTPHEADER + // will override CURLOPT_USERAGENT, see Curl_http_useragent(), so no need + // to check anything here + auto rc = curl_easy_setopt(m_pCurl.get(), CURLOPT_USERAGENT, useragent.getStr()); + if (rc != CURLE_OK) + { + SAL_WARN("ucb.ucp.webdav.curl", "CURLOPT_USERAGENT failed: " << GetErrorString(rc)); + throw DAVException(DAVException::DAV_SESSION_CREATE, + ConnectionEndPointString(m_URI.GetHost(), m_URI.GetPort())); + } + m_ErrorBuffer[0] = '\0'; + // this supposedly gives the highest quality error reporting + rc = curl_easy_setopt(m_pCurl.get(), CURLOPT_ERRORBUFFER, m_ErrorBuffer); + assert(rc == CURLE_OK); +#if 1 + // just for debugging... + rc = curl_easy_setopt(m_pCurl.get(), CURLOPT_DEBUGFUNCTION, debug_callback); + assert(rc == CURLE_OK); +#endif + rc = curl_easy_setopt(m_pCurl.get(), CURLOPT_VERBOSE, 1L); + assert(rc == CURLE_OK); + // accept any encoding supported by libcurl + rc = curl_easy_setopt(m_pCurl.get(), CURLOPT_ACCEPT_ENCODING, ""); + if (rc != CURLE_OK) + { + SAL_WARN("ucb.ucp.webdav.curl", "CURLOPT_ACCEPT_ENCODING failed: " << GetErrorString(rc)); + throw DAVException(DAVException::DAV_SESSION_CREATE, + ConnectionEndPointString(m_URI.GetHost(), m_URI.GetPort())); + } + auto const connectTimeout(officecfg::Inet::Settings::ConnectTimeout::get(m_xContext)); + // default is 300s + rc = curl_easy_setopt(m_pCurl.get(), CURLOPT_CONNECTTIMEOUT, + ::std::max<long>(2L, ::std::min<long>(connectTimeout, 180L))); + if (rc != CURLE_OK) + { + SAL_WARN("ucb.ucp.webdav.curl", "CURLOPT_CONNECTTIMEOUT failed: " << GetErrorString(rc)); + throw DAVException(DAVException::DAV_SESSION_CREATE, + ConnectionEndPointString(m_URI.GetHost(), m_URI.GetPort())); + } + auto const readTimeout(officecfg::Inet::Settings::ReadTimeout::get(m_xContext)); + m_nReadTimeout = ::std::max<int>(20, ::std::min<long>(readTimeout, 180)) * 1000; + // default is infinite + rc = curl_easy_setopt(m_pCurl.get(), CURLOPT_TIMEOUT, 300L); + if (rc != CURLE_OK) + { + SAL_WARN("ucb.ucp.webdav.curl", "CURLOPT_TIMEOUT failed: " << GetErrorString(rc)); + throw DAVException(DAVException::DAV_SESSION_CREATE, + ConnectionEndPointString(m_URI.GetHost(), m_URI.GetPort())); + } + rc = curl_easy_setopt(m_pCurl.get(), CURLOPT_WRITEFUNCTION, &write_callback); + assert(rc == CURLE_OK); + rc = curl_easy_setopt(m_pCurl.get(), CURLOPT_READFUNCTION, &read_callback); + assert(rc == CURLE_OK); + rc = curl_easy_setopt(m_pCurl.get(), CURLOPT_HEADERFUNCTION, &header_callback); + // set this initially, may be overwritten during authentication + assert(rc == CURLE_OK); + rc = curl_easy_setopt(m_pCurl.get(), CURLOPT_HTTPAUTH, CURLAUTH_ANY); + assert(rc == CURLE_OK); // ANY is always available + // always set CURLOPT_PROXY to suppress proxy detection in libcurl + OString const utf8Proxy(OUStringToOString(m_Proxy.aName, RTL_TEXTENCODING_UTF8)); + rc = curl_easy_setopt(m_pCurl.get(), CURLOPT_PROXY, utf8Proxy.getStr()); + if (rc != CURLE_OK) + { + SAL_WARN("ucb.ucp.webdav.curl", "CURLOPT_PROXY failed: " << GetErrorString(rc)); + throw DAVException(DAVException::DAV_SESSION_CREATE, + ConnectionEndPointString(m_Proxy.aName, m_Proxy.nPort)); + } + if (!m_Proxy.aName.isEmpty()) + { + rc = curl_easy_setopt(m_pCurl.get(), CURLOPT_PROXYPORT, static_cast<long>(m_Proxy.nPort)); + assert(rc == CURLE_OK); + // set this initially, may be overwritten during authentication + rc = curl_easy_setopt(m_pCurl.get(), CURLOPT_PROXYAUTH, CURLAUTH_ANY); + assert(rc == CURLE_OK); // ANY is always available + } + auto const it(::std::find_if(m_Flags.begin(), m_Flags.end(), + [](auto const& rFlag) { return rFlag.Name == "KeepAlive"; })); + if (it != m_Flags.end() && it->Value.get<bool>()) + { + // neon would close the connection from ne_end_request(), this seems + // to be the equivalent and not CURLOPT_TCP_KEEPALIVE + rc = curl_easy_setopt(m_pCurl.get(), CURLOPT_FORBID_REUSE, 1L); + assert(rc == CURLE_OK); + } +} + +CurlSession::~CurlSession() {} + +auto CurlSession::CanUse(OUString const& rURI, uno::Sequence<beans::NamedValue> const& rFlags) + -> bool +{ + try + { + CurlUri const uri(rURI); + + return m_URI.GetScheme() == uri.GetScheme() && m_URI.GetHost() == uri.GetHost() + && m_URI.GetPort() == uri.GetPort() && m_Flags == rFlags; + } + catch (DAVException const&) + { + return false; + } +} + +auto CurlSession::UsesProxy() -> bool +{ + assert(m_URI.GetScheme() == "http" || m_URI.GetScheme() == "https"); + return !m_Proxy.aName.isEmpty(); +} + +auto CurlSession::abort() -> void +{ + // note: abort() was a no-op since OOo 3.2 and before that it crashed. + bool expected(false); + // it would be pointless to lock m_Mutex here as the other thread holds it + if (m_AbortFlag.compare_exchange_strong(expected, true)) + { + // This function looks safe to call without m_Mutex as long as the + // m_pCurlMulti handle is not destroyed, and the caller must own a ref + // to this object which keeps it alive; it should cause poll to return. + curl_multi_wakeup(m_pCurlMulti.get()); + } +} + +/// this is just a bunch of static member functions called from CurlSession +struct CurlProcessor +{ + static auto URIReferenceToURI(CurlSession& rSession, OUString const& rURIReference) -> CurlUri; + + static auto ProcessRequestImpl( + CurlSession& rSession, CurlUri const& rURI, 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; + + static auto ProcessRequest( + CurlSession& rSession, CurlUri const& rURI, ::std::vector<CurlOption> const& rOptions, + DAVRequestEnvironment const* pEnv, + ::std::unique_ptr<curl_slist, deleter_from_fn<curl_slist, curl_slist_free_all>> + pRequestHeaderList, + uno::Reference<io::XOutputStream> const* pxOutStream, + uno::Reference<io::XInputStream> const* pxInStream, + ::std::pair<::std::vector<OUString> const&, DAVResource&> const* pRequestedHeaders) -> void; + + static auto + PropFind(CurlSession& rSession, CurlUri const& rURI, Depth depth, + ::std::tuple<::std::vector<OUString> const&, ::std::vector<DAVResource>* const, + ::std::vector<ucb::Lock>* const> const* o_pRequestedProperties, + ::std::vector<DAVResourceInfo>* const o_pResourceInfos, + DAVRequestEnvironment const& rEnv) -> void; + + static auto MoveOrCopy(CurlSession& rSession, OUString const& rSourceURIReference, + ::std::u16string_view rDestinationURI, DAVRequestEnvironment const& rEnv, + bool isOverwrite, char const* pMethod) -> void; + + static auto Lock(CurlSession& rSession, CurlUri const& rURI, DAVRequestEnvironment const* pEnv, + ::std::unique_ptr<curl_slist, deleter_from_fn<curl_slist, curl_slist_free_all>> + pRequestHeaderList, + uno::Reference<io::XInputStream> const* pxInStream) + -> ::std::vector<::std::pair<ucb::Lock, sal_Int32>>; + + static auto Unlock(CurlSession& rSession, CurlUri const& rURI, + DAVRequestEnvironment const* pEnv) -> void; +}; + +auto CurlProcessor::URIReferenceToURI(CurlSession& rSession, OUString const& rURIReference) + -> CurlUri +{ + // No need to acquire rSession.m_Mutex because accessed members are const. + if (rSession.UsesProxy()) + // very odd, but see DAVResourceAccess::getRequestURI() :-/ + { + assert(rURIReference.startsWith("http://") || rURIReference.startsWith("https://")); + return CurlUri(rURIReference); + } + else + { + assert(rURIReference.startsWith("/")); + return rSession.m_URI.CloneWithRelativeRefPathAbsolute(rURIReference); + } +} + +/// main function to initiate libcurl requests +auto CurlProcessor::ProcessRequestImpl( + CurlSession& rSession, CurlUri const& rURI, curl_slist* const pRequestHeaderList, + uno::Reference<io::XOutputStream> const* const pxOutStream, + uno::Sequence<sal_Int8> const* const pInData, + ::std::pair<::std::vector<OUString> const&, DAVResource&> const* const pRequestedHeaders, + ResponseHeaders& rHeaders) -> void +{ + ::comphelper::ScopeGuard const g([&]() { + auto rc = curl_easy_setopt(rSession.m_pCurl.get(), CURLOPT_HEADERDATA, nullptr); + assert(rc == CURLE_OK); + (void)rc; + if (pxOutStream) + { + rc = curl_easy_setopt(rSession.m_pCurl.get(), CURLOPT_WRITEDATA, nullptr); + assert(rc == CURLE_OK); + } + if (pInData) + { + rc = curl_easy_setopt(rSession.m_pCurl.get(), CURLOPT_READDATA, nullptr); + assert(rc == CURLE_OK); + rc = curl_easy_setopt(rSession.m_pCurl.get(), CURLOPT_UPLOAD, 0L); + assert(rc == CURLE_OK); + } + if (pRequestHeaderList) + { + rc = curl_easy_setopt(rSession.m_pCurl.get(), CURLOPT_HTTPHEADER, nullptr); + assert(rc == CURLE_OK); + } + }); + + if (pRequestHeaderList) + { + auto rc = curl_easy_setopt(rSession.m_pCurl.get(), CURLOPT_HTTPHEADER, pRequestHeaderList); + assert(rc == CURLE_OK); + (void)rc; + } + + auto rc = curl_easy_setopt(rSession.m_pCurl.get(), CURLOPT_CURLU, rURI.GetCURLU()); + assert(rc == CURLE_OK); // can't fail since 7.63.0 + + rc = curl_easy_setopt(rSession.m_pCurl.get(), CURLOPT_HEADERDATA, &rHeaders); + assert(rc == CURLE_OK); + ::std::optional<DownloadTarget> oDownloadTarget; + if (pxOutStream) + { + oDownloadTarget.emplace(*pxOutStream, rHeaders); + rc = curl_easy_setopt(rSession.m_pCurl.get(), CURLOPT_WRITEDATA, &*oDownloadTarget); + assert(rc == CURLE_OK); + } + ::std::optional<UploadSource> oUploadSource; + if (pInData) + { + oUploadSource.emplace(*pInData, rHeaders); + rc = curl_easy_setopt(rSession.m_pCurl.get(), CURLOPT_READDATA, &*oUploadSource); + assert(rc == CURLE_OK); + // libcurl won't upload without setting this + rc = curl_easy_setopt(rSession.m_pCurl.get(), CURLOPT_UPLOAD, 1L); + assert(rc == CURLE_OK); + } + rSession.m_ErrorBuffer[0] = '\0'; + + // note: easy handle must be added for *every* transfer! + // otherwise it gets stuck in MSTATE_MSGSENT forever after 1st transfer + auto mc = curl_multi_add_handle(rSession.m_pCurlMulti.get(), rSession.m_pCurl.get()); + if (mc != CURLM_OK) + { + SAL_WARN("ucb.ucp.webdav.curl", + "curl_multi_add_handle failed: " << GetErrorStringMulti(mc)); + throw DAVException( + DAVException::DAV_SESSION_CREATE, + ConnectionEndPointString(rSession.m_URI.GetHost(), rSession.m_URI.GetPort())); + } + ::comphelper::ScopeGuard const gg([&]() { + mc = curl_multi_remove_handle(rSession.m_pCurlMulti.get(), rSession.m_pCurl.get()); + if (mc != CURLM_OK) + { + SAL_WARN("ucb.ucp.webdav.curl", + "curl_multi_remove_handle failed: " << GetErrorStringMulti(mc)); + } + }); + + // this is where libcurl actually does something + rc = CURL_LAST; // clear current value + int nRunning; + do + { + mc = curl_multi_perform(rSession.m_pCurlMulti.get(), &nRunning); + if (mc != CURLM_OK) + { + SAL_WARN("ucb.ucp.webdav.curl", + "curl_multi_perform failed: " << GetErrorStringMulti(mc)); + throw DAVException( + DAVException::DAV_HTTP_CONNECT, + ConnectionEndPointString(rSession.m_URI.GetHost(), rSession.m_URI.GetPort())); + } + if (nRunning == 0) + { // short request like HEAD on loopback could be done in first call + break; + } + int nFDs; + mc = curl_multi_poll(rSession.m_pCurlMulti.get(), nullptr, 0, rSession.m_nReadTimeout, + &nFDs); + if (mc != CURLM_OK) + { ... etc. - the rest is truncated