//
//  ========================================================================
//  Copyright (c) 1995-2022 Mort Bay Consulting Pty Ltd and others.
//  ------------------------------------------------------------------------
//  All rights reserved. This program and the accompanying materials
//  are made available under the terms of the Eclipse Public License v1.0
//  and Apache License v2.0 which accompanies this distribution.
//
//      The Eclipse Public License is available at
//      http://www.eclipse.org/legal/epl-v10.html
//
//      The Apache License v2.0 is available at
//      http://www.opensource.org/licenses/apache2.0.php
//
//  You may elect to redistribute this code under either of these licenses.
//  ========================================================================
//

package org.eclipse.jetty.websocket.common.io.http;

import java.nio.ByteBuffer;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.List;

import org.eclipse.jetty.util.BufferUtil;
import org.eclipse.jetty.util.StringUtil;
import org.junit.jupiter.api.Test;

import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.is;
import static org.hamcrest.Matchers.notNullValue;
import static org.hamcrest.Matchers.nullValue;

public class HttpResponseHeaderParserTest
{
    private void appendUtf8(ByteBuffer buf, String line)
    {
        buf.put(ByteBuffer.wrap(StringUtil.getUtf8Bytes(line)));
    }

    @Test
    public void testParseNotFound()
    {
        StringBuilder resp = new StringBuilder();
        resp.append("HTTP/1.1 404 Not Found\r\n");
        resp.append("Date: Fri, 26 Apr 2013 21:43:08 GMT\r\n");
        resp.append("Content-Type: text/html; charset=ISO-8859-1\r\n");
        resp.append("Cache-Control: must-revalidate,no-cache,no-store\r\n");
        resp.append("Content-Length: 38\r\n");
        resp.append("Server: Jetty(9.0.0.v20130308)\r\n");
        resp.append("\r\n");
        // and some body content
        resp.append("What you are looking for is not here\r\n");

        ByteBuffer buf = BufferUtil.toBuffer(resp.toString(), StandardCharsets.UTF_8);

        HttpResponseParseCapture capture = new HttpResponseParseCapture();
        HttpResponseHeaderParser parser = new HttpResponseHeaderParser(capture);
        assertThat("Parser.parse", parser.parse(buf), notNullValue());
        assertThat("Response.statusCode", capture.getStatusCode(), is(404));
        assertThat("Response.statusReason", capture.getStatusReason(), is("Not Found"));
        assertThat("Response.headers[Content-Length]", capture.getHeader("Content-Length"), is("38"));

        assertThat("Response.remainingBuffer", capture.getRemainingBuffer().remaining(), is(38));
    }

    @Test
    public void testParseRealWorldResponse()
    {
        // Arbitrary Http Response Headers seen in the wild.
        // Request URI -> https://ssl.google-analytics.com/__utm.gif
        List<String> expected = new ArrayList<>();
        expected.add("HTTP/1.0 200 OK");
        expected.add("Date: Thu, 09 Aug 2012 16:16:39 GMT");
        expected.add("Content-Length: 35");
        expected.add("X-Content-Type-Options: nosniff");
        expected.add("Pragma: no-cache");
        expected.add("Expires: Wed, 19 Apr 2000 11:43:00 GMT");
        expected.add("Last-Modified: Wed, 21 Jan 2004 19:51:30 GMT");
        expected.add("Content-Type: image/gif");
        expected.add("Cache-Control: private, no-cache, no-cache=Set-Cookie, proxy-revalidate");
        expected.add("Age: 518097");
        expected.add("Server: GFE/2.0");
        expected.add("Connection: Keep-Alive");
        expected.add("");

        // Prepare Buffer
        ByteBuffer buf = ByteBuffer.allocate(512);
        for (String line : expected)
        {
            appendUtf8(buf, line + "\r\n");
        }

        BufferUtil.flipToFlush(buf, 0);

        // Parse Buffer
        HttpResponseParseCapture capture = new HttpResponseParseCapture();
        HttpResponseHeaderParser parser = new HttpResponseHeaderParser(capture);
        assertThat("Parser.parse", parser.parse(buf), notNullValue());

        assertThat("Response.statusCode", capture.getStatusCode(), is(200));
        assertThat("Response.statusReason", capture.getStatusReason(), is("OK"));

        assertThat("Response.header[age]", capture.getHeader("age"), is("518097"));
    }

