This is an automated email from the ASF dual-hosted git repository.
reta pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/cxf.git
The following commit(s) were added to refs/heads/master by this push:
new 6347a2f CXF-8619 Prevent double URL-decoding for form parameters
where the pa… (#878)
6347a2f is described below
commit 6347a2f0c37237f0af041b5b7acd8c4fb283dd25
Author: Jonathan Gallimore <[email protected]>
AuthorDate: Wed Dec 1 17:45:28 2021 +0000
CXF-8619 Prevent double URL-decoding for form parameters where the pa…
(#878)
* CXF-8619 Prevent double URL-decoding for form parameters where the
parameters are read from HttpServletRequest.getParameter()
* Introduce contextual property to mark form params as encoded if they come
from ServletRequest request parameters
Co-authored-by: Andriy Redko <[email protected]>
---
.../java/org/apache/cxf/jaxrs/utils/FormUtils.java | 3 +
.../org/apache/cxf/jaxrs/utils/JAXRSUtils.java | 2 +-
.../org/apache/cxf/jaxrs/utils/FormUtilsTest.java | 4 +-
.../org/apache/cxf/jaxrs/utils/JAXRSUtilsTest.java | 4 +-
.../cxf/systest/jaxrs/form/FormReaderFilter.java | 58 +++++++++++++++
.../systest/jaxrs/form/FormWithFilterServer.java | 85 ++++++++++++++++++++++
.../cxf/systest/jaxrs/form/FormWithFilterTest.java | 75 +++++++++++++++++++
7 files changed, 227 insertions(+), 4 deletions(-)
diff --git
a/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/utils/FormUtils.java
b/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/utils/FormUtils.java
index 979b703..0c14eb0 100644
--- a/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/utils/FormUtils.java
+++ b/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/utils/FormUtils.java
@@ -56,6 +56,7 @@ import org.apache.cxf.transport.http.AbstractHTTPDestination;
public final class FormUtils {
public static final String FORM_PARAMS_FROM_HTTP_PARAMS =
"set.form.parameters.from.http.parameters";
public static final String FORM_PARAM_MAP = "org.apache.cxf.form_data";
+ public static final String FORM_PARAM_MAP_DECODED =
"org.apache.cxf.form_data.decoded";
private static final Logger LOG = LogUtils.getL7dLogger(FormUtils.class);
private static final String MULTIPART_FORM_DATA_TYPE = "form-data";
@@ -179,6 +180,8 @@ public final class FormUtils {
params.put(HttpUtils.urlDecode(paramName),
Arrays.asList(values));
}
logRequestParametersIfNeeded(params, enc);
+ // The form params extracted from the HttpServelRequest are
already decoded
+ m.put(FORM_PARAM_MAP_DECODED, true);
}
}
diff --git
a/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/utils/JAXRSUtils.java
b/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/utils/JAXRSUtils.java
index 9189d2a..43dce37 100644
--- a/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/utils/JAXRSUtils.java
+++ b/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/utils/JAXRSUtils.java
@@ -1169,7 +1169,7 @@ public final class JAXRSUtils {
}
}
- if (decode) {
+ if (decode && !MessageUtils.getContextualBoolean(m,
FormUtils.FORM_PARAM_MAP_DECODED, false)) {
List<String> values = params.get(key);
if (values != null) {
values = values.stream().map(value ->
HttpUtils.urlDecode(value, enc)).collect(Collectors.toList());
diff --git
a/rt/frontend/jaxrs/src/test/java/org/apache/cxf/jaxrs/utils/FormUtilsTest.java
b/rt/frontend/jaxrs/src/test/java/org/apache/cxf/jaxrs/utils/FormUtilsTest.java
index d712cbf..a6eb9bb 100644
---
a/rt/frontend/jaxrs/src/test/java/org/apache/cxf/jaxrs/utils/FormUtilsTest.java
+++
b/rt/frontend/jaxrs/src/test/java/org/apache/cxf/jaxrs/utils/FormUtilsTest.java
@@ -97,7 +97,9 @@ public class FormUtilsTest {
EasyMock.expect(mockMessage.getContextualProperty(FormUtils.FORM_PARAMS_FROM_HTTP_PARAMS))
.andReturn(formPropertyValue).anyTimes();
EasyMock.expect(mockMessage.getExchange()).andReturn(null).anyTimes();
-
+ EasyMock.expect(mockMessage.put(FormUtils.FORM_PARAM_MAP_DECODED,
true))
+ .andReturn(null).anyTimes();
+
mockRequest = EasyMock.createMock(HttpServletRequest.class);
String[] httpParamNames = {HTTP_PARAM1, HTTP_PARAM2};
Enumeration<String> httpParamsEnum =
Collections.enumeration(Arrays.asList(httpParamNames));
diff --git
a/rt/frontend/jaxrs/src/test/java/org/apache/cxf/jaxrs/utils/JAXRSUtilsTest.java
b/rt/frontend/jaxrs/src/test/java/org/apache/cxf/jaxrs/utils/JAXRSUtilsTest.java
index b665aeb..e4df29c 100644
---
a/rt/frontend/jaxrs/src/test/java/org/apache/cxf/jaxrs/utils/JAXRSUtilsTest.java
+++
b/rt/frontend/jaxrs/src/test/java/org/apache/cxf/jaxrs/utils/JAXRSUtilsTest.java
@@ -1571,7 +1571,7 @@ public class JAXRSUtilsTest {
Class<?>[] argType = {String.class, List.class};
Method m = Customer.class.getMethod("testFormParam", argType);
Message messageImpl = createMessage();
- String body = "p1=1&p2=2&p2=3";
+ String body = "p1=hello%2bworld&p2=2&p2=3";
messageImpl.put(Message.REQUEST_URI, "/foo");
MultivaluedMap<String, String> headers = new MetadataMap<>();
if (useMediaType) {
@@ -1585,7 +1585,7 @@ public class JAXRSUtilsTest {
assertEquals("2 form params should've been identified", 2,
params.size());
assertEquals("First Form Parameter not matched correctly",
- "1", params.get(0));
+ "hello+world", params.get(0));
List<String> list = (List<String>)params.get(1);
assertEquals(2, list.size());
assertEquals("2", list.get(0));
diff --git
a/systests/jaxrs/src/test/java/org/apache/cxf/systest/jaxrs/form/FormReaderFilter.java
b/systests/jaxrs/src/test/java/org/apache/cxf/systest/jaxrs/form/FormReaderFilter.java
new file mode 100644
index 0000000..fb2f71a
--- /dev/null
+++
b/systests/jaxrs/src/test/java/org/apache/cxf/systest/jaxrs/form/FormReaderFilter.java
@@ -0,0 +1,58 @@
+/**
+ * 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.
+ */
+package org.apache.cxf.systest.jaxrs.form;
+
+
+import java.io.IOException;
+import java.util.logging.Logger;
+
+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 org.apache.cxf.common.logging.LogUtils;
+
+public class FormReaderFilter implements Filter {
+
+ private static final Logger LOGGER =
LogUtils.getL7dLogger(FormReaderFilter.class);
+
+ @Override
+ public void init(FilterConfig filterConfig) throws ServletException {
+
+ }
+
+ @Override
+ public void doFilter(final ServletRequest servletRequest,
+ final ServletResponse servletResponse,
+ final FilterChain filterChain) throws IOException,
ServletException {
+
+ final String value = servletRequest.getParameter("value");
+ LOGGER.info("Seen value=" + value);
+
+ filterChain.doFilter(servletRequest, servletResponse);
+ }
+
+ @Override
+ public void destroy() {
+
+ }
+}
diff --git
a/systests/jaxrs/src/test/java/org/apache/cxf/systest/jaxrs/form/FormWithFilterServer.java
b/systests/jaxrs/src/test/java/org/apache/cxf/systest/jaxrs/form/FormWithFilterServer.java
new file mode 100644
index 0000000..8f16155
--- /dev/null
+++
b/systests/jaxrs/src/test/java/org/apache/cxf/systest/jaxrs/form/FormWithFilterServer.java
@@ -0,0 +1,85 @@
+/**
+ * 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.
+ */
+
+package org.apache.cxf.systest.jaxrs.form;
+
+import org.apache.cxf.Bus;
+import org.apache.cxf.BusFactory;
+import org.apache.cxf.jaxrs.JAXRSServerFactoryBean;
+import org.apache.cxf.jaxrs.lifecycle.SingletonResourceProvider;
+import org.apache.cxf.testutil.common.AbstractBusTestServerBase;
+import org.apache.cxf.transport.servlet.CXFNonSpringServlet;
+import org.eclipse.jetty.server.Server;
+import org.eclipse.jetty.servlet.FilterHolder;
+import org.eclipse.jetty.servlet.FilterMapping;
+import org.eclipse.jetty.servlet.ServletHandler;
+import org.eclipse.jetty.servlet.ServletHolder;
+
+public class FormWithFilterServer extends AbstractBusTestServerBase {
+ public static final String PORT = allocatePort(FormWithFilterServer.class);
+
+ protected void run() {
+ String busFactory =
System.getProperty(BusFactory.BUS_FACTORY_PROPERTY_NAME);
+ System.setProperty(BusFactory.BUS_FACTORY_PROPERTY_NAME,
"org.apache.cxf.bus.CXFBusFactory");
+ try {
+ CXFNonSpringServlet cxf = new CXFNonSpringServlet();
+ httpServer(cxf).start();
+ serverFactory(cxf.getBus()).create();
+ } catch (Exception e) {
+ throw new RuntimeException(e);
+ } finally {
+ if (busFactory != null) {
+ System.setProperty(BusFactory.BUS_FACTORY_PROPERTY_NAME,
busFactory);
+ } else {
+ System.clearProperty(BusFactory.BUS_FACTORY_PROPERTY_NAME);
+ }
+ }
+ }
+
+ private Server httpServer(CXFNonSpringServlet cxf) {
+ Server server = new Server(Integer.parseInt(PORT));
+ ServletHandler handler = new ServletHandler();
+ server.setHandler(handler);
+ handler.addServletWithMapping(new ServletHolder(cxf), "/*");
+ handler.addFilterWithMapping(new FilterHolder(new FormReaderFilter()),
"/*", FilterMapping.ALL);
+ return server;
+ }
+
+ private JAXRSServerFactoryBean serverFactory(Bus bus) {
+ JAXRSServerFactoryBean sf = new JAXRSServerFactoryBean();
+ sf.setBus(bus);
+ sf.setResourceClasses(FormResource.class);
+ sf.setResourceProvider(FormResource.class,
+ new SingletonResourceProvider(new
FormResource()));
+ sf.setAddress("/");
+ return sf;
+ }
+
+ public static void main(String[] args) {
+ try {
+ FormWithFilterServer s = new FormWithFilterServer();
+ s.start();
+ } catch (Exception ex) {
+ ex.printStackTrace();
+ System.exit(-1);
+ } finally {
+ System.out.println("done!");
+ }
+ }
+}
diff --git
a/systests/jaxrs/src/test/java/org/apache/cxf/systest/jaxrs/form/FormWithFilterTest.java
b/systests/jaxrs/src/test/java/org/apache/cxf/systest/jaxrs/form/FormWithFilterTest.java
new file mode 100644
index 0000000..7e7868f
--- /dev/null
+++
b/systests/jaxrs/src/test/java/org/apache/cxf/systest/jaxrs/form/FormWithFilterTest.java
@@ -0,0 +1,75 @@
+/**
+ * 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.
+ */
+
+package org.apache.cxf.systest.jaxrs.form;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.apache.cxf.jaxrs.model.AbstractResourceInfo;
+import org.apache.cxf.testutil.common.AbstractBusClientServerTestBase;
+import org.apache.http.NameValuePair;
+import org.apache.http.client.entity.UrlEncodedFormEntity;
+import org.apache.http.client.methods.CloseableHttpResponse;
+import org.apache.http.client.methods.HttpPost;
+import org.apache.http.impl.client.CloseableHttpClient;
+import org.apache.http.impl.client.HttpClientBuilder;
+import org.apache.http.message.BasicNameValuePair;
+
+import org.junit.BeforeClass;
+import org.junit.Test;
+
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+public class FormWithFilterTest extends AbstractBusClientServerTestBase {
+ public static final String PORT = FormWithFilterServer.PORT;
+
+ @BeforeClass
+ public static void startServers() throws Exception {
+ AbstractResourceInfo.clearAllMaps();
+ createStaticBus();
+ assertTrue("server did not launch correctly",
+ launchServer(FormWithFilterServer.class));
+ }
+
+ @Test
+ public void testEncodedURL() throws Exception {
+ CloseableHttpClient client = HttpClientBuilder.create().build();
+ HttpPost post = new HttpPost("http://localhost:" + PORT + "/form");
+
+ List<NameValuePair> params = new ArrayList<NameValuePair>();
+ final String expected = "This%2Bis+a+test";
+ params.add(new BasicNameValuePair("value", expected));
+ post.setEntity(new UrlEncodedFormEntity(params));
+
+ try {
+ CloseableHttpResponse response = client.execute(post);
+ assertEquals("POST was not handled successfully",
+ 200, response.getStatusLine().getStatusCode());
+
+ assertEquals(expected,
response.getFirstHeader("FromForm").getValue());
+ assertEquals(expected,
response.getFirstHeader("FromFormParam").getValue());
+ } finally {
+ // Release current connection to the connection pool once you are
done
+ post.releaseConnection();
+ }
+ }
+}