From d46091565b0da437f6c9d501122e7f2ddaf1f79a Mon Sep 17 00:00:00 2001 From: Rossen Stoyanchev Date: Thu, 26 Nov 2020 16:14:39 +0000 Subject: [PATCH] MessageHeaderAccessor handle self-copy correctly 1. Revert changes in setHeader from 5.2.9 that caused regression on self-copy. 2. Update copyHeaders to ensure it still gets a copy of native headers. 3. Exit if source and target are the same instance, as an optimization. Closes gh-26155 --- .../support/MessageHeaderAccessor.java | 26 ++++---- .../support/NativeMessageHeaderAccessor.java | 60 ++++++++++++------- .../NativeMessageHeaderAccessorTests.java | 10 ++++ 3 files changed, 62 insertions(+), 34 deletions(-) diff --git a/spring-messaging/src/main/java/org/springframework/messaging/support/MessageHeaderAccessor.java b/spring-messaging/src/main/java/org/springframework/messaging/support/MessageHeaderAccessor.java index ef7130f54a9..95e04b63f48 100644 --- a/spring-messaging/src/main/java/org/springframework/messaging/support/MessageHeaderAccessor.java +++ b/spring-messaging/src/main/java/org/springframework/messaging/support/MessageHeaderAccessor.java @@ -378,13 +378,14 @@ public class MessageHeaderAccessor { * {@link #copyHeadersIfAbsent(Map)} to avoid overwriting values. */ public void copyHeaders(@Nullable Map headersToCopy) { - if (headersToCopy != null) { - headersToCopy.forEach((key, value) -> { - if (!isReadOnly(key)) { - setHeader(key, value); - } - }); + if (headersToCopy == null || this.headers == headersToCopy) { + return; } + headersToCopy.forEach((key, value) -> { + if (!isReadOnly(key)) { + setHeader(key, value); + } + }); } /** @@ -392,13 +393,14 @@ public class MessageHeaderAccessor { *

This operation will not overwrite any existing values. */ public void copyHeadersIfAbsent(@Nullable Map headersToCopy) { - if (headersToCopy != null) { - headersToCopy.forEach((key, value) -> { - if (!isReadOnly(key)) { - setHeaderIfAbsent(key, value); - } - }); + if (headersToCopy == null || this.headers == headersToCopy) { + return; } + headersToCopy.forEach((key, value) -> { + if (!isReadOnly(key)) { + setHeaderIfAbsent(key, value); + } + }); } protected boolean isReadOnly(String headerName) { diff --git a/spring-messaging/src/main/java/org/springframework/messaging/support/NativeMessageHeaderAccessor.java b/spring-messaging/src/main/java/org/springframework/messaging/support/NativeMessageHeaderAccessor.java index 3aea04dbca0..25fdb60fa73 100644 --- a/spring-messaging/src/main/java/org/springframework/messaging/support/NativeMessageHeaderAccessor.java +++ b/spring-messaging/src/main/java/org/springframework/messaging/support/NativeMessageHeaderAccessor.java @@ -75,6 +75,8 @@ public class NativeMessageHeaderAccessor extends MessageHeaderAccessor { @SuppressWarnings("unchecked") Map> map = (Map>) getHeader(NATIVE_HEADERS); if (map != null) { + // setHeader checks for equality but we need copy of native headers + setHeader(NATIVE_HEADERS, null); setHeader(NATIVE_HEADERS, new LinkedMultiValueMap<>(map)); } } @@ -103,6 +105,8 @@ public class NativeMessageHeaderAccessor extends MessageHeaderAccessor { if (isMutable()) { Map> map = getNativeHeaders(); if (map != null) { + // setHeader checks for equality but we need immutable wrapper + setHeader(NATIVE_HEADERS, null); setHeader(NATIVE_HEADERS, Collections.unmodifiableMap(map)); } super.setImmutable(); @@ -110,31 +114,19 @@ public class NativeMessageHeaderAccessor extends MessageHeaderAccessor { } @Override - public void setHeader(String name, @Nullable Object value) { - if (name.equalsIgnoreCase(NATIVE_HEADERS)) { - // Force removal since setHeader checks for equality - super.setHeader(NATIVE_HEADERS, null); + public void copyHeaders(@Nullable Map headersToCopy) { + if (headersToCopy == null) { + return; } - super.setHeader(name, value); - } - @Override - @SuppressWarnings("unchecked") - public void copyHeaders(@Nullable Map headersToCopy) { - if (headersToCopy != null) { - Map> nativeHeaders = getNativeHeaders(); - Map> map = (Map>) headersToCopy.get(NATIVE_HEADERS); - if (map != null) { - if (nativeHeaders != null) { - nativeHeaders.putAll(map); - } - else { - nativeHeaders = new LinkedMultiValueMap<>(map); - } - } - super.copyHeaders(headersToCopy); - setHeader(NATIVE_HEADERS, nativeHeaders); + @SuppressWarnings("unchecked") + Map> map = (Map>) headersToCopy.get(NATIVE_HEADERS); + if (map != null && map != getNativeHeaders()) { + map.forEach(this::setNativeHeaderValues); } + + // setHeader checks for equality, native headers should be equal by now + super.copyHeaders(headersToCopy); } /** @@ -201,6 +193,30 @@ public class NativeMessageHeaderAccessor extends MessageHeaderAccessor { } } + /** + * Variant of {@link #addNativeHeader(String, String)} for all values. + * @since 5.2.12 + */ + public void setNativeHeaderValues(String name, @Nullable List values) { + Assert.state(isMutable(), "Already immutable"); + Map> map = getNativeHeaders(); + if (values == null) { + if (map != null && map.get(name) != null) { + setModified(true); + map.remove(name); + } + return; + } + if (map == null) { + map = new LinkedMultiValueMap<>(3); + setHeader(NATIVE_HEADERS, map); + } + if (!ObjectUtils.nullSafeEquals(values, getHeader(name))) { + setModified(true); + map.put(name, new ArrayList<>(values)); + } + } + /** * Add the specified native header value to existing values. *

In order for this to work, the accessor must be {@link #isMutable() diff --git a/spring-messaging/src/test/java/org/springframework/messaging/support/NativeMessageHeaderAccessorTests.java b/spring-messaging/src/test/java/org/springframework/messaging/support/NativeMessageHeaderAccessorTests.java index 726f80c256e..4d0e7b4b672 100644 --- a/spring-messaging/src/test/java/org/springframework/messaging/support/NativeMessageHeaderAccessorTests.java +++ b/spring-messaging/src/test/java/org/springframework/messaging/support/NativeMessageHeaderAccessorTests.java @@ -241,4 +241,14 @@ public class NativeMessageHeaderAccessorTests { assertThat(((NativeMessageHeaderAccessor) accessor).getNativeHeader("foo")).containsExactly("bar", "baz"); } + @Test // gh-26155 + void copySelf() { + NativeMessageHeaderAccessor accessor = new NativeMessageHeaderAccessor(); + accessor.addNativeHeader("foo", "bar"); + accessor.setHeader("otherHeader", "otherHeaderValue"); + accessor.setLeaveMutable(true); + + // Does not fail with ConcurrentModificationException + accessor.copyHeaders(accessor.getMessageHeaders()); + } }