From 601710c9dc1ff376c779dc83d7aca09077431cc2 Mon Sep 17 00:00:00 2001 From: Greg Wilkins Date: Wed, 26 Aug 2020 23:55:36 +0200 Subject: [PATCH] Issue #5171 Simplify GzipHandler user-agent handling (#5196) * Issue #5171 Simplify GzipHandler user-agent handling + Remove User-Agent handling from GzipHandler + Allow Vary header to be set + Create rewrite MsieRule to remove Accept-Encoding from IE<=6 Signed-off-by: Greg Wilkins * + Full implementation of HttpFields ensure + use for Vary field * + fixed checkstyle * + fixed test for merged header * + fixed javadoc * Issue #5171 Simplify GzipHandler user-agent handling + improved comments Signed-off-by: Greg Wilkins * rename and testing after review --- .../org/eclipse/jetty/http/HttpFields.java | 153 ++++++++++ .../eclipse/jetty/http/HttpFieldsTest.java | 140 ++++++++- .../src/main/config/modules/msie.mod | 13 + .../config/modules/rewrite/rewrite-msie.xml | 10 + .../config/modules/rewrite/rewrite-rules.xml | 16 +- .../jetty/rewrite/handler/MsieRule.java | 119 ++++++++ .../jetty/rewrite/handler/MsieSslRule.java | 2 + .../jetty/rewrite/handler/MsieRuleTest.java | 277 ++++++++++++++++++ .../src/main/config/etc/jetty-gzip.xml | 7 - jetty-server/src/main/config/modules/gzip.mod | 9 +- .../server/handler/gzip/GzipHandler.java | 125 ++------ .../gzip/GzipHttpOutputInterceptor.java | 31 +- .../servlet/GzipHandlerBreakEvenSizeTest.java | 1 - .../jetty/servlet/GzipHandlerTest.java | 5 +- .../servlets/GzipDefaultServletTest.java | 246 +--------------- .../jetty/servlets/GzipHandlerTest.java | 1 - 16 files changed, 758 insertions(+), 397 deletions(-) create mode 100644 jetty-rewrite/src/main/config/modules/msie.mod create mode 100644 jetty-rewrite/src/main/config/modules/rewrite/rewrite-msie.xml create mode 100644 jetty-rewrite/src/main/java/org/eclipse/jetty/rewrite/handler/MsieRule.java create mode 100644 jetty-rewrite/src/test/java/org/eclipse/jetty/rewrite/handler/MsieRuleTest.java diff --git a/jetty-http/src/main/java/org/eclipse/jetty/http/HttpFields.java b/jetty-http/src/main/java/org/eclipse/jetty/http/HttpFields.java index 673c8d750204..285953681a22 100644 --- a/jetty-http/src/main/java/org/eclipse/jetty/http/HttpFields.java +++ b/jetty-http/src/main/java/org/eclipse/jetty/http/HttpFields.java @@ -738,6 +738,159 @@ public Mutable clear() return this; } + /** Ensure that specific HttpField exists when the field may not exist or may + * exist and be multi valued. Multiple existing fields are merged into a + * single field. + * @param field The header to ensure is contained. The field is used + * directly if possible so {@link PreEncodedHttpField}s can be + * passed. If the value needs to be merged with existing values, + * then a new field is created. + */ + public void ensureField(HttpField field) + { + // Is the field value multi valued? + if (field.getValue().indexOf(',') < 0) + { + // Call Single valued computeEnsure with either String header name or enum HttpHeader + if (field.getHeader() != null) + computeField(field.getHeader(), (h, l) -> computeEnsure(field, l)); + else + computeField(field.getName(), (h, l) -> computeEnsure(field, l)); + } + else + { + // call multi valued computeEnsure with either String header name or enum HttpHeader + if (field.getHeader() != null) + computeField(field.getHeader(), (h, l) -> computeEnsure(field, field.getValues(), l)); + else + computeField(field.getName(), (h, l) -> computeEnsure(field, field.getValues(), l)); + } + } + + /** + * Compute ensure field with a single value + * @param ensure The field to ensure exists + * @param fields The list of existing fields with the same header + */ + private static HttpField computeEnsure(HttpField ensure, List fields) + { + // If no existing fields return the ensure field + if (fields == null || fields.isEmpty()) + return ensure; + + String ensureValue = ensure.getValue(); + + // Handle a single existing field + if (fields.size() == 1) + { + // If the existing field contains the ensure value, return it, else append values. + HttpField f = fields.get(0); + return f.contains(ensureValue) + ? f + : new HttpField(ensure.getHeader(), ensure.getName(), f.getValue() + ", " + ensureValue); + } + + // Handle multiple existing fields + StringBuilder v = new StringBuilder(); + for (HttpField f : fields) + { + // Always append multiple fields into a single field value + if (v.length() > 0) + v.append(", "); + v.append(f.getValue()); + + // check if the ensure value is already contained + if (ensureValue != null && f.contains(ensureValue)) + ensureValue = null; + } + + // If the ensure value was not contained append it + if (ensureValue != null) + v.append(", ").append(ensureValue); + + return new HttpField(ensure.getHeader(), ensure.getName(), v.toString()); + } + + /** + * Compute ensure field with a multiple values + * @param ensure The field to ensure exists + * @param values The QuotedCSV parsed field values. + * @param fields The list of existing fields with the same header + */ + private static HttpField computeEnsure(HttpField ensure, String[] values, List fields) + { + // If no existing fields return the ensure field + if (fields == null || fields.isEmpty()) + return ensure; + + // Handle a single existing field + if (fields.size() == 1) + { + HttpField f = fields.get(0); + // check which ensured values are already contained + int ensured = values.length; + for (int i = 0; i < values.length; i++) + { + if (f.contains(values[i])) + { + ensured--; + values[i] = null; + } + } + + // if all ensured values contained return the existing field + if (ensured == 0) + return f; + // else if no ensured values contained append the entire ensured valued + if (ensured == values.length) + return new HttpField(ensure.getHeader(), ensure.getName(), + f.getValue() + ", " + ensure.getValue()); + // else append just the ensured values that are not contained + StringBuilder v = new StringBuilder(f.getValue()); + for (String value : values) + { + if (value != null) + v.append(", ").append(value); + } + return new HttpField(ensure.getHeader(), ensure.getName(), v.toString()); + } + + // Handle a multiple existing field + StringBuilder v = new StringBuilder(); + int ensured = values.length; + for (HttpField f : fields) + { + // Always append multiple fields into a single field value + if (v.length() > 0) + v.append(", "); + v.append(f.getValue()); + + // null out ensured values that are included + for (int i = 0; i < values.length; i++) + { + if (values[i] != null && f.contains(values[i])) + { + ensured--; + values[i] = null; + } + } + } + + // if no ensured values exist append them all + if (ensured == values.length) + v.append(", ").append(ensure.getValue()); + // else if some ensured values are missing, append them + else if (ensured > 0) + { + for (String value : values) + if (value != null) + v.append(", ").append(value); + } + + // return a merged header with missing ensured values added + return new HttpField(ensure.getHeader(), ensure.getName(), v.toString()); + } + @Override public boolean equals(Object o) { diff --git a/jetty-http/src/test/java/org/eclipse/jetty/http/HttpFieldsTest.java b/jetty-http/src/test/java/org/eclipse/jetty/http/HttpFieldsTest.java index e70a9322a7ba..4a77a4cdf496 100644 --- a/jetty-http/src/test/java/org/eclipse/jetty/http/HttpFieldsTest.java +++ b/jetty-http/src/test/java/org/eclipse/jetty/http/HttpFieldsTest.java @@ -41,6 +41,7 @@ import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.contains; import static org.hamcrest.Matchers.containsString; +import static org.hamcrest.Matchers.instanceOf; import static org.hamcrest.Matchers.is; import static org.hamcrest.Matchers.nullValue; import static org.junit.jupiter.api.Assertions.assertEquals; @@ -899,7 +900,6 @@ public void testComputeField() { HttpFields.Mutable fields = HttpFields.build(); assertThat(fields.size(), is(0)); - fields.computeField("Test", (n, f) -> null); assertThat(fields.size(), is(0)); @@ -922,4 +922,140 @@ public void testComputeField() fields.computeField("TEST", (n, f) -> null); assertThat(fields.stream().map(HttpField::toString).collect(Collectors.toList()), contains("Before: value", "After: value")); } -} + + @Test + public void testEnsureSingleValue() + { + HttpFields.Mutable fields = HttpFields.build(); + + // 0 existing case + assertThat(fields.size(), is(0)); + fields.ensureField(new PreEncodedHttpField(HttpHeader.VARY, "one")); + assertThat(fields.stream().map(HttpField::toString).collect(Collectors.toList()), contains("Vary: one")); + assertThat(fields.getField(0), instanceOf(PreEncodedHttpField.class)); + + // 1 existing cases + fields.ensureField(new HttpField(HttpHeader.VARY, "one")); + assertThat(fields.stream().map(HttpField::toString).collect(Collectors.toList()), contains("Vary: one")); +; + fields.ensureField(new HttpField(HttpHeader.VARY, "two")); + assertThat(fields.stream().map(HttpField::toString).collect(Collectors.toList()), contains("Vary: one, two")); + + // many existing cases + fields.put(new HttpField(HttpHeader.VARY, "one")); + fields.add(new HttpField(HttpHeader.VARY, "two")); + fields.ensureField(new HttpField(HttpHeader.VARY, "one")); + assertThat(fields.stream().map(HttpField::toString).collect(Collectors.toList()), contains("Vary: one, two")); + + fields.put(new HttpField(HttpHeader.VARY, "one")); + fields.add(new HttpField(HttpHeader.VARY, "two")); + fields.ensureField(new HttpField(HttpHeader.VARY, "three")); + assertThat(fields.stream().map(HttpField::toString).collect(Collectors.toList()), contains("Vary: one, two, three")); + } + + @Test + public void testEnsureMultiValue() + { + HttpFields.Mutable fields = HttpFields.build(); + + // zero existing case + assertThat(fields.size(), is(0)); + fields.ensureField(new PreEncodedHttpField(HttpHeader.VARY, "one, two")); + assertThat(fields.stream().map(HttpField::toString).collect(Collectors.toList()), contains("Vary: one, two")); + assertThat(fields.getField(0), instanceOf(PreEncodedHttpField.class)); + + // one existing cases + fields.ensureField(new HttpField(HttpHeader.VARY, "two, one")); + assertThat(fields.stream().map(HttpField::toString).collect(Collectors.toList()), contains("Vary: one, two")); + + fields.ensureField(new HttpField(HttpHeader.VARY, "three, one")); + assertThat(fields.stream().map(HttpField::toString).collect(Collectors.toList()), contains("Vary: one, two, three")); + + fields.ensureField(new HttpField(HttpHeader.VARY, "four, five")); + assertThat(fields.stream().map(HttpField::toString).collect(Collectors.toList()), contains("Vary: one, two, three, four, five")); + + // many existing cases + fields.put(new HttpField(HttpHeader.VARY, "one")); + fields.add(new HttpField(HttpHeader.VARY, "two")); + fields.ensureField(new HttpField(HttpHeader.VARY, "two, one")); + assertThat(fields.stream().map(HttpField::toString).collect(Collectors.toList()), contains("Vary: one, two")); + + fields.put(new HttpField(HttpHeader.VARY, "one")); + fields.add(new HttpField(HttpHeader.VARY, "two")); + fields.ensureField(new HttpField(HttpHeader.VARY, "three, two")); + assertThat(fields.stream().map(HttpField::toString).collect(Collectors.toList()), contains("Vary: one, two, three")); + + fields.put(new HttpField(HttpHeader.VARY, "one")); + fields.add(new HttpField(HttpHeader.VARY, "two")); + fields.ensureField(new HttpField(HttpHeader.VARY, "three, four")); + assertThat(fields.stream().map(HttpField::toString).collect(Collectors.toList()), contains("Vary: one, two, three, four")); + } + + @Test + public void testEnsureStringSingleValue() + { + HttpFields.Mutable fields = HttpFields.build(); + + // 0 existing case + assertThat(fields.size(), is(0)); + fields.ensureField(new PreEncodedHttpField("Test", "one")); + assertThat(fields.stream().map(HttpField::toString).collect(Collectors.toList()), contains("Test: one")); + assertThat(fields.getField(0), instanceOf(PreEncodedHttpField.class)); + + // 1 existing cases + fields.ensureField(new HttpField("Test", "one")); + assertThat(fields.stream().map(HttpField::toString).collect(Collectors.toList()), contains("Test: one")); + ; + fields.ensureField(new HttpField("Test", "two")); + assertThat(fields.stream().map(HttpField::toString).collect(Collectors.toList()), contains("Test: one, two")); + + // many existing cases + fields.put(new HttpField("Test", "one")); + fields.add(new HttpField("Test", "two")); + fields.ensureField(new HttpField("Test", "one")); + assertThat(fields.stream().map(HttpField::toString).collect(Collectors.toList()), contains("Test: one, two")); + + fields.put(new HttpField("Test", "one")); + fields.add(new HttpField("Test", "two")); + fields.ensureField(new HttpField("Test", "three")); + assertThat(fields.stream().map(HttpField::toString).collect(Collectors.toList()), contains("Test: one, two, three")); + } + + @Test + public void testEnsureStringMultiValue() + { + HttpFields.Mutable fields = HttpFields.build(); + + // zero existing case + assertThat(fields.size(), is(0)); + fields.ensureField(new PreEncodedHttpField("Test", "one, two")); + assertThat(fields.stream().map(HttpField::toString).collect(Collectors.toList()), contains("Test: one, two")); + assertThat(fields.getField(0), instanceOf(PreEncodedHttpField.class)); + + // one existing cases + fields.ensureField(new HttpField("Test", "two, one")); + assertThat(fields.stream().map(HttpField::toString).collect(Collectors.toList()), contains("Test: one, two")); + + fields.ensureField(new HttpField("Test", "three, one")); + assertThat(fields.stream().map(HttpField::toString).collect(Collectors.toList()), contains("Test: one, two, three")); + + fields.ensureField(new HttpField("Test", "four, five")); + assertThat(fields.stream().map(HttpField::toString).collect(Collectors.toList()), contains("Test: one, two, three, four, five")); + + // many existing cases + fields.put(new HttpField("Test", "one")); + fields.add(new HttpField("Test", "two")); + fields.ensureField(new HttpField("Test", "two, one")); + assertThat(fields.stream().map(HttpField::toString).collect(Collectors.toList()), contains("Test: one, two")); + + fields.put(new HttpField("Test", "one")); + fields.add(new HttpField("Test", "two")); + fields.ensureField(new HttpField("Test", "three, two")); + assertThat(fields.stream().map(HttpField::toString).collect(Collectors.toList()), contains("Test: one, two, three")); + + fields.put(new HttpField("Test", "one")); + fields.add(new HttpField("Test", "two")); + fields.ensureField(new HttpField("Test", "three, four")); + assertThat(fields.stream().map(HttpField::toString).collect(Collectors.toList()), contains("Test: one, two, three, four")); + } +} \ No newline at end of file diff --git a/jetty-rewrite/src/main/config/modules/msie.mod b/jetty-rewrite/src/main/config/modules/msie.mod new file mode 100644 index 000000000000..e5944100ff78 --- /dev/null +++ b/jetty-rewrite/src/main/config/modules/msie.mod @@ -0,0 +1,13 @@ +# DO NOT EDIT - See: https://www.eclipse.org/jetty/documentation/current/startup-modules.html + +[description] +Enables the MSIE rewrite rule for MSIE 5 and 6 known bugs. + +[depend] +rewrite + +[files] +basehome:modules/rewrite/rewrite-msie.xml|etc/rewrite-msie.xml + +[xml] +etc/rewrite-msie.xml diff --git a/jetty-rewrite/src/main/config/modules/rewrite/rewrite-msie.xml b/jetty-rewrite/src/main/config/modules/rewrite/rewrite-msie.xml new file mode 100644 index 000000000000..d4b5b3edccb7 --- /dev/null +++ b/jetty-rewrite/src/main/config/modules/rewrite/rewrite-msie.xml @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/jetty-rewrite/src/main/config/modules/rewrite/rewrite-rules.xml b/jetty-rewrite/src/main/config/modules/rewrite/rewrite-rules.xml index 41be9bb71801..b12af8f4da34 100644 --- a/jetty-rewrite/src/main/config/modules/rewrite/rewrite-rules.xml +++ b/jetty-rewrite/src/main/config/modules/rewrite/rewrite-rules.xml @@ -2,14 +2,6 @@ - - + + diff --git a/jetty-rewrite/src/main/java/org/eclipse/jetty/rewrite/handler/MsieRule.java b/jetty-rewrite/src/main/java/org/eclipse/jetty/rewrite/handler/MsieRule.java new file mode 100644 index 000000000000..3b0e0ce02a98 --- /dev/null +++ b/jetty-rewrite/src/main/java/org/eclipse/jetty/rewrite/handler/MsieRule.java @@ -0,0 +1,119 @@ +// +// ======================================================================== +// Copyright (c) 1995-2020 Mort Bay Consulting Pty Ltd and others. +// +// This program and the accompanying materials are made available under +// the terms of the Eclipse Public License 2.0 which is available at +// https://www.eclipse.org/legal/epl-2.0 +// +// This Source Code may also be made available under the following +// Secondary Licenses when the conditions for such availability set +// forth in the Eclipse Public License, v. 2.0 are satisfied: +// the Apache License v2.0 which is available at +// https://www.apache.org/licenses/LICENSE-2.0 +// +// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 +// ======================================================================== +// + +package org.eclipse.jetty.rewrite.handler; + +import java.io.IOException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.eclipse.jetty.http.HttpField; +import org.eclipse.jetty.http.HttpFields; +import org.eclipse.jetty.http.HttpHeader; +import org.eclipse.jetty.http.HttpHeaderValue; +import org.eclipse.jetty.http.PreEncodedHttpField; +import org.eclipse.jetty.server.Request; +import org.eclipse.jetty.util.ArrayTernaryTrie; +import org.eclipse.jetty.util.Trie; + +/** + * Special handling for MSIE (Microsoft Internet Explorer). + *
    + *
  • Disable keep alive for SSL from IE5 or IE6 on Windows 2000
  • + *
  • Disable encodings for IE<=6
  • + *
