|
1 | 1 | /*
|
2 |
| - * Copyright (c) 2021, Oracle and/or its affiliates. All rights reserved. |
| 2 | + * Copyright (c) 2021, 2025, Oracle and/or its affiliates. All rights reserved. |
3 | 3 | * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
4 | 4 | *
|
5 | 5 | * This code is free software; you can redistribute it and/or modify it
|
|
24 | 24 | /*
|
25 | 25 | * @test
|
26 | 26 | * @bug 8263899
|
27 |
| - * @summary HttpClient throws NPE in AuthenticationFilter when parsing www-authenticate head |
28 |
| - * |
29 |
| - * @run main/othervm EmptyAuthenticate |
| 27 | + * @summary Verifies that empty `WWW-Authenticate` header is correctly parsed |
| 28 | + * @library /test/jdk/java/net/httpclient/lib |
| 29 | + * /test/lib |
| 30 | + * @build jdk.httpclient.test.lib.common.HttpServerAdapters |
| 31 | + * jdk.test.lib.net.SimpleSSLContext |
| 32 | + * @run junit EmptyAuthenticate |
30 | 33 | */
|
31 |
| -import com.sun.net.httpserver.HttpServer; |
| 34 | + |
| 35 | +import jdk.httpclient.test.lib.common.HttpServerAdapters; |
| 36 | +import jdk.httpclient.test.lib.common.HttpServerAdapters.HttpTestHandler; |
| 37 | +import jdk.httpclient.test.lib.common.HttpServerAdapters.HttpTestServer; |
| 38 | +import jdk.test.lib.net.SimpleSSLContext; |
| 39 | +import org.junit.jupiter.params.ParameterizedTest; |
| 40 | +import org.junit.jupiter.params.provider.Arguments; |
| 41 | +import org.junit.jupiter.params.provider.MethodSource; |
| 42 | + |
| 43 | +import javax.net.ssl.SSLContext; |
32 | 44 | import java.io.IOException;
|
33 |
| -import java.io.OutputStream; |
34 |
| -import java.net.InetSocketAddress; |
35 | 45 | import java.net.URI;
|
36 |
| -import java.net.URISyntaxException; |
37 | 46 | import java.net.http.HttpClient;
|
| 47 | +import java.net.http.HttpClient.Version; |
| 48 | +import java.net.http.HttpHeaders; |
38 | 49 | import java.net.http.HttpRequest;
|
39 | 50 | import java.net.http.HttpResponse;
|
| 51 | +import java.nio.charset.StandardCharsets; |
| 52 | +import java.util.stream.Stream; |
40 | 53 |
|
41 |
| -public class EmptyAuthenticate { |
42 |
| - |
43 |
| - public static void main(String[] args) throws IOException, URISyntaxException, InterruptedException { |
44 |
| - int port = 0; |
45 |
| - |
46 |
| - //start server: |
47 |
| - HttpServer server = HttpServer.create(new InetSocketAddress(port), 0); |
48 |
| - port = server.getAddress().getPort(); |
49 |
| - server.createContext("/", exchange -> { |
50 |
| - String response = "test body"; |
51 |
| - //this empty header will make the HttpClient throw NPE |
52 |
| - exchange.getResponseHeaders().add("www-authenticate", ""); |
53 |
| - exchange.sendResponseHeaders(401, response.length()); |
54 |
| - OutputStream os = exchange.getResponseBody(); |
55 |
| - os.write(response.getBytes()); |
56 |
| - os.close(); |
57 |
| - }); |
58 |
| - server.start(); |
| 54 | +import static java.net.http.HttpClient.Builder.NO_PROXY; |
| 55 | +import static org.junit.jupiter.api.Assertions.assertEquals; |
59 | 56 |
|
60 |
| - HttpResponse<String> response = null; |
61 |
| - //run client: |
| 57 | +class EmptyAuthenticate { |
| 58 | + |
| 59 | + private static final SSLContext SSL_CONTEXT = createSslContext(); |
| 60 | + |
| 61 | + private static final String WWW_AUTH_HEADER_NAME = "WWW-Authenticate"; |
| 62 | + |
| 63 | + private static SSLContext createSslContext() { |
62 | 64 | try {
|
63 |
| - HttpClient httpClient = HttpClient.newHttpClient(); |
64 |
| - HttpRequest request = HttpRequest.newBuilder(new URI("http://localhost:" + port + "/")).GET().build(); |
65 |
| - //this line will throw NPE (wrapped by IOException) when parsing empty www-authenticate response header in AuthenticationFilter: |
66 |
| - response = httpClient.send(request, HttpResponse.BodyHandlers.ofString()); |
67 |
| - boolean ok = !response.headers().firstValue("WWW-Authenticate").isEmpty(); |
68 |
| - if (!ok) { |
69 |
| - throw new RuntimeException("WWW-Authenicate missing"); |
70 |
| - } |
71 |
| - } catch (IOException e) { |
72 |
| - e.printStackTrace(); |
73 |
| - throw new RuntimeException("Test failed"); |
| 65 | + return new SimpleSSLContext().get(); |
| 66 | + } catch (IOException exception) { |
| 67 | + throw new RuntimeException(exception); |
| 68 | + } |
| 69 | + } |
| 70 | + |
| 71 | + @ParameterizedTest |
| 72 | + @MethodSource("args") |
| 73 | + void test(Version version, boolean secure) throws Exception { |
| 74 | + String handlerPath = "/%s/%s/".formatted(EmptyAuthenticate.class.getSimpleName(), version); |
| 75 | + String uriPath = handlerPath + (secure ? 's' : 'c'); |
| 76 | + HttpTestServer server = createServer(version, secure, handlerPath); |
| 77 | + try (HttpClient client = createClient(version, secure)) { |
| 78 | + HttpRequest request = createRequest(server, secure, uriPath); |
| 79 | + HttpResponse<Void> response = client.send(request, HttpResponse.BodyHandlers.discarding()); |
| 80 | + HttpHeaders responseHeaders = response.headers(); |
| 81 | + assertEquals( |
| 82 | + "", |
| 83 | + responseHeaders.firstValue(WWW_AUTH_HEADER_NAME).orElse(null), |
| 84 | + () -> "was expecting empty `%s` header in: %s".formatted( |
| 85 | + WWW_AUTH_HEADER_NAME, responseHeaders.map())); |
74 | 86 | } finally {
|
75 |
| - server.stop(0); |
| 87 | + server.stop(); |
76 | 88 | }
|
77 | 89 | }
|
| 90 | + |
| 91 | + static Stream<Arguments> args() { |
| 92 | + return Stream |
| 93 | + .of(Version.HTTP_1_1, Version.HTTP_2) |
| 94 | + .flatMap(version -> Stream |
| 95 | + .of(true, false) |
| 96 | + .map(secure -> Arguments.of(version, secure))); |
| 97 | + } |
| 98 | + |
| 99 | + private static HttpTestServer createServer(Version version, boolean secure, String uriPath) |
| 100 | + throws IOException { |
| 101 | + HttpTestServer server = secure |
| 102 | + ? HttpTestServer.create(version, SSL_CONTEXT) |
| 103 | + : HttpTestServer.create(version); |
| 104 | + HttpTestHandler handler = new ServerHandlerRespondingWithEmptyWwwAuthHeader(); |
| 105 | + server.addHandler(handler, uriPath); |
| 106 | + server.start(); |
| 107 | + return server; |
| 108 | + } |
| 109 | + |
| 110 | + private static final class ServerHandlerRespondingWithEmptyWwwAuthHeader implements HttpTestHandler { |
| 111 | + |
| 112 | + private int responseIndex = 0; |
| 113 | + |
| 114 | + @Override |
| 115 | + public synchronized void handle(HttpServerAdapters.HttpTestExchange exchange) throws IOException { |
| 116 | + try (exchange) { |
| 117 | + exchange.getResponseHeaders().addHeader(WWW_AUTH_HEADER_NAME, ""); |
| 118 | + byte[] responseBodyBytes = "test body %d" |
| 119 | + .formatted(responseIndex) |
| 120 | + .getBytes(StandardCharsets.US_ASCII); |
| 121 | + exchange.sendResponseHeaders(401, responseBodyBytes.length); |
| 122 | + exchange.getResponseBody().write(responseBodyBytes); |
| 123 | + } finally { |
| 124 | + responseIndex++; |
| 125 | + } |
| 126 | + } |
| 127 | + |
| 128 | + } |
| 129 | + |
| 130 | + private static HttpClient createClient(Version version, boolean secure) { |
| 131 | + HttpClient.Builder clientBuilder = HttpClient.newBuilder().version(version).proxy(NO_PROXY); |
| 132 | + if (secure) { |
| 133 | + clientBuilder.sslContext(SSL_CONTEXT); |
| 134 | + } |
| 135 | + return clientBuilder.build(); |
| 136 | + } |
| 137 | + |
| 138 | + private static HttpRequest createRequest(HttpTestServer server, boolean secure, String uriPath) { |
| 139 | + URI uri = URI.create("%s://%s%s".formatted(secure ? "https" : "http", server.serverAuthority(), uriPath)); |
| 140 | + return HttpRequest.newBuilder(uri).version(server.getVersion()).GET().build(); |
| 141 | + } |
| 142 | + |
78 | 143 | }
|
0 commit comments