diff --git a/jetty-http/src/main/java/org/eclipse/jetty/http/CompressedContentFormat.java b/jetty-http/src/main/java/org/eclipse/jetty/http/CompressedContentFormat.java index e72fcf958fd..a41ab7194a1 100644 --- a/jetty-http/src/main/java/org/eclipse/jetty/http/CompressedContentFormat.java +++ b/jetty-http/src/main/java/org/eclipse/jetty/http/CompressedContentFormat.java @@ -15,6 +15,7 @@ package org.eclipse.jetty.http; import java.util.Objects; +import org.eclipse.jetty.util.QuotedStringTokenizer; import org.eclipse.jetty.util.StringUtil; public class CompressedContentFormat @@ -32,18 +33,18 @@ public class CompressedContentFormat public static final CompressedContentFormat BR = new CompressedContentFormat("br", ".br"); public static final CompressedContentFormat[] NONE = new CompressedContentFormat[0]; - public final String _encoding; - public final String _extension; - public final String _etag; - public final String _etagQuote; - public final PreEncodedHttpField _contentEncoding; + private final String _encoding; + private final String _extension; + private final String _etagSuffix; + private final String _etagSuffixQuote; + private final PreEncodedHttpField _contentEncoding; public CompressedContentFormat(String encoding, String extension) { _encoding = StringUtil.asciiToLowerCase(encoding); _extension = StringUtil.asciiToLowerCase(extension); - _etag = ETAG_SEPARATOR + _encoding; - _etagQuote = _etag + "\""; + _etagSuffix = StringUtil.isEmpty(ETAG_SEPARATOR) ? "" : (ETAG_SEPARATOR + _encoding); + _etagSuffixQuote = _etagSuffix + "\""; _contentEncoding = new PreEncodedHttpField(HttpHeader.CONTENT_ENCODING, _encoding); } @@ -56,20 +57,104 @@ public class CompressedContentFormat return Objects.equals(_encoding, ccf._encoding) && Objects.equals(_extension, ccf._extension); } + public String getEncoding() + { + return _encoding; + } + + public String getExtension() + { + return _extension; + } + + public String getEtagSuffix() + { + return _etagSuffix; + } + + public HttpField getContentEncoding() + { + return _contentEncoding; + } + + /** Get an etag with suffix that represents this compressed type. + * @param etag An etag + * @return An etag with compression suffix, or the etag itself if no suffix is configured. + */ + public String etag(String etag) + { + if (StringUtil.isEmpty(ETAG_SEPARATOR)) + return etag; + int end = etag.length() - 1; + if (etag.charAt(end) == '"') + return etag.substring(0, end) + _etagSuffixQuote; + return etag + _etagSuffix; + } + @Override public int hashCode() { return Objects.hash(_encoding, _extension); } - public static boolean tagEquals(String etag, String tag) + /** Check etags for equality, accounting for quoting and compression suffixes. + * @param etag An etag without a compression suffix + * @param etagWithSuffix An etag optionally with a compression suffix. + * @return True if the tags are equal. + */ + public static boolean tagEquals(String etag, String etagWithSuffix) { - if (etag.equals(tag)) + // Handle simple equality + if (etag.equals(etagWithSuffix)) return true; - int separator = tag.lastIndexOf(ETAG_SEPARATOR); - if (separator > 0 && separator == etag.length() - 1) - return etag.regionMatches(0, tag, 0, separator); - return false; + // If no separator defined, then simple equality is only possible positive + if (StringUtil.isEmpty(ETAG_SEPARATOR)) + return false; + + // Are both tags quoted? + boolean etagQuoted = etag.endsWith("\""); + boolean etagSuffixQuoted = etagWithSuffix.endsWith("\""); + + // Look for a separator + int separator = etagWithSuffix.lastIndexOf(ETAG_SEPARATOR); + + // If both tags are quoted the same (the norm) then any difference must be the suffix + if (etagQuoted == etagSuffixQuoted) + return separator > 0 && etag.regionMatches(0, etagWithSuffix, 0, separator); + + // If either tag is weak then we can't match because weak tags must be quoted + if (etagWithSuffix.startsWith("W/") || etag.startsWith("W/")) + return false; + + // compare unquoted strong etags + etag = etagQuoted ? QuotedStringTokenizer.unquote(etag) : etag; + etagWithSuffix = etagSuffixQuoted ? QuotedStringTokenizer.unquote(etagWithSuffix) : etagWithSuffix; + separator = etagWithSuffix.lastIndexOf(ETAG_SEPARATOR); + if (separator > 0) + return etag.regionMatches(0, etagWithSuffix, 0, separator); + + return Objects.equals(etag, etagWithSuffix); + } + + public String stripSuffixes(String etagsList) + { + if (StringUtil.isEmpty(ETAG_SEPARATOR)) + return etagsList; + + // This is a poor implementation that ignores list and tag structure + while (true) + { + int i = etagsList.lastIndexOf(_etagSuffix); + if (i < 0) + return etagsList; + etagsList = etagsList.substring(0, i) + etagsList.substring(i + _etagSuffix.length()); + } + } + + @Override + public String toString() + { + return _encoding; } } diff --git a/jetty-http/src/main/java/org/eclipse/jetty/http/PrecompressedHttpContent.java b/jetty-http/src/main/java/org/eclipse/jetty/http/PrecompressedHttpContent.java index 90666bbca5d..9096866ef34 100644 --- a/jetty-http/src/main/java/org/eclipse/jetty/http/PrecompressedHttpContent.java +++ b/jetty-http/src/main/java/org/eclipse/jetty/http/PrecompressedHttpContent.java @@ -66,7 +66,7 @@ public class PrecompressedHttpContent implements HttpContent @Override public String getETagValue() { - return _content.getResource().getWeakETag(_format._etag); + return _content.getResource().getWeakETag(_format.getEtagSuffix()); } @Override @@ -96,13 +96,13 @@ public class PrecompressedHttpContent implements HttpContent @Override public HttpField getContentEncoding() { - return _format._contentEncoding; + return _format.getContentEncoding(); } @Override public String getContentEncodingValue() { - return _format._contentEncoding.getValue(); + return _format.getContentEncoding().getValue(); } @Override @@ -162,7 +162,9 @@ public class PrecompressedHttpContent implements HttpContent @Override public String toString() { - return String.format("PrecompressedHttpContent@%x{e=%s,r=%s|%s,lm=%s|%s,ct=%s}", hashCode(), _format._encoding, + return String.format("%s@%x{e=%s,r=%s|%s,lm=%s|%s,ct=%s}", + this.getClass().getSimpleName(), hashCode(), + _format, _content.getResource(), _precompressedContent.getResource(), _content.getResource().lastModified(), _precompressedContent.getResource().lastModified(), getContentType()); diff --git a/jetty-http/src/test/java/org/eclipse/jetty/http/GZIPContentDecoderTest.java b/jetty-http/src/test/java/org/eclipse/jetty/http/GZIPContentDecoderTest.java index 12d2675b945..3eaef0618d2 100644 --- a/jetty-http/src/test/java/org/eclipse/jetty/http/GZIPContentDecoderTest.java +++ b/jetty-http/src/test/java/org/eclipse/jetty/http/GZIPContentDecoderTest.java @@ -30,6 +30,8 @@ import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.ValueSource; +import static org.eclipse.jetty.http.CompressedContentFormat.BR; +import static org.eclipse.jetty.http.CompressedContentFormat.GZIP; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.is; import static org.junit.jupiter.api.Assertions.assertEquals; @@ -75,9 +77,25 @@ public class GZIPContentDecoderTest { assertTrue(CompressedContentFormat.tagEquals("tag", "tag")); assertTrue(CompressedContentFormat.tagEquals("\"tag\"", "\"tag\"")); - assertTrue(CompressedContentFormat.tagEquals("\"tag\"", "\"tag--gzip\"")); - assertFalse(CompressedContentFormat.tagEquals("Zag", "Xag--gzip")); + assertTrue(CompressedContentFormat.tagEquals("\"tag\"", "\"tag" + GZIP.getEtagSuffix() + "\"")); + assertTrue(CompressedContentFormat.tagEquals("\"tag\"", "\"tag" + BR.getEtagSuffix() + "\"")); + assertTrue(CompressedContentFormat.tagEquals("W/\"1234567\"", "W/\"1234567\"")); + assertTrue(CompressedContentFormat.tagEquals("W/\"1234567\"", "W/\"1234567" + GZIP.getEtagSuffix() + "\"")); + + assertFalse(CompressedContentFormat.tagEquals("Zag", "Xag" + GZIP.getEtagSuffix())); assertFalse(CompressedContentFormat.tagEquals("xtag", "tag")); + assertFalse(CompressedContentFormat.tagEquals("W/\"1234567\"", "W/\"1234111\"")); + assertFalse(CompressedContentFormat.tagEquals("W/\"1234567\"", "W/\"1234111" + GZIP.getEtagSuffix() + "\"")); + + assertTrue(CompressedContentFormat.tagEquals("12345", "\"12345\"")); + assertTrue(CompressedContentFormat.tagEquals("\"12345\"", "12345")); + assertTrue(CompressedContentFormat.tagEquals("12345", "\"12345" + GZIP.getEtagSuffix() + "\"")); + assertTrue(CompressedContentFormat.tagEquals("\"12345\"", "12345" + GZIP.getEtagSuffix())); + + assertThat(GZIP.stripSuffixes("12345"), is("12345")); + assertThat(GZIP.stripSuffixes("12345, 666" + GZIP.getEtagSuffix()), is("12345, 666")); + assertThat(GZIP.stripSuffixes("12345, 666" + GZIP.getEtagSuffix() + ",W/\"9999" + GZIP.getEtagSuffix() + "\""), + is("12345, 666,W/\"9999\"")); } @Test diff --git a/jetty-rewrite/src/main/java/org/eclipse/jetty/rewrite/handler/ForceRequestHeaderValueRule.java b/jetty-rewrite/src/main/java/org/eclipse/jetty/rewrite/handler/ForceRequestHeaderValueRule.java new file mode 100644 index 00000000000..e042ceb594c --- /dev/null +++ b/jetty-rewrite/src/main/java/org/eclipse/jetty/rewrite/handler/ForceRequestHeaderValueRule.java @@ -0,0 +1,73 @@ +// +// ======================================================================== +// Copyright (c) 1995-2021 Mort Bay Consulting Pty Ltd and others. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== +// + +package org.eclipse.jetty.rewrite.handler; + +import java.io.IOException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.eclipse.jetty.server.Request; + +public class ForceRequestHeaderValueRule extends Rule +{ + private String headerName; + private String forcedValue; + + public String getHeaderName() + { + return headerName; + } + + public void setHeaderName(String headerName) + { + this.headerName = headerName; + } + + public String getForcedValue() + { + return forcedValue; + } + + public void setForcedValue(String forcedValue) + { + this.forcedValue = forcedValue; + } + + @Override + public String matchAndApply(String target, HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse) throws IOException + { + String existingValue = httpServletRequest.getHeader(headerName); + if (existingValue == null) + { + // no hit, skip this rule. + return null; + } + + if (existingValue.equals(forcedValue)) + { + // already what we expect, skip this rule. + return null; + } + + Request baseRequest = Request.getBaseRequest(httpServletRequest); + baseRequest.getHttpFields().remove(headerName); + baseRequest.getHttpFields().add(headerName, forcedValue); + return target; + } +} diff --git a/jetty-rewrite/src/test/java/org/eclipse/jetty/rewrite/handler/ForceRequestHeaderValueRuleTest.java b/jetty-rewrite/src/test/java/org/eclipse/jetty/rewrite/handler/ForceRequestHeaderValueRuleTest.java new file mode 100644 index 00000000000..2b5954e6852 --- /dev/null +++ b/jetty-rewrite/src/test/java/org/eclipse/jetty/rewrite/handler/ForceRequestHeaderValueRuleTest.java @@ -0,0 +1,187 @@ +// +// ======================================================================== +// Copyright (c) 1995-2021 Mort Bay Consulting Pty Ltd and others. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== +// + +package org.eclipse.jetty.rewrite.handler; + +import java.io.IOException; +import java.io.OutputStream; +import java.io.OutputStreamWriter; +import java.util.Collections; +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.eclipse.jetty.http.HttpTester; +import org.eclipse.jetty.server.Handler; +import org.eclipse.jetty.server.LocalConnector; +import org.eclipse.jetty.server.Request; +import org.eclipse.jetty.server.Server; +import org.eclipse.jetty.server.handler.AbstractHandler; +import org.eclipse.jetty.server.handler.HandlerList; +import org.eclipse.jetty.util.component.LifeCycle; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.containsString; +import static org.hamcrest.Matchers.not; +import static org.junit.jupiter.api.Assertions.assertEquals; + +public class ForceRequestHeaderValueRuleTest +{ + private Server server; + private LocalConnector connector; + private ForceRequestHeaderValueRule rule; + + @BeforeEach + public void setup() throws Exception + { + server = new Server(); + connector = new LocalConnector(server); + server.addConnector(connector); + + HandlerList handlers = new HandlerList(); + + RewriteHandler rewriteHandler = new RewriteHandler(); + rule = new ForceRequestHeaderValueRule(); + rewriteHandler.addRule(rule); + + Handler handler = new AbstractHandler() + { + @Override + public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException + { + response.setContentType("text/plain"); + response.setCharacterEncoding("utf-8"); + OutputStream stream = response.getOutputStream(); + OutputStreamWriter out = new OutputStreamWriter(stream); + out.append("Echo\n"); + for (String headerName : Collections.list(request.getHeaderNames())) + { + // Combine all values for header into single output on response body + out.append("Request Header[").append(headerName).append("]: [") + .append(request.getHeader(headerName)).append("]\n"); + } + out.flush(); + baseRequest.setHandled(true); + } + }; + + handlers.addHandler(rewriteHandler); + handlers.addHandler(handler); + server.setHandler(handlers); + server.start(); + } + + @AfterEach + public void teardown() + { + LifeCycle.stop(server); + } + + @Test + public void testNormalRequest() throws Exception + { + rule.setHeaderName("Accept"); + rule.setForcedValue("*/*"); + + StringBuilder request = new StringBuilder(); + request.append("GET /echo/foo HTTP/1.1\r\n"); + request.append("Host: local\r\n"); + request.append("Connection: closed\r\n"); + request.append("\r\n"); + + HttpTester.Response response = HttpTester.parseResponse(connector.getResponse(request.toString())); + assertEquals(200, response.getStatus()); + assertThat(response.getContent(), not(containsString("[Accept]"))); + assertThat(response.getContent(), containsString("[Host]: [local]")); + assertThat(response.getContent(), containsString("[Connection]: [closed]")); + } + + @Test + public void testOneAcceptHeaderRequest() throws Exception + { + rule.setHeaderName("Accept"); + rule.setForcedValue("*/*"); + + StringBuilder request = new StringBuilder(); + request.append("GET /echo/foo HTTP/1.1\r\n"); + request.append("Host: local\r\n"); + request.append("Accept: */*\r\n"); + request.append("Connection: closed\r\n"); + request.append("\r\n"); + + String rawResponse = connector.getResponse(request.toString()); + HttpTester.Response response = HttpTester.parseResponse(rawResponse); + assertEquals(200, response.getStatus()); + assertThat(response.getContent(), containsString("[Accept]: [*/*]")); + assertThat(response.getContent(), containsString("[Host]: [local]")); + assertThat(response.getContent(), containsString("[Connection]: [closed]")); + } + + @Test + public void testThreeAcceptHeadersRequest() throws Exception + { + rule.setHeaderName("Accept"); + rule.setForcedValue("*/*"); + + StringBuilder request = new StringBuilder(); + request.append("GET /echo/foo HTTP/1.1\r\n"); + request.append("Host: local\r\n"); + request.append("Accept: images/jpeg\r\n"); + request.append("Accept: text/plain\r\n"); + request.append("Accept: */*\r\n"); + request.append("Connection: closed\r\n"); + request.append("\r\n"); + + String rawResponse = connector.getResponse(request.toString()); + HttpTester.Response response = HttpTester.parseResponse(rawResponse); + assertEquals(200, response.getStatus()); + assertThat(response.getContent(), containsString("[Accept]: [*/*]")); + assertThat(response.getContent(), containsString("[Host]: [local]")); + assertThat(response.getContent(), containsString("[Connection]: [closed]")); + } + + @Test + public void testInterleavedAcceptHeadersRequest() throws Exception + { + rule.setHeaderName("Accept"); + rule.setForcedValue("*/*"); + + StringBuilder request = new StringBuilder(); + request.append("GET /echo/foo HTTP/1.1\r\n"); + request.append("Host: local\r\n"); + request.append("Accept: images/jpeg\r\n"); // not value intended to be forced + request.append("Accept-Encoding: gzip;q=1.0, identity; q=0.5, *;q=0\r\n"); + request.append("accept: text/plain\r\n"); // interleaved with other headers shouldn't matter + request.append("Accept-Charset: iso-8859-5, unicode-1-1;q=0.8\r\n"); + request.append("ACCEPT: */*\r\n"); // case shouldn't matter + request.append("Connection: closed\r\n"); + request.append("\r\n"); + + String rawResponse = connector.getResponse(request.toString()); + HttpTester.Response response = HttpTester.parseResponse(rawResponse); + assertEquals(200, response.getStatus()); + assertThat(response.getContent(), containsString("[Accept]: [*/*]")); + assertThat(response.getContent(), containsString("[Accept-Charset]: [iso-8859-5, unicode-1-1;q=0.8]")); + assertThat(response.getContent(), containsString("[Accept-Encoding]: [gzip;q=1.0, identity; q=0.5, *;q=0]")); + assertThat(response.getContent(), containsString("[Host]: [local]")); + assertThat(response.getContent(), containsString("[Connection]: [closed]")); + } +} diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/CachedContentFactory.java b/jetty-server/src/main/java/org/eclipse/jetty/server/CachedContentFactory.java index ac771356632..2617bee9b68 100644 --- a/jetty-server/src/main/java/org/eclipse/jetty/server/CachedContentFactory.java +++ b/jetty-server/src/main/java/org/eclipse/jetty/server/CachedContentFactory.java @@ -224,7 +224,7 @@ public class CachedContentFactory implements HttpContent.ContentFactory Map precompresssedContents = new HashMap<>(_precompressedFormats.length); for (CompressedContentFormat format : _precompressedFormats) { - String compressedPathInContext = pathInContext + format._extension; + String compressedPathInContext = pathInContext + format.getExtension(); CachedHttpContent compressedContent = _cache.get(compressedPathInContext); if (compressedContent == null || compressedContent.isValid()) { @@ -269,7 +269,7 @@ public class CachedContentFactory implements HttpContent.ContentFactory Map compressedContents = new HashMap<>(); for (CompressedContentFormat format : _precompressedFormats) { - String compressedPathInContext = pathInContext + format._extension; + String compressedPathInContext = pathInContext + format.getExtension(); CachedHttpContent compressedContent = _cache.get(compressedPathInContext); if (compressedContent != null && compressedContent.isValid() && compressedContent.getResource().lastModified() >= resource.lastModified()) compressedContents.put(format, compressedContent); @@ -682,7 +682,7 @@ public class CachedContentFactory implements HttpContent.ContentFactory _content = content; _precompressedContent = precompressedContent; - _etag = (CachedContentFactory.this._etags) ? new PreEncodedHttpField(HttpHeader.ETAG, _content.getResource().getWeakETag(format._etag)) : null; + _etag = (CachedContentFactory.this._etags) ? new PreEncodedHttpField(HttpHeader.ETAG, _content.getResource().getWeakETag(format.getEtagSuffix())) : null; } public boolean isValid() diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/ResourceContentFactory.java b/jetty-server/src/main/java/org/eclipse/jetty/server/ResourceContentFactory.java index a16889dc0ce..13caa139e70 100644 --- a/jetty-server/src/main/java/org/eclipse/jetty/server/ResourceContentFactory.java +++ b/jetty-server/src/main/java/org/eclipse/jetty/server/ResourceContentFactory.java @@ -85,7 +85,7 @@ public class ResourceContentFactory implements ContentFactory Map compressedContents = new HashMap<>(_precompressedFormats.length); for (CompressedContentFormat format : _precompressedFormats) { - String compressedPathInContext = pathInContext + format._extension; + String compressedPathInContext = pathInContext + format.getExtension(); Resource compressedResource = _factory.getResource(compressedPathInContext); if (compressedResource != null && compressedResource.exists() && compressedResource.lastModified() >= resource.lastModified() && compressedResource.length() < resource.length()) diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/ResourceService.java b/jetty-server/src/main/java/org/eclipse/jetty/server/ResourceService.java index cae6a51ffe9..0259755c1ab 100644 --- a/jetty-server/src/main/java/org/eclipse/jetty/server/ResourceService.java +++ b/jetty-server/src/main/java/org/eclipse/jetty/server/ResourceService.java @@ -138,7 +138,7 @@ public class ResourceService public void setPrecompressedFormats(CompressedContentFormat[] precompressedFormats) { _precompressedFormats = precompressedFormats; - _preferredEncodingOrder = stream(_precompressedFormats).map(f -> f._encoding).toArray(String[]::new); + _preferredEncodingOrder = stream(_precompressedFormats).map(f -> f.getEncoding()).toArray(String[]::new); } public void setEncodingCacheSize(int encodingCacheSize) @@ -279,7 +279,7 @@ public class ResourceService if (LOG.isDebugEnabled()) LOG.debug("precompressed={}", precompressedContent); content = precompressedContent; - response.setHeader(HttpHeader.CONTENT_ENCODING.asString(), precompressedContentEncoding._encoding); + response.setHeader(HttpHeader.CONTENT_ENCODING.asString(), precompressedContentEncoding.getEncoding()); } } @@ -352,7 +352,7 @@ public class ResourceService { for (CompressedContentFormat format : availableFormats) { - if (format._encoding.equals(encoding)) + if (format.getEncoding().equals(encoding)) return format; } @@ -526,9 +526,9 @@ public class ResourceService if (etag != null) { QuotedCSV quoted = new QuotedCSV(true, ifm); - for (String tag : quoted) + for (String etagWithSuffix : quoted) { - if (CompressedContentFormat.tagEquals(etag, tag)) + if (CompressedContentFormat.tagEquals(etag, etagWithSuffix)) { match = true; break; 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 1e0dbbedef2..2044cf33723 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 @@ -31,7 +31,6 @@ import org.eclipse.jetty.http.CompressedContentFormat; 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.HttpMethod; import org.eclipse.jetty.http.MimeTypes; import org.eclipse.jetty.http.PreEncodedHttpField; @@ -150,13 +149,13 @@ import org.slf4j.LoggerFactory; public class GzipHandler extends HandlerWrapper implements GzipFactory { public static final EnumSet ETAG_HEADERS = EnumSet.of(HttpHeader.IF_MATCH, HttpHeader.IF_NONE_MATCH); + public static final String GZIP_HANDLER_ETAGS = "o.e.j.s.h.gzip.GzipHandler.etag"; public static final String GZIP = "gzip"; public static final String DEFLATE = "deflate"; public static final int DEFAULT_MIN_GZIP_SIZE = 32; public static final int BREAK_EVEN_GZIP_SIZE = 23; private static final Logger LOG = LoggerFactory.getLogger(GzipHandler.class); private static final HttpField X_CE_GZIP = new PreEncodedHttpField("X-Content-Encoding", "gzip"); - private static final HttpField TE_CHUNKED = new PreEncodedHttpField(HttpHeader.TRANSFER_ENCODING, HttpHeaderValue.CHUNKED.asString()); private static final Pattern COMMA_GZIP = Pattern.compile(".*, *gzip"); private InflaterPool _inflaterPool; @@ -600,23 +599,17 @@ public class GzipHandler extends HandlerWrapper implements GzipFactory case IF_MATCH: case IF_NONE_MATCH: { - String etag = field.getValue(); - int i = etag.indexOf(CompressedContentFormat.GZIP._etagQuote); - if (i <= 0 || alreadyGzipped) + String etags = field.getValue(); + String etagsNoSuffix = CompressedContentFormat.GZIP.stripSuffixes(etags); + if (etagsNoSuffix.equals(etags)) newFields.add(field); else { - baseRequest.setAttribute("o.e.j.s.h.gzip.GzipHandler.etag", etag); - while (i >= 0) - { - etag = etag.substring(0, i) + etag.substring(i + CompressedContentFormat.GZIP._etag.length()); - i = etag.indexOf(CompressedContentFormat.GZIP._etagQuote, i); - } - newFields.add(new HttpField(field.getHeader(), etag)); + newFields.add(new HttpField(field.getHeader(), etagsNoSuffix)); + baseRequest.setAttribute(GZIP_HANDLER_ETAGS, etags); } break; } - case CONTENT_LENGTH: newFields.add(inflated ? new HttpField("X-Content-Length", field.getValue()) : field); break; 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 be33871a989..ab855e9c970 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 @@ -22,6 +22,7 @@ import java.util.zip.Deflater; import org.eclipse.jetty.http.HttpField; import org.eclipse.jetty.http.HttpFields; import org.eclipse.jetty.http.HttpHeader; +import org.eclipse.jetty.http.HttpStatus; import org.eclipse.jetty.http.MimeTypes; import org.eclipse.jetty.http.PreEncodedHttpField; import org.eclipse.jetty.server.HttpChannel; @@ -139,9 +140,9 @@ public class GzipHttpOutputInterceptor implements HttpOutput.Interceptor LOG.debug("{} exclude by status {}", this, sc); noCompression(); - if (sc == 304) + if (sc == HttpStatus.NOT_MODIFIED_304) { - String requestEtags = (String)_channel.getRequest().getAttribute("o.e.j.s.h.gzip.GzipHandler.etag"); + String requestEtags = (String)_channel.getRequest().getAttribute(GzipHandler.GZIP_HANDLER_ETAGS); String responseEtag = response.getHttpFields().get(HttpHeader.ETAG); if (requestEtags != null && responseEtag != null) { @@ -200,7 +201,7 @@ public class GzipHttpOutputInterceptor implements HttpOutput.Interceptor return; } - fields.put(GZIP._contentEncoding); + fields.put(GZIP.getContentEncoding()); _crc.reset(); // Adjust headers @@ -228,8 +229,7 @@ public class GzipHttpOutputInterceptor implements HttpOutput.Interceptor private String etagGzip(String etag) { - int end = etag.length() - 1; - return (etag.charAt(end) == '"') ? etag.substring(0, end) + GZIP._etag + '"' : etag + GZIP._etag; + return GZIP.etag(etag); } public void noCompression() 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 9904028be48..43d3f031f85 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 @@ -83,7 +83,7 @@ public class GzipHandlerTest private static final String __micro = __content.substring(0, 10); private static final String __contentETag = String.format("W/\"%x\"", __content.hashCode()); - private static final String __contentETagGzip = String.format("W/\"%x" + CompressedContentFormat.GZIP._etag + "\"", __content.hashCode()); + private static final String __contentETagGzip = String.format("W/\"%x" + CompressedContentFormat.GZIP.getEtagSuffix() + "\"", __content.hashCode()); private static final String __icontent = "BEFORE" + __content + "AFTER"; private Server _server; @@ -586,7 +586,7 @@ public class GzipHandlerTest request.setURI("/ctx/content"); request.setVersion("HTTP/1.0"); request.setHeader("Host", "tester"); - request.setHeader("If-Match", "WrongEtag" + CompressedContentFormat.GZIP._etag); + request.setHeader("If-Match", "WrongEtag" + CompressedContentFormat.GZIP.getEtagSuffix()); request.setHeader("accept-encoding", "gzip"); response = HttpTester.parseResponse(_connector.getResponse(request.generate())); 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 86c43724c02..a556ab98ccb 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 @@ -118,7 +118,7 @@ public class GzipDefaultServletTest extends AbstractGzipTest // Response Content-Encoding check 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[ETag]", response.get("ETag"), containsString(CompressedContentFormat.GZIP.getEtagSuffix())); assertThat("Response[Content-Length]", response.get("Content-Length"), is(nullValue())); // A HEAD request should have similar headers, but no body @@ -320,7 +320,7 @@ public class GzipDefaultServletTest extends AbstractGzipTest // Response Content-Encoding check 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[ETag]", response.get("ETag"), containsString(CompressedContentFormat.GZIP.getEtagSuffix())); assertThat("Response[Vary]", response.get("Vary"), containsString("Accept-Encoding")); // Response Content checks @@ -439,7 +439,7 @@ public class GzipDefaultServletTest extends AbstractGzipTest // Response Content-Encoding check assertThat("Response[Content-Encoding]", response.get("Content-Encoding"), not(containsString("gzip"))); assertThat("Response[ETag]", response.get("ETag"), startsWith("W/")); - assertThat("Response[ETag]", response.get("ETag"), not(containsString(CompressedContentFormat.GZIP._etag))); + assertThat("Response[ETag]", response.get("ETag"), not(containsString(CompressedContentFormat.GZIP.getEtagSuffix()))); // Response Content checks UncompressedMetadata metadata = parseResponseContent(response);