From 6d424d7edaab0d281c97d0b91a625feee8449c32 Mon Sep 17 00:00:00 2001 From: "David M. Johnson" Date: Sat, 29 Aug 2026 10:31:30 -0400 Subject: [PATCH] Disable vendor extension types on the XML-RPC servlet Roller's XML-RPC API uses only the standard XML-RPC value types; the library's vendor extension types are unused and are switched off. The endpoint is also gated on webservices.enableXmlRpc so a disabled service is not reachable and does not read request bodies. Adds XmlRpcExtensionTypeTest, which checks the shipped configuration and that ordinary calls still parse. Claude-Session: https://claude.ai/code/session_01A1fhY1E2PCFU6UAPXu2WtV --- .../ui/core/filters/XmlRpcEnabledFilter.java | 66 +++++++++++ app/src/main/webapp/WEB-INF/web.xml | 14 ++- .../xmlrpc/XmlRpcExtensionTypeTest.java | 103 ++++++++++++++++++ 3 files changed, 182 insertions(+), 1 deletion(-) create mode 100644 app/src/main/java/org/apache/roller/weblogger/ui/core/filters/XmlRpcEnabledFilter.java create mode 100644 app/src/test/java/org/apache/roller/weblogger/webservices/xmlrpc/XmlRpcExtensionTypeTest.java diff --git a/app/src/main/java/org/apache/roller/weblogger/ui/core/filters/XmlRpcEnabledFilter.java b/app/src/main/java/org/apache/roller/weblogger/ui/core/filters/XmlRpcEnabledFilter.java new file mode 100644 index 000000000..e99fd4f9a --- /dev/null +++ b/app/src/main/java/org/apache/roller/weblogger/ui/core/filters/XmlRpcEnabledFilter.java @@ -0,0 +1,66 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. 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 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. For additional information regarding + * copyright in this work, please see the NOTICE file in the top level + * directory of this distribution. + */ + +package org.apache.roller.weblogger.ui.core.filters; + +import java.io.IOException; +import javax.servlet.Filter; +import javax.servlet.FilterChain; +import javax.servlet.FilterConfig; +import javax.servlet.ServletException; +import javax.servlet.ServletRequest; +import javax.servlet.ServletResponse; +import javax.servlet.http.HttpServletResponse; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.apache.roller.weblogger.config.WebloggerRuntimeConfig; + +/** + * Gates the XML-RPC endpoint on webservices.enableXmlRpc, ahead of + * the servlet, so a disabled service is not reachable. + */ +public class XmlRpcEnabledFilter implements Filter { + + private static final Log LOG = LogFactory.getLog(XmlRpcEnabledFilter.class); + + @Override + public void init(FilterConfig filterConfig) { + // nothing to configure + } + + @Override + public void doFilter(ServletRequest request, ServletResponse response, + FilterChain chain) throws IOException, ServletException { + + if (!WebloggerRuntimeConfig.getBooleanProperty("webservices.enableXmlRpc")) { + if (LOG.isDebugEnabled()) { + LOG.debug("XML-RPC service is disabled; rejecting request"); + } + ((HttpServletResponse) response).sendError(HttpServletResponse.SC_NOT_FOUND); + return; + } + + chain.doFilter(request, response); + } + + @Override + public void destroy() { + // nothing to release + } +} diff --git a/app/src/main/webapp/WEB-INF/web.xml b/app/src/main/webapp/WEB-INF/web.xml index 0418832da..63f53f062 100644 --- a/app/src/main/webapp/WEB-INF/web.xml +++ b/app/src/main/webapp/WEB-INF/web.xml @@ -45,6 +45,11 @@ org.apache.roller.weblogger.ui.core.filters.BootstrapFilter + + XmlRpcEnabledFilter + org.apache.roller.weblogger.ui.core.filters.XmlRpcEnabledFilter + + RequestMappingFilter org.apache.roller.weblogger.ui.rendering.filters.RequestMappingFilter @@ -154,6 +159,11 @@ + + XmlRpcEnabledFilter + /roller-services/xmlrpc + + RequestMappingFilter /* @@ -296,9 +306,11 @@ Sets whether the servlet supports vendor extensions for XML-RPC. + Roller uses only the standard XML-RPC value types; the library's + vendor extension types are unused and are switched off. enabledForExtensions - true + false diff --git a/app/src/test/java/org/apache/roller/weblogger/webservices/xmlrpc/XmlRpcExtensionTypeTest.java b/app/src/test/java/org/apache/roller/weblogger/webservices/xmlrpc/XmlRpcExtensionTypeTest.java new file mode 100644 index 000000000..848a44d77 --- /dev/null +++ b/app/src/test/java/org/apache/roller/weblogger/webservices/xmlrpc/XmlRpcExtensionTypeTest.java @@ -0,0 +1,103 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. 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 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. For additional information regarding + * copyright in this work, please see the NOTICE file in the top level + * directory of this distribution. + */ +package org.apache.roller.weblogger.webservices.xmlrpc; + +import java.io.ByteArrayInputStream; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; + +import org.apache.xmlrpc.parser.XmlRpcRequestParser; +import org.apache.xmlrpc.common.XmlRpcHttpRequestConfigImpl; +import org.apache.xmlrpc.common.TypeFactoryImpl; +import org.apache.xmlrpc.common.XmlRpcController; +import org.apache.xmlrpc.server.XmlRpcServer; +import org.junit.jupiter.api.Test; +import org.xml.sax.InputSource; +import org.xml.sax.XMLReader; +import org.xml.sax.helpers.XMLReaderFactory; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +/** + * Covers how the XML-RPC endpoint treats vendor extension types. + * + *

