diff --git a/spring-web/src/main/java/org/springframework/http/HttpHeaders.java b/spring-web/src/main/java/org/springframework/http/HttpHeaders.java index cd5662fb73..7bbff3aba2 100644 --- a/spring-web/src/main/java/org/springframework/http/HttpHeaders.java +++ b/spring-web/src/main/java/org/springframework/http/HttpHeaders.java @@ -1537,8 +1537,11 @@ public class HttpHeaders implements MultiValueMap, Serializable } /** - * Return all values of a given header name, - * even if this header is set multiple times. + * Return all values of a given header name, even if this header is set + * multiple times. + *

This method supports double-quoted values, as described in + * RFC + * 9110, section 5.5. * @param headerName the header name * @return all associated values * @since 4.3 @@ -1549,7 +1552,7 @@ public class HttpHeaders implements MultiValueMap, Serializable List result = new ArrayList<>(); for (String value : values) { if (value != null) { - Collections.addAll(result, StringUtils.tokenizeToStringArray(value, ",")); + result.addAll(tokenizeQuoted(value)); } } return result; @@ -1557,6 +1560,53 @@ public class HttpHeaders implements MultiValueMap, Serializable return Collections.emptyList(); } + private static List tokenizeQuoted(String str) { + List tokens = new ArrayList<>(); + boolean quoted = false; + boolean trim = true; + StringBuilder builder = new StringBuilder(str.length()); + for (int i = 0; i < str.length(); ++i) { + char ch = str.charAt(i); + if (ch == '"') { + if (builder.isEmpty()) { + quoted = true; + } + else if (quoted) { + quoted = false; + trim = false; + } + else { + builder.append(ch); + } + } + else if (ch == '\\' && quoted && i < str.length() - 1) { + builder.append(str.charAt(++i)); + } + else if (ch == ',' && !quoted) { + addToken(builder, tokens, trim); + builder.setLength(0); + trim = false; + } + else if (quoted || (!builder.isEmpty() && trim) || !Character.isWhitespace(ch)) { + builder.append(ch); + } + } + if (!builder.isEmpty()) { + addToken(builder, tokens, trim); + } + return tokens; + } + + private static void addToken(StringBuilder builder, List tokens, boolean trim) { + String token = builder.toString(); + if (trim) { + token = token.trim(); + } + if (!token.isEmpty()) { + tokens.add(token); + } + } + /** * Remove the well-known {@code "Content-*"} HTTP headers. *

Such headers should be cleared from the response if the intended diff --git a/spring-web/src/test/java/org/springframework/http/HttpHeadersTests.java b/spring-web/src/test/java/org/springframework/http/HttpHeadersTests.java index 68f77065c1..6f24ea477c 100644 --- a/spring-web/src/test/java/org/springframework/http/HttpHeadersTests.java +++ b/spring-web/src/test/java/org/springframework/http/HttpHeadersTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2022 the original author or authors. + * Copyright 2002-2023 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -713,4 +713,19 @@ public class HttpHeadersTests { assertThat(headers2).isEqualTo(headers1); } + @Test + void getValuesAsList() { + HttpHeaders headers = new HttpHeaders(); + headers.add("Foo", "Bar"); + headers.add("Foo", "Baz, Qux"); + headers.add("Quux", "\t\"Corge\", \"Grault\""); + headers.add("Garply", " Waldo \"Fred\\!\", \"\tPlugh, Xyzzy! \""); + headers.add("Example-Dates", "\"Sat, 04 May 1996\", \"Wed, 14 Sep 2005\""); + + assertThat(headers.getValuesAsList("Foo")).containsExactly("Bar", "Baz", "Qux"); + assertThat(headers.getValuesAsList("Quux")).containsExactly("Corge", "Grault"); + assertThat(headers.getValuesAsList("Garply")).containsExactly("Waldo \"Fred\\!\"", "\tPlugh, Xyzzy! "); + assertThat(headers.getValuesAsList("Example-Dates")).containsExactly("Sat, 04 May 1996", "Wed, 14 Sep 2005"); + } + }