diff --git a/jetty-core/jetty-http/src/main/java/org/eclipse/jetty/http/HttpFields.java b/jetty-core/jetty-http/src/main/java/org/eclipse/jetty/http/HttpFields.java index 4de6e6b10eb7..93d73d3825f5 100644 --- a/jetty-core/jetty-http/src/main/java/org/eclipse/jetty/http/HttpFields.java +++ b/jetty-core/jetty-http/src/main/java/org/eclipse/jetty/http/HttpFields.java @@ -617,7 +617,7 @@ default Enumeration getFieldNames() default Set getFieldNamesCollection() { Set seenByHeader = EnumSet.noneOf(HttpHeader.class); - Set seenByName = null; + Set buildByName = null; List list = new ArrayList<>(size()); for (HttpField f : this) @@ -625,9 +625,9 @@ default Set getFieldNamesCollection() HttpHeader header = f.getHeader(); if (header == null) { - if (seenByName == null) - seenByName = new TreeSet<>(String::compareToIgnoreCase); - if (seenByName.add(f.getName())) + if (buildByName == null) + buildByName = new TreeSet<>(String::compareToIgnoreCase); + if (buildByName.add(f.getName())) list.add(f.getName()); } else if (seenByHeader.add(header)) @@ -636,6 +636,8 @@ else if (seenByHeader.add(header)) } } + Set seenByName = buildByName; + // use the list to retain a rough ordering return new AbstractSet<>() { @@ -650,6 +652,14 @@ public int size() { return list.size(); } + + @Override + public boolean contains(Object o) + { + if (o instanceof String s) + return seenByName != null && seenByName.contains(s) || seenByHeader.contains(HttpHeader.CACHE.get(s)); + return false; + } }; } diff --git a/jetty-core/jetty-http/src/main/java/org/eclipse/jetty/http/ImmutableHttpFields.java b/jetty-core/jetty-http/src/main/java/org/eclipse/jetty/http/ImmutableHttpFields.java index f00e9b85fec7..d8b5063f758b 100644 --- a/jetty-core/jetty-http/src/main/java/org/eclipse/jetty/http/ImmutableHttpFields.java +++ b/jetty-core/jetty-http/src/main/java/org/eclipse/jetty/http/ImmutableHttpFields.java @@ -244,15 +244,15 @@ public void set(HttpField field) /** * An immutable {@link HttpFields} instance, optimized for random field access. */ - private static class RandomAccess implements HttpFields + public static class RandomAccess implements HttpFields { private final HttpFields _httpFields; private final EnumMap _enumMap = new EnumMap<>(HttpHeader.class); private final Map _stringMap; - RandomAccess(org.eclipse.jetty.http.ImmutableHttpFields httpFields) + RandomAccess(HttpFields httpFields) { - _httpFields = Objects.requireNonNull(httpFields); + _httpFields = Objects.requireNonNull(httpFields.asImmutable()); Map stringMap = null; for (HttpField field : httpFields) { @@ -354,7 +354,16 @@ public ListIterator listIterator(int index) @Override public Set getFieldNamesCollection() { - LinkedHashSet set = new LinkedHashSet<>(); + LinkedHashSet set = new LinkedHashSet<>() + { + @Override + public boolean contains(Object o) + { + if (o instanceof String s) + return _stringMap.containsKey(s) || _enumMap.containsKey(HttpHeader.CACHE.get(s)); + return false; + } + }; TreeSet seenByName = null; for (HttpField field : _httpFields) { diff --git a/jetty-core/jetty-http/src/test/java/org/eclipse/jetty/http/HttpFieldsTest.java b/jetty-core/jetty-http/src/test/java/org/eclipse/jetty/http/HttpFieldsTest.java index 938ca15d8676..3913e63903bb 100644 --- a/jetty-core/jetty-http/src/test/java/org/eclipse/jetty/http/HttpFieldsTest.java +++ b/jetty-core/jetty-http/src/test/java/org/eclipse/jetty/http/HttpFieldsTest.java @@ -25,6 +25,7 @@ import java.util.Locale; import java.util.Map; import java.util.NoSuchElementException; +import java.util.Set; import java.util.function.Consumer; import java.util.stream.Collectors; import java.util.stream.Stream; @@ -354,16 +355,27 @@ public void testCaseInsensitive() assertThat(header.get("EXPECT"), is("100")); assertThat(header.get("eXpEcT"), is("100")); assertThat(header.get(HttpHeader.EXPECT), is("100")); + assertTrue(header.contains("expect")); + assertTrue(header.contains("Expect")); + assertTrue(header.contains("EXPECT")); + assertTrue(header.contains("eXpEcT")); assertThat(header.get("random"), is("value")); assertThat(header.get("Random"), is("value")); assertThat(header.get("RANDOM"), is("value")); assertThat(header.get("rAnDoM"), is("value")); assertThat(header.get("RaNdOm"), is("value")); + assertTrue(header.contains("random")); + assertTrue(header.contains("Random")); + assertTrue(header.contains("RANDOM")); + assertTrue(header.contains("rAnDoM")); + assertTrue(header.contains("RaNdOm")); assertThat(header.get("Accept-Charset"), is("UTF-8")); assertThat(header.get("accept-charset"), is("UTF-8")); assertThat(header.get(HttpHeader.ACCEPT_CHARSET), is("UTF-8")); + assertTrue(header.contains("Accept-Charset")); + assertTrue(header.contains("accept-charset")); assertThat(header.getValuesList("Accept-Charset"), contains("UTF-8", "UTF-16")); assertThat(header.getValuesList("accept-charset"), contains("UTF-8", "UTF-16")); @@ -373,9 +385,19 @@ public void testCaseInsensitive() assertThat(header.get("Foo-Bar"), is("one")); assertThat(header.getValuesList("foo-bar"), contains("one", "two")); assertThat(header.getValuesList("Foo-Bar"), contains("one", "two")); + assertTrue(header.contains("foo-bar")); + assertTrue(header.contains("Foo-Bar")); // We know the order of the set is deterministic - assertThat(header.getFieldNamesCollection(), contains("expect", "RaNdOm", "Accept-Charset", "foo-bar")); + Set names = header.getFieldNamesCollection(); + assertThat(names, contains("expect", "RaNdOm", "Accept-Charset", "foo-bar")); + assertTrue(names.contains("expect")); + assertTrue(names.contains("Expect")); + assertTrue(names.contains("random")); + assertTrue(names.contains("accept-charset")); + assertTrue(names.contains("Accept-Charset")); + assertTrue(names.contains("foo-bar")); + assertTrue(names.contains("Foo-Bar")); } @ParameterizedTest @@ -1492,16 +1514,27 @@ public void testRandomAccess() assertThat(header.get("EXPECT"), is("100")); assertThat(header.get("eXpEcT"), is("100")); assertThat(header.get(HttpHeader.EXPECT), is("100")); + assertTrue(header.contains("expect")); + assertTrue(header.contains("Expect")); + assertTrue(header.contains("EXPECT")); + assertTrue(header.contains("eXpEcT")); assertThat(header.get("random"), is("value")); assertThat(header.get("Random"), is("value")); assertThat(header.get("RANDOM"), is("value")); assertThat(header.get("rAnDoM"), is("value")); assertThat(header.get("RaNdOm"), is("value")); + assertTrue(header.contains("random")); + assertTrue(header.contains("Random")); + assertTrue(header.contains("RANDOM")); + assertTrue(header.contains("rAnDoM")); + assertTrue(header.contains("RaNdOm")); assertThat(header.get("Accept-Charset"), is("UTF-8")); assertThat(header.get("accept-charset"), is("UTF-8")); assertThat(header.get(HttpHeader.ACCEPT_CHARSET), is("UTF-8")); + assertTrue(header.contains("Accept-Charset")); + assertTrue(header.contains("accept-charset")); assertThat(header.getValuesList("Accept-Charset"), contains("UTF-8", "UTF-16")); assertThat(header.getValuesList("accept-charset"), contains("UTF-8", "UTF-16")); @@ -1511,8 +1544,18 @@ public void testRandomAccess() assertThat(header.get("Foo-Bar"), is("one")); assertThat(header.getValuesList("foo-bar"), contains("one", "two")); assertThat(header.getValuesList("Foo-Bar"), contains("one", "two")); + assertTrue(header.contains("foo-bar")); + assertTrue(header.contains("Foo-Bar")); // We know the order of the set is deterministic - assertThat(header.getFieldNamesCollection(), contains("Expect", "RaNdOm", "Accept-Charset", "foo-bar")); + Set names = header.getFieldNamesCollection(); + assertThat(names, contains("Expect", "RaNdOm", "Accept-Charset", "foo-bar")); + assertTrue(names.contains("expect")); + assertTrue(names.contains("Expect")); + assertTrue(names.contains("random")); + assertTrue(names.contains("accept-charset")); + assertTrue(names.contains("Accept-Charset")); + assertTrue(names.contains("foo-bar")); + assertTrue(names.contains("Foo-Bar")); } }