/*
 * JBoss, Home of Professional Open Source.
 * Copyright 2025 Red Hat, Inc., and individual contributors
 * as indicated by the @author tags.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 */
package io.undertow.server.handlers;

import java.io.IOException;

import org.apache.http.HttpHost;
import org.apache.http.HttpResponse;
import org.apache.http.client.ClientProtocolException;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.protocol.HttpClientContext;
import org.apache.http.conn.params.ConnRoutePNames;
import org.junit.Assert;
import org.junit.BeforeClass;
import org.junit.Ignore;
import org.junit.Test;
import org.junit.runner.RunWith;

import io.undertow.Handlers;
import io.undertow.server.HttpHandler;
import io.undertow.server.HttpServerExchange;
import io.undertow.testutils.DefaultServer;
import io.undertow.testutils.ProxyIgnore;
import io.undertow.testutils.TestHttpClient;
import io.undertow.util.Headers;

@RunWith(DefaultServer.class)
@ProxyIgnore
public class HostHandlerTestCase {

    @BeforeClass
    public static void setup() {
        DefaultServer.setRootHandler(Handlers.hostHeaderHandler(new HttpHandler() {
            @Override
            public void handleRequest(HttpServerExchange exchange) throws Exception {
                exchange.getResponseSender().send("OK");
            }
        }));
    }

    @Test
    @Ignore // ignore, since client will add one if there no present
    public void testNoHostHeader() throws Exception {
        test(new String[] {}, null, 400, null);
    }

    @Test
    public void testTooManyHostHeader() throws Exception {
        test(new String[] { "212.138.1.1", "data.com" }, null, 400, null);
    }

    @Test
    public void testIPv4HostHeader() throws Exception {
        // dont test bad IPv4, as this will be valid... reg-name
        test(new String[] { "212.138.1.1" }, null, 200, null);
    }

    @Test
    public void testIPv4AndPortHostHeader() throws Exception {
        test(new String[] { "212.138.1.1:80" }, null, 200, null);
    }

    @Test
    public void testIPv6HostHeader() throws Exception {
        test(new String[] { "[1:2:3:4::]" }, null, 200, null);
    }

    @Test
    public void testIPv6AndPortHostHeader() throws Exception {
        test(new String[] { "[1:2:3:4::]:80" }, null, 200, null);
    }

    @Test
    public void testIPv6HostHeader2() throws Exception {
        test(new String[] { "[1:2:3:4:::]" }, null, 400, HostHeaderHandler.STATUS_MALFORMED_IP_LITERAL);
    }

    @Test
    public void testIPv6AndPortHostHeader2() throws Exception {
        test(new String[] { "[1:2:3:4::]:80000" }, null, 400, HostHeaderHandler.STATUS_MALFORMED_PORT);
    }

    @Test
    public void testIPv6HostHeader3() throws Exception {
        test(new String[] { "[1:2:3:4::" }, null, 400, HostHeaderHandler.STATUS_MALFORMED_IP_LITERAL);
    }

    @Test
    public void testIPv6AndPortHostHeader3() throws Exception {
        test(new String[] { "[1:2:3:4:::80" }, null, 400, HostHeaderHandler.STATUS_MALFORMED_IP_LITERAL);
    }

    @Test
    public void testIPv6HostHeader4() throws Exception {
        test(new String[] { "1:2:3:4::]" }, null, 400, HostHeaderHandler.STATUS_MALFORMED_IP_LITERAL);
    }

    @Test
    public void testIPv6HostHeader5() throws Exception {
        test(new String[] { "1:2:3:4::" }, null, 400, HostHeaderHandler.STATUS_MALFORMED_PORT);
    }

    @Test
    public void testIPv6HostHeader6() throws Exception {
        //this will just fall into reg-name
        test(new String[] { "1:2:3:4:5:6:7:8" }, null, 400, HostHeaderHandler.STATUS_MALFORMED_IP_LITERAL_BAD_CHARS);
    }

    @Test
    public void testIPv6AndPortHostHeader4() throws Exception {
        test(new String[] { "1:2:3:4::]:80" }, null, 400, HostHeaderHandler.STATUS_MALFORMED_IP_LITERAL);
    }

    @Test
    public void testIPv6AndPortHostHeader6() throws Exception {
        test(new String[] { "1:2:3:4:5:6:7:8]:80" }, null, 400, HostHeaderHandler.STATUS_MALFORMED_IP_LITERAL);
    }

    @Test
    public void testIPv6EmbeddedIPv4HostHeader() throws Exception {
        test(new String[] { "[1:2:3:4::192.168.32.1]" }, null, 200, null);
    }

    @Test
    public void testIPv6EmbeddedIPv4HostHeader2() throws Exception {
        test(new String[] { "[1:2:3:4::192.168.32.1]:80" }, null, 200, null);
    }

    @Test
    public void testIPv6EmbeddedIPv4HostHeader3() throws Exception {
        test(new String[] { "[1:2:3:4::192.355.32.1]:80" }, null, 400, HostHeaderHandler.STATUS_MALFORMED_IP_LITERAL);
    }

    @Test
    public void testIPvFutureHostHeader() throws Exception {
        test(new String[] { "[vAF.1:2:3:4::]" }, null, 200, null);
    }

