diff --git a/CHANGES.md b/CHANGES.md
index 3144129598..e89399d136 100644
--- a/CHANGES.md
+++ b/CHANGES.md
@@ -97,6 +97,21 @@
### Behaviour changes worth reading before upgrading
+- **Blogger and MetaWeblog requests have a size limit.** Roller now answers
+ XML-RPC itself instead of through Apache XML-RPC. Clients see the same
+ methods, faults and fault codes, but a request larger than 1 MiB is
+ refused with HTTP 413. While uploads are on, the limit is raised to fit
+ the largest allowed upload as base64, up to 32 MiB, so a file larger
+ than about 23 MB has to be uploaded in the media library instead of
+ through a blog client. The limit also applies to the
+ decompressed size of a gzip-encoded request. Only the standard XML-RPC
+ value types are accepted.
+ Two other differences: characters outside the Basic Multilingual Plane,
+ such as emoji, are now sent as valid XML (the old library wrote each
+ surrogate half as its own reference, which clients rejected), and stored
+ carriage returns are now sent as `
`, so clients receive line breaks
+ as stored rather than normalised to `\n`.
+
- **Themes from Roller 5.0 or earlier may need updating.** Roller 5.1 removed
the pre-3.0 macros in `WEB-INF/velocity/deprecated/`, such as
`#showWeblogEntries` and `#showBookmarks`, and referrer macros such as
@@ -132,6 +147,9 @@
### Build and packaging
+- Apache XML-RPC (`xmlrpc-common`, `xmlrpc-server`) and `ws-commons-util`
+ are no longer shipped. Apache XML-RPC is end of life, and 3.1.3 has an
+ unfixed denial-of-service issue (CVE-2016-5004).
- Third-party libraries are updated to their latest patch and minor releases,
among them Struts 7.4.0, Spring 7.0.10 and Spring Security 7.1.2. OGNL is
pinned to 3.4.17, which fixes CVE-2026-113730.
diff --git a/app/pom.xml b/app/pom.xml
index e959000802..8018cdb087 100644
--- a/app/pom.xml
+++ b/app/pom.xml
@@ -61,8 +61,6 @@ limitations under the License.
5.3.81.13.22.4.1
- 1.0.2
- 3.1.3
@@ -402,47 +400,6 @@ limitations under the License.
${commons-secure-xml.version}
-
- org.apache.xmlrpc
- xmlrpc-common
- ${xmlrpc-version}
-
-
-
-
- org.apache.xmlrpc
- xmlrpc-server
- ${xmlrpc-version}
-
-
- commons-logging
- commons-logging
-
-
-
-
-
- org.apache.ws.commons.util
- ws-commons-util
- ${ws-commons-util.version}
-
-
- xml-apis
- xml-apis
-
-
-
- junit
- junit
-
-
-
-
diff --git a/app/src/main/java/org/apache/roller/weblogger/business/WebloggerImpl.java b/app/src/main/java/org/apache/roller/weblogger/business/WebloggerImpl.java
index 9696295f0e..52aed9cd73 100644
--- a/app/src/main/java/org/apache/roller/weblogger/business/WebloggerImpl.java
+++ b/app/src/main/java/org/apache/roller/weblogger/business/WebloggerImpl.java
@@ -27,7 +27,6 @@
import org.apache.roller.weblogger.business.runnable.ThreadManager;
import org.apache.roller.weblogger.business.search.IndexManager;
import org.apache.roller.weblogger.business.themes.ThemeManager;
-import org.apache.roller.weblogger.util.SecureXmlParsers;
import java.io.IOException;
@@ -296,10 +295,6 @@ public void initialize() throws InitializationException {
getIndexManager().initialize();
getMediaFileManager().initialize();
- // XML-RPC requests are parsed with Roller's standard parser
- // configuration. If it cannot be applied, startup fails.
- SecureXmlParsers.installForXmlRpc();
-
// we always need to do a flush after initialization because it's
// possible that some changes need to be persisted
try {
diff --git a/app/src/main/java/org/apache/roller/weblogger/util/SecureXmlParsers.java b/app/src/main/java/org/apache/roller/weblogger/util/SecureXmlParsers.java
index 973d93c984..720bce9102 100644
--- a/app/src/main/java/org/apache/roller/weblogger/util/SecureXmlParsers.java
+++ b/app/src/main/java/org/apache/roller/weblogger/util/SecureXmlParsers.java
@@ -23,7 +23,6 @@
import javax.xml.parsers.SAXParserFactory;
import org.apache.commons.xml.secure.SecureSAXParserFactory;
-import org.apache.xmlrpc.util.SAXParsers;
import org.jdom2.JDOMException;
import org.jdom2.input.sax.XMLReaderJDOMFactory;
import org.xml.sax.SAXException;
@@ -82,13 +81,4 @@ public static SAXParserFactory newSAXParserFactory() {
}
return factory;
}
-
- /**
- * Makes the XML-RPC library parse requests with {@link #newSAXParserFactory()}.
- *
- * @throws IllegalStateException if the configuration cannot be applied
- */
- public static void installForXmlRpc() {
- SAXParsers.setSAXParserFactory(newSAXParserFactory());
- }
}
diff --git a/app/src/main/java/org/apache/roller/weblogger/util/XmlCharacters.java b/app/src/main/java/org/apache/roller/weblogger/util/XmlCharacters.java
new file mode 100644
index 0000000000..9c6791d461
--- /dev/null
+++ b/app/src/main/java/org/apache/roller/weblogger/util/XmlCharacters.java
@@ -0,0 +1,59 @@
+/*
+ * 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.util;
+
+/**
+ * Text that Roller writes into XML documents it generates itself.
+ */
+public final class XmlCharacters {
+
+ private XmlCharacters() {
+ }
+
+ /**
+ * Drops the characters that XML 1.0 does not allow in a document (most
+ * control characters, U+FFFE, U+FFFF and unpaired surrogates). Stored
+ * text can contain them, and a single one makes the whole response
+ * unreadable for a client.
+ *
+ * @return the text without those characters, or null for null
+ */
+ public static String withoutIllegalCharacters(String text) {
+ if (text == null) {
+ return null;
+ }
+ StringBuilder out = null;
+ int i = 0;
+ while (i < text.length()) {
+ int cp = text.codePointAt(i);
+ int len = Character.charCount(cp);
+ boolean legal = cp == 0x9 || cp == 0xA || cp == 0xD
+ || (cp >= 0x20 && cp <= 0xD7FF)
+ || (cp >= 0xE000 && cp <= 0xFFFD)
+ || (cp >= 0x10000 && cp <= 0x10FFFF);
+ if (!legal && out == null) {
+ out = new StringBuilder(text.length()).append(text, 0, i);
+ } else if (legal && out != null) {
+ out.appendCodePoint(cp);
+ }
+ i += len;
+ }
+ return out == null ? text : out.toString();
+ }
+}
diff --git a/app/src/main/java/org/apache/roller/weblogger/webservices/atomprotocol/AtomWriter.java b/app/src/main/java/org/apache/roller/weblogger/webservices/atomprotocol/AtomWriter.java
index 8ba6eefdbf..c1f21ba98a 100644
--- a/app/src/main/java/org/apache/roller/weblogger/webservices/atomprotocol/AtomWriter.java
+++ b/app/src/main/java/org/apache/roller/weblogger/webservices/atomprotocol/AtomWriter.java
@@ -31,6 +31,8 @@
import javax.xml.stream.XMLStreamException;
import javax.xml.stream.XMLStreamWriter;
+import org.apache.roller.weblogger.util.XmlCharacters;
+
/**
* Serializes the AtomPub wire model ({@link AtomEntry}, {@link AtomFeed},
* {@link AtomServiceDoc}) to XML using the JDK StAX API. Replaces the ROME and
@@ -276,32 +278,8 @@ private void writeAtomText(XMLStreamWriter w, String name, String text)
w.writeEndElement();
}
- /**
- * Drops the characters that XML 1.0 does not allow in a document (most
- * control characters, U+FFFE, U+FFFF and unpaired surrogates). Stored
- * text can contain them, and a single one makes the whole response
- * unreadable for a client.
- */
+ /** See {@link XmlCharacters#withoutIllegalCharacters(String)}. */
static String xml(String text) {
- if (text == null) {
- return null;
- }
- StringBuilder out = null;
- int i = 0;
- while (i < text.length()) {
- int cp = text.codePointAt(i);
- int len = Character.charCount(cp);
- boolean legal = cp == 0x9 || cp == 0xA || cp == 0xD
- || (cp >= 0x20 && cp <= 0xD7FF)
- || (cp >= 0xE000 && cp <= 0xFFFD)
- || (cp >= 0x10000 && cp <= 0x10FFFF);
- if (!legal && out == null) {
- out = new StringBuilder(text.length()).append(text, 0, i);
- } else if (legal && out != null) {
- out.appendCodePoint(cp);
- }
- i += len;
- }
- return out == null ? text : out.toString();
+ return XmlCharacters.withoutIllegalCharacters(text);
}
}
diff --git a/app/src/main/java/org/apache/roller/weblogger/webservices/xmlrpc/BaseAPIHandler.java b/app/src/main/java/org/apache/roller/weblogger/webservices/xmlrpc/BaseAPIHandler.java
index 766354505a..80e435d717 100644
--- a/app/src/main/java/org/apache/roller/weblogger/webservices/xmlrpc/BaseAPIHandler.java
+++ b/app/src/main/java/org/apache/roller/weblogger/webservices/xmlrpc/BaseAPIHandler.java
@@ -24,7 +24,6 @@
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
-import org.apache.xmlrpc.XmlRpcException;
import org.apache.roller.weblogger.config.WebloggerRuntimeConfig;
import org.apache.roller.weblogger.business.WebloggerFactory;
import org.apache.roller.weblogger.business.UserManager;
@@ -36,7 +35,6 @@
import org.apache.roller.weblogger.pojos.WeblogPermission;
import org.apache.roller.weblogger.ui.core.RollerContext;
import org.apache.roller.weblogger.util.cache.CacheManager;
-import org.apache.xmlrpc.common.XmlRpcNotAuthorizedException;
/**
* Base API handler does user validation, provides exception types, etc.
diff --git a/app/src/main/java/org/apache/roller/weblogger/webservices/xmlrpc/BloggerAPIHandler.java b/app/src/main/java/org/apache/roller/weblogger/webservices/xmlrpc/BloggerAPIHandler.java
index 32fa5b5519..9169ae7cb6 100644
--- a/app/src/main/java/org/apache/roller/weblogger/webservices/xmlrpc/BloggerAPIHandler.java
+++ b/app/src/main/java/org/apache/roller/weblogger/webservices/xmlrpc/BloggerAPIHandler.java
@@ -46,7 +46,6 @@
import org.apache.roller.weblogger.pojos.WeblogPermission;
import org.apache.roller.weblogger.pojos.ThemeTemplate.ComponentType;
import org.apache.roller.weblogger.util.Utilities;
-import org.apache.xmlrpc.XmlRpcException;
/**
* Weblogger XML-RPC Handler for the Blogger v1 API.
diff --git a/app/src/main/java/org/apache/roller/weblogger/webservices/xmlrpc/MetaWeblogAPIHandler.java b/app/src/main/java/org/apache/roller/weblogger/webservices/xmlrpc/MetaWeblogAPIHandler.java
index f618065003..23ac09a1f1 100644
--- a/app/src/main/java/org/apache/roller/weblogger/webservices/xmlrpc/MetaWeblogAPIHandler.java
+++ b/app/src/main/java/org/apache/roller/weblogger/webservices/xmlrpc/MetaWeblogAPIHandler.java
@@ -48,7 +48,6 @@
import org.apache.roller.weblogger.ui.core.RollerContext;
import org.apache.roller.weblogger.util.RollerMessages;
import org.apache.roller.weblogger.util.Utilities;
-import org.apache.xmlrpc.XmlRpcException;
/**
@@ -113,7 +112,7 @@ public Object getCategories(String blogid, String userid, String password)
* @param password Password for said username
* @param struct Contents of the post
* @param publish If true, the blog will be published immediately after the post is made
- * @throws org.apache.xmlrpc.XmlRpcException
+ * @throws XmlRpcException
* @return
*/
public boolean editPost(String postid, String userid, String password,
@@ -213,7 +212,7 @@ public boolean editPost(String postid, String userid, String password,
* @param password Password for said username
* @param struct Contents of the post
* @param publish If true, the blog will be published immediately after the post is made
- * @throws org.apache.xmlrpc.XmlRpcException
+ * @throws XmlRpcException
* @return
*/
public String newPost(String blogid, String userid, String password,
diff --git a/app/src/main/java/org/apache/roller/weblogger/webservices/xmlrpc/RollerXmlRpcServlet.java b/app/src/main/java/org/apache/roller/weblogger/webservices/xmlrpc/RollerXmlRpcServlet.java
index 62db6b427e..2a4a78a8e0 100644
--- a/app/src/main/java/org/apache/roller/weblogger/webservices/xmlrpc/RollerXmlRpcServlet.java
+++ b/app/src/main/java/org/apache/roller/weblogger/webservices/xmlrpc/RollerXmlRpcServlet.java
@@ -16,198 +16,138 @@
* specific language governing permissions and limitations
* under the License.
*/
-/*
- * Derived from org.apache.xmlrpc.webserver.XmlRpcServlet
- * in Apache XML-RPC 3.1.3. Forked into the Roller codebase because
- * xmlrpc-server has no Jakarta Servlet compatible release.
- */
package org.apache.roller.weblogger.webservices.xmlrpc;
import java.io.IOException;
-import java.lang.reflect.InvocationTargetException;
-import java.net.URL;
-import java.util.Enumeration;
+import java.io.InputStream;
+import java.util.Locale;
+import java.util.Map;
+import java.util.zip.GZIPInputStream;
-import jakarta.servlet.ServletConfig;
-import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServlet;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
-import org.apache.xmlrpc.XmlRpcConfig;
-import org.apache.xmlrpc.XmlRpcException;
-import org.apache.xmlrpc.common.TypeConverterFactory;
-import org.apache.xmlrpc.server.AbstractReflectiveHandlerMapping;
-import org.apache.xmlrpc.server.PropertyHandlerMapping;
-import org.apache.xmlrpc.server.RequestProcessorFactoryFactory;
-import org.apache.xmlrpc.server.XmlRpcHandlerMapping;
-import org.apache.xmlrpc.server.XmlRpcServer;
-import org.apache.xmlrpc.util.ReflectionUtil;
-
+import org.apache.roller.weblogger.config.WebloggerRuntimeConfig;
/**
- * A servlet for processing XML-RPC requests, derived from the Apache XML-RPC
- * library's {@code XmlRpcServlet}. The typical use is to configure it in
- * {@code web.xml} with an {@code XmlRpcServlet.properties} resource on the
- * classpath that maps handler names to implementation classes.
+ * The Blogger and MetaWeblog XML-RPC endpoint.
*
- *
The servlet accepts the following init parameters:
- *
- *
Name
Description
- *
enabledForExtensions
Sets the value of
- * {@link XmlRpcConfig#isEnabledForExtensions()} to true.
- *
+ *
Calls are answered by {@link BloggerAPIHandler} under
+ * blogger.* and {@link MetaWeblogAPIHandler} under
+ * metaWeblog.*; see {@link XmlRpcServer}. The endpoint is gated by
+ * XmlRpcEnabledFilter while webservices.enableXmlRpc
+ * is off.
+ *
+ *
Request bodies are capped before they are parsed: at 1 MiB, or, while
+ * uploads are on, at what the largest allowed upload takes as base64 plus room
+ * for the call around it, but never above 32 MiB. The whole body is read and
+ * parsed before the caller is authenticated, so the cap can't simply follow
+ * however large an upload the site allows. The cap applies to the decompressed
+ * size of a gzip-encoded request too.
*/
public class RollerXmlRpcServlet extends HttpServlet {
- private static final long serialVersionUID = 2348768267234L;
+ private static final long serialVersionUID = 1L;
private static final Log log = LogFactory.getLog(RollerXmlRpcServlet.class);
- /**
- * Classpath location of the handler mapping properties file.
- * Roller ships this at {@code org/apache/xmlrpc/webserver/XmlRpcServlet.properties}.
- */
- private static final String HANDLER_MAPPING_RESOURCE =
- "org/apache/xmlrpc/webserver/XmlRpcServlet.properties";
-
- private RollerXmlRpcServletServer server;
- private AbstractReflectiveHandlerMapping.AuthenticationHandler authenticationHandler;
- private RequestProcessorFactoryFactory requestProcessorFactoryFactory;
- private TypeConverterFactory typeConverterFactory;
-
- /**
- * Returns the servlet's instance of {@link RollerXmlRpcServletServer}.
- * @return The configurable server instance.
- */
- public RollerXmlRpcServletServer getXmlRpcServletServer() {
- return server;
- }
-
- private void handleInitParameters(ServletConfig pConfig) throws ServletException {
- for (Enumeration en = pConfig.getInitParameterNames(); en.hasMoreElements(); ) {
- String name = (String) en.nextElement();
- String value = pConfig.getInitParameter(name);
- try {
- if (!ReflectionUtil.setProperty(this, name, value)
- && !ReflectionUtil.setProperty(server, name, value)
- && !ReflectionUtil.setProperty(server.getConfig(), name, value)) {
- throw new ServletException("Unknown init parameter " + name);
- }
- } catch (IllegalAccessException e) {
- throw new ServletException("Illegal access to instance of " + server.getClass().getName()
- + " while setting property " + name + ": " + e.getMessage(), e);
- } catch (InvocationTargetException e) {
- Throwable t = e.getTargetException();
- throw new ServletException("Failed to invoke setter for property " + name
- + " on instance of " + server.getClass().getName()
- + ": " + t.getMessage(), t);
- }
- }
- }
+ /** Smallest request cap, in bytes; also the cap while uploads are off. */
+ static final int MIN_MAX_REQUEST_BYTES = 1024 * 1024;
- public void init(ServletConfig pConfig) throws ServletException {
- super.init(pConfig);
- try {
- server = newXmlRpcServer(pConfig);
- handleInitParameters(pConfig);
- server.setHandlerMapping(newXmlRpcHandlerMapping());
- } catch (XmlRpcException e) {
- try {
- log("Failed to create XmlRpcServer: " + e.getMessage(), e);
- } catch (Throwable ignore) {
- }
- throw new ServletException(e);
- }
- }
-
- /** Sets the servlet's {@link AbstractReflectiveHandlerMapping.AuthenticationHandler}. */
- public void setAuthenticationHandler(AbstractReflectiveHandlerMapping.AuthenticationHandler pHandler) {
- authenticationHandler = pHandler;
- }
-
- /** Returns the servlet's {@link AbstractReflectiveHandlerMapping.AuthenticationHandler}. */
- public AbstractReflectiveHandlerMapping.AuthenticationHandler getAuthenticationHandler() {
- return authenticationHandler;
- }
+ /** Largest request cap, in bytes, however large an upload the site allows. */
+ static final int MAX_MAX_REQUEST_BYTES = 32 * 1024 * 1024;
- /** Sets the servlet's {@link RequestProcessorFactoryFactory}. */
- public void setRequestProcessorFactoryFactory(RequestProcessorFactoryFactory pFactory) {
- requestProcessorFactoryFactory = pFactory;
- }
+ /** Room for the methodCall around a base64 upload, in bytes. */
+ static final int ENVELOPE_BYTES = 64 * 1024;
- /** Returns the servlet's {@link RequestProcessorFactoryFactory}. */
- public RequestProcessorFactoryFactory getRequestProcessorFactoryFactory() {
- return requestProcessorFactoryFactory;
- }
+ static final Map> HANDLERS = Map.of(
+ "blogger", BloggerAPIHandler.class,
+ "metaWeblog", MetaWeblogAPIHandler.class);
- /** Sets the servlet's {@link TypeConverterFactory}. */
- public void setTypeConverterFactory(TypeConverterFactory pFactory) {
- typeConverterFactory = pFactory;
- }
+ private transient XmlRpcServer server;
- /** Returns the servlet's {@link TypeConverterFactory}. */
- public TypeConverterFactory getTypeConverterFactory() {
- return typeConverterFactory;
+ @Override
+ public void init() {
+ server = new XmlRpcServer(HANDLERS);
}
- /**
- * Creates a new instance of {@link RollerXmlRpcServletServer} to process requests.
- * @param pConfig The servlet's configuration.
- * @throws XmlRpcException Creating the server failed.
- */
- protected RollerXmlRpcServletServer newXmlRpcServer(ServletConfig pConfig)
- throws XmlRpcException {
- return new RollerXmlRpcServletServer();
- }
+ @Override
+ protected void doPost(HttpServletRequest request, HttpServletResponse response)
+ throws IOException {
+ int maxBytes = maxRequestBytes();
+ if (request.getContentLengthLong() > maxBytes) {
+ sendText(response, HttpServletResponse.SC_REQUEST_ENTITY_TOO_LARGE, "Request is too large");
+ return;
+ }
- /**
- * Creates a new handler mapping. Loads the property file from the classpath
- * resource {@value #HANDLER_MAPPING_RESOURCE}.
- */
- protected XmlRpcHandlerMapping newXmlRpcHandlerMapping() throws XmlRpcException {
- URL url = Thread.currentThread().getContextClassLoader()
- .getResource(HANDLER_MAPPING_RESOURCE);
- if (url == null) {
- throw new XmlRpcException("Failed to locate resource " + HANDLER_MAPPING_RESOURCE);
+ InputStream in = request.getInputStream();
+ String encoding = request.getHeader("Content-Encoding");
+ boolean gzip = false;
+ if (encoding != null && !encoding.isBlank()
+ && !"identity".equalsIgnoreCase(encoding.trim())) {
+ String name = encoding.trim().toLowerCase(Locale.ROOT);
+ if (!"gzip".equals(name) && !"x-gzip".equals(name)) {
+ sendText(response, HttpServletResponse.SC_UNSUPPORTED_MEDIA_TYPE,
+ "Unsupported Content-Encoding");
+ return;
+ }
+ gzip = true;
}
+
+ byte[] body;
try {
- return newPropertyHandlerMapping(url);
+ if (gzip) {
+ in = new GZIPInputStream(in);
+ }
+ // Read one byte past the cap, so an oversized body can be detected
+ // without holding more than that in memory.
+ body = in.readNBytes(maxBytes + 1);
} catch (IOException e) {
- throw new XmlRpcException("Failed to load resource " + url + ": " + e.getMessage(), e);
- }
- }
-
- /**
- * Creates a new instance of {@link PropertyHandlerMapping} by loading
- * the property file from the given URL.
- */
- protected PropertyHandlerMapping newPropertyHandlerMapping(URL url) throws IOException, XmlRpcException {
- PropertyHandlerMapping mapping = new PropertyHandlerMapping();
- mapping.setAuthenticationHandler(authenticationHandler);
- if (requestProcessorFactoryFactory != null) {
- mapping.setRequestProcessorFactoryFactory(requestProcessorFactoryFactory);
+ if (!gzip) {
+ throw e;
+ }
+ log.debug("Rejecting XML-RPC request with an unreadable gzip body", e);
+ sendText(response, HttpServletResponse.SC_BAD_REQUEST, "Invalid gzip request body");
+ return;
}
- if (typeConverterFactory != null) {
- mapping.setTypeConverterFactory(typeConverterFactory);
- } else {
- mapping.setTypeConverterFactory(server.getTypeConverterFactory());
+ if (body.length > maxBytes) {
+ sendText(response, HttpServletResponse.SC_REQUEST_ENTITY_TOO_LARGE, "Request is too large");
+ return;
}
- mapping.setVoidMethodEnabled(server.getConfig().isEnabledForExtensions());
- mapping.load(Thread.currentThread().getContextClassLoader(), url);
- return mapping;
- }
- /** Handles an XML-RPC POST request. */
- public void doPost(HttpServletRequest pRequest, HttpServletResponse pResponse) throws IOException, ServletException {
- server.execute(pRequest, pResponse);
+ byte[] result = server.execute(body);
+ response.setStatus(HttpServletResponse.SC_OK);
+ response.setContentType("text/xml;charset=UTF-8");
+ response.setContentLength(result.length);
+ response.getOutputStream().write(result);
}
- public void log(String pMessage, Throwable pThrowable) {
- server.getErrorLogger().log(pMessage, pThrowable);
+ /** The largest request body accepted, in bytes. */
+ int maxRequestBytes() {
+ if (!WebloggerRuntimeConfig.getBooleanProperty("uploads.enabled")) {
+ return MIN_MAX_REQUEST_BYTES;
+ }
+ double megabytes;
+ try {
+ megabytes = Double.parseDouble(WebloggerRuntimeConfig.getProperty("uploads.file.maxsize"));
+ } catch (RuntimeException e) {
+ return MIN_MAX_REQUEST_BYTES;
+ }
+ if (!(megabytes > 0)) {
+ return MIN_MAX_REQUEST_BYTES;
+ }
+ double uploadBytes = Math.ceil(megabytes * 1024 * 1024);
+ // base64 takes 4 bytes per 3, plus CRLF after every 76 characters if the client wraps it
+ double base64Bytes = Math.ceil(uploadBytes / 3) * 4 * 78 / 76;
+ double limit = Math.max(MIN_MAX_REQUEST_BYTES, base64Bytes + ENVELOPE_BYTES);
+ return (int) Math.min(limit, MAX_MAX_REQUEST_BYTES);
}
- public void log(String pMessage) {
- log.info(pMessage);
+ private static void sendText(HttpServletResponse response, int status, String text)
+ throws IOException {
+ response.setStatus(status);
+ response.setContentType("text/plain;charset=UTF-8");
+ response.getWriter().write(text);
}
}
diff --git a/app/src/main/java/org/apache/roller/weblogger/webservices/xmlrpc/RollerXmlRpcServletServer.java b/app/src/main/java/org/apache/roller/weblogger/webservices/xmlrpc/RollerXmlRpcServletServer.java
deleted file mode 100644
index 3bed68a639..0000000000
--- a/app/src/main/java/org/apache/roller/weblogger/webservices/xmlrpc/RollerXmlRpcServletServer.java
+++ /dev/null
@@ -1,150 +0,0 @@
-/*
- * 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
- *
- * 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.
- */
-/*
- * Derived from org.apache.xmlrpc.webserver.XmlRpcServletServer
- * in Apache XML-RPC 3.1.3. Forked into the Roller codebase because
- * xmlrpc-server has no Jakarta Servlet compatible release.
- */
-package org.apache.roller.weblogger.webservices.xmlrpc;
-
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.OutputStream;
-
-import jakarta.servlet.ServletException;
-import jakarta.servlet.http.HttpServletRequest;
-import jakarta.servlet.http.HttpServletResponse;
-
-import org.apache.xmlrpc.XmlRpcException;
-import org.apache.xmlrpc.common.ServerStreamConnection;
-import org.apache.xmlrpc.common.XmlRpcHttpRequestConfig;
-import org.apache.xmlrpc.common.XmlRpcHttpRequestConfigImpl;
-import org.apache.xmlrpc.common.XmlRpcStreamRequestConfig;
-import org.apache.xmlrpc.server.XmlRpcHttpServer;
-import org.apache.xmlrpc.server.XmlRpcHttpServerConfig;
-import org.apache.xmlrpc.util.HttpUtil;
-
-
-/**
- * An extension of {@link org.apache.xmlrpc.server.XmlRpcServer}
- * suitable for processing servlet requests.
- */
-public class RollerXmlRpcServletServer extends XmlRpcHttpServer {
-
- protected static class ServletStreamConnection implements ServerStreamConnection {
- private final HttpServletRequest request;
- private final HttpServletResponse response;
-
- protected ServletStreamConnection(HttpServletRequest pRequest,
- HttpServletResponse pResponse) {
- request = pRequest;
- response = pResponse;
- }
-
- /** Returns the servlet request. */
- public HttpServletRequest getRequest() { return request; }
-
- /** Returns the servlet response. */
- public HttpServletResponse getResponse() { return response; }
-
- public InputStream newInputStream() throws IOException {
- return request.getInputStream();
- }
-
- public OutputStream newOutputStream() throws IOException {
- response.setContentType("text/xml");
- return response.getOutputStream();
- }
-
- public void close() throws IOException {
- response.getOutputStream().close();
- }
- }
-
- /**
- * @param pRequest The request object.
- */
- protected XmlRpcHttpRequestConfigImpl newConfig(HttpServletRequest pRequest) {
- return new XmlRpcHttpRequestConfigImpl();
- }
-
- protected XmlRpcHttpRequestConfigImpl getConfig(HttpServletRequest pRequest) {
- XmlRpcHttpRequestConfigImpl result = newConfig(pRequest);
- XmlRpcHttpServerConfig serverConfig = (XmlRpcHttpServerConfig) getConfig();
- result.setBasicEncoding(serverConfig.getBasicEncoding());
- result.setContentLengthOptional(serverConfig.isContentLengthOptional()
- && (pRequest.getHeader("Content-Length") == null));
- result.setEnabledForExtensions(serverConfig.isEnabledForExtensions());
- result.setGzipCompressing(HttpUtil.isUsingGzipEncoding(pRequest.getHeader("Content-Encoding")));
- result.setGzipRequesting(HttpUtil.isUsingGzipEncoding(pRequest.getHeaders("Accept-Encoding")));
- result.setEncoding(pRequest.getCharacterEncoding());
- result.setEnabledForExceptions(serverConfig.isEnabledForExceptions());
- HttpUtil.parseAuthorization(result, pRequest.getHeader("Authorization"));
- return result;
- }
-
- protected ServletStreamConnection newStreamConnection(HttpServletRequest pRequest,
- HttpServletResponse pResponse) {
- return new ServletStreamConnection(pRequest, pResponse);
- }
-
- /**
- * Processes the servlet request.
- * @param pRequest The servlet request being read.
- * @param pResponse The servlet response being created.
- * @throws IOException Reading the request or writing the response failed.
- * @throws ServletException Processing the request failed.
- */
- public void execute(HttpServletRequest pRequest, HttpServletResponse pResponse)
- throws ServletException, IOException {
- XmlRpcHttpRequestConfigImpl config = getConfig(pRequest);
- ServletStreamConnection ssc = newStreamConnection(pRequest, pResponse);
- try {
- super.execute(config, ssc);
- } catch (XmlRpcException e) {
- throw new ServletException(e);
- }
- }
-
- /** Returns whether the request's content length is required. */
- protected boolean isContentLengthRequired(XmlRpcStreamRequestConfig pConfig) {
- if (!pConfig.isEnabledForExtensions()) {
- // The spec requires a content-length.
- return true;
- }
- boolean isRequired = !((XmlRpcHttpServerConfig) getConfig()).isContentLengthOptional();
- if (pConfig instanceof XmlRpcHttpRequestConfig) {
- isRequired |= !((XmlRpcHttpRequestConfig) pConfig).isContentLengthOptional();
- }
- return isRequired;
- }
-
- protected OutputStream getOutputStream(XmlRpcStreamRequestConfig pConfig,
- ServerStreamConnection pConnection,
- int pSize) throws IOException {
- if (pSize != -1) {
- ((ServletStreamConnection) pConnection).getResponse().setContentLength(pSize);
- }
- return super.getOutputStream(pConfig, pConnection, pSize);
- }
-
- protected void setResponseHeader(ServerStreamConnection pConnection, String pHeader, String pValue) {
- ((ServletStreamConnection) pConnection).getResponse().setHeader(pHeader, pValue);
- }
-}
diff --git a/app/src/main/java/org/apache/roller/weblogger/webservices/xmlrpc/XmlRpcException.java b/app/src/main/java/org/apache/roller/weblogger/webservices/xmlrpc/XmlRpcException.java
new file mode 100644
index 0000000000..ce1de990da
--- /dev/null
+++ b/app/src/main/java/org/apache/roller/weblogger/webservices/xmlrpc/XmlRpcException.java
@@ -0,0 +1,52 @@
+/*
+ * 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;
+
+/**
+ * An XML-RPC fault: the client receives {@link #code} as faultCode
+ * and the message as faultString.
+ */
+public class XmlRpcException extends Exception {
+ private static final long serialVersionUID = 1L;
+
+ /** The fault code sent to the client. */
+ public final int code;
+
+ /** The fault string sent to the client; written for the client, not the log. */
+ private final String faultString;
+
+ public XmlRpcException(int code, String faultString) {
+ super(faultString);
+ this.code = code;
+ this.faultString = faultString;
+ }
+
+ /** A fault with code 0. */
+ public XmlRpcException(String message) {
+ this(0, message);
+ }
+
+ public int getCode() {
+ return code;
+ }
+
+ public String getFaultString() {
+ return faultString;
+ }
+}
diff --git a/app/src/main/java/org/apache/roller/weblogger/webservices/xmlrpc/XmlRpcNotAuthorizedException.java b/app/src/main/java/org/apache/roller/weblogger/webservices/xmlrpc/XmlRpcNotAuthorizedException.java
new file mode 100644
index 0000000000..accd99fb9a
--- /dev/null
+++ b/app/src/main/java/org/apache/roller/weblogger/webservices/xmlrpc/XmlRpcNotAuthorizedException.java
@@ -0,0 +1,31 @@
+/*
+ * 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;
+
+/**
+ * A fault for a caller that is not authenticated or not allowed to do what it
+ * asked. Its fault code is 0, as it was with the Apache XML-RPC library.
+ */
+public class XmlRpcNotAuthorizedException extends XmlRpcException {
+ private static final long serialVersionUID = 1L;
+
+ public XmlRpcNotAuthorizedException(String message) {
+ super(message);
+ }
+}
diff --git a/app/src/main/java/org/apache/roller/weblogger/webservices/xmlrpc/XmlRpcRequest.java b/app/src/main/java/org/apache/roller/weblogger/webservices/xmlrpc/XmlRpcRequest.java
new file mode 100644
index 0000000000..5e28031a63
--- /dev/null
+++ b/app/src/main/java/org/apache/roller/weblogger/webservices/xmlrpc/XmlRpcRequest.java
@@ -0,0 +1,30 @@
+/*
+ * 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.util.List;
+
+/**
+ * A parsed XML-RPC methodCall.
+ *
+ * @param methodName the called method, such as blogger.getUsersBlogs
+ * @param params the parameter values, in order
+ */
+record XmlRpcRequest(String methodName, List