+ */ +public class MsieRule extends Rule +{ + private static final int IEv5 = '5'; + private static final int IEv6 = '6'; + private static final Trie __IE6_BadOS = new ArrayTernaryTrie<>(); + private static final HttpField CONNECTION_CLOSE = new HttpField(HttpHeader.CONNECTION, HttpHeaderValue.CLOSE); + private static final HttpField VARY_USER_AGENT = new PreEncodedHttpField(HttpHeader.VARY, HttpHeader.USER_AGENT.asString()); + + static + { + __IE6_BadOS.put("NT 5.01", Boolean.TRUE); + __IE6_BadOS.put("NT 5.0", Boolean.TRUE); + __IE6_BadOS.put("NT 4.0", Boolean.TRUE); + __IE6_BadOS.put("98", Boolean.TRUE); + __IE6_BadOS.put("98; Win 9x 4.90", Boolean.TRUE); + __IE6_BadOS.put("95", Boolean.TRUE); + __IE6_BadOS.put("CE", Boolean.TRUE); + } + + public MsieRule() + { + _handling = false; + _terminating = false; + } + + @Override + public String matchAndApply(String target, HttpServletRequest request, HttpServletResponse response) throws IOException + { + Request baseRequest = Request.getBaseRequest(request); + if (baseRequest == null) + return null; + + HttpFields.Mutable reqFields = HttpFields.build(baseRequest.getHttpFields()); + HttpFields.Mutable resFields = baseRequest.getResponse().getHttpFields(); + String userAgent = reqFields.get(HttpHeader.USER_AGENT); + boolean acceptEncodings = reqFields.contains(HttpHeader.ACCEPT_ENCODING); + if (acceptEncodings) + resFields.ensureField(VARY_USER_AGENT); + + int msie = userAgent.indexOf("MSIE"); + if (msie >= 0) + { + int version = (userAgent.length() - msie > 5) ? userAgent.charAt(msie + 5) : IEv5; + + if (version <= IEv6) + { + // Don't gzip responses for IE<=6 + if (acceptEncodings) + reqFields.remove(HttpHeader.ACCEPT_ENCODING); + + // IE<=6 can't do persistent SSL + if (request.isSecure()) + { + boolean badOs = false; + if (version == IEv6) + { + int windows = userAgent.indexOf("Windows", msie + 5); + if (windows > 0) + { + int end = userAgent.indexOf(')', windows + 8); + badOs = (end < 0 || __IE6_BadOS.get(userAgent, windows + 8, end - windows - 8) != null); + } + } + + if (version <= IEv5 || badOs) + { + reqFields.remove(HttpHeader.KEEP_ALIVE); + reqFields.ensureField(CONNECTION_CLOSE); + resFields.ensureField(CONNECTION_CLOSE); + response.setHeader(HttpHeader.CONNECTION.asString(), HttpHeaderValue.CLOSE.asString()); + } + } + baseRequest.setHttpFields(reqFields); + return target; + } + } + return null; + } +} diff --git a/jetty-rewrite/src/main/java/org/eclipse/jetty/rewrite/handler/MsieSslRule.java b/jetty-rewrite/src/main/java/org/eclipse/jetty/rewrite/handler/MsieSslRule.java index f88e5640b461..fda3c1d3238d 100644 --- a/jetty-rewrite/src/main/java/org/eclipse/jetty/rewrite/handler/MsieSslRule.java +++ b/jetty-rewrite/src/main/java/org/eclipse/jetty/rewrite/handler/MsieSslRule.java @@ -30,7 +30,9 @@ /** * MSIE (Microsoft Internet Explorer) SSL Rule. * Disable keep alive for SSL from IE5 or IE6 on Windows 2000. + * @deprecated use MsieRule */ +@Deprecated public class MsieSslRule extends Rule { private static final int IEv5 = '5'; diff --git a/jetty-rewrite/src/test/java/org/eclipse/jetty/rewrite/handler/MsieRuleTest.java b/jetty-rewrite/src/test/java/org/eclipse/jetty/rewrite/handler/MsieRuleTest.java new file mode 100644 index 000000000000..6cbfbb242fc3 --- /dev/null +++ b/jetty-rewrite/src/test/java/org/eclipse/jetty/rewrite/handler/MsieRuleTest.java @@ -0,0 +1,277 @@ +// +// ======================================================================== +// Copyright (c) 1995-2020 Mort Bay Consulting Pty Ltd and others. +// +// This program and the accompanying materials are made available under +// the terms of the Eclipse Public License 2.0 which is available at +// https://www.eclipse.org/legal/epl-2.0 +// +// This Source Code may also be made available under the following +// Secondary Licenses when the conditions for such availability set +// forth in the Eclipse Public License, v. 2.0 are satisfied: +// the Apache License v2.0 which is available at +// https://www.apache.org/licenses/LICENSE-2.0 +// +// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 +// ======================================================================== +// + +package org.eclipse.jetty.rewrite.handler; + +import java.util.stream.Collectors; + +import org.eclipse.jetty.http.HttpField; +import org.eclipse.jetty.http.HttpFields; +import org.eclipse.jetty.http.HttpHeader; +import org.eclipse.jetty.http.HttpHeaderValue; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.contains; +import static org.hamcrest.Matchers.is; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNull; + +public class MsieRuleTest extends AbstractRuleTestCase +{ + private MsieRule _rule; + + @BeforeEach + public void init() throws Exception + { + // enable SSL + start(true); + _rule = new MsieRule(); + } + + @Test + public void testWin2kSP1WithIE5() throws Exception + { + HttpFields.Mutable fields = HttpFields.build(_request.getHttpFields()); + fields.add("User-Agent", "Mozilla/4.0 (compatible; MSIE 5.0; Windows NT 5.01)"); + _request.setHttpFields(fields); + + String result = _rule.matchAndApply(_request.getRequestURI(), _request, _response); + + assertEquals(_request.getRequestURI(), result); + assertEquals(HttpHeaderValue.CLOSE.asString(), _response.getHeader(HttpHeader.CONNECTION.asString())); + + fields.add("User-Agent", "Mozilla/4.0 (compatible; MSIE 5.01; Windows NT 5.01)"); + _request.setHttpFields(fields); + result = _rule.matchAndApply(_request.getRequestURI(), _request, _response); + assertEquals(_request.getRequestURI(), result); + assertEquals(HttpHeaderValue.CLOSE.asString(), _response.getHeader(HttpHeader.CONNECTION.asString())); + + fields.add("User-Agent", "Mozilla/4.0 (compatible; MSIE 5.5; Windows NT 5.01)"); + _request.setHttpFields(fields); + result = _rule.matchAndApply(_request.getRequestURI(), _request, _response); + assertEquals(_request.getRequestURI(), result); + assertEquals(HttpHeaderValue.CLOSE.asString(), _response.getHeader(HttpHeader.CONNECTION.asString())); + } + + @Test + public void testWin2kSP1WithIE6() throws Exception + { + _request.setHttpFields(HttpFields.build(_request.getHttpFields()) + .add("User-Agent", "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.01)")); + + String result = _rule.matchAndApply(_request.getRequestURI(), _request, _response); + + assertEquals(_request.getRequestURI(), result); + assertEquals(HttpHeaderValue.CLOSE.asString(), _response.getHeader(HttpHeader.CONNECTION.asString())); + } + + @Test + public void testWin2kSP1WithIE7() throws Exception + { + _request.setHttpFields(HttpFields.build(_request.getHttpFields()) + .add("User-Agent", "Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 5.01)")); + + String result = _rule.matchAndApply(_request.getRequestURI(), _request, _response); + + assertNull(result); + assertNull(_response.getHeader(HttpHeader.CONNECTION.asString())); + } + + @Test + public void testWin2kWithIE5() throws Exception + { + HttpFields.Mutable fields = HttpFields.build(_request.getHttpFields()); + fields.add("User-Agent", "Mozilla/4.0 (compatible; MSIE 5.0; Windows NT 5.0)"); + _request.setHttpFields(fields); + + String result = _rule.matchAndApply(_request.getRequestURI(), _request, _response); + + assertEquals(_request.getRequestURI(), result); + assertEquals(HttpHeaderValue.CLOSE.asString(), _response.getHeader(HttpHeader.CONNECTION.asString())); + + fields.add("User-Agent", "Mozilla/4.0 (compatible; MSIE 5.01; Windows NT 5.0)"); + _request.setHttpFields(fields); + result = _rule.matchAndApply(_request.getRequestURI(), _request, _response); + assertEquals(_request.getRequestURI(), result); + assertEquals(HttpHeaderValue.CLOSE.asString(), _response.getHeader(HttpHeader.CONNECTION.asString())); + + fields.add("User-Agent", "Mozilla/4.0 (compatible; MSIE 5.5; Windows NT 5.0)"); + _request.setHttpFields(fields); + result = _rule.matchAndApply(_request.getRequestURI(), _request, _response); + assertEquals(_request.getRequestURI(), result); + assertEquals(HttpHeaderValue.CLOSE.asString(), _response.getHeader(HttpHeader.CONNECTION.asString())); + } + + @Test + public void testWin2kWithIE6() throws Exception + { + _request.setHttpFields(HttpFields.build(_request.getHttpFields()) + .add("User-Agent", "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.0)") + .asImmutable()); + + String result = _rule.matchAndApply(_request.getRequestURI(), _request, _response); + + assertEquals(_request.getRequestURI(), result); + assertEquals(HttpHeaderValue.CLOSE.asString(), _response.getHeader(HttpHeader.CONNECTION.asString())); + } + + @Test + public void testWin2kWithIE7() throws Exception + { + _request.setHttpFields(HttpFields.build(_request.getHttpFields()) + .add("User-Agent", "Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 5.0)")); + + String result = _rule.matchAndApply(_request.getRequestURI(), _request, _response); + + assertNull(result); + assertNull(_response.getHeader(HttpHeader.CONNECTION.asString())); + } + + @Test + public void testWinVistaWithIE5() throws Exception + { + HttpFields.Mutable fields = HttpFields.build(_request.getHttpFields()); + fields.add("User-Agent", "Mozilla/4.0 (compatible; MSIE 5.0; Windows NT 6.0)"); + _request.setHttpFields(fields); + + String result = _rule.matchAndApply(_request.getRequestURI(), _request, _response); + + assertEquals(_request.getRequestURI(), result); + assertEquals(HttpHeaderValue.CLOSE.asString(), _response.getHeader(HttpHeader.CONNECTION.asString())); + + fields.add("User-Agent", "Mozilla/4.0 (compatible; MSIE 5.01; Windows NT 6.0)"); + _request.setHttpFields(fields); + result = _rule.matchAndApply(_request.getRequestURI(), _request, _response); + assertEquals(_request.getRequestURI(), result); + assertEquals(HttpHeaderValue.CLOSE.asString(), _response.getHeader(HttpHeader.CONNECTION.asString())); + + fields.add("User-Agent", "Mozilla/4.0 (compatible; MSIE 5.5; Windows NT 6.0)"); + _request.setHttpFields(fields); + result = _rule.matchAndApply(_request.getRequestURI(), _request, _response); + assertEquals(_request.getRequestURI(), result); + assertEquals(HttpHeaderValue.CLOSE.asString(), _response.getHeader(HttpHeader.CONNECTION.asString())); + } + + @Test + public void testWinVistaWithIE6() throws Exception + { + _request.setHttpFields(HttpFields.build(_request.getHttpFields()) + .add("User-Agent", "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 6.0)")); + + String result = _rule.matchAndApply(_request.getRequestURI(), _request, _response); + + assertEquals(_request.getRequestURI(), result); + assertThat(_request.getHttpFields().stream().map(HttpField::toString).collect(Collectors.toList()), + contains("Cookie: set=already", "User-Agent: Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 6.0)")); + } + + @Test + public void testWinVistaWithIE7() throws Exception + { + _request.setHttpFields(HttpFields.build(_request.getHttpFields()) + .add("User-Agent", "Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 6.0)")); + + String result = _rule.matchAndApply(_request.getRequestURI(), _request, _response); + + assertNull(result); + assertNull(_response.getHeader(HttpHeader.CONNECTION.asString())); + } + + @Test + public void testWinXpWithIE5() throws Exception + { + HttpFields.Mutable fields = HttpFields.build(_request.getHttpFields()); + fields.add("User-Agent", "Mozilla/4.0 (compatible; MSIE 5.0; Windows NT 5.1)"); + _request.setHttpFields(fields); + + String result = _rule.matchAndApply(_request.getRequestURI(), _request, _response); + + assertEquals(_request.getRequestURI(), result); + assertEquals(HttpHeaderValue.CLOSE.asString(), _response.getHeader(HttpHeader.CONNECTION.asString())); + + fields.add("User-Agent", "Mozilla/4.0 (compatible; MSIE 5.01; Windows NT 5.1)"); + _request.setHttpFields(fields); + result = _rule.matchAndApply(_request.getRequestURI(), _request, _response); + assertEquals(_request.getRequestURI(), result); + assertEquals(HttpHeaderValue.CLOSE.asString(), _response.getHeader(HttpHeader.CONNECTION.asString())); + + fields.add("User-Agent", "Mozilla/4.0 (compatible; MSIE 5.5; Windows NT 5.1)"); + _request.setHttpFields(fields); + result = _rule.matchAndApply(_request.getRequestURI(), _request, _response); + assertEquals(_request.getRequestURI(), result); + assertEquals(HttpHeaderValue.CLOSE.asString(), _response.getHeader(HttpHeader.CONNECTION.asString())); + } + + @Test + public void testWinXpWithIE6() throws Exception + { + _request.setHttpFields(HttpFields.build(_request.getHttpFields()) + .add("User-Agent", "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1)") + .add(HttpHeader.ACCEPT_ENCODING, "gzip")); + + _response.addHeader("Vary", "Something"); + + String result = _rule.matchAndApply(_request.getRequestURI(), _request, _response); + + assertEquals(_request.getRequestURI(), result); + assertThat(_request.getHttpFields().stream().map(HttpField::toString).collect(Collectors.toList()), + contains("Cookie: set=already", "User-Agent: Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1)")); + assertThat(_response.getHeader("Vary"), is("Something, User-Agent")); + } + + @Test + public void testWinXpWithIE7() throws Exception + { + _request.setHttpFields(HttpFields.build(_request.getHttpFields()) + .add("User-Agent", "Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 5.1)") + .add(HttpHeader.ACCEPT_ENCODING, "gzip")); + _response.addHeader("Vary", "Something"); + + String result = _rule.matchAndApply(_request.getRequestURI(), _request, _response); + + assertNull(result); + assertThat(_request.getHttpFields().stream().map(HttpField::toString).collect(Collectors.toList()), + contains( + "Cookie: set=already", + "User-Agent: Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 5.1)", + "Accept-Encoding: gzip")); + + assertThat(_response.getHeader("Vary"), is("Something, User-Agent")); + } + + @Test + public void testWithoutSsl() throws Exception + { + // disable SSL + super.stop(); + super.start(false); + + _request.setHttpFields(HttpFields.build(_request.getHttpFields()) + .add("User-Agent", "Mozilla/4.0 (compatible; MSIE 5.0; Windows NT 5.0)") + .add(HttpHeader.ACCEPT_ENCODING, "deflate") + ); + + String result = _rule.matchAndApply(_request.getRequestURI(), _request, _response); + + assertEquals(_request.getRequestURI(), result); + assertThat(_request.getHttpFields().stream().map(HttpField::toString).collect(Collectors.toList()), + contains("Cookie: set=already", "User-Agent: Mozilla/4.0 (compatible; MSIE 5.0; Windows NT 5.0)")); + } +} diff --git a/jetty-server/src/main/config/etc/jetty-gzip.xml b/jetty-server/src/main/config/etc/jetty-gzip.xml index 2ebd183623b3..24ccbbb4b8c8 100644 --- a/jetty-server/src/main/config/etc/jetty-gzip.xml +++ b/jetty-server/src/main/config/etc/jetty-gzip.xml @@ -18,13 +18,6 @@ - - - - - - - diff --git a/jetty-server/src/main/config/modules/gzip.mod b/jetty-server/src/main/config/modules/gzip.mod index 261615eb0c79..50fa8302865d 100644 --- a/jetty-server/src/main/config/modules/gzip.mod +++ b/jetty-server/src/main/config/modules/gzip.mod @@ -1,8 +1,8 @@ # DO NOT EDIT - See: https://www.eclipse.org/jetty/documentation/current/startup-modules.html [description] -Enable GzipHandler for dynamic gzip compression -for the entire server. +Enable GzipHandler for dynamic gzip compression for the entire server. +If MSIE prior to version 7 are to be handled, also enable the msie module. [tags] handler @@ -23,9 +23,6 @@ etc/jetty-gzip.xml ## Gzip compression level (-1 for default) # jetty.gzip.compressionLevel=-1 -## User agents for which gzip is disabled -# jetty.gzip.excludedUserAgent=.*MSIE.6\.0.* - ## Inflate request buffer size, or 0 for no request inflation # jetty.gzip.inflateBufferSize=0 @@ -33,7 +30,7 @@ etc/jetty-gzip.xml # jetty.gzip.deflaterPoolCapacity=-1 ## Comma separated list of included methods -# jetty.gzip.includedMethodList=GET +# jetty.gzip.includedMethodList=GET,POST ## Comma separated list of excluded methods # jetty.gzip.excludedMethodList= diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/handler/gzip/GzipHandler.java b/jetty-server/src/main/java/org/eclipse/jetty/server/handler/gzip/GzipHandler.java index 87130148d222..c2c3b49e52d1 100644 --- a/jetty-server/src/main/java/org/eclipse/jetty/server/handler/gzip/GzipHandler.java +++ b/jetty-server/src/main/java/org/eclipse/jetty/server/handler/gzip/GzipHandler.java @@ -43,7 +43,6 @@ import org.eclipse.jetty.server.Request; import org.eclipse.jetty.server.handler.HandlerWrapper; import org.eclipse.jetty.util.IncludeExclude; -import org.eclipse.jetty.util.RegexSet; import org.eclipse.jetty.util.StringUtil; import org.eclipse.jetty.util.compression.DeflaterPool; import org.slf4j.Logger; @@ -167,11 +166,10 @@ public class GzipHandler extends HandlerWrapper implements GzipFactory private int _inflateBufferSize = -1; private EnumSet _dispatchers = EnumSet.of(DispatcherType.REQUEST); // non-static, as other GzipHandler instances may have different configurations - private final IncludeExclude _agentPatterns = new IncludeExclude<>(RegexSet.class); private final IncludeExclude _methods = new IncludeExclude<>(); private final IncludeExclude _paths = new IncludeExclude<>(PathSpecSet.class); private final IncludeExclude _mimeTypes = new IncludeExclude<>(); - private HttpField _vary; + private HttpField _vary = GzipHttpOutputInterceptor.VARY_ACCEPT_ENCODING; /** * Instantiates a new GzipHandler. @@ -179,6 +177,7 @@ public class GzipHandler extends HandlerWrapper implements GzipFactory public GzipHandler() { _methods.include(HttpMethod.GET.asString()); + _methods.include(HttpMethod.POST.asString()); for (String type : MimeTypes.getKnownMimeTypes()) { if ("image/svg+xml".equals(type)) @@ -198,19 +197,29 @@ else if (type.startsWith("image/") || if (LOG.isDebugEnabled()) LOG.debug("{} mime types {}", this, _mimeTypes); + } - _agentPatterns.exclude(".*MSIE 6.0.*"); + /** + * @return The VARY field to use. + */ + public HttpField getVary() + { + return _vary; } /** - * Add excluded to the User-Agent filtering. - * - * @param patterns Regular expressions matching user agents to exclude - * @see #addIncludedAgentPatterns(String...) + * @param vary The VARY field to use. It if is not an instance of {@link PreEncodedHttpField}, + * then it will be converted to one. */ - public void addExcludedAgentPatterns(String... patterns) + public void setVary(HttpField vary) { - _agentPatterns.exclude(patterns); + if (isRunning()) + throw new IllegalStateException(getState()); + + if (vary == null || (vary instanceof PreEncodedHttpField)) + _vary = vary; + else + _vary = new PreEncodedHttpField(vary.getHeader(), vary.getName(), vary.getValue()); } /** @@ -312,17 +321,6 @@ public void addExcludedPaths(String... pathspecs) } } - /** - * Adds included User-Agents for filtering. - * - * @param patterns Regular expressions matching user agents to include - * @see #addExcludedAgentPatterns(String...) - */ - public void addIncludedAgentPatterns(String... patterns) - { - _agentPatterns.include(patterns); - } - /** * Adds included HTTP Methods (eg: POST, PATCH, DELETE) for filtering. * @@ -413,20 +411,12 @@ public void addIncludedPaths(String... pathspecs) protected void doStart() throws Exception { _deflaterPool = newDeflaterPool(poolCapacity); - _vary = (_agentPatterns.size() > 0) ? GzipHttpOutputInterceptor.VARY_ACCEPT_ENCODING_USER_AGENT : GzipHttpOutputInterceptor.VARY_ACCEPT_ENCODING; super.doStart(); } @Override public Deflater getDeflater(Request request, long contentLength) { - String ua = request.getHttpFields().get(HttpHeader.USER_AGENT); - if (ua != null && !isAgentGzipable(ua)) - { - LOG.debug("{} excluded user agent {}", this, request); - return null; - } - if (contentLength >= 0 && contentLength < _minGzipSize) { LOG.debug("{} excluded minGzipSize {}", this, request); @@ -443,18 +433,6 @@ public Deflater getDeflater(Request request, long contentLength) return _deflaterPool.acquire(); } - /** - * Get the current filter list of excluded User-Agent patterns - * - * @return the filter list of excluded User-Agent patterns - * @see #getIncludedAgentPatterns() - */ - public String[] getExcludedAgentPatterns() - { - Set excluded = _agentPatterns.getExcluded(); - return excluded.toArray(new String[excluded.size()]); - } - /** * Get the current filter list of excluded HTTP methods * @@ -464,7 +442,7 @@ public String[] getExcludedAgentPatterns() public String[] getExcludedMethods() { Set excluded = _methods.getExcluded(); - return excluded.toArray(new String[excluded.size()]); + return excluded.toArray(new String[0]); } /** @@ -476,7 +454,7 @@ public String[] getExcludedMethods() public String[] getExcludedMimeTypes() { Set excluded = _mimeTypes.getExcluded(); - return excluded.toArray(new String[excluded.size()]); + return excluded.toArray(new String[0]); } /** @@ -488,19 +466,7 @@ public String[] getExcludedMimeTypes() public String[] getExcludedPaths() { Set excluded = _paths.getExcluded(); - return excluded.toArray(new String[excluded.size()]); - } - - /** - * Get the current filter list of included User-Agent patterns - * - * @return the filter list of included User-Agent patterns - * @see #getExcludedAgentPatterns() - */ - public String[] getIncludedAgentPatterns() - { - Set includes = _agentPatterns.getIncluded(); - return includes.toArray(new String[includes.size()]); + return excluded.toArray(new String[0]); } /** @@ -512,7 +478,7 @@ public String[] getIncludedAgentPatterns() public String[] getIncludedMethods() { Set includes = _methods.getIncluded(); - return includes.toArray(new String[includes.size()]); + return includes.toArray(new String[0]); } /** @@ -524,7 +490,7 @@ public String[] getIncludedMethods() public String[] getIncludedMimeTypes() { Set includes = _mimeTypes.getIncluded(); - return includes.toArray(new String[includes.size()]); + return includes.toArray(new String[0]); } /** @@ -536,7 +502,7 @@ public String[] getIncludedMimeTypes() public String[] getIncludedPaths() { Set includes = _paths.getIncluded(); - return includes.toArray(new String[includes.size()]); + return includes.toArray(new String[0]); } /** @@ -735,20 +701,6 @@ else if (COMMA_GZIP.matcher(field.getValue()).matches()) } } - /** - * Test if the provided User-Agent is allowed based on the User-Agent filters. - * - * @param ua the user agent - * @return whether compressing is allowed for the given user agent - */ - protected boolean isAgentGzipable(String ua) - { - if (ua == null) - return false; - - return _agentPatterns.test(ua); - } - /** * Test if the provided MIME type is allowed based on the MIME type filters. * @@ -781,21 +733,6 @@ public void recycle(Deflater deflater) _deflaterPool.release(deflater); } - /** - * if(isStarted()) - * throw new IllegalStateException(getState()); - * - * Set the excluded filter list of User-Agent patterns (replacing any previously set) - * - * @param patterns Regular expressions list matching user agents to exclude - * @see #setIncludedAgentPatterns(String...) - */ - public void setExcludedAgentPatterns(String... patterns) - { - _agentPatterns.getExcluded().clear(); - addExcludedAgentPatterns(patterns); - } - /** * Set the excluded filter list of HTTP methods (replacing any previously set) * @@ -834,18 +771,6 @@ public void setExcludedPaths(String... pathspecs) _paths.exclude(pathspecs); } - /** - * Set the included filter list of User-Agent patterns (replacing any previously set) - * - * @param patterns Regular expressions matching user agents to include - * @see #setExcludedAgentPatterns(String...) - */ - public void setIncludedAgentPatterns(String... patterns) - { - _agentPatterns.getIncluded().clear(); - addIncludedAgentPatterns(patterns); - } - /** * Set the included filter list of HTTP methods (replacing any previously set) * diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/handler/gzip/GzipHttpOutputInterceptor.java b/jetty-server/src/main/java/org/eclipse/jetty/server/handler/gzip/GzipHttpOutputInterceptor.java index d45008076e29..7a39f160b955 100644 --- a/jetty-server/src/main/java/org/eclipse/jetty/server/handler/gzip/GzipHttpOutputInterceptor.java +++ b/jetty-server/src/main/java/org/eclipse/jetty/server/handler/gzip/GzipHttpOutputInterceptor.java @@ -46,7 +46,6 @@ public class GzipHttpOutputInterceptor implements HttpOutput.Interceptor public static Logger LOG = LoggerFactory.getLogger(GzipHttpOutputInterceptor.class); private static final byte[] GZIP_HEADER = new byte[]{(byte)0x1f, (byte)0x8b, Deflater.DEFLATED, 0, 0, 0, 0, 0, 0, 0}; - public static final HttpField VARY_ACCEPT_ENCODING_USER_AGENT = new PreEncodedHttpField(HttpHeader.VARY, HttpHeader.ACCEPT_ENCODING + ", " + HttpHeader.USER_AGENT); public static final HttpField VARY_ACCEPT_ENCODING = new PreEncodedHttpField(HttpHeader.VARY, HttpHeader.ACCEPT_ENCODING.asString()); private enum GZState @@ -69,7 +68,7 @@ private enum GZState public GzipHttpOutputInterceptor(GzipFactory factory, HttpChannel channel, HttpOutput.Interceptor next, boolean syncFlush) { - this(factory, VARY_ACCEPT_ENCODING_USER_AGENT, channel.getHttpConfiguration().getOutputBufferSize(), channel, next, syncFlush); + this(factory, VARY_ACCEPT_ENCODING, channel.getHttpConfiguration().getOutputBufferSize(), channel, next, syncFlush); } public GzipHttpOutputInterceptor(GzipFactory factory, HttpField vary, HttpChannel channel, HttpOutput.Interceptor next, boolean syncFlush) @@ -190,12 +189,7 @@ protected void commit(ByteBuffer content, boolean complete, Callback callback) { // We are varying the response due to accept encoding header. if (_vary != null) - { - if (fields.contains(HttpHeader.VARY)) - fields.addCSV(HttpHeader.VARY, _vary.getValues()); - else - fields.add(_vary); - } + fields.ensureField(_vary); long contentLength = response.getContentLength(); if (contentLength < 0 && complete) @@ -263,27 +257,6 @@ public void noCompression() } } - public void noCompressionIfPossible() - { - while (true) - { - switch (_state.get()) - { - case COMPRESSING: - case NOT_COMPRESSING: - return; - - case MIGHT_COMPRESS: - if (_state.compareAndSet(GZState.MIGHT_COMPRESS, GZState.NOT_COMPRESSING)) - return; - break; - - default: - throw new IllegalStateException(_state.get().toString()); - } - } - } - public boolean mightCompress() { return _state.get() == GZState.MIGHT_COMPRESS; diff --git a/jetty-servlet/src/test/java/org/eclipse/jetty/servlet/GzipHandlerBreakEvenSizeTest.java b/jetty-servlet/src/test/java/org/eclipse/jetty/servlet/GzipHandlerBreakEvenSizeTest.java index 2739bbe77d77..5a2b46b56a9c 100644 --- a/jetty-servlet/src/test/java/org/eclipse/jetty/servlet/GzipHandlerBreakEvenSizeTest.java +++ b/jetty-servlet/src/test/java/org/eclipse/jetty/servlet/GzipHandlerBreakEvenSizeTest.java @@ -59,7 +59,6 @@ public void startServerAndClient() throws Exception server.addConnector(connector); GzipHandler gzipHandler = new GzipHandler(); - gzipHandler.setExcludedAgentPatterns(); gzipHandler.setMinGzipSize(0); ServletContextHandler context = new ServletContextHandler(gzipHandler, "/"); diff --git a/jetty-servlet/src/test/java/org/eclipse/jetty/servlet/GzipHandlerTest.java b/jetty-servlet/src/test/java/org/eclipse/jetty/servlet/GzipHandlerTest.java index 95d12675c7ba..c0a85e5a4a29 100644 --- a/jetty-servlet/src/test/java/org/eclipse/jetty/servlet/GzipHandlerTest.java +++ b/jetty-servlet/src/test/java/org/eclipse/jetty/servlet/GzipHandlerTest.java @@ -101,7 +101,6 @@ public void init() throws Exception _server.addConnector(_connector); GzipHandler gzipHandler = new GzipHandler(); - gzipHandler.setExcludedAgentPatterns(); gzipHandler.setMinGzipSize(16); gzipHandler.setInflateBufferSize(4096); @@ -174,7 +173,7 @@ protected void doGet(HttpServletRequest req, HttpServletResponse response) throw } @Override - protected void doDelete(HttpServletRequest req, HttpServletResponse response) throws ServletException, IOException + protected void doDelete(HttpServletRequest req, HttpServletResponse response) throws IOException { String ifm = req.getHeader("If-Match"); if (ifm != null && ifm.equals(__contentETag)) @@ -312,7 +311,7 @@ public void testNotGzipHandler() throws Exception assertThat(response.getStatus(), is(200)); assertThat(response.get("Content-Encoding"), not(equalToIgnoringCase("gzip"))); assertThat(response.get("ETag"), is(__contentETag)); - assertThat(response.getValuesList("Vary"), Matchers.contains("Other", "Accept-Encoding")); + assertThat(response.getCSV("Vary", false), Matchers.contains("Other", "Accept-Encoding")); InputStream testIn = new ByteArrayInputStream(response.getContentBytes()); ByteArrayOutputStream testOut = new ByteArrayOutputStream(); diff --git a/jetty-servlets/src/test/java/org/eclipse/jetty/servlets/GzipDefaultServletTest.java b/jetty-servlets/src/test/java/org/eclipse/jetty/servlets/GzipDefaultServletTest.java index 958058db75c2..3d60c01eac94 100644 --- a/jetty-servlets/src/test/java/org/eclipse/jetty/servlets/GzipDefaultServletTest.java +++ b/jetty-servlets/src/test/java/org/eclipse/jetty/servlets/GzipDefaultServletTest.java @@ -267,7 +267,7 @@ public void testIsGzipCompressed(int fileSize) throws Exception // Response Content-Encoding check assertThat("Response[Content-Encoding]", response.get("Content-Encoding"), containsString("gzip")); - assertThat("Response[Vary]", response.get("Vary"), containsString("Accept-Encoding, User-Agent")); + assertThat("Response[Vary]", response.get("Vary"), containsString("Accept-Encoding")); // Response Content checks UncompressedMetadata metadata = parseResponseContent(response); @@ -328,7 +328,7 @@ public void testIsGzipCompressedIfModifiedSince(int fileSize) throws Exception assertThat("Response[Content-Encoding]", response.get("Content-Encoding"), containsString("gzip")); assertThat("Response[ETag]", response.get("ETag"), startsWith("W/")); assertThat("Response[ETag]", response.get("ETag"), containsString(CompressedContentFormat.GZIP._etag)); - assertThat("Response[Vary]", response.get("Vary"), containsString("Accept-Encoding, User-Agent")); + assertThat("Response[Vary]", response.get("Vary"), containsString("Accept-Encoding")); // Response Content checks UncompressedMetadata metadata = parseResponseContent(response); @@ -387,7 +387,7 @@ public void testGzippedIfSVG() throws Exception // Response Content-Encoding check assertThat("Response[Content-Encoding]", response.get("Content-Encoding"), containsString("gzip")); - assertThat("Response[Vary]", response.get("Vary"), containsString("Accept-Encoding, User-Agent")); + assertThat("Response[Vary]", response.get("Vary"), containsString("Accept-Encoding")); // Response Content checks UncompressedMetadata metadata = parseResponseContent(response); @@ -511,7 +511,7 @@ public void testIsNotGzipCompressedWithZeroQ() throws Exception // Response Content-Encoding check assertThat("Response[Content-Encoding]", response.get("Content-Encoding"), not(containsString("gzip"))); - assertThat("Response[Vary]", response.get("Vary"), containsString("Accept-Encoding, User-Agent")); + assertThat("Response[Vary]", response.get("Vary"), containsString("Accept-Encoding")); // Response Content checks UncompressedMetadata metadata = parseResponseContent(response); @@ -568,7 +568,7 @@ public void testIsGzipCompressedWithQ() throws Exception // Response Content-Encoding check assertThat("Response[Content-Encoding]", response.get("Content-Encoding"), containsString("gzip")); - assertThat("Response[Vary]", response.get("Vary"), containsString("Accept-Encoding, User-Agent")); + assertThat("Response[Vary]", response.get("Vary"), containsString("Accept-Encoding")); // Response Content checks UncompressedMetadata metadata = parseResponseContent(response); @@ -750,240 +750,6 @@ public void testIsNotGzipCompressedByExcludedContentTypeWithCharset() throws Exc assertThat("(Uncompressed) Content Hash", metadata.uncompressedSha1Sum, is(expectedSha1Sum)); } - @Test - public void testUserAgentExclusionNoUAProvided() throws Exception - { - GzipHandler gzipHandler = new GzipHandler(); - gzipHandler.addIncludedMimeTypes("text/plain"); - gzipHandler.setExcludedAgentPatterns("bar", "foo"); - - server = new Server(); - LocalConnector localConnector = new LocalConnector(server); - server.addConnector(localConnector); - - Path contextDir = workDir.resolve("context"); - FS.ensureDirExists(contextDir); - - ServletContextHandler servletContextHandler = new ServletContextHandler(); - servletContextHandler.setContextPath("/context"); - servletContextHandler.setBaseResource(new PathResource(contextDir)); - ServletHolder holder = new ServletHolder("default", DefaultServlet.class); - servletContextHandler.addServlet(holder, "/"); - servletContextHandler.insertHandler(gzipHandler); - - server.setHandler(servletContextHandler); - - // Prepare Server File - int fileSize = DEFAULT_OUTPUT_BUFFER_SIZE * 4; - Path file = createFile(contextDir, "file.txt", fileSize); - String expectedSha1Sum = Sha1Sum.calculate(file); - - server.start(); - - // Setup request - HttpTester.Request request = HttpTester.newRequest(); - request.setMethod("GET"); - request.setVersion(HttpVersion.HTTP_1_1); - request.setHeader("Host", "tester"); - request.setHeader("Connection", "close"); - request.setHeader("Accept-Encoding", "gzip"); - // INTENTIONALLY NOT SET - request.setHeader("User-Agent", "foo"); - request.setURI("/context/file.txt"); - - // Issue request - ByteBuffer rawResponse = localConnector.getResponse(request.generate(), 5, TimeUnit.SECONDS); - - // Parse response - HttpTester.Response response = HttpTester.parseResponse(rawResponse); - - assertThat("Response status", response.getStatus(), is(HttpStatus.OK_200)); - - // Response Content-Encoding check - assertThat("Response[Content-Encoding]", response.get("Content-Encoding"), containsString("gzip")); - assertThat("Response[Vary]", response.get("Vary"), is("Accept-Encoding, User-Agent")); - - // Response Content checks - UncompressedMetadata metadata = parseResponseContent(response); - assertThat("(Uncompressed) Content Length", metadata.uncompressedSize, is(fileSize)); - assertThat("(Uncompressed) Content Hash", metadata.uncompressedSha1Sum, is(expectedSha1Sum)); - } - - @Test - public void testUserAgentExclusionUAMatch() throws Exception - { - GzipHandler gzipHandler = new GzipHandler(); - gzipHandler.addIncludedMimeTypes("text/plain"); - gzipHandler.setExcludedAgentPatterns("bar", "foo"); - - server = new Server(); - LocalConnector localConnector = new LocalConnector(server); - server.addConnector(localConnector); - - Path contextDir = workDir.resolve("context"); - FS.ensureDirExists(contextDir); - - ServletContextHandler servletContextHandler = new ServletContextHandler(); - servletContextHandler.setContextPath("/context"); - servletContextHandler.setBaseResource(new PathResource(contextDir)); - ServletHolder holder = new ServletHolder("default", DefaultServlet.class); - servletContextHandler.addServlet(holder, "/"); - servletContextHandler.insertHandler(gzipHandler); - - server.setHandler(servletContextHandler); - - // Prepare Server File - int fileSize = DEFAULT_OUTPUT_BUFFER_SIZE * 4; - Path file = createFile(contextDir, "file.txt", fileSize); - String expectedSha1Sum = Sha1Sum.calculate(file); - - server.start(); - - // Setup request - HttpTester.Request request = HttpTester.newRequest(); - request.setMethod("GET"); - request.setVersion(HttpVersion.HTTP_1_1); - request.setHeader("Host", "tester"); - request.setHeader("Connection", "close"); - request.setHeader("Accept-Encoding", "gzip"); - request.setHeader("User-Agent", "foo"); - request.setURI("/context/file.txt"); - - // Issue request - ByteBuffer rawResponse = localConnector.getResponse(request.generate(), 5, TimeUnit.SECONDS); - - // Parse response - HttpTester.Response response = HttpTester.parseResponse(rawResponse); - - assertThat("Response status", response.getStatus(), is(HttpStatus.OK_200)); - - // Response Content-Encoding check - assertThat("Response[Content-Encoding]", response.get("Content-Encoding"), not(containsString("gzip"))); - assertThat("Response[Vary]", response.get("Vary"), is("Accept-Encoding, User-Agent")); - - // Response Content checks - UncompressedMetadata metadata = parseResponseContent(response); - assertThat("Response Content Length", metadata.contentLength, is(fileSize)); - assertThat("(Uncompressed) Content Length", metadata.uncompressedSize, is(fileSize)); - assertThat("(Uncompressed) Content Hash", metadata.uncompressedSha1Sum, is(expectedSha1Sum)); - } - - @Test - public void testUserAgentExclusionDefault() throws Exception - { - GzipHandler gzipHandler = new GzipHandler(); - gzipHandler.addIncludedMimeTypes("text/plain"); - - server = new Server(); - LocalConnector localConnector = new LocalConnector(server); - server.addConnector(localConnector); - - Path contextDir = workDir.resolve("context"); - FS.ensureDirExists(contextDir); - - ServletContextHandler servletContextHandler = new ServletContextHandler(); - servletContextHandler.setContextPath("/context"); - servletContextHandler.setBaseResource(new PathResource(contextDir)); - ServletHolder holder = new ServletHolder("default", DefaultServlet.class); - servletContextHandler.addServlet(holder, "/"); - servletContextHandler.insertHandler(gzipHandler); - - server.setHandler(servletContextHandler); - - // Prepare Server File - int fileSize = DEFAULT_OUTPUT_BUFFER_SIZE * 4; - Path file = createFile(contextDir, "file.txt", fileSize); - String expectedSha1Sum = Sha1Sum.calculate(file); - - server.start(); - - // Setup request - HttpTester.Request request = HttpTester.newRequest(); - request.setMethod("GET"); - request.setVersion(HttpVersion.HTTP_1_1); - request.setHeader("Host", "tester"); - request.setHeader("Connection", "close"); - request.setHeader("Accept-Encoding", "gzip"); - request.setHeader("User-Agent", "Some MSIE 6.0 user-agent"); - request.setURI("/context/file.txt"); - - // Issue request - ByteBuffer rawResponse = localConnector.getResponse(request.generate(), 5, TimeUnit.SECONDS); - - // Parse response - HttpTester.Response response = HttpTester.parseResponse(rawResponse); - - assertThat("Response status", response.getStatus(), is(HttpStatus.OK_200)); - - // Response Content-Encoding check - assertThat("Response[Content-Encoding]", response.get("Content-Encoding"), not(containsString("gzip"))); - assertThat("Response[Vary]", response.get("Vary"), is("Accept-Encoding, User-Agent")); - - // Response Content checks - UncompressedMetadata metadata = parseResponseContent(response); - assertThat("Response Content Length", metadata.contentLength, is(fileSize)); - assertThat("(Uncompressed) Content Length", metadata.uncompressedSize, is(fileSize)); - assertThat("(Uncompressed) Content Hash", metadata.uncompressedSha1Sum, is(expectedSha1Sum)); - } - - @Test - public void testUserAgentExclusionByExcludedAgentPatterns() throws Exception - { - GzipHandler gzipHandler = new GzipHandler(); - gzipHandler.addIncludedMimeTypes("text/plain"); - gzipHandler.setExcludedAgentPatterns("bar", "fo.*"); - - server = new Server(); - LocalConnector localConnector = new LocalConnector(server); - server.addConnector(localConnector); - - Path contextDir = workDir.resolve("context"); - FS.ensureDirExists(contextDir); - - ServletContextHandler servletContextHandler = new ServletContextHandler(); - servletContextHandler.setContextPath("/context"); - servletContextHandler.setBaseResource(new PathResource(contextDir)); - ServletHolder holder = new ServletHolder("default", DefaultServlet.class); - servletContextHandler.addServlet(holder, "/"); - servletContextHandler.insertHandler(gzipHandler); - - server.setHandler(servletContextHandler); - - // Prepare Server File - int fileSize = DEFAULT_OUTPUT_BUFFER_SIZE * 4; - Path file = createFile(contextDir, "file.txt", fileSize); - String expectedSha1Sum = Sha1Sum.calculate(file); - - server.start(); - - // Setup request - HttpTester.Request request = HttpTester.newRequest(); - request.setMethod("GET"); - request.setVersion(HttpVersion.HTTP_1_1); - request.setHeader("Host", "tester"); - request.setHeader("Connection", "close"); - request.setHeader("Accept-Encoding", "gzip"); - request.setHeader("User-Agent", "foo"); - request.setURI("/context/file.txt"); - - // Issue request - ByteBuffer rawResponse = localConnector.getResponse(request.generate(), 5, TimeUnit.SECONDS); - - // Parse response - HttpTester.Response response = HttpTester.parseResponse(rawResponse); - - assertThat("Response status", response.getStatus(), is(HttpStatus.OK_200)); - - // Response Content-Encoding check - assertThat("Response[Content-Encoding]", response.get("Content-Encoding"), not(containsString("gzip"))); - assertThat("Response[Vary]", response.get("Vary"), is("Accept-Encoding, User-Agent")); - - // Response Content checks - UncompressedMetadata metadata = parseResponseContent(response); - assertThat("Response Content Length", metadata.contentLength, is(fileSize)); - assertThat("(Uncompressed) Content Length", metadata.uncompressedSize, is(fileSize)); - assertThat("(Uncompressed) Content Hash", metadata.uncompressedSha1Sum, is(expectedSha1Sum)); - } - @Test public void testExcludePaths() throws Exception { @@ -1090,7 +856,7 @@ public void testIncludedPaths() throws Exception assertThat("Response status", response.getStatus(), is(HttpStatus.OK_200)); assertThat("Response[Content-Encoding]", response.get("Content-Encoding"), containsString("gzip")); - assertThat("Response[Vary]", response.get("Vary"), is("Accept-Encoding, User-Agent")); + assertThat("Response[Vary]", response.get("Vary"), is("Accept-Encoding")); UncompressedMetadata metadata = parseResponseContent(response); assertThat("(Uncompressed) Content Length", metadata.uncompressedSize, is((int)Files.size(fileGood))); diff --git a/jetty-servlets/src/test/java/org/eclipse/jetty/servlets/GzipHandlerTest.java b/jetty-servlets/src/test/java/org/eclipse/jetty/servlets/GzipHandlerTest.java index 980b254f6723..c3e93b5b6692 100644 --- a/jetty-servlets/src/test/java/org/eclipse/jetty/servlets/GzipHandlerTest.java +++ b/jetty-servlets/src/test/java/org/eclipse/jetty/servlets/GzipHandlerTest.java @@ -59,7 +59,6 @@ public void testGzipCompressedByContentTypeWithEncoding() throws Exception GzipHandler gzipHandler = new GzipHandler(); gzipHandler.setMinGzipSize(32); gzipHandler.addIncludedMimeTypes("text/plain"); - gzipHandler.setExcludedAgentPatterns(); server = new Server(); LocalConnector localConnector = new LocalConnector(server);