diff --git a/jetty-http/src/main/java/org/eclipse/jetty/http/HttpGenerator.java b/jetty-http/src/main/java/org/eclipse/jetty/http/HttpGenerator.java index aeba1953818..9b6c83b1fb0 100644 --- a/jetty-http/src/main/java/org/eclipse/jetty/http/HttpGenerator.java +++ b/jetty-http/src/main/java/org/eclipse/jetty/http/HttpGenerator.java @@ -157,6 +157,12 @@ public class HttpGenerator return _endOfContent==EndOfContent.CHUNKED_CONTENT; } + /* ------------------------------------------------------------ */ + public boolean isNoContent() + { + return _noContent; + } + /* ------------------------------------------------------------ */ public void setPersistent(boolean persistent) { diff --git a/jetty-http/src/test/java/org/eclipse/jetty/http/HttpGeneratorServerTest.java b/jetty-http/src/test/java/org/eclipse/jetty/http/HttpGeneratorServerTest.java index 90100154a3e..03992957e46 100644 --- a/jetty-http/src/test/java/org/eclipse/jetty/http/HttpGeneratorServerTest.java +++ b/jetty-http/src/test/java/org/eclipse/jetty/http/HttpGeneratorServerTest.java @@ -72,7 +72,40 @@ public class HttpGeneratorServerTest assertThat(response, containsString("Content-Length: 10")); assertThat(response, containsString("\r\n0123456789")); } + + @Test + public void test204() throws Exception + { + ByteBuffer header = BufferUtil.allocate(8096); + ByteBuffer content = BufferUtil.toBuffer("0123456789"); + HttpGenerator gen = new HttpGenerator(); + + ResponseInfo info = new ResponseInfo(HttpVersion.HTTP_1_1, new HttpFields(), 10, 204, "Foo", false); + info.getHttpFields().add("Content-Type", "test/data"); + info.getHttpFields().add("Last-Modified", DateGenerator.__01Jan1970); + + HttpGenerator.Result result = gen.generateResponse(info, header, null, content, true); + + assertEquals(gen.isNoContent(), true); + assertEquals(HttpGenerator.Result.FLUSH, result); + assertEquals(HttpGenerator.State.COMPLETING, gen.getState()); + String responseheaders = BufferUtil.toString(header); + BufferUtil.clear(header); + + result = gen.generateResponse(null, null, null, content, false); + assertEquals(HttpGenerator.Result.DONE, result); + assertEquals(HttpGenerator.State.END, gen.getState()); + + assertThat(responseheaders, containsString("HTTP/1.1 204 Foo")); + assertThat(responseheaders, containsString("Last-Modified: Thu, 01 Jan 1970 00:00:00 GMT")); + assertThat(responseheaders, not(containsString("Content-Length: 10"))); + + //Note: the HttpConnection.process() method is responsible for actually + //excluding the content from the response based on generator.isNoContent()==true + } + + @Test public void testComplexChars() throws Exception { diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/HttpConnection.java b/jetty-server/src/main/java/org/eclipse/jetty/server/HttpConnection.java index 5be5c31e3ee..bc89093eee0 100644 --- a/jetty-server/src/main/java/org/eclipse/jetty/server/HttpConnection.java +++ b/jetty-server/src/main/java/org/eclipse/jetty/server/HttpConnection.java @@ -537,8 +537,8 @@ public class HttpConnection extends AbstractConnection implements Runnable, Http } case FLUSH: { - // Don't write the chunk or the content if this is a HEAD response - if (_channel.getRequest().isHead()) + // Don't write the chunk or the content if this is a HEAD response, or any other type of response that should have no content + if (_channel.getRequest().isHead() || _generator.isNoContent()) { BufferUtil.clear(chunk); BufferUtil.clear(_content); diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/Request.java b/jetty-server/src/main/java/org/eclipse/jetty/server/Request.java index eb38f250bbc..938937f76ae 100644 --- a/jetty-server/src/main/java/org/eclipse/jetty/server/Request.java +++ b/jetty-server/src/main/java/org/eclipse/jetty/server/Request.java @@ -388,7 +388,7 @@ public class Request implements HttpServletRequest public AsyncContext getAsyncContext() { HttpChannelState state = getHttpChannelState(); - if (_async==null || state.isInitial() && !state.isAsync()) + if (_async==null || !state.isAsyncStarted()) throw new IllegalStateException(state.getStatusString()); return _async; diff --git a/jetty-servlet/src/test/java/org/eclipse/jetty/servlet/AsyncServletTest.java b/jetty-servlet/src/test/java/org/eclipse/jetty/servlet/AsyncServletTest.java index 5a50e834b2b..af7e06add14 100644 --- a/jetty-servlet/src/test/java/org/eclipse/jetty/servlet/AsyncServletTest.java +++ b/jetty-servlet/src/test/java/org/eclipse/jetty/servlet/AsyncServletTest.java @@ -583,6 +583,17 @@ public class AsyncServletTest @Override public void doGet(final HttpServletRequest request, final HttpServletResponse response) throws ServletException, IOException { + // this should always fail at this point + try + { + request.getAsyncContext(); + throw new IllegalStateException(); + } + catch(IllegalStateException e) + { + // ignored + } + // System.err.println(request.getDispatcherType()+" "+request.getRequestURI()); response.addHeader("history",request.getDispatcherType()+" "+request.getRequestURI()); if (request instanceof ServletRequestWrapper || response instanceof ServletResponseWrapper) diff --git a/jetty-servlets/src/main/java/org/eclipse/jetty/servlets/CrossOriginFilter.java b/jetty-servlets/src/main/java/org/eclipse/jetty/servlets/CrossOriginFilter.java index 017b9f1cf66..ff790ade10a 100644 --- a/jetty-servlets/src/main/java/org/eclipse/jetty/servlets/CrossOriginFilter.java +++ b/jetty-servlets/src/main/java/org/eclipse/jetty/servlets/CrossOriginFilter.java @@ -35,6 +35,8 @@ import javax.servlet.ServletResponse; 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.util.log.Log; import org.eclipse.jetty.util.log.Logger; @@ -68,7 +70,7 @@ import org.eclipse.jetty.util.log.Logger; * minutes *
  • allowCredentials, a boolean indicating if the resource allows * requests with credentials. Default value is false
  • - *
  • exposeHeaders, a comma separated list of HTTP headers that + *
  • exposedHeaders, a comma separated list of HTTP headers that * are allowed to be exposed on the client. Default value is the * empty list
  • *
  • chainPreflight, if true preflight requests are chained to their @@ -339,6 +341,9 @@ public class CrossOriginFilter implements Filter private void handleSimpleResponse(HttpServletRequest request, HttpServletResponse response, String origin) { response.setHeader(ACCESS_CONTROL_ALLOW_ORIGIN_HEADER, origin); + //W3C CORS spec http://www.w3.org/TR/cors/#resource-implementation + if (!anyOriginAllowed) + response.addHeader("Vary", ORIGIN_HEADER); if (allowCredentials) response.setHeader(ACCESS_CONTROL_ALLOW_CREDENTIALS_HEADER, "true"); if (!exposedHeaders.isEmpty()) @@ -354,6 +359,9 @@ public class CrossOriginFilter implements Filter if (!headersAllowed) return; response.setHeader(ACCESS_CONTROL_ALLOW_ORIGIN_HEADER, origin); + //W3C CORS spec http://www.w3.org/TR/cors/#resource-implementation + if (!anyOriginAllowed) + response.addHeader("Vary", ORIGIN_HEADER); if (allowCredentials) response.setHeader(ACCESS_CONTROL_ALLOW_CREDENTIALS_HEADER, "true"); if (preflightMaxAge > 0) diff --git a/jetty-servlets/src/test/java/org/eclipse/jetty/servlets/CrossOriginFilterTest.java b/jetty-servlets/src/test/java/org/eclipse/jetty/servlets/CrossOriginFilterTest.java index d1dbca78545..9b1f5e67e0c 100644 --- a/jetty-servlets/src/test/java/org/eclipse/jetty/servlets/CrossOriginFilterTest.java +++ b/jetty-servlets/src/test/java/org/eclipse/jetty/servlets/CrossOriginFilterTest.java @@ -97,6 +97,30 @@ public class CrossOriginFilterTest Assert.assertFalse(response.contains(CrossOriginFilter.ACCESS_CONTROL_ALLOW_CREDENTIALS_HEADER)); Assert.assertTrue(latch.await(1, TimeUnit.SECONDS)); } + + @Test + public void testSimpleRequestWithWildcardOrigin() throws Exception + { + FilterHolder filterHolder = new FilterHolder(new CrossOriginFilter()); + tester.getContext().addFilter(filterHolder, "/*", EnumSet.of(DispatcherType.REQUEST)); + + CountDownLatch latch = new CountDownLatch(1); + tester.getContext().addServlet(new ServletHolder(new ResourceServlet(latch)), "/*"); + String origin = "http://foo.example.com"; + + String request = "" + + "GET / HTTP/1.1\r\n" + + "Host: localhost\r\n" + + "Connection: close\r\n" + + "Origin: "+origin+"\r\n" + + "\r\n"; + String response = tester.getResponses(request); + Assert.assertTrue(response.contains("HTTP/1.1 200")); + Assert.assertTrue(response.contains(CrossOriginFilter.ACCESS_CONTROL_ALLOW_ORIGIN_HEADER)); + Assert.assertTrue(response.contains(CrossOriginFilter.ACCESS_CONTROL_ALLOW_CREDENTIALS_HEADER)); + Assert.assertTrue(!response.contains("Vary")); + Assert.assertTrue(latch.await(1, TimeUnit.SECONDS)); + } @Test public void testSimpleRequestWithMatchingWildcardOrigin() throws Exception @@ -119,6 +143,7 @@ public class CrossOriginFilterTest Assert.assertTrue(response.contains("HTTP/1.1 200")); Assert.assertTrue(response.contains(CrossOriginFilter.ACCESS_CONTROL_ALLOW_ORIGIN_HEADER)); Assert.assertTrue(response.contains(CrossOriginFilter.ACCESS_CONTROL_ALLOW_CREDENTIALS_HEADER)); + Assert.assertTrue(response.contains("Vary")); Assert.assertTrue(latch.await(1, TimeUnit.SECONDS)); } @@ -143,6 +168,7 @@ public class CrossOriginFilterTest Assert.assertTrue(response.contains("HTTP/1.1 200")); Assert.assertTrue(response.contains(CrossOriginFilter.ACCESS_CONTROL_ALLOW_ORIGIN_HEADER)); Assert.assertTrue(response.contains(CrossOriginFilter.ACCESS_CONTROL_ALLOW_CREDENTIALS_HEADER)); + Assert.assertTrue(response.contains("Vary")); Assert.assertTrue(latch.await(1, TimeUnit.SECONDS)); } @@ -167,6 +193,7 @@ public class CrossOriginFilterTest Assert.assertTrue(response.contains("HTTP/1.1 200")); Assert.assertTrue(response.contains(CrossOriginFilter.ACCESS_CONTROL_ALLOW_ORIGIN_HEADER)); Assert.assertTrue(response.contains(CrossOriginFilter.ACCESS_CONTROL_ALLOW_CREDENTIALS_HEADER)); + Assert.assertTrue(response.contains("Vary")); Assert.assertTrue(latch.await(1, TimeUnit.SECONDS)); } @@ -193,6 +220,7 @@ public class CrossOriginFilterTest Assert.assertTrue(response.contains("HTTP/1.1 200")); Assert.assertTrue(response.contains(CrossOriginFilter.ACCESS_CONTROL_ALLOW_ORIGIN_HEADER)); Assert.assertTrue(response.contains(CrossOriginFilter.ACCESS_CONTROL_ALLOW_CREDENTIALS_HEADER)); + Assert.assertTrue(response.contains("Vary")); Assert.assertTrue(latch.await(1, TimeUnit.SECONDS)); }