diff --git a/modules/transport/http/src/main/java/org/apache/axis2/transport/http/util/RESTUtil.java b/modules/transport/http/src/main/java/org/apache/axis2/transport/http/util/RESTUtil.java
index f11662d565..1e336a29af 100644
--- a/modules/transport/http/src/main/java/org/apache/axis2/transport/http/util/RESTUtil.java
+++ b/modules/transport/http/src/main/java/org/apache/axis2/transport/http/util/RESTUtil.java
@@ -53,6 +53,7 @@ public static Handler.InvocationResponse processXMLRequest(MessageContext msgCon
InputStream in,
OutputStream out, String contentType)
throws AxisFault {
+ checkJSONOnly(msgContext, contentType);
try {
msgContext.setDoingREST(true);
String charSetEncoding = BuilderUtil.getCharSetEncoding(contentType);
@@ -95,6 +96,7 @@ public static Handler.InvocationResponse processXMLRequest(MessageContext msgCon
OutputStream out, String contentType,
Builder builder)
throws AxisFault {
+ checkJSONOnly(msgContext, contentType);
try {
msgContext.setDoingREST(true);
String charSetEncoding = BuilderUtil.getCharSetEncoding(contentType);
@@ -136,6 +138,7 @@ public static Handler.InvocationResponse processXMLRequest(MessageContext msgCon
public static Handler.InvocationResponse processURLRequest(MessageContext msgContext,
OutputStream out, String contentType)
throws AxisFault {
+ checkJSONOnly(msgContext, contentType);
// here, only the parameters in the URI are supported. Others will be discarded.
try {
@@ -188,6 +191,7 @@ public static Handler.InvocationResponse processURLRequest(MessageContext msgCon
OutputStream out,
String contentType, Builder builder)
throws AxisFault {
+ checkJSONOnly(msgContext, contentType);
// here, only the parameters in the URI are supported. Others will be discarded.
try {
@@ -237,6 +241,24 @@ public static Handler.InvocationResponse processURLRequest(MessageContext msgCon
return invokeAxisEngine(msgContext);
}
+ /**
+ * enableJSONOnly refuses any request whose content type is not application/json.
+ * HTTPTransportUtils.processHTTPPostRequest applies that to POSTed messages; the
+ * REST entry points need the same check, or a GET, PUT or DELETE (and a REST POST
+ * on the standalone server) still reaches the operation through the XML and form
+ * builders.
+ */
+ private static void checkJSONOnly(MessageContext msgContext, String contentType)
+ throws AxisFault {
+ String enableJSONOnly = (String) msgContext.getConfigurationContext()
+ .getAxisConfiguration().getParameterValue(Constants.Configuration.ENABLE_JSON_ONLY);
+ if (enableJSONOnly != null && enableJSONOnly.equalsIgnoreCase("true")
+ && !HTTPTransportUtils.isJSONRequest(contentType)) {
+ throw new AxisFault("JSON-only mode enabled but content-type is not application/json: "
+ + contentType);
+ }
+ }
+
private static Handler.InvocationResponse invokeAxisEngine(MessageContext messageContext)
throws AxisFault {
return AxisEngine.receive(messageContext);
diff --git a/modules/transport/http/src/test/java/org/apache/axis2/transport/http/AxisServletJSONOnlyTest.java b/modules/transport/http/src/test/java/org/apache/axis2/transport/http/AxisServletJSONOnlyTest.java
index e50da72fe8..900c70dcd1 100644
--- a/modules/transport/http/src/test/java/org/apache/axis2/transport/http/AxisServletJSONOnlyTest.java
+++ b/modules/transport/http/src/test/java/org/apache/axis2/transport/http/AxisServletJSONOnlyTest.java
@@ -22,6 +22,7 @@
import java.io.ByteArrayOutputStream;
import java.io.PrintWriter;
import java.io.StringWriter;
+import java.util.concurrent.atomic.AtomicInteger;
import jakarta.servlet.ServletConfig;
import jakarta.servlet.ServletContext;
@@ -34,13 +35,17 @@
import org.apache.axis2.AxisFault;
import org.apache.axis2.Constants;
+import org.apache.axis2.builder.ApplicationXMLBuilder;
+import org.apache.axis2.builder.XFormURLEncodedBuilder;
import org.apache.axis2.context.ConfigurationContext;
import org.apache.axis2.context.ConfigurationContextFactory;
import org.apache.axis2.context.MessageContext;
+import org.apache.axis2.description.AxisOperation;
import org.apache.axis2.description.AxisService;
import org.apache.axis2.description.InOnlyAxisOperation;
import org.apache.axis2.description.Parameter;
import org.apache.axis2.engine.AxisConfiguration;
+import org.apache.axis2.engine.MessageReceiver;
import org.mockito.Mockito;
import static org.mockito.ArgumentMatchers.any;
@@ -204,6 +209,47 @@ public void testEnableJSONOnlyRejectsNonJSONRequests() throws Exception {
responseContent.contains("application/json is mandatory"));
}
+ /**
+ * GET and PUT go through RESTUtil rather than processHTTPPostRequest, and must
+ * refuse a request that is not application/json as POST does, rather than
+ * binding the operation from the query string or an XML body.
+ */
+ public void testEnableJSONOnlyRejectsNonJSONRestRequests() throws Exception {
+ axisConfig.addParameter(new Parameter(Constants.Configuration.ENABLE_JSON_ONLY, "true"));
+ axisConfig.addMessageBuilder("application/x-www-form-urlencoded", new XFormURLEncodedBuilder());
+ axisConfig.addMessageBuilder("application/xml", new ApplicationXMLBuilder());
+
+ final AtomicInteger invocations = new AtomicInteger();
+ AxisOperation operation = axisConfig.getService("TestService")
+ .getOperation(new QName("testOperation"));
+ operation.setMessageReceiver(new MessageReceiver() {
+ public void receive(MessageContext messageContext) {
+ invocations.incrementAndGet();
+ }
+ });
+
+ String uri = "/axis2/services/TestService/testOperation";
+ when(mockRequest.getRequestURI()).thenReturn(uri);
+ when(mockRequest.getRequestURL()).thenReturn(new StringBuffer("http://localhost:8080" + uri));
+
+ when(mockServletContext.getAttribute(AxisServlet.CONFIGURATION_CONTEXT)).thenReturn(configContext);
+ servlet.init(mockServletConfig);
+
+ when(mockRequest.getMethod()).thenReturn("GET");
+ when(mockRequest.getQueryString()).thenReturn("value=1");
+ servlet.doGet(mockRequest, mockResponse);
+
+ when(mockRequest.getMethod()).thenReturn("PUT");
+ when(mockRequest.getQueryString()).thenReturn(null);
+ when(mockRequest.getContentType()).thenReturn("application/xml");
+ when(mockRequest.getInputStream()).thenReturn(
+ new MockServletInputStream("1"));
+ servlet.doPut(mockRequest, mockResponse);
+
+ assertEquals("non-JSON REST requests must not reach the operation", 0, invocations.get());
+ verify(mockResponse, times(2)).setStatus(HttpServletResponse.SC_BAD_REQUEST);
+ }
+
// Helper classes for mocking
private static class MockServletInputStream extends jakarta.servlet.ServletInputStream {
private final ByteArrayInputStream inputStream;
diff --git a/src/site/markdown/release-notes/2.0.3.md b/src/site/markdown/release-notes/2.0.3.md
index 5dc77fff7f..8fb2e19a96 100644
--- a/src/site/markdown/release-notes/2.0.3.md
+++ b/src/site/markdown/release-notes/2.0.3.md
@@ -8,6 +8,7 @@ Apache Axis2 2.0.3 Release Notes
| Change | Restore the old behaviour with |
|---|---|
| The JSON dispatcher no longer selects a service from the XML body namespace | `true` |
+| `enableJSONOnly` refuses non-JSON GET, PUT and DELETE requests as it does POST | no switch; send `Content-Type: application/json` |
`JSONBasedDefaultDispatcher`, the Dispatch-phase dispatcher in the Spring Boot
starter's `axis2-json.xml`, now honours `allowContentBasedServiceDispatch` as the
@@ -17,3 +18,9 @@ URI naming no service, relying on the root element's namespace to pick the servi
now gets a service-not-found fault until the parameter is set. Set it only if
services are genuinely addressed that way, and not alongside per-service security
modules.
+
+`enableJSONOnly` was only enforced for POST. A GET, PUT or DELETE, and on the
+standalone server a POST with an XML or form content type, took the REST path, which
+bound the operation's arguments from the query string or an XML body and invoked it.
+Those requests are now refused unless they carry `Content-Type: application/json`.
+Metadata requests (`?wsdl`, `?xsd`, the service listing) are unaffected.