From 4bb08c2cf4399db04a53160933afc14fb893d7d6 Mon Sep 17 00:00:00 2001 From: Zibran Khan Date: Mon, 2 Nov 2026 19:16:06 +0530 Subject: [PATCH] apply enableJSONOnly to the REST entry points in RESTUtil --- .../axis2/transport/http/util/RESTUtil.java | 22 +++++++++ .../http/AxisServletJSONOnlyTest.java | 46 +++++++++++++++++++ src/site/markdown/release-notes/2.0.3.md | 7 +++ 3 files changed, 75 insertions(+) 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.