--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/java/net/httpclient/DigestEchoClient.java Tue Apr 17 08:54:17 2018 -0700
@@ -0,0 +1,700 @@
+/*
+ * Copyright (c) 2018, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+import java.io.IOException;
+import java.io.UncheckedIOException;
+import java.math.BigInteger;
+import java.net.ProxySelector;
+import java.net.URI;
+import java.net.http.HttpClient;
+import java.net.http.HttpClient.Version;
+import java.net.http.HttpRequest;
+import java.net.http.HttpRequest.BodyPublisher;
+import java.net.http.HttpRequest.BodyPublishers;
+import java.net.http.HttpResponse;
+import java.net.http.HttpResponse.BodyHandler;
+import java.net.http.HttpResponse.BodyHandlers;
+import java.nio.charset.StandardCharsets;
+import java.security.NoSuchAlgorithmException;
+import java.util.Arrays;
+import java.util.Base64;
+import java.util.EnumSet;
+import java.util.List;
+import java.util.Optional;
+import java.util.Random;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.CompletionException;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.concurrent.atomic.AtomicLong;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+import javax.net.ssl.SSLContext;
+import jdk.testlibrary.SimpleSSLContext;
+import sun.net.NetProperties;
+import sun.net.www.HeaderParser;
+import static java.lang.System.out;
+import static java.lang.String.format;
+
+/**
+ * @test
+ * @summary this test verifies that a client may provides authorization
+ * headers directly when connecting with a server.
+ * @bug 8087112
+ * @library /lib/testlibrary http2/server
+ * @build jdk.testlibrary.SimpleSSLContext HttpServerAdapters DigestEchoServer
+ * ReferenceTracker DigestEchoClient
+ * @modules java.net.http/jdk.internal.net.http.common
+ * java.net.http/jdk.internal.net.http.frame
+ * java.net.http/jdk.internal.net.http.hpack
+ * java.logging
+ * java.base/sun.net.www.http
+ * java.base/sun.net.www
+ * java.base/sun.net
+ * @run main/othervm DigestEchoClient
+ * @run main/othervm -Djdk.http.auth.proxying.disabledSchemes=
+ * -Djdk.http.auth.tunneling.disabledSchemes=
+ * DigestEchoClient
+ */
+
+public class DigestEchoClient {
+
+ static final String data[] = {
+ "Lorem ipsum",
+ "dolor sit amet",
+ "consectetur adipiscing elit, sed do eiusmod tempor",
+ "quis nostrud exercitation ullamco",
+ "laboris nisi",
+ "ut",
+ "aliquip ex ea commodo consequat." +
+ "Duis aute irure dolor in reprehenderit in voluptate velit esse" +
+ "cillum dolore eu fugiat nulla pariatur.",
+ "Excepteur sint occaecat cupidatat non proident."
+ };
+
+ static final AtomicLong serverCount = new AtomicLong();
+ static final class EchoServers {
+ final DigestEchoServer.HttpAuthType authType;
+ final DigestEchoServer.HttpAuthSchemeType authScheme;
+ final String protocolScheme;
+ final String key;
+ final DigestEchoServer server;
+ final Version serverVersion;
+
+ private EchoServers(DigestEchoServer server,
+ Version version,
+ String protocolScheme,
+ DigestEchoServer.HttpAuthType authType,
+ DigestEchoServer.HttpAuthSchemeType authScheme) {
+ this.authType = authType;
+ this.authScheme = authScheme;
+ this.protocolScheme = protocolScheme;
+ this.key = key(version, protocolScheme, authType, authScheme);
+ this.server = server;
+ this.serverVersion = version;
+ }
+
+ static String key(Version version,
+ String protocolScheme,
+ DigestEchoServer.HttpAuthType authType,
+ DigestEchoServer.HttpAuthSchemeType authScheme) {
+ return String.format("%s:%s:%s:%s", version, protocolScheme, authType, authScheme);
+ }
+
+ private static EchoServers create(Version version,
+ String protocolScheme,
+ DigestEchoServer.HttpAuthType authType,
+ DigestEchoServer.HttpAuthSchemeType authScheme) {
+ try {
+ serverCount.incrementAndGet();
+ DigestEchoServer server =
+ DigestEchoServer.create(version, protocolScheme, authType, authScheme);
+ return new EchoServers(server, version, protocolScheme, authType, authScheme);
+ } catch (IOException x) {
+ throw new UncheckedIOException(x);
+ }
+ }
+
+ public static DigestEchoServer of(Version version,
+ String protocolScheme,
+ DigestEchoServer.HttpAuthType authType,
+ DigestEchoServer.HttpAuthSchemeType authScheme) {
+ String key = key(version, protocolScheme, authType, authScheme);
+ return servers.computeIfAbsent(key, (k) ->
+ create(version, protocolScheme, authType, authScheme)).server;
+ }
+
+ public static void stop() {
+ for (EchoServers s : servers.values()) {
+ s.server.stop();
+ }
+ }
+
+ private static final ConcurrentMap<String, EchoServers> servers = new ConcurrentHashMap<>();
+ }
+
+ final static String PROXY_DISABLED = NetProperties.get("jdk.http.auth.proxying.disabledSchemes");
+ final static String TUNNEL_DISABLED = NetProperties.get("jdk.http.auth.tunneling.disabledSchemes");
+ static {
+ System.out.println("jdk.http.auth.proxying.disabledSchemes=" + PROXY_DISABLED);
+ System.out.println("jdk.http.auth.tunneling.disabledSchemes=" + TUNNEL_DISABLED);
+ }
+
+
+
+ static final AtomicInteger NC = new AtomicInteger();
+ static final Random random = new Random();
+ static final SSLContext context;
+ static {
+ try {
+ context = new SimpleSSLContext().get();
+ SSLContext.setDefault(context);
+ } catch (Exception x) {
+ throw new ExceptionInInitializerError(x);
+ }
+ }
+ static final List<Boolean> BOOLEANS = List.of(true, false);
+
+ final boolean useSSL;
+ final DigestEchoServer.HttpAuthSchemeType authScheme;
+ final DigestEchoServer.HttpAuthType authType;
+ DigestEchoClient(boolean useSSL,
+ DigestEchoServer.HttpAuthSchemeType authScheme,
+ DigestEchoServer.HttpAuthType authType)
+ throws IOException {
+ this.useSSL = useSSL;
+ this.authScheme = authScheme;
+ this.authType = authType;
+ }
+
+ static final AtomicLong clientCount = new AtomicLong();
+ static final ReferenceTracker TRACKER = ReferenceTracker.INSTANCE;
+ public HttpClient newHttpClient(DigestEchoServer server) {
+ clientCount.incrementAndGet();
+ HttpClient.Builder builder = HttpClient.newBuilder();
+ builder = builder.proxy(ProxySelector.of(null));
+ if (useSSL) {
+ builder.sslContext(context);
+ }
+ switch (authScheme) {
+ case BASIC:
+ builder = builder.authenticator(DigestEchoServer.AUTHENTICATOR);
+ break;
+ case BASICSERVER:
+ // don't set the authenticator: we will handle the header ourselves.
+ // builder = builder.authenticator(DigestEchoServer.AUTHENTICATOR);
+ break;
+ default:
+ break;
+ }
+ switch (authType) {
+ case PROXY:
+ builder = builder.proxy(ProxySelector.of(server.getProxyAddress()));
+ break;
+ case PROXY305:
+ builder = builder.proxy(ProxySelector.of(server.getProxyAddress()));
+ builder = builder.followRedirects(HttpClient.Redirect.NORMAL);
+ break;
+ case SERVER307:
+ builder = builder.followRedirects(HttpClient.Redirect.NORMAL);
+ break;
+ default:
+ break;
+ }
+ return TRACKER.track(builder.build());
+ }
+
+ public static List<Version> serverVersions(Version clientVersion) {
+ if (clientVersion == Version.HTTP_1_1) {
+ return List.of(clientVersion);
+ } else {
+ return List.of(Version.values());
+ }
+ }
+
+ public static List<Version> clientVersions() {
+ return List.of(Version.values());
+ }
+
+ public static List<Boolean> expectContinue(Version serverVersion) {
+ if (serverVersion == Version.HTTP_1_1) {
+ return BOOLEANS;
+ } else {
+ // our test HTTP/2 server does not support Expect: 100-Continue
+ return List.of(Boolean.FALSE);
+ }
+ }
+
+ public static void main(String[] args) throws Exception {
+ HttpServerAdapters.enableServerLogging();
+ boolean useSSL = false;
+ EnumSet<DigestEchoServer.HttpAuthType> types =
+ EnumSet.complementOf(EnumSet.of(DigestEchoServer.HttpAuthType.PROXY305));
+ Throwable failed = null;
+ if (args != null && args.length >= 1) {
+ useSSL = "SSL".equals(args[0]);
+ if (args.length > 1) {
+ List<DigestEchoServer.HttpAuthType> httpAuthTypes =
+ Stream.of(Arrays.copyOfRange(args, 1, args.length))
+ .map(DigestEchoServer.HttpAuthType::valueOf)
+ .collect(Collectors.toList());
+ types = EnumSet.copyOf(httpAuthTypes);
+ }
+ }
+ try {
+ for (DigestEchoServer.HttpAuthType authType : types) {
+ // The test server does not support PROXY305 properly
+ if (authType == DigestEchoServer.HttpAuthType.PROXY305) continue;
+ EnumSet<DigestEchoServer.HttpAuthSchemeType> basics =
+ EnumSet.of(DigestEchoServer.HttpAuthSchemeType.BASICSERVER,
+ DigestEchoServer.HttpAuthSchemeType.BASIC);
+ for (DigestEchoServer.HttpAuthSchemeType authScheme : basics) {
+ DigestEchoClient dec = new DigestEchoClient(useSSL,
+ authScheme,
+ authType);
+ for (Version clientVersion : clientVersions()) {
+ for (Version serverVersion : serverVersions(clientVersion)) {
+ for (boolean expectContinue : expectContinue(serverVersion)) {
+ for (boolean async : BOOLEANS) {
+ for (boolean preemptive : BOOLEANS) {
+ dec.testBasic(clientVersion,
+ serverVersion, async,
+ expectContinue, preemptive);
+ }
+ }
+ }
+ }
+ }
+ }
+ EnumSet<DigestEchoServer.HttpAuthSchemeType> digests =
+ EnumSet.of(DigestEchoServer.HttpAuthSchemeType.DIGEST);
+ for (DigestEchoServer.HttpAuthSchemeType authScheme : digests) {
+ DigestEchoClient dec = new DigestEchoClient(useSSL,
+ authScheme,
+ authType);
+ for (Version clientVersion : clientVersions()) {
+ for (Version serverVersion : serverVersions(clientVersion)) {
+ for (boolean expectContinue : expectContinue(serverVersion)) {
+ for (boolean async : BOOLEANS) {
+ dec.testDigest(clientVersion, serverVersion,
+ async, expectContinue);
+ }
+ }
+ }
+ }
+ }
+ }
+ } catch(Throwable t) {
+ out.println(DigestEchoServer.now()
+ + ": Unexpected exception: " + t);
+ t.printStackTrace();
+ failed = t;
+ throw t;
+ } finally {
+ Thread.sleep(100);
+ AssertionError trackFailed = TRACKER.check(500);
+ EchoServers.stop();
+ System.out.println(" ---------------------------------------------------------- ");
+ System.out.println(String.format("DigestEchoClient %s %s", useSSL ? "SSL" : "CLEAR", types));
+ System.out.println(String.format("Created %d clients and %d servers",
+ clientCount.get(), serverCount.get()));
+ System.out.println(String.format("basics: %d requests sent, %d ns / req",
+ basicCount.get(), basics.get()));
+ System.out.println(String.format("digests: %d requests sent, %d ns / req",
+ digestCount.get(), digests.get()));
+ System.out.println(" ---------------------------------------------------------- ");
+ if (trackFailed != null) {
+ if (failed != null) {
+ failed.addSuppressed(trackFailed);
+ if (failed instanceof Error) throw (Error) failed;
+ if (failed instanceof Exception) throw (Exception) failed;
+ }
+ throw trackFailed;
+ }
+ }
+ }
+
+ boolean isSchemeDisabled() {
+ String disabledSchemes;
+ if (isProxy(authType)) {
+ disabledSchemes = useSSL
+ ? TUNNEL_DISABLED
+ : PROXY_DISABLED;
+ } else return false;
+ if (disabledSchemes == null
+ || disabledSchemes.isEmpty()) {
+ return false;
+ }
+ String scheme;
+ switch (authScheme) {
+ case DIGEST:
+ scheme = "Digest";
+ break;
+ case BASIC:
+ scheme = "Basic";
+ break;
+ case BASICSERVER:
+ scheme = "Basic";
+ break;
+ case NONE:
+ return false;
+ default:
+ throw new InternalError("Unknown auth scheme: " + authScheme);
+ }
+ return Stream.of(disabledSchemes.split(","))
+ .map(String::trim)
+ .filter(scheme::equalsIgnoreCase)
+ .findAny()
+ .isPresent();
+ }
+
+ final static AtomicLong basics = new AtomicLong();
+ final static AtomicLong basicCount = new AtomicLong();
+ // @Test
+ void testBasic(Version clientVersion, Version serverVersion, boolean async,
+ boolean expectContinue, boolean preemptive)
+ throws Exception
+ {
+ final boolean addHeaders = authScheme == DigestEchoServer.HttpAuthSchemeType.BASICSERVER;
+ // !preemptive has no meaning if we don't handle the authorization
+ // headers ourselves
+ if (!preemptive && !addHeaders) return;
+
+ out.println(format("*** testBasic: client: %s, server: %s, async: %s, useSSL: %s, " +
+ "authScheme: %s, authType: %s, expectContinue: %s preemptive: %s***",
+ clientVersion, serverVersion, async, useSSL, authScheme, authType,
+ expectContinue, preemptive));
+
+ DigestEchoServer server = EchoServers.of(serverVersion,
+ useSSL ? "https" : "http", authType, authScheme);
+ URI uri = DigestEchoServer.uri(useSSL ? "https" : "http",
+ server.getServerAddress(), "/foo/");
+
+ HttpClient client = newHttpClient(server);
+ HttpResponse<String> r;
+ CompletableFuture<HttpResponse<String>> cf1;
+ String auth = null;
+
+ try {
+ for (int i=0; i<data.length; i++) {
+ out.println(DigestEchoServer.now() + " ----- iteration " + i + " -----");
+ List<String> lines = List.of(Arrays.copyOfRange(data, 0, i+1));
+ assert lines.size() == i + 1;
+ String body = lines.stream().collect(Collectors.joining("\r\n"));
+ BodyPublisher reqBody = BodyPublishers.ofString(body);
+ HttpRequest.Builder builder = HttpRequest.newBuilder(uri).version(clientVersion)
+ .POST(reqBody).expectContinue(expectContinue);
+ boolean isTunnel = isProxy(authType) && useSSL;
+ if (addHeaders) {
+ // handle authentication ourselves
+ assert !client.authenticator().isPresent();
+ if (auth == null) auth = "Basic " + getBasicAuth("arthur");
+ try {
+ if ((i > 0 || preemptive)
+ && (!isTunnel || i == 0 || isSchemeDisabled())) {
+ // In case of a SSL tunnel through proxy then only the
+ // first request should require proxy authorization
+ // Though this might be invalidated if the server decides
+ // to close the connection...
+ out.println(String.format("%s adding %s: %s",
+ DigestEchoServer.now(),
+ authorizationKey(authType),
+ auth));
+ builder = builder.header(authorizationKey(authType), auth);
+ }
+ } catch (IllegalArgumentException x) {
+ throw x;
+ }
+ } else {
+ // let the stack do the authentication
+ assert client.authenticator().isPresent();
+ }
+ long start = System.nanoTime();
+ HttpRequest request = builder.build();
+ HttpResponse<Stream<String>> resp;
+ try {
+ if (async) {
+ resp = client.sendAsync(request, BodyHandlers.ofLines()).join();
+ } else {
+ resp = client.send(request, BodyHandlers.ofLines());
+ }
+ } catch (Throwable t) {
+ long stop = System.nanoTime();
+ synchronized (basicCount) {
+ long n = basicCount.getAndIncrement();
+ basics.set((basics.get() * n + (stop - start)) / (n + 1));
+ }
+ // unwrap CompletionException
+ if (t instanceof CompletionException) {
+ assert t.getCause() != null;
+ t = t.getCause();
+ }
+ out.println(DigestEchoServer.now()
+ + ": Unexpected exception: " + t);
+ throw new RuntimeException("Unexpected exception: " + t, t);
+ }
+
+ if (addHeaders && !preemptive && (i==0 || isSchemeDisabled())) {
+ assert resp.statusCode() == 401 || resp.statusCode() == 407;
+ Stream<String> respBody = resp.body();
+ if (respBody != null) {
+ System.out.printf("Response body (%s):\n", resp.statusCode());
+ respBody.forEach(System.out::println);
+ }
+ System.out.println(String.format("%s received: adding header %s: %s",
+ resp.statusCode(), authorizationKey(authType), auth));
+ request = HttpRequest.newBuilder(uri).version(clientVersion)
+ .POST(reqBody).header(authorizationKey(authType), auth).build();
+ if (async) {
+ resp = client.sendAsync(request, BodyHandlers.ofLines()).join();
+ } else {
+ resp = client.send(request, BodyHandlers.ofLines());
+ }
+ }
+ final List<String> respLines;
+ try {
+ if (isSchemeDisabled()) {
+ if (resp.statusCode() != 407) {
+ throw new RuntimeException("expected 407 not received");
+ }
+ System.out.println("Scheme disabled for [" + authType
+ + ", " + authScheme
+ + ", " + (useSSL ? "HTTP" : "HTTPS")
+ + "]: Received expected " + resp.statusCode());
+ continue;
+ } else {
+ System.out.println("Scheme enabled for [" + authType
+ + ", " + authScheme
+ + ", " + (useSSL ? "HTTPS" : "HTTP")
+ + "]: Expecting 200, response is: " + resp);
+ assert resp.statusCode() == 200 : "200 expected, received " + resp;
+ respLines = resp.body().collect(Collectors.toList());
+ }
+ } finally {
+ long stop = System.nanoTime();
+ synchronized (basicCount) {
+ long n = basicCount.getAndIncrement();
+ basics.set((basics.get() * n + (stop - start)) / (n + 1));
+ }
+ }
+ if (!lines.equals(respLines)) {
+ throw new RuntimeException("Unexpected response: " + respLines);
+ }
+ }
+ } finally {
+ }
+ System.out.println("OK");
+ }
+
+ String getBasicAuth(String username) {
+ StringBuilder builder = new StringBuilder(username);
+ builder.append(':');
+ for (char c : DigestEchoServer.AUTHENTICATOR.getPassword(username)) {
+ builder.append(c);
+ }
+ return Base64.getEncoder().encodeToString(builder.toString().getBytes(StandardCharsets.UTF_8));
+ }
+
+ final static AtomicLong digests = new AtomicLong();
+ final static AtomicLong digestCount = new AtomicLong();
+ // @Test
+ void testDigest(Version clientVersion, Version serverVersion,
+ boolean async, boolean expectContinue)
+ throws Exception
+ {
+ String test = format("testDigest: client: %s, server: %s, async: %s, useSSL: %s, " +
+ "authScheme: %s, authType: %s, expectContinue: %s",
+ clientVersion, serverVersion, async, useSSL,
+ authScheme, authType, expectContinue);
+ out.println("*** " + test + " ***");
+ DigestEchoServer server = EchoServers.of(serverVersion,
+ useSSL ? "https" : "http", authType, authScheme);
+
+ URI uri = DigestEchoServer.uri(useSSL ? "https" : "http",
+ server.getServerAddress(), "/foo/");
+
+ HttpClient client = newHttpClient(server);
+ HttpResponse<String> r;
+ CompletableFuture<HttpResponse<String>> cf1;
+ byte[] cnonce = new byte[16];
+ String cnonceStr = null;
+ DigestEchoServer.DigestResponse challenge = null;
+
+ try {
+ for (int i=0; i<data.length; i++) {
+ out.println(DigestEchoServer.now() + "----- iteration " + i + " -----");
+ List<String> lines = List.of(Arrays.copyOfRange(data, 0, i+1));
+ assert lines.size() == i + 1;
+ String body = lines.stream().collect(Collectors.joining("\r\n"));
+ HttpRequest.BodyPublisher reqBody = HttpRequest.BodyPublishers.ofString(body);
+ HttpRequest.Builder reqBuilder = HttpRequest
+ .newBuilder(uri).version(clientVersion).POST(reqBody)
+ .expectContinue(expectContinue);
+
+ boolean isTunnel = isProxy(authType) && useSSL;
+ String digestMethod = isTunnel ? "CONNECT" : "POST";
+
+ // In case of a tunnel connection only the first request
+ // which establishes the tunnel needs to authenticate with
+ // the proxy.
+ if (challenge != null && (!isTunnel || isSchemeDisabled())) {
+ assert cnonceStr != null;
+ String auth = digestResponse(uri, digestMethod, challenge, cnonceStr);
+ try {
+ reqBuilder = reqBuilder.header(authorizationKey(authType), auth);
+ } catch (IllegalArgumentException x) {
+ throw x;
+ }
+ }
+
+ long start = System.nanoTime();
+ HttpRequest request = reqBuilder.build();
+ HttpResponse<Stream<String>> resp;
+ if (async) {
+ resp = client.sendAsync(request, BodyHandlers.ofLines()).join();
+ } else {
+ resp = client.send(request, BodyHandlers.ofLines());
+ }
+ System.out.println(resp);
+ assert challenge != null || resp.statusCode() == 401 || resp.statusCode() == 407
+ : "challenge=" + challenge + ", resp=" + resp + ", test=[" + test + "]";
+ if (resp.statusCode() == 401 || resp.statusCode() == 407) {
+ // This assert may need to be relaxed if our server happened to
+ // decide to close the tunnel connection, in which case we would
+ // receive 407 again...
+ assert challenge == null || !isTunnel || isSchemeDisabled()
+ : "No proxy auth should be required after establishing an SSL tunnel";
+
+ System.out.println("Received " + resp.statusCode() + " answering challenge...");
+ random.nextBytes(cnonce);
+ cnonceStr = new BigInteger(1, cnonce).toString(16);
+ System.out.println("Response headers: " + resp.headers());
+ Optional<String> authenticateOpt = resp.headers().firstValue(authenticateKey(authType));
+ String authenticate = authenticateOpt.orElseThrow(
+ () -> new RuntimeException(authenticateKey(authType) + ": not found"));
+ assert authenticate.startsWith("Digest ");
+ HeaderParser hp = new HeaderParser(authenticate.substring("Digest ".length()));
+ String qop = hp.findValue("qop");
+ String nonce = hp.findValue("nonce");
+ if (qop == null && nonce == null) {
+ throw new RuntimeException("QOP and NONCE not found");
+ }
+ challenge = DigestEchoServer.DigestResponse
+ .create(authenticate.substring("Digest ".length()));
+ String auth = digestResponse(uri, digestMethod, challenge, cnonceStr);
+ try {
+ request = HttpRequest.newBuilder(uri).version(clientVersion)
+ .POST(reqBody).header(authorizationKey(authType), auth).build();
+ } catch (IllegalArgumentException x) {
+ throw x;
+ }
+
+ if (async) {
+ resp = client.sendAsync(request, BodyHandlers.ofLines()).join();
+ } else {
+ resp = client.send(request, BodyHandlers.ofLines());
+ }
+ System.out.println(resp);
+ }
+ final List<String> respLines;
+ try {
+ if (isSchemeDisabled()) {
+ if (resp.statusCode() != 407) {
+ throw new RuntimeException("expected 407 not received");
+ }
+ System.out.println("Scheme disabled for [" + authType
+ + ", " + authScheme +
+ ", " + (useSSL ? "HTTP" : "HTTPS")
+ + "]: Received expected " + resp.statusCode());
+ continue;
+ } else {
+ assert resp.statusCode() == 200;
+ respLines = resp.body().collect(Collectors.toList());
+ }
+ } finally {
+ long stop = System.nanoTime();
+ synchronized (digestCount) {
+ long n = digestCount.getAndIncrement();
+ digests.set((digests.get() * n + (stop - start)) / (n + 1));
+ }
+ }
+ if (!lines.equals(respLines)) {
+ throw new RuntimeException("Unexpected response: " + respLines);
+ }
+ }
+ } finally {
+ }
+ System.out.println("OK");
+ }
+
+ // WARNING: This is not a full fledged implementation of DIGEST.
+ // It does contain bugs and inaccuracy.
+ static String digestResponse(URI uri, String method, DigestEchoServer.DigestResponse challenge, String cnonce)
+ throws NoSuchAlgorithmException {
+ int nc = NC.incrementAndGet();
+ DigestEchoServer.DigestResponse response1 = new DigestEchoServer.DigestResponse("earth",
+ "arthur", challenge.nonce, cnonce, String.valueOf(nc), uri.toASCIIString(),
+ challenge.algorithm, challenge.qop, challenge.opaque, null);
+ String response = DigestEchoServer.DigestResponse.computeDigest(true, method,
+ DigestEchoServer.AUTHENTICATOR.getPassword("arthur"), response1);
+ String auth = "Digest username=\"arthur\", realm=\"earth\""
+ + ", response=\"" + response + "\", uri=\""+uri.toASCIIString()+"\""
+ + ", qop=\"" + response1.qop + "\", cnonce=\"" + response1.cnonce
+ + "\", nc=\"" + nc + "\", nonce=\"" + response1.nonce + "\"";
+ if (response1.opaque != null) {
+ auth = auth + ", opaque=\"" + response1.opaque + "\"";
+ }
+ return auth;
+ }
+
+ static String authenticateKey(DigestEchoServer.HttpAuthType authType) {
+ switch (authType) {
+ case SERVER: return "www-authenticate";
+ case SERVER307: return "www-authenticate";
+ case PROXY: return "proxy-authenticate";
+ case PROXY305: return "proxy-authenticate";
+ default: throw new InternalError("authType: " + authType);
+ }
+ }
+
+ static String authorizationKey(DigestEchoServer.HttpAuthType authType) {
+ switch (authType) {
+ case SERVER: return "authorization";
+ case SERVER307: return "Authorization";
+ case PROXY: return "Proxy-Authorization";
+ case PROXY305: return "proxy-Authorization";
+ default: throw new InternalError("authType: " + authType);
+ }
+ }
+
+ static boolean isProxy(DigestEchoServer.HttpAuthType authType) {
+ switch (authType) {
+ case SERVER: return false;
+ case SERVER307: return false;
+ case PROXY: return true;
+ case PROXY305: return true;
+ default: throw new InternalError("authType: " + authType);
+ }
+ }
+}