//
//  ========================================================================
//  Copyright (c) 1995-2015 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.junit.Assert.*;
import static org.junit.matchers.JUnitMatchers.*;

import java.io.File;
import java.io.IOException;
import java.net.Socket;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Collections;
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.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.MultiPartInputStreamParser;
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.getStringField("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";
        assertEquals(specId,HttpStatus.FOUND_302, response.getStatus());
        assertEquals(specId,server.getScheme() + "://localhost/tests/R2.txt", response.get("Location"));
        assertEquals(specId,"close", response.get("Connection"));
        assertTrue(specId,response.get("Content-Length") == null);
    }

    /**
     * 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

        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.BAD_REQUEST_400, 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);
    }
}
