Skip to content

Commit 7e5fef9

Browse files
Anthonyas007artembilan
authored andcommitted
GH-3641: Handle duplicate cookies properly
Fixes #3641 When a duplicate cookie name appears in a request, an `IllegalStateException` is thrown. The default `Collectors.toMap()` does not allow a duplicated keys. * Handle `servletRequest.getCookies()` as a `MultiValueMap` * Call `toSingleValueMap()` for the evaluation context variable to restore previous behavior. The next major version must expose the `MultiValueMap` as is to give access to all cookies from end-user expressions * Rework some HTTP tests to JUnit 5 **Cherry-pick to `5.4.x` & `5.3.x`**
1 parent 6c47593 commit 7e5fef9

File tree

7 files changed

+88
-47
lines changed

7 files changed

+88
-47
lines changed

Diff for: spring-integration-http/src/main/java/org/springframework/integration/http/inbound/HttpRequestHandlingEndpointSupport.java

+8-8
Original file line numberDiff line numberDiff line change
@@ -19,11 +19,8 @@
1919
import java.io.IOException;
2020
import java.lang.reflect.Type;
2121
import java.util.ArrayList;
22-
import java.util.Arrays;
2322
import java.util.List;
2423
import java.util.Map;
25-
import java.util.function.Function;
26-
import java.util.stream.Collectors;
2724

