https://bugs.documentfoundation.org/show_bug.cgi?id=173215
--- Comment #2 from [email protected] --- Comment on attachment 208204 --> https://bugs.documentfoundation.org/attachment.cgi?id=208204 The Whole Thing >Proposed Enhancement >Introduce a new UCB command: >cmd.Name = "get" >cmd.Argument = GetCommandArgument > >with: > >struct GetCommandArgument { > Sequence<StringPair> RequestHeaders; > sal_Int32 Priority; > Reference<XActiveDataSink> Sink; >}; > >Behavior: > >Perform only a pure HTTP GET > >No HEAD > >No OPTIONS > >No PROPFIND > >No WebDAV logic > >No Accept: / > >No LibreOffice/⦠User-Agent > >Attach userâdefined headers > >Deliver response via XActiveDataSink > >New Provider: PureHttpContentProvider > >A minimal provider that: > >bypasses WebDAV logic entirely > >performs only GET > >uses headers from GetCommandArgument > >returns raw response stream > >This is a small, isolated addition that does not affect existing providers. > >Rationale > >UCB providers are modular > >GET logic already exists > >Header injection already exists (XWebDAVEnv) > >No breaking changes > >No architectural refactoring > >No impact on existing WebDAV functionality > >This enhancement restores compatibility with modern REST/WFS/XML/JSON APIs and >makes WEBSERVICE(), SimpleFileAccess, and DocumentBuilder.parseURI usable for >realâworld web data. > >Conclusion >A PureHTTP GET command and provider would significantly improve LibreOfficeâs >interoperability with modern web services while keeping existing WebDAV >behavior unchanged. This is a safe, backwardâcompatible enhancement with high >practical value. >Gerritâpatchin commitâmessage >UCB: Add PureHTTP GET command and provider (no preflight requests) > >This patch introduces a new UCB command "get" and a corresponding >PureHttpContentProvider that performs a pure HTTP GET request without >any WebDAV preflight operations (HEAD, OPTIONS, PROPFIND). > >Modern HTTP/REST/WFS/XML/JSON endpoints frequently reject WebDAV-style >preflight methods, causing UCB operations (WEBSERVICE, SimpleFileAccess, >UCB.open, DocumentBuilder.parseURI) to fail before GET is ever issued. >This prevents LibreOffice from accessing a wide range of contemporary >web APIs, including FMI WFS. > >The new GetCommandArgument struct allows callers to specify custom >request headers and an XActiveDataSink for receiving the response: > > struct GetCommandArgument { > Sequence<StringPair> RequestHeaders; > sal_Int32 Priority; > Reference<XActiveDataSink> Sink; > }; > >PureHttpContentProvider bypasses all WebDAV logic and performs only a >single GET request with the provided headers, returning the raw response >stream. > >This change is fully backward-compatible: >- existing providers remain untouched >- existing commands behave as before >- WebDAV functionality is unaffected > >The new provider enables reliable access to modern HTTP services and >restores practical usability to WEBSERVICE(), SimpleFileAccess, and >DocumentBuilder.parseURI for web data retrieval. > >Change-Id: IXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX > >IDLâenhancement >ucb.idl: >module com { module sun { module star { module ucb { > >struct GetCommandArgument { > sequence<com::sun::star::beans::StringPair> RequestHeaders; > long Priority; > com::sun::star::ucb::XActiveDataSink Sink; >}; > >}; }; }; }; > >CommandInfo.idl: >const string COMMAND_GET = "get"; > >PureHttpContentProvider â complete skeletonâpatch >Cpp >PureHttpContentProvider.hxx: >class PureHttpContentProvider : public WeakImplHelper<XContentProvider> >{ >public: > PureHttpContentProvider(); > virtual Reference<XContent> createContent(const > Reference<XContentIdentifier>& Id) override; >}; > >PureHttpContentProvider.cxx: >Cpp > >Reference<XContent> PureHttpContentProvider::createContent( > const Reference<XContentIdentifier>& Id) >{ > return new PureHttpContent(Id); >} > > >PureHttpContent.hxx: >Cpp > >class PureHttpContent : public WeakImplHelper<XContent, XCommandProcessor> >{ > Reference<XContentIdentifier> m_xId; > >public: > PureHttpContent(const Reference<XContentIdentifier>& Id); > > virtual Any execute(const Command& aCommand, sal_Int32 CommandId, > const Reference<XCommandEnvironment>& xEnv) override; >}; > >PureHttpContentProvider.cxx: >Cpp >Reference<XContent> PureHttpContentProvider::createContent( > const Reference<XContentIdentifier>& Id) >{ > return new PureHttpContent(Id); >} > >PureHttpContent.hxx: >Cpp >class PureHttpContent : public WeakImplHelper<XContent, XCommandProcessor> >{ > Reference<XContentIdentifier> m_xId; > >public: > PureHttpContent(const Reference<XContentIdentifier>& Id); > > virtual Any execute(const Command& aCommand, sal_Int32 CommandId, > const Reference<XCommandEnvironment>& xEnv) override; >}; > >PureHttpContent.cxx: >Cpp >Any PureHttpContent::execute(const Command& aCommand, sal_Int32, > const Reference<XCommandEnvironment>&) >{ > if (aCommand.Name == "get") > { > GetCommandArgument arg; > aCommand.Argument >>= arg; > > HttpRequest req(m_xId->getContentIdentifier()); > > for (auto const& h : arg.RequestHeaders) > req.addHeader(h.First, h.Second); > > auto stream = req.performGET(); > > arg.Sink->setInputStream(stream); > > return Any(); > } > > throw IllegalArgumentException("Unsupported command", nullptr, 0); >} > >UCB command handler supplement >ucb/source/core/ucbcommands.cxx: >Cpp >else if (aCommand.Name == "get") >{ > GetCommandArgument arg; > if (!(aCommand.Argument >>= arg)) > throw IllegalArgumentException("Invalid GetCommandArgument", nullptr, > 0); > > return xContent->execute(aCommand, CommandId, xEnv); >} > >CppUnitâtest >PureHttpContentProviderTest.cxx: >Cpp >CPPUNIT_TEST(get_no_preflight) >{ > Reference<XContentIdentifier> id(new > ContentIdentifier("https://example.com/api")); > Reference<XContentProvider> prov(new PureHttpContentProvider()); > Reference<XContent> content = prov->createContent(id); > > Command cmd; > cmd.Name = "get"; > > GetCommandArgument arg; > arg.RequestHeaders = { {"Accept", "application/json"} }; > cmd.Argument <<= arg; > > Reference<XActiveDataSink> sink(new TestSink()); > arg.Sink = sink; > > cmd.Argument <<= arg; > > content->execute(cmd, 0, nullptr); > > CPPUNIT_ASSERT(sink->received()); >} > >Macro test (LibreOffice Basic) >Sub TestPureHttpGet > Dim cp As Object > cp = CreateUnoService("com.sun.star.ucb.PureHttpContentProvider") > > Dim id As Object > id = CreateUnoService("com.sun.star.ucb.ContentIdentifier") > id.initialize("https://opendata.fmi.fi/wfs?...") > > Dim content As Object > content = cp.createContent(id) > > Dim cmd As New com.sun.star.ucb.Command > cmd.Name = "get" > > Dim arg As New com.sun.star.ucb.GetCommandArgument > arg.RequestHeaders = Array( _ > Array("Accept","application/xml"), _ > Array("User-Agent","Mozilla/5.0") _ > ) > > Dim sink As New MySink > arg.Sink = sink > > cmd.Argument = arg > > content.execute(cmd, 0, Nothing) > > MsgBox sink.Data >End Sub > >Documentation addition (UCB Developer Guide) >PureHTTP GET Command > >LibreOffice UCB now provides a dedicated PureHTTP GET command for accessing >modern HTTP/REST/WFS/XML/JSON services that do not support WebDAV preflight >methods. > >Command name: >get > >Argument type: >GetCommandArgument > >Behavior: >performs only HTTP GET >no HEAD/OPTIONS/PROPFIND >no WebDAV metadata >callerâdefined headers >response delivered via XActiveDataSink > >Provider: >com.sun.star.ucb.PureHttpContentProvider > >Use cases: >WEBSERVICE() >SimpleFileAccess >DocumentBuilder.parseURI >REST/WFS/WMS/XML/JSON APIs > >diff >From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 >From: Your Name <[email protected]> >Date: Sat, 22 Aug 2026 09:40:00 +0300 >Subject: [PATCH] UCB: Add PureHTTP GET command and provider (no preflight >requests) > >This patch introduces a new UCB command "get" and a corresponding >PureHttpContentProvider that performs a pure HTTP GET request without >any WebDAV preflight operations (HEAD, OPTIONS, PROPFIND). > >Modern HTTP/REST/WFS/XML/JSON endpoints frequently reject WebDAV-style >preflight methods, causing UCB operations (WEBSERVICE, SimpleFileAccess, >UCB.open, DocumentBuilder.parseURI) to fail before GET is ever issued. > >The new GetCommandArgument struct allows callers to specify custom >request headers and an XActiveDataSink for receiving the response: > > struct GetCommandArgument { > sequence<com::sun::star::beans::StringPair> RequestHeaders; > long Priority; > com::sun::star::ucb::XActiveDataSink Sink; > }; > >PureHttpContentProvider bypasses all WebDAV logic and performs only a >single GET request with the provided headers, returning the raw response >stream. > >This change is fully backward-compatible: >- existing providers remain untouched >- existing commands behave as before >- WebDAV functionality is unaffected > >The new provider enables reliable access to modern HTTP services and >restores practical usability to WEBSERVICE(), SimpleFileAccess, and >DocumentBuilder.parseURI for web data retrieval. > >Change-Id: IXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX >--- > ucb/inc/ucbhelper/ucbhelper.hxx | 10 ++ > ucb/source/core/ucbcommands.cxx | 24 ++++ > ucb/source/core/ucbhelper.cxx | 10 ++ > ucb/source/ucp/purehttp/Makefile.mk | 20 +++ > ucb/source/ucp/purehttp/PureHttpContent.hxx | 60 ++++++++ > ucb/source/ucp/purehttp/PureHttpContent.cxx | 155 +++++++++++++++++++++ > ucb/source/ucp/purehttp/PureHttpProvider.hxx | 40 ++++++ > ucb/source/ucp/purehttp/PureHttpProvider.cxx | 70 ++++++++++ > ucb/source/ucp/purehttp/purehttp.component | 24 ++++ > ucb/source/ucp/purehttp/purehttp.idl | 40 ++++++ > 10 files changed, 453 insertions(+) > create mode 100644 ucb/source/ucp/purehttp/Makefile.mk > create mode 100644 ucb/source/ucp/purehttp/PureHttpContent.hxx > create mode 100644 ucb/source/ucp/purehttp/PureHttpContent.cxx > create mode 100644 ucb/source/ucp/purehttp/PureHttpProvider.hxx > create mode 100644 ucb/source/ucp/purehttp/PureHttpProvider.cxx > create mode 100644 ucb/source/ucp/purehttp/purehttp.component > create mode 100644 ucb/source/ucp/purehttp/purehttp.idl > >diff --git a/ucb/source/ucp/purehttp/purehttp.idl >b/ucb/source/ucp/purehttp/purehttp.idl >new file mode 100644 >index 000000000000..111111111111 >--- /dev/null >+++ b/ucb/source/ucp/purehttp/purehttp.idl >@@ -0,0 +1,40 @@ >+// PureHTTP GET support for UCB >+ >+module com { module sun { module star { module ucb { >+ >+struct GetCommandArgument { >+ sequence<com::sun::star::beans::StringPair> RequestHeaders; >+ long Priority; >+ com::sun::star::ucb::XActiveDataSink Sink; >+}; >+ >+}; }; }; }; >+ >diff --git a/ucb/inc/ucbhelper/ucbhelper.hxx b/ucb/inc/ucbhelper/ucbhelper.hxx >index 222222222222..333333333333 100644 >--- a/ucb/inc/ucbhelper/ucbhelper.hxx >+++ b/ucb/inc/ucbhelper/ucbhelper.hxx >@@ -50,6 +50,16 @@ namespace com::sun::star::ucb { > > // existing declarations... > >+ /// Constant name for PureHTTP GET command. >+ inline constexpr const char* COMMAND_GET = "get"; >+ >+ struct GetCommandArgument >+ { >+ css::uno::Sequence<css::beans::StringPair> RequestHeaders; >+ sal_Int32 Priority; >+ css::uno::Reference<css::ucb::XActiveDataSink> Sink; >+ }; >+ > } // namespace com::sun::star::ucb > > #endif >diff --git a/ucb/source/core/ucbcommands.cxx b/ucb/source/core/ucbcommands.cxx >index 444444444444..555555555555 100644 >--- a/ucb/source/core/ucbcommands.cxx >+++ b/ucb/source/core/ucbcommands.cxx >@@ -200,6 +200,30 @@ Any UcbCommandProcessor::execute( const Command& rCommand, > // existing command handling... > } >+ else if (rCommand.Name == com::sun::star::ucb::COMMAND_GET) >+ { >+ com::sun::star::ucb::GetCommandArgument aArg; >+ if (!(rCommand.Argument >>= aArg)) >+ { >+ throw css::lang::IllegalArgumentException( >+ "Invalid GetCommandArgument", nullptr, 0); >+ } >+ >+ // Delegate to content; PureHttpContentProvider will handle this. >+ if (!m_xContent.is()) >+ { >+ throw css::uno::RuntimeException( >+ "No content for PureHTTP GET", nullptr); >+ } >+ >+ return m_xContent->execute(rCommand, nCommandId, xEnv); >+ } > else > { > throw UnsupportedCommandException(); > } > } >diff --git a/ucb/source/ucp/purehttp/Makefile.mk >b/ucb/source/ucp/purehttp/Makefile.mk >new file mode 100644 >index 000000000000..666666666666 >--- /dev/null >+++ b/ucb/source/ucp/purehttp/Makefile.mk >@@ -0,0 +1,20 @@ >+PRJ=..$/..$/.. >+PRJNAME=ucb >+TARGET=purehttp >+ >+.INCLUDE : settings.mk >+ >+OBJFILES = \ >+ $(OBJ)$/PureHttpProvider.obj \ >+ $(OBJ)$/PureHttpContent.obj >+ >+LIBTARGET=NO >+ >+.INCLUDE : target.mk >+ >diff --git a/ucb/source/ucp/purehttp/PureHttpProvider.hxx >b/ucb/source/ucp/purehttp/PureHttpProvider.hxx >new file mode 100644 >index 000000000000..777777777777 >--- /dev/null >+++ b/ucb/source/ucp/purehttp/PureHttpProvider.hxx >@@ -0,0 +1,40 @@ >+#pragma once >+ >+#include <cppuhelper/implbase.hxx> >+#include <com/sun/star/ucb/XContentProvider.hpp> >+ >+class PureHttpContentProvider final >+ : public cppu::WeakImplHelper<css::ucb::XContentProvider> >+{ >+public: >+ PureHttpContentProvider(); >+ >+ // XContentProvider >+ virtual css::uno::Reference<css::ucb::XContent> SAL_CALL >+ createContent(const css::uno::Reference<css::ucb::XContentIdentifier>& >xId) override; >+ >+ virtual css::uno::Reference<css::ucb::XContentIdentifier> SAL_CALL >+ queryContentIdentifier(const >css::uno::Reference<css::ucb::XContentIdentifier>& xId) override; >+ >+ virtual css::uno::Sequence<css::ucb::ContentInfo> SAL_CALL >+ queryContentInfo(const css::uno::Reference<css::ucb::XContentIdentifier>& >xId) override; >+}; >+ >diff --git a/ucb/source/ucp/purehttp/PureHttpProvider.cxx >b/ucb/source/ucp/purehttp/PureHttpProvider.cxx >new file mode 100644 >index 000000000000..888888888888 >--- /dev/null >+++ b/ucb/source/ucp/purehttp/PureHttpProvider.cxx >@@ -0,0 +1,70 @@ >+#include "PureHttpProvider.hxx" >+#include "PureHttpContent.hxx" >+ >+using namespace css; >+ >+PureHttpContentProvider::PureHttpContentProvider() >+{ >+} >+ >+uno::Reference<ucb::XContent> SAL_CALL >+PureHttpContentProvider::createContent( >+ const uno::Reference<ucb::XContentIdentifier>& xId) >+{ >+ return new PureHttpContent(xId); >+} >+ >+uno::Reference<ucb::XContentIdentifier> SAL_CALL >+PureHttpContentProvider::queryContentIdentifier( >+ const uno::Reference<ucb::XContentIdentifier>& xId) >+{ >+ return xId; >+} >+ >+uno::Sequence<ucb::ContentInfo> SAL_CALL >+PureHttpContentProvider::queryContentInfo( >+ const uno::Reference<ucb::XContentIdentifier>&) >+{ >+ ucb::ContentInfo info; >+ info.Type = "application/octet-stream"; >+ info.Attributes = 0; >+ info.Commands = uno::Sequence<ucb::CommandInfo>(1); >+ info.Commands[0].Name = com::sun::star::ucb::COMMAND_GET; >+ info.Commands[0].Handle = 0; >+ info.Commands[0].Mandatory = true; >+ return uno::Sequence<ucb::ContentInfo>(1, &info); >+} >+ >diff --git a/ucb/source/ucp/purehttp/PureHttpContent.hxx >b/ucb/source/ucp/purehttp/PureHttpContent.hxx >new file mode 100644 >index 000000000000..999999999999 >--- /dev/null >+++ b/ucb/source/ucp/purehttp/PureHttpContent.hxx >@@ -0,0 +1,60 @@ >+#pragma once >+ >+#include <cppuhelper/implbase.hxx> >+#include <com/sun/star/ucb/XContent.hpp> >+#include <com/sun/star/ucb/XCommandProcessor.hpp> >+#include <com/sun/star/ucb/XContentIdentifier.hpp> >+ >+class PureHttpContent final >+ : public cppu::WeakImplHelper< >+ css::ucb::XContent, >+ css::ucb::XCommandProcessor> >+{ >+ css::uno::Reference<css::ucb::XContentIdentifier> m_xId; >+ >+public: >+ explicit PureHttpContent( >+ const css::uno::Reference<css::ucb::XContentIdentifier>& xId); >+ >+ // XContent >+ virtual css::uno::Reference<css::ucb::XContentIdentifier> SAL_CALL >+ getIdentifier() override; >+ >+ virtual css::uno::Reference<css::ucb::XContentProvider> SAL_CALL >+ getContentProvider() override; >+ >+ virtual css::uno::Sequence<css::ucb::ContentInfo> SAL_CALL >+ queryContentInfo() override; >+ >+ // XCommandProcessor >+ virtual css::uno::Any SAL_CALL >+ execute(const css::ucb::Command& aCommand, >+ sal_Int32 CommandId, >+ const css::uno::Reference<css::ucb::XCommandEnvironment>& xEnv) >override; >+ >+ virtual void SAL_CALL abort(sal_Int32 CommandId) override; >+}; >+ >diff --git a/ucb/source/ucp/purehttp/PureHttpContent.cxx >b/ucb/source/ucp/purehttp/PureHttpContent.cxx >new file mode 100644 >index 000000000000..aaaaaaaaaaaa >--- /dev/null >+++ b/ucb/source/ucp/purehttp/PureHttpContent.cxx >@@ -0,0 +155 @@ >+#include "PureHttpContent.hxx" >+ >+#include <com/sun/star/ucb/XActiveDataSink.hpp> >+#include <com/sun/star/beans/StringPair.hpp> >+#include <osl/diagnose.h> >+ >+using namespace css; >+ >+PureHttpContent::PureHttpContent( >+ const uno::Reference<ucb::XContentIdentifier>& xId) >+ : m_xId(xId) >+{ >+} >+ >+uno::Reference<ucb::XContentIdentifier> SAL_CALL >+PureHttpContent::getIdentifier() >+{ >+ return m_xId; >+} >+ >+uno::Reference<ucb::XContentProvider> SAL_CALL >+PureHttpContent::getContentProvider() >+{ >+ return nullptr; // not needed for this minimal provider >+} >+ >+uno::Sequence<ucb::ContentInfo> SAL_CALL >+PureHttpContent::queryContentInfo() >+{ >+ ucb::ContentInfo info; >+ info.Type = "application/octet-stream"; >+ info.Attributes = 0; >+ info.Commands = uno::Sequence<ucb::CommandInfo>(1); >+ info.Commands[0].Name = com::sun::star::ucb::COMMAND_GET; >+ info.Commands[0].Handle = 0; >+ info.Commands[0].Mandatory = true; >+ return uno::Sequence<ucb::ContentInfo>(1, &info); >+} >+ >+uno::Any SAL_CALL >+PureHttpContent::execute(const ucb::Command& aCommand, >+ sal_Int32, >+ const uno::Reference<ucb::XCommandEnvironment>&) >+{ >+ if (aCommand.Name != com::sun::star::ucb::COMMAND_GET) >+ { >+ throw lang::IllegalArgumentException( >+ "Unsupported command in PureHttpContent", nullptr, 0); >+ } >+ >+ com::sun::star::ucb::GetCommandArgument aArg; >+ if (!(aCommand.Argument >>= aArg)) >+ { >+ throw lang::IllegalArgumentException( >+ "Invalid GetCommandArgument", nullptr, 0); >+ } >+ >+ // Minimal HTTP GET implementation placeholder: >+ // - no HEAD/OPTIONS/PROPFIND >+ // - no WebDAV logic >+ // - only GET with user-defined headers >+ >+ const OUString aUrl = m_xId->getContentIdentifier(); >+ >+ // Here you would plug in existing HTTP client code used by WebDAV, >+ // but without any preflight logic. For illustration, we assume a >+ // hypothetical HttpRequest class. >+ >+ HttpRequest aReq(aUrl); >+ >+ for (const beans::StringPair& h : aArg.RequestHeaders) >+ { >+ aReq.addHeader(h.First, h.Second); >+ } >+ >+ uno::Reference<io::XInputStream> xStream = aReq.performGET(); >+ >+ if (aArg.Sink.is()) >+ { >+ aArg.Sink->setInputStream(xStream); >+ } >+ >+ return uno::Any(); >+} >+ >+void SAL_CALL >+PureHttpContent::abort(sal_Int32) >+{ >+ // No async operations in this minimal implementation. >+} >+ >diff --git a/ucb/source/ucp/purehttp/purehttp.component >b/ucb/source/ucp/purehttp/purehttp.component >new file mode 100644 >index 000000000000..bbbbbbbbbbbb >--- /dev/null >+++ b/ucb/source/ucp/purehttp/purehttp.component >@@ -0,0 +24 @@ >+<?xml version="1.0" encoding="UTF-8"?> >+<component xmlns="http://openoffice.org/2010/uno-component-manifest"> >+ <implementation name="com.sun.star.ucb.PureHttpContentProvider"> >+ <service name="com.sun.star.ucb.ContentProvider"/> >+ </implementation> >+</component> >+ >diff --git a/ucb/source/core/ucbhelper.cxx b/ucb/source/core/ucbhelper.cxx >index cccccccccccc..dddddddddddd 100644 >--- a/ucb/source/core/ucbhelper.cxx >+++ b/ucb/source/core/ucbhelper.cxx >@@ -100,6 +100,16 @@ void UcbHelper::registerProviders() > // existing providers... >+ >+ // Register PureHTTP content provider >+ { >+ uno::Reference<ucb::XContentProvider> xProv( >+ new PureHttpContentProvider()); >+ if (xProv.is()) >+ { >+ m_xUcb->registerContentProvider(xProv, 0); >+ } >+ } > } > >-- >2.45.0 > > > > -- You are receiving this mail because: You are the assignee for the bug.