Roller's XML-RPC API uses only the standard value types, so the library's + * vendor extension types are switched off in web.xml and the + * endpoint is closed entirely while the service is disabled. + */ +public class XmlRpcExtensionTypeTest { + + private static final Path WEB_XML = + Paths.get("src", "main", "webapp", "WEB-INF", "web.xml"); + + private String parseRequest(String xml, boolean extensionsEnabled) throws Exception { + XmlRpcServer server = new XmlRpcServer(); + XmlRpcHttpRequestConfigImpl config = new XmlRpcHttpRequestConfigImpl(); + config.setEnabledForExtensions(extensionsEnabled); + + XmlRpcRequestParser parser = new XmlRpcRequestParser( + config, new TypeFactoryImpl((XmlRpcController) server)); + XMLReader reader = XMLReaderFactory.createXMLReader(); + reader.setContentHandler(parser); + reader.parse(new InputSource( + new ByteArrayInputStream(xml.getBytes(StandardCharsets.UTF_8)))); + return parser.getMethodName(); + } + + private static final String ORDINARY_REQUEST = + "" + + "blogger.getUsersBlogs" + + "hello" + + ""; + + /** The shipped configuration must not enable the extension types. */ + @Test + public void shippedConfigurationDisablesExtensionTypes() throws Exception { + String webXml = new String(Files.readAllBytes(WEB_XML), StandardCharsets.UTF_8); + int idx = webXml.indexOf("enabledForExtensions"); + assertTrue(idx > 0, "enabledForExtensions param not found in web.xml"); + String tail = webXml.substring(idx, Math.min(idx + 300, webXml.length())); + assertTrue(tail.contains("false"), + "the XML-RPC servlet must not enable vendor extension types:\n" + tail); + } + + /** The endpoint is closed by a filter while the service is switched off. */ + @Test + public void endpointIsGatedWhileTheServiceIsDisabled() throws Exception { + String webXml = new String(Files.readAllBytes(WEB_XML), StandardCharsets.UTF_8); + assertTrue(webXml.contains("XmlRpcEnabledFilter"), + "a filter must gate the XML-RPC endpoint"); + int mapping = webXml.indexOf("XmlRpcEnabledFilter", + webXml.indexOf("")); + assertTrue(mapping > 0, "the gating filter must be mapped"); + assertTrue(webXml.indexOf("/roller-services/xmlrpc", mapping) > 0, + "the gating filter must be mapped to the XML-RPC endpoint"); + } + + /** Ordinary XML-RPC calls must still parse with extensions disabled. */ + @Test + public void ordinaryCallsStillParseWithExtensionsDisabled() throws Exception { + String methodName = parseRequest(ORDINARY_REQUEST, false); + assertNotNull(methodName, "an ordinary XML-RPC call must still parse"); + assertEquals("blogger.getUsersBlogs", methodName); + } +} \ No newline at end of file