SSLParamConfiguratorTest.java

/*
 * Copyright (c) 2024, 2025 Oracle and/or its affiliates. All rights reserved.
 *
 * This program and the accompanying materials are made available under the
 * terms of the Eclipse Public License v. 2.0, which is available at
 * http://www.eclipse.org/legal/epl-2.0.
 *
 * This Source Code may also be made available under the following Secondary
 * Licenses when the conditions for such availability set forth in the
 * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
 * version 2 with the GNU Classpath Exception, which is available at
 * https://www.gnu.org/software/classpath/license.html.
 *
 * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
 */

package org.glassfish.jersey.client.innate.http;

import org.glassfish.jersey.client.ClientConfig;
import org.glassfish.jersey.client.ClientProperties;
import org.glassfish.jersey.client.ClientRequest;
import org.glassfish.jersey.client.JerseyClient;
import org.glassfish.jersey.http.HttpHeaders;
import org.glassfish.jersey.internal.MapPropertiesDelegate;
import org.glassfish.jersey.internal.PropertiesDelegate;
import org.hamcrest.MatcherAssert;
import org.hamcrest.Matchers;
import org.junit.jupiter.api.Test;

import javax.ws.rs.ProcessingException;
import javax.ws.rs.client.Client;
import javax.ws.rs.client.ClientBuilder;
import javax.ws.rs.core.MultivaluedHashMap;
import javax.ws.rs.core.Response;
import java.net.ConnectException;
import java.net.URI;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;

public class SSLParamConfiguratorTest {
    @Test
    public void testNoHost() {
        final URI uri = URI.create("http://xxx.com:8080");
        final JerseyClient client = (JerseyClient) ClientBuilder.newClient();
        final ClientConfig config = client.getConfiguration();
        final PropertiesDelegate delegate = new MapPropertiesDelegate();
        ClientRequest request = new ClientRequest(uri, config, delegate) {};
        SSLParamConfigurator configurator = SSLParamConfigurator.builder().request(request).build();
        MatcherAssert.assertThat(configurator.isSNIRequired(), Matchers.is(false));
    }

    @Test
    public void testHostHeaderHasPrecedence() {
        final URI uri = URI.create("http://xxx.com:8080");
        final JerseyClient client = (JerseyClient) ClientBuilder.newClient();
        final ClientConfig config = client.getConfiguration();
        final PropertiesDelegate delegate = new MapPropertiesDelegate();
        ClientRequest request = new ClientRequest(uri, config, delegate) {};
        request.getHeaders().add(HttpHeaders.HOST, "yyy.com");
        SSLParamConfigurator configurator = SSLParamConfigurator.builder().request(request).build();
        MatcherAssert.assertThat(configurator.isSNIRequired(), Matchers.is(true));
        MatcherAssert.assertThat(configurator.getSNIHostName(), Matchers.is("yyy.com"));
    }

    @Test
    public void testPropertyOnClientHasPrecedence() {
        final URI uri = URI.create("http://xxx.com:8080");
        final JerseyClient client = (JerseyClient) ClientBuilder.newClient();
        final ClientConfig config = client.getConfiguration();
        final PropertiesDelegate delegate = new MapPropertiesDelegate();
        client.property(ClientProperties.SNI_HOST_NAME, "yyy.com");
        ClientRequest request = new ClientRequest(uri, config, delegate) {};
        SSLParamConfigurator configurator = SSLParamConfigurator.builder().request(request).build();
        MatcherAssert.assertThat(configurator.isSNIRequired(), Matchers.is(true));
        MatcherAssert.assertThat(configurator.getSNIHostName(), Matchers.is("yyy.com"));
    }

    @Test
    public void testPropertyOnDelegateHasPrecedence() {
        final URI uri = URI.create("http://xxx.com:8080");
        final JerseyClient client = (JerseyClient) ClientBuilder.newClient();
        final ClientConfig config = client.getConfiguration();
        final PropertiesDelegate delegate = new MapPropertiesDelegate();
        client.property(ClientProperties.SNI_HOST_NAME, "yyy.com");
        delegate.setProperty(ClientProperties.SNI_HOST_NAME, "zzz.com");
        ClientRequest request = new ClientRequest(uri, config, delegate) {};
        SSLParamConfigurator configurator = SSLParamConfigurator.builder().request(request).build();
        MatcherAssert.assertThat(configurator.isSNIRequired(), Matchers.is(true));
        MatcherAssert.assertThat(configurator.getSNIHostName(), Matchers.is("zzz.com"));
    }

    @Test
    public void testPropertyOnDelegateHasPrecedenceOverHost() {
        final URI uri = URI.create("http://xxx.com:8080");
        final JerseyClient client = (JerseyClient) ClientBuilder.newClient();
        final ClientConfig config = client.getConfiguration();
        final PropertiesDelegate delegate = new MapPropertiesDelegate();
        client.property(ClientProperties.SNI_HOST_NAME, "yyy.com");
        delegate.setProperty(ClientProperties.SNI_HOST_NAME, "zzz.com");
        ClientRequest request = new ClientRequest(uri, config, delegate) {};
        request.getHeaders().add(HttpHeaders.HOST, "www.com");
        SSLParamConfigurator configurator = SSLParamConfigurator.builder().request(request).build();
        MatcherAssert.assertThat(configurator.isSNIRequired(), Matchers.is(true));
        MatcherAssert.assertThat(configurator.getSNIHostName(), Matchers.is("zzz.com"));
    }

