From 9e8a776c3eff2ba8acbbf2b1735cc01b8c161b2e Mon Sep 17 00:00:00 2001 From: Joakim Erdfelt Date: Mon, 10 Nov 2014 11:22:22 -0700 Subject: 450855 - GZipFilter declaration order can result in MIGHT_COMPRESS + Adding testcase GzipFilterLayeredTest to demonstrate this bug + Some cleanup of Gzip testing behavior to be easier to follow (less reliance on GzipTester utility class) --- .../jetty/servlets/GzipFilterLayeredTest.java | 184 ++++++++++++ .../jetty/servlets/gzip/AsyncManipFilter.java | 103 +++++++ .../eclipse/jetty/servlets/gzip/GzipTester.java | 325 ++++++++++++++------- .../jetty/servlets/gzip/PassThruInputStream.java | 36 +++ .../src/test/resources/jetty-logging.properties | 1 + 5 files changed, 539 insertions(+), 110 deletions(-) create mode 100644 jetty-servlets/src/test/java/org/eclipse/jetty/servlets/GzipFilterLayeredTest.java create mode 100644 jetty-servlets/src/test/java/org/eclipse/jetty/servlets/gzip/AsyncManipFilter.java create mode 100644 jetty-servlets/src/test/java/org/eclipse/jetty/servlets/gzip/PassThruInputStream.java (limited to 'jetty-servlets') diff --git a/jetty-servlets/src/test/java/org/eclipse/jetty/servlets/GzipFilterLayeredTest.java b/jetty-servlets/src/test/java/org/eclipse/jetty/servlets/GzipFilterLayeredTest.java new file mode 100644 index 0000000000..c36ac1a546 --- /dev/null +++ b/jetty-servlets/src/test/java/org/eclipse/jetty/servlets/GzipFilterLayeredTest.java @@ -0,0 +1,184 @@ +// +// ======================================================================== +// Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// 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.servlets; + +import static org.hamcrest.Matchers.*; +import static org.junit.Assert.*; + +import java.io.File; +import java.util.ArrayList; +import java.util.EnumSet; +import java.util.List; +import java.util.concurrent.TimeUnit; + +import javax.servlet.DispatcherType; + +import org.eclipse.jetty.http.HttpStatus; +import org.eclipse.jetty.http.HttpTester; +import org.eclipse.jetty.server.HttpConfiguration; +import org.eclipse.jetty.servlet.FilterHolder; +import org.eclipse.jetty.servlets.gzip.AsyncManipFilter; +import org.eclipse.jetty.servlets.gzip.GzipTester; +import org.eclipse.jetty.servlets.gzip.GzipTester.ContentMetadata; +import org.eclipse.jetty.servlets.gzip.TestServletLengthStreamTypeWrite; +import org.eclipse.jetty.toolchain.test.TestTracker; +import org.eclipse.jetty.toolchain.test.TestingDir; +import org.junit.Ignore; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; +import org.junit.runners.Parameterized.Parameter; +import org.junit.runners.Parameterized.Parameters; + +/** + * Test the GzipFilter support when under several layers of Filters. + */ +@RunWith(Parameterized.class) +public class GzipFilterLayeredTest +{ + @Rule + public final TestTracker tracker = new TestTracker(); + + private static final HttpConfiguration defaultHttp = new HttpConfiguration(); + private static final int LARGE = defaultHttp.getOutputBufferSize() * 8; + private static final int SMALL = defaultHttp.getOutputBufferSize() / 4; + private static final int TINY = AsyncGzipFilter.DEFAULT_MIN_GZIP_SIZE / 2; + private static final boolean EXPECT_COMPRESSED = true; + + @Parameters(name = "{0} bytes - {1} - compressed: {2}") + public static List data() + { + List ret = new ArrayList(); + + ret.add(new Object[] { 0, "empty.txt", !EXPECT_COMPRESSED }); + ret.add(new Object[] { TINY, "file-tiny.txt", !EXPECT_COMPRESSED }); + ret.add(new Object[] { SMALL, "file-small.txt", EXPECT_COMPRESSED }); + ret.add(new Object[] { LARGE, "file-large.txt", EXPECT_COMPRESSED }); + ret.add(new Object[] { LARGE, "file-large.mp3", !EXPECT_COMPRESSED }); + + return ret; + } + + @Parameter(0) + public int fileSize; + @Parameter(1) + public String fileName; + @Parameter(2) + public boolean expectCompressed; + + @Rule + public TestingDir testingdir = new TestingDir(); + + @Test + @Ignore + public void testGzipDosNormal() throws Exception + { + GzipTester tester = new GzipTester(testingdir, GzipFilter.GZIP); + + // Add Gzip Filter first + FilterHolder gzipHolder = new FilterHolder(AsyncGzipFilter.class); + gzipHolder.setAsyncSupported(true); + tester.addFilter(gzipHolder,"*.txt",EnumSet.of(DispatcherType.REQUEST,DispatcherType.ASYNC)); + tester.addFilter(gzipHolder,"*.mp3",EnumSet.of(DispatcherType.REQUEST,DispatcherType.ASYNC)); + gzipHolder.setInitParameter("mimeTypes","text/plain"); + + // Add (DoSFilter-like) manip filter (in chain of Gzip) + FilterHolder manipHolder = new FilterHolder(AsyncManipFilter.class); + manipHolder.setAsyncSupported(true); + tester.addFilter(manipHolder,"/*",EnumSet.of(DispatcherType.REQUEST,DispatcherType.ASYNC)); + + // Add normal content servlet + tester.setContentServlet(TestServletLengthStreamTypeWrite.class); + + try + { + File testFile = tester.prepareServerFile("GzipDosNormal-" + fileName,fileSize); + + tester.start(); + + HttpTester.Response response = tester.issueRequest("GET","/" + testFile.getName(), 2, TimeUnit.SECONDS); + + assertThat("Response status", response.getStatus(), is(HttpStatus.OK_200)); + + if (expectCompressed) + { + // Must be gzip compressed + assertThat("Content-Encoding",response.get("Content-Encoding"),containsString(GzipFilter.GZIP)); + } + + // Uncompressed content Size + ContentMetadata content = tester.getResponseMetadata(response); + assertThat("(Uncompressed) Content Length", content.size, is((long)fileSize)); + } + finally + { + tester.stop(); + } + } + + @Test + public void testDosGzipNormal() throws Exception + { + GzipTester tester = new GzipTester(testingdir, GzipFilter.GZIP); + + // Add (DoSFilter-like) manip filter + FilterHolder manipHolder = new FilterHolder(AsyncManipFilter.class); + manipHolder.setAsyncSupported(true); + tester.addFilter(manipHolder,"/*",EnumSet.of(DispatcherType.REQUEST,DispatcherType.ASYNC)); + + // Add Gzip Filter first (in chain of DosFilter) + FilterHolder gzipHolder = new FilterHolder(AsyncGzipFilter.class); + gzipHolder.setAsyncSupported(true); + tester.addFilter(gzipHolder,"*.txt",EnumSet.of(DispatcherType.REQUEST,DispatcherType.ASYNC)); + tester.addFilter(gzipHolder,"*.mp3",EnumSet.of(DispatcherType.REQUEST,DispatcherType.ASYNC)); + gzipHolder.setInitParameter("mimeTypes","text/plain"); + + // Add normal content servlet + tester.setContentServlet(TestServletLengthStreamTypeWrite.class); + + try + { + File testFile = tester.prepareServerFile("DosGzipNormal-" + fileName,fileSize); + + tester.start(); + + HttpTester.Response response = tester.issueRequest("GET",testFile.getName(),2,TimeUnit.SECONDS); + + assertThat("Response status", response.getStatus(), is(HttpStatus.OK_200)); + + if (expectCompressed) + { + // Must be gzip compressed + assertThat("Content-Encoding",response.get("Content-Encoding"),containsString(GzipFilter.GZIP)); + } else + { + assertThat("Content-Encoding",response.get("Content-Encoding"),not(containsString(GzipFilter.GZIP))); + } + + // Uncompressed content Size + ContentMetadata content = tester.getResponseMetadata(response); + assertThat("(Uncompressed) Content Length", content.size, is((long)fileSize)); + } + finally + { + tester.stop(); + } + } +} diff --git a/jetty-servlets/src/test/java/org/eclipse/jetty/servlets/gzip/AsyncManipFilter.java b/jetty-servlets/src/test/java/org/eclipse/jetty/servlets/gzip/AsyncManipFilter.java new file mode 100644 index 0000000000..1c76234ff9 --- /dev/null +++ b/jetty-servlets/src/test/java/org/eclipse/jetty/servlets/gzip/AsyncManipFilter.java @@ -0,0 +1,103 @@ +// +// ======================================================================== +// Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// 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.servlets.gzip; + +import java.io.IOException; + +import javax.servlet.AsyncContext; +import javax.servlet.AsyncEvent; +import javax.servlet.AsyncListener; +import javax.servlet.Filter; +import javax.servlet.FilterChain; +import javax.servlet.FilterConfig; +import javax.servlet.ServletException; +import javax.servlet.ServletRequest; +import javax.servlet.ServletResponse; + +import org.eclipse.jetty.util.log.Log; +import org.eclipse.jetty.util.log.Logger; + +/** + * Filter that merely manipulates the AsyncContext. + *