    @Test
    public void testIPvFutureHostHeader2() throws Exception {
        test(new String[] { "[vG.1:2:3:4::]" }, null, 400, HostHeaderHandler.STATUS_MALFORMED_IP_LITERAL_BAD_CHARS);
    }

    @Test
    public void testIPvFutureHostHeader3() throws Exception {
        test(new String[] { "[vAF.abdc:abcd_-~AF]" }, null, 200, null);
    }

    @Test
    public void testIPvFutureHostHeader4() throws Exception {
        test(new String[] { "[vAF.ImVal1.com-._~!$&'()*+,;=:]" }, null, 200, null);
    }

    @Test
    public void testIPvFutureHostHeader5() throws Exception {
        test(new String[] { "[vAF.]" }, null, 400, HostHeaderHandler.STATUS_MALFORMED_IP_LITERAL);
    }

    @Test
    public void testIPvFutureHostHeader6() throws Exception {
        test(new String[] { "[vAF]" }, null, 400, HostHeaderHandler.STATUS_MALFORMED_IP_LITERAL);
    }

    @Test
    public void testIPvFutureHostHeader7() throws Exception {
        test(new String[] { "[v.abcd]" }, null, 400, HostHeaderHandler.STATUS_MALFORMED_IP_LITERAL);
    }

    @Test
    public void testIPRegNameHostHeader() throws Exception {
        test(new String[] { "366.66.12.12" }, null, 200, null);
    }

    @Test
    public void testIPRegNameHostHeader2() throws Exception {
        test(new String[] { "domain.com%20" }, null, 200, null);
    }

    @Test
    public void testIPRegNameHostHeader3() throws Exception {
        test(new String[] { "domain.com%2" }, null, 400, HostHeaderHandler.STATUS_MALFORMED_IP_LITERAL);
    }

    @Test
    public void testIPRegNameHostHeader4() throws Exception {
        test(new String[] { "doma&n.com%20" }, null, 200, null);
    }

    @Test
    public void testIPRegNameHostHeader5() throws Exception {
        test(new String[] { "ImVal1.com-._~!$&'()*+,;=" }, null, 200, null);
    }

    @Test
    public void testIPRegNameHostHeader6() throws Exception {
        // test userinfo presence?
        test(new String[] { "juicyUserInfo@ImVal1.com-._~!$&'()*+,;=" }, null, 400, HostHeaderHandler.STATUS_MALFORMED_IP_LITERAL_BAD_CHARS);
    }

    @Test
    public void testAbsoluteURLBad() throws Exception {
        // test userinfo presence?
        test(new String[] { "wrong.com:8080" }, new HttpHost(DefaultServer.getHostAddress(), DefaultServer.getHostPort()), 400, HostHeaderHandler.STATUS_HOST_NO_MATCH);
    }

    @Test
    public void testAbsoluteURLGood() throws Exception {
        // test userinfo presence?
        test(new String[] { DefaultServer.getHostAddress() + ":" + DefaultServer.getHostPort() },
                new HttpHost(DefaultServer.getHostAddress(), DefaultServer.getHostPort()), 200, null);
    }

    @Test
    public void testEmptyHost() throws Exception {
        // test userinfo presence?
        test(new String[] { "" },
                null, 200, null);
    }

    @Test
    public void testEmptyHost2() throws Exception {
        // test userinfo presence?
        test(new String[] { "" },
                new HttpHost(DefaultServer.getHostAddress(), DefaultServer.getHostPort()), 400, HostHeaderHandler.STATUS_HOST_NO_MATCH);
    }

    public void test(final String[] headers, final HttpHost proxy, final int resultCode, final String statusMessage)
            throws ClientProtocolException, IOException {
        TestHttpClient client = new TestHttpClient();
        if (proxy != null) {
            client.getParams().setParameter(ConnRoutePNames.DEFAULT_PROXY, proxy);
        }
        try {
            HttpGet get = new HttpGet(DefaultServer.getDefaultServerURL());
            for (String i : headers) {
                get.addHeader(Headers.HOST_STRING, i);
            }
            HttpResponse result = client.execute(get, HttpClientContext.create());
            Assert.assertEquals(result.getStatusLine().getReasonPhrase(), resultCode, result.getStatusLine().getStatusCode());
            if (statusMessage != null) {
                Assert.assertEquals(statusMessage, result.getStatusLine().getReasonPhrase());
            }

        } finally {
            client.getConnectionManager().shutdown();
        }
    }

    public void testProxyMode(final String[] headers, final int resultCode, final String statusMessage) throws ClientProtocolException, IOException {
        // this has to be done this way in order to trick apache to use absolute form...
        HttpHost proxy = new HttpHost(DefaultServer.getHostAddress(), DefaultServer.getHostPort());
        TestHttpClient client = new TestHttpClient();
        client.getParams().setParameter(ConnRoutePNames.DEFAULT_PROXY, proxy);
        try {
            HttpGet get = new HttpGet(DefaultServer.getDefaultServerURL());
            for (String i : headers) {
                get.addHeader(Headers.HOST_STRING, i);
            }
            HttpResponse result = client.execute(get, HttpClientContext.create());
            Assert.assertEquals(result.getStatusLine().getReasonPhrase(), resultCode, result.getStatusLine().getStatusCode());

        } finally {
            client.getConnectionManager().shutdown();
        }
    }

}
