Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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 {

Expand Down Expand Up @@ -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 {

Expand Down Expand Up @@ -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);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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;
Expand Down Expand Up @@ -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("<testOperation><value>1</value></testOperation>"));
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;
Expand Down
7 changes: 7 additions & 0 deletions src/site/markdown/release-notes/2.0.3.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 | `<parameter name="allowContentBasedServiceDispatch">true</parameter>` |
| `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
Expand All @@ -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.