    @Test
    public void testDisableSni() {
        final URI uri = URI.create("http://xxx.com:8080");
        final JerseyClient client = (JerseyClient) ClientBuilder.newClient();
        final ClientConfig config = client.getConfiguration();
        final PropertiesDelegate delegate = new MapPropertiesDelegate();
        client.property(ClientProperties.SNI_HOST_NAME, "yyy.com");
        delegate.setProperty(ClientProperties.SNI_HOST_NAME, "xxx.com");
        ClientRequest request = new ClientRequest(uri, config, delegate) {};
        request.getHeaders().add(HttpHeaders.HOST, "www.com");
        SSLParamConfigurator configurator = SSLParamConfigurator.builder().request(request).build();
        MatcherAssert.assertThat(configurator.isSNIRequired(), Matchers.is(false));
        MatcherAssert.assertThat(configurator.getSNIHostName(), Matchers.is("xxx.com"));
    }

    @Test
    public void testLowerCasePropertyOnClientHasPrecedence() {
        final URI uri = URI.create("http://xxx.com:8080");
        final JerseyClient client = (JerseyClient) ClientBuilder.newClient();
        final ClientConfig config = client.getConfiguration();
        final PropertiesDelegate delegate = new MapPropertiesDelegate();
        client.property(ClientProperties.SNI_HOST_NAME.toLowerCase(Locale.ROOT), "yyy.com");
        ClientRequest request = new ClientRequest(uri, config, delegate) {};
        request.getHeaders().add(HttpHeaders.HOST, "www.com");
        SSLParamConfigurator configurator = SSLParamConfigurator.builder().request(request).build();
        MatcherAssert.assertThat(configurator.isSNIRequired(), Matchers.is(true));
        MatcherAssert.assertThat(configurator.getSNIHostName(), Matchers.is("yyy.com"));
    }

    @Test
    public void testUriAndHeadersAndConfig() {
        final URI uri = URI.create("http://xxx.com:8080");
        final JerseyClient client = (JerseyClient) ClientBuilder.newClient();
        Map<String, List<Object>> httpHeaders = new MultivaluedHashMap<>();
        httpHeaders.put(HttpHeaders.HOST, Collections.singletonList("www.com"));
        SSLParamConfigurator configurator = SSLParamConfigurator.builder()
                .uri(uri)
                .headers(httpHeaders)
                .configuration(client.getConfiguration())
                .build();
        MatcherAssert.assertThat(configurator.isSNIRequired(), Matchers.is(true));
        MatcherAssert.assertThat(configurator.getSNIHostName(), Matchers.is("www.com"));

        client.property(ClientProperties.SNI_HOST_NAME, "yyy.com");
        configurator = SSLParamConfigurator.builder()
                .uri(uri)
                .headers(httpHeaders)
                .configuration(client.getConfiguration())
                .build();
        MatcherAssert.assertThat(configurator.isSNIRequired(), Matchers.is(true));
        MatcherAssert.assertThat(configurator.getSNIHostName(), Matchers.is("yyy.com"));
    }

    @Test
    public void testIPv6Header() {
        final String HOST_HEADER_IPv6 = "[172:30::333b]";
        final URI uri = URI.create("http://[172:30::333a]:8080/api/demo/v1");
        final JerseyClient client = (JerseyClient) ClientBuilder.newClient();
        Map<String, List<Object>> httpHeaders = new MultivaluedHashMap<>();
        httpHeaders.put(HttpHeaders.HOST, Collections.singletonList(HOST_HEADER_IPv6 + ":8080"));
        SSLParamConfigurator configurator = SSLParamConfigurator.builder()
                .uri(uri)
                .headers(httpHeaders)
                .configuration(client.getConfiguration())
                .build();
        MatcherAssert.assertThat(configurator.isSNIRequired(), Matchers.is(true));
        MatcherAssert.assertThat(configurator.getSNIHostName(), Matchers.is(HOST_HEADER_IPv6));
        URI expected = URI.create("http://" + HOST_HEADER_IPv6 + ":8080/api/demo/v1");
        MatcherAssert.assertThat(configurator.getSNIUri(), Matchers.is(expected));
        MatcherAssert.assertThat(configurator.toIPRequestUri().toString(),
                Matchers.is(uri.toString().replace("::", ":0:0:0:0:0:")));
    }

    @Test
    public void testIpv6Request() {
        Client client = ClientBuilder.newClient();
        String u = "http://[::1]:8080";
        try {
            client.target(u)
                    .request()
                    .header(HttpHeaders.HOST, "[172:30::333b]:8080")
                    .get();
        } catch (ProcessingException pe) {
            if (!ConnectException.class.isInstance(pe.getCause())) {
                throw pe;
            }
        }
    }

    @Test
    public void testIpv6RequestNoPort() {
        Client client = ClientBuilder.newClient();
        String u = "http://[::1]";
        try {
            client.target(u)
                    .request()
                    .header(HttpHeaders.HOST, "[172:30::333b]")
                    .get();
        } catch (ProcessingException pe) {
            if (!ConnectException.class.isInstance(pe.getCause())) {
                throw pe;
            }
        }
    }
}