2825
import javax.servlet.http.Cookie;
2926
import javax.servlet.http.HttpServletRequest;
@@ -384,10 +381,13 @@ private StandardEvaluationContext prepareEvaluationContext(HttpServletRequest se
384381

385382
Cookie[] requestCookies = servletRequest.getCookies();
386383
if (!ObjectUtils.isEmpty(requestCookies)) {
387-
Map<String, Cookie> cookies =
388-
Arrays.stream(requestCookies)
389-
.collect(Collectors.toMap(Cookie::getName, Function.identity()));
390-
evaluationContext.setVariable("cookies", cookies);
384+
385+
MultiValueMap<String, Cookie> cookies = new LinkedMultiValueMap<>(requestCookies.length);
386+
for (Cookie requestCookie : requestCookies) {
387+
cookies.add(requestCookie.getName(), requestCookie);
388+
}
389+
// TODO no toSingleValueMap() in the next major version
390+
evaluationContext.setVariable("cookies", cookies.toSingleValueMap());
391391
}
392392

393393
Map<?, ?> pathVariables =
@@ -500,7 +500,7 @@ protected RequestEntity<Object> prepareRequestEntity(ServletServerHttpRequest re
500500
return new RequestEntity<>(requestBody, request.getHeaders(), request.getMethod(), request.getURI());
501501
}
502502

503-
@SuppressWarnings({"unchecked", "rawtypes"})
503+
@SuppressWarnings({ "unchecked", "rawtypes" })
504504
protected Object extractRequestBody(ServletServerHttpRequest request) throws IOException {
505505
MediaType contentType = request.getHeaders().getContentType();
506506
if (contentType == null) {

Diff for: spring-integration-http/src/test/java/org/springframework/integration/http/AbstractHttpInboundTests.java

+6-5
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2013-2019 the original author or authors.
2+
* Copyright 2013-2021 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -16,25 +16,26 @@
1616

1717
package org.springframework.integration.http;
1818

19-
import org.junit.After;
20-
import org.junit.Before;
19+
import org.junit.jupiter.api.AfterEach;
20+
import org.junit.jupiter.api.BeforeEach;
2121

2222
import org.springframework.mock.web.MockHttpServletRequest;
2323
import org.springframework.web.context.request.RequestContextHolder;
2424
import org.springframework.web.context.request.ServletRequestAttributes;
2525

2626
/**
2727
* @author Artem Bilan
28+
*
2829
* @since 3.0
2930
*/
3031
public abstract class AbstractHttpInboundTests {
3132

32-
@Before
33+
@BeforeEach
3334
public void setupHttpInbound() {
3435
RequestContextHolder.setRequestAttributes(new ServletRequestAttributes(new MockHttpServletRequest()));
3536
}
3637

37-
@After
38+
@AfterEach
3839
public void tearDownHttpInbound() {
3940
RequestContextHolder.resetRequestAttributes();
4041
}

Diff for: spring-integration-http/src/test/java/org/springframework/integration/http/config/HttpInboundChannelAdapterParserTests.java

+4-5
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2019 the original author or authors.
2+
* Copyright 2002-2021 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -30,8 +30,7 @@
3030

3131
import javax.servlet.http.HttpServletResponse;
3232

33-
import org.junit.Test;
34-
import org.junit.runner.RunWith;
33+
import org.junit.jupiter.api.Test;
3534

3635
import org.springframework.beans.factory.annotation.Autowired;
3736
import org.springframework.beans.factory.annotation.Qualifier;
@@ -52,7 +51,7 @@
5251
import org.springframework.mock.web.MockHttpServletRequest;
5352
import org.springframework.mock.web.MockHttpServletResponse;
5453
import org.springframework.test.annotation.DirtiesContext;
55-
import org.springframework.test.context.junit4.SpringRunner;
54+
import org.springframework.test.context.junit.jupiter.SpringJUnitConfig;
5655
import org.springframework.util.AntPathMatcher;
5756
import org.springframework.util.MultiValueMap;
5857
import org.springframework.validation.Validator;
@@ -68,7 +67,7 @@
6867
* @author Artem Bilan
6968
* @author Biju Kunjummen
7069
*/
71-
@RunWith(SpringRunner.class)
70+
@SpringJUnitConfig
7271
@DirtiesContext
7372
public class HttpInboundChannelAdapterParserTests extends AbstractHttpInboundTests {
7473

Diff for: spring-integration-http/src/test/java/org/springframework/integration/http/inbound/HttpRequestHandlingControllerTests.java

+58-11
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2019 the original author or authors.
2+
* Copyright 2002-2021 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -25,8 +25,10 @@
2525
import java.util.concurrent.atomic.AtomicBoolean;
2626
import java.util.concurrent.atomic.AtomicInteger;
2727

28+
import javax.servlet.http.Cookie;
29+
2830
import org.apache.commons.logging.LogFactory;
29-
import org.junit.Test;
31+
import org.junit.jupiter.api.Test;
3032

3133
import org.springframework.beans.factory.BeanFactory;
3234
import org.springframework.expression.Expression;
@@ -51,12 +53,14 @@
5153
* @author Gunnar Hillert
5254
* @author Biju Kunjummen
5355
* @author Artem Bilan
56+
* @author Anthony Schweigard
57+
*
5458
* @since 2.0
5559
*/
5660
public class HttpRequestHandlingControllerTests extends AbstractHttpInboundTests {
5761

5862
@Test
59-
public void sendOnly() throws Exception {
63+
public void sendOnly() {
6064
QueueChannel requestChannel = new QueueChannel();
6165
HttpRequestHandlingController controller = new HttpRequestHandlingController(false);
6266
controller.setBeanFactory(mock(BeanFactory.class));
@@ -83,7 +87,7 @@ public void sendOnly() throws Exception {
8387
}
8488

8589
@Test
86-
public void sendOnlyViewExpression() throws Exception {
90+
public void sendOnlyViewExpression() {
8791
QueueChannel requestChannel = new QueueChannel();
8892
HttpRequestHandlingController controller = new HttpRequestHandlingController(false);
8993
controller.setBeanFactory(mock(BeanFactory.class));
@@ -111,9 +115,10 @@ public void sendOnlyViewExpression() throws Exception {
111115
}
112116

113117
@Test
114-
public void requestReply() throws Exception {
118+
public void requestReply() {
115119
DirectChannel requestChannel = new DirectChannel();
116120
AbstractReplyProducingMessageHandler handler = new AbstractReplyProducingMessageHandler() {
121+
117122
@Override
118123
protected Object handleRequestMessage(Message<?> requestMessage) {
119124
return requestMessage.getPayload().toString().toUpperCase();
@@ -145,9 +150,10 @@ protected Object handleRequestMessage(Message<?> requestMessage) {
145150
}
146151

147152
@Test
148-
public void requestReplyViewExpressionString() throws Exception {
153+
public void requestReplyViewExpressionString() {
149154
DirectChannel requestChannel = new DirectChannel();
150155
AbstractReplyProducingMessageHandler handler = new AbstractReplyProducingMessageHandler() {
156+
151157
@Override
152158
protected Message<String> handleRequestMessage(Message<?> requestMessage) {
153159
return MessageBuilder.withPayload("foo")
@@ -177,10 +183,11 @@ protected Message<String> handleRequestMessage(Message<?> requestMessage) {
177183
}
178184

179185
@Test
180-
public void requestReplyViewExpressionView() throws Exception {
186+
public void requestReplyViewExpressionView() {
181187
final View view = mock(View.class);
182188
DirectChannel requestChannel = new DirectChannel();
183189
AbstractReplyProducingMessageHandler handler = new AbstractReplyProducingMessageHandler() {
190+
184191
@Override
185192
protected Message<String> handleRequestMessage(Message<?> requestMessage) {
186193
return MessageBuilder.withPayload("foo")
@@ -210,9 +217,10 @@ protected Message<String> handleRequestMessage(Message<?> requestMessage) {
210217
}
211218

212219
@Test
213-
public void requestReplyWithCustomReplyKey() throws Exception {
220+
public void requestReplyWithCustomReplyKey() {
214221
DirectChannel requestChannel = new DirectChannel();
215222
AbstractReplyProducingMessageHandler handler = new AbstractReplyProducingMessageHandler() {
223+
216224
@Override
217225
protected Object handleRequestMessage(Message<?> requestMessage) {
218226
return requestMessage.getPayload().toString().toUpperCase();
@@ -245,9 +253,10 @@ protected Object handleRequestMessage(Message<?> requestMessage) {
245253
}
246254

247255
@Test
248-
public void requestReplyWithFullMessageInModel() throws Exception {
256+
public void requestReplyWithFullMessageInModel() {
249257
DirectChannel requestChannel = new DirectChannel();
250258
AbstractReplyProducingMessageHandler handler = new AbstractReplyProducingMessageHandler() {
259+
251260
@Override
252261
protected Object handleRequestMessage(Message<?> requestMessage) {
253262
return requestMessage.getPayload().toString().toUpperCase();
@@ -281,8 +290,9 @@ protected Object handleRequestMessage(Message<?> requestMessage) {
281290
}
282291

283292
@Test
284-
public void testSendWithError() throws Exception {
293+
public void testSendWithError() {
285294
QueueChannel requestChannel = new QueueChannel() {
295+
286296
@Override
287297
protected boolean doSend(Message<?> message, long timeout) {
288298
throw new RuntimeException("Planned");
@@ -310,11 +320,12 @@ protected boolean doSend(Message<?> message, long timeout) {
310320
}
311321

312322
@Test
313-
public void shutDown() throws Exception {
323+
public void shutDown() {
314324
DirectChannel requestChannel = new DirectChannel();
315325
final CountDownLatch latch1 = new CountDownLatch(1);
316326
final CountDownLatch latch2 = new CountDownLatch(1);
317327
AbstractReplyProducingMessageHandler handler = new AbstractReplyProducingMessageHandler() {
328+
318329
@Override
319330
protected Object handleRequestMessage(Message<?> requestMessage) {
320331
try {
@@ -379,5 +390,41 @@ protected Object handleRequestMessage(Message<?> requestMessage) {
379390
assertThat(reply).isEqualTo("HELLO");
380391
}
381392

393+
@Test
394+
public void handleRequestDuplicateCookies() {
395+
DirectChannel requestChannel = new DirectChannel();
396+
requestChannel.subscribe(new AbstractReplyProducingMessageHandler() {
397+
398+
@Override
399+
protected Object handleRequestMessage(Message<?> requestMessage) {
400+
return requestMessage.getPayload().toString();
401+
}
402+
});
403+
404+
HttpRequestHandlingController controller = new HttpRequestHandlingController(true);
405+
controller.setErrorsKey("errors");
406+
controller.setRequestChannel(requestChannel);
407+
controller.setViewName("foo");
408+
controller.setReplyKey("cookiesReply");
409+
controller.setExtractReplyPayload(true);
410+
controller.setPayloadExpression(new SpelExpressionParser().parseExpression("#cookies['c1']?.value"));
411+
controller.setBeanFactory(mock(BeanFactory.class));
412+
controller.afterPropertiesSet();
413+
controller.start();
414+
415+
MockHttpServletRequest request = new MockHttpServletRequest();
416+
request.setMethod("POST");
417+
request.setContent("hello".getBytes());
418+
request.addHeader("Content-Type", "text/plain");
419+
request.setCookies(new Cookie("c1", "first"), new Cookie("c1", "last"));
420+
421+
MockHttpServletResponse response = new MockHttpServletResponse();
422+
423+
ModelAndView modelAndView = controller.handleRequest(request, response);
424+
assertThat(modelAndView.getModelMap()).doesNotContainKey("errors");
425+
assertThat(modelAndView.getModelMap()).containsKey("cookiesReply");
426+
assertThat(modelAndView.getModelMap()).containsEntry("cookiesReply", "first");
427+
}
428+
382429

383430
}

Diff for: spring-integration-http/src/test/java/org/springframework/integration/http/inbound/HttpRequestHandlingMessagingGatewayTests.java

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2019 the original author or authors.
2+
* Copyright 2002-2021 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -31,7 +31,7 @@
3131

3232
import javax.servlet.http.HttpServletRequest;
3333

34-
import org.junit.Test;
34+
import org.junit.jupiter.api.Test;
3535

3636
import org.springframework.beans.factory.BeanFactory;
3737
import org.springframework.core.ParameterizedTypeReference;

Diff for: spring-integration-http/src/test/java/org/springframework/integration/http/inbound/HttpRequestHandlingMessagingGatewayWithPathMappingTests.java

+5-8
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2019 the original author or authors.
2+
* Copyright 2002-2021 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -19,10 +19,9 @@
1919
import static org.assertj.core.api.Assertions.assertThat;
2020
import static org.mockito.Mockito.mock;
2121

22-
import java.io.IOException;
2322
import java.util.Map;
2423

25-
import org.junit.Test;
24+
import org.junit.jupiter.api.Test;
2625

2726
import org.springframework.beans.factory.BeanFactory;
2827
import org.springframework.expression.ExpressionParser;
@@ -52,7 +51,7 @@ public class HttpRequestHandlingMessagingGatewayWithPathMappingTests extends Abs
5251

5352

5453
@Test
55-
public void withoutExpression() throws IOException {
54+
public void withoutExpression() {
5655
DirectChannel echoChannel = new DirectChannel();
5756
echoChannel.subscribe(message -> {
5857
MessageChannel replyChannel = (MessageChannel) message.getHeaders().getReplyChannel();
@@ -78,8 +77,6 @@ public void withoutExpression() throws IOException {
7877

7978
gateway.setRequestChannel(echoChannel);
8079

81-
MockHttpServletResponse response = new MockHttpServletResponse();
82-
8380
RequestEntity<Object> httpEntity = prepareRequestEntity(body, new ServletServerHttpRequest(request));
8481

8582
Object result = gateway.doHandleRequest(request, httpEntity);
@@ -89,7 +86,7 @@ public void withoutExpression() throws IOException {
8986
}
9087

9188
@Test
92-
public void withPayloadExpressionPointingToPathVariable() throws Exception {
89+
public void withPayloadExpressionPointingToPathVariable() {
9390
DirectChannel echoChannel = new DirectChannel();
9491
echoChannel.subscribe(message -> {
9592
MessageChannel replyChannel = (MessageChannel) message.getHeaders().getReplyChannel();
@@ -132,7 +129,7 @@ public void withPayloadExpressionPointingToPathVariable() throws Exception {
132129

133130
@SuppressWarnings("unchecked")
134131
@Test
135-
public void withoutPayloadExpressionPointingToUriVariables() throws Exception {
132+
public void withoutPayloadExpressionPointingToUriVariables() {
136133

137134
DirectChannel echoChannel = new DirectChannel();
138135
echoChannel.subscribe(message -> {

0 commit comments

Comments
 (0)