| // |
| // ======================================================================== |
| // Copyright (c) 1995-2017 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.test.rfcs; |
| |
| import static org.hamcrest.Matchers.containsString; |
| import static org.hamcrest.Matchers.is; |
| import static org.hamcrest.Matchers.nullValue; |
| import static org.junit.Assert.assertEquals; |
| import static org.junit.Assert.assertThat; |
| import static org.junit.Assert.assertTrue; |
| |
| import java.io.File; |
| import java.io.IOException; |
| import java.net.Socket; |
| import java.text.SimpleDateFormat; |
| import java.util.Calendar; |
| import java.util.Date; |
| import java.util.Enumeration; |
| import java.util.List; |
| import java.util.TimeZone; |
| |
| import org.eclipse.jetty.http.HttpFields; |
| import org.eclipse.jetty.http.HttpParser; |
| import org.eclipse.jetty.http.HttpStatus; |
| import org.eclipse.jetty.http.HttpTester; |
| import org.eclipse.jetty.test.support.StringUtil; |
| import org.eclipse.jetty.test.support.TestableJettyServer; |
| import org.eclipse.jetty.test.support.rawhttp.HttpSocket; |
| import org.eclipse.jetty.test.support.rawhttp.HttpTesting; |
| import org.eclipse.jetty.toolchain.test.FS; |
| import org.eclipse.jetty.toolchain.test.MavenTestingUtils; |
| import org.eclipse.jetty.toolchain.test.StringAssert; |
| import org.eclipse.jetty.util.log.StacklessLogging; |
| import org.hamcrest.Matchers; |
| import org.junit.AfterClass; |
| import org.junit.Assert; |
| import org.junit.Before; |
| import org.junit.Ignore; |
| import org.junit.Test; |
| |
| /** |
| * <a href="http://tools.ietf.org/html/rfc2616">RFC 2616</a> (HTTP/1.1) Test Case |
| */ |
| public abstract class RFC2616BaseTest |
| { |
| private static final String ALPHA = "ABCDEFGHIJKLMNOPQRSTUVWXYZ\n"; |
| /** STRICT RFC TESTS */ |
| private static final boolean STRICT = false; |
| private static TestableJettyServer server; |
| private HttpTesting http; |
| |
| class TestFile |
| { |
| String name; |
| String modDate; |
| String data; |
| long length; |
| |
| public TestFile(String name) |
| { |
| this.name = name; |
| // HTTP-Date format - see RFC 2616 section 14.29 |
| SimpleDateFormat sdf = new SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss zzz"); |
| sdf.setTimeZone(TimeZone.getTimeZone("GMT")); |
| this.modDate = sdf.format(new Date()); |
| } |
| |
| public void setData(String data) |
| { |
| this.data = data; |
| this.length = data.length(); |
| } |
| } |
| |
| public static void setUpServer(TestableJettyServer testableserver, Class<?> testclazz) throws Exception |
| { |
| File testWorkDir = MavenTestingUtils.getTargetTestingDir(testclazz.getName()); |
| FS.ensureDirExists(testWorkDir); |
| |
| System.setProperty("java.io.tmpdir",testWorkDir.getAbsolutePath()); |
| |
| server = testableserver; |
| server.load(); |
| server.start(); |
| //server.getServer().dumpStdErr(); |
| } |
| |
| @Before |
| public void setUp() throws Exception |
| { |
| http = new HttpTesting(getHttpClientSocket(),server.getServerPort()); |
| } |
| |
| @AfterClass |
| public static void tearDownServer() throws Exception |
| { |
| server.stop(); |
| } |
| |
| public abstract HttpSocket getHttpClientSocket() throws Exception; |
| |
| /** |
| * Test Date/Time format Specs. |
| * |
| * @see <a href="http://tools.ietf.org/html/rfc2616#section-3.3">RFC 2616 (section 3.3)</a> |
| */ |
| @Test |
| public void test3_3() |
| { |
| Calendar expected = Calendar.getInstance(); |
| expected.set(Calendar.YEAR,1994); |
| expected.set(Calendar.MONTH,Calendar.NOVEMBER); |
| expected.set(Calendar.DAY_OF_MONTH,6); |
| expected.set(Calendar.HOUR_OF_DAY,8); |
| expected.set(Calendar.MINUTE,49); |
| expected.set(Calendar.SECOND,37); |
| expected.set(Calendar.MILLISECOND,0); // Milliseconds is not compared |
| expected.set(Calendar.ZONE_OFFSET,0); // Use GMT+0:00 |
| expected.set(Calendar.DST_OFFSET,0); // No Daylight Savings Offset |
| |
| HttpFields fields = new HttpFields(); |
| |
| // RFC 822 Preferred Format |
| fields.put("D1","Sun, 6 Nov 1994 08:49:37 GMT"); |
| // RFC 822 / RFC 850 Format |
| fields.put("D2","Sunday, 6-Nov-94 08:49:37 GMT"); |
| // RFC 850 / ANSIC C Format |
| fields.put("D3","Sun Nov 6 08:49:37 1994"); |
| |
| // Test parsing |
| assertDate("3.3.1 RFC 822 Preferred",expected,fields.getDateField("D1")); |
| assertDate("3.3.1 RFC 822 / RFC 850",expected,fields.getDateField("D2")); |
| assertDate("3.3.1 RFC 850 / ANSI C",expected,fields.getDateField("D3")); |
| |
| // Test formatting |
| fields.putDateField("Date",expected.getTime().getTime()); |
| Assert.assertEquals("3.3.1 RFC 822 preferred","Sun, 06 Nov 1994 08:49:37 GMT",fields.get("Date")); |
| } |
| |
| /** |
| * Test Transfer Codings |
| * |
| * @see <a href="http://tools.ietf.org/html/rfc2616#section-3.6">RFC 2616 (section 3.6)</a> |
| */ |
| @Test |
| public void test3_6() throws Throwable |
| { |
| // Chunk last |
| StringBuffer req1 = new StringBuffer(); |
| req1.append("GET /tests/R1 HTTP/1.1\n"); |
| req1.append("Host: localhost\n"); |
| req1.append("Transfer-Encoding: chunked,identity\n"); // Invalid Transfer-Encoding |
| req1.append("Content-Type: text/plain\n"); |
| req1.append("Connection: close\n"); |
| req1.append("\r\n"); |
| req1.append("5;\r\n"); |
| req1.append("123\r\n\r\n"); |
| req1.append("0;\r\n\r\n"); |
| |
| HttpTester.Response response = http.request(req1); |
| |
| assertEquals("3.6 Transfer Coding / Bad 400",HttpStatus.BAD_REQUEST_400,response.getStatus()); |
| } |
| |
| /** |
| * Test Transfer Codings |
| * |
| * @see <a href="http://tools.ietf.org/html/rfc2616#section-3.6">RFC 2616 (section 3.6)</a> |
| */ |
| @Test |
| public void test3_6_2() throws Throwable |
| { |
| // Chunked |
| StringBuffer req2 = new StringBuffer(); |
| req2.append("GET /echo/R1 HTTP/1.1\n"); |
| req2.append("Host: localhost\n"); |
| req2.append("Transfer-Encoding: chunked\n"); |
| req2.append("Content-Type: text/plain\n"); |
| req2.append("\n"); |
| req2.append("2;\n"); // 2 chars |
| req2.append("12\n"); |
| req2.append("3;\n"); // 3 chars |
| req2.append("345\n"); |
| req2.append("0;\n\n"); |
| |
| req2.append("GET /echo/R2 HTTP/1.1\n"); |
| req2.append("Host: localhost\n"); |
| req2.append("Transfer-Encoding: chunked\n"); |
| req2.append("Content-Type: text/plain\n"); |
| req2.append("\n"); |
| req2.append("4;\n"); // 4 chars |
| req2.append("6789\n"); |
| req2.append("5;\n"); // 5 chars |
| req2.append("abcde\n"); |
| req2.append("0;\n\n"); // 0 chars |
| |
| req2.append("GET /echo/R3 HTTP/1.1\n"); |
| req2.append("Host: localhost\n"); |
| req2.append("Connection: close\n"); |
| req2.append("\n"); |
| |
| List<HttpTester.Response> responses = http.requests(req2); |
| Assert.assertEquals("Response Count",3,responses.size()); |
| |
| HttpTester.Response response = responses.get(0); // Response 1 |
| assertEquals("3.6.1 Transfer Codings / Response 1 Code", HttpStatus.OK_200, response.getStatus()); |
| assertTrue("3.6.1 Transfer Codings / Chunked String", response.getContent().contains("12345\n")); |
| |
| response = responses.get(1); // Response 2 |
| assertEquals("3.6.1 Transfer Codings / Response 2 Code", HttpStatus.OK_200, response.getStatus()); |
| assertThat("3.6.1 Transfer Codings / Chunked String",response.getContent(),Matchers.containsString("6789abcde\n")); |
| |
| response = responses.get(2); // Response 3 |
| assertEquals("3.6.1 Transfer Codings / Response 3 Code", HttpStatus.OK_200, response.getStatus()); |
| assertEquals("3.6.1 Transfer Codings / No Body","",response.getContent()); |
| } |
| |
| /** |
| * Test Transfer Codings |
| * |
| * @see <a href="http://tools.ietf.org/html/rfc2616#section-3.6">RFC 2616 (section 3.6)</a> |
| */ |
| @Test |
| public void test3_6_3() throws Throwable |
| { |
| // Chunked |
| StringBuffer req3 = new StringBuffer(); |
| req3.append("POST /echo/R1 HTTP/1.1\n"); |
| req3.append("Host: localhost\n"); |
| req3.append("Transfer-Encoding: chunked\n"); |
| req3.append("Content-Type: text/plain\n"); |
| req3.append("\n"); |
| req3.append("3;\n"); // 3 chars |
| req3.append("fgh\n"); |
| req3.append("3;\n"); // 3 chars |
| req3.append("Ijk\n"); |
| req3.append("0;\n\n"); // 0 chars |
| |
| req3.append("POST /echo/R2 HTTP/1.1\n"); |
| req3.append("Host: localhost\n"); |
| req3.append("Transfer-Encoding: chunked\n"); |
| req3.append("Content-Type: text/plain\n"); |
| req3.append("\n"); |
| req3.append("4;\n"); // 4 chars |
| req3.append("lmno\n"); |
| req3.append("5;\n"); // 5 chars |
| req3.append("Pqrst\n"); |
| req3.append("0;\n\n"); // 0 chars |
| |
| req3.append("GET /echo/R3 HTTP/1.1\n"); |
| req3.append("Host: localhost\n"); |
| req3.append("Connection: close\n"); |
| req3.append("\n"); |
| |
| List<HttpTester.Response> responses = http.requests(req3); |
| Assert.assertEquals("Response Count",3,responses.size()); |
| |
| HttpTester.Response response = responses.get(0); // Response 1 |
| assertEquals("3.6.1 Transfer Codings / Response 1 Code", HttpStatus.OK_200, response.getStatus()); |
| assertTrue("3.6.1 Transfer Codings / Chunked String", response.getContent().contains("fghIjk\n")); // Complete R1 string |
| |
| response = responses.get(1); // Response 2 |
| assertEquals("3.6.1 Transfer Codings / Response 2 Code", HttpStatus.OK_200, response.getStatus()); |
| assertTrue("3.6.1 Transfer Codings / Chunked String", response.getContent().contains("lmnoPqrst\n")); // Complete R2 string |
| |
| response = responses.get(2); // Response 3 |
| assertEquals("3.6.1 Transfer Codings / Response 3 Code", HttpStatus.OK_200, response.getStatus()); |
| assertEquals("3.6.1 Transfer Codings / No Body","",response.getContent()); |
| |
| } |
| |
| /** |
| * Test Transfer Codings |
| * |
| * @see <a href="http://tools.ietf.org/html/rfc2616#section-3.6">RFC 2616 (section 3.6)</a> |
| */ |
| @Test |
| public void test3_6_4() throws Throwable |
| { |
| // Chunked and keep alive |
| StringBuffer req4 = new StringBuffer(); |
| req4.append("GET /echo/R1 HTTP/1.1\n"); |
| req4.append("Host: localhost\n"); |
| req4.append("Transfer-Encoding: chunked\n"); |
| req4.append("Content-Type: text/plain\n"); |
| req4.append("Connection: keep-alive\n"); // keep-alive |
| req4.append("\n"); |
| req4.append("3;\n"); // 3 chars |
| req4.append("123\n"); |
| req4.append("3;\n"); // 3 chars |
| req4.append("456\n"); |
| req4.append("0;\n\n"); // 0 chars |
| |
| req4.append("GET /echo/R2 HTTP/1.1\n"); |
| req4.append("Host: localhost\n"); |
| req4.append("Connection: close\n"); // close |
| req4.append("\n"); |
| |
| List<HttpTester.Response> responses = http.requests(req4); |
| Assert.assertEquals("Response Count",2,responses.size()); |
| |
| HttpTester.Response response = responses.get(0); // Response 1 |
| assertEquals("3.6.1 Transfer Codings / Response 1 Code", HttpStatus.OK_200, response.getStatus()); |
| assertTrue("3.6.1 Transfer Codings / Chunked String", response.getContent().contains("123456\n")); // Complete R1 string |
| |
| response = responses.get(1); // Response 2 |
| assertEquals("3.6.1 Transfer Codings / Response 2 Code", HttpStatus.OK_200, response.getStatus()); |
| assertEquals("3.6.1 Transfer Codings / No Body","",response.getContent()); |
| } |
| |
| /** |
| * Test Quality Values |
| * |
| * @see <a href="http://tools.ietf.org/html/rfc2616#section-3.9">RFC 2616 (section 3.9)</a> |
| */ |
| @Test |
| public void test3_9() |
| { |
| HttpFields fields = new HttpFields(); |
| |
| fields.put("Q","bbb;q=0.5,aaa,ccc;q=0.002,d;q=0,e;q=0.0001,ddd;q=0.001,aa2,abb;q=0.7"); |
| Enumeration<String> qualities = fields.getValues("Q",", \t"); |
| List<?> list = HttpFields.qualityList(qualities); |
| Assert.assertEquals("Quality parameters","aaa",HttpFields.valueParameters(list.get(0).toString(),null)); |
| Assert.assertEquals("Quality parameters","aa2",HttpFields.valueParameters(list.get(1).toString(),null)); |
| Assert.assertEquals("Quality parameters","abb",HttpFields.valueParameters(list.get(2).toString(),null)); |
| Assert.assertEquals("Quality parameters","bbb",HttpFields.valueParameters(list.get(3).toString(),null)); |
| Assert.assertEquals("Quality parameters","ccc",HttpFields.valueParameters(list.get(4).toString(),null)); |
| Assert.assertEquals("Quality parameters","ddd",HttpFields.valueParameters(list.get(5).toString(),null)); |
| } |
| |
| /** |
| * Test Message Length |
| * |
| * @see <a href="http://tools.ietf.org/html/rfc2616#section-4.4">RFC 2616 (section 4.4)</a> |
| */ |
| @Test |
| public void test4_4() throws Exception |
| { |
| // 4.4.2 - transfer length is 'chunked' when the 'Transfer-Encoding' header |
| // is provided with a value other than 'identity', unless the |
| // request message is terminated with a 'Connection: close'. |
| |
| StringBuffer req1 = new StringBuffer(); |
| req1.append("GET /echo/R1 HTTP/1.1\n"); |
| req1.append("Host: localhost\n"); |
| req1.append("Transfer-Encoding: identity\n"); |
| req1.append("Content-Type: text/plain\n"); |
| req1.append("Content-Length: 5\n"); |
| req1.append("\n"); |
| req1.append("123\r\n"); |
| |
| req1.append("GET /echo/R2 HTTP/1.1\n"); |
| req1.append("Host: localhost\n"); |
| req1.append("Connection: close\n"); |
| req1.append("\n"); |
| |
| List<HttpTester.Response> responses = http.requests(req1); |
| Assert.assertEquals("Response Count",2,responses.size()); |
| |
| HttpTester.Response response = responses.get(0); |
| assertEquals("4.4.2 Message Length / Response Code", HttpStatus.OK_200, response.getStatus()); |
| assertThat("4.4.2 Message Length / Body",response.getContent(),Matchers.containsString("123\n")); |
| response = responses.get(1); |
| assertEquals("4.4.2 Message Length / Response Code", HttpStatus.OK_200, response.getStatus()); |
| assertEquals("4.4.2 Message Length / No Body", "",response.getContent()); |
| |
| // 4.4.3 - |
| // Client - do not send 'Content-Length' if entity-length |
| // and the transfer-length are different. |
| // Server - ignore 'Content-Length' if 'Transfer-Encoding' is provided. |
| |
| StringBuffer req2 = new StringBuffer(); |
| req2.append("GET /echo/R1 HTTP/1.1\n"); |
| req2.append("Host: localhost\n"); |
| req2.append("Transfer-Encoding: chunked\n"); |
| req2.append("Content-Type: text/plain\n"); |
| req2.append("Content-Length: 100\n"); |
| req2.append("\n"); |
| req2.append("3;\n"); |
| req2.append("123\n"); |
| req2.append("3;\n"); |
| req2.append("456\n"); |
| req2.append("0;\n"); |
| req2.append("\n"); |
| |
| req2.append("GET /echo/R2 HTTP/1.1\n"); |
| req2.append("Host: localhost\n"); |
| req2.append("Connection: close\n"); |
| req2.append("Content-Type: text/plain\n"); |
| req2.append("Content-Length: 6\n"); |
| req2.append("\n"); |
| req2.append("7890AB"); |
| |
| responses = http.requests(req2); |
| Assert.assertEquals("Response Count",2,responses.size()); |
| |
| response = responses.get(0); // response 1 |
| assertEquals("4.4.3 Ignore Content-Length / Response Code", HttpStatus.OK_200, response.getStatus()); |
| assertTrue("4.4.3 Ignore Content-Length / Body", response.getContent().contains("123456\n")); |
| response = responses.get(1); // response 2 |
| assertEquals("4.4.3 Ignore Content-Length / Response Code", HttpStatus.OK_200, response.getStatus()); |
| assertTrue("4.4.3 Ignore Content-Length / Body", response.getContent().contains("7890AB\n")); |
| |
| // 4.4 - Server can request valid Content-Length from client if client |
| // fails to provide a Content-Length. |
| // Server can respond with 400 (Bad Request) or 411 (Length Required). |
| |
| // NOTE: MSIE breaks this rule |
| // TODO: Document which version of MSIE Breaks Rule. |
| // TODO: Document which versions of MSIE pass this rule (if any). |
| if (STRICT) |
| { |
| StringBuffer req3 = new StringBuffer(); |
| req3.append("GET /echo/R2 HTTP/1.1\n"); |
| req3.append("Host: localhost\n"); |
| req3.append("Content-Type: text/plain\n"); |
| req3.append("Connection: close\n"); |
| req3.append("\n"); |
| req3.append("123456"); |
| |
| response = http.request(req3); |
| |
| assertEquals("4.4 Valid Content-Length Required",HttpStatus.LENGTH_REQUIRED_411, response.getStatus()); |
| assertTrue("4.4 Valid Content-Length Required", response.getContent() == null); |
| |
| StringBuffer req4 = new StringBuffer(); |
| req4.append("GET /echo/R2 HTTP/1.0\n"); |
| req4.append("Content-Type: text/plain\n"); |
| req4.append("\n"); |
| req4.append("123456"); |
| |
| response = http.request(req4); |
| |
| assertEquals("4.4 Valid Content-Length Required",HttpStatus.LENGTH_REQUIRED_411, response.getStatus()); |
| assertTrue("4.4 Valid Content-Length Required", response.getContent() == null); |
| } |
| } |
| |
| /** |
| * Test The Resource Identified by a Request |
| * |
| * @see <a href="http://tools.ietf.org/html/rfc2616#section-5.2">RFC 2616 (section 5.2)</a> |
| */ |
| @Test |
| public void test5_2_DefaultHost() throws Exception |
| { |
| // Default Host |
| |
| StringBuffer req1 = new StringBuffer(); |
| req1.append("GET /tests/index.html HTTP/1.1\n"); |
| req1.append("Host: localhost\n"); // default host |
| req1.append("Connection: close\n"); |
| req1.append("\r\n"); |
| |
| HttpTester.Response response = http.request(req1); |
| |
| assertEquals("5.2 Default Host", HttpStatus.OK_200, response.getStatus()); |
| assertThat("5.2 Default Host",response.getContent(),Matchers.containsString("Default DOCRoot")); |
| } |
| |
| /** |
| * Test The Resource Identified by a Request |
| * |
| * @see <a href="http://tools.ietf.org/html/rfc2616#section-5.2">RFC 2616 (section 5.2)</a> |
| */ |
| @Test |
| public void test5_2_VirtualHost() throws Exception |
| { |
| // Virtual Host |
| |
| StringBuffer req2 = new StringBuffer(); |
| req2.append("GET /tests/ HTTP/1.1\n"); |
| req2.append("Host: VirtualHost\n"); // simple virtual host |
| req2.append("Connection: close\n"); |
| req2.append("\r\n"); |
| |
| HttpTester.Response response = http.request(req2); |
| |
| assertEquals("5.2 Virtual Host", HttpStatus.OK_200, response.getStatus()); |
| assertThat("5.2 Virtual Host",response.getContent(),Matchers.containsString("VirtualHost DOCRoot")); |
| } |
| |
| /** |
| * Test The Resource Identified by a Request |
| * |
| * @see <a href="http://tools.ietf.org/html/rfc2616#section-5.2">RFC 2616 (section 5.2)</a> |
| */ |
| @Test |
| public void test5_2_VirtualHostInsensitive() throws Exception |
| { |
| // Virtual Host case insensitive |
| |
| StringBuffer req3 = new StringBuffer(); |
| req3.append("GET /tests/ HTTP/1.1\n"); |
| req3.append("Host: ViRtUalhOst\n"); // mixed case host |
| req3.append("Connection: close\n"); |
| req3.append("\n"); |
| |
| HttpTester.Response response = http.request(req3); |
| |
| assertEquals("5.2 Virtual Host (mixed case)", HttpStatus.OK_200, response.getStatus()); |
| assertThat("5.2 Virtual Host (mixed case)",response.getContent(),Matchers.containsString("VirtualHost DOCRoot")); |
| } |
| |
| /** |
| * Test The Resource Identified by a Request |
| * |
| * @see <a href="http://tools.ietf.org/html/rfc2616#section-5.2">RFC 2616 (section 5.2)</a> |
| */ |
| @Test |
| public void test5_2_NoVirtualHost() throws Exception |
| { |
| // No Virtual Host |
| |
| StringBuffer req4 = new StringBuffer(); |
| req4.append("GET /tests/ HTTP/1.1\n"); |
| req4.append("Connection: close\n"); |
| req4.append("\n"); // no virtual host |
| |
| HttpTester.Response response = http.request(req4); |
| |
| assertEquals("5.2 No Host",HttpStatus.BAD_REQUEST_400,response.getStatus()); |
| assertEquals("5.2 No Host","", response.getContent()); |
| } |
| |
| /** |
| * Test The Resource Identified by a Request |
| * |
| * @see <a href="http://tools.ietf.org/html/rfc2616#section-5.2">RFC 2616 (section 5.2)</a> |
| */ |
| @Test |
| public void test5_2_BadVirtualHost() throws Exception |
| { |
| // Bad Virtual Host |
| |
| StringBuffer req5 = new StringBuffer(); |
| req5.append("GET /tests/ HTTP/1.1\n"); |
| req5.append("Host: bad.eclipse.org\n"); // Bad virtual host |
| req5.append("Connection: close\n"); |
| req5.append("\n"); |
| |
| HttpTester.Response response = http.request(req5); |
| |
| assertEquals("5.2 Bad Host",HttpStatus.OK_200, response.getStatus()); |
| assertThat("5.2 Bad Host",response.getContent(),Matchers.containsString("Default DOCRoot")); // served by default context |
| } |
| |
| /** |
| * Test The Resource Identified by a Request |
| * |
| * @see <a href="http://tools.ietf.org/html/rfc2616#section-5.2">RFC 2616 (section 5.2)</a> |
| */ |
| @Test |
| public void test5_2_VirtualHostAbsoluteURI_Http11_WithoutHostHeader() throws Exception |
| { |
| // Virtual Host as Absolute URI |
| |
| StringBuffer req6 = new StringBuffer(); |
| req6.append("GET http://VirtualHost/tests/ HTTP/1.1\n"); |
| req6.append("Connection: close\n"); |
| req6.append("\n"); |
| |
| HttpTester.Response response = http.request(req6); |
| |
| // No host header should always return a 400 Bad Request by 19.6.1.1 |
| assertEquals("5.2 Virtual Host as AbsoluteURI (No Host Header / HTTP 1.1)",HttpStatus.BAD_REQUEST_400,response.getStatus()); |
| } |
| |
| /** |
| * Test The Resource Identified by a Request |
| * |
| * @see <a href="http://tools.ietf.org/html/rfc2616#section-5.2">RFC 2616 (section 5.2)</a> |
| */ |
| @Test |
| public void test5_2_VirtualHostAbsoluteURI_Http10_WithoutHostHeader() throws Exception |
| { |
| // Virtual Host as Absolute URI |
| |
| StringBuffer req6 = new StringBuffer(); |
| req6.append("GET http://VirtualHost/tests/ HTTP/1.0\n"); |
| req6.append("Connection: close\n"); |
| req6.append("\n"); |
| |
| HttpTester.Response response = http.request(req6); |
| |
| assertEquals("5.2 Virtual Host as AbsoluteURI (No Host Header / HTTP 1.0)",HttpStatus.OK_200, response.getStatus()); |
| assertThat("5.2 Virtual Host as AbsoluteURI (No Host Header / HTTP 1.1)",response.getContent(),Matchers.containsString("VirtualHost DOCRoot")); |
| } |
| |
| /** |
| * Test The Resource Identified by a Request |
| * |
| * @see <a href="http://tools.ietf.org/html/rfc2616#section-5.2">RFC 2616 (section 5.2)</a> |
| */ |
| @Test |
| public void test5_2_VirtualHostAbsoluteURI_WithHostHeader() throws Exception |
| { |
| // Virtual Host as Absolute URI (with Host header) |
| |
| StringBuffer req7 = new StringBuffer(); |
| req7.append("GET http://VirtualHost/tests/ HTTP/1.1\n"); |
| req7.append("Host: localhost\n"); // is ignored (would normally trigger default context) |
| req7.append("Connection: close\n"); |
| req7.append("\n"); |
| |
| HttpTester.Response response = http.request(req7); |
| |
| assertEquals("5.2 Virtual Host as AbsoluteURI (and Host header)", HttpStatus.OK_200, response.getStatus()); |
| // System.err.println(response.getContent()); |
| assertThat("5.2 Virtual Host as AbsoluteURI (and Host header)",response.getContent(),Matchers.containsString("VirtualHost DOCRoot")); |
| } |
| |
| /** |
| * Test Persistent Connections |
| * |
| * @see <a href="http://tools.ietf.org/html/rfc2616#section-8.1">RFC 2616 (section 8.1)</a> |
| */ |
| @Test |
| public void test8_1() throws Exception |
| { |
| StringBuffer req1 = new StringBuffer(); |
| req1.append("GET /tests/R1.txt HTTP/1.1\n"); |
| req1.append("Host: localhost\n"); |
| req1.append("Connection: close\n"); |
| req1.append("\n"); |
| |
| HttpTester.Response response = http.request(req1); |
| |
| assertEquals("8.1 Persistent Connections", HttpStatus.OK_200, response.getStatus()); |
| assertTrue("8.1 Persistent Connections", response.get("Content-Length") != null); |
| assertThat("8.1 Persistent Connections",response.getContent(),Matchers.containsString("Resource=R1")); |
| |
| StringBuffer req2 = new StringBuffer(); |
| req2.append("GET /tests/R1.txt HTTP/1.1\n"); |
| req2.append("Host: localhost\n"); |
| req2.append("\n"); |
| |
| req2.append("GET /tests/R2.txt HTTP/1.1\n"); |
| req2.append("Host: localhost\n"); |
| req2.append("Connection: close\n"); |
| req2.append("\n"); |
| |
| req2.append("GET /tests/R3.txt HTTP/1.1\n"); |
| req2.append("Host: localhost\n"); |
| req2.append("Connection: close\n"); |
| req2.append("\n"); |
| |
| List<HttpTester.Response> responses = http.requests(req2); |
| Assert.assertEquals("Response Count",2,responses.size()); // Should not have a R3 response. |
| |
| response = responses.get(0); // response 1 |
| assertEquals("8.1 Persistent Connections", HttpStatus.OK_200, response.getStatus()); |
| assertTrue("8.1 Persistent Connections",response.get("Content-Length") != null); |
| assertTrue("8.1 Peristent Connections", response.getContent().contains("Resource=R1")); |
| |
| response = responses.get(1); // response 2 |
| assertEquals("8.1.2.2 Persistent Connections / Pipeline", HttpStatus.OK_200, response.getStatus()); |
| assertTrue("8.1.2.2 Persistent Connections / Pipeline", response.get("Content-Length") != null); |
| assertEquals("8.1.2.2 Persistent Connections / Pipeline","close", response.get("Connection")); |
| assertTrue("8.1.2.2 Peristent Connections / Pipeline", response.getContent().contains("Resource=R2")); |
| } |
| |
| /** |
| * Test Message Transmission Requirements -- Bad client behaviour, invalid Expect header. |
| * |
| * @see <a href="http://tools.ietf.org/html/rfc2616#section-8.2">RFC 2616 (section 8.2)</a> |
| */ |
| @Test |
| public void test8_2_ExpectInvalid() throws Exception |
| { |
| // Expect Failure |
| |
| StringBuffer req2 = new StringBuffer(); |
| req2.append("GET /echo/R1 HTTP/1.1\n"); |
| req2.append("Host: localhost\n"); |
| req2.append("Expect: unknown\n"); // Invalid Expect header. |
| req2.append("Content-Type: text/plain\n"); |
| req2.append("Content-Length: 8\n"); |
| req2.append("\n"); |
| req2.append("12345678\n"); |
| |
| HttpTester.Response response = http.request(req2); |
| |
| assertEquals("8.2.3 expect failure",HttpStatus.EXPECTATION_FAILED_417, response.getStatus()); |
| } |
| |
| /** |
| * Test Message Transmission Requirements -- Acceptable bad client behavior, Expect 100 with body content. |
| * |
| * @see <a href="http://tools.ietf.org/html/rfc2616#section-8.2">RFC 2616 (section 8.2)</a> |
| */ |
| @Test |
| public void test8_2_ExpectWithBody() throws Exception |
| { |
| // Expect with body |
| |
| StringBuffer req3 = new StringBuffer(); |
| req3.append("GET /echo/R1 HTTP/1.1\n"); |
| req3.append("Host: localhost\n"); |
| req3.append("Expect: 100-continue\n"); // Valid Expect header. |
| req3.append("Content-Type: text/plain\n"); |
| req3.append("Content-Length: 8\n"); |
| req3.append("Connection: close\n"); |
| req3.append("\n"); |
| req3.append("123456\r\n"); // Body |
| |
| // Should only expect 1 response. |
| // The existence of 2 responses usually means a bad "HTTP/1.1 100" was received. |
| HttpTester.Response response = http.request(req3); |
| |
| assertEquals("8.2.3 expect 100", HttpStatus.OK_200, response.getStatus()); |
| } |
| |
| |
| /** |
| * Test Message Transmission Requirements -- Acceptable bad client behavior, Expect 100 with body content. |
| * @throws Exception failure |
| * |
| * @see <a href="http://tools.ietf.org/html/rfc2616#section-8.2">RFC 2616 (section 8.2)</a> |
| */ |
| @Test |
| public void test8_2_UnexpectWithBody() throws Exception |
| { |
| // Expect with body |
| |
| StringBuffer req3 = new StringBuffer(); |
| req3.append("GET /redirect/R1 HTTP/1.1\n"); |
| req3.append("Host: localhost\n"); |
| req3.append("Expect: 100-continue\n"); // Valid Expect header. |
| req3.append("Content-Type: text/plain\n"); |
| req3.append("Content-Length: 8\n"); |
| req3.append("\n"); |
| req3.append("123456\r\n"); |
| req3.append("GET /echo/R1 HTTP/1.1\n"); |
| req3.append("Host: localhost\n"); |
| req3.append("Content-Type: text/plain\n"); |
| req3.append("Content-Length: 8\n"); |
| req3.append("Connection: close\n"); |
| req3.append("\n"); |
| req3.append("87654321"); // Body |
| |
| List<HttpTester.Response> responses = http.requests(req3); |
| |
| // System.err.println(responses); |
| |
| HttpTester.Response response=responses.get(0); |
| // System.err.println(response); |
| |
| assertEquals("8.2.3 ignored no 100",302, response.getStatus()); |
| assertEquals("close",response.get("Connection")); |
| assertEquals(1,responses.size()); |
| } |
| |
| /** |
| * Test Message Transmission Requirements |
| * |
| * @see <a href="http://tools.ietf.org/html/rfc2616#section-8.2">RFC 2616 (section 8.2)</a> |
| */ |
| @Test |
| public void test8_2_ExpectNormal() throws Exception |
| { |
| // Expect 100 |
| |
| StringBuffer req4 = new StringBuffer(); |
| req4.append("GET /echo/R1 HTTP/1.1\n"); |
| req4.append("Host: localhost\n"); |
| req4.append("Connection: close\n"); |
| req4.append("Expect: 100-continue\n"); // Valid Expect header. |
| req4.append("Content-Type: text/plain\n"); |
| req4.append("Content-Length: 7\n"); |
| req4.append("\n"); // No body |
| |
| Socket sock = http.open(); |
| try |
| { |
| http.send(sock,req4); |
| |
| http.setTimeoutMillis(2000); |
| HttpTester.Response response = http.readAvailable(sock); |
| assertEquals("8.2.3 expect 100",HttpStatus.CONTINUE_100,response.getStatus()); |
| |
| http.send(sock,"654321\n"); // Now send the data |
| response = http.read(sock); |
| |
| assertEquals("8.2.3 expect 100", HttpStatus.OK_200, response.getStatus()); |
| assertThat("8.2.3 expect 100",response.getContent(),Matchers.containsString("654321\n")); |
| } |
| finally |
| { |
| http.close(sock); |
| } |
| } |
| |
| /** |
| * Test OPTIONS (HTTP) method - Server Options |
| * |
| * @see <a href="http://tools.ietf.org/html/rfc2616#section-9.2">RFC 2616 (section 9.2)</a> |
| */ |
| @Test |
| public void test9_2_ServerOptions() throws Exception |
| { |
| // Unsupported in Jetty. |
| // Server can handle many webapps, each with their own set of supported OPTIONS. |
| // Both www.cnn.com and www.apache.org do NOT support this request as well. |
| |
| if (STRICT) |
| { |
| // Server OPTIONS |
| |
| StringBuffer req1 = new StringBuffer(); |
| req1.append("OPTIONS * HTTP/1.1\n"); // Apply to server in general, rather than a specific resource |
| req1.append("Connection: close\n"); |
| req1.append("Host: localhost\n"); |
| req1.append("\n"); |
| |
| HttpTester.Response response = http.request(req1); |
| |
| assertEquals("9.2 OPTIONS", HttpStatus.OK_200, response.getStatus()); |
| assertTrue("9.2 OPTIONS",response.get("Allow") != null); |
| // Header expected ... |
| // Allow: GET, HEAD, POST, PUT, DELETE, MOVE, OPTIONS, TRACE |
| String allow = response.get("Allow"); |
| String expectedMethods[] = |
| { "GET", "HEAD", "POST", "PUT", "DELETE", "MOVE", "OPTIONS", "TRACE" }; |
| for (String expectedMethod : expectedMethods) |
| { |
| assertThat(allow,containsString(expectedMethod)); |
| } |
| assertEquals("9.2 OPTIONS","0", response.get("Content-Length")); // Required if no response body. |
| } |
| } |
| |
| /** |
| * Test OPTIONS (HTTP) method - Resource Options |
| * |
| * @see <a href="http://tools.ietf.org/html/rfc2616#section-9.2">RFC 2616 (section 9.2)</a> |
| */ |
| @Test |
| public void test9_2_ResourceOptions() throws Exception |
| { |
| // Jetty is conditionally compliant. |
| // Possible Bug in the Spec. |
| // The content-length: 0 in the spec is not appropriate if the connection is being closed. |
| |
| // Resource specific OPTIONS |
| StringBuffer req2 = new StringBuffer(); |
| req2.append("OPTIONS /rfc2616-webapp/httpmethods HTTP/1.1\n"); // Apply to specific resource |
| req2.append("Host: localhost\n"); |
| req2.append("\n"); |
| |
| // Test issues 2 requests. first as OPTIONS (not closed), |
| // second as GET (closed), this is to allow the 2 conflicting aspects of the |
| // RFC2616 rules with regards to section 9.2 (OPTIONS) and section 4.4 (Message Length) |
| // to not conflict with each other. |
| |
| req2.append("GET /rfc2616-webapp/httpmethods HTTP/1.1\n"); |
| req2.append("Host: localhost\n"); |
| req2.append("Connection: close\n"); // Close this second request |
| req2.append("\n"); |
| |
| List<HttpTester.Response> responses = http.requests(req2); |
| |
| Assert.assertEquals("Response Count",2,responses.size()); // Should have 2 responses |
| |
| HttpTester.Response response = responses.get(0); // Only interested in first response |
| assertTrue("9.2 OPTIONS", response.get("Allow") != null); |
| // Header expected ... |
| // Allow: GET, HEAD, POST, TRACE, OPTIONS |
| String allow = response.get("Allow"); |
| String expectedMethods[] = |
| { "GET", "HEAD", "POST", "OPTIONS", "TRACE" }; |
| for (String expectedMethod : expectedMethods) |
| { |
| assertThat(allow,containsString(expectedMethod)); |
| } |
| |
| assertEquals("9.2 OPTIONS","0", response.get("Content-Length")); // Required if no response body. |
| } |
| |
| /** |
| * Test HEAD (HTTP) method |
| * |
| * @see <a href="http://tools.ietf.org/html/rfc2616#section-9.4">RFC 2616 (section 9.4)</a> |
| */ |
| @Test |
| public void test9_4() throws Exception |
| { |
| /* Test GET first. (should have body) */ |
| |
| StringBuffer req1 = new StringBuffer(); |
| req1.append("GET /tests/R1.txt HTTP/1.1\n"); |
| req1.append("Host: localhost\n"); |
| req1.append("Connection: close\n"); |
| req1.append("\n"); |
| |
| HttpTester.Response response = http.request(req1); |
| |
| assertEquals("9.4 GET / Response Code", HttpStatus.OK_200, response.getStatus()); |
| assertEquals("9.4 GET / Content Type","text/plain", response.get("Content-Type")); |
| assertEquals("9.4 HEAD / Content Type","25", response.get("Content-Length")); |
| assertTrue("9.4 GET / Body", response.getContent().contains("Host=Default\nResource=R1\n")); |
| |
| /* Test HEAD next. (should have no body) */ |
| |
| StringBuffer req2 = new StringBuffer(); |
| req2.append("HEAD /tests/R1.txt HTTP/1.1\n"); |
| req2.append("Host: localhost\n"); |
| req2.append("Connection: close\n"); |
| req2.append("\n"); |
| |
| // Need to get the HEAD response in a RAW format, as HttpParser.parse() |
| // can't properly parse a HEAD response. |
| Socket sock = http.open(); |
| try |
| { |
| http.send(sock,req2); |
| |
| String rawHeadResponse = http.readRaw(sock); |
| int headResponseLength = rawHeadResponse.length(); |
| // Only interested in the response header from the GET request above. |
| String rawGetResponse = response.toString().substring(0,headResponseLength); |
| |
| // As there is a possibility that the time between GET and HEAD requests |
| // can cross the second mark. (eg: GET at 11:00:00.999 and HEAD at 11:00:01.001) |
| // So with that knowledge, we will remove the 'Date:' header from both sides before comparing. |
| List<String> linesGet = StringUtil.asLines(rawGetResponse.trim()); |
| List<String> linesHead = StringUtil.asLines(rawHeadResponse.trim()); |
| |
| StringUtil.removeStartsWith("Date: ",linesGet); |
| StringUtil.removeStartsWith("Date: ",linesHead); |
| |
| // Compare the 2 lists of lines to make sure they contain the same information |
| // Do not worry about order of the headers, as that's not important to test, |
| // just the existence of the same headers |
| StringAssert.assertContainsSame("9.4 HEAD equals GET",linesGet,linesHead); |
| } |
| finally |
| { |
| http.close(sock); |
| } |
| } |
| |
| /** |
| * Test TRACE (HTTP) method |
| * |
| * @see <a href="http://tools.ietf.org/html/rfc2616#section-9.8">RFC 2616 (section 9.8)</a> |
| */ |
| @Test |
| @Ignore("Introduction of fix for realm-less security constraints has rendered this test invalid due to default configuration preventing use of TRACE in webdefault.xml") |
| public void test9_8() throws Exception |
| { |
| |
| StringBuffer req1 = new StringBuffer(); |
| req1.append("TRACE /rfc2616-webapp/httpmethods HTTP/1.1\n"); |
| req1.append("Host: localhost\n"); |
| req1.append("Connection: close\n"); |
| req1.append("\n"); |
| |
| HttpTester.Response response = http.request(req1); |
| |
| assertEquals("9.8 TRACE / Response Code", HttpStatus.OK_200, response.getStatus()); |
| assertEquals("9.8 TRACE / Content Type", "message/http", response.get("Content-Type")); |
| assertTrue("9.8 TRACE / echo", response.getContent().contains("TRACE /rfc2616-webapp/httpmethods HTTP/1.1")); |
| assertTrue("9.8 TRACE / echo", response.getContent().contains("Host: localhost")); |
| } |
| |
| /** |
| * Test 206 Partial Content (Response Code) |
| * |
| * @see <a href="http://tools.ietf.org/html/rfc2616#section-10.2.7">RFC 2616 (section 10.2.7)</a> |
| */ |
| @Test |
| public void test10_2_7() throws Exception |
| { |
| // check to see if corresponding GET w/o range would return |
| // a) ETag |
| // b) Content-Location |
| // these same headers will be required for corresponding |
| // sub range requests |
| |
| StringBuffer req1 = new StringBuffer(); |
| req1.append("GET /rfc2616-webapp/alpha.txt HTTP/1.1\n"); |
| req1.append("Host: localhost\n"); |
| req1.append("Connection: close\n"); |
| req1.append("\n"); |
| |
| HttpTester.Response response = http.request(req1); |
| |
| boolean noRangeHasContentLocation = (response.get("Content-Location") != null); |
| boolean noRangeHasETag = (response.get("ETag") != null); |
| |
| // now try again for the same resource but this time WITH range header |
| |
| StringBuffer req2 = new StringBuffer(); |
| req2.append("GET /rfc2616-webapp/alpha.txt HTTP/1.1\n"); |
| req2.append("Host: localhost\n"); |
| req2.append("Connection: close\n"); |
| req2.append("Range: bytes=1-3\n"); // request first 3 bytes |
| req2.append("\n"); |
| |
| response = http.request(req2); |
| |
| // System.err.println(response); |
| |
| assertEquals("10.2.7 Partial Content",HttpStatus.PARTIAL_CONTENT_206, response.getStatus()); |
| |
| // (point 1) A 206 response MUST contain either a Content-Range header |
| // field (section 14.16) indicating the range included with this |
| // response, or a multipart/byteranges Content-Type including Content-Range |
| // fields for each part. If a Content-Length header field is present |
| // in the response, its value MUST match the actual number of OCTETs |
| // transmitted in the message-body. |
| |
| if (response.get("Content-Range") != null) |
| { |
| assertEquals("10.2.7 Partial Content / Response / Content Range","bytes 1-3/27",response.get("Content-Range")); |
| } |
| |
| if (response.get("Content-Length") != null) |
| { |
| assertEquals("10.2.7 Patial Content / Response / Content Length","3", response.get("Content-Length")); |
| } |
| |
| // (point 2) A 206 response MUST contain a Date header |
| assertTrue("10.2.7 Partial Content / Response / Date", response.get("Date") != null); |
| |
| // (point 3) A 206 response MUST contain ETag and/or Content-Location, |
| // if the header would have been sent in a 200 response to the same request |
| if (noRangeHasContentLocation) |
| { |
| assertTrue("10.2.7 Partial Content / Content-Location", response.get("Content-Location") != null); |
| } |
| if (noRangeHasETag) |
| { |
| assertTrue("10.2.7 Partial Content / Content-Location", response.get("ETag") != null); |
| } |
| |
| // (point 4) A 206 response MUST contain Expires, Cache-Control, and/or Vary, |
| // if the field-value might differ from that sent in any previous response |
| // for the same variant |
| |
| // TODO: Not sure how to test this condition. |
| |
| // Test the body sent |
| assertThat("10.2.7 Partial Content",response.getContent(),Matchers.containsString("BCD")); // should only have bytes 1-3 |
| } |
| |
| /** |
| * Test Redirection 3xx (Response Code) |
| * |
| * @see <a href="http://tools.ietf.org/html/rfc2616#section-10.3">RFC 2616 (section 10.3)</a> |
| */ |
| @Test |
| public void test10_3_RedirectHttp10Path() throws Exception |
| { |
| String specId; |
| |
| String serverURI = server.getServerURI().toASCIIString(); |
| |
| // HTTP/1.0 |
| |
| StringBuffer req1 = new StringBuffer(); |
| req1.append("GET /redirect/ HTTP/1.0\n"); |
| req1.append("Connection: Close\n"); |
| req1.append("\n"); |
| |
| HttpTester.Response response = http.request(req1); |
| |
| specId = "10.3 Redirection HTTP/1.0 - basic"; |
| assertEquals(specId,HttpStatus.FOUND_302, response.getStatus()); |
| assertEquals(specId,serverURI + "/tests/", response.get("Location")); |
| } |
| |
| /** |
| * Test Redirection 3xx (Response Code) |
| * |
| * @see <a href="http://tools.ietf.org/html/rfc2616#section-10.3">RFC 2616 (section 10.3)</a> |
| */ |
| @Test |
| public void test10_3_RedirectHttp11Path() throws Exception |
| { |
| // HTTP/1.1 |
| |
| StringBuffer req2 = new StringBuffer(); |
| req2.append("GET /redirect/ HTTP/1.1\n"); |
| req2.append("Host: localhost\n"); |
| req2.append("\n"); |
| |
| req2.append("GET /redirect/ HTTP/1.1\n"); |
| req2.append("Host: localhost\n"); |
| req2.append("Connection: close\n"); |
| req2.append("\n"); |
| |
| List<HttpTester.Response> responses = http.requests(req2); |
| Assert.assertEquals("Response Count",2,responses.size()); |
| |
| HttpTester.Response response = responses.get(0); |
| String specId = "10.3 Redirection HTTP/1.1 - basic (response 1)"; |
| assertEquals(specId,HttpStatus.FOUND_302, response.getStatus()); |
| assertEquals(specId,server.getScheme() + "://localhost/tests/", response.get("Location")); |
| |
| response = responses.get(1); |
| specId = "10.3 Redirection HTTP/1.1 - basic (response 2)"; |
| assertEquals(specId,HttpStatus.FOUND_302, response.getStatus()); |
| assertEquals(specId,server.getScheme() + "://localhost/tests/", response.get("Location")); |
| assertEquals(specId,"close", response.get("Connection")); |
| } |
| |
| /** |
| * Test Redirection 3xx (Response Code) |
| * |
| * @see <a href="http://tools.ietf.org/html/rfc2616#section-10.3">RFC 2616 (section 10.3)</a> |
| */ |
| @Test |
| public void test10_3_RedirectHttp10Resource() throws Exception |
| { |
| // HTTP/1.0 - redirect with resource/content |
| |
| StringBuffer req3 = new StringBuffer(); |
| req3.append("GET /redirect/R1.txt HTTP/1.0\n"); |
| req3.append("Host: localhost\n"); |
| req3.append("Connection: close\n"); |
| req3.append("\n"); |
| |
| HttpTester.Response response = http.request(req3); |
| |
| String specId = "10.3 Redirection HTTP/1.0 w/content"; |
| assertEquals(specId,HttpStatus.FOUND_302, response.getStatus()); |
| assertEquals(specId,server.getScheme() + "://localhost/tests/R1.txt", response.get("Location")); |
| } |
| |
| /** |
| * Test Redirection 3xx (Response Code) |
| * |
| * @see <a href="http://tools.ietf.org/html/rfc2616#section-10.3">RFC 2616 (section 10.3)</a> |
| */ |
| @Test |
| public void test10_3_RedirectHttp11Resource() throws Exception |
| { |
| // HTTP/1.1 - redirect with resource/content |
| |
| StringBuffer req4 = new StringBuffer(); |
| req4.append("GET /redirect/R2.txt HTTP/1.1\n"); |
| req4.append("Host: localhost\n"); |
| req4.append("Connection: close\n"); |
| req4.append("\n"); |
| |
| HttpTester.Response response = http.request(req4); |
| |
| String specId = "10.3 Redirection HTTP/1.1 w/content"; |
| assertThat(specId + " [status]",response.getStatus(),is(HttpStatus.FOUND_302)); |
| assertThat(specId + " [location]",response.get("Location"),is(server.getScheme() + "://localhost/tests/R2.txt")); |
| assertThat(specId + " [connection]",response.get("Connection"),is("close")); |
| assertThat(specId + " [content-length]",response.get("Content-Length"), nullValue()); |
| } |
| |
| /** |
| * Test Accept-Encoding (Header Field) |
| * |
| * @see <a href="http://tools.ietf.org/html/rfc2616#section-14.3">RFC 2616 (section 14.3)</a> |
| */ |
| @Test |
| public void test14_3_AcceptEncodingGzip() throws Exception |
| { |
| String specId; |
| |
| // Gzip accepted |
| |
| StringBuffer req1 = new StringBuffer(); |
| req1.append("GET /rfc2616-webapp/solutions.html HTTP/1.1\n"); |
| req1.append("Host: localhost\n"); |
| req1.append("Accept-Encoding: gzip\n"); |
| req1.append("Connection: close\n"); |
| req1.append("\n"); |
| |
| HttpTester.Response response = http.request(req1); |
| specId = "14.3 Accept-Encoding Header"; |
| assertEquals(specId, HttpStatus.OK_200, response.getStatus()); |
| assertEquals(specId,"gzip", response.get("Content-Encoding")); |
| assertEquals(specId,"text/html", response.get("Content-Type")); |
| } |
| |
| /** |
| * Test Content-Range (Header Field) |
| * |
| * @see <a href="http://tools.ietf.org/html/rfc2616#section-14.16">RFC 2616 (section 14.16)</a> |
| */ |
| @Test |
| public void test14_16_NoRange() throws Exception |
| { |
| // |
| // calibrate with normal request (no ranges); if this doesnt |
| // work, dont expect ranges to work either |
| // |
| |
| StringBuffer req1 = new StringBuffer(); |
| req1.append("GET /rfc2616-webapp/alpha.txt HTTP/1.1\n"); |
| req1.append("Host: localhost\n"); |
| req1.append("Connection: close\n"); |
| req1.append("\n"); |
| |
| HttpTester.Response response = http.request(req1); |
| |
| assertEquals(HttpStatus.OK_200, response.getStatus()); |
| assertTrue(response.getContent().contains(ALPHA)); |
| } |
| |
| |
| private void assertPartialContentRange(String rangedef, String expectedRange, String expectedBody) throws IOException |
| { |
| // server should ignore all range headers which include |
| // at least one syntactically invalid range |
| |
| StringBuffer req1 = new StringBuffer(); |
| req1.append("GET /rfc2616-webapp/alpha.txt HTTP/1.1\n"); |
| req1.append("Host: localhost\n"); |
| req1.append("Range: ").append(rangedef).append("\n"); // Invalid range |
| req1.append("Connection: close\n"); |
| req1.append("\n"); |
| |
| HttpTester.Response response = http.request(req1); |
| |
| String msg = "Partial Range: '" + rangedef + "'"; |
| assertEquals(msg,HttpStatus.PARTIAL_CONTENT_206, response.getStatus()); |
| assertEquals(msg,"bytes " + expectedRange, response.get("Content-Range")); |
| assertTrue(msg,response.getContent().contains(expectedBody)); |
| } |
| |
| /** |
| * Test Content-Range (Header Field) - Tests multiple ranges, where all defined ranges are syntactically valid, however some ranges are outside of the |
| * limits of the available data |
| * |
| * @see <a href="http://tools.ietf.org/html/rfc2616#section-14.16">RFC 2616 (section 14.16)</a> |
| */ |
| @Test |
| public void test14_16_PartialRange() throws Exception |
| { |
| String alpha = ALPHA; |
| |
| // server should not return a 416 if at least one syntactically valid ranges |
| // are is satisfiable |
| |
| assertPartialContentRange("bytes=5-8,50-60","5-8/27",alpha.substring(5,8 + 1)); |
| assertPartialContentRange("bytes=50-60,5-8","5-8/27",alpha.substring(5,8 + 1)); |
| } |
| |
| /** |
| * Test Content-Range (Header Field) - Tests single Range request header with 2 ranges defined, where there is a mixed case of validity, 1 range invalid, |
| * another 1 valid. |
| * |
| * Only the valid range should be processed. The invalid range should be ignored. |
| * |
| * @see <a href="http://tools.ietf.org/html/rfc2616#section-14.16">RFC 2616 (section 14.16)</a> |
| */ |
| @Test |
| public void test14_16_PartialRange_MixedRanges() throws Exception |
| { |
| String alpha = ALPHA; |
| |
| // server should not return a 416 if at least one syntactically valid ranges |
| // are is satisfiable |
| // |
| // should test for combinations of good and syntactically |
| // invalid ranges here, but I am not certain what the right |
| // behavior is anymore |
| // |
| // return data for valid ranges while ignoring unsatisfiable |
| // ranges |
| |
| // a) Range: bytes=a-b,5-8 |
| |
| StringBuffer req1 = new StringBuffer(); |
| req1.append("GET /rfc2616-webapp/alpha.txt HTTP/1.1\n"); |
| req1.append("Host: localhost\n"); |
| req1.append("Range: bytes=a-b,5-8\n"); // Invalid range, then Valid range |
| req1.append("Connection: close\n"); |
| req1.append("\n"); |
| |
| http.setTimeoutMillis(60000); |
| HttpTester.Response response = http.request(req1); |
| |
| String msg = "Partial Range (Mixed): 'bytes=a-b,5-8'"; |
| assertEquals(msg,HttpStatus.PARTIAL_CONTENT_206, response.getStatus()); |
| assertEquals(msg,"bytes 5-8/27", response.get("Content-Range")); |
| assertTrue(msg,response.getContent().contains(alpha.substring(5,8 + 1))); |
| } |
| |
| /** |
| * Test Content-Range (Header Field) - Tests single Range request header with 2 ranges defined, where there is a mixed case of validity, 1 range invalid, |
| * another 1 valid. |
| * |
| * Only the valid range should be processed. The invalid range should be ignored. |
| * |
| * @see <a href="http://tools.ietf.org/html/rfc2616#section-14.16">RFC 2616 (section 14.16)</a> |
| */ |
| @Test |
| public void test14_16_PartialRange_MixedBytes() throws Exception |
| { |
| String alpha = ALPHA; |
| |
| // server should not return a 416 if at least one syntactically valid ranges |
| // are is satisfiable |
| // |
| // should test for combinations of good and syntactically |
| // invalid ranges here, but I am not certain what the right |
| // behavior is anymore |
| // |
| // return data for valid ranges while ignoring unsatisfiable |
| // ranges |
| |
| // b) Range: bytes=a-b,bytes=5-8 |
| |
| StringBuffer req1 = new StringBuffer(); |
| req1.append("GET /rfc2616-webapp/alpha.txt HTTP/1.1\n"); |
| req1.append("Host: localhost\n"); |
| req1.append("Range: bytes=a-b,bytes=5-8\n"); // Invalid range, then Valid range |
| req1.append("Connection: close\n"); |
| req1.append("\n"); |
| |
| HttpTester.Response response = http.request(req1); |
| |
| String msg = "Partial Range (Mixed): 'bytes=a-b,bytes=5-8'"; |
| assertEquals(msg,HttpStatus.PARTIAL_CONTENT_206, response.getStatus()); |
| assertEquals(msg,"bytes 5-8/27", response.get("Content-Range")); |
| assertTrue(msg,response.getContent().contains(alpha.substring(5,8 + 1))); |
| } |
| |
| /** |
| * Test Content-Range (Header Field) - Tests multiple Range request headers, where there is a mixed case of validity, 1 range invalid, another 1 valid. |
| * |
| * Only the valid range should be processed. The invalid range should be ignored. |
| * |
| * @see <a href="http://tools.ietf.org/html/rfc2616#section-14.16">RFC 2616 (section 14.16)</a> |
| */ |
| @Test |
| public void test14_16_PartialRange_MixedMultiple() throws Exception |
| { |
| String alpha = ALPHA; |
| |
| // server should not return a 416 if at least one syntactically valid ranges |
| // are is satisfiable |
| // |
| // should test for combinations of good and syntactically |
| // invalid ranges here, but I am not certain what the right |
| // behavior is anymore |
| // |
| // return data for valid ranges while ignoring unsatisfiable |
| // ranges |
| |
| // c) Range: bytes=a-b |
| // Range: bytes=5-8 |
| |
| StringBuffer req1 = new StringBuffer(); |
| req1.append("GET /rfc2616-webapp/alpha.txt HTTP/1.1\n"); |
| req1.append("Host: localhost\n"); |
| req1.append("Range: bytes=a-b\n"); // Invalid range |
| req1.append("Range: bytes=5-8\n"); // Valid range |
| req1.append("Connection: close\n"); |
| req1.append("\n"); |
| |
| HttpTester.Response response = http.request(req1); |
| |
| String msg = "Partial Range (Mixed): 'bytes=a-b' 'bytes=5-8'"; |
| assertEquals(msg,HttpStatus.PARTIAL_CONTENT_206, response.getStatus()); |
| assertEquals(msg,"bytes 5-8/27", response.get("Content-Range")); |
| assertTrue(msg,response.getContent().contains(alpha.substring(5,8 + 1))); |
| } |
| |
| /** |
| * Test Host (Header Field) |
| * |
| * @see <a href="http://tools.ietf.org/html/rfc2616#section-14.23">RFC 2616 (section 14.23)</a> |
| */ |
| @Test |
| public void test14_23_Http10_NoHostHeader() throws Exception |
| { |
| // HTTP/1.0 OK with no host |
| |
| StringBuffer req1 = new StringBuffer(); |
| req1.append("GET /tests/R1.txt HTTP/1.0\n"); |
| req1.append("Connection: close\n"); |
| req1.append("\n"); |
| |
| HttpTester.Response response = http.request(req1); |
| assertEquals("14.23 HTTP/1.0 - No Host", HttpStatus.OK_200, response.getStatus()); |
| } |
| |
| /** |
| * Test Host (Header Field) |
| * |
| * @see <a href="http://tools.ietf.org/html/rfc2616#section-14.23">RFC 2616 (section 14.23)</a> |
| */ |
| @Test |
| public void test14_23_Http11_NoHost() throws Exception |
| { |
| // HTTP/1.1 400 (bad request) with no host |
| |
| StringBuffer req2 = new StringBuffer(); |
| req2.append("GET /tests/R1.txt HTTP/1.1\n"); |
| req2.append("Connection: close\n"); |
| req2.append("\n"); |
| |
| HttpTester.Response response = http.request(req2); |
| assertEquals("14.23 HTTP/1.1 - No Host",HttpStatus.BAD_REQUEST_400, response.getStatus()); |
| } |
| |
| /** |
| * Test Host (Header Field) |
| * |
| * @see <a href="http://tools.ietf.org/html/rfc2616#section-14.23">RFC 2616 (section 14.23)</a> |
| */ |
| @Test |
| public void test14_23_ValidHost() throws Exception |
| { |
| // HTTP/1.1 - Valid host |
| |
| StringBuffer req3 = new StringBuffer(); |
| req3.append("GET /tests/R1.txt HTTP/1.1\n"); |
| req3.append("Host: localhost\n"); |
| req3.append("Connection: close\n"); |
| req3.append("\n"); |
| |
| HttpTester.Response response = http.request(req3); |
| assertEquals("14.23 HTTP/1.1 - Valid Host", HttpStatus.OK_200, response.getStatus()); |
| } |
| |
| /** |
| * Test Host (Header Field) |
| * |
| * @see <a href="http://tools.ietf.org/html/rfc2616#section-14.23">RFC 2616 (section 14.23)</a> |
| */ |
| @Test |
| public void test14_23_IncompleteHostHeader() throws Exception |
| { |
| // HTTP/1.1 - Incomplete (empty) Host header |
| try (StacklessLogging stackless = new StacklessLogging(HttpParser.class)) |
| { |
| StringBuffer req4 = new StringBuffer(); |
| req4.append("GET /tests/R1.txt HTTP/1.1\n"); |
| req4.append("Host:\n"); |
| req4.append("Connection: close\n"); |
| req4.append("\n"); |
| |
| HttpTester.Response response = http.request(req4); |
| assertEquals("14.23 HTTP/1.1 - Empty Host", HttpStatus.OK_200, response.getStatus()); |
| } |
| } |
| |
| /** |
| * Tests the (byte) "Range" header for partial content. |
| * |
| * Note: This is similar to {@link #assertPartialContentRange(String, String, String)} but uses the "Range" header and not the "Content-Range" header. |
| * |
| * @param rangedef |
| * @param expectedRange |
| * @param expectedBody |
| * @throws IOException |
| */ |
| private void assertByteRange(String rangedef, String expectedRange, String expectedBody) throws IOException |
| { |
| StringBuffer req1 = new StringBuffer(); |
| req1.append("GET /rfc2616-webapp/alpha.txt HTTP/1.1\n"); |
| req1.append("Host: localhost\n"); |
| req1.append("Range: ").append(rangedef).append("\n"); |
| req1.append("Connection: close\n"); |
| req1.append("\n"); |
| |
| HttpTester.Response response = http.request(req1); |
| |
| String msg = "Partial (Byte) Range: '" + rangedef + "'"; |
| assertEquals(msg,HttpStatus.PARTIAL_CONTENT_206, response.getStatus()); |
| // It might be strange to see a "Content-Range' response header to a 'Range' request, |
| // but this is appropriate per the RFC2616 spec. |
| assertEquals(msg,"bytes " + expectedRange, response.get("Content-Range")); |
| assertTrue(msg,response.getContent().contains(expectedBody)); |
| } |
| |
| /** |
| * Test Range (Header Field) |
| * |
| * @see <a href="http://tools.ietf.org/html/rfc2616#section-14.35">RFC 2616 (section 14.35)</a> |
| */ |
| @Test |
| public void test14_35_Range() throws Exception |
| { |
| // |
| // test various valid range specs that have not been |
| // tested yet |
| // |
| |
| String alpha = ALPHA; |
| |
| // First 3 bytes |
| assertByteRange("bytes=0-2","0-2/27",alpha.substring(0,2 + 1)); |
| |
| // From byte offset 23 thru the end of the content |
| assertByteRange("bytes=23-","23-26/27",alpha.substring(23)); |
| |
| // Request byte offset 23 thru 42 (only 26 bytes in content) |
| // The last 3 bytes are returned. |
| assertByteRange("bytes=23-42","23-26/27",alpha.substring(23,26 + 1)); |
| |
| // Request the last 3 bytes |
| assertByteRange("bytes=-3","24-26/27",alpha.substring(24,26 + 1)); |
| } |
| |
| /** |
| * Test Range (Header Field) |
| * |
| * @see <a href="http://tools.ietf.org/html/rfc2616#section-14.35">RFC 2616 (section 14.35)</a> |
| */ |
| @Test |
| public void test14_35_Range_Multipart1() throws Exception |
| { |
| String rangedef = "23-23,-2"; // Request byte at offset 23, and the last 2 bytes |
| |
| StringBuffer req1 = new StringBuffer(); |
| req1.append("GET /rfc2616-webapp/alpha.txt HTTP/1.1\n"); |
| req1.append("Host: localhost\n"); |
| req1.append("Range: ").append(rangedef).append("\n"); |
| req1.append("Connection: close\n"); |
| req1.append("\n"); |
| |
| HttpTester.Response response = http.request(req1); |
| |
| String msg = "Partial (Byte) Range: '" + rangedef + "'"; |
| assertEquals(msg,HttpStatus.PARTIAL_CONTENT_206, response.getStatus()); |
| |
| String contentType = response.get("Content-Type"); |
| // RFC states that multiple parts should result in multipart/byteranges Content type. |
| StringAssert.assertContains(msg + " Content-Type",contentType,"multipart/byteranges"); |
| |
| // Collect 'boundary' string |
| String boundary = null; |
| String parts[] = StringUtil.split(contentType,';'); |
| for (int i = 0; i < parts.length; i++) |
| { |
| if (parts[i].trim().startsWith("boundary=")) |
| { |
| String boundparts[] = StringUtil.split(parts[i],'='); |
| Assert.assertEquals(msg + " Boundary parts.length",2,boundparts.length); |
| boundary = boundparts[1]; |
| } |
| } |
| |
| Assert.assertNotNull(msg + " Should have found boundary in Content-Type header",boundary); |
| |
| List<String> lines = StringUtil.asLines(response.getContent().trim()); |
| int i=0; |
| assertEquals("--"+boundary,lines.get(i++)); |
| assertEquals("Content-Type: text/plain",lines.get(i++)); |
| assertEquals("Content-Range: bytes 23-23/27",lines.get(i++)); |
| assertEquals("",lines.get(i++)); |
| assertEquals("X",lines.get(i++)); |
| assertEquals("--"+boundary,lines.get(i++)); |
| assertEquals("Content-Type: text/plain",lines.get(i++)); |
| assertEquals("Content-Range: bytes 25-26/27",lines.get(i++)); |
| assertEquals("",lines.get(i++)); |
| assertEquals("Z",lines.get(i++)); |
| assertEquals("",lines.get(i++)); |
| assertEquals("--"+boundary+"--",lines.get(i++)); |
| } |
| |
| /** |
| * Test Range (Header Field) |
| * |
| * @see <a href="http://tools.ietf.org/html/rfc2616#section-14.35">RFC 2616 (section 14.35)</a> |
| */ |
| @Test |
| public void test14_35_Range_Multipart2() throws Exception |
| { |
| // Request the last 1 byte, last 2 bytes, and last 3 bytes. |
| // This is an example of overlapping ranges |
| |
| String rangedef = "-1,-2,-3"; |
| |
| StringBuffer req1 = new StringBuffer(); |
| req1.append("GET /rfc2616-webapp/alpha.txt HTTP/1.1\n"); |
| req1.append("Host: localhost\n"); |
| req1.append("Range: ").append(rangedef).append("\n"); |
| req1.append("Connection: close\n"); |
| req1.append("\n"); |
| |
| HttpTester.Response response = http.request(req1); |
| // System.err.println(response+response.getContent()); |
| |
| String msg = "Partial (Byte) Range: '" + rangedef + "'"; |
| assertEquals(msg,HttpStatus.PARTIAL_CONTENT_206,response.getStatus()); |
| |
| String contentType = response.get("Content-Type"); |
| // RFC states that multiple parts should result in multipart/byteranges Content type. |
| StringAssert.assertContains(msg + " Content-Type",contentType,"multipart/byteranges"); |
| |
| // Collect 'boundary' string |
| String boundary = null; |
| String parts[] = StringUtil.split(contentType,';'); |
| for (int i = 0; i < parts.length; i++) |
| { |
| if (parts[i].trim().startsWith("boundary=")) |
| { |
| String boundparts[] = StringUtil.split(parts[i],'='); |
| Assert.assertEquals(msg + " Boundary parts.length",2,boundparts.length); |
| boundary = boundparts[1]; |
| } |
| } |
| |
| Assert.assertNotNull(msg + " Should have found boundary in Content-Type header",boundary); |
| |
| |
| List<String> lines = StringUtil.asLines(response.getContent().trim()); |
| int i=0; |
| assertEquals("--"+boundary,lines.get(i++)); |
| assertEquals("Content-Type: text/plain",lines.get(i++)); |
| assertEquals("Content-Range: bytes 26-26/27",lines.get(i++)); |
| assertEquals("",lines.get(i++)); |
| assertEquals("",lines.get(i++)); |
| assertEquals("",lines.get(i++)); |
| assertEquals("--"+boundary,lines.get(i++)); |
| assertEquals("Content-Type: text/plain",lines.get(i++)); |
| assertEquals("Content-Range: bytes 25-26/27",lines.get(i++)); |
| assertEquals("",lines.get(i++)); |
| assertEquals("Z",lines.get(i++)); |
| assertEquals("",lines.get(i++)); |
| assertEquals("--"+boundary,lines.get(i++)); |
| assertEquals("Content-Type: text/plain",lines.get(i++)); |
| assertEquals("Content-Range: bytes 24-26/27",lines.get(i++)); |
| assertEquals("",lines.get(i++)); |
| assertEquals("YZ",lines.get(i++)); |
| assertEquals("",lines.get(i++)); |
| assertEquals("--"+boundary+"--",lines.get(i++)); |
| |
| } |
| |
| /** |
| * Test Range (Header Field) |
| * |
| * @see <a href="http://tools.ietf.org/html/rfc2616#section-14.35">RFC 2616 (section 14.35)</a> |
| */ |
| @Test |
| public void test14_35_PartialRange() throws Exception |
| { |
| // |
| // test various valid range specs that have not been |
| // tested yet |
| // |
| |
| String alpha = ALPHA; |
| |
| // server should not return a 416 if at least one syntactically valid ranges |
| // are is satisfiable |
| |
| assertByteRange("bytes=5-8,50-60","5-8/27",alpha.substring(5,8 + 1)); |
| assertByteRange("bytes=50-60,5-8","5-8/27",alpha.substring(5,8 + 1)); |
| } |
| |
| private void assertBadByteRange(String rangedef) throws IOException |
| { |
| StringBuffer req1 = new StringBuffer(); |
| req1.append("GET /rfc2616-webapp/alpha.txt HTTP/1.1\n"); |
| req1.append("Host: localhost\n"); |
| req1.append("Range: ").append(rangedef).append("\n"); // Invalid range |
| req1.append("Connection: close\n"); |
| req1.append("\n"); |
| |
| HttpTester.Response response = http.request(req1); |
| |
| assertEquals("BadByteRange: '" + rangedef + "'",HttpStatus.REQUESTED_RANGE_NOT_SATISFIABLE_416, response.getStatus()); |
| } |
| |
| /** |
| * Test Range (Header Field) - Bad Range Request |
| * |
| * @see <a href="http://tools.ietf.org/html/rfc2616#section-14.35">RFC 2616 (section 14.35)</a> |
| */ |
| @Test |
| public void test14_35_BadRange_InvalidSyntax() throws Exception |
| { |
| // server should ignore all range headers which include |
| // at least one syntactically invalid range |
| |
| assertBadByteRange("bytes=a-b"); // Invalid due to non-digit entries |
| assertBadByteRange("bytes=-"); // Invalid due to missing range ends |
| assertBadByteRange("bytes=-1-"); // Invalid due negative to end range |
| assertBadByteRange("doublehalfwords=1-2"); // Invalid due to bad key 'doublehalfwords' |
| } |
| |
| /** |
| * Test TE (Header Field) / Transfer Codings |
| * |
| * @see <a href="http://tools.ietf.org/html/rfc2616#section-14.39">RFC 2616 (section 14.39)</a> |
| */ |
| @Test |
| public void test14_39_TEGzip() throws Exception |
| { |
| if (STRICT) |
| { |
| String specId; |
| |
| // Gzip accepted |
| |
| StringBuffer req1 = new StringBuffer(); |
| req1.append("GET /rfc2616-webapp/solutions.html HTTP/1.1\n"); |
| req1.append("Host: localhost\n"); |
| req1.append("TE: gzip\n"); |
| req1.append("Connection: close\n"); |
| req1.append("\n"); |
| |
| HttpTester.Response response = http.request(req1); |
| specId = "14.39 TE Header"; |
| assertEquals(specId, HttpStatus.OK_200, response.getStatus()); |
| assertEquals(specId,"gzip", response.get("Transfer-Encoding")); |
| } |
| } |
| |
| /** |
| * Test TE (Header Field) / Transfer Codings |
| * |
| * @see <a href="http://tools.ietf.org/html/rfc2616#section-14.39">RFC 2616 (section 14.39)</a> |
| */ |
| @Test |
| public void test14_39_TEDeflate() throws Exception |
| { |
| if (STRICT) |
| { |
| String specId; |
| |
| // Deflate not accepted |
| StringBuffer req2 = new StringBuffer(); |
| req2.append("GET /rfc2616-webapp/solutions.html HTTP/1.1\n"); |
| req2.append("Host: localhost\n"); |
| req2.append("TE: deflate\n"); // deflate not accepted |
| req2.append("Connection: close\n"); |
| req2.append("\n"); |
| |
| HttpTester.Response response = http.request(req2); |
| specId = "14.39 TE Header"; |
| assertEquals(specId,HttpStatus.NOT_IMPLEMENTED_501, response.getStatus()); // Error on TE (deflate not supported) |
| } |
| } |
| |
| /** |
| * Test Compatibility with Previous (HTTP) Versions. |
| * |
| * @see <a href="http://tools.ietf.org/html/rfc2616#section-19.6">RFC 2616 (section 19.6)</a> |
| */ |
| @Test |
| public void test19_6() throws Exception |
| { |
| |
| String specId; |
| |
| /* Compatibility with HTTP/1.0 */ |
| |
| StringBuffer req1 = new StringBuffer(); |
| req1.append("GET /tests/R1.txt HTTP/1.0\n"); |
| req1.append("\n"); |
| |
| HttpTester.Response response = http.request(req1); |
| specId = "19.6 Compatibility with HTTP/1.0 - simple request"; |
| assertEquals(specId, HttpStatus.OK_200, response.getStatus()); |
| assertTrue(specId + " - connection closed not assumed",response.get("Connection") == null); |
| |
| /* Compatibility with HTTP/1.0 */ |
| |
| StringBuffer req2 = new StringBuffer(); |
| req2.append("GET /tests/R1.txt HTTP/1.0\n"); |
| req2.append("Host: localhost\n"); |
| req2.append("Connection: keep-alive\n"); |
| req2.append("\n"); |
| |
| req2.append("GET /tests/R2.txt HTTP/1.0\n"); |
| req2.append("Host: localhost\n"); |
| req2.append("Connection: close\n"); // Connection closed here |
| req2.append("\n"); |
| |
| req2.append("GET /tests/R3.txt HTTP/1.0\n"); // This request should not be handled |
| req2.append("Host: localhost\n"); |
| req2.append("Connection: close\n"); |
| req2.append("\n"); |
| |
| List<HttpTester.Response> responses = http.requests(req2); |
| // Since R2 closes the connection, should only get 2 responses (R1 & |
| // R2), not (R3) |
| Assert.assertEquals("Response Count",2,responses.size()); |
| |
| response = responses.get(0); // response 1 |
| specId = "19.6.2 Compatibility with previous HTTP - Keep-alive"; |
| assertEquals(specId, HttpStatus.OK_200, response.getStatus()); |
| assertEquals(specId,"keep-alive", response.get("Connection")); |
| assertTrue(specId,response.getContent().contains("Resource=R1")); |
| |
| response = responses.get(1); // response 2 |
| assertEquals(specId, HttpStatus.OK_200, response.getStatus()); |
| assertTrue(specId,response.getContent().contains("Resource=R2")); |
| |
| /* Compatibility with HTTP/1.0 */ |
| |
| StringBuffer req3 = new StringBuffer(); |
| req3.append("GET /echo/R1 HTTP/1.0\n"); |
| req3.append("Host: localhost\n"); |
| req3.append("Connection: keep-alive\n"); |
| req3.append("Content-Length: 10\n"); |
| req3.append("\n"); |
| req3.append("1234567890\n"); |
| |
| req3.append("GET /echo/RA HTTP/1.0\n"); |
| req3.append("Host: localhost\n"); |
| req3.append("Connection: keep-alive\n"); |
| req3.append("Content-Length: 10\n"); |
| req3.append("\n"); |
| req3.append("ABCDEFGHIJ\n"); |
| |
| req3.append("GET /tests/R2.txt HTTP/1.0\n"); |
| req3.append("Host: localhost\n"); |
| req3.append("Connection: close\n"); // Close connection here |
| req3.append("\n"); |
| |
| req3.append("GET /tests/R3.txt HTTP/1.0\n"); // This request should not |
| // be handled. |
| req3.append("Host: localhost\n"); |
| req3.append("Connection: close\n"); |
| req3.append("\n"); |
| responses = http.requests(req3); |
| Assert.assertEquals("Response Count",3,responses.size()); |
| |
| specId = "19.6.2 Compatibility with HTTP/1.0- Keep-alive"; |
| response = responses.get(0); |
| assertEquals(specId, HttpStatus.OK_200, response.getStatus()); |
| assertEquals(specId,"keep-alive", response.get("Connection")); |
| assertTrue(specId, response.getContent().contains("1234567890\n")); |
| |
| response = responses.get(1); |
| assertEquals(specId, HttpStatus.OK_200, response.getStatus()); |
| assertEquals(specId, "keep-alive", response.get("Connection")); |
| assertTrue(specId,response.getContent().contains("ABCDEFGHIJ\n")); |
| |
| response = responses.get(2); |
| assertEquals(specId, HttpStatus.OK_200, response.getStatus()); |
| assertTrue(specId,response.getContent().contains("Host=Default\nResource=R2\n")); |
| } |
| |
| protected void assertDate(String msg, Calendar expectedTime, long actualTime) |
| { |
| SimpleDateFormat sdf = new SimpleDateFormat("EEEE, d MMMM yyyy HH:mm:ss:SSS zzz"); |
| sdf.setTimeZone(TimeZone.getTimeZone("GMT")); |
| String actual = sdf.format(new Date(actualTime)); |
| String expected = sdf.format(expectedTime.getTime()); |
| |
| Assert.assertEquals(msg,expected,actual); |
| } |
| } |