    @Test
    public void testParseRealWorldResponseSmallBuffers()
    {
        // Arbitrary Http Response Headers seen in the wild.
        // Request URI -> https://ssl.google-analytics.com/__utm.gif
        List<String> expected = new ArrayList<>();
        expected.add("HTTP/1.0 200 OK");
        expected.add("Date: Thu, 09 Aug 2012 16:16:39 GMT");
        expected.add("Content-Length: 35");
        expected.add("X-Content-Type-Options: nosniff");
        expected.add("Pragma: no-cache");
        expected.add("Expires: Wed, 19 Apr 2000 11:43:00 GMT");
        expected.add("Last-Modified: Wed, 21 Jan 2004 19:51:30 GMT");
        expected.add("Content-Type: image/gif");
        expected.add("Cache-Control: private, no-cache, no-cache=Set-Cookie, proxy-revalidate");
        expected.add("Age: 518097");
        expected.add("Server: GFE/2.0");
        expected.add("Connection: Keep-Alive");
        expected.add("");

        // Prepare Buffer
        ByteBuffer buf = ByteBuffer.allocate(512);
        for (String line : expected)
        {
            appendUtf8(buf, line + "\r\n");
        }
        BufferUtil.flipToFlush(buf, 0);

        // Prepare small buffers to simulate a slow read/fill/parse from the network
        ByteBuffer small1 = buf.slice();
        ByteBuffer small2 = buf.slice();
        ByteBuffer small3 = buf.slice();

        small1.limit(50);
        small2.position(50);
        small2.limit(70);
        small3.position(70);

        // Parse Buffer
        HttpResponseParseCapture capture = new HttpResponseParseCapture();
        HttpResponseHeaderParser parser = new HttpResponseHeaderParser(capture);
        assertThat("Parser.parse", parser.parse(buf), notNullValue());

        // Parse small 1
        assertThat("Small 1", parser.parse(small1), nullValue());

        // Parse small 2
        assertThat("Small 2", parser.parse(small2), nullValue());

        // Parse small 3
        assertThat("Small 3", parser.parse(small3), notNullValue());

        assertThat("Response.statusCode", capture.getStatusCode(), is(200));
        assertThat("Response.statusReason", capture.getStatusReason(), is("OK"));

        assertThat("Response.header[age]", capture.getHeader("age"), is("518097"));
    }

    @Test
    public void testParseUpgrade()
    {
        // Example from RFC6455 - Section 1.2 (Protocol Overview)
        StringBuilder resp = new StringBuilder();
        resp.append("HTTP/1.1 101 Switching Protocols\r\n");
        resp.append("Upgrade: websocket\r\n");
        resp.append("Connection: Upgrade\r\n");
        resp.append("Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=\r\n");
        resp.append("Sec-WebSocket-Protocol: chat\r\n");
        resp.append("\r\n");

        ByteBuffer buf = BufferUtil.toBuffer(resp.toString(), StandardCharsets.UTF_8);

        HttpResponseParseCapture capture = new HttpResponseParseCapture();
        HttpResponseHeaderParser parser = new HttpResponseHeaderParser(capture);
        assertThat("Parser.parse", parser.parse(buf), notNullValue());
        assertThat("Response.statusCode", capture.getStatusCode(), is(101));
        assertThat("Response.statusReason", capture.getStatusReason(), is("Switching Protocols"));
        assertThat("Response.headers[Upgrade]", capture.getHeader("Upgrade"), is("websocket"));
        assertThat("Response.headers[Connection]", capture.getHeader("Connection"), is("Upgrade"));

        assertThat("Buffer.remaining", buf.remaining(), is(0));
    }
}