+ * The pattern of manipulation is modeled after how DOSFilter behaves. The purpose of this filter is to test arbitrary filter chains that could see unintended + * side-effects of async context manipulation. + */ +public class AsyncManipFilter implements Filter, AsyncListener +{ + private static final Logger LOG = Log.getLogger(AsyncManipFilter.class); + private static final String MANIP_KEY = AsyncManipFilter.class.getName(); + + @Override + public void init(FilterConfig filterConfig) throws ServletException + { + } + + @Override + public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException + { + LOG.debug("doFilter() - {}", chain); + AsyncContext ctx = (AsyncContext)request.getAttribute(MANIP_KEY); + if (ctx == null) + { + LOG.debug("Initial pass through: {}", chain); + ctx = request.startAsync(); + ctx.addListener(this); + ctx.setTimeout(1000); + LOG.debug("AsyncContext: {}", ctx); + request.setAttribute(MANIP_KEY,ctx); + return; + } + else + { + LOG.debug("Second pass through: {}", chain); + chain.doFilter(request,response); + } + } + + @Override + public void destroy() + { + } + + @Override + public void onComplete(AsyncEvent event) throws IOException + { + LOG.debug("onComplete() {}",event); + } + + @Override + public void onTimeout(AsyncEvent event) throws IOException + { + LOG.debug("onTimeout() {}",event.getAsyncContext()); + event.getAsyncContext().dispatch(); + } + + @Override + public void onError(AsyncEvent event) throws IOException + { + LOG.debug("onError()",event.getThrowable()); + } + + @Override + public void onStartAsync(AsyncEvent event) throws IOException + { + LOG.debug("onTimeout() {}",event); + } +} diff --git a/jetty-servlets/src/test/java/org/eclipse/jetty/servlets/gzip/GzipTester.java b/jetty-servlets/src/test/java/org/eclipse/jetty/servlets/gzip/GzipTester.java index ed202b076f..d627c3afaf 100644 --- a/jetty-servlets/src/test/java/org/eclipse/jetty/servlets/gzip/GzipTester.java +++ b/jetty-servlets/src/test/java/org/eclipse/jetty/servlets/gzip/GzipTester.java @@ -18,13 +18,8 @@ package org.eclipse.jetty.servlets.gzip; -import static org.hamcrest.Matchers.containsString; -import static org.hamcrest.Matchers.equalTo; -import static org.hamcrest.Matchers.is; -import static org.hamcrest.Matchers.not; -import static org.hamcrest.Matchers.notNullValue; -import static org.hamcrest.Matchers.nullValue; -import static org.junit.Assert.assertThat; +import static org.hamcrest.Matchers.*; +import static org.junit.Assert.*; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; @@ -37,6 +32,7 @@ import java.security.DigestOutputStream; import java.security.MessageDigest; import java.util.EnumSet; import java.util.Enumeration; +import java.util.concurrent.TimeUnit; import java.util.regex.Matcher; import java.util.regex.Pattern; import java.util.zip.GZIPInputStream; @@ -51,6 +47,7 @@ import javax.servlet.http.HttpServletResponse; import org.eclipse.jetty.http.DateGenerator; import org.eclipse.jetty.http.HttpHeader; import org.eclipse.jetty.http.HttpTester; +import org.eclipse.jetty.http.HttpTester.Response; import org.eclipse.jetty.server.HttpConnectionFactory; import org.eclipse.jetty.servlet.FilterHolder; import org.eclipse.jetty.servlet.ServletHolder; @@ -67,8 +64,20 @@ import org.junit.Assert; public class GzipTester { private static final Logger LOG = Log.getLogger(GzipTester.class); - - private Class gzipFilterClass = GzipFilter.class; + + public static class ContentMetadata + { + public final long size; + public final String sha1; + + public ContentMetadata(long size, String sha1checksum) + { + this.size = size; + this.sha1 = sha1checksum; + } + } + + private Class gzipFilterClass = null; private String encoding = "ISO8859_1"; private String userAgent = null; private final ServletTester tester = new ServletTester();; @@ -80,14 +89,14 @@ public class GzipTester { this.testdir = testingdir; this.compressionType = compressionType; - this.accept=accept; + this.accept = accept; } - + public GzipTester(TestingDir testingdir, String compressionType) { this.testdir = testingdir; this.compressionType = compressionType; - this.accept=compressionType; + this.accept = compressionType; } public int getOutputBufferSize() @@ -95,11 +104,88 @@ public class GzipTester return tester.getConnector().getConnectionFactory(HttpConnectionFactory.class).getHttpConfiguration().getOutputBufferSize(); } + public HttpTester.Response issueRequest(String method, String path) throws Exception + { + return issueRequest(method, path, 2, TimeUnit.SECONDS); + } + + public HttpTester.Response issueRequest(String method, String path, int idleFor, TimeUnit idleUnit) throws Exception + { + HttpTester.Request request = HttpTester.newRequest(); + + request.setMethod(method); + request.setVersion("HTTP/1.1"); + request.setHeader("Host","tester"); + request.setHeader("Accept-Encoding",accept); + request.setHeader("Connection","close"); + + if (this.userAgent != null) + { + request.setHeader("User-Agent",this.userAgent); + } + + request.setURI("/context/" + path); + + // Issue the request + return HttpTester.parseResponse(tester.getResponses(request.generate(),idleFor,idleUnit)); + } + + public ContentMetadata getResponseMetadata(Response response) throws Exception + { + long size = response.getContentBytes().length; + + String contentEncoding = response.get("Content-Encoding"); + + ByteArrayInputStream bais = null; + InputStream in = null; + DigestOutputStream digester = null; + ByteArrayOutputStream uncompressedStream = null; + try + { + MessageDigest digest = MessageDigest.getInstance("SHA1"); + bais = new ByteArrayInputStream(response.getContentBytes()); + + if (contentEncoding == null) + { + LOG.debug("No response content-encoding"); + in = new PassThruInputStream(bais); + } + else if (contentEncoding.contains(GzipFilter.GZIP)) + { + in = new GZIPInputStream(bais); + } + else if (contentEncoding.contains(GzipFilter.DEFLATE)) + { + in = new InflaterInputStream(bais,new Inflater(true)); + } + else + { + assertThat("Unexpected response content-encoding", contentEncoding, isEmptyOrNullString()); + } + + uncompressedStream = new ByteArrayOutputStream((int)size); + + digester = new DigestOutputStream(uncompressedStream,digest); + IO.copy(in,digester); + + byte output[] = uncompressedStream.toByteArray(); + String actualSha1Sum = Hex.asHex(digest.digest()); + return new ContentMetadata(output.length,actualSha1Sum); + } + finally + { + IO.close(digester); + IO.close(in); + IO.close(bais); + IO.close(uncompressedStream); + } + } + public HttpTester.Response assertIsResponseGzipCompressed(String method, String filename) throws Exception { return assertIsResponseGzipCompressed(method,filename,filename,-1); } - + public HttpTester.Response assertIsResponseGzipCompressed(String method, String filename, long ifmodifiedsince) throws Exception { return assertIsResponseGzipCompressed(method,filename,filename,ifmodifiedsince); @@ -109,7 +195,6 @@ public class GzipTester { return assertIsResponseGzipCompressed(method,requestedFilename,serverFilename,-1); } - public HttpTester.Response assertNonStaticContentIsResponseGzipCompressed(String method, String path, String expected) throws Exception { @@ -120,25 +205,25 @@ public class GzipTester request.setVersion("HTTP/1.0"); request.setHeader("Host","tester"); request.setHeader("Accept-Encoding",accept); - + if (this.userAgent != null) - request.setHeader("User-Agent", this.userAgent); + request.setHeader("User-Agent",this.userAgent); request.setURI("/context/" + path); // Issue the request response = HttpTester.parseResponse(tester.getResponses(request.generate())); - + int qindex = compressionType.indexOf(";"); if (qindex < 0) Assert.assertThat("Response.header[Content-Encoding]",response.get("Content-Encoding"),containsString(compressionType)); else - Assert.assertThat("Response.header[Content-Encoding]", response.get("Content-Encoding"),containsString(compressionType.substring(0,qindex))); + Assert.assertThat("Response.header[Content-Encoding]",response.get("Content-Encoding"),containsString(compressionType.substring(0,qindex))); + + ByteArrayInputStream bais = null; + InputStream in = null; + ByteArrayOutputStream out = null; + String actual = null; - ByteArrayInputStream bais = null; - InputStream in = null; - ByteArrayOutputStream out = null; - String actual = null; - try { bais = new ByteArrayInputStream(response.getContentBytes()); @@ -148,7 +233,7 @@ public class GzipTester } else if (compressionType.startsWith(GzipFilter.DEFLATE)) { - in = new InflaterInputStream(bais, new Inflater(true)); + in = new InflaterInputStream(bais,new Inflater(true)); } out = new ByteArrayOutputStream(); IO.copy(in,out); @@ -162,12 +247,12 @@ public class GzipTester IO.close(in); IO.close(bais); } - - + return response; } - - public HttpTester.Response assertIsResponseGzipCompressed(String method, String requestedFilename, String serverFilename, long ifmodifiedsince) throws Exception + + public HttpTester.Response assertIsResponseGzipCompressed(String method, String requestedFilename, String serverFilename, long ifmodifiedsince) + throws Exception { HttpTester.Request request = HttpTester.newRequest(); HttpTester.Response response; @@ -176,39 +261,36 @@ public class GzipTester request.setVersion("HTTP/1.0"); request.setHeader("Host","tester"); request.setHeader("Accept-Encoding",compressionType); - if (ifmodifiedsince>0) + if (ifmodifiedsince > 0) request.setHeader(HttpHeader.IF_MODIFIED_SINCE.asString(),DateGenerator.formatDate(ifmodifiedsince)); if (this.userAgent != null) - request.setHeader("User-Agent", this.userAgent); + request.setHeader("User-Agent",this.userAgent); request.setURI("/context/" + requestedFilename); // Issue the request response = HttpTester.parseResponse(tester.getResponses(request.generate())); - + // Assert the response headers // Assert.assertThat("Response.status",response.getStatus(),is(HttpServletResponse.SC_OK)); - + // Response headers should have either a Transfer-Encoding indicating chunked OR a Content-Length - /* TODO need to check for the 3rd option of EOF content. To do this properly you might need to look at both HTTP/1.1 and HTTP/1.0 requests - String contentLength = response.get("Content-Length"); - String transferEncoding = response.get("Transfer-Encoding"); - - boolean chunked = (transferEncoding != null) && (transferEncoding.indexOf("chunk") >= 0); - if(!chunked) { - Assert.assertThat("Response.header[Content-Length]",contentLength,notNullValue()); - } else { - Assert.assertThat("Response.header[Transfer-Encoding]",transferEncoding,notNullValue()); - } - */ - + /* + * TODO need to check for the 3rd option of EOF content. To do this properly you might need to look at both HTTP/1.1 and HTTP/1.0 requests String + * contentLength = response.get("Content-Length"); String transferEncoding = response.get("Transfer-Encoding"); + * + * boolean chunked = (transferEncoding != null) && (transferEncoding.indexOf("chunk") >= 0); if(!chunked) { + * Assert.assertThat("Response.header[Content-Length]",contentLength,notNullValue()); } else { + * Assert.assertThat("Response.header[Transfer-Encoding]",transferEncoding,notNullValue()); } + */ + int qindex = compressionType.indexOf(";"); if (qindex < 0) Assert.assertThat("Response.header[Content-Encoding]",response.get("Content-Encoding"),containsString(compressionType)); else - Assert.assertThat("Response.header[Content-Encoding]", response.get("Content-Encoding"),containsString(compressionType.substring(0,qindex))); + Assert.assertThat("Response.header[Content-Encoding]",response.get("Content-Encoding"),containsString(compressionType.substring(0,qindex))); Assert.assertThat(response.get("ETag"),Matchers.startsWith("W/")); - + // Assert that the decompressed contents are what we expect. File serverFile = testdir.getFile(serverFilename); String expected = IO.readToString(serverFile); @@ -226,7 +308,7 @@ public class GzipTester } else if (compressionType.startsWith(GzipFilter.DEFLATE)) { - in = new InflaterInputStream(bais, new Inflater(true)); + in = new InflaterInputStream(bais,new Inflater(true)); } out = new ByteArrayOutputStream(); IO.copy(in,out); @@ -240,11 +322,10 @@ public class GzipTester IO.close(in); IO.close(bais); } - + return response; } - public HttpTester.Response assertIsResponseNotModified(String method, String requestedFilename, long ifmodifiedsince) throws Exception { HttpTester.Request request = HttpTester.newRequest(); @@ -254,10 +335,10 @@ public class GzipTester request.setVersion("HTTP/1.0"); request.setHeader("Host","tester"); request.setHeader("Accept-Encoding",compressionType); - if (ifmodifiedsince>0) + if (ifmodifiedsince > 0) request.setHeader(HttpHeader.IF_MODIFIED_SINCE.asString(),DateGenerator.formatDate(ifmodifiedsince)); if (this.userAgent != null) - request.setHeader("User-Agent", this.userAgent); + request.setHeader("User-Agent",this.userAgent); request.setURI("/context/" + requestedFilename); // Issue the request @@ -265,47 +346,45 @@ public class GzipTester Assert.assertThat(response.getStatus(),Matchers.equalTo(304)); Assert.assertThat(response.get("ETag"),Matchers.startsWith("W/")); - + return response; } - + /** * Makes sure that the response contains an unfiltered file contents. *

* This is used to test exclusions and passthroughs in the GzipFilter. *

- * An example is to test that it is possible to configure GzipFilter to not recompress content that shouldn't be - * compressed by the GzipFilter. + * An example is to test that it is possible to configure GzipFilter to not recompress content that shouldn't be compressed by the GzipFilter. * * @param requestedFilename * the filename used to on the GET request,. * @param testResourceSha1Sum - * the sha1sum file that contains the SHA1SUM checksum that will be used to verify that the response - * contents are what is intended. + * the sha1sum file that contains the SHA1SUM checksum that will be used to verify that the response contents are what is intended. * @param expectedContentType */ public void assertIsResponseNotGzipFiltered(String requestedFilename, String testResourceSha1Sum, String expectedContentType) throws Exception { - assertIsResponseNotGzipFiltered(requestedFilename, testResourceSha1Sum, expectedContentType,null); + assertIsResponseNotGzipFiltered(requestedFilename,testResourceSha1Sum,expectedContentType,null); } - + /** * Makes sure that the response contains an unfiltered file contents. *

* This is used to test exclusions and passthroughs in the GzipFilter. *

- * An example is to test that it is possible to configure GzipFilter to not recompress content that shouldn't be - * compressed by the GzipFilter. + * An example is to test that it is possible to configure GzipFilter to not recompress content that shouldn't be compressed by the GzipFilter. * * @param requestedFilename * the filename used to on the GET request,. * @param testResourceSha1Sum - * the sha1sum file that contains the SHA1SUM checksum that will be used to verify that the response - * contents are what is intended. + * the sha1sum file that contains the SHA1SUM checksum that will be used to verify that the response contents are what is intended. * @param expectedContentType - * @param expectedContentEncoding can be non-null in some circumstances, eg when dealing with pre-gzipped .svgz files + * @param expectedContentEncoding + * can be non-null in some circumstances, eg when dealing with pre-gzipped .svgz files */ - public void assertIsResponseNotGzipFiltered(String requestedFilename, String testResourceSha1Sum, String expectedContentType, String expectedContentEncoding) throws Exception + public void assertIsResponseNotGzipFiltered(String requestedFilename, String testResourceSha1Sum, String expectedContentType, String expectedContentEncoding) + throws Exception { HttpTester.Request request = HttpTester.newRequest(); HttpTester.Response response; @@ -315,7 +394,7 @@ public class GzipTester request.setHeader("Host","tester"); request.setHeader("Accept-Encoding",compressionType); if (this.userAgent != null) - request.setHeader("User-Agent", this.userAgent); + request.setHeader("User-Agent",this.userAgent); request.setURI("/context/" + requestedFilename); // Issue the request @@ -328,14 +407,14 @@ public class GzipTester Assert.assertThat(prefix + ".status",response.getStatus(),is(HttpServletResponse.SC_OK)); Assert.assertThat(prefix + ".header[Content-Length]",response.get("Content-Length"),notNullValue()); Assert.assertThat(prefix + ".header[Content-Encoding] (should not be recompressed by GzipFilter)",response.get("Content-Encoding"), - expectedContentEncoding == null? nullValue() : notNullValue()); + expectedContentEncoding == null?nullValue():notNullValue()); if (expectedContentEncoding != null) Assert.assertThat(prefix + ".header[Content-Encoding]",response.get("Content-Encoding"),is(expectedContentEncoding)); Assert.assertThat(prefix + ".header[Content-Type] (should have a Content-Type associated with it)",response.get("Content-Type"),notNullValue()); Assert.assertThat(prefix + ".header[Content-Type]",response.get("Content-Type"),is(expectedContentType)); Assert.assertThat(response.get("ETAG"),Matchers.startsWith("W/")); - + ByteArrayInputStream bais = null; DigestOutputStream digester = null; try @@ -358,7 +437,7 @@ public class GzipTester private void dumpHeaders(String prefix, HttpTester.Message message) { - LOG.debug("dumpHeaders: {}", prefix); + LOG.debug("dumpHeaders: {}",prefix); Enumeration names = message.getFieldNames(); while (names.hasMoreElements()) { @@ -379,21 +458,20 @@ public class GzipTester } /** - * Asserts that the requested filename results in a properly structured GzipFilter response, where the content is - * not compressed, and the content-length is returned appropriately. + * Asserts that the requested filename results in a properly structured GzipFilter response, where the content is not compressed, and the content-length is + * returned appropriately. * * @param filename - * the filename used for the request, and also used to compare the response to the server file, assumes - * that the file is suitable for {@link Assert#assertEquals(Object, Object)} use. (in other words, the - * contents of the file are text) + * the filename used for the request, and also used to compare the response to the server file, assumes that the file is suitable for + * {@link Assert#assertEquals(Object, Object)} use. (in other words, the contents of the file are text) * @param expectedFilesize - * the expected filesize to be specified on the Content-Length portion of the response headers. (note: - * passing -1 will disable the Content-Length assertion) + * the expected filesize to be specified on the Content-Length portion of the response headers. (note: passing -1 will disable the Content-Length + * assertion) * @throws Exception */ public HttpTester.Response assertIsResponseNotGzipCompressed(String method, String filename, int expectedFilesize, int status) throws Exception { - String uri = "/context/"+filename; + String uri = "/context/" + filename; HttpTester.Response response = executeRequest(method,uri); assertResponseHeaders(expectedFilesize,status,response); // Assert that the contents are what we expect. @@ -405,23 +483,23 @@ public class GzipTester String actual = readResponse(response); Assert.assertEquals("Expected response equals actual response",expectedResponse,actual); } - + return response; } - /** - * Asserts that the request results in a properly structured GzipFilter response, where the content is - * not compressed, and the content-length is returned appropriately. + * Asserts that the request results in a properly structured GzipFilter response, where the content is not compressed, and the content-length is returned + * appropriately. * * @param expectedResponse * the expected response body string * @param expectedFilesize - * the expected filesize to be specified on the Content-Length portion of the response headers. (note: - * passing -1 will disable the Content-Length assertion) + * the expected filesize to be specified on the Content-Length portion of the response headers. (note: passing -1 will disable the Content-Length + * assertion) * @throws Exception */ - public void assertIsResponseNotGzipCompressedAndEqualToExpectedString(String method,String expectedResponse, int expectedFilesize, int status) throws Exception + public void assertIsResponseNotGzipCompressedAndEqualToExpectedString(String method, String expectedResponse, int expectedFilesize, int status) + throws Exception { String uri = "/context/"; HttpTester.Response response = executeRequest(method,uri); @@ -432,15 +510,15 @@ public class GzipTester } /** - * Asserts that the request results in a properly structured GzipFilter response, where the content is - * not compressed, and the content-length is returned appropriately. + * Asserts that the request results in a properly structured GzipFilter response, where the content is not compressed, and the content-length is returned + * appropriately. * * @param expectedFilesize - * the expected filesize to be specified on the Content-Length portion of the response headers. (note: - * passing -1 will disable the Content-Length assertion) + * the expected filesize to be specified on the Content-Length portion of the response headers. (note: passing -1 will disable the Content-Length + * assertion) * @throws Exception */ - public void assertIsResponseNotGzipCompressed(String method,int expectedFilesize, int status) throws Exception + public void assertIsResponseNotGzipCompressed(String method, int expectedFilesize, int status) throws Exception { String uri = "/context/"; HttpTester.Response response = executeRequest(method,uri); @@ -454,16 +532,16 @@ public class GzipTester if (expectedFilesize != (-1)) { Assert.assertEquals(expectedFilesize,response.getContentBytes().length); - String cl=response.get("Content-Length"); - if (cl!=null) + String cl = response.get("Content-Length"); + if (cl != null) { int serverLength = Integer.parseInt(response.get("Content-Length")); Assert.assertEquals(serverLength,expectedFilesize); } - - if (status>=200 && status<300) - Assert.assertThat(response.get("ETAG"),Matchers.startsWith("W/")); - + + if (status >= 200 && status < 300) + Assert.assertThat(response.get("ETAG"),Matchers.startsWith("W/")); + } Assert.assertThat("Response.header[Content-Encoding]",response.get("Content-Encoding"),not(containsString(compressionType))); } @@ -478,7 +556,7 @@ public class GzipTester request.setHeader("Host","tester"); request.setHeader("Accept-Encoding",compressionType); if (this.userAgent != null) - request.setHeader("User-Agent", this.userAgent); + request.setHeader("User-Agent",this.userAgent); request.setURI(uri); response = HttpTester.parseResponse(tester.getResponses(request.generate())); @@ -492,11 +570,11 @@ public class GzipTester ByteArrayOutputStream out = null; try { - byte[] content=response.getContentBytes(); - if (content!=null) - actual=new String(response.getContentBytes(),encoding); + byte[] content = response.getContentBytes(); + if (content != null) + actual = new String(response.getContentBytes(),encoding); else - actual=""; + actual = ""; } finally { @@ -506,7 +584,6 @@ public class GzipTester return actual; } - /** * Generate string content of arbitrary length. * @@ -579,7 +656,7 @@ public class GzipTester finally { IO.close(in); - IO.close(fos); + IO.close(fos); } } @@ -611,9 +688,17 @@ public class GzipTester ServletHolder servletHolder = tester.addServlet(servletClass,"/"); servletHolder.setInitParameter("baseDir",testdir.getDir().getAbsolutePath()); servletHolder.setInitParameter("etags","true"); - FilterHolder holder = tester.addFilter(gzipFilterClass,"/*",EnumSet.of(DispatcherType.REQUEST)); - holder.setInitParameter("vary","Accept-Encoding"); - return holder; + + if (gzipFilterClass != null) + { + FilterHolder holder = tester.addFilter(gzipFilterClass,"/*",EnumSet.of(DispatcherType.REQUEST)); + holder.setInitParameter("vary","Accept-Encoding"); + return holder; + } + else + { + return null; + } } public Class getGzipFilterClass() @@ -635,22 +720,41 @@ public class GzipTester { this.userAgent = ua; } - - public void addMimeType (String extension, String mimetype) + + public void addMimeType(String extension, String mimetype) { - this.tester.getContext().getMimeTypes().addMimeMapping(extension, mimetype); + this.tester.getContext().getMimeTypes().addMimeMapping(extension,mimetype); + } + + /** + * Add an arbitrary filter to the test case. + * + * @param holder + * the filter to add + * @param pathSpec + * the path spec for this filter + * @param dispatches + * the set of {@link DispatcherType} to associate with this filter + */ + public void addFilter(FilterHolder holder, String pathSpec, EnumSet dispatches) throws IOException + { + tester.addFilter(holder,pathSpec,dispatches); } public void start() throws Exception { Assert.assertThat("No servlet defined yet. Did you use #setContentServlet()?",tester,notNullValue()); - tester.dump(); + + if (LOG.isDebugEnabled()) + { + tester.dumpStdErr(); + } tester.start(); } public void stop() { - // NOTE: Do not cleanup the testdir. Failures can't be diagnosed if you do that. + // NOTE: Do not cleanup the testdir. Failures can't be diagnosed if you do that. // IO.delete(testdir.getDir()): try { @@ -664,4 +768,5 @@ public class GzipTester e.printStackTrace(System.err); } } + } diff --git a/jetty-servlets/src/test/java/org/eclipse/jetty/servlets/gzip/PassThruInputStream.java b/jetty-servlets/src/test/java/org/eclipse/jetty/servlets/gzip/PassThruInputStream.java new file mode 100644 index 0000000000..3b4af548e3 --- /dev/null +++ b/jetty-servlets/src/test/java/org/eclipse/jetty/servlets/gzip/PassThruInputStream.java @@ -0,0 +1,36 @@ +// +// ======================================================================== +// Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// 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.servlets.gzip; + +import java.io.FilterInputStream; +import java.io.InputStream; + +/** + * A simple pass-through input stream. + *

+ * Used in some test cases where a proper resource open/close is needed for + * some potentially optional layers of the input stream. + */ +public class PassThruInputStream extends FilterInputStream +{ + public PassThruInputStream(InputStream in) + { + super(in); + } +} diff --git a/jetty-servlets/src/test/resources/jetty-logging.properties b/jetty-servlets/src/test/resources/jetty-logging.properties index a280c75884..65026397c6 100644 --- a/jetty-servlets/src/test/resources/jetty-logging.properties +++ b/jetty-servlets/src/test/resources/jetty-logging.properties @@ -1,6 +1,7 @@ org.eclipse.jetty.util.log.class=org.eclipse.jetty.util.log.StdErrLog #org.eclipse.jetty.LEVEL=DEBUG #org.eclipse.jetty.servlets.LEVEL=DEBUG +#org.eclipse.jetty.servlet.ServletTester.LEVEL=DEBUG #org.eclipse.jetty.servlets.GzipFilter.LEVEL=DEBUG #org.eclipse.jetty.servlets.QoSFilter.LEVEL=DEBUG #org.eclipse.jetty.servlets.DoSFilter.LEVEL=DEBUG -- cgit v1